diff options
author | Kim <kim.sidney@gmail.com> | 2021-10-07 16:52:56 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-10-07 16:52:56 +0200 |
commit | 2274dbf9047493a00a6f30346b38dacd8cfcf965 (patch) | |
tree | f431f5f6f1b2770c98ed9047e1cec9209e536366 | |
parent | 2acfffab8b98238e7d869673a858a4ae21651f0b (diff) | |
parent | adc7ef387d40e92bd7163ee6b401e99e554394a3 (diff) | |
download | vyos-1x-2274dbf9047493a00a6f30346b38dacd8cfcf965.tar.gz vyos-1x-2274dbf9047493a00a6f30346b38dacd8cfcf965.zip |
Merge branch 'current' into 2fa
343 files changed, 6770 insertions, 4294 deletions
diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..ea2cc59e4 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +data/templates/**/*.conf linguist-language=Jinja +*.tmpl linguist-language=Jinja +*.xml.i linguist-language=XML +*.xml.in linguist-language=XML diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 04ca4070d..61ee1d9ff 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,5 +1,5 @@ <!-- All PR should follow this template to allow a clean and transparent review --> -<!-- Text placed between these delimiters is considered a commend and is not rendered --> +<!-- Text placed between these delimiters is considered a comment and is not rendered --> ## Change Summary <!--- Provide a general summary of your changes in the Title above --> @@ -61,12 +61,7 @@ op_mode_definitions: $(op_xml_obj) # XXX: test if there are empty node.def files - this is not allowed as these # could mask help strings or mandatory priority statements - #find $(OP_TMPL_DIR) -name node.def -type f -empty -exec false {} + || sh -c 'echo "There are empty node.def files! Check your interface definitions." && exit 1' - -.PHONY: component_versions -.ONESHELL: -component_versions: interface_definitions - $(CURDIR)/scripts/build-component-versions $(BUILD_DIR)/interface-definitions $(DATA_DIR) + find $(OP_TMPL_DIR) -name node.def -type f -empty -exec false {} + || sh -c 'echo "There are empty node.def files! Check your interface definitions." && exit 1' .PHONY: vyshim vyshim: @@ -77,7 +72,7 @@ vyxdp: $(MAKE) -C $(XDP_DIR) .PHONY: all -all: clean interface_definitions op_mode_definitions component_versions vyshim +all: clean interface_definitions op_mode_definitions vyshim .PHONY: clean clean: @@ -89,7 +84,7 @@ clean: .PHONY: test test: - set -e; python3 -m compileall -q -x '/vmware-tools/scripts/' . + set -e; python3 -m compileall -q -x '/vmware-tools/scripts/, /ppp/' . PYTHONPATH=python/ python3 -m "nose" --with-xunit src --with-coverage --cover-erase --cover-xml --cover-package src/conf_mode,src/op_mode,src/completion,src/helpers,src/validators,src/tests --verbose .PHONY: sonar diff --git a/data/configd-include.json b/data/configd-include.json index 3b4e2925b..6893aaa86 100644 --- a/data/configd-include.json +++ b/data/configd-include.json @@ -6,7 +6,6 @@ "dhcpv6_relay.py", "dns_forwarding.py", "dynamic_dns.py", -"firewall_options.py", "host_name.py", "https.py", "igmp_proxy.py", @@ -69,5 +68,6 @@ "vpn_pptp.py", "vpn_sstp.py", "vrf.py", +"vrf_vni.py", "vrrp.py" ] diff --git a/data/templates/accel-ppp/l2tp.config.tmpl b/data/templates/accel-ppp/l2tp.config.tmpl index 070a966b7..44c96b935 100644 --- a/data/templates/accel-ppp/l2tp.config.tmpl +++ b/data/templates/accel-ppp/l2tp.config.tmpl @@ -150,3 +150,4 @@ vendor={{ radius_shaper_vendor }} [cli] tcp=127.0.0.1:2004 sessions-columns=ifname,username,calling-sid,ip,{{ ip6_column | join(',') }}{{ ',' if ip6_column }}rate-limit,type,comp,state,rx-bytes,tx-bytes,uptime + diff --git a/data/templates/accel-ppp/pptp.config.tmpl b/data/templates/accel-ppp/pptp.config.tmpl index 5a6cfe749..3cfc4a906 100644 --- a/data/templates/accel-ppp/pptp.config.tmpl +++ b/data/templates/accel-ppp/pptp.config.tmpl @@ -2,12 +2,13 @@ [modules] log_syslog pptp -ippool +shaper {% if auth_mode == 'local' %} chap-secrets {% elif auth_mode == 'radius' %} radius {% endif %} +ippool {% for proto in auth_proto %} {{proto}} {% endfor %} @@ -87,6 +88,10 @@ nas-ip-address={{ radius_nas_ip }} bind={{ radius_source_address }} {% endif %} {% endif %} +{# Both chap-secrets and radius block required the gw-ip-address #} +{% if gw_ip is defined and gw_ip is not none %} +gw-ip-address={{ gw_ip }} +{% endif %} [cli] tcp=127.0.0.1:2003 diff --git a/data/templates/dhcp-server/dhcpd.conf.tmpl b/data/templates/dhcp-server/dhcpd.conf.tmpl index f0bfa468c..233e2cc53 100644 --- a/data/templates/dhcp-server/dhcpd.conf.tmpl +++ b/data/templates/dhcp-server/dhcpd.conf.tmpl @@ -31,32 +31,24 @@ option wpad-url code 252 = text; {% endfor %} {% endif %} -{% if shared_network_name is defined and shared_network_name is not none %} -{% for network, network_config in shared_network_name.items() if network_config.disable is not defined %} -{% if network_config.subnet is defined and network_config.subnet is not none %} -{% for subnet, subnet_config in network_config.subnet.items() %} -{% if subnet_config.failover is defined and subnet_config.failover is defined and subnet_config.failover.name is defined and subnet_config.failover.name is not none %} -# Failover configuration for {{ subnet }} -failover peer "{{ subnet_config.failover.name }}" { -{% if subnet_config.failover.status == 'primary' %} +{% if failover is defined and failover is not none %} +# DHCP failover configuration +failover peer "{{ failover.name }}" { +{% if failover.status == 'primary' %} primary; mclt 1800; split 128; -{% elif subnet_config.failover.status == 'secondary' %} +{% elif failover.status == 'secondary' %} secondary; -{% endif %} - address {{ subnet_config.failover.local_address }}; +{% endif %} + address {{ failover.source_address }}; port 520; - peer address {{ subnet_config.failover.peer_address }}; + peer address {{ failover.remote }}; peer port 520; max-response-delay 30; max-unacked-updates 10; load balance max seconds 3; } -{% endif %} -{% endfor %} -{% endif %} -{% endfor %} {% endif %} {% if listen_address is defined and listen_address is not none %} @@ -74,6 +66,21 @@ shared-network {{ network | replace('_','-') }} { {% if network_config.authoritative is defined %} authoritative; {% endif %} +{% if network_config.name_server is defined and network_config.name_server is not none %} + option domain-name-servers {{ network_config.name_server | join(', ') }}; +{% endif %} +{% if network_config.domain_name is defined and network_config.domain_name is not none %} + option domain-name "{{ network_config.domain_name }}"; +{% endif %} +{% if network_config.domain_search is defined and network_config.domain_search is not none %} + option domain-search "{{ network_config.domain_search | join('", "') }}"; +{% endif %} +{% if network_config.ntp_server is defined and network_config.ntp_server is not none %} + option ntp-servers {{ network_config.ntp_server | join(', ') }}; +{% endif %} +{% if network_config.ping_check is defined %} + ping-check true; +{% endif %} {% if network_config.shared_network_parameters is defined and network_config.shared_network_parameters is not none %} # The following {{ network_config.shared_network_parameters | length }} line(s) # were added as shared-network-parameters in the CLI and have not been validated @@ -83,9 +90,15 @@ shared-network {{ network | replace('_','-') }} { {% endif %} {% if network_config.subnet is defined and network_config.subnet is not none %} {% for subnet, subnet_config in network_config.subnet.items() %} +{% if subnet_config.description is defined and subnet_config.description is not none %} + # {{ subnet_config.description }} +{% endif %} subnet {{ subnet | address_from_cidr }} netmask {{ subnet | netmask_from_cidr }} { -{% if subnet_config.dns_server is defined and subnet_config.dns_server is not none %} - option domain-name-servers {{ subnet_config.dns_server | join(', ') }}; +{% if subnet_config.name_server is defined and subnet_config.name_server is not none %} + option domain-name-servers {{ subnet_config.name_server | join(', ') }}; +{% endif %} +{% if subnet_config.domain_name is defined and subnet_config.domain_name is not none %} + option domain-name "{{ subnet_config.domain_name }}"; {% endif %} {% if subnet_config.domain_search is defined and subnet_config.domain_search is not none %} option domain-search "{{ subnet_config.domain_search | join('", "') }}"; @@ -110,9 +123,13 @@ shared-network {{ network | replace('_','-') }} { {% if subnet_config.default_router and subnet_config.default_router is not none %} {% set static_default_route = ', ' + '0.0.0.0/0' | isc_static_route(subnet_config.default_router) %} {% endif %} -{% if subnet_config.static_route.router is defined and subnet_config.static_route.router is not none and subnet_config.static_route.destination_subnet is defined and subnet_config.static_route.destination_subnet is not none %} - option rfc3442-static-route {{ subnet_config.static_route.destination_subnet | isc_static_route(subnet_config.static_route.router) }}{{ static_default_route }}; - option windows-static-route {{ subnet_config.static_route.destination_subnet | isc_static_route(subnet_config.static_route.router) }}; +{% if subnet_config.static_route is defined and subnet_config.static_route is not none %} +{% 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 defined %} @@ -124,9 +141,6 @@ shared-network {{ network | replace('_','-') }} { {% if subnet_config.server_identifier is defined and subnet_config.server_identifier is not none %} option dhcp-server-identifier {{ subnet_config.server_identifier }}; {% endif %} -{% if subnet_config.domain_name is defined and subnet_config.domain_name is not none %} - option domain-name "{{ subnet_config.domain_name }}"; -{% endif %} {% if subnet_config.subnet_parameters is defined and subnet_config.subnet_parameters is not none %} # The following {{ subnet_config.subnet_parameters | length }} line(s) were added as # subnet-parameters in the CLI and have not been validated!!! @@ -157,6 +171,9 @@ shared-network {{ network | replace('_','-') }} { default-lease-time {{ subnet_config.lease }}; max-lease-time {{ subnet_config.lease }}; {% endif %} +{% if network_config.ping_check is not defined and subnet_config.ping_check is defined %} + ping-check true; +{% endif %} {% if subnet_config.static_mapping is defined and subnet_config.static_mapping is not none %} {% for host, host_config in subnet_config.static_mapping.items() if host_config.disable is not defined %} host {{ host | replace('_','-') if host_decl_name is defined else network | replace('_','-') + '_' + host | replace('_','-') }} { @@ -174,22 +191,22 @@ shared-network {{ network | replace('_','-') }} { } {% endfor %} {% endif %} -{% if subnet_config.failover is defined and subnet_config.failover.name is defined and subnet_config.failover.name is not none %} +{% if subnet_config.range is defined and subnet_config.range is not none %} +{# pool configuration can only be used if there follows a range option #} pool { - failover peer "{{ subnet_config.failover.name }}"; +{% endif %} +{% if subnet_config.enable_failover is defined %} + failover peer "{{ failover.name }}"; deny dynamic bootp clients; +{% endif %} {% if subnet_config.range is defined and subnet_config.range is not none %} {% for range, range_options in subnet_config.range.items() %} range {{ range_options.start }} {{ range_options.stop }}; {% endfor %} {% endif %} +{% if subnet_config.range is defined and subnet_config.range is not none %} +{# pool configuration can only be used if there follows a range option #} } -{% else %} -{% if subnet_config.range is defined and subnet_config.range is not none %} -{% for range, range_options in subnet_config.range.items() %} - range {{ range_options.start }} {{ range_options.stop }}; -{% endfor %} -{% endif %} {% endif %} } {% endfor %} diff --git a/data/templates/dns-forwarding/recursor.conf.tmpl b/data/templates/dns-forwarding/recursor.conf.tmpl index 9e0ad5d17..d44f756e8 100644 --- a/data/templates/dns-forwarding/recursor.conf.tmpl +++ b/data/templates/dns-forwarding/recursor.conf.tmpl @@ -10,8 +10,7 @@ threads=1 allow-from={{ allow_from | join(',') }} log-common-errors=yes non-local-bind=yes -query-local-address={{ source_address_v4 | join(',') }} -query-local-address6={{ source_address_v6 | join(',') }} +query-local-address={{ source_address | join(',') }} lua-config-file=recursor.conf.lua # cache-size diff --git a/data/templates/frr/bfd.frr.tmpl b/data/templates/frr/bfdd.frr.tmpl index 16f8be92c..16f8be92c 100644 --- a/data/templates/frr/bfd.frr.tmpl +++ b/data/templates/frr/bfdd.frr.tmpl diff --git a/data/templates/frr/bgpd.frr.tmpl b/data/templates/frr/bgpd.frr.tmpl index aa297876b..a35930c93 100644 --- a/data/templates/frr/bgpd.frr.tmpl +++ b/data/templates/frr/bgpd.frr.tmpl @@ -46,8 +46,9 @@ neighbor {{ neighbor }} {{ graceful_restart }} {% endif %} {% if config.local_as is defined and config.local_as is not none %} -{% for local_asn in config.local_as %} - neighbor {{ neighbor }} local-as {{ local_asn }} {{ 'no-prepend' if config.local_as[local_asn].no_prepend is defined }} +{% for local_as, local_as_config in config.local_as.items() %} +{# There can be only one local-as value, this is checked in the Python code #} + neighbor {{ neighbor }} local-as {{ local_as }} {{ 'no-prepend' if local_as_config.no_prepend is defined }} {{ 'replace-as' if local_as_config.no_prepend is defined and local_as_config.no_prepend.replace_as is defined }} {% endfor %} {% endif %} {% if config.override_capability is defined %} @@ -89,6 +90,9 @@ {% if config.interface.peer_group is defined and config.interface.peer_group is not none %} neighbor {{ neighbor }} interface peer-group {{ config.interface.peer_group }} {% endif %} +{% if config.interface.source_interface is defined and config.interface.source_interface is not none %} + neighbor {{ neighbor }} interface {{ config.interface.source_interface }} +{% endif %} {% if config.interface.v6only is defined and config.interface.v6only is not none %} {% if config.interface.v6only.peer_group is defined and config.interface.v6only.peer_group is not none %} neighbor {{ neighbor }} interface v6only peer-group {{ config.interface.v6only.peer_group }} @@ -226,10 +230,8 @@ router bgp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is defined and vrf is not none {% else %} no bgp ebgp-requires-policy {% endif %} -{% if parameters is defined and parameters.default is defined and parameters.default.no_ipv4_unicast is defined %} {# Option must be set before any neighbor - see https://phabricator.vyos.net/T3463 #} no bgp default ipv4-unicast -{% endif %} {# Workaround for T2100 until we have decided about a migration script #} no bgp network import-check {% if address_family is defined and address_family is not none %} @@ -257,6 +259,9 @@ router bgp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is defined and vrf is not none address-family ipv6 flowspec {% elif afi == 'l2vpn_evpn' %} address-family l2vpn evpn +{% if afi_config.rd is defined and afi_config.rd is not none %} + rd {{ afi_config.rd }} +{% endif %} {% endif %} {% if afi_config.aggregate_address is defined and afi_config.aggregate_address is not none %} {% for ip in afi_config.aggregate_address %} @@ -294,23 +299,39 @@ router bgp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is defined and vrf is not none {% if afi_config.advertise is defined and afi_config.advertise is not none %} {% for adv_afi, adv_afi_config in afi_config.advertise.items() %} {% if adv_afi_config.unicast is defined and adv_afi_config.unicast is not none %} - advertise {{ adv_afi }} unicast {{ 'route-map ' ~ adv_afi_config.unicast.route_map if adv_afi_config.unicast.route_map is defined }} + advertise {{ adv_afi }} unicast {{ 'route-map ' ~ adv_afi_config.unicast.route_map if adv_afi_config.unicast.route_map is defined }} {% endif %} {% endfor %} {% endif %} {% if afi_config.distance is defined and afi_config.distance is not none %} {% if afi_config.distance is defined and afi_config.distance.external is defined and afi_config.distance.internal is defined and afi_config.distance.local is defined %} - distance bgp {{ afi_config.distance.external }} {{ afi_config.distance.internal }} {{ afi_config.distance.local }} + distance bgp {{ afi_config.distance.external }} {{ afi_config.distance.internal }} {{ afi_config.distance.local }} {% endif %} {% if afi_config.distance.prefix is defined and afi_config.distance.prefix is not none %} {% for prefix in afi_config.distance.prefix %} - distance {{ afi_config.distance.prefix[prefix].distance }} {{ prefix }} + distance {{ afi_config.distance.prefix[prefix].distance }} {{ prefix }} +{% endfor %} +{% endif %} +{% endif %} +{% if afi_config.export is defined and afi_config.export.vpn is defined %} + export vpn +{% endif %} +{% if afi_config.import is defined and afi_config.import is not none %} +{% if afi_config.import.vpn is defined %} + import vpn +{% endif %} +{% if afi_config.import.vrf is defined and afi_config.import.vrf is not none %} +{% for vrf in afi_config.import.vrf %} + import vrf {{ vrf }} {% endfor %} {% endif %} {% endif %} +{% if afi_config.label is defined and afi_config.label.vpn is defined and afi_config.label.vpn.export is defined and afi_config.label.vpn.export is not none %} + label vpn export {{ afi_config.label.vpn.export }} +{% endif %} {% if afi_config.local_install is defined and afi_config.local_install is not none %} {% for interface in afi_config.local_install.interface %} - local-install {{ interface }} + local-install {{ interface }} {% endfor %} {% endif %} {% if afi_config.advertise_all_vni is defined %} @@ -326,26 +347,47 @@ router bgp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is defined and vrf is not none advertise-svi-ip {% endif %} {% if afi_config.rt_auto_derive is defined %} - autort rfc8365-compatible + autort rfc8365-compatible {% endif %} {% if afi_config.flooding is defined and afi_config.flooding.disable is defined %} - flooding disable + flooding disable {% endif %} {% if afi_config.flooding is defined and afi_config.flooding.head_end_replication is defined %} - flooding head-end-replication + flooding head-end-replication {% endif %} -{% if afi_config.rd is defined and afi_config.rd is not none %} - rd {{ afi_config.rd }} +{% if afi_config.rd is defined and afi_config.rd.vpn is defined and afi_config.rd.vpn.export is defined %} + rd vpn export {{ afi_config.rd.vpn.export }} {% endif %} {% if afi_config.route_target is defined and afi_config.route_target is not none %} +{% if afi_config.route_target.vpn is defined and afi_config.route_target.vpn is not none %} +{% if afi_config.route_target.vpn.both is defined and afi_config.route_target.vpn.both is not none %} + route-target vpn both {{ afi_config.route_target.vpn.both }} +{% else %} +{% if afi_config.route_target.vpn.export is defined and afi_config.route_target.vpn.export is not none %} + route-target vpn export {{ afi_config.route_target.vpn.export }} +{% endif %} +{% if afi_config.route_target.vpn.import is defined and afi_config.route_target.vpn.import is not none %} + route-target vpn import {{ afi_config.route_target.vpn.import }} +{% endif %} +{% endif %} +{% endif %} {% if afi_config.route_target.both is defined and afi_config.route_target.both is not none %} - route-target both {{ afi_config.route_target.both }} + route-target both {{ afi_config.route_target.both }} +{% else %} +{% if afi_config.route_target.export is defined and afi_config.route_target.export is not none %} + route-target export {{ afi_config.route_target.export }} +{% endif %} +{% if afi_config.route_target.import is defined and afi_config.route_target.import is not none %} + route-target import {{ afi_config.route_target.import }} +{% endif %} {% endif %} -{% if afi_config.route_target.export is defined and afi_config.route_target.export is not none %} - route-target export {{ afi_config.route_target.export }} +{% endif %} +{% if afi_config.route_map is defined and afi_config.route_map.vpn is defined and afi_config.route_map.vpn is not none %} +{% if afi_config.route_map.vpn.export is defined and afi_config.route_map.vpn.export is not none %} + route-map vpn export {{ afi_config.route_map.vpn.export }} {% endif %} -{% if afi_config.route_target.import is defined and afi_config.route_target.import is not none %} - route-target import {{ afi_config.route_target.import }} +{% if afi_config.route_map.vpn.import is defined and afi_config.route_map.vpn.import is not none %} + route-map vpn import {{ afi_config.route_map.vpn.import }} {% endif %} {% endif %} {% if afi_config.vni is defined and afi_config.vni is not none %} diff --git a/data/templates/frr/igmp.frr.tmpl b/data/templates/frr/igmp.frr.tmpl index cdb7ee6cc..49b5aeaa5 100644 --- a/data/templates/frr/igmp.frr.tmpl +++ b/data/templates/frr/igmp.frr.tmpl @@ -1,41 +1,41 @@ ! {% for iface in old_ifaces %} interface {{ iface }} -{% for group in old_ifaces[iface].gr_join %} -{% if old_ifaces[iface].gr_join[group] %} -{% for source in old_ifaces[iface].gr_join[group] %} -no ip igmp join {{ group }} {{ source }} -{% endfor %} -{% else %} -no ip igmp join {{ group }} -{% endif %} -{% endfor %} -no ip igmp +{% for group in old_ifaces[iface].gr_join %} +{% if old_ifaces[iface].gr_join[group] %} +{% for source in old_ifaces[iface].gr_join[group] %} + no ip igmp join {{ group }} {{ source }} +{% endfor %} +{% else %} + no ip igmp join {{ group }} +{% endif %} +{% endfor %} + no ip igmp ! {% endfor %} {% for iface in ifaces %} interface {{ iface }} -{% if ifaces[iface].version %} -ip igmp version {{ ifaces[iface].version }} -{% else %} +{% if ifaces[iface].version %} + ip igmp version {{ ifaces[iface].version }} +{% else %} {# IGMP default version 3 #} -ip igmp -{% endif %} -{% if ifaces[iface].query_interval %} -ip igmp query-interval {{ ifaces[iface].query_interval }} -{% endif %} -{% if ifaces[iface].query_max_resp_time %} -ip igmp query-max-response-time {{ ifaces[iface].query_max_resp_time }} -{% endif %} -{% for group in ifaces[iface].gr_join %} -{% if ifaces[iface].gr_join[group] %} -{% for source in ifaces[iface].gr_join[group] %} -ip igmp join {{ group }} {{ source }} -{% endfor %} -{% else %} -ip igmp join {{ group }} -{% endif %} -{% endfor %} + ip igmp +{% endif %} +{% if ifaces[iface].query_interval %} + ip igmp query-interval {{ ifaces[iface].query_interval }} +{% endif %} +{% if ifaces[iface].query_max_resp_time %} + ip igmp query-max-response-time {{ ifaces[iface].query_max_resp_time }} +{% endif %} +{% for group in ifaces[iface].gr_join %} +{% if ifaces[iface].gr_join[group] %} +{% for source in ifaces[iface].gr_join[group] %} + ip igmp join {{ group }} {{ source }} +{% endfor %} +{% else %} + ip igmp join {{ group }} +{% endif %} +{% endfor %} ! {% endfor %} ! diff --git a/data/templates/frr/isisd.frr.tmpl b/data/templates/frr/isisd.frr.tmpl index 6cfa076d0..51ac40060 100644 --- a/data/templates/frr/isisd.frr.tmpl +++ b/data/templates/frr/isisd.frr.tmpl @@ -100,7 +100,7 @@ router isis VyOS {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} {% endif %} {% endif %} {% if spf_delay_ietf is defined and spf_delay_ietf.init_delay is defined and spf_delay_ietf.init_delay is not none %} - spf-delay-ietf init-delay {{ spf_delay_ietf.init_delay }} + spf-delay-ietf init-delay {{ spf_delay_ietf.init_delay }} short-delay {{ spf_delay_ietf.short_delay }} long-delay {{ spf_delay_ietf.long_delay }} holddown {{ spf_delay_ietf.holddown }} time-to-learn {{ spf_delay_ietf.time_to_learn }} {% endif %} {% if area_password is defined and area_password is not none %} {% if area_password.md5 is defined and area_password.md5 is not none %} diff --git a/data/templates/frr/ospfv3.frr.tmpl b/data/templates/frr/ospf6d.frr.tmpl index 0026c0d2c..a8c53738f 100644 --- a/data/templates/frr/ospfv3.frr.tmpl +++ b/data/templates/frr/ospf6d.frr.tmpl @@ -76,6 +76,9 @@ router ospf6 distance ospf6 {{ 'intra-area ' + distance.ospfv3.intra_area if distance.ospfv3.intra_area is defined }} {{ 'inter-area ' + distance.ospfv3.inter_area if distance.ospfv3.inter_area is defined }} {{ 'external ' + distance.ospfv3.external if distance.ospfv3.external is defined }} {% endif %} {% endif %} +{% if log_adjacency_changes is defined %} + log-adjacency-changes {{ "detail" if log_adjacency_changes.detail is defined }} +{% endif %} {% if parameters is defined and parameters is not none %} {% if parameters.router_id is defined and parameters.router_id is not none %} ospf6 router-id {{ parameters.router_id }} diff --git a/data/templates/frr/ospf.frr.tmpl b/data/templates/frr/ospfd.frr.tmpl index 36aa699a9..90a6bbd56 100644 --- a/data/templates/frr/ospf.frr.tmpl +++ b/data/templates/frr/ospfd.frr.tmpl @@ -14,6 +14,12 @@ interface {{ iface }} {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} {% endif %} {% endif %} {% endif %} +{% if iface_config.area is defined and iface_config.area is not none %} + ip ospf area {{ iface_config.area }} +{% endif %} +{% if iface_config.bandwidth is defined and iface_config.bandwidth is not none %} + bandwidth {{ iface_config.bandwidth }} +{% endif %} {% if iface_config.cost is defined and iface_config.cost is not none %} ip ospf cost {{ iface_config.cost }} {% endif %} @@ -43,9 +49,6 @@ interface {{ iface }} {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} {% if iface_config.network is defined and iface_config.network is not none %} ip ospf network {{ iface_config.network }} {% endif %} -{% if iface_config.bandwidth is defined and iface_config.bandwidth is not none %} - bandwidth {{ iface_config.bandwidth }} -{% endif %} ! {% endfor %} {% endif %} @@ -155,18 +158,28 @@ router ospf {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} ospf router-id {{ parameters.router_id }} {% endif %} {% endif %} -{% for interface in passive_interface if passive_interface is defined %} +{% if passive_interface is defined and passive_interface is not none %} +{% for interface in passive_interface %} passive-interface {{ interface }} -{% endfor %} -{% for interface in passive_interface_exclude if passive_interface_exclude is defined %} -{% if interface.startswith('vlink') %} +{% endfor %} +{% endif %} +{% if passive_interface_exclude is defined and passive_interface_exclude is not none %} +{% for interface in passive_interface_exclude if passive_interface_exclude is defined %} +{% if interface.startswith('vlink') %} {% set interface = interface.upper() %} -{% endif %} +{% endif %} no passive-interface {{ interface }} -{% endfor %} +{% endfor %} +{% endif %} {% if redistribute is defined and redistribute is not none %} -{% for protocol, options in redistribute.items() %} - redistribute {{ protocol }} {{ 'metric ' + options.metric if options.metric is defined }} {{ 'metric-type ' + options.metric_type if options.metric_type is defined }} {{ 'route-map ' + options.route_map if options.route_map is defined }} +{% for protocol, protocols_options in redistribute.items() %} +{% if protocol == 'table' %} +{% for table, table_options in protocols_options.items() %} + redistribute {{ protocol }} {{ table }} {{ 'metric ' + table_options.metric if table_options.metric is defined }} {{ 'metric-type ' + table_options.metric_type if table_options.metric_type is defined }} {{ 'route-map ' + table_options.route_map if table_options.route_map is defined }} +{% endfor %} +{% else %} + redistribute {{ protocol }} {{ 'metric ' + protocols_options.metric if protocols_options.metric is defined }} {{ 'metric-type ' + protocols_options.metric_type if protocols_options.metric_type is defined }} {{ 'route-map ' + protocols_options.route_map if protocols_options.route_map is defined }} +{% endif %} {% endfor %} {% endif %} {% if refresh is defined and refresh.timers is defined and refresh.timers is not none %} diff --git a/data/templates/frr/policy.frr.tmpl b/data/templates/frr/policy.frr.tmpl index b5649b44e..51adc1902 100644 --- a/data/templates/frr/policy.frr.tmpl +++ b/data/templates/frr/policy.frr.tmpl @@ -165,6 +165,18 @@ route-map {{ route_map }} {{ rule_config.action }} {{ rule }} {% if rule_config.match.extcommunity is defined and rule_config.match.extcommunity is not none %} match extcommunity {{ rule_config.match.extcommunity }} {% endif %} +{% if rule_config.match.evpn is defined and rule_config.match.evpn.default_route is defined %} + match evpn default-route +{% endif %} +{% if rule_config.match.evpn is defined and rule_config.match.evpn.rd is defined and rule_config.match.evpn.rd is not none %} + match evpn rd {{ rule_config.match.evpn.rd }} +{% endif %} +{% if rule_config.match.evpn is defined and rule_config.match.evpn.route_type is defined and rule_config.match.evpn.route_type is not none %} + match evpn route-type {{ rule_config.match.evpn.route_type }} +{% endif %} +{% if rule_config.match.evpn is defined and rule_config.match.evpn.vni is defined and rule_config.match.evpn.vni is not none %} + match evpn vni {{ rule_config.match.evpn.vni }} +{% endif %} {% if rule_config.match.interface is defined and rule_config.match.interface is not none %} match interface {{ rule_config.match.interface }} {% endif %} @@ -271,6 +283,9 @@ route-map {{ route_map }} {{ rule_config.action }} {{ rule }} {% if rule_config.set.large_community is defined and rule_config.set.large_community is not none %} set large-community {{ rule_config.set.large_community }} {% endif %} +{% if rule_config.set.large_comm_list_delete is defined and rule_config.set.large_comm_list_delete is not none %} + set large-comm-list {{ rule_config.set.large_comm_list_delete }} delete +{% endif %} {% if rule_config.set.local_preference is defined and rule_config.set.local_preference is not none %} set local-preference {{ rule_config.set.local_preference }} {% endif %} diff --git a/data/templates/frr/rip.frr.tmpl b/data/templates/frr/ripd.frr.tmpl index cabc236f0..cabc236f0 100644 --- a/data/templates/frr/rip.frr.tmpl +++ b/data/templates/frr/ripd.frr.tmpl diff --git a/data/templates/frr/ripng.frr.tmpl b/data/templates/frr/ripngd.frr.tmpl index 25df15121..25df15121 100644 --- a/data/templates/frr/ripng.frr.tmpl +++ b/data/templates/frr/ripngd.frr.tmpl diff --git a/data/templates/frr/static_routes_macro.j2 b/data/templates/frr/static_routes_macro.j2 index f10b58047..3b432b49b 100644 --- a/data/templates/frr/static_routes_macro.j2 +++ b/data/templates/frr/static_routes_macro.j2 @@ -5,7 +5,7 @@ {% if prefix_config.dhcp_interface is defined and prefix_config.dhcp_interface is not none %} {% set next_hop = prefix_config.dhcp_interface | get_dhcp_router %} {% if next_hop is defined and next_hop is not none %} -{{ ip_ipv6 }} route {{ prefix }} {{ next_hop }} +{{ ip_ipv6 }} route {{ prefix }} {{ next_hop }} {{ prefix_config.dhcp_interface }} {% endif %} {% endif %} {% if prefix_config.interface is defined and prefix_config.interface is not none %} diff --git a/data/templates/frr/static.frr.tmpl b/data/templates/frr/staticd.frr.tmpl index db59a44c2..db59a44c2 100644 --- a/data/templates/frr/static.frr.tmpl +++ b/data/templates/frr/staticd.frr.tmpl diff --git a/data/templates/frr/vrf-vni.frr.tmpl b/data/templates/frr/vrf-vni.frr.tmpl new file mode 100644 index 000000000..51d4ede1b --- /dev/null +++ b/data/templates/frr/vrf-vni.frr.tmpl @@ -0,0 +1,7 @@ +{% if vrf is defined and vrf is not none %} +vrf {{ vrf }} +{% if vni is defined and vni is not none %} + vni {{ vni }} +{% endif %} + exit-vrf +{% endif %} diff --git a/data/templates/frr/vrf.frr.tmpl b/data/templates/frr/vrf.frr.tmpl deleted file mode 100644 index 299c9719e..000000000 --- a/data/templates/frr/vrf.frr.tmpl +++ /dev/null @@ -1,9 +0,0 @@ -{% if name is defined and name is not none %} -{% for vrf, vrf_config in name.items() %} -vrf {{ vrf }} -{% if vrf_config.vni is defined and vrf_config.vni is not none %} - vni {{ vrf_config.vni }} -{% endif %} - exit-vrf -{% endfor %} -{% endif %} diff --git a/data/templates/https/nginx.default.tmpl b/data/templates/https/nginx.default.tmpl index b40ddcc74..9d73baeee 100644 --- a/data/templates/https/nginx.default.tmpl +++ b/data/templates/https/nginx.default.tmpl @@ -17,7 +17,7 @@ server { listen {{ server.port }} ssl; listen [::]:{{ server.port }} ssl; {% else %} - listen {{ server.address }}:{{ server.port }} ssl; + listen {{ server.address | bracketize_ipv6 }}:{{ server.port }} ssl; {% endif %} {% for name in server.name %} @@ -39,6 +39,7 @@ server { # include snippets/snakeoil.conf; {% endif %} + ssl_protocols TLSv1.2 TLSv1.3; # proxy settings for HTTP API, if enabled; 503, if not location ~ /(retrieve|configure|config-file|image|generate|show|docs|openapi.json|redoc|graphql) { diff --git a/data/templates/ipsec/swanctl/l2tp.tmpl b/data/templates/ipsec/swanctl/l2tp.tmpl index 2df5c2a4d..4cd1b4af3 100644 --- a/data/templates/ipsec/swanctl/l2tp.tmpl +++ b/data/templates/ipsec/swanctl/l2tp.tmpl @@ -20,7 +20,7 @@ children { l2tp_remote_access_esp { mode = transport - esp_proposals = {{ l2tp_esp | get_esp_ike_cipher | join(',') if l2tp_esp else l2tp_esp_default }} + esp_proposals = {{ l2tp_esp | get_esp_ike_cipher(l2tp_ike) | join(',') if l2tp_esp else l2tp_esp_default }} life_time = {{ l2tp_esp.lifetime if l2tp_esp else l2tp.lifetime }}s local_ts = dynamic[/1701] remote_ts = dynamic diff --git a/data/templates/ipsec/swanctl/peer.tmpl b/data/templates/ipsec/swanctl/peer.tmpl index dd29ea7d4..8c3776bf1 100644 --- a/data/templates/ipsec/swanctl/peer.tmpl +++ b/data/templates/ipsec/swanctl/peer.tmpl @@ -17,10 +17,10 @@ {% if ike.key_exchange is defined and ike.key_exchange == "ikev1" and ike.mode is defined and ike.mode == "aggressive" %} aggressive = yes {% endif %} + rekey_time = {{ ike.lifetime }}s mobike = {{ "yes" if ike.mobike is not defined or ike.mobike == "enable" else "no" }} {% if peer[0:1] == '@' %} keyingtries = 0 - rekey_time = 0 reauth_time = 0 {% elif peer_conf.connection_type is not defined or peer_conf.connection_type == 'initiate' %} keyingtries = 0 @@ -31,7 +31,7 @@ encap = yes {% endif %} local { -{% if peer_conf.authentication is defined and peer_conf.authentication.id is defined and peer_conf.authentication.use_x509_id is not defined %} +{% if peer_conf.authentication is defined and peer_conf.authentication.id is defined and peer_conf.authentication.id is not none %} id = "{{ peer_conf.authentication.id }}" {% endif %} auth = {{ 'psk' if peer_conf.authentication.mode == 'pre-shared-secret' else 'pubkey' }} @@ -42,9 +42,9 @@ {% endif %} } remote { -{% if peer_conf.authentication.remote_id is defined %} +{% if peer_conf.authentication is defined and peer_conf.authentication.remote_id is defined and peer_conf.authentication.remote_id is not none %} id = "{{ peer_conf.authentication.remote_id }}" -{% elif peer[0:1] == '@' %} +{% else %} id = "{{ peer }}" {% endif %} auth = {{ 'psk' if peer_conf.authentication.mode == 'pre-shared-secret' else 'pubkey' }} @@ -56,12 +56,16 @@ {% if peer_conf.vti is defined and peer_conf.vti.bind is defined and peer_conf.tunnel is not defined %} {% set vti_esp = esp_group[ peer_conf.vti.esp_group ] if peer_conf.vti.esp_group is defined else esp_group[ peer_conf.default_esp_group ] %} peer_{{ name }}_vti { - esp_proposals = {{ vti_esp | get_esp_ike_cipher | join(',') }} + esp_proposals = {{ vti_esp | get_esp_ike_cipher(ike) | join(',') }} + life_time = {{ vti_esp.lifetime }}s local_ts = 0.0.0.0/0,::/0 remote_ts = 0.0.0.0/0,::/0 - updown = "/etc/ipsec.d/vti-up-down {{ peer_conf.vti.bind }} {{ peer_conf.dhcp_interface if peer_conf.dhcp_interface is defined else 'no' }}" - if_id_in = {{ peer_conf.vti.bind | replace('vti', '') }} - if_id_out = {{ peer_conf.vti.bind | replace('vti', '') }} + updown = "/etc/ipsec.d/vti-up-down {{ peer_conf.vti.bind }}" + {# The key defaults to 0 and will match any policies which similarly do not have a lookup key configuration. #} + {# Thus we simply shift the key by one to also support a vti0 interface #} +{% set if_id = peer_conf.vti.bind | replace('vti', '') | int +1 %} + if_id_in = {{ if_id }} + if_id_out = {{ if_id }} ipcomp = {{ 'yes' if vti_esp.compression is defined and vti_esp.compression == 'enable' else 'no' }} mode = {{ vti_esp.mode }} {% if peer[0:1] == '@' %} @@ -86,7 +90,8 @@ {% set remote_port = tunnel_conf.remote.port if tunnel_conf.remote is defined and tunnel_conf.remote.port is defined else '' %} {% set remote_suffix = '[{0}/{1}]'.format(proto, remote_port) if proto or remote_port else '' %} peer_{{ name }}_tunnel_{{ tunnel_id }} { - esp_proposals = {{ tunnel_esp | get_esp_ike_cipher | join(',') }} + esp_proposals = {{ tunnel_esp | get_esp_ike_cipher(ike) | join(',') }} + life_time = {{ tunnel_esp.lifetime }}s {% if tunnel_esp.mode is not defined or tunnel_esp.mode == 'tunnel' %} {% if tunnel_conf.local is defined and tunnel_conf.local.prefix is defined %} {% set local_prefix = tunnel_conf.local.prefix if 'any' not in tunnel_conf.local.prefix else ['0.0.0.0/0', '::/0'] %} @@ -114,9 +119,12 @@ dpd_action = {{ dpd_translate[ike.dead_peer_detection.action] }} {% endif %} {% if peer_conf.vti is defined and peer_conf.vti.bind is defined %} - updown = "/etc/ipsec.d/vti-up-down {{ peer_conf.vti.bind }} {{ peer_conf.dhcp_interface if peer_conf.dhcp_interface is defined else 'no' }}" - if_id_in = {{ peer_conf.vti.bind | replace('vti', '') }} - if_id_out = {{ peer_conf.vti.bind | replace('vti', '') }} + updown = "/etc/ipsec.d/vti-up-down {{ peer_conf.vti.bind }}" + {# The key defaults to 0 and will match any policies which similarly do not have a lookup key configuration. #} + {# Thus we simply shift the key by one to also support a vti0 interface #} +{% set if_id = peer_conf.vti.bind | replace('vti', '') | int +1 %} + if_id_in = {{ if_id }} + if_id_out = {{ if_id }} {% endif %} } {% if tunnel_conf.passthrough is defined and tunnel_conf.passthrough %} diff --git a/data/templates/ipsec/swanctl/profile.tmpl b/data/templates/ipsec/swanctl/profile.tmpl index 0a7268405..948dd8f87 100644 --- a/data/templates/ipsec/swanctl/profile.tmpl +++ b/data/templates/ipsec/swanctl/profile.tmpl @@ -7,7 +7,7 @@ dmvpn-{{ name }}-{{ interface }} { proposals = {{ ike_group[profile_conf.ike_group] | get_esp_ike_cipher | join(',') }} version = {{ ike.key_exchange[4:] if ike is defined and ike.key_exchange is defined else "0" }} - rekey_time = {{ ike.lifetime }}s + life_time = {{ ike.lifetime }}s keyingtries = 0 {% if profile_conf.authentication is defined and profile_conf.authentication.mode is defined and profile_conf.authentication.mode == 'pre-shared-secret' %} local { @@ -19,7 +19,7 @@ {% endif %} children { dmvpn { - esp_proposals = {{ esp | get_esp_ike_cipher | join(',') }} + esp_proposals = {{ esp | get_esp_ike_cipher(ike) | join(',') }} rekey_time = {{ esp.lifetime }}s rand_time = 540s local_ts = dynamic[gre] diff --git a/data/templates/ipsec/swanctl/remote_access.tmpl b/data/templates/ipsec/swanctl/remote_access.tmpl index 456842488..6354c60b1 100644 --- a/data/templates/ipsec/swanctl/remote_access.tmpl +++ b/data/templates/ipsec/swanctl/remote_access.tmpl @@ -10,7 +10,9 @@ send_certreq = no rekey_time = {{ ike.lifetime }}s keyingtries = 0 +{% if rw_conf.unique is defined and rw_conf.unique is not none %} unique = {{ rw_conf.unique }} +{% endif %} {% if rw_conf.pool is defined and rw_conf.pool is not none %} pools = {{ rw_conf.pool | join(',') }} {% endif %} @@ -33,10 +35,11 @@ } children { ikev2-vpn { - esp_proposals = {{ esp | get_esp_ike_cipher | join(',') }} + esp_proposals = {{ esp | get_esp_ike_cipher(ike) | join(',') }} rekey_time = {{ esp.lifetime }}s rand_time = 540s dpd_action = clear + inactivity = {{ rw_conf.timeout }} {% set local_prefix = rw_conf.local.prefix if rw_conf.local is defined and rw_conf.local.prefix is defined else ['0.0.0.0/0', '::/0'] %} {% set local_port = rw_conf.local.port if rw_conf.local is defined and rw_conf.local.port is defined else '' %} {% set local_suffix = '[%any/{1}]'.format(local_port) if local_port else '' %} diff --git a/data/templates/proxy-ndp/ndppd.conf.tmpl b/data/templates/ndppd/ndppd.conf.tmpl index ccd1d37ad..502dab5b8 100644 --- a/data/templates/proxy-ndp/ndppd.conf.tmpl +++ b/data/templates/ndppd/ndppd.conf.tmpl @@ -6,10 +6,10 @@ # interface. # # For some services, such as nat66, because it runs -# stateless, it needs to rely on NDP Proxy to respond +# stateless, it needs to rely on NDP Proxy to respond # to NDP requests. # -# When using nat66 source rules, NDP Proxy needs +# When using nat66 source rules, NDP Proxy needs # to be enabled # ######################################################## @@ -21,7 +21,7 @@ {% if config.outbound_interface not in global.ndppd_interfaces %} {% set global.ndppd_interfaces = global.ndppd_interfaces + [config.outbound_interface] %} {% endif %} -{% if config.translation.address is defined and config.translation.address | is_ip_network %} +{% if config.translation is defined and config.translation.address is defined and config.translation.address | is_ip_network %} {% set global.ndppd_prefixs = global.ndppd_prefixs + [{'interface':config.outbound_interface,'rule':config.translation.address}] %} {% endif %} {% endif %} @@ -41,4 +41,4 @@ proxy {{ interface }} { {% endif %} {% endfor %} } -{% endfor %} +{% endfor %} diff --git a/data/templates/openvpn/server.conf.tmpl b/data/templates/openvpn/server.conf.tmpl index 5c78d998e..bc2790965 100644 --- a/data/templates/openvpn/server.conf.tmpl +++ b/data/templates/openvpn/server.conf.tmpl @@ -72,6 +72,16 @@ topology {{ server.topology }} {% 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 defined and server.push_route is not none %} +{% 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 defined %} {{ subnet | first_host_address }} {{ route_config.metric }}{% endif %}" +{% elif route | is_ipv6 %} +push "route-ipv6 {{ route }}" +{% 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 defined and server.topology == 'net30' %} @@ -104,15 +114,6 @@ management /run/openvpn/openvpn-mgmt-intf unix ccd-exclusive {% endif %} -{% if server.push_route is defined and server.push_route is not none %} -{% for route in server.push_route %} -{% if route | is_ipv4 %} -push "route {{ route | address_from_cidr }} {{ route | netmask_from_cidr }}" -{% elif route | is_ipv6 %} -push "route-ipv6 {{ route }}" -{% endif %} -{% endfor %} -{% endif %} {% if server.name_server is defined and server.name_server is not none %} {% for nameserver in server.name_server %} {% if nameserver | is_ipv4 %} @@ -181,6 +182,8 @@ tls-version-min {{ tls.tls_version_min }} {% endif %} {% if tls.dh_params is defined and tls.dh_params is not none %} dh /run/openvpn/{{ ifname }}_dh.pem +{% elif mode == 'server' and tls.private_key is defined %} +dh none {% endif %} {% if tls.auth_key is defined and tls.auth_key is not none %} {% if mode == 'client' %} diff --git a/data/templates/pppoe/ipv6-up.script.tmpl b/data/templates/pppoe/ipv6-up.script.tmpl index 7e1bc33b4..da73cb4d5 100644 --- a/data/templates/pppoe/ipv6-up.script.tmpl +++ b/data/templates/pppoe/ipv6-up.script.tmpl @@ -7,43 +7,6 @@ if [ "$6" != "{{ ifname }}" ]; then exit fi -{% if ipv6 is defined and ipv6.address is defined and ipv6.address.autoconf is defined %} -# add some info to syslog -DIALER_PID=$(cat /var/run/{{ ifname }}.pid) -logger -t pppd[$DIALER_PID] "executing $0" -logger -t pppd[$DIALER_PID] "configuring interface {{ ifname }} via {{ source_interface }}" - -# Configure interface-specific Host/Router behaviour. -# Note: It is recommended to have the same setting on all interfaces; mixed -# router/host scenarios are rather uncommon. Possible values are: -# -# 0 Forwarding disabled -# 1 Forwarding enabled -# -echo 1 > /proc/sys/net/ipv6/conf/{{ ifname }}/forwarding - -# Accept Router Advertisements; autoconfigure using them. -# -# It also determines whether or not to transmit Router -# Solicitations. If and only if the functional setting is to -# accept Router Advertisements, Router Solicitations will be -# transmitted. Possible values are: -# -# 0 Do not accept Router Advertisements. -# 1 Accept Router Advertisements if forwarding is disabled. -# 2 Overrule forwarding behaviour. Accept Router Advertisements -# even if forwarding is enabled. -# -echo 2 > /proc/sys/net/ipv6/conf/{{ ifname }}/accept_ra - -# Autoconfigure addresses using Prefix Information in Router Advertisements. -echo 1 > /proc/sys/net/ipv6/conf/{{ ifname }}/autoconf -{% endif %} - -{% if dhcpv6_options is defined and dhcpv6_options.pd is defined %} -# Start wide dhcpv6 client -systemctl restart dhcp6c@{{ ifname }}.service -{% endif %} {% if default_route != 'none' %} # See https://phabricator.vyos.net/T2248 & T2220. Determine if we are enslaved diff --git a/data/templates/pppoe/peer.tmpl b/data/templates/pppoe/peer.tmpl index 0f78f9384..928ed1238 100644 --- a/data/templates/pppoe/peer.tmpl +++ b/data/templates/pppoe/peer.tmpl @@ -1,8 +1,5 @@ ### Autogenerated by interfaces-pppoe.py ### - -{% if description %} -# {{ description }} -{% endif %} +{{ '# ' ~ description if description is defined else '' }} # Require peer to provide the local IP address if it is not # specified explicitly in the config file. @@ -30,15 +27,21 @@ connect /bin/true noauth # Don't try to proxy ARP for the remote endpoint. User can set proxy -# arp entries up manually if they wish. More importantly, having +# arp entries up manually if they wish. More importantly, having # the "proxyarp" parameter set disables the "defaultroute" option. noproxyarp # Unlimited connection attempts maxfail 0 -plugin rp-pppoe.so -{{ source_interface }} +plugin rp-pppoe.so {{ source_interface }} +{% if access_concentrator is defined and access_concentrator is not none %} +rp_pppoe_ac '{{ access_concentrator }}' +{% endif %} +{% if service_name is defined and service_name is not none %} +rp_pppoe_service '{{ service_name }}' +{% endif %} + persist ifname {{ ifname }} ipparam {{ ifname }} @@ -54,14 +57,9 @@ mru {{ mtu }} {{ "usepeerdns" if no_peer_dns is not defined }} {% if ipv6 is defined %} -+ipv6 -{% if ipv6.address is defined and ipv6.address.autoconf is defined %} -ipv6cp-use-ipaddr -{% endif %} -{% endif %} - -{% if service_name is defined %} -rp_pppoe_service "{{ service_name }}" ++ipv6 {{ 'ipv6cp-use-ipaddr' if ipv6.address is defined and ipv6.address.autoconf is defined }} +{% else %} +noipv6 {% endif %} {% if connect_on_demand is defined %} @@ -71,8 +69,14 @@ demand # passed to the ip-up.d/ip-down.s scripts which is required for VRF support. {% if 'auto' in default_route %} defaultroute +{{ 'defaultroute6' if ipv6 is defined }} {% elif 'force' in default_route %} defaultroute replacedefaultroute +{{ 'defaultroute6' if ipv6 is defined }} {% endif %} +{% else %} +nodefaultroute +noreplacedefaultroute +{{ 'nodefaultroute6' if ipv6 is defined }} {% endif %} diff --git a/data/templates/squid/squid.conf.tmpl b/data/templates/squid/squid.conf.tmpl index 8754e762d..80826fc75 100644 --- a/data/templates/squid/squid.conf.tmpl +++ b/data/templates/squid/squid.conf.tmpl @@ -1,7 +1,5 @@ ### generated by service_webproxy.py ### -acl localhost src 127.0.0.1/32 -acl to_localhost dst 127.0.0.0/8 acl net src all acl SSL_ports port 443 acl Safe_ports port 80 # http @@ -101,9 +99,9 @@ forwarded_for off {# SquidGuard #} {% if url_filtering is defined and url_filtering.disable is not defined %} {% if url_filtering.squidguard is defined and url_filtering.squidguard is not none %} -redirect_program /usr/bin/squidGuard -c {{ squidguard_conf }} -redirect_children 8 -redirector_bypass on +url_rewrite_program /usr/bin/squidGuard -c {{ squidguard_conf }} +url_rewrite_children 8 +url_rewrite_bypass on {% endif %} {% endif %} diff --git a/data/templates/squid/squidGuard.conf.tmpl b/data/templates/squid/squidGuard.conf.tmpl index f530d1072..c59dc901e 100644 --- a/data/templates/squid/squidGuard.conf.tmpl +++ b/data/templates/squid/squidGuard.conf.tmpl @@ -75,17 +75,50 @@ dest local-block-keyword-default { {% set acl.value = acl.value + ' ' + category + '-default' %} {% endfor %} {% endif %} +{% if sg_config.source_group is defined and sg_config.source_group is not none %} +{% for sgroup, sg_config in sg_config.source_group.items() %} +{% if sg_config.address is defined and sg_config.address is not none %} +src {{ sgroup }} { +{% for address in sg_config.address %} + ip {{ address }} +{% endfor %} +} + +{% endif %} +{% endfor %} +{% endif %} +{% if sg_config.rule is defined and sg_config.rule is not none %} +{% for rule, rule_config in sg_config.rule.items() %} +{% for b_category in rule_config.block_category%} +dest {{ b_category }} { + domainlist {{ b_category }}/domains + urllist {{ b_category }}/urls +} +{% endfor %} + +{% endfor %} +{% endif %} acl { - default { +{% if sg_config.rule is defined and sg_config.rule is not none %} +{% for rule, rule_config in sg_config.rule.items() %} + {{ rule_config.source_group }} { +{% for b_category in rule_config.block_category%} + pass local-ok-1 !in-addr !{{ b_category }} all +{% endfor %} + } +{% endfor %} +{% endif %} + + default { {% if sg_config.enable_safe_search is defined %} - rewrite safesearch + rewrite safesearch {% endif %} - pass {{ acl.value }} {{ 'none' if sg_config.default_action is defined and sg_config.default_action == 'block' else 'allow' }} - redirect 302:http://{{ sg_config.redirect_url }} + pass {{ acl.value }} {{ 'none' if sg_config.default_action is defined and sg_config.default_action == 'block' else 'allow' }} + redirect 302:http://{{ sg_config.redirect_url }} {% if sg_config.log is defined and sg_config.log is not none %} - log blacklist.log + log blacklist.log {% endif %} - } + } } {% endif %} {% endif %} diff --git a/data/templates/vrrp/daemon.tmpl b/data/templates/vrrp/daemon.tmpl deleted file mode 100644 index c9dbea72d..000000000 --- a/data/templates/vrrp/daemon.tmpl +++ /dev/null @@ -1,5 +0,0 @@ -# Autogenerated by VyOS -# Options to pass to keepalived - -# DAEMON_ARGS are appended to the keepalived command-line -DAEMON_ARGS="--snmp" diff --git a/data/templates/vrrp/keepalived.conf.tmpl b/data/templates/vrrp/keepalived.conf.tmpl index c01101d85..b4824a994 100644 --- a/data/templates/vrrp/keepalived.conf.tmpl +++ b/data/templates/vrrp/keepalived.conf.tmpl @@ -5,102 +5,102 @@ global_defs { dynamic_interfaces script_user root - notify_fifo /run/keepalived_notify_fifo + # Don't run scripts configured to be run as root if any part of the path + # is writable by a non-root user. + enable_script_security + notify_fifo /run/keepalived/keepalived_notify_fifo notify_fifo_script /usr/libexec/vyos/system/keepalived-fifo.py } -{% for group in groups %} - -{% if group.health_check_script %} -vrrp_script healthcheck_{{ group.name }} { - script "{{ group.health_check_script }}" - interval {{ group.health_check_interval }} - fall {{ group.health_check_count }} +{% if group is defined and group is not none %} +{% for name, group_config in group.items() if group_config.disable is not defined %} +{% if group_config.health_check is defined and group_config.health_check.script is defined and group_config.health_check.script is not none %} +vrrp_script healthcheck_{{ name }} { + script "{{ group_config.health_check.script }}" + interval {{ group_config.health_check.interval }} + fall {{ group_config.health_check.failure_count }} rise 1 - } -{% endif %} - -vrrp_instance {{ group.name }} { - {% if group.description %} - # {{ group.description }} - {% endif %} - +{% endif %} +vrrp_instance {{ name }} { +{% if group_config.description is defined and group_config.description is not none %} + # {{ group_config.description }} +{% endif %} state BACKUP - interface {{ group.interface }} - virtual_router_id {{ group.vrid }} - priority {{ group.priority }} - advert_int {{ group.advertise_interval }} - - {% if group.preempt %} - preempt_delay {{ group.preempt_delay }} - {% else %} + interface {{ group_config.interface }} + virtual_router_id {{ group_config.vrid }} + priority {{ group_config.priority }} + advert_int {{ group_config.advertise_interval }} +{% if group_config.no_preempt is not defined and group_config.preempt_delay is defined and group_config.preempt_delay is not none %} + preempt_delay {{ group_config.preempt_delay }} +{% elif group_config.no_preempt is defined %} nopreempt - {% endif %} - - {% if group.peer_address %} - unicast_peer { {{ group.peer_address }} } - {% endif %} - - {% if group.hello_source %} - {% if group.peer_address %} - unicast_src_ip {{ group.hello_source }} - {% else %} - mcast_src_ip {{ group.hello_source }} - {% endif %} - {% endif %} - - {% if group.use_vmac and group.peer_address %} - use_vmac {{group.interface}}v{{group.vrid}} - vmac_xmit_base - {% elif group.use_vmac %} - use_vmac {{group.interface}}v{{group.vrid}} - {% endif %} - - {% if group.auth_password %} - authentication { - auth_pass "{{ group.auth_password }}" - auth_type {{ group.auth_type }} - } - {% endif %} - +{% endif %} +{% if group_config.peer_address is defined and group_config.peer_address is not none %} + unicast_peer { {{ group_config.peer_address }} } +{% endif %} +{% if group_config.hello_source_address is defined and group_config.hello_source_address is not none %} +{% if group_config.peer_address is defined and group_config.peer_address is not none %} + unicast_src_ip {{ group_config.hello_source_address }} +{% else %} + mcast_src_ip {{ group_config.hello_source_address }} +{% endif %} +{% endif %} +{% if group_config.rfc3768_compatibility is defined and group_config.peer_address is defined %} + use_vmac {{ group_config.interface }}v{{ group_config.vrid }} + vmac_xmit_base +{% elif group_config.rfc3768_compatibility is defined %} + use_vmac {{ group_config.interface }}v{{ group_config.vrid }} +{% endif %} +{% if group_config.authentication is defined and group_config.authentication is not none %} + authentication { + auth_pass "{{ group_config.authentication.password }}" +{% if group_config.authentication.type == 'plaintext-password' %} + auth_type PASS +{% else %} + auth_type {{ group_config.authentication.type | upper }} +{% endif %} + } +{% endif %} +{% if group_config.address is defined and group_config.address is not none %} virtual_ipaddress { - {% for addr in group.virtual_addresses %} +{% for addr in group_config.address %} {{ addr }} - {% endfor %} +{% endfor %} } - - {% if group.virtual_addresses_excluded %} +{% endif %} +{% if group_config.excluded_address is defined and group_config.excluded_address is not none %} virtual_ipaddress_excluded { - {% for addr in group.virtual_addresses_excluded %} +{% for addr in group_config.excluded_address %} {{ addr }} - {% endfor %} +{% endfor %} } - {% endif %} - - {% if group.health_check_script %} +{% endif %} +{% if group_config.health_check is defined and group_config.health_check.script is defined and group_config.health_check.script is not none %} track_script { - healthcheck_{{ group.name }} + healthcheck_{{ name }} } - {% endif %} +{% endif %} } +{% endfor %} +{% endif %} -{% endfor %} - -{% for sync_group in sync_groups %} -vrrp_sync_group {{ sync_group.name }} { - group { - {% for member in sync_group.members %} - {{ member }} - {% endfor %} - } - - {% if sync_group.conntrack_sync %} - {% set vyos_helper = "/usr/libexec/vyos/vyos-vrrp-conntracksync.sh" %} - notify_master "{{ vyos_helper }} master {{ sync_group.name }}" - notify_backup "{{ vyos_helper }} backup {{ sync_group.name }}" - notify_fault "{{ vyos_helper }} fault {{ sync_group.name }}" - {% endif %} +{% if sync_group is defined and sync_group is not none %} +{% for name, group_config in sync_group.items() if group_config.disable is not defined %} +vrrp_sync_group {{ name }} { + group { +{% if group_config.member is defined and group_config.member is not none %} +{% for member in group_config.member %} + {{ member }} +{% endfor %} +{% endif %} + } +{% if conntrack_sync_group is defined and conntrack_sync_group == name %} +{% set vyos_helper = "/usr/libexec/vyos/vyos-vrrp-conntracksync.sh" %} + notify_master "{{ vyos_helper }} master {{ name }}" + notify_backup "{{ vyos_helper }} backup {{ name }}" + notify_fault "{{ vyos_helper }} fault {{ name }}" +{% endif %} } - -{% endfor %} +{% endfor %} +{% endif %} diff --git a/debian/control b/debian/control index c5cbeb7d4..f3a26e73e 100644 --- a/debian/control +++ b/debian/control @@ -156,6 +156,7 @@ Depends: traceroute, tuned, udp-broadcast-relay, + uidmap, usb-modeswitch, usbutils, vyatta-bash, diff --git a/debian/rules b/debian/rules index 70d39c481..5a58aeeb6 100755 --- a/debian/rules +++ b/debian/rules @@ -18,6 +18,10 @@ DEB_TARGET_ARCH := $(shell dpkg-architecture -qDEB_TARGET_ARCH) %: dh $@ --with python3, --with quilt +# Skip dh_strip_nondeterminism - this is very time consuming +# and we have no non deterministic output (yet) +override_dh_strip_nondeterminism: + override_dh_gencontrol: dh_gencontrol -- -v$(shell (git describe --tags --long --match 'vyos/*' --dirty 2>/dev/null || echo 0.0-no.git.tag) | sed -E 's%vyos/%%' | sed -E 's%-dirty%+dirty%') @@ -116,6 +120,10 @@ override_dh_auto_install: mkdir -p $(DIR)/$(VYOS_BIN_DIR) cp -r smoketest/bin/* $(DIR)/$(VYOS_BIN_DIR) + # Install udev script + mkdir -p $(DIR)/usr/lib/udev + cp src/helpers/vyos_net_name $(DIR)/usr/lib/udev + ifeq ($(DEB_TARGET_ARCH),amd64) # We only install XDP on amd64 systems mkdir -p $(DIR)/$(VYOS_DATA_DIR)/xdp diff --git a/debian/vyos-1x.install b/debian/vyos-1x.install index 2ed25755f..d332e0d36 100644 --- a/debian/vyos-1x.install +++ b/debian/vyos-1x.install @@ -3,6 +3,7 @@ etc/dhcp etc/ipsec.d etc/netplug etc/opennhrp +etc/ppp etc/rsyslog.d etc/securetty etc/security @@ -10,6 +11,7 @@ etc/sudoers.d etc/systemd etc/sysctl.d etc/udev +etc/update-motd.d etc/vyos lib/ opt/ diff --git a/interface-definitions/bcast-relay.xml.in b/interface-definitions/bcast-relay.xml.in index 1b354d885..3f781f07f 100644 --- a/interface-definitions/bcast-relay.xml.in +++ b/interface-definitions/bcast-relay.xml.in @@ -1,5 +1,4 @@ <?xml version="1.0"?> -<!-- UDP broadcast relay configuration --> <interfaceDefinition> <node name="service"> <children> @@ -14,8 +13,8 @@ <properties> <help>Unique ID for each UDP port to forward</help> <valueHelp> - <format>1-99</format> - <description>Numerical ID #</description> + <format>u32:1-99</format> + <description>Broadcast relay instance ID</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-99"/> @@ -49,18 +48,7 @@ <multi/> </properties> </leafNode> - <leafNode name="port"> - <properties> - <help>Destination or source port to listen and retransmit on [REQUIRED]</help> - <valueHelp> - <format>u32:1-65535</format> - <description>UDP port to listen on</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1-65535"/> - </constraint> - </properties> - </leafNode> + #include <include/port-number.xml.i> </children> </tagNode> </children> diff --git a/interface-definitions/containers.xml.in b/interface-definitions/containers.xml.in index 124b1f65e..fb8241d71 100644 --- a/interface-definitions/containers.xml.in +++ b/interface-definitions/containers.xml.in @@ -9,6 +9,10 @@ <tagNode name="name"> <properties> <help>Container name</help> + <constraint> + <regex>^[-a-zA-Z0-9]+$</regex> + </constraint> + <constraintErrorMessage>Container name must be alphanumeric and can contain hyphens</constraintErrorMessage> </properties> <children> <leafNode name="allow-host-networks"> @@ -17,14 +21,15 @@ <valueless/> </properties> </leafNode> - <leafNode name="description"> - <properties> - <help>Container description</help> - </properties> - </leafNode> + #include <include/generic-description.xml.i> + #include <include/generic-disable-node.xml.i> <tagNode name="environment"> <properties> <help>Add custom environment variables</help> + <constraint> + <regex>^[-_a-zA-Z0-9]+$</regex> + </constraint> + <constraintErrorMessage>Environment variable name must be alphanumeric and can contain hyphen and underscores</constraintErrorMessage> </properties> <children> <leafNode name="value"> @@ -43,6 +48,24 @@ <help>Image name in the hub-registry</help> </properties> </leafNode> + <leafNode name="memory"> + <properties> + <help>Constrain the memory available to a container (default: 512MB)</help> + <valueHelp> + <format>u32:0</format> + <description>Unlimited</description> + </valueHelp> + <valueHelp> + <format>u32:1-16384</format> + <description>Container memory in megabytes (MB)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-16384"/> + </constraint> + <constraintErrorMessage>Container memory must be in range 0 to 16384 MB</constraintErrorMessage> + </properties> + <defaultValue>512</defaultValue> + </leafNode> <tagNode name="network"> <properties> <help>Attach user defined network to container</help> @@ -53,10 +76,11 @@ <children> <leafNode name="address"> <properties> - <help>Set IPv4 static address to container (optional)</help> + <!-- PODMAN currently does not support more then one IPv4 or IPv6 address assignments to a container --> + <help>Assign static IP address to container</help> <valueHelp> <format>ipv4</format> - <description>IPv4 address (x.x.x.1 reserved)</description> + <description>IPv4 address</description> </valueHelp> <constraint> <validator name="ipv4-address"/> @@ -115,6 +139,30 @@ </leafNode> </children> </tagNode> + <leafNode name="restart"> + <properties> + <help>Restart options for container</help> + <completionHelp> + <list>no on-failure always</list> + </completionHelp> + <valueHelp> + <format>no</format> + <description>Do not restart containers on exit</description> + </valueHelp> + <valueHelp> + <format>on-failure</format> + <description>Restart containers when they exit with a non-zero exit code, retrying indefinitely (default)</description> + </valueHelp> + <valueHelp> + <format>always</format> + <description>Restart containers when they exit, regardless of status, retrying indefinitely</description> + </valueHelp> + <constraint> + <regex>^(no|on-failure|always)$</regex> + </constraint> + </properties> + <defaultValue>on-failure</defaultValue> + </leafNode> <tagNode name="volume"> <properties> <help>Mount a volume into the container</help> @@ -159,8 +207,13 @@ <format>ipv4net</format> <description>IPv4 network prefix</description> </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 network prefix</description> + </valueHelp> <constraint> <validator name="ipv4-prefix"/> + <validator name="ipv6-prefix"/> </constraint> <multi/> </properties> diff --git a/interface-definitions/dhcp-relay.xml.in b/interface-definitions/dhcp-relay.xml.in index 8c95239d9..0d485ef80 100644 --- a/interface-definitions/dhcp-relay.xml.in +++ b/interface-definitions/dhcp-relay.xml.in @@ -27,7 +27,7 @@ <properties> <help>Policy to discard packets that have reached specified hop-count</help> <valueHelp> - <format>1-255</format> + <format>u32:1-255</format> <description>Hop count (default: 10)</description> </valueHelp> <constraint> @@ -41,7 +41,7 @@ <properties> <help>Maximum packet size to send to a DHCPv4/BOOTP server</help> <valueHelp> - <format>64-1400</format> + <format>u32:64-1400</format> <description>Maximum packet size (default: 576)</description> </valueHelp> <constraint> diff --git a/interface-definitions/dhcp-server.xml.in b/interface-definitions/dhcp-server.xml.in index 015500043..47bdc4db1 100644 --- a/interface-definitions/dhcp-server.xml.in +++ b/interface-definitions/dhcp-server.xml.in @@ -16,6 +16,55 @@ <valueless/> </properties> </leafNode> + <node name="failover"> + <properties> + <help>DHCP failover configuration</help> + </properties> + <children> + #include <include/source-address-ipv4.xml.i> + <leafNode name="remote"> + <properties> + <help>IPv4 remote address used for connectio</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address of failover peer</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="name"> + <properties> + <help>Peer name used to identify connection</help> + <constraint> + <regex>[-_a-zA-Z0-9.]+</regex> + </constraint> + <constraintErrorMessage>Invalid failover peer name. May only contain letters, numbers and .-_</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="status"> + <properties> + <help>Failover hierarchy</help> + <completionHelp> + <list>primary secondary</list> + </completionHelp> + <valueHelp> + <format>primary</format> + <description>Configure this server to be the primary node</description> + </valueHelp> + <valueHelp> + <format>secondary</format> + <description>Configure this server to be the secondary node</description> + </valueHelp> + <constraint> + <regex>^(primary|secondary)$</regex> + </constraint> + <constraintErrorMessage>Invalid DHCP failover peer status</constraintErrorMessage> + </properties> + </leafNode> + </children> + </node> <leafNode name="global-parameters"> <properties> <help>Additional global parameters for DHCP server. You must @@ -53,12 +102,13 @@ <valueless/> </properties> </leafNode> - <leafNode name="description"> - <properties> - <help>Shared-network-name description</help> - </properties> - </leafNode> + #include <include/dhcp/domain-name.xml.i> + #include <include/dhcp/domain-search.xml.i> + #include <include/dhcp/ntp-server.xml.i> + #include <include/dhcp/ping-check.xml.i> + #include <include/generic-description.xml.i> #include <include/generic-disable-node.xml.i> + #include <include/name-server-ipv4.xml.i> <leafNode name="shared-network-parameters"> <properties> <help>Additional shared-network parameters for DHCP server. @@ -96,7 +146,7 @@ <properties> <help>Specifies the clients subnet mask as per RFC 950. If unset, subnet declaration is used.</help> <valueHelp> - <format>0-32</format> + <format>u32:0-32</format> <description>DHCP client prefix length must be 0 to 32</description> </valueHelp> <constraint> @@ -117,29 +167,16 @@ </constraint> </properties> </leafNode> - <leafNode name="dns-server"> + #include <include/dhcp/domain-name.xml.i> + #include <include/dhcp/domain-search.xml.i> + #include <include/generic-description.xml.i> + #include <include/name-server-ipv4.xml.i> + <leafNode name="enable-failover"> <properties> - <help>DNS server IPv4 address</help> - <valueHelp> - <format>ipv4</format> - <description>DNS server IPv4 address</description> - </valueHelp> - <constraint> - <validator name="ipv4-address"/> - </constraint> - <multi/> - </properties> - </leafNode> - <leafNode name="domain-name"> - <properties> - <help>Client Domain Name</help> - <constraint> - <validator name="fqdn"/> - </constraint> - <constraintErrorMessage>Invalid domain name (RFC 1123 section 2).\nMay only contain letters, numbers and .-_</constraintErrorMessage> + <help>Enable DHCP failover support for this subnet</help> + <valueless/> </properties> </leafNode> - #include <include/dhcp-server-domain-search.xml.i> <leafNode name="exclude"> <properties> <help>IP address to exclude from DHCP lease range</help> @@ -153,58 +190,6 @@ <multi/> </properties> </leafNode> - <node name="failover"> - <properties> - <help>DHCP failover parameters</help> - </properties> - <children> - <leafNode name="local-address"> - <properties> - <help>IP address for failover peer to connect [REQUIRED]</help> - <valueHelp> - <format>ipv4</format> - <description>IPv4 address to exclude from lease range</description> - </valueHelp> - <constraint> - <validator name="ipv4-address"/> - </constraint> - </properties> - </leafNode> - <leafNode name="name"> - <properties> - <help>DHCP failover peer name [REQUIRED]</help> - <constraint> - <regex>[-_a-zA-Z0-9.]+</regex> - </constraint> - <constraintErrorMessage>Invalid failover peer name. May only contain letters, numbers and .-_</constraintErrorMessage> - </properties> - </leafNode> - <leafNode name="peer-address"> - <properties> - <help>IP address of failover peer [REQUIRED]</help> - <valueHelp> - <format>ipv4</format> - <description>IPv4 address of failover peer</description> - </valueHelp> - <constraint> - <validator name="ipv4-address"/> - </constraint> - </properties> - </leafNode> - <leafNode name="status"> - <properties> - <help>DHCP failover peer status (primary|secondary) [REQUIRED]</help> - <completionHelp> - <list>primary secondary</list> - </completionHelp> - <constraint> - <regex>^(primary|secondary)$</regex> - </constraint> - <constraintErrorMessage>Invalid DHCP failover peer status</constraintErrorMessage> - </properties> - </leafNode> - </children> - </node> <leafNode name="ip-forwarding"> <properties> <help>Enable IP forwarding on client</help> @@ -225,19 +210,8 @@ </properties> <defaultValue>86400</defaultValue> </leafNode> - <leafNode name="ntp-server"> - <properties> - <help>IP address of NTP server</help> - <valueHelp> - <format>ipv4</format> - <description>NTP server IPv4 address</description> - </valueHelp> - <constraint> - <validator name="ipv4-address"/> - </constraint> - <multi/> - </properties> - </leafNode> + #include <include/dhcp/ntp-server.xml.i> + #include <include/dhcp/ping-check.xml.i> <leafNode name="pop-server"> <properties> <help>IP address of POP3 server</help> @@ -357,26 +331,21 @@ </leafNode> </children> </tagNode> - <node name="static-route"> + <tagNode name="static-route"> <properties> - <help>Classless static route</help> + <help>Classless static route destination subnet [REQUIRED]</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 address and prefix length</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> </properties> <children> - <leafNode name="destination-subnet"> - <properties> - <help>Destination subnet [REQUIRED]</help> - <valueHelp> - <format>ipv4net</format> - <description>IPv4 address and prefix length</description> - </valueHelp> - <constraint> - <validator name="ipv4-prefix"/> - </constraint> - </properties> - </leafNode> - <leafNode name="router"> + <leafNode name="next-hop"> <properties> - <help>IP address of router to be used to reach the destination subnet [REQUIRED]</help> + <help>IP address of router to be used to reach the destination subnet</help> <valueHelp> <format>ipv4</format> <description>IPv4 address of router</description> @@ -387,7 +356,7 @@ </properties> </leafNode> </children> - </node> + </tagNode > <leafNode name="subnet-parameters"> <properties> <help>Additional subnet parameters for DHCP server. You must diff --git a/interface-definitions/dhcpv6-relay.xml.in b/interface-definitions/dhcpv6-relay.xml.in index 308f94a01..7162cf353 100644 --- a/interface-definitions/dhcpv6-relay.xml.in +++ b/interface-definitions/dhcpv6-relay.xml.in @@ -35,7 +35,7 @@ <properties> <help>Maximum hop count for which requests will be processed</help> <valueHelp> - <format>1-255</format> + <format>u32:1-255</format> <description>Hop count (default: 10)</description> </valueHelp> <constraint> diff --git a/interface-definitions/dhcpv6-server.xml.in b/interface-definitions/dhcpv6-server.xml.in index 5d6c64685..fb96571f5 100644 --- a/interface-definitions/dhcpv6-server.xml.in +++ b/interface-definitions/dhcpv6-server.xml.in @@ -1,5 +1,4 @@ <?xml version="1.0"?> -<!-- DHCPv6 server configuration --> <interfaceDefinition> <node name="service"> <children> @@ -15,26 +14,14 @@ <help>Additional global parameters for DHCPv6 server</help> </properties> <children> - <leafNode name="name-server"> - <properties> - <help>IPv6 address of a Recursive DNS Server</help> - <valueHelp> - <format>ipv6</format> - <description>IPv6 address of DNS name server</description> - </valueHelp> - <constraint> - <validator name="ipv6-address"/> - </constraint> - <multi/> - </properties> - </leafNode> + #include <include/name-server-ipv6.xml.i> </children> </node> <leafNode name="preference"> <properties> <help>Preference of this DHCPv6 server compared with others</help> <valueHelp> - <format>0-255</format> + <format>u32:0-255</format> <description>DHCPv6 server preference (0-255)</description> </valueHelp> <constraint> @@ -53,6 +40,7 @@ </properties> <children> #include <include/generic-disable-node.xml.i> + #include <include/generic-description.xml.i> <node name="common-options"> <properties> <help>Common options to distribute to all clients, including stateless clients</help> @@ -62,7 +50,7 @@ <properties> <help>Time (in seconds) that stateless clients should wait between refreshing the information they were given</help> <valueHelp> - <format>1-4294967295</format> + <format>u32:1-4294967295</format> <description>DHCPv6 information refresh time</description> </valueHelp> <constraint> @@ -70,20 +58,8 @@ </constraint> </properties> </leafNode> - #include <include/dhcp-server-domain-search.xml.i> - <leafNode name="name-server"> - <properties> - <help>IPv6 address of a Recursive DNS Server</help> - <valueHelp> - <format>ipv6</format> - <description>IPv6 address of DNS name server</description> - </valueHelp> - <constraint> - <validator name="ipv6-address"/> - </constraint> - <multi/> - </properties> - </leafNode> + #include <include/dhcp/domain-search.xml.i> + #include <include/name-server-ipv6.xml.i> </children> </node> <tagNode name="subnet"> @@ -151,7 +127,7 @@ </tagNode> </children> </node> - #include <include/dhcp-server-domain-search.xml.i> + #include <include/dhcp/domain-search.xml.i> <node name="lease-time"> <properties> <help>Parameters relating to the lease time</help> @@ -161,7 +137,7 @@ <properties> <help>Default time (in seconds) that will be assigned to a lease</help> <valueHelp> - <format>1-4294967295</format> + <format>u32:1-4294967295</format> <description>DHCPv6 valid lifetime</description> </valueHelp> <constraint> @@ -173,7 +149,7 @@ <properties> <help>Maximum time (in seconds) that will be assigned to a lease</help> <valueHelp> - <format>1-4294967295</format> + <format>u32:1-4294967295</format> <description>Maximum lease time in seconds</description> </valueHelp> <constraint> @@ -185,7 +161,7 @@ <properties> <help>Minimum time (in seconds) that will be assigned to a lease</help> <valueHelp> - <format>1-4294967295</format> + <format>u32:1-4294967295</format> <description>Minimum lease time in seconds</description> </valueHelp> <constraint> @@ -195,19 +171,7 @@ </leafNode> </children> </node> - <leafNode name="name-server"> - <properties> - <help>IPv6 address of a Recursive DNS Server</help> - <valueHelp> - <format>ipv6</format> - <description>IPv6 address of DNS name server</description> - </valueHelp> - <constraint> - <validator name="ipv6-address"/> - </constraint> - <multi/> - </properties> - </leafNode> + #include <include/name-server-ipv6.xml.i> <leafNode name="nis-domain"> <properties> <help>NIS domain name for client to use</help> @@ -273,7 +237,7 @@ <properties> <help>Length in bits of prefixes to be delegated</help> <valueHelp> - <format>32-64</format> + <format>u32:32-64</format> <description>Delagated prefix length (32-64)</description> </valueHelp> <constraint> diff --git a/interface-definitions/dns-domain-name.xml.in b/interface-definitions/dns-domain-name.xml.in index ff632e1d1..2b1644609 100644 --- a/interface-definitions/dns-domain-name.xml.in +++ b/interface-definitions/dns-domain-name.xml.in @@ -1,37 +1,34 @@ <?xml version="1.0"?> -<!-- host-name configuration --> <interfaceDefinition> <node name="system"> <children> <leafNode name="name-server" owner="${vyos_conf_scripts_dir}/host_name.py"> <properties> - <help>Domain Name Servers (DNS) used by the system (resolv.conf)</help> + <help>System Domain Name Servers (DNS)</help> <priority>400</priority> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces.py</script> + </completionHelp> <valueHelp> <format>ipv4</format> - <description>Domain Name Server (DNS) address</description> + <description>Domain Name Server IPv4 address</description> </valueHelp> <valueHelp> <format>ipv6</format> - <description>Domain Name Server (DNS) address</description> + <description>Domain Name Server IPv6 address</description> + </valueHelp> + <valueHelp> + <format>txt</format> + <description>Use Domain Name Server from DHCP interface</description> </valueHelp> <multi/> <constraint> <validator name="ipv4-address"/> <validator name="ipv6-address"/> + <validator name="interface-name"/> </constraint> </properties> </leafNode> - <leafNode name="name-servers-dhcp" owner="${vyos_conf_scripts_dir}/host_name.py"> - <properties> - <help>Interfaces whose DHCP client nameservers will be used by the system (resolv.conf)</help> - <priority>400</priority> - <completionHelp> - <script>${vyos_completion_dir}/list_interfaces.py</script> - </completionHelp> - <multi/> - </properties> - </leafNode> <leafNode name="host-name" owner="${vyos_conf_scripts_dir}/host_name.py"> <properties> <help>System host name (default: vyos)</help> diff --git a/interface-definitions/dns-dynamic.xml.in b/interface-definitions/dns-dynamic.xml.in index b0b9158c8..250642691 100644 --- a/interface-definitions/dns-dynamic.xml.in +++ b/interface-definitions/dns-dynamic.xml.in @@ -49,7 +49,7 @@ <properties> <help>Time To Live (default: 600)</help> <valueHelp> - <format>1-86400</format> + <format>u32:1-86400</format> <description>DNS forwarding cache size</description> </valueHelp> <constraint> diff --git a/interface-definitions/dns-forwarding.xml.in b/interface-definitions/dns-forwarding.xml.in index c420e9b8b..5b0c87597 100644 --- a/interface-definitions/dns-forwarding.xml.in +++ b/interface-definitions/dns-forwarding.xml.in @@ -18,11 +18,11 @@ <properties> <help>DNS forwarding cache size (default: 10000)</help> <valueHelp> - <format>0-10000</format> + <format>u32:0-2147483647</format> <description>DNS forwarding cache size</description> </valueHelp> <constraint> - <validator name="numeric" argument="--range 0-10000"/> + <validator name="numeric" argument="--range 0-2147483647"/> </constraint> </properties> <defaultValue>10000</defaultValue> @@ -139,7 +139,7 @@ <properties> <help>Maximum amount of time negative entries are cached (default: 3600)</help> <valueHelp> - <format>0-7200</format> + <format>u32:0-7200</format> <description>Seconds to cache NXDOMAIN entries</description> </valueHelp> <constraint> @@ -148,24 +148,7 @@ </properties> <defaultValue>3600</defaultValue> </leafNode> - <leafNode name="name-server"> - <properties> - <help>Domain Name Servers (DNS) addresses [OPTIONAL]</help> - <valueHelp> - <format>ipv4</format> - <description>Domain Name Server (DNS) IPv4 address</description> - </valueHelp> - <valueHelp> - <format>ipv6</format> - <description>Domain Name Server (DNS) IPv6 address</description> - </valueHelp> - <multi/> - <constraint> - <validator name="ipv4-address"/> - <validator name="ipv6-address"/> - </constraint> - </properties> - </leafNode> + #include <include/name-server-ipv4-ipv6.xml.i> <leafNode name="source-address"> <properties> <help>Local addresses from which to send DNS queries</help> diff --git a/interface-definitions/firewall-options.xml.in b/interface-definitions/firewall-options.xml.in deleted file mode 100644 index 8d9225a9a..000000000 --- a/interface-definitions/firewall-options.xml.in +++ /dev/null @@ -1,50 +0,0 @@ -<?xml version="1.0"?> -<interfaceDefinition> - <node name="firewall"> - <children> - <node name="options"> - <properties> - <help>Firewall options/Packet manipulation</help> - <priority>990</priority> - </properties> - <children> - <tagNode name="interface" owner="${vyos_conf_scripts_dir}/firewall_options.py"> - <properties> - <help>Interface clamping options</help> - <completionHelp> - <script>${vyos_completion_dir}/list_interfaces.py</script> - </completionHelp> - </properties> - <children> - #include <include/generic-disable-node.xml.i> - <leafNode name="adjust-mss"> - <properties> - <help>Adjust MSS for IPv4 transit packets</help> - <valueHelp> - <format>500-1460</format> - <description>TCP Maximum segment size in bytes</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 500-1460"/> - </constraint> - </properties> - </leafNode> - <leafNode name="adjust-mss6"> - <properties> - <help>Adjust MSS for IPv6 transit packets</help> - <valueHelp> - <format>1280-1492</format> - <description>TCP Maximum segment size in bytes</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1280-1492"/> - </constraint> - </properties> - </leafNode> - </children> - </tagNode> - </children> - </node> - </children> - </node> -</interfaceDefinition> diff --git a/interface-definitions/flow-accounting-conf.xml.in b/interface-definitions/flow-accounting-conf.xml.in index b3980d9e2..b0f308afd 100644 --- a/interface-definitions/flow-accounting-conf.xml.in +++ b/interface-definitions/flow-accounting-conf.xml.in @@ -267,7 +267,7 @@ <properties> <help>Expiry scan interval</help> <valueHelp> - <format>0-2147483647</format> + <format>u32:0-2147483647</format> <description>Expiry scan interval (default 60)</description> </valueHelp> <constraint> @@ -279,7 +279,7 @@ <properties> <help>Generic flow timeout value</help> <valueHelp> - <format>0-2147483647</format> + <format>u32:0-2147483647</format> <description>Generic flow timeout in seconds (default 3600)</description> </valueHelp> <constraint> @@ -291,7 +291,7 @@ <properties> <help>ICMP timeout value</help> <valueHelp> - <format>0-2147483647</format> + <format>u32:0-2147483647</format> <description>ICMP timeout in seconds (default 300)</description> </valueHelp> <constraint> @@ -303,7 +303,7 @@ <properties> <help>Max active timeout value</help> <valueHelp> - <format>0-2147483647</format> + <format>u32:0-2147483647</format> <description>Max active timeout in seconds (default 604800)</description> </valueHelp> <constraint> @@ -315,7 +315,7 @@ <properties> <help>TCP finish timeout value</help> <valueHelp> - <format>0-2147483647</format> + <format>u32:0-2147483647</format> <description>TCP FIN timeout in seconds (default 300)</description> </valueHelp> <constraint> @@ -327,7 +327,7 @@ <properties> <help>TCP generic timeout value</help> <valueHelp> - <format>0-2147483647</format> + <format>u32:0-2147483647</format> <description>TCP generic timeout in seconds (default 3600)</description> </valueHelp> <constraint> @@ -339,7 +339,7 @@ <properties> <help>TCP reset timeout value</help> <valueHelp> - <format>0-2147483647</format> + <format>u32:0-2147483647</format> <description>TCP RST timeout in seconds (default 120)</description> </valueHelp> <constraint> @@ -351,7 +351,7 @@ <properties> <help>UDP timeout value</help> <valueHelp> - <format>0-2147483647</format> + <format>u32:0-2147483647</format> <description>UDP timeout in seconds (default 300)</description> </valueHelp> <constraint> diff --git a/interface-definitions/https.xml.in b/interface-definitions/https.xml.in index b65a89b56..bb6f71744 100644 --- a/interface-definitions/https.xml.in +++ b/interface-definitions/https.xml.in @@ -1,7 +1,5 @@ <?xml version="1.0"?> -<!-- HTTPS configuration --> <interfaceDefinition> - <syntaxVersion component='https' version='3'></syntaxVersion> <node name="service"> <children> <node name="https" owner="${vyos_conf_scripts_dir}/https.py"> @@ -48,7 +46,7 @@ <properties> <help>Port to listen for HTTPS requests; default 443</help> <valueHelp> - <format>1-65535</format> + <format>u32:1-65535</format> <description>Numeric IP port</description> </valueHelp> <constraint> @@ -136,9 +134,9 @@ </properties> </leafNode> <leafNode name="email"> - <properties> - <help>Email address to associate with certificate</help> - </properties> + <properties> + <help>Email address to associate with certificate</help> + </properties> </leafNode> </children> </node> diff --git a/interface-definitions/igmp-proxy.xml.in b/interface-definitions/igmp-proxy.xml.in index d0f44eada..91c912d8b 100644 --- a/interface-definitions/igmp-proxy.xml.in +++ b/interface-definitions/igmp-proxy.xml.in @@ -65,7 +65,7 @@ <properties> <help>TTL threshold (default: 1)</help> <valueHelp> - <format>1-255</format> + <format>u32:1-255</format> <description>TTL threshold for the interfaces (default: 1)</description> </valueHelp> <constraint> diff --git a/interface-definitions/include/accel-ppp/ppp-interface-cache.xml.i b/interface-definitions/include/accel-ppp/ppp-interface-cache.xml.i index 9f223d7ed..019601c85 100644 --- a/interface-definitions/include/accel-ppp/ppp-interface-cache.xml.i +++ b/interface-definitions/include/accel-ppp/ppp-interface-cache.xml.i @@ -3,7 +3,7 @@ <properties> <help>PPP interface cache</help> <valueHelp> - <format>1-256000</format> + <format>u32:1-256000</format> <description>Count of interfaces to keep in cache</description> </valueHelp> <constraint> diff --git a/interface-definitions/include/accel-ppp/radius-additions.xml.i b/interface-definitions/include/accel-ppp/radius-additions.xml.i index fdcff36bf..258ece2b5 100644 --- a/interface-definitions/include/accel-ppp/radius-additions.xml.i +++ b/interface-definitions/include/accel-ppp/radius-additions.xml.i @@ -5,7 +5,7 @@ <properties> <help>Maximum jitter value in seconds to be applied to accounting information interval</help> <valueHelp> - <format>1-60</format> + <format>u32:1-60</format> <description>Maximum jitter value in seconds</description> </valueHelp> <constraint> @@ -20,7 +20,7 @@ <properties> <help>Accounting port</help> <valueHelp> - <format>1-65535</format> + <format>u32:1-65535</format> <description>Numeric IP port (default: 1813)</description> </valueHelp> <constraint> @@ -34,7 +34,7 @@ <properties> <help>Mark server unavailable for <n> seconds on failure</help> <valueHelp> - <format>0-600</format> + <format>u32:0-600</format> <description>Fail time penalty</description> </valueHelp> <constraint> @@ -50,7 +50,7 @@ <properties> <help>Timeout in seconds to wait response from RADIUS server</help> <valueHelp> - <format>1-60</format> + <format>u32:1-60</format> <description>Timeout in seconds</description> </valueHelp> <constraint> @@ -64,7 +64,7 @@ <properties> <help>Timeout for Interim-Update packets, terminate session afterwards (default 3 seconds)</help> <valueHelp> - <format>0-60</format> + <format>u32:0-60</format> <description>Timeout in seconds, 0 to keep active</description> </valueHelp> <constraint> @@ -78,7 +78,7 @@ <properties> <help>Number of tries to send Access-Request/Accounting-Request queries</help> <valueHelp> - <format>1-20</format> + <format>u32:1-20</format> <description>Maximum tries</description> </valueHelp> <constraint> diff --git a/interface-definitions/include/bfd-common.xml.i b/interface-definitions/include/bfd-common.xml.i index b47b47612..1d6ab5d55 100644 --- a/interface-definitions/include/bfd-common.xml.i +++ b/interface-definitions/include/bfd-common.xml.i @@ -14,7 +14,7 @@ <properties> <help>Minimum interval of receiving control packets</help> <valueHelp> - <format>10-60000</format> + <format>u32:10-60000</format> <description>Interval in milliseconds</description> </valueHelp> <constraint> @@ -27,7 +27,7 @@ <properties> <help>Minimum interval of transmitting control packets</help> <valueHelp> - <format>10-60000</format> + <format>u32:10-60000</format> <description>Interval in milliseconds</description> </valueHelp> <constraint> @@ -40,7 +40,7 @@ <properties> <help>Multiplier to determine packet loss</help> <valueHelp> - <format>2-255</format> + <format>u32:2-255</format> <description>Remote transmission interval will be multiplied by this value</description> </valueHelp> <constraint> @@ -53,7 +53,7 @@ <properties> <help>Echo receive transmission interval</help> <valueHelp> - <format>10-60000</format> + <format>u32:10-60000</format> <description>The minimal echo receive transmission interval that this system is capable of handling</description> </valueHelp> <constraint> diff --git a/interface-definitions/include/bgp/afi-export-import.xml.i b/interface-definitions/include/bgp/afi-export-import.xml.i new file mode 100644 index 000000000..86817cdb3 --- /dev/null +++ b/interface-definitions/include/bgp/afi-export-import.xml.i @@ -0,0 +1,41 @@ +<!-- include start from bgp/afi-export-import.xml.i --> +<node name="export"> + <properties> + <help>Export routes from this address-family</help> + </properties> + <children> + <leafNode name="vpn"> + <properties> + <help>to/from default instance VPN RIB</help> + <valueless/> + </properties> + </leafNode> + </children> +</node> +<node name="import"> + <properties> + <help>Import routes to this address-family</help> + </properties> + <children> + <leafNode name="vpn"> + <properties> + <help>to/from default instance VPN RIB</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="vrf"> + <properties> + <help>VRF to import from</help> + <valueHelp> + <format>txt</format> + <description>VRF instance name</description> + </valueHelp> + <completionHelp> + <path>vrf name</path> + </completionHelp> + <multi/> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-l2vpn-common.xml.i b/interface-definitions/include/bgp/afi-l2vpn-common.xml.i index 1673f25a5..8deb189ab 100644 --- a/interface-definitions/include/bgp/afi-l2vpn-common.xml.i +++ b/interface-definitions/include/bgp/afi-l2vpn-common.xml.i @@ -11,17 +11,48 @@ <valueless/> </properties> </leafNode> -<leafNode name="rd"> +#include <include/bgp/route-distinguisher.xml.i> +<node name="route-target"> <properties> - <help>Route Distinguisher</help> - <valueHelp> - <format>txt</format> - <description>Route Distinguisher, (x.x.x.x:yyy|xxxx:yyyy)</description> - </valueHelp> - <constraint> - <regex>^((25[0-5]|2[0-4][0-9]|[1][0-9][0-9]|[1-9][0-9]|[0-9]?)(\.(25[0-5]|2[0-4][0-9]|[1][0-9][0-9]|[1-9][0-9]|[0-9]?)){3}|[0-9]{1,10}):[0-9]{1,5}$</regex> - </constraint> + <help>Route Target</help> </properties> -</leafNode> -#include <include/bgp/route-target.xml.i> + <children> + <leafNode name="both"> + <properties> + <help>Route Target both import and export</help> + <valueHelp> + <format>txt</format> + <description>Route target (A.B.C.D:MN|EF:OPQR|GHJK:MN)</description> + </valueHelp> + <constraint> + <validator name="bgp-route-target" argument="--single"/> + </constraint> + </properties> + </leafNode> + <leafNode name="import"> + <properties> + <help>Route Target import</help> + <valueHelp> + <format>txt</format> + <description>Route target (A.B.C.D:MN|EF:OPQR|GHJK:MN)</description> + </valueHelp> + <constraint> + <validator name="bgp-route-target" argument="--single"/> + </constraint> + </properties> + </leafNode> + <leafNode name="export"> + <properties> + <help>Route Target export</help> + <valueHelp> + <format>txt</format> + <description>Route target (A.B.C.D:MN|EF:OPQR|GHJK:MN)</description> + </valueHelp> + <constraint> + <validator name="bgp-route-target" argument="--single"/> + </constraint> + </properties> + </leafNode> + </children> +</node> <!-- include end --> diff --git a/interface-definitions/include/bgp/afi-label.xml.i b/interface-definitions/include/bgp/afi-label.xml.i new file mode 100644 index 000000000..f7a1f609f --- /dev/null +++ b/interface-definitions/include/bgp/afi-label.xml.i @@ -0,0 +1,36 @@ +<!-- include start from bgp/afi-label.xml.i --> +<node name="label"> + <properties> + <help>Label value for VRF</help> + </properties> + <children> + <node name="vpn"> + <properties> + <help>Between current address-family and VPN</help> + </properties> + <children> + <leafNode name="export"> + <properties> + <help>For routes leaked from current address-family to VPN</help> + <completionHelp> + <list>auto</list> + </completionHelp> + <valueHelp> + <format>auto</format> + <description>Automatically assign a label</description> + </valueHelp> + <valueHelp> + <format>u32:0-1048575</format> + <description>Label Value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-1048575"/> + <regex>^(auto)$</regex> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-path-limit.xml.i b/interface-definitions/include/bgp/afi-path-limit.xml.i new file mode 100644 index 000000000..e3d630a57 --- /dev/null +++ b/interface-definitions/include/bgp/afi-path-limit.xml.i @@ -0,0 +1,14 @@ +<!-- include start from bgp/afi-path-limit.xml.i --> +<leafNode name="path-limit"> + <properties> + <help>AS-path hopcount limit</help> + <valueHelp> + <format>u32:0-255</format> + <description>AS path hop count limit</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-255"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-rd.xml.i b/interface-definitions/include/bgp/afi-rd.xml.i new file mode 100644 index 000000000..c4d29268c --- /dev/null +++ b/interface-definitions/include/bgp/afi-rd.xml.i @@ -0,0 +1,28 @@ +<!-- include start from bgp/afi-rd.xml.i --> +<node name="rd"> + <properties> + <help>Specify route distinguisher</help> + </properties> + <children> + <node name="vpn"> + <properties> + <help>Between current address-family and VPN</help> + </properties> + <children> + <leafNode name="export"> + <properties> + <help>For routes leaked from current address-family to VPN</help> + <valueHelp> + <format>ASN:NN_OR_IP-ADDRESS:NN</format> + <description>Route Distinguisher, (x.x.x.x:yyy|xxxx:yyyy)</description> + </valueHelp> + <constraint> + <regex>^((25[0-5]|2[0-4][0-9]|[1][0-9][0-9]|[1-9][0-9]|[0-9]?)(\.(25[0-5]|2[0-4][0-9]|[1][0-9][0-9]|[1-9][0-9]|[0-9]?)){3}|[0-9]{1,10}):[0-9]{1,5}$</regex> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-route-map-export-import.xml.i b/interface-definitions/include/bgp/afi-route-map-export-import.xml.i new file mode 100644 index 000000000..eae10d312 --- /dev/null +++ b/interface-definitions/include/bgp/afi-route-map-export-import.xml.i @@ -0,0 +1,34 @@ +<!-- include start from bgp/afi-route-map.xml.i --> +<leafNode name="export"> + <properties> + <help>Route-map to filter outgoing route updates</help> + <completionHelp> + <path>policy route-map</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Route map name</description> + </valueHelp> + <constraint> + <regex>^[-_a-zA-Z0-9.]+$</regex> + </constraint> + <constraintErrorMessage>Name of route-map can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage> + </properties> +</leafNode> +<leafNode name="import"> + <properties> + <help>Route-map to filter incoming route updates</help> + <completionHelp> + <path>policy route-map</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Route map name</description> + </valueHelp> + <constraint> + <regex>^[-_a-zA-Z0-9.]+$</regex> + </constraint> + <constraintErrorMessage>Name of route-map can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-route-map-vpn.xml.i b/interface-definitions/include/bgp/afi-route-map-vpn.xml.i new file mode 100644 index 000000000..e6be113c5 --- /dev/null +++ b/interface-definitions/include/bgp/afi-route-map-vpn.xml.i @@ -0,0 +1,17 @@ +<!-- include start from bgp/afi-route-map-vpn.xml.i --> +<node name="route-map"> + <properties> + <help>Route-map to filter route updates to/from this peer</help> + </properties> + <children> + <node name="vpn"> + <properties> + <help>Between current address-family and VPN</help> + </properties> + <children> + #include <include/bgp/afi-route-map-export-import.xml.i> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-route-map.xml.i b/interface-definitions/include/bgp/afi-route-map.xml.i index 24a5ddd12..0b6178176 100644 --- a/interface-definitions/include/bgp/afi-route-map.xml.i +++ b/interface-definitions/include/bgp/afi-route-map.xml.i @@ -4,38 +4,7 @@ <help>Route-map to filter route updates to/from this peer</help> </properties> <children> - <leafNode name="export"> - <properties> - <help>Route-map to filter outgoing route updates</help> - <completionHelp> - <path>policy route-map</path> - </completionHelp> - <valueHelp> - <format>txt</format> - <description>Route map name</description> - </valueHelp> - <constraint> - <regex>^[-_a-zA-Z0-9.]+$</regex> - </constraint> - <constraintErrorMessage>Name of route-map can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage> - </properties> - </leafNode> - <leafNode name="import"> - <properties> - <help>Route-map to filter incoming route updates</help> - <completionHelp> - <path>policy route-map</path> - </completionHelp> - <valueHelp> - <format>txt</format> - <description>Route map name</description> - </valueHelp> - <constraint> - <regex>^[-_a-zA-Z0-9.]+$</regex> - </constraint> - <constraintErrorMessage>Name of route-map can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage> - </properties> - </leafNode> + #include <include/bgp/afi-route-map-export-import.xml.i> </children> </node> <!-- include end --> diff --git a/interface-definitions/include/bgp/afi-route-target-vpn.xml.i b/interface-definitions/include/bgp/afi-route-target-vpn.xml.i new file mode 100644 index 000000000..1dc184a02 --- /dev/null +++ b/interface-definitions/include/bgp/afi-route-target-vpn.xml.i @@ -0,0 +1,52 @@ +<!-- include start from bgp/route-target-both.xml.i --> +<node name="route-target"> + <properties> + <help>Specify route distinguisher</help> + </properties> + <children> + <node name="vpn"> + <properties> + <help>Between current address-family and VPN</help> + </properties> + <children> + <leafNode name="both"> + <properties> + <help>Route Target both import and export</help> + <valueHelp> + <format>txt</format> + <description>Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)</description> + </valueHelp> + <constraint> + <validator name="bgp-route-target" argument="--multi"/> + </constraint> + </properties> + </leafNode> + <leafNode name="import"> + <properties> + <help>Route Target import</help> + <valueHelp> + <format>txt</format> + <description>Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)</description> + </valueHelp> + <constraint> + <validator name="bgp-route-target" argument="--multi"/> + </constraint> + </properties> + </leafNode> + <leafNode name="export"> + <properties> + <help>Route Target export</help> + <valueHelp> + <format>txt</format> + <description>Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)</description> + </valueHelp> + <constraint> + <validator name="bgp-route-target" argument="--multi"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-description.xml.i b/interface-definitions/include/bgp/neighbor-description.xml.i deleted file mode 100644 index 3095d2560..000000000 --- a/interface-definitions/include/bgp/neighbor-description.xml.i +++ /dev/null @@ -1,7 +0,0 @@ -<!-- include start from bgp/neighbor-description.xml.i --> -<leafNode name="description"> - <properties> - <help>Neighbor specific description</help> - </properties> -</leafNode> -<!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-local-as.xml.i b/interface-definitions/include/bgp/neighbor-local-as.xml.i index 28c6b72b6..8868e3093 100644 --- a/interface-definitions/include/bgp/neighbor-local-as.xml.i +++ b/interface-definitions/include/bgp/neighbor-local-as.xml.i @@ -1,22 +1,29 @@ <!-- include start from bgp/neighbor-local-as.xml.i --> <tagNode name="local-as"> <properties> - <help>Local AS number [REQUIRED]</help> + <help>Specify alternate ASN for this BGP process</help> <valueHelp> <format>u32:1-4294967294</format> - <description>Local AS number</description> + <description>Autonomous System Number (ASN)</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-4294967294"/> </constraint> </properties> <children> - <leafNode name="no-prepend"> + <node name="no-prepend"> <properties> - <help>Disable prepending local-as to updates from EBGP peers</help> - <valueless/> + <help>Disable prepending local-as from/to updates for eBGP peers</help> </properties> - </leafNode> + <children> + <leafNode name="replace-as"> + <properties> + <help>Prepend only local-as from/to updates for eBGP peers</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> </children> </tagNode> <!-- include end --> diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i index 5080ce588..2dfae517e 100644 --- a/interface-definitions/include/bgp/protocol-common-config.xml.i +++ b/interface-definitions/include/bgp/protocol-common-config.xml.i @@ -93,6 +93,9 @@ </tagNode> </children> </node> + #include <include/bgp/afi-export-import.xml.i> + #include <include/bgp/afi-label.xml.i> + #include <include/bgp/afi-maximum-paths.xml.i> <tagNode name="network"> <properties> <help>BGP network</help> @@ -114,7 +117,9 @@ #include <include/route-map.xml.i> </children> </tagNode> - #include <include/bgp/afi-maximum-paths.xml.i> + #include <include/bgp/afi-rd.xml.i> + #include <include/bgp/afi-route-map-vpn.xml.i> + #include <include/bgp/afi-route-target-vpn.xml.i> <node name="redistribute"> <properties> <help>Redistribute routes from other protocols into BGP</help> @@ -372,18 +377,7 @@ </constraint> </properties> <children> - <leafNode name="rd"> - <properties> - <help>Route Distinguisher</help> - <valueHelp> - <format>txt</format> - <description>Route Distinguisher, asn:xxx</description> - </valueHelp> - <constraint> - <regex>^[0-9]{1,10}:[0-9]{1,5}$</regex> - </constraint> - </properties> - </leafNode> + #include <include/bgp/route-distinguisher.xml.i> <leafNode name="label"> <properties> <help>MPLS label value assigned to route</help> @@ -489,6 +483,9 @@ </tagNode> </children> </node> + #include <include/bgp/afi-export-import.xml.i> + #include <include/bgp/afi-label.xml.i> + #include <include/bgp/afi-maximum-paths.xml.i> <tagNode name="network"> <properties> <help>BGP network</help> @@ -501,22 +498,13 @@ </constraint> </properties> <children> - <leafNode name="path-limit"> - <properties> - <help>AS-path hopcount limit</help> - <valueHelp> - <format>u32:0-255</format> - <description>AS path hop count limit</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 0-255"/> - </constraint> - </properties> - </leafNode> + #include <include/bgp/afi-path-limit.xml.i> #include <include/route-map.xml.i> </children> </tagNode> - #include <include/bgp/afi-maximum-paths.xml.i> + #include <include/bgp/afi-rd.xml.i> + #include <include/bgp/afi-route-map-vpn.xml.i> + #include <include/bgp/afi-route-target-vpn.xml.i> <node name="redistribute"> <properties> <help>Redistribute routes from other protocols into BGP</help> @@ -672,18 +660,7 @@ </constraint> </properties> <children> - <leafNode name="path-limit"> - <properties> - <help>AS-path hopcount limit</help> - <valueHelp> - <format>u32:0-255</format> - <description>AS path hop count limit</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 0-255"/> - </constraint> - </properties> - </leafNode> + #include <include/bgp/afi-path-limit.xml.i> #include <include/route-map.xml.i> </children> </tagNode> @@ -772,18 +749,7 @@ </constraint> </properties> <children> - <leafNode name="rd"> - <properties> - <help>Route Distinguisher</help> - <valueHelp> - <format>txt</format> - <description>Route Distinguisher, asn:xxx</description> - </valueHelp> - <constraint> - <regex>^[0-9]{1,10}:[0-9]{1,5}$</regex> - </constraint> - </properties> - </leafNode> + #include <include/bgp/route-distinguisher.xml.i> <leafNode name="label"> <properties> <help>MPLS label value assigned to route</help> @@ -994,9 +960,9 @@ </constraint> </properties> </leafNode> + #include <include/generic-description.xml.i> #include <include/bgp/neighbor-bfd.xml.i> #include <include/bgp/neighbor-capability.xml.i> - #include <include/bgp/neighbor-description.xml.i> #include <include/bgp/neighbor-disable-capability-negotiation.xml.i> #include <include/bgp/neighbor-disable-connected-check.xml.i> #include <include/bgp/neighbor-ebgp-multihop.xml.i> @@ -1008,6 +974,7 @@ <children> #include <include/bgp/peer-group.xml.i> #include <include/bgp/remote-as.xml.i> + #include <include/source-interface.xml.i> <node name="v6only"> <properties> <help>Enable BGP with v6 link-local only</help> @@ -1286,12 +1253,6 @@ </constraint> </properties> </leafNode> - <leafNode name="no-ipv4-unicast"> - <properties> - <help>Deactivate IPv4 unicast for a peer by default</help> - <valueless/> - </properties> - </leafNode> </children> </node> <leafNode name="deterministic-med"> @@ -1452,9 +1413,9 @@ #include <include/bgp/neighbor-afi-l2vpn-evpn.xml.i> </children> </node> + #include <include/generic-description.xml.i> #include <include/bgp/neighbor-bfd.xml.i> #include <include/bgp/neighbor-capability.xml.i> - #include <include/bgp/neighbor-description.xml.i> #include <include/bgp/neighbor-disable-capability-negotiation.xml.i> #include <include/bgp/neighbor-disable-connected-check.xml.i> #include <include/bgp/neighbor-ebgp-multihop.xml.i> diff --git a/interface-definitions/include/bgp/route-distinguisher.xml.i b/interface-definitions/include/bgp/route-distinguisher.xml.i new file mode 100644 index 000000000..6d0aa3ef1 --- /dev/null +++ b/interface-definitions/include/bgp/route-distinguisher.xml.i @@ -0,0 +1,14 @@ +<!-- include start from bgp/route-distinguisher.xml.i --> +<leafNode name="rd"> + <properties> + <help>Route Distinguisher</help> + <valueHelp> + <format>ASN:NN_OR_IP-ADDRESS:NN</format> + <description>Route Distinguisher, (x.x.x.x:yyy|xxxx:yyyy)</description> + </valueHelp> + <constraint> + <regex>^((25[0-5]|2[0-4][0-9]|[1][0-9][0-9]|[1-9][0-9]|[0-9]?)(\.(25[0-5]|2[0-4][0-9]|[1][0-9][0-9]|[1-9][0-9]|[0-9]?)){3}|[0-9]{1,10}):[0-9]{1,5}$</regex> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/route-target.xml.i b/interface-definitions/include/bgp/route-target.xml.i deleted file mode 100644 index 674b6db15..000000000 --- a/interface-definitions/include/bgp/route-target.xml.i +++ /dev/null @@ -1,45 +0,0 @@ -<!-- include start from bgp/route-target.xml.i --> -<node name="route-target"> - <properties> - <help>Route Target</help> - </properties> - <children> - <leafNode name="both"> - <properties> - <help>Route Target both import and export</help> - <valueHelp> - <format>txt</format> - <description>Route target (x.x.x.x:yyy|xxxx:yyyy)</description> - </valueHelp> - <constraint> - <regex>^((25[0-5]|2[0-4][0-9]|[1][0-9][0-9]|[1-9][0-9]|[0-9]?)(\.(25[0-5]|2[0-4][0-9]|[1][0-9][0-9]|[1-9][0-9]|[0-9]?)){3}|[0-9]{1,10}):[0-9]{1,5}$</regex> - </constraint> - </properties> - </leafNode> - <leafNode name="export"> - <properties> - <help>Route Target export</help> - <valueHelp> - <format>txt</format> - <description>Route target (x.x.x.x:yyy|xxxx:yyyy)</description> - </valueHelp> - <constraint> - <regex>^((25[0-5]|2[0-4][0-9]|[1][0-9][0-9]|[1-9][0-9]|[0-9]?)(\.(25[0-5]|2[0-4][0-9]|[1][0-9][0-9]|[1-9][0-9]|[0-9]?)){3}|[0-9]{1,10}):[0-9]{1,5}$</regex> - </constraint> - </properties> - </leafNode> - <leafNode name="import"> - <properties> - <help>Route Target import</help> - <valueHelp> - <format>txt</format> - <description>Route target (x.x.x.x:yyy|xxxx:yyyy)</description> - </valueHelp> - <constraint> - <regex>^((25[0-5]|2[0-4][0-9]|[1][0-9][0-9]|[1-9][0-9]|[0-9]?)(\.(25[0-5]|2[0-4][0-9]|[1][0-9][0-9]|[1-9][0-9]|[0-9]?)){3}|[0-9]{1,10}):[0-9]{1,5}$</regex> - </constraint> - </properties> - </leafNode> - </children> -</node> -<!-- include end --> diff --git a/interface-definitions/include/conntrack-module-disable.xml.i b/interface-definitions/include/conntrack-module-disable.xml.i deleted file mode 100644 index f891225e0..000000000 --- a/interface-definitions/include/conntrack-module-disable.xml.i +++ /dev/null @@ -1,8 +0,0 @@ -<!-- include start from conntrack-module-disable.xml.i --> -<leafNode name="disable"> - <properties> - <help>Disable connection tracking helper</help> - <valueless/> - </properties> -</leafNode> -<!-- include end --> diff --git a/interface-definitions/include/dhcp/domain-name.xml.i b/interface-definitions/include/dhcp/domain-name.xml.i new file mode 100644 index 000000000..410e27d29 --- /dev/null +++ b/interface-definitions/include/dhcp/domain-name.xml.i @@ -0,0 +1,11 @@ +<!-- include start from dhcp/domain-name.xml.i --> +<leafNode name="domain-name"> + <properties> + <help>Client Domain Name</help> + <constraint> + <validator name="fqdn"/> + </constraint> + <constraintErrorMessage>Invalid domain name (RFC 1123 section 2).\nMay only contain letters, numbers and .-_</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/dhcp-server-domain-search.xml.i b/interface-definitions/include/dhcp/domain-search.xml.i index 4fc55097b..bcc8fcd12 100644 --- a/interface-definitions/include/dhcp-server-domain-search.xml.i +++ b/interface-definitions/include/dhcp/domain-search.xml.i @@ -1,4 +1,4 @@ -<!-- include start from dhcp-server-domain-search.xml.i --> +<!-- include start from dhcp/domain-search.xml.i --> <leafNode name="domain-search"> <properties> <help>Client Domain Name search list</help> diff --git a/interface-definitions/include/dhcp/ntp-server.xml.i b/interface-definitions/include/dhcp/ntp-server.xml.i new file mode 100644 index 000000000..32d8207e5 --- /dev/null +++ b/interface-definitions/include/dhcp/ntp-server.xml.i @@ -0,0 +1,15 @@ +<!-- include start from dhcp/ntp-server.xml.i --> + <leafNode name="ntp-server"> + <properties> + <help>IP address of NTP server</help> + <valueHelp> + <format>ipv4</format> + <description>NTP server IPv4 address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <multi/> + </properties> + </leafNode> +<!-- include end --> diff --git a/interface-definitions/include/dhcp/ping-check.xml.i b/interface-definitions/include/dhcp/ping-check.xml.i new file mode 100644 index 000000000..a506f68e4 --- /dev/null +++ b/interface-definitions/include/dhcp/ping-check.xml.i @@ -0,0 +1,8 @@ +<!-- include start from dhcp/ping-check.xml.i --> +<leafNode name="ping-check"> + <properties> + <help>Sends ICMP Echo request to the address being assigned</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/common-rule.xml.i b/interface-definitions/include/firewall/common-rule.xml.i index 1ee8da73d..a59c0b390 100644 --- a/interface-definitions/include/firewall/common-rule.xml.i +++ b/interface-definitions/include/firewall/common-rule.xml.i @@ -110,7 +110,7 @@ <description>Both TCP and UDP</description> </valueHelp> <valueHelp> - <format>0-255</format> + <format>u32:0-255</format> <description>IP protocol number</description> </valueHelp> <valueHelp> diff --git a/interface-definitions/include/interface/adjust-mss.xml.i b/interface-definitions/include/interface/adjust-mss.xml.i new file mode 100644 index 000000000..57019f02c --- /dev/null +++ b/interface-definitions/include/interface/adjust-mss.xml.i @@ -0,0 +1,23 @@ +<!-- include start from interface/adjust-mss.xml.i --> +<!-- https://datatracker.ietf.org/doc/html/rfc6691 --> +<leafNode name="adjust-mss"> + <properties> + <help>Adjust TCP MSS value</help> + <completionHelp> + <list>clamp-mss-to-pmtu</list> + </completionHelp> + <valueHelp> + <format>clamp-mss-to-pmtu</format> + <description>Automatically sets the MSS to the proper value</description> + </valueHelp> + <valueHelp> + <format>u32:500-65535</format> + <description>TCP Maximum segment size in bytes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 500-65535"/> + <regex>^(clamp-mss-to-pmtu)$</regex> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/interface-arp-cache-timeout.xml.i b/interface-definitions/include/interface/arp-cache-timeout.xml.i index b269fecd8..cb01d0525 100644 --- a/interface-definitions/include/interface/interface-arp-cache-timeout.xml.i +++ b/interface-definitions/include/interface/arp-cache-timeout.xml.i @@ -1,9 +1,9 @@ -<!-- include start from interface/interface-arp-cache-timeout.xml.i --> +<!-- include start from interface/arp-cache-timeout.xml.i --> <leafNode name="arp-cache-timeout"> <properties> <help>ARP cache entry timeout in seconds</help> <valueHelp> - <format>1-86400</format> + <format>u32:1-86400</format> <description>ARP cache entry timout in seconds (default 30)</description> </valueHelp> <constraint> diff --git a/interface-definitions/include/interface/interface-description.xml.i b/interface-definitions/include/interface/description.xml.i index d618b50d2..8579cf7d1 100644 --- a/interface-definitions/include/interface/interface-description.xml.i +++ b/interface-definitions/include/interface/description.xml.i @@ -1,4 +1,4 @@ -<!-- include start from interface/interface-description.xml.i --> +<!-- include start from interface/description.xml.i --> <leafNode name="description"> <properties> <help>Interface specific description</help> diff --git a/interface-definitions/include/interface/dhcpv6-options.xml.i b/interface-definitions/include/interface/dhcpv6-options.xml.i index ca478a3eb..d1abf4a90 100644 --- a/interface-definitions/include/interface/dhcpv6-options.xml.i +++ b/interface-definitions/include/interface/dhcpv6-options.xml.i @@ -38,7 +38,7 @@ <properties> <help>Request IPv6 prefix length from peer</help> <valueHelp> - <format>32-64</format> + <format>u32:32-64</format> <description>Length of delegated prefix</description> </valueHelp> <constraint> @@ -71,7 +71,7 @@ <properties> <help>Interface site-Level aggregator (SLA)</help> <valueHelp> - <format>0-128</format> + <format>u32:0-128</format> <description>Decimal integer which fits in the length of SLA IDs</description> </valueHelp> <constraint> diff --git a/interface-definitions/include/interface/interface-dial-on-demand.xml.i b/interface-definitions/include/interface/dial-on-demand.xml.i index 66edd9678..30e8c7e97 100644 --- a/interface-definitions/include/interface/interface-dial-on-demand.xml.i +++ b/interface-definitions/include/interface/dial-on-demand.xml.i @@ -1,4 +1,4 @@ -<!-- include start from interface/interface-dial-on-demand.xml.i --> +<!-- include start from interface/dial-on-demand.xml.i --> <leafNode name="connect-on-demand"> <properties> <help>Establishment connection automatically when traffic is sent</help> diff --git a/interface-definitions/include/interface/interface-disable-arp-filter.xml.i b/interface-definitions/include/interface/disable-arp-filter.xml.i index 49cddaf76..a69455d58 100644 --- a/interface-definitions/include/interface/interface-disable-arp-filter.xml.i +++ b/interface-definitions/include/interface/disable-arp-filter.xml.i @@ -1,4 +1,4 @@ -<!-- include start from interface/interface-disable-arp-filter.xml.i --> +<!-- include start from interface/disable-arp-filter.xml.i --> <leafNode name="disable-arp-filter"> <properties> <help>Disable ARP filter on this interface</help> diff --git a/interface-definitions/include/interface/disable-forwarding.xml.i b/interface-definitions/include/interface/disable-forwarding.xml.i new file mode 100644 index 000000000..45382ec95 --- /dev/null +++ b/interface-definitions/include/interface/disable-forwarding.xml.i @@ -0,0 +1,8 @@ +<!-- include start from interface/disable-forwarding.xml.i --> +<leafNode name="disable-forwarding"> + <properties> + <help>Disable IP forwarding on this interface</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/interface-disable-link-detect.xml.i b/interface-definitions/include/interface/disable-link-detect.xml.i index c528885b2..b101ec292 100644 --- a/interface-definitions/include/interface/interface-disable-link-detect.xml.i +++ b/interface-definitions/include/interface/disable-link-detect.xml.i @@ -1,4 +1,4 @@ -<!-- include start from interface/interface-disable-link-detect.xml.i --> +<!-- include start from interface/disable-link-detect.xml.i --> <leafNode name="disable-link-detect"> <properties> <help>Ignore link state changes</help> diff --git a/interface-definitions/include/interface/interface-disable.xml.i b/interface-definitions/include/interface/disable.xml.i index d90e6395b..b76bd3f53 100644 --- a/interface-definitions/include/interface/interface-disable.xml.i +++ b/interface-definitions/include/interface/disable.xml.i @@ -1,4 +1,4 @@ -<!-- include start from interface/interface-disable.xml.i --> +<!-- include start from interface/disable.xml.i --> <leafNode name="disable"> <properties> <help>Administratively disable interface</help> diff --git a/interface-definitions/include/interface/interface-eapol.xml.i b/interface-definitions/include/interface/eapol.xml.i index 270ec5b13..c4cdeae0c 100644 --- a/interface-definitions/include/interface/interface-eapol.xml.i +++ b/interface-definitions/include/interface/eapol.xml.i @@ -1,4 +1,4 @@ -<!-- include start from interface/interface-eapol.xml.i --> +<!-- include start from interface/eapol.xml.i --> <node name="eapol"> <properties> <help>Extensible Authentication Protocol over Local Area Network</help> diff --git a/interface-definitions/include/interface/interface-enable-arp-accept.xml.i b/interface-definitions/include/interface/enable-arp-accept.xml.i index 7c5d51857..90f6bc3db 100644 --- a/interface-definitions/include/interface/interface-enable-arp-accept.xml.i +++ b/interface-definitions/include/interface/enable-arp-accept.xml.i @@ -1,4 +1,4 @@ -<!-- include start from interface/interface-enable-arp-accept.xml.i --> +<!-- include start from interface/enable-arp-accept.xml.i --> <leafNode name="enable-arp-accept"> <properties> <help>Enable ARP accept on this interface</help> diff --git a/interface-definitions/include/interface/interface-enable-arp-announce.xml.i b/interface-definitions/include/interface/enable-arp-announce.xml.i index f44599c54..cf02fce0b 100644 --- a/interface-definitions/include/interface/interface-enable-arp-announce.xml.i +++ b/interface-definitions/include/interface/enable-arp-announce.xml.i @@ -1,4 +1,4 @@ -<!-- include start from interface/interface-enable-arp-announce.xml.i --> +<!-- include start from interface/enable-arp-announce.xml.i --> <leafNode name="enable-arp-announce"> <properties> <help>Enable ARP announce on this interface</help> diff --git a/interface-definitions/include/interface/interface-enable-arp-ignore.xml.i b/interface-definitions/include/interface/enable-arp-ignore.xml.i index 3ea39613c..5bb444f35 100644 --- a/interface-definitions/include/interface/interface-enable-arp-ignore.xml.i +++ b/interface-definitions/include/interface/enable-arp-ignore.xml.i @@ -1,4 +1,4 @@ -<!-- include start from interface/interface-enable-arp-ignore.xml.i --> +<!-- include start from interface/enable-arp-ignore.xml.i --> <leafNode name="enable-arp-ignore"> <properties> <help>Enable ARP ignore on this interface</help> diff --git a/interface-definitions/include/interface/interface-enable-proxy-arp.xml.i b/interface-definitions/include/interface/enable-proxy-arp.xml.i index dbdeeb7a7..27e497f84 100644 --- a/interface-definitions/include/interface/interface-enable-proxy-arp.xml.i +++ b/interface-definitions/include/interface/enable-proxy-arp.xml.i @@ -1,4 +1,4 @@ -<!-- include start from interface/interface-enable-proxy-arp.xml.i --> +<!-- include start from interface/enable-proxy-arp.xml.i --> <leafNode name="enable-proxy-arp"> <properties> <help>Enable proxy-arp on this interface</help> diff --git a/interface-definitions/include/interface/interface-hw-id.xml.i b/interface-definitions/include/interface/hw-id.xml.i index 989cd9cb7..a3a1eec7c 100644 --- a/interface-definitions/include/interface/interface-hw-id.xml.i +++ b/interface-definitions/include/interface/hw-id.xml.i @@ -1,4 +1,4 @@ -<!-- include start from interface/interface-hw-id.xml.i --> +<!-- include start from interface/hw-id.xml.i --> <leafNode name="hw-id"> <properties> <help>Associate Ethernet Interface with given Media Access Control (MAC) address</help> diff --git a/interface-definitions/include/interface/interface-disable-forwarding.xml.i b/interface-definitions/include/interface/interface-disable-forwarding.xml.i deleted file mode 100644 index cb6ef0475..000000000 --- a/interface-definitions/include/interface/interface-disable-forwarding.xml.i +++ /dev/null @@ -1,8 +0,0 @@ -<!-- include start from interface/interface-disable-forwarding.xml.i --> -<leafNode name="disable-forwarding"> - <properties> - <help>Disable IPv4 forwarding on this interface</help> - <valueless/> - </properties> -</leafNode> -<!-- include end --> diff --git a/interface-definitions/include/interface/interface-ipv4-options.xml.i b/interface-definitions/include/interface/interface-ipv4-options.xml.i deleted file mode 100644 index c2d0677b7..000000000 --- a/interface-definitions/include/interface/interface-ipv4-options.xml.i +++ /dev/null @@ -1,18 +0,0 @@ -<!-- include start from interface/interface-ipv4-options.xml.i --> -<node name="ip"> - <properties> - <help>IPv4 routing parameters</help> - </properties> - <children> - #include <include/interface/interface-arp-cache-timeout.xml.i> - #include <include/interface/interface-disable-arp-filter.xml.i> - #include <include/interface/interface-disable-forwarding.xml.i> - #include <include/interface/interface-enable-arp-accept.xml.i> - #include <include/interface/interface-enable-arp-announce.xml.i> - #include <include/interface/interface-enable-arp-ignore.xml.i> - #include <include/interface/interface-enable-proxy-arp.xml.i> - #include <include/interface/interface-proxy-arp-pvlan.xml.i> - #include <include/interface/interface-source-validation.xml.i> - </children> -</node> -<!-- include end --> diff --git a/interface-definitions/include/interface/ipv4-options.xml.i b/interface-definitions/include/interface/ipv4-options.xml.i new file mode 100644 index 000000000..bca1229c6 --- /dev/null +++ b/interface-definitions/include/interface/ipv4-options.xml.i @@ -0,0 +1,19 @@ +<!-- include start from interface/ipv4-options.xml.i --> +<node name="ip"> + <properties> + <help>IPv4 routing parameters</help> + </properties> + <children> + #include <include/interface/adjust-mss.xml.i> + #include <include/interface/arp-cache-timeout.xml.i> + #include <include/interface/disable-arp-filter.xml.i> + #include <include/interface/disable-forwarding.xml.i> + #include <include/interface/enable-arp-accept.xml.i> + #include <include/interface/enable-arp-announce.xml.i> + #include <include/interface/enable-arp-ignore.xml.i> + #include <include/interface/enable-proxy-arp.xml.i> + #include <include/interface/proxy-arp-pvlan.xml.i> + #include <include/interface/source-validation.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/interface/ipv6-disable-forwarding.xml.i b/interface-definitions/include/interface/ipv6-disable-forwarding.xml.i deleted file mode 100644 index 4adb77d1b..000000000 --- a/interface-definitions/include/interface/ipv6-disable-forwarding.xml.i +++ /dev/null @@ -1,8 +0,0 @@ -<!-- include start from interface/ipv6-disable-forwarding.xml.i --> -<leafNode name="disable-forwarding"> - <properties> - <help>Disable IPv6 forwarding on this interface</help> - <valueless/> - </properties> -</leafNode> -<!-- include end --> diff --git a/interface-definitions/include/interface/ipv6-dup-addr-detect-transmits.xml.i b/interface-definitions/include/interface/ipv6-dup-addr-detect-transmits.xml.i index 2b5ec0281..babe6d20f 100644 --- a/interface-definitions/include/interface/ipv6-dup-addr-detect-transmits.xml.i +++ b/interface-definitions/include/interface/ipv6-dup-addr-detect-transmits.xml.i @@ -3,12 +3,12 @@ <properties> <help>Number of NS messages to send while performing DAD (default: 1)</help> <valueHelp> - <format>1-n</format> - <description>Number of NS messages to send while performing DAD</description> + <format>u32:0</format> + <description>Disable Duplicate Address Dectection (DAD)</description> </valueHelp> <valueHelp> - <format>0</format> - <description>Disable Duplicate Address Dectection (DAD)</description> + <format>u32:1-n</format> + <description>Number of NS messages to send while performing DAD</description> </valueHelp> <constraint> <validator name="numeric" argument="--non-negative"/> diff --git a/interface-definitions/include/interface/interface-ipv6-options.xml.i b/interface-definitions/include/interface/ipv6-options.xml.i index dcd5a8710..f740ce0c2 100644 --- a/interface-definitions/include/interface/interface-ipv6-options.xml.i +++ b/interface-definitions/include/interface/ipv6-options.xml.i @@ -1,11 +1,12 @@ -<!-- include start from interface/interface-ipv6-options.xml.i --> +<!-- include start from interface/ipv6-options.xml.i --> <node name="ipv6"> <properties> <help>IPv6 routing parameters</help> </properties> <children> + #include <include/interface/adjust-mss.xml.i> + #include <include/interface/disable-forwarding.xml.i> #include <include/interface/ipv6-address.xml.i> - #include <include/interface/ipv6-disable-forwarding.xml.i> #include <include/interface/ipv6-dup-addr-detect-transmits.xml.i> </children> </node> diff --git a/interface-definitions/include/interface/interface-mac.xml.i b/interface-definitions/include/interface/mac.xml.i index d7107ad23..705330dce 100644 --- a/interface-definitions/include/interface/interface-mac.xml.i +++ b/interface-definitions/include/interface/mac.xml.i @@ -1,4 +1,4 @@ -<!-- include start from interface/interface-mac.xml.i --> +<!-- include start from interface/mac.xml.i --> <leafNode name="mac"> <properties> <help>Media Access Control (MAC) address</help> diff --git a/interface-definitions/include/interface/interface-mirror.xml.i b/interface-definitions/include/interface/mirror.xml.i index b3b45fb43..2959551f0 100644 --- a/interface-definitions/include/interface/interface-mirror.xml.i +++ b/interface-definitions/include/interface/mirror.xml.i @@ -1,4 +1,4 @@ -<!-- include start from interface/interface-mirror.xml.i --> +<!-- include start from interface/mirror.xml.i --> <node name="mirror"> <properties> <help>Incoming/outgoing packet mirroring destination</help> diff --git a/interface-definitions/include/interface/interface-mtu-1200-16000.xml.i b/interface-definitions/include/interface/mtu-1200-16000.xml.i index 3241ba912..fab053fc1 100644 --- a/interface-definitions/include/interface/interface-mtu-1200-16000.xml.i +++ b/interface-definitions/include/interface/mtu-1200-16000.xml.i @@ -1,9 +1,9 @@ -<!-- include start from interface/interface-mtu-1200-16000.xml.i --> +<!-- include start from interface/mtu-1200-16000.xml.i --> <leafNode name="mtu"> <properties> <help>Maximum Transmission Unit (MTU)</help> <valueHelp> - <format>1200-16000</format> + <format>u32:1200-16000</format> <description>Maximum Transmission Unit in byte</description> </valueHelp> <constraint> diff --git a/interface-definitions/include/interface/interface-mtu-1450-16000.xml.i b/interface-definitions/include/interface/mtu-1450-16000.xml.i index 0a35bbbaa..1e71eab01 100644 --- a/interface-definitions/include/interface/interface-mtu-1450-16000.xml.i +++ b/interface-definitions/include/interface/mtu-1450-16000.xml.i @@ -1,9 +1,9 @@ -<!-- include start from interface/interface-mtu-1450-16000.xml.i --> +<!-- include start from interface/mtu-1450-16000.xml.i --> <leafNode name="mtu"> <properties> <help>Maximum Transmission Unit (MTU)</help> <valueHelp> - <format>1450-16000</format> + <format>u32:1450-16000</format> <description>Maximum Transmission Unit in byte</description> </valueHelp> <constraint> diff --git a/interface-definitions/include/interface/interface-mtu-64-8024.xml.i b/interface-definitions/include/interface/mtu-64-8024.xml.i index f75de02ba..30c77f768 100644 --- a/interface-definitions/include/interface/interface-mtu-64-8024.xml.i +++ b/interface-definitions/include/interface/mtu-64-8024.xml.i @@ -1,9 +1,9 @@ -<!-- include start from interface/interface-mtu-68-8024.xml.i --> +<!-- include start from interface/mtu-68-8024.xml.i --> <leafNode name="mtu"> <properties> <help>Maximum Transmission Unit (MTU)</help> <valueHelp> - <format>64-8024</format> + <format>u32:64-8024</format> <description>Maximum Transmission Unit in byte</description> </valueHelp> <constraint> diff --git a/interface-definitions/include/interface/interface-mtu-68-1500.xml.i b/interface-definitions/include/interface/mtu-68-1500.xml.i index 9e6fe8760..693e0be7e 100644 --- a/interface-definitions/include/interface/interface-mtu-68-1500.xml.i +++ b/interface-definitions/include/interface/mtu-68-1500.xml.i @@ -1,9 +1,9 @@ -<!-- include start from interface/interface-mtu-68-1500.xml.i --> +<!-- include start from interface/mtu-68-1500.xml.i --> <leafNode name="mtu"> <properties> <help>Maximum Transmission Unit (MTU)</help> <valueHelp> - <format>68-1500</format> + <format>u32:68-1500</format> <description>Maximum Transmission Unit in byte</description> </valueHelp> <constraint> diff --git a/interface-definitions/include/interface/interface-mtu-68-16000.xml.i b/interface-definitions/include/interface/mtu-68-16000.xml.i index 83af7bbd4..cb666f470 100644 --- a/interface-definitions/include/interface/interface-mtu-68-16000.xml.i +++ b/interface-definitions/include/interface/mtu-68-16000.xml.i @@ -1,9 +1,9 @@ -<!-- include start from interface/interface-mtu-68-16000.xml.i --> +<!-- include start from interface/mtu-68-16000.xml.i --> <leafNode name="mtu"> <properties> <help>Maximum Transmission Unit (MTU)</help> <valueHelp> - <format>68-16000</format> + <format>u32:68-16000</format> <description>Maximum Transmission Unit in byte</description> </valueHelp> <constraint> diff --git a/interface-definitions/include/interface/interface-parameters-dont-fragment.xml.i b/interface-definitions/include/interface/parameters-dont-fragment.xml.i index 166c31115..d34f0a97b 100644 --- a/interface-definitions/include/interface/interface-parameters-dont-fragment.xml.i +++ b/interface-definitions/include/interface/parameters-dont-fragment.xml.i @@ -1,4 +1,4 @@ -<!-- include start from interface/interface-parameters-df.xml.i --> +<!-- include start from interface/parameters-df.xml.i --> <leafNode name="dont-fragment"> <properties> <help>Specifies the usage of the dont fragment (DF) bit</help> diff --git a/interface-definitions/include/interface/interface-parameters-flowlabel.xml.i b/interface-definitions/include/interface/parameters-flowlabel.xml.i index ed075e40d..bd0d1e070 100644 --- a/interface-definitions/include/interface/interface-parameters-flowlabel.xml.i +++ b/interface-definitions/include/interface/parameters-flowlabel.xml.i @@ -1,10 +1,17 @@ -<!-- include start from interface/interface-parameters-flowlabel.xml.i --> +<!-- include start from interface/parameters-flowlabel.xml.i --> <leafNode name="flowlabel"> <properties> <help>Specifies the flow label to use in outgoing packets</help> + <completionHelp> + <list>inherit</list> + </completionHelp> <valueHelp> - <format>0x0-0x0FFFFF</format> - <description>Tunnel key, 'inherit' or hex value</description> + <format>inherit</format> + <description>Copy field from original header</description> + </valueHelp> + <valueHelp> + <format>0x0-0x0fffff</format> + <description>Tunnel key, or hex value</description> </valueHelp> <constraint> <regex>^((0x){0,1}(0?[0-9A-Fa-f]{1,5})|inherit)$</regex> diff --git a/interface-definitions/include/interface/interface-parameters-key.xml.i b/interface-definitions/include/interface/parameters-key.xml.i index 6c59f7879..25a6c0303 100644 --- a/interface-definitions/include/interface/interface-parameters-key.xml.i +++ b/interface-definitions/include/interface/parameters-key.xml.i @@ -1,4 +1,4 @@ -<!-- include start from interface/interface-parameters-key.xml.i --> +<!-- include start from interface/parameters-key.xml.i --> <leafNode name="key"> <properties> <help>Tunnel key (only GRE tunnels)</help> diff --git a/interface-definitions/include/interface/interface-parameters-tos.xml.i b/interface-definitions/include/interface/parameters-tos.xml.i index 83b4e0671..1b342a43e 100644 --- a/interface-definitions/include/interface/interface-parameters-tos.xml.i +++ b/interface-definitions/include/interface/parameters-tos.xml.i @@ -3,7 +3,7 @@ <properties> <help>Specifies TOS value to use in outgoing packets</help> <valueHelp> - <format>0-99</format> + <format>u32:0-99</format> <description>Type of Service (TOS)</description> </valueHelp> <constraint> diff --git a/interface-definitions/include/interface/interface-parameters-ttl.xml.i b/interface-definitions/include/interface/parameters-ttl.xml.i index df193cf24..ade33b4a4 100644 --- a/interface-definitions/include/interface/interface-parameters-ttl.xml.i +++ b/interface-definitions/include/interface/parameters-ttl.xml.i @@ -1,13 +1,13 @@ -<!-- include start from interface/interface-parameters-ttl.xml.i --> +<!-- include start from interface/parameters-ttl.xml.i --> <leafNode name="ttl"> <properties> <help>Specifies TTL value to use in outgoing packets</help> <valueHelp> - <format>0</format> + <format>u32:0</format> <description>Inherit - copy value from original IP header</description> </valueHelp> <valueHelp> - <format>1-255</format> + <format>u32:1-255</format> <description>Time to Live</description> </valueHelp> <constraint> diff --git a/interface-definitions/include/interface/interface-proxy-arp-pvlan.xml.i b/interface-definitions/include/interface/proxy-arp-pvlan.xml.i index 153dfc072..c00b2fe85 100644 --- a/interface-definitions/include/interface/interface-proxy-arp-pvlan.xml.i +++ b/interface-definitions/include/interface/proxy-arp-pvlan.xml.i @@ -1,4 +1,4 @@ -<!-- include start from interface/interface-proxy-arp-pvlan.xml.i --> +<!-- include start from interface/proxy-arp-pvlan.xml.i --> <leafNode name="proxy-arp-pvlan"> <properties> <help>Enable private VLAN proxy ARP on this interface</help> diff --git a/interface-definitions/include/interface/interface-source-validation.xml.i b/interface-definitions/include/interface/source-validation.xml.i index 70914f2e9..f38065f4d 100644 --- a/interface-definitions/include/interface/interface-source-validation.xml.i +++ b/interface-definitions/include/interface/source-validation.xml.i @@ -1,4 +1,4 @@ -<!-- include start from interface/interface-source-validation.xml.i --> +<!-- include start from interface/source-validation.xml.i --> <leafNode name="source-validation"> <properties> <help>Source validation by reversed path (RFC3704)</help> diff --git a/interface-definitions/include/interface/vif-s.xml.i b/interface-definitions/include/interface/vif-s.xml.i index 17d1746be..e7ba6d193 100644 --- a/interface-definitions/include/interface/vif-s.xml.i +++ b/interface-definitions/include/interface/vif-s.xml.i @@ -2,6 +2,10 @@ <tagNode name="vif-s"> <properties> <help>QinQ TAG-S Virtual Local Area Network (VLAN) ID</help> + <valueHelp> + <format>u32:0-4094</format> + <description>QinQ Virtual Local Area Network (VLAN) ID</description> + </valueHelp> <constraint> <validator name="numeric" argument="--range 0-4094"/> </constraint> @@ -9,11 +13,11 @@ </properties> <children> #include <include/interface/address-ipv4-ipv6-dhcp.xml.i> - #include <include/interface/interface-description.xml.i> + #include <include/interface/description.xml.i> #include <include/interface/dhcp-options.xml.i> #include <include/interface/dhcpv6-options.xml.i> - #include <include/interface/interface-disable-link-detect.xml.i> - #include <include/interface/interface-disable.xml.i> + #include <include/interface/disable-link-detect.xml.i> + #include <include/interface/disable.xml.i> <leafNode name="protocol"> <properties> <help>Protocol used for service VLAN (default: 802.1ad)</help> @@ -35,10 +39,10 @@ </properties> <defaultValue>802.1ad</defaultValue> </leafNode> - #include <include/interface/interface-ipv4-options.xml.i> - #include <include/interface/interface-ipv6-options.xml.i> - #include <include/interface/interface-mac.xml.i> - #include <include/interface/interface-mtu-68-16000.xml.i> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> + #include <include/interface/mac.xml.i> + #include <include/interface/mtu-68-16000.xml.i> <tagNode name="vif-c"> <properties> <help>QinQ TAG-C Virtual Local Area Network (VLAN) ID</help> @@ -49,19 +53,19 @@ </properties> <children> #include <include/interface/address-ipv4-ipv6-dhcp.xml.i> - #include <include/interface/interface-description.xml.i> + #include <include/interface/description.xml.i> #include <include/interface/dhcp-options.xml.i> #include <include/interface/dhcpv6-options.xml.i> - #include <include/interface/interface-disable-link-detect.xml.i> - #include <include/interface/interface-disable.xml.i> - #include <include/interface/interface-ipv4-options.xml.i> - #include <include/interface/interface-ipv6-options.xml.i> - #include <include/interface/interface-mac.xml.i> - #include <include/interface/interface-mtu-68-16000.xml.i> - #include <include/interface/interface-vrf.xml.i> + #include <include/interface/disable-link-detect.xml.i> + #include <include/interface/disable.xml.i> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> + #include <include/interface/mac.xml.i> + #include <include/interface/mtu-68-16000.xml.i> + #include <include/interface/vrf.xml.i> </children> </tagNode> - #include <include/interface/interface-vrf.xml.i> + #include <include/interface/vrf.xml.i> </children> </tagNode> <!-- include end --> diff --git a/interface-definitions/include/interface/vif.xml.i b/interface-definitions/include/interface/vif.xml.i index 9e89cbbf6..5644c554f 100644 --- a/interface-definitions/include/interface/vif.xml.i +++ b/interface-definitions/include/interface/vif.xml.i @@ -3,7 +3,7 @@ <properties> <help>Virtual Local Area Network (VLAN) ID</help> <valueHelp> - <format>0-4094</format> + <format>u32:0-4094</format> <description>Virtual Local Area Network (VLAN) ID</description> </valueHelp> <constraint> @@ -13,12 +13,12 @@ </properties> <children> #include <include/interface/address-ipv4-ipv6-dhcp.xml.i> - #include <include/interface/interface-description.xml.i> + #include <include/interface/description.xml.i> #include <include/interface/dhcp-options.xml.i> #include <include/interface/dhcpv6-options.xml.i> - #include <include/interface/interface-disable-link-detect.xml.i> - #include <include/interface/interface-disable.xml.i> - #include <include/interface/interface-vrf.xml.i> + #include <include/interface/disable-link-detect.xml.i> + #include <include/interface/disable.xml.i> + #include <include/interface/vrf.xml.i> <leafNode name="egress-qos"> <properties> <help>VLAN egress QoS</help> @@ -45,10 +45,10 @@ <constraintErrorMessage>QoS mapping should be in the format of '0:7 2:3' with numbers 0-9</constraintErrorMessage> </properties> </leafNode> - #include <include/interface/interface-ipv4-options.xml.i> - #include <include/interface/interface-ipv6-options.xml.i> - #include <include/interface/interface-mac.xml.i> - #include <include/interface/interface-mtu-68-16000.xml.i> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> + #include <include/interface/mac.xml.i> + #include <include/interface/mtu-68-16000.xml.i> </children> </tagNode> <!-- include end --> diff --git a/interface-definitions/include/interface/interface-vrf.xml.i b/interface-definitions/include/interface/vrf.xml.i index ef6ca1241..5ad978a27 100644 --- a/interface-definitions/include/interface/interface-vrf.xml.i +++ b/interface-definitions/include/interface/vrf.xml.i @@ -1,4 +1,4 @@ -<!-- include start from interface/interface-vrf.xml.i --> +<!-- include start from interface/vrf.xml.i --> <leafNode name="vrf"> <properties> <help>VRF instance name</help> diff --git a/interface-definitions/include/interface/interface-xdp.xml.i b/interface-definitions/include/interface/xdp.xml.i index 0253f6dad..10223e766 100644 --- a/interface-definitions/include/interface/interface-xdp.xml.i +++ b/interface-definitions/include/interface/xdp.xml.i @@ -1,4 +1,4 @@ -<!-- include start from interface/interface-xdp.xml.i --> +<!-- include start from interface/xdp.xml.i --> <leafNode name="xdp"> <properties> <help>Enable eXpress Data Path</help> diff --git a/interface-definitions/include/isis/protocol-common-config.xml.i b/interface-definitions/include/isis/protocol-common-config.xml.i index af5a21f49..84e2f7bb2 100644 --- a/interface-definitions/include/isis/protocol-common-config.xml.i +++ b/interface-definitions/include/isis/protocol-common-config.xml.i @@ -447,7 +447,7 @@ <help>Border Gateway Protocol (BGP)</help> </properties> <children> - #include <include/isis/redistribute-ipv4.xml.i> + #include <include/isis/redistribute-level-1-2.xml.i> </children> </node> <node name="connected"> @@ -455,7 +455,7 @@ <help>Redistribute connected routes into IS-IS</help> </properties> <children> - #include <include/isis/redistribute-ipv4.xml.i> + #include <include/isis/redistribute-level-1-2.xml.i> </children> </node> <node name="kernel"> @@ -463,7 +463,7 @@ <help>Redistribute kernel routes into IS-IS</help> </properties> <children> - #include <include/isis/redistribute-ipv4.xml.i> + #include <include/isis/redistribute-level-1-2.xml.i> </children> </node> <node name="ospf"> @@ -471,7 +471,7 @@ <help>Redistribute OSPF routes into IS-IS</help> </properties> <children> - #include <include/isis/redistribute-ipv4.xml.i> + #include <include/isis/redistribute-level-1-2.xml.i> </children> </node> <node name="rip"> @@ -479,7 +479,7 @@ <help>Redistribute RIP routes into IS-IS</help> </properties> <children> - #include <include/isis/redistribute-ipv4.xml.i> + #include <include/isis/redistribute-level-1-2.xml.i> </children> </node> <node name="static"> @@ -487,7 +487,7 @@ <help>Redistribute static routes into IS-IS</help> </properties> <children> - #include <include/isis/redistribute-ipv4.xml.i> + #include <include/isis/redistribute-level-1-2.xml.i> </children> </node> </children> @@ -502,7 +502,7 @@ <help>Redistribute BGP routes into IS-IS</help> </properties> <children> - #include <include/isis/redistribute-ipv6.xml.i> + #include <include/isis/redistribute-level-1-2.xml.i> </children> </node> <node name="connected"> @@ -510,7 +510,7 @@ <help>Redistribute connected routes into IS-IS</help> </properties> <children> - #include <include/isis/redistribute-ipv6.xml.i> + #include <include/isis/redistribute-level-1-2.xml.i> </children> </node> <node name="kernel"> @@ -518,7 +518,7 @@ <help>Redistribute kernel routes into IS-IS</help> </properties> <children> - #include <include/isis/redistribute-ipv6.xml.i> + #include <include/isis/redistribute-level-1-2.xml.i> </children> </node> <node name="ospf6"> @@ -526,7 +526,7 @@ <help>Redistribute OSPFv3 routes into IS-IS</help> </properties> <children> - #include <include/isis/redistribute-ipv6.xml.i> + #include <include/isis/redistribute-level-1-2.xml.i> </children> </node> <node name="ripng"> @@ -534,7 +534,7 @@ <help>Redistribute RIPng routes into IS-IS</help> </properties> <children> - #include <include/isis/redistribute-ipv6.xml.i> + #include <include/isis/redistribute-level-1-2.xml.i> </children> </node> <node name="static"> @@ -542,7 +542,7 @@ <help>Redistribute static routes into IS-IS</help> </properties> <children> - #include <include/isis/redistribute-ipv6.xml.i> + #include <include/isis/redistribute-level-1-2.xml.i> </children> </node> </children> diff --git a/interface-definitions/include/isis/redistribute-ipv4.xml.i b/interface-definitions/include/isis/redistribute-ipv4.xml.i deleted file mode 100644 index fbb6210c7..000000000 --- a/interface-definitions/include/isis/redistribute-ipv4.xml.i +++ /dev/null @@ -1,42 +0,0 @@ -<!-- include start from isis/redistribute-ipv4.xml.i --> -<node name="level-1"> - <properties> - <help>Redistribute into level-1</help> - </properties> - <children> - <leafNode name="metric"> - <properties> - <help>Metric for redistributed routes</help> - <valueHelp> - <format>u32:0-16777215</format> - <description>ISIS default metric</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 0-16777215"/> - </constraint> - </properties> - </leafNode> - #include <include/route-map.xml.i> - </children> -</node> -<node name="level-2"> - <properties> - <help>Redistribute into level-2</help> - </properties> - <children> - <leafNode name="metric"> - <properties> - <help>Metric for redistributed routes</help> - <valueHelp> - <format>u32:0-16777215</format> - <description>ISIS default metric</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 0-16777215"/> - </constraint> - </properties> - </leafNode> - #include <include/route-map.xml.i> - </children> -</node> -<!-- include end --> diff --git a/interface-definitions/include/isis/redistribute-ipv6.xml.i b/interface-definitions/include/isis/redistribute-ipv6.xml.i deleted file mode 100644 index 7e679e38a..000000000 --- a/interface-definitions/include/isis/redistribute-ipv6.xml.i +++ /dev/null @@ -1,42 +0,0 @@ -<!-- include start from isis/redistribute-ipv6.xml.i --> -<node name="level-1"> - <properties> - <help>Redistribute into level-1</help> - </properties> - <children> - <leafNode name="metric"> - <properties> - <help>Metric for redistributed routes</help> - <valueHelp> - <format>u32:0-16777215</format> - <description>ISIS default metric</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 0-16777215"/> - </constraint> - </properties> - </leafNode> - #include <include/route-map.xml.i> - </children> -</node> -<node name="level-2"> - <properties> - <help>Redistribute into level-2</help> - </properties> - <children> - <leafNode name="metric"> - <properties> - <help>Metric for redistributed routes</help> - <valueHelp> - <format>u32:0-16777215</format> - <description>ISIS default metric</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 0-16777215"/> - </constraint> - </properties> - </leafNode> - #include <include/route-map.xml.i> - </children> -</node> -<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/isis/redistribute-level-1-2.xml.i b/interface-definitions/include/isis/redistribute-level-1-2.xml.i new file mode 100644 index 000000000..abb85274f --- /dev/null +++ b/interface-definitions/include/isis/redistribute-level-1-2.xml.i @@ -0,0 +1,20 @@ +<!-- include start from isis/redistribute-level-1-2.xml.i --> +<node name="level-1"> + <properties> + <help>Redistribute into level-1</help> + </properties> + <children> + #include <include/isis/metric.xml.i> + #include <include/route-map.xml.i> + </children> +</node> +<node name="level-2"> + <properties> + <help>Redistribute into level-2</help> + </properties> + <children> + #include <include/isis/metric.xml.i> + #include <include/route-map.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/name-server.xml.i b/interface-definitions/include/name-server-ipv4-ipv6.xml.i index e744b384f..14973234b 100644 --- a/interface-definitions/include/accel-ppp/name-server.xml.i +++ b/interface-definitions/include/name-server-ipv4-ipv6.xml.i @@ -1,7 +1,7 @@ -<!-- include start from accel-ppp/name-server.xml.i --> +<!-- include start from name-server-ipv4-ipv6.xml.i --> <leafNode name="name-server"> <properties> - <help>Domain Name Server (DNS) propagated to client</help> + <help>Domain Name Servers (DNS) addresses</help> <valueHelp> <format>ipv4</format> <description>Domain Name Server (DNS) IPv4 address</description> diff --git a/interface-definitions/include/name-server-ipv4.xml.i b/interface-definitions/include/name-server-ipv4.xml.i new file mode 100644 index 000000000..0cf884e03 --- /dev/null +++ b/interface-definitions/include/name-server-ipv4.xml.i @@ -0,0 +1,15 @@ +<!-- include start from name-server-ipv4.xml.i --> +<leafNode name="name-server"> + <properties> + <help>Domain Name Servers (DNS) addresses</help> + <valueHelp> + <format>ipv4</format> + <description>Domain Name Server (DNS) IPv4 address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/name-server-ipv6.xml.i b/interface-definitions/include/name-server-ipv6.xml.i new file mode 100644 index 000000000..d4517c4c6 --- /dev/null +++ b/interface-definitions/include/name-server-ipv6.xml.i @@ -0,0 +1,15 @@ +<!-- include start from name-server-ipv6.xml.i --> +<leafNode name="name-server"> + <properties> + <help>Domain Name Servers (DNS) addresses</help> + <valueHelp> + <format>ipv6</format> + <description>Domain Name Server (DNS) IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/nat-rule.xml.i b/interface-definitions/include/nat-rule.xml.i index 579d19bdd..084f1f722 100644 --- a/interface-definitions/include/nat-rule.xml.i +++ b/interface-definitions/include/nat-rule.xml.i @@ -278,7 +278,7 @@ <description>Robust Header Compression</description> </valueHelp> <valueHelp> - <format>0-255</format> + <format>u32:0-255</format> <description>IP protocol number</description> </valueHelp> <constraint> diff --git a/interface-definitions/include/ospf/log-adjacency-changes.xml.i b/interface-definitions/include/ospf/log-adjacency-changes.xml.i new file mode 100644 index 000000000..24c6cbe7a --- /dev/null +++ b/interface-definitions/include/ospf/log-adjacency-changes.xml.i @@ -0,0 +1,15 @@ +<!-- include start from ospf/metric-type.xml.i --> +<node name="log-adjacency-changes"> + <properties> + <help>Log adjacency state changes</help> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Log all state changes</help> + <valueless/> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/ospf/metric-type.xml.i b/interface-definitions/include/ospf/metric-type.xml.i index 83dc24909..ef9fd8ac0 100644 --- a/interface-definitions/include/ospf/metric-type.xml.i +++ b/interface-definitions/include/ospf/metric-type.xml.i @@ -4,7 +4,7 @@ <help>OSPF metric type for default routes (default: 2)</help> <valueHelp> <format>u32:1-2</format> - <description>Metric type for default routes</description> + <description>Set OSPF External Type 1/2 metrics</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-2"/> diff --git a/interface-definitions/include/ospf/protocol-common-config.xml.i b/interface-definitions/include/ospf/protocol-common-config.xml.i index db39b1a86..982e519a9 100644 --- a/interface-definitions/include/ospf/protocol-common-config.xml.i +++ b/interface-definitions/include/ospf/protocol-common-config.xml.i @@ -361,6 +361,26 @@ </constraint> </properties> <children> + <leafNode name="area"> + <properties> + <help>Enable OSPF on this interface</help> + <completionHelp> + <path>protocols ospf area</path> + </completionHelp> + <valueHelp> + <format>u32</format> + <description>OSPF area ID as decimal notation</description> + </valueHelp> + <valueHelp> + <format>ipv4</format> + <description>OSPF area ID in IP address notation</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + <validator name="ip-address"/> + </constraint> + </properties> + </leafNode> #include <include/ospf/authentication.xml.i> #include <include/ospf/intervals.xml.i> #include <include/ospf/interface-common.xml.i> @@ -418,19 +438,7 @@ </leafNode> </children> </tagNode> -<node name="log-adjacency-changes"> - <properties> - <help>Log adjacency state changes</help> - </properties> - <children> - <leafNode name="detail"> - <properties> - <help>Log all state changes</help> - <valueless/> - </properties> - </leafNode> - </children> -</node> +#include <include/ospf/log-adjacency-changes.xml.i> <node name="max-metric"> <properties> <help>OSPF maximum and infinite-distance metric</help> @@ -589,7 +597,7 @@ #include <include/router-id.xml.i> </children> </node> -#include <include/routing-passive-interface-xml.i> +#include <include/routing-passive-interface.xml.i> <leafNode name="passive-interface-exclude"> <properties> <help>Interface to exclude when using 'passive-interface default'</help> @@ -648,7 +656,7 @@ </node> <node name="kernel"> <properties> - <help>Redistribute kernel routes</help> + <help>Redistribute Kernel routes</help> </properties> <children> #include <include/ospf/metric.xml.i> @@ -668,7 +676,7 @@ </node> <node name="static"> <properties> - <help>Redistribute static routes</help> + <help>Redistribute statically configured routes</help> </properties> <children> #include <include/ospf/metric.xml.i> @@ -676,6 +684,23 @@ #include <include/route-map.xml.i> </children> </node> + <tagNode name="table"> + <properties> + <help>Redistribute non-main Kernel Routing Table</help> + <completionHelp> + <path>protocols static table</path> + </completionHelp> + <valueHelp> + <format>u32:1-200</format> + <description>Policy route table number</description> + </valueHelp> + </properties> + <children> + #include <include/ospf/metric.xml.i> + #include <include/ospf/metric-type.xml.i> + #include <include/route-map.xml.i> + </children> + </tagNode> </children> </node> <node name="refresh"> diff --git a/interface-definitions/include/pki/ca-certificate.xml.i b/interface-definitions/include/pki/ca-certificate.xml.i index 14295a281..b32bb676a 100644 --- a/interface-definitions/include/pki/ca-certificate.xml.i +++ b/interface-definitions/include/pki/ca-certificate.xml.i @@ -2,13 +2,13 @@ <leafNode name="ca-certificate"> <properties> <help>Certificate Authority in PKI configuration</help> - <valueHelp> - <format>CA name</format> - <description>Name of CA in PKI configuration</description> - </valueHelp> <completionHelp> <path>pki ca</path> </completionHelp> + <valueHelp> + <format>txt</format> + <description>Name of CA in PKI configuration</description> + </valueHelp> </properties> </leafNode> <!-- include end --> diff --git a/interface-definitions/include/pki/certificate.xml.i b/interface-definitions/include/pki/certificate.xml.i index 436aa90ba..1ba70e058 100644 --- a/interface-definitions/include/pki/certificate.xml.i +++ b/interface-definitions/include/pki/certificate.xml.i @@ -2,13 +2,13 @@ <leafNode name="certificate"> <properties> <help>Certificate in PKI configuration</help> - <valueHelp> - <format>cert name</format> - <description>Name of certificate in PKI configuration</description> - </valueHelp> <completionHelp> <path>pki certificate</path> </completionHelp> + <valueHelp> + <format>txt</format> + <description>Name of certificate in PKI configuration</description> + </valueHelp> </properties> </leafNode> <!-- include end --> diff --git a/interface-definitions/include/pki/private-key.xml.i b/interface-definitions/include/pki/private-key.xml.i index 6099daa89..ae4e9103e 100644 --- a/interface-definitions/include/pki/private-key.xml.i +++ b/interface-definitions/include/pki/private-key.xml.i @@ -7,13 +7,13 @@ <leafNode name="key"> <properties> <help>Private key in PKI configuration</help> - <valueHelp> - <format>key name</format> - <description>Name of private key in PKI configuration</description> - </valueHelp> <completionHelp> <path>pki key-pair</path> </completionHelp> + <valueHelp> + <format>txt</format> + <description>Name of private key in PKI configuration</description> + </valueHelp> </properties> </leafNode> <leafNode name="passphrase"> diff --git a/interface-definitions/include/pki/public-key.xml.i b/interface-definitions/include/pki/public-key.xml.i index dfc6979fd..3067bff74 100644 --- a/interface-definitions/include/pki/public-key.xml.i +++ b/interface-definitions/include/pki/public-key.xml.i @@ -2,13 +2,13 @@ <leafNode name="public-key"> <properties> <help>Public key in PKI configuration</help> - <valueHelp> - <format>key name</format> - <description>Name of public key in PKI configuration</description> - </valueHelp> <completionHelp> <path>pki key-pair</path> </completionHelp> + <valueHelp> + <format>txt</format> + <description>Name of public key in PKI configuration</description> + </valueHelp> </properties> </leafNode> <!-- include end --> diff --git a/interface-definitions/include/port-number.xml.i b/interface-definitions/include/port-number.xml.i index b62aef32b..6820df0c4 100644 --- a/interface-definitions/include/port-number.xml.i +++ b/interface-definitions/include/port-number.xml.i @@ -9,6 +9,7 @@ <constraint> <validator name="numeric" argument="--range 1-65535"/> </constraint> + <constraintErrorMessage>Port number must be in range 1 to 65535</constraintErrorMessage> </properties> </leafNode> <!-- include end --> diff --git a/interface-definitions/include/pppoe-access-concentrator.xml.i b/interface-definitions/include/pppoe-access-concentrator.xml.i new file mode 100644 index 000000000..ccfcc1c49 --- /dev/null +++ b/interface-definitions/include/pppoe-access-concentrator.xml.i @@ -0,0 +1,11 @@ +<!-- include start from pppoe-access-concentrator.xml.i --> +<leafNode name="access-concentrator"> + <properties> + <help>Access concentrator name</help> + <constraint> + <regex>[a-zA-Z0-9]{1,100}</regex> + </constraint> + <constraintErrorMessage>Access-concentrator name must be alphanumerical only (max. 100 characters)</constraintErrorMessage> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/routing-passive-interface-xml.i b/interface-definitions/include/routing-passive-interface.xml.i index 9bd4dac2a..43dfb5e44 100644 --- a/interface-definitions/include/routing-passive-interface-xml.i +++ b/interface-definitions/include/routing-passive-interface.xml.i @@ -1,4 +1,4 @@ -<!-- include start from routing-passive-interface-xml.i --> +<!-- include start from routing-passive-interface.xml.i --> <leafNode name="passive-interface"> <properties> <help>Suppress routing updates on an interface</help> diff --git a/interface-definitions/include/source-address-ipv4.xml.i b/interface-definitions/include/source-address-ipv4.xml.i index 86235df61..052678113 100644 --- a/interface-definitions/include/source-address-ipv4.xml.i +++ b/interface-definitions/include/source-address-ipv4.xml.i @@ -1,7 +1,7 @@ <!-- include start from source-address-ipv4.xml.i --> <leafNode name="source-address"> <properties> - <help>IPv4 source address used to initiiate connection</help> + <help>IPv4 source address used to initiate connection</help> <completionHelp> <script>${vyos_completion_dir}/list_local_ips.sh --ipv4</script> </completionHelp> diff --git a/interface-definitions/include/vni.xml.i b/interface-definitions/include/vni.xml.i index be45c0c97..36176caa3 100644 --- a/interface-definitions/include/vni.xml.i +++ b/interface-definitions/include/vni.xml.i @@ -3,7 +3,7 @@ <properties> <help>Virtual Network Identifier</help> <valueHelp> - <format>0-16777214</format> + <format>u32:0-16777214</format> <description>VXLAN virtual network identifier</description> </valueHelp> <constraint> diff --git a/interface-definitions/include/vrrp-transition-script.xml.i b/interface-definitions/include/vrrp-transition-script.xml.i new file mode 100644 index 000000000..cf57c3c74 --- /dev/null +++ b/interface-definitions/include/vrrp-transition-script.xml.i @@ -0,0 +1,41 @@ +<!-- include start from vrrp-transition-script.xml.i --> +<node name="transition-script"> + <properties> + <help>VRRP transition scripts</help> + </properties> + <children> + <leafNode name="master"> + <properties> + <help>Script to run on VRRP state transition to master</help> + <constraint> + <validator name="script"/> + </constraint> + </properties> + </leafNode> + <leafNode name="backup"> + <properties> + <help>Script to run on VRRP state transition to backup</help> + <constraint> + <validator name="script"/> + </constraint> + </properties> + </leafNode> + <leafNode name="fault"> + <properties> + <help>Script to run on VRRP state transition to fault</help> + <constraint> + <validator name="script"/> + </constraint> + </properties> + </leafNode> + <leafNode name="stop"> + <properties> + <help>Script to run on VRRP state transition to stop</help> + <constraint> + <validator name="script"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/interfaces-bonding.xml.in b/interface-definitions/interfaces-bonding.xml.in index 4bfc6e730..05e0d8461 100644 --- a/interface-definitions/interfaces-bonding.xml.in +++ b/interface-definitions/interfaces-bonding.xml.in @@ -49,13 +49,13 @@ </leafNode> </children> </node> - #include <include/interface/interface-description.xml.i> + #include <include/interface/description.xml.i> #include <include/interface/dhcp-options.xml.i> #include <include/interface/dhcpv6-options.xml.i> - #include <include/interface/interface-disable-link-detect.xml.i> - #include <include/interface/interface-disable.xml.i> - #include <include/interface/interface-vrf.xml.i> - #include <include/interface/interface-mirror.xml.i> + #include <include/interface/disable-link-detect.xml.i> + #include <include/interface/disable.xml.i> + #include <include/interface/vrf.xml.i> + #include <include/interface/mirror.xml.i> <leafNode name="hash-policy"> <properties> <help>Bonding transmit hash policy</help> @@ -89,9 +89,9 @@ </properties> <defaultValue>layer2</defaultValue> </leafNode> - #include <include/interface/interface-ipv4-options.xml.i> - #include <include/interface/interface-ipv6-options.xml.i> - #include <include/interface/interface-mac.xml.i> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> + #include <include/interface/mac.xml.i> <leafNode name="min-links"> <properties> <help>Minimum number of member interfaces required up before enabling bond</help> @@ -182,7 +182,7 @@ </leafNode> </children> </node> - #include <include/interface/interface-mtu-68-16000.xml.i> + #include <include/interface/mtu-68-16000.xml.i> <leafNode name="primary"> <properties> <help>Primary device interface</help> @@ -193,7 +193,7 @@ </leafNode> #include <include/interface/vif-s.xml.i> #include <include/interface/vif.xml.i> - #include <include/interface/interface-xdp.xml.i> + #include <include/interface/xdp.xml.i> </children> </tagNode> </children> diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in index 1af002142..144f43f32 100644 --- a/interface-definitions/interfaces-bridge.xml.in +++ b/interface-definitions/interfaces-bridge.xml.in @@ -21,11 +21,11 @@ <properties> <help>MAC address aging interval</help> <valueHelp> - <format>0</format> + <format>u32:0</format> <description>Disable MAC address learning (always flood)</description> </valueHelp> <valueHelp> - <format>10-1000000</format> + <format>u32:10-1000000</format> <description>MAC address aging time in seconds (default: 300)</description> </valueHelp> <constraint> @@ -34,18 +34,18 @@ </properties> <defaultValue>300</defaultValue> </leafNode> - #include <include/interface/interface-description.xml.i> + #include <include/interface/description.xml.i> #include <include/interface/dhcp-options.xml.i> #include <include/interface/dhcpv6-options.xml.i> - #include <include/interface/interface-disable-link-detect.xml.i> - #include <include/interface/interface-disable.xml.i> - #include <include/interface/interface-vrf.xml.i> - #include <include/interface/interface-mtu-68-16000.xml.i> + #include <include/interface/disable-link-detect.xml.i> + #include <include/interface/disable.xml.i> + #include <include/interface/vrf.xml.i> + #include <include/interface/mtu-68-16000.xml.i> <leafNode name="forwarding-delay"> <properties> <help>Forwarding delay</help> <valueHelp> - <format>0-200</format> + <format>u32:0-200</format> <description>Spanning Tree Protocol forwarding delay in seconds (default 15)</description> </valueHelp> <constraint> @@ -59,7 +59,7 @@ <properties> <help>Hello packet advertisment interval</help> <valueHelp> - <format>1-10</format> + <format>u32:1-10</format> <description>Spanning Tree Protocol hello advertisement interval in seconds (default 2)</description> </valueHelp> <constraint> @@ -82,10 +82,10 @@ </leafNode> </children> </node> - #include <include/interface/interface-ipv4-options.xml.i> - #include <include/interface/interface-ipv6-options.xml.i> - #include <include/interface/interface-mac.xml.i> - #include <include/interface/interface-mirror.xml.i> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> + #include <include/interface/mac.xml.i> + #include <include/interface/mirror.xml.i> <leafNode name="enable-vlan"> <properties> <help>Enable VLAN aware bridge</help> @@ -96,7 +96,7 @@ <properties> <help>Interval at which neighbor bridges are removed</help> <valueHelp> - <format>1-40</format> + <format>u32:1-40</format> <description>Bridge maximum aging time in seconds (default 20)</description> </valueHelp> <constraint> @@ -123,7 +123,7 @@ <properties> <help>Specify VLAN id which should natively be present on the link</help> <valueHelp> - <format>1-4094</format> + <format>u32:1-4094</format> <description>Virtual Local Area Network (VLAN) ID</description> </valueHelp> <constraint> @@ -154,7 +154,7 @@ <properties> <help>Bridge port cost</help> <valueHelp> - <format>1-65535</format> + <format>u32:1-65535</format> <description>Path cost value for Spanning Tree Protocol</description> </valueHelp> <constraint> @@ -168,7 +168,7 @@ <properties> <help>Bridge port priority</help> <valueHelp> - <format>0-63</format> + <format>u32:0-63</format> <description>Bridge port priority</description> </valueHelp> <constraint> @@ -192,7 +192,7 @@ <properties> <help>Priority for this bridge</help> <valueHelp> - <format>0-65535</format> + <format>u32:0-65535</format> <description>Bridge priority (default 32768)</description> </valueHelp> <constraint> diff --git a/interface-definitions/interfaces-dummy.xml.in b/interface-definitions/interfaces-dummy.xml.in index 84c6903c7..2bc88c1a7 100644 --- a/interface-definitions/interfaces-dummy.xml.in +++ b/interface-definitions/interfaces-dummy.xml.in @@ -17,17 +17,17 @@ </properties> <children> #include <include/interface/address-ipv4-ipv6.xml.i> - #include <include/interface/interface-description.xml.i> - #include <include/interface/interface-disable.xml.i> + #include <include/interface/description.xml.i> + #include <include/interface/disable.xml.i> <node name="ip"> <properties> <help>IPv4 routing parameters</help> </properties> <children> - #include <include/interface/interface-source-validation.xml.i> + #include <include/interface/source-validation.xml.i> </children> </node> - #include <include/interface/interface-vrf.xml.i> + #include <include/interface/vrf.xml.i> </children> </tagNode> </children> diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in index cb451f5be..ceeda12a0 100644 --- a/interface-definitions/interfaces-ethernet.xml.in +++ b/interface-definitions/interfaces-ethernet.xml.in @@ -20,7 +20,7 @@ </properties> <children> #include <include/interface/address-ipv4-ipv6-dhcp.xml.i> - #include <include/interface/interface-description.xml.i> + #include <include/interface/description.xml.i> #include <include/interface/dhcp-options.xml.i> #include <include/interface/dhcpv6-options.xml.i> <leafNode name="disable-flow-control"> @@ -29,8 +29,8 @@ <valueless/> </properties> </leafNode> - #include <include/interface/interface-disable-link-detect.xml.i> - #include <include/interface/interface-disable.xml.i> + #include <include/interface/disable-link-detect.xml.i> + #include <include/interface/disable.xml.i> <leafNode name="duplex"> <properties> <help>Duplex mode</help> @@ -56,13 +56,13 @@ </properties> <defaultValue>auto</defaultValue> </leafNode> - #include <include/interface/interface-eapol.xml.i> - #include <include/interface/interface-hw-id.xml.i> - #include <include/interface/interface-ipv4-options.xml.i> - #include <include/interface/interface-ipv6-options.xml.i> - #include <include/interface/interface-mac.xml.i> - #include <include/interface/interface-mtu-68-16000.xml.i> - #include <include/interface/interface-mirror.xml.i> + #include <include/interface/eapol.xml.i> + #include <include/interface/hw-id.xml.i> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> + #include <include/interface/mac.xml.i> + #include <include/interface/mtu-68-16000.xml.i> + #include <include/interface/mirror.xml.i> <node name="offload"> <properties> <help>Configurable offload options</help> @@ -104,12 +104,6 @@ <valueless/> </properties> </leafNode> - <leafNode name="ufo"> - <properties> - <help>Enable UDP Fragmentation Offloading</help> - <valueless/> - </properties> - </leafNode> </children> </node> <leafNode name="speed"> @@ -202,8 +196,8 @@ </node> #include <include/interface/vif-s.xml.i> #include <include/interface/vif.xml.i> - #include <include/interface/interface-vrf.xml.i> - #include <include/interface/interface-xdp.xml.i> + #include <include/interface/vrf.xml.i> + #include <include/interface/xdp.xml.i> </children> </tagNode> </children> diff --git a/interface-definitions/interfaces-geneve.xml.in b/interface-definitions/interfaces-geneve.xml.in index bdcbc3f5e..2ca7dd9f6 100644 --- a/interface-definitions/interfaces-geneve.xml.in +++ b/interface-definitions/interfaces-geneve.xml.in @@ -17,12 +17,12 @@ </properties> <children> #include <include/interface/address-ipv4-ipv6.xml.i> - #include <include/interface/interface-description.xml.i> - #include <include/interface/interface-disable.xml.i> - #include <include/interface/interface-ipv4-options.xml.i> - #include <include/interface/interface-ipv6-options.xml.i> - #include <include/interface/interface-mac.xml.i> - #include <include/interface/interface-mtu-1450-16000.xml.i> + #include <include/interface/description.xml.i> + #include <include/interface/disable.xml.i> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> + #include <include/interface/mac.xml.i> + #include <include/interface/mtu-1450-16000.xml.i> <node name="parameters"> <properties> <help>GENEVE tunnel parameters</help> @@ -33,9 +33,9 @@ <help>IPv4 specific tunnel parameters</help> </properties> <children> - #include <include/interface/interface-parameters-dont-fragment.xml.i> - #include <include/interface/interface-parameters-tos.xml.i> - #include <include/interface/interface-parameters-ttl.xml.i> + #include <include/interface/parameters-dont-fragment.xml.i> + #include <include/interface/parameters-tos.xml.i> + #include <include/interface/parameters-ttl.xml.i> </children> </node> <node name="ipv6"> @@ -43,7 +43,7 @@ <help>IPv6 specific tunnel parameters</help> </properties> <children> - #include <include/interface/interface-parameters-flowlabel.xml.i> + #include <include/interface/parameters-flowlabel.xml.i> </children> </node> </children> diff --git a/interface-definitions/interfaces-l2tpv3.xml.in b/interface-definitions/interfaces-l2tpv3.xml.in index 8835a6b1d..9364c85cd 100644 --- a/interface-definitions/interfaces-l2tpv3.xml.in +++ b/interface-definitions/interfaces-l2tpv3.xml.in @@ -17,12 +17,12 @@ </properties> <children> #include <include/interface/address-ipv4-ipv6.xml.i> - #include <include/interface/interface-description.xml.i> + #include <include/interface/description.xml.i> <leafNode name="destination-port"> <properties> <help>UDP destination port for L2TPv3 tunnel (default: 5000)</help> <valueHelp> - <format>1-65535</format> + <format>u32:1-65535</format> <description>Numeric IP port</description> </valueHelp> <constraint> @@ -31,7 +31,7 @@ </properties> <defaultValue>5000</defaultValue> </leafNode> - #include <include/interface/interface-disable.xml.i> + #include <include/interface/disable.xml.i> <leafNode name="encapsulation"> <properties> <help>Encapsulation type (default: UDP)</help> @@ -53,10 +53,10 @@ </properties> <defaultValue>udp</defaultValue> </leafNode> - #include <include/interface/interface-ipv4-options.xml.i> - #include <include/interface/interface-ipv6-options.xml.i> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> #include <include/source-address-ipv4-ipv6.xml.i> - #include <include/interface/interface-mtu-68-16000.xml.i> + #include <include/interface/mtu-68-16000.xml.i> <leafNode name="mtu"> <defaultValue>1488</defaultValue> </leafNode> @@ -64,7 +64,7 @@ <properties> <help>Peer session identifier</help> <valueHelp> - <format>1-429496729</format> + <format>u32:1-429496729</format> <description>L2TPv3 peer session identifier</description> </valueHelp> <constraint> @@ -76,7 +76,7 @@ <properties> <help>Peer tunnel identifier</help> <valueHelp> - <format>1-429496729</format> + <format>u32:1-429496729</format> <description>L2TPv3 peer tunnel identifier</description> </valueHelp> <constraint> @@ -84,13 +84,13 @@ </constraint> </properties> </leafNode> - #include <include/interface/interface-mtu-68-16000.xml.i> + #include <include/interface/mtu-68-16000.xml.i> #include <include/interface/tunnel-remote.xml.i> <leafNode name="session-id"> <properties> <help>Session identifier</help> <valueHelp> - <format>1-429496729</format> + <format>u32:1-429496729</format> <description>L2TPv3 session identifier</description> </valueHelp> <constraint> @@ -102,7 +102,7 @@ <properties> <help>UDP source port for L2TPv3 tunnel (default: 5000)</help> <valueHelp> - <format>1-65535</format> + <format>u32:1-65535</format> <description>Numeric IP port</description> </valueHelp> <constraint> @@ -115,7 +115,7 @@ <properties> <help>Local tunnel identifier</help> <valueHelp> - <format>1-429496729</format> + <format>u32:1-429496729</format> <description>L2TPv3 local tunnel identifier</description> </valueHelp> <constraint> @@ -123,7 +123,7 @@ </constraint> </properties> </leafNode> - #include <include/interface/interface-vrf.xml.i> + #include <include/interface/vrf.xml.i> </children> </tagNode> </children> diff --git a/interface-definitions/interfaces-loopback.xml.in b/interface-definitions/interfaces-loopback.xml.in index 5d0ca5b0a..7be15ab89 100644 --- a/interface-definitions/interfaces-loopback.xml.in +++ b/interface-definitions/interfaces-loopback.xml.in @@ -17,13 +17,13 @@ </properties> <children> #include <include/interface/address-ipv4-ipv6.xml.i> - #include <include/interface/interface-description.xml.i> + #include <include/interface/description.xml.i> <node name="ip"> <properties> <help>IPv4 routing parameters</help> </properties> <children> - #include <include/interface/interface-source-validation.xml.i> + #include <include/interface/source-validation.xml.i> </children> </node> </children> diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in index fce88b21c..4a566ef8b 100644 --- a/interface-definitions/interfaces-macsec.xml.in +++ b/interface-definitions/interfaces-macsec.xml.in @@ -17,8 +17,8 @@ </properties> <children> #include <include/interface/address-ipv4-ipv6.xml.i> - #include <include/interface/interface-ipv4-options.xml.i> - #include <include/interface/interface-ipv6-options.xml.i> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> <node name="security"> <properties> <help>Security/Encryption Settings</help> @@ -82,7 +82,7 @@ <properties> <help>Priority of MACsec Key Agreement protocol (MKA) actor (default: 255)</help> <valueHelp> - <format>0-255</format> + <format>u32:0-255</format> <description>MACsec Key Agreement protocol (MKA) priority</description> </valueHelp> <constraint> @@ -97,11 +97,11 @@ <properties> <help>IEEE 802.1X/MACsec replay protection window</help> <valueHelp> - <format>0</format> + <format>u32:0</format> <description>No replay window, strict check</description> </valueHelp> <valueHelp> - <format>1-4294967295</format> + <format>u32:1-4294967295</format> <description>Number of packets that could be misordered</description> </valueHelp> <constraint> @@ -111,14 +111,14 @@ </leafNode> </children> </node> - #include <include/interface/interface-description.xml.i> - #include <include/interface/interface-disable.xml.i> - #include <include/interface/interface-mtu-68-16000.xml.i> + #include <include/interface/description.xml.i> + #include <include/interface/disable.xml.i> + #include <include/interface/mtu-68-16000.xml.i> <leafNode name="mtu"> <defaultValue>1460</defaultValue> </leafNode> #include <include/source-interface-ethernet.xml.i> - #include <include/interface/interface-vrf.xml.i> + #include <include/interface/vrf.xml.i> </children> </tagNode> </children> diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in index 023f9f55d..6b4440688 100644 --- a/interface-definitions/interfaces-openvpn.xml.in +++ b/interface-definitions/interfaces-openvpn.xml.in @@ -33,7 +33,7 @@ </leafNode> </children> </node> - #include <include/interface/interface-description.xml.i> + #include <include/interface/description.xml.i> <leafNode name="device-type"> <properties> <help>OpenVPN interface device-type (default: tun)</help> @@ -54,7 +54,7 @@ </properties> <defaultValue>tun</defaultValue> </leafNode> - #include <include/interface/interface-disable.xml.i> + #include <include/interface/disable.xml.i> <node name="encryption"> <properties> <help>Data Encryption settings</help> @@ -165,7 +165,7 @@ </leafNode> </children> </node> - #include <include/interface/interface-ipv6-options.xml.i> + #include <include/interface/ipv6-options.xml.i> <leafNode name="hash"> <properties> <help>Hashing Algorithm</help> @@ -206,7 +206,7 @@ <properties> <help>Maximum number of keepalive packet failures (default: 60)</help> <valueHelp> - <format>0-1000</format> + <format>u32:0-1000</format> <description>Maximum number of keepalive packet failures</description> </valueHelp> <constraint> @@ -219,7 +219,7 @@ <properties> <help>Keepalive packet interval in seconds (default: 10)</help> <valueHelp> - <format>0-600</format> + <format>u32:0-600</format> <description>Keepalive packet interval (seconds)</description> </valueHelp> <constraint> @@ -268,7 +268,7 @@ <properties> <help>Local port number to accept connections</help> <valueHelp> - <format>1-65535</format> + <format>u32:1-65535</format> <description>Numeric IP port</description> </valueHelp> <constraint> @@ -378,7 +378,7 @@ <properties> <help>Remote port number to connect to</help> <valueHelp> - <format>1-65535</format> + <format>u32:1-65535</format> <description>Numeric IP port</description> </valueHelp> <constraint> @@ -546,7 +546,7 @@ <properties> <help>Number of maximum client connections</help> <valueHelp> - <format>1-4096</format> + <format>u32:1-4096</format> <description>Number of concurrent clients</description> </valueHelp> <constraint> @@ -554,24 +554,8 @@ </constraint> </properties> </leafNode> - <leafNode name="name-server"> - <properties> - <help>Domain Name Server (DNS)</help> - <valueHelp> - <format>ipv4</format> - <description>DNS server IPv4 address</description> - </valueHelp> - <valueHelp> - <format>ipv6</format> - <description>DNS server IPv6 address</description> - </valueHelp> - <constraint> - <validator name="ip-address"/> - </constraint> - <multi/> - </properties> - </leafNode> - <leafNode name="push-route"> + #include <include/name-server-ipv4-ipv6.xml.i> + <tagNode name="push-route"> <properties> <help>Route to be pushed to all clients</help> <valueHelp> @@ -585,9 +569,23 @@ <constraint> <validator name="ip-prefix"/> </constraint> - <multi/> </properties> - </leafNode> + <children> + <leafNode name="metric"> + <properties> + <help>Set metric for this route</help> + <valueHelp> + <format>u32:0-4294967295</format> + <description>Metric for this route</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + <defaultValue>0</defaultValue> + </leafNode> + </children> + </tagNode> <leafNode name="reject-unconfigured-clients"> <properties> <help>Reject connections from clients that are not explicitly configured</help> @@ -766,7 +764,7 @@ <properties> <help>Specify the minimum required TLS version</help> <completionHelp> - <list>1.0 1.1 1.2</list> + <list>1.0 1.1 1.2 1.3</list> </completionHelp> <valueHelp> <format>1.0</format> @@ -780,8 +778,12 @@ <format>1.2</format> <description>TLS v1.2</description> </valueHelp> + <valueHelp> + <format>1.3</format> + <description>TLS v1.3</description> + </valueHelp> <constraint> - <regex>^(1.0|1.1|1.2)$</regex> + <regex>^(1.0|1.1|1.2|1.3)$</regex> </constraint> </properties> </leafNode> @@ -812,7 +814,7 @@ <valueless/> </properties> </leafNode> - #include <include/interface/interface-vrf.xml.i> + #include <include/interface/vrf.xml.i> </children> </tagNode> </children> diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in index 96479e057..57bb01258 100644 --- a/interface-definitions/interfaces-pppoe.xml.in +++ b/interface-definitions/interfaces-pppoe.xml.in @@ -5,7 +5,7 @@ <tagNode name="pppoe" owner="${vyos_conf_scripts_dir}/interfaces-pppoe.py"> <properties> <help>Point-to-Point Protocol over Ethernet (PPPoE)</help> - <priority>321</priority> + <priority>322</priority> <constraint> <regex>^pppoe[0-9]+$</regex> </constraint> @@ -16,17 +16,9 @@ </valueHelp> </properties> <children> - <leafNode name="access-concentrator"> - <properties> - <help>Access concentrator name (only connect to this concentrator)</help> - <constraint> - <regex>[a-zA-Z0-9]+$</regex> - </constraint> - <constraintErrorMessage>Access concentrator name must be composed of uppper and lower case letters or numbers only</constraintErrorMessage> - </properties> - </leafNode> + #include <include/pppoe-access-concentrator.xml.i> #include <include/interface/authentication.xml.i> - #include <include/interface/interface-dial-on-demand.xml.i> + #include <include/interface/dial-on-demand.xml.i> <leafNode name="default-route"> <properties> <help>Default route insertion behaviour (default: auto)</help> @@ -53,16 +45,20 @@ <defaultValue>auto</defaultValue> </leafNode> #include <include/interface/dhcpv6-options.xml.i> - #include <include/interface/interface-description.xml.i> - #include <include/interface/interface-disable.xml.i> - #include <include/interface/interface-vrf.xml.i> + #include <include/interface/description.xml.i> + #include <include/interface/disable.xml.i> + #include <include/interface/vrf.xml.i> <leafNode name="idle-timeout"> <properties> <help>Delay before disconnecting idle session (in seconds)</help> <valueHelp> - <format>n</format> + <format>u32:0-86400</format> <description>Idle timeout in seconds</description> </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-86400"/> + </constraint> + <constraintErrorMessage>Timeout must be in range 0 to 86400</constraintErrorMessage> </properties> </leafNode> <node name="ip"> @@ -70,7 +66,9 @@ <help>IPv4 routing parameters</help> </properties> <children> - #include <include/interface/interface-source-validation.xml.i> + #include <include/interface/adjust-mss.xml.i> + #include <include/interface/disable-forwarding.xml.i> + #include <include/interface/source-validation.xml.i> </children> </node> <node name="ipv6"> @@ -86,16 +84,11 @@ #include <include/interface/ipv6-address-autoconf.xml.i> </children> </node> + #include <include/interface/adjust-mss.xml.i> + #include <include/interface/disable-forwarding.xml.i> </children> </node> - <leafNode name="source-interface"> - <properties> - <help>Physical Interface used for this PPPoE session</help> - <completionHelp> - <script>${vyos_completion_dir}/list_interfaces.py --broadcast</script> - </completionHelp> - </properties> - </leafNode> + #include <include/source-interface.xml.i> <leafNode name="local-address"> <properties> <help>IPv4 address of local end of the PPPoE link</help> @@ -108,7 +101,7 @@ </constraint> </properties> </leafNode> - #include <include/interface/interface-mtu-68-1500.xml.i> + #include <include/interface/mtu-68-1500.xml.i> <leafNode name="mtu"> <defaultValue>1492</defaultValue> </leafNode> @@ -136,7 +129,7 @@ <constraint> <regex>[a-zA-Z0-9]+$</regex> </constraint> - <constraintErrorMessage>Service name must be composed of uppper and lower case letters or numbers only</constraintErrorMessage> + <constraintErrorMessage>Service name must be alphanumeric only</constraintErrorMessage> </properties> </leafNode> </children> diff --git a/interface-definitions/interfaces-pseudo-ethernet.xml.in b/interface-definitions/interfaces-pseudo-ethernet.xml.in index 136841290..366892032 100644 --- a/interface-definitions/interfaces-pseudo-ethernet.xml.in +++ b/interface-definitions/interfaces-pseudo-ethernet.xml.in @@ -17,16 +17,16 @@ </properties> <children> #include <include/interface/address-ipv4-ipv6-dhcp.xml.i> - #include <include/interface/interface-description.xml.i> + #include <include/interface/description.xml.i> #include <include/interface/dhcp-options.xml.i> #include <include/interface/dhcpv6-options.xml.i> - #include <include/interface/interface-disable-link-detect.xml.i> - #include <include/interface/interface-disable.xml.i> - #include <include/interface/interface-vrf.xml.i> - #include <include/interface/interface-ipv4-options.xml.i> - #include <include/interface/interface-ipv6-options.xml.i> + #include <include/interface/disable-link-detect.xml.i> + #include <include/interface/disable.xml.i> + #include <include/interface/vrf.xml.i> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> #include <include/source-interface-ethernet.xml.i> - #include <include/interface/interface-mac.xml.i> + #include <include/interface/mac.xml.i> <leafNode name="mode"> <properties> <help>Receive mode (default: private)</help> @@ -56,7 +56,7 @@ </properties> <defaultValue>private</defaultValue> </leafNode> - #include <include/interface/interface-mtu-68-16000.xml.i> + #include <include/interface/mtu-68-16000.xml.i> #include <include/interface/vif-s.xml.i> #include <include/interface/vif.xml.i> </children> diff --git a/interface-definitions/interfaces-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in index b994bdafc..7450ef2af 100644 --- a/interface-definitions/interfaces-tunnel.xml.in +++ b/interface-definitions/interfaces-tunnel.xml.in @@ -16,17 +16,17 @@ </valueHelp> </properties> <children> - #include <include/interface/interface-description.xml.i> + #include <include/interface/description.xml.i> #include <include/interface/address-ipv4-ipv6.xml.i> - #include <include/interface/interface-disable.xml.i> - #include <include/interface/interface-disable-link-detect.xml.i> - #include <include/interface/interface-vrf.xml.i> - #include <include/interface/interface-mtu-64-8024.xml.i> + #include <include/interface/disable.xml.i> + #include <include/interface/disable-link-detect.xml.i> + #include <include/interface/vrf.xml.i> + #include <include/interface/mtu-64-8024.xml.i> <leafNode name="mtu"> <defaultValue>1476</defaultValue> </leafNode> - #include <include/interface/interface-ipv4-options.xml.i> - #include <include/interface/interface-ipv6-options.xml.i> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> #include <include/source-address-ipv4-ipv6.xml.i> #include <include/interface/tunnel-remote.xml.i> #include <include/source-interface.xml.i> @@ -160,7 +160,7 @@ <properties> <help>Unique identifier of ERSPAN engine within a system</help> <valueHelp> - <format>0-1048575</format> + <format>u32:0-1048575</format> <description>Unique identifier of ERSPAN engine</description> </valueHelp> <constraint> @@ -172,7 +172,7 @@ <properties> <help>Specifify ERSPAN version 1 index field</help> <valueHelp> - <format>0-63</format> + <format>u32:0-63</format> <description>Platform-depedent field for specifying port number and direction</description> </valueHelp> <constraint> @@ -183,6 +183,9 @@ <leafNode name="version"> <properties> <help>Protocol version</help> + <completionHelp> + <list>1 2</list> + </completionHelp> <valueHelp> <format>1</format> <description>ERSPAN Type II</description> @@ -216,9 +219,9 @@ <valueless/> </properties> </leafNode> - #include <include/interface/interface-parameters-key.xml.i> - #include <include/interface/interface-parameters-tos.xml.i> - #include <include/interface/interface-parameters-ttl.xml.i> + #include <include/interface/parameters-key.xml.i> + #include <include/interface/parameters-tos.xml.i> + #include <include/interface/parameters-ttl.xml.i> <leafNode name="ttl"> <defaultValue>64</defaultValue> </leafNode> @@ -236,7 +239,7 @@ <list>none</list> </completionHelp> <valueHelp> - <format>0-255</format> + <format>u32:0-255</format> <description>Encaplimit (default: 4)</description> </valueHelp> <valueHelp> @@ -251,12 +254,12 @@ </properties> <defaultValue>4</defaultValue> </leafNode> - #include <include/interface/interface-parameters-flowlabel.xml.i> + #include <include/interface/parameters-flowlabel.xml.i> <leafNode name="hoplimit"> <properties> <help>Hoplimit</help> <valueHelp> - <format>0-255</format> + <format>u32:0-255</format> <description>Hoplimit (default 64)</description> </valueHelp> <constraint> @@ -270,7 +273,7 @@ <properties> <help>Traffic class (Tclass)</help> <valueHelp> - <format>0x0-0x0FFFFF</format> + <format>0x0-0x0fffff</format> <description>Traffic class, 'inherit' or hex value</description> </valueHelp> <constraint> diff --git a/interface-definitions/interfaces-vti.xml.in b/interface-definitions/interfaces-vti.xml.in index 10e1feb6b..b12434ae7 100644 --- a/interface-definitions/interfaces-vti.xml.in +++ b/interface-definitions/interfaces-vti.xml.in @@ -29,10 +29,12 @@ <multi/> </properties> </leafNode> - #include <include/interface/interface-description.xml.i> - #include <include/interface/interface-disable.xml.i> - #include <include/interface/interface-mtu-68-16000.xml.i> - #include <include/interface/interface-vrf.xml.i> + #include <include/interface/description.xml.i> + #include <include/interface/disable.xml.i> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> + #include <include/interface/mtu-68-16000.xml.i> + #include <include/interface/vrf.xml.i> </children> </tagNode> </children> diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in index 56d01dfb6..0a8a88596 100644 --- a/interface-definitions/interfaces-vxlan.xml.in +++ b/interface-definitions/interfaces-vxlan.xml.in @@ -17,8 +17,8 @@ </properties> <children> #include <include/interface/address-ipv4-ipv6.xml.i> - #include <include/interface/interface-description.xml.i> - #include <include/interface/interface-disable.xml.i> + #include <include/interface/description.xml.i> + #include <include/interface/disable.xml.i> <leafNode name="group"> <properties> <help>Multicast group address for VXLAN interface</help> @@ -31,14 +31,16 @@ <description>Multicast IPv6 group address</description> </valueHelp> <constraint> - <validator name="ip-address"/> + <validator name="ipv4-multicast"/> + <validator name="ipv6-multicast"/> </constraint> + <constraintErrorMessage>Multicast IPv4/IPv6 address required</constraintErrorMessage> </properties> </leafNode> - #include <include/interface/interface-ipv4-options.xml.i> - #include <include/interface/interface-ipv6-options.xml.i> - #include <include/interface/interface-mac.xml.i> - #include <include/interface/interface-mtu-1200-16000.xml.i> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> + #include <include/interface/mac.xml.i> + #include <include/interface/mtu-1200-16000.xml.i> <leafNode name="mtu"> <defaultValue>1450</defaultValue> </leafNode> @@ -52,9 +54,9 @@ <help>IPv4 specific tunnel parameters</help> </properties> <children> - #include <include/interface/interface-parameters-dont-fragment.xml.i> - #include <include/interface/interface-parameters-tos.xml.i> - #include <include/interface/interface-parameters-ttl.xml.i> + #include <include/interface/parameters-dont-fragment.xml.i> + #include <include/interface/parameters-tos.xml.i> + #include <include/interface/parameters-ttl.xml.i> <leafNode name="ttl"> <defaultValue>16</defaultValue> </leafNode> @@ -65,7 +67,7 @@ <help>IPv6 specific tunnel parameters</help> </properties> <children> - #include <include/interface/interface-parameters-flowlabel.xml.i> + #include <include/interface/parameters-flowlabel.xml.i> </children> </node> <leafNode name="nolearning"> @@ -76,23 +78,14 @@ </leafNode> </children> </node> + #include <include/port-number.xml.i> <leafNode name="port"> - <properties> - <help>Destination port of VXLAN tunnel (default: 8472)</help> - <valueHelp> - <format>u32:1-65535</format> - <description>Numeric IP port</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1-65535"/> - </constraint> - </properties> <defaultValue>8472</defaultValue> </leafNode> #include <include/source-address-ipv4-ipv6.xml.i> #include <include/source-interface.xml.i> #include <include/interface/tunnel-remote.xml.i> - #include <include/interface/interface-vrf.xml.i> + #include <include/interface/vrf.xml.i> #include <include/vni.xml.i> </children> </tagNode> diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in index 773bde09c..403282e5c 100644 --- a/interface-definitions/interfaces-wireguard.xml.in +++ b/interface-definitions/interfaces-wireguard.xml.in @@ -17,16 +17,16 @@ </properties> <children> #include <include/interface/address-ipv4-ipv6.xml.i> - #include <include/interface/interface-description.xml.i> - #include <include/interface/interface-disable.xml.i> - #include <include/interface/interface-vrf.xml.i> + #include <include/interface/description.xml.i> + #include <include/interface/disable.xml.i> + #include <include/interface/vrf.xml.i> #include <include/port-number.xml.i> - #include <include/interface/interface-mtu-68-16000.xml.i> + #include <include/interface/mtu-68-16000.xml.i> <leafNode name="mtu"> <defaultValue>1420</defaultValue> </leafNode> - #include <include/interface/interface-ipv4-options.xml.i> - #include <include/interface/interface-ipv6-options.xml.i> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> <leafNode name="fwmark"> <properties> <help>A 32-bit fwmark value set on all outgoing packets</help> @@ -102,23 +102,12 @@ </constraint> </properties> </leafNode> - <leafNode name="port"> - <properties> - <help>Port number used for tunnel endpoint</help> - <valueHelp> - <format>u32:1-65535</format> - <description>Numeric IP port</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1-65535"/> - </constraint> - </properties> - </leafNode> + #include <include/port-number.xml.i> <leafNode name="persistent-keepalive"> <properties> <help>Interval to send keepalive messages</help> <valueHelp> - <format>1-65535</format> + <format>u32:1-65535</format> <description>Interval in seconds</description> </valueHelp> <constraint> diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in index aaeb285f1..048c7b475 100644 --- a/interface-definitions/interfaces-wireless.xml.in +++ b/interface-definitions/interfaces-wireless.xml.in @@ -206,7 +206,7 @@ <properties> <help>Number of antennas on this card</help> <valueHelp> - <format>1-8</format> + <format>u32:1-8</format> <description>Number of antennas for this card</description> </valueHelp> <constraint> @@ -464,7 +464,7 @@ <constraintErrorMessage>Invalid ISO/IEC 3166-1 Country Code</constraintErrorMessage> </properties> </leafNode> - #include <include/interface/interface-description.xml.i> + #include <include/interface/description.xml.i> #include <include/interface/dhcp-options.xml.i> #include <include/interface/dhcpv6-options.xml.i> <leafNode name="disable-broadcast-ssid"> @@ -473,25 +473,25 @@ <valueless/> </properties> </leafNode> - #include <include/interface/interface-disable-link-detect.xml.i> - #include <include/interface/interface-disable.xml.i> - #include <include/interface/interface-vrf.xml.i> + #include <include/interface/disable-link-detect.xml.i> + #include <include/interface/disable.xml.i> + #include <include/interface/vrf.xml.i> <leafNode name="expunge-failing-stations"> <properties> <help>Disassociate stations based on excessive transmission failures</help> <valueless/> </properties> </leafNode> - #include <include/interface/interface-ipv4-options.xml.i> - #include <include/interface/interface-ipv6-options.xml.i> - #include <include/interface/interface-hw-id.xml.i> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> + #include <include/interface/hw-id.xml.i> <leafNode name="isolate-stations"> <properties> <help>Isolate stations on the AP so they cannot see each other</help> <valueless/> </properties> </leafNode> - #include <include/interface/interface-mac.xml.i> + #include <include/interface/mac.xml.i> <leafNode name="max-stations"> <properties> <help>Maximum number of wireless radio stations. Excess stations will be rejected upon authentication request.</help> diff --git a/interface-definitions/interfaces-wwan.xml.in b/interface-definitions/interfaces-wwan.xml.in index ea3184a11..6b6fa1a66 100644 --- a/interface-definitions/interfaces-wwan.xml.in +++ b/interface-definitions/interfaces-wwan.xml.in @@ -28,17 +28,17 @@ #include <include/interface/dhcp-options.xml.i> #include <include/interface/dhcpv6-options.xml.i> #include <include/interface/authentication.xml.i> - #include <include/interface/interface-description.xml.i> - #include <include/interface/interface-disable.xml.i> - #include <include/interface/interface-vrf.xml.i> - #include <include/interface/interface-disable-link-detect.xml.i> - #include <include/interface/interface-mtu-68-1500.xml.i> + #include <include/interface/description.xml.i> + #include <include/interface/disable.xml.i> + #include <include/interface/vrf.xml.i> + #include <include/interface/disable-link-detect.xml.i> + #include <include/interface/mtu-68-1500.xml.i> <leafNode name="mtu"> <defaultValue>1430</defaultValue> </leafNode> - #include <include/interface/interface-ipv4-options.xml.i> - #include <include/interface/interface-ipv6-options.xml.i> - #include <include/interface/interface-dial-on-demand.xml.i> + #include <include/interface/ipv4-options.xml.i> + #include <include/interface/ipv6-options.xml.i> + #include <include/interface/dial-on-demand.xml.i> </children> </tagNode> </children> diff --git a/interface-definitions/lldp.xml.in b/interface-definitions/lldp.xml.in index e14abae14..32ef0ad14 100644 --- a/interface-definitions/lldp.xml.in +++ b/interface-definitions/lldp.xml.in @@ -105,7 +105,7 @@ <properties> <help>ECS ELIN (Emergency location identifier number)</help> <valueHelp> - <format>0-9999999999</format> + <format>u32:0-9999999999</format> <description>Emergency Call Service ELIN number (between 10-25 numbers)</description> </valueHelp> <constraint> diff --git a/interface-definitions/nat66.xml.in b/interface-definitions/nat66.xml.in index 7b1ec3706..11d986c96 100644 --- a/interface-definitions/nat66.xml.in +++ b/interface-definitions/nat66.xml.in @@ -3,7 +3,7 @@ <node name="nat66" owner="${vyos_conf_scripts_dir}/nat66.py"> <properties> <help>IPv6-to-IPv6 Network Prefix Translation (NAT66/NPT) Settings</help> - <priority>220</priority> + <priority>500</priority> </properties> <children> <node name="source"> @@ -15,7 +15,7 @@ <properties> <help>Source NAT66 rule number</help> <valueHelp> - <format>1-999999</format> + <format>u32:1-999999</format> <description>Number for this rule</description> </valueHelp> <constraint> @@ -113,7 +113,7 @@ <properties> <help>Destination NAT66 rule number</help> <valueHelp> - <format>1-999999</format> + <format>u32:1-999999</format> <description>Number for this rule</description> </valueHelp> <constraint> diff --git a/interface-definitions/ntp.xml.in b/interface-definitions/ntp.xml.in index 2bfac900b..a518a9def 100644 --- a/interface-definitions/ntp.xml.in +++ b/interface-definitions/ntp.xml.in @@ -82,7 +82,7 @@ </children> </node> #include <include/listen-address.xml.i> - #include <include/interface/interface-vrf.xml.i> + #include <include/interface/vrf.xml.i> </children> </node> </children> diff --git a/interface-definitions/policy-local-route.xml.in b/interface-definitions/policy-local-route.xml.in index 3769c3748..86445b65d 100644 --- a/interface-definitions/policy-local-route.xml.in +++ b/interface-definitions/policy-local-route.xml.in @@ -40,6 +40,18 @@ </leafNode> </children> </node> + <leafNode name="fwmark"> + <properties> + <help>Match fwmark value</help> + <valueHelp> + <format>u32:1-2147483647</format> + <description>Address to match against</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2147483647"/> + </constraint> + </properties> + </leafNode> <leafNode name="source"> <properties> <help>Source address or prefix</help> diff --git a/interface-definitions/policy.xml.in b/interface-definitions/policy.xml.in index 5a3c58fa8..cf65daf00 100644 --- a/interface-definitions/policy.xml.in +++ b/interface-definitions/policy.xml.in @@ -139,7 +139,7 @@ </tagNode> <tagNode name="as-path-list"> <properties> - <help>Border Gateway Protocol (BGP) autonomous system path filter</help> + <help>Add a BGP autonomous system path filter</help> <valueHelp> <format>txt</format> <description>AS path list name</description> @@ -176,10 +176,10 @@ </tagNode> <tagNode name="community-list"> <properties> - <help>Border Gateway Protocol (BGP) autonomous system path filter</help> + <help>Add a BGP community list entry</help> <valueHelp> <format>txt</format> - <description>Border Gateway Protocol (BGP) community-list filter</description> + <description>BGP community-list name</description> </valueHelp> </properties> <children> @@ -236,11 +236,15 @@ </tagNode> <tagNode name="extcommunity-list"> <properties> - <help>Border Gateway Protocol (BGP) extended community-list filter</help> + <help>Add a BGP extended community list entry</help> <valueHelp> <format>txt</format> - <description>Border Gateway Protocol (BGP) extended community-list filter</description> + <description>BGP extended community-list name</description> </valueHelp> + <constraint> + <regex>^[-_a-zA-Z0-9]+$</regex> + </constraint> + <constraintErrorMessage>Should be an alphanumeric name</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> @@ -281,11 +285,15 @@ </tagNode> <tagNode name="large-community-list"> <properties> - <help>Border Gateway Protocol (BGP) large-community-list filter</help> + <help>Add a BGP large community list entry</help> <valueHelp> <format>txt</format> - <description>Border Gateway Protocol (BGP) large-community-list filter</description> + <description>BGP large-community-list name</description> </valueHelp> + <constraint> + <regex>^[-_a-zA-Z0-9]+$</regex> + </constraint> + <constraintErrorMessage>Should be an alphanumeric name</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> @@ -307,9 +315,17 @@ <properties> <help>Regular expression to match against a large community list</help> <valueHelp> - <format><aa:nn:nn></format> - <description>Large Community value</description> + <format>ASN:NN:NN</format> + <description>BGP large-community-list filter</description> + </valueHelp> + <valueHelp> + <format>IP:NN:NN</format> + <description>BGP large-community-list filter (IPv4 address format)</description> </valueHelp> + <constraint> + <validator name="bgp-large-community-list"/> + </constraint> + <constraintErrorMessage>Malformed large-community-list</constraintErrorMessage> </properties> </leafNode> </children> @@ -535,6 +551,44 @@ </leafNode> </children> </node> + <node name="evpn"> + <properties> + <help>Ethernet Virtual Private Network</help> + </properties> + <children> + <leafNode name="default-route"> + <properties> + <help>Default EVPN type-5 route</help> + <valueless/> + </properties> + </leafNode> + #include <include/bgp/route-distinguisher.xml.i> + <leafNode name="route-type"> + <properties> + <help>Match route-type</help> + <completionHelp> + <list>macip multicast prefix</list> + </completionHelp> + <valueHelp> + <format>macip</format> + <description>mac-ip route</description> + </valueHelp> + <valueHelp> + <format>multicast</format> + <description>IMET route</description> + </valueHelp> + <valueHelp> + <format>prefix</format> + <description>Prefix route</description> + </valueHelp> + <constraint> + <regex>^(macip|multicast|prefix)$</regex> + </constraint> + </properties> + </leafNode> + #include <include/vni.xml.i> + </children> + </node> <leafNode name="extcommunity"> <properties> <help>BGP extended community to match</help> @@ -770,7 +824,7 @@ </leafNode> <leafNode name="origin"> <properties> - <help>Border Gateway Protocol (BGP) origin code to match</help> + <help>BGP origin code to match</help> <completionHelp> <list>egp igp incomplete</list> </completionHelp> @@ -872,7 +926,7 @@ <children> <node name="aggregator"> <properties> - <help>Border Gateway Protocol (BGP) aggregator attribute</help> + <help>BGP aggregator attribute</help> </properties> <children> <leafNode name="as"> @@ -921,13 +975,13 @@ </leafNode> <leafNode name="atomic-aggregate"> <properties> - <help>Border Gateway Protocol (BGP) atomic aggregate attribute</help> + <help>BGP atomic aggregate attribute</help> <valueless/> </properties> </leafNode> <node name="comm-list"> <properties> - <help>Border Gateway Protocol (BGP) communities matching a community-list</help> + <help>BGP communities matching a community-list</help> </properties> <children> <leafNode name="comm-list"> @@ -1124,9 +1178,21 @@ </completionHelp> </properties> </leafNode> + <leafNode name="large-comm-list-delete"> + <properties> + <help>Delete BGP communities matching the large community-list</help> + <completionHelp> + <path>policy large-community-list</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>BGP large community-list</description> + </valueHelp> + </properties> + </leafNode> <leafNode name="local-preference"> <properties> - <help>Border Gateway Protocol (BGP) local preference attribute</help> + <help>BGP local preference attribute</help> <valueHelp> <format>u32:0-4294967295</format> <description>Local preference value</description> @@ -1196,7 +1262,7 @@ </leafNode> <leafNode name="originator-id"> <properties> - <help>Border Gateway Protocol (BGP) originator ID attribute</help> + <help>BGP originator ID attribute</help> <valueHelp> <format>ipv4</format> <description>Orignator IP address</description> @@ -1249,7 +1315,7 @@ </leafNode> <leafNode name="weight"> <properties> - <help>Border Gateway Protocol (BGP) weight attribute</help> + <help>BGP weight attribute</help> <valueHelp> <format>u32:0-4294967295</format> <description>BGP weight</description> diff --git a/interface-definitions/protocols-igmp.xml.in b/interface-definitions/protocols-igmp.xml.in index a9b11e1a3..e10340512 100644 --- a/interface-definitions/protocols-igmp.xml.in +++ b/interface-definitions/protocols-igmp.xml.in @@ -46,9 +46,16 @@ <leafNode name="version"> <properties> <help>IGMP version</help> + <completionHelp> + <list>2 3</list> + </completionHelp> <valueHelp> - <format>2-3</format> - <description>IGMP version</description> + <format>2</format> + <description>IGMP version 2</description> + </valueHelp> + <valueHelp> + <format>3</format> + <description>IGMP version 3</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 2-3"/> @@ -59,7 +66,7 @@ <properties> <help>IGMP host query interval</help> <valueHelp> - <format>1-1800</format> + <format>u32:1-1800</format> <description>Query interval in seconds</description> </valueHelp> <constraint> @@ -71,7 +78,7 @@ <properties> <help>IGMP max query response time</help> <valueHelp> - <format>10-250</format> + <format>u32:10-250</format> <description>Query response value in deci-seconds</description> </valueHelp> <constraint> diff --git a/interface-definitions/protocols-multicast.xml.in b/interface-definitions/protocols-multicast.xml.in index bf0ead78f..b1791c471 100644 --- a/interface-definitions/protocols-multicast.xml.in +++ b/interface-definitions/protocols-multicast.xml.in @@ -37,7 +37,7 @@ <properties> <help>Distance value for this route</help> <valueHelp> - <format>1-255</format> + <format>u32:1-255</format> <description>Distance for this route</description> </valueHelp> <constraint> @@ -73,7 +73,7 @@ <properties> <help>Distance value for this route</help> <valueHelp> - <format>1-255</format> + <format>u32:1-255</format> <description>Distance for this route</description> </valueHelp> <constraint> diff --git a/interface-definitions/protocols-ospfv3.xml.in b/interface-definitions/protocols-ospfv3.xml.in index 7b42c448d..99cfec661 100644 --- a/interface-definitions/protocols-ospfv3.xml.in +++ b/interface-definitions/protocols-ospfv3.xml.in @@ -186,6 +186,7 @@ #include <include/isis/passive.xml.i> </children> </tagNode> + #include <include/ospf/log-adjacency-changes.xml.i> <node name="parameters"> <properties> <help>OSPFv3 specific parameters</help> diff --git a/interface-definitions/protocols-pim.xml.in b/interface-definitions/protocols-pim.xml.in index 6152045a7..bb5cc797b 100644 --- a/interface-definitions/protocols-pim.xml.in +++ b/interface-definitions/protocols-pim.xml.in @@ -21,7 +21,7 @@ <properties> <help>Designated Router Election Priority</help> <valueHelp> - <format>1-4294967295</format> + <format>u32:1-4294967295</format> <description>Value of the new DR Priority</description> </valueHelp> <constraint> @@ -33,7 +33,7 @@ <properties> <help>Hello Interval</help> <valueHelp> - <format>1-180</format> + <format>u32:1-180</format> <description>Hello Interval in seconds</description> </valueHelp> <constraint> @@ -79,7 +79,7 @@ <properties> <help>Keep alive Timer</help> <valueHelp> - <format>31-60000</format> + <format>u32:31-60000</format> <description>Keep alive Timer in seconds</description> </valueHelp> <constraint> diff --git a/interface-definitions/protocols-rip.xml.in b/interface-definitions/protocols-rip.xml.in index 4fcfcfc27..d3be4e1af 100644 --- a/interface-definitions/protocols-rip.xml.in +++ b/interface-definitions/protocols-rip.xml.in @@ -155,7 +155,7 @@ #include <include/static/static-route-distance.xml.i> </children> </tagNode> - #include <include/routing-passive-interface-xml.i> + #include <include/routing-passive-interface.xml.i> <node name="redistribute"> <properties> <help>Redistribute information from another routing protocol</help> diff --git a/interface-definitions/service_console-server.xml.in b/interface-definitions/service_console-server.xml.in index 78eb2d0ba..28aa7ea71 100644 --- a/interface-definitions/service_console-server.xml.in +++ b/interface-definitions/service_console-server.xml.in @@ -27,7 +27,7 @@ </constraint> </properties> <children> - #include <include/interface/interface-description.xml.i> + #include <include/interface/description.xml.i> <leafNode name="speed"> <properties> <help>Serial port baud rate</help> diff --git a/interface-definitions/service_ipoe-server.xml.in b/interface-definitions/service_ipoe-server.xml.in index 7c575ba77..b19acab56 100644 --- a/interface-definitions/service_ipoe-server.xml.in +++ b/interface-definitions/service_ipoe-server.xml.in @@ -111,7 +111,7 @@ </leafNode> </children> </tagNode> - #include <include/accel-ppp/name-server.xml.i> + #include <include/name-server-ipv4-ipv6.xml.i> #include <include/accel-ppp/client-ipv6-pool.xml.i> <node name="authentication"> <properties> diff --git a/interface-definitions/service_pppoe-server.xml.in b/interface-definitions/service_pppoe-server.xml.in index 7b96b5692..188aed6c4 100644 --- a/interface-definitions/service_pppoe-server.xml.in +++ b/interface-definitions/service_pppoe-server.xml.in @@ -8,14 +8,8 @@ <priority>900</priority> </properties> <children> + #include <include/pppoe-access-concentrator.xml.i> <leafNode name="access-concentrator"> - <properties> - <help>Access concentrator name</help> - <constraint> - <regex>[a-zA-Z0-9]{1,100}</regex> - </constraint> - <constraintErrorMessage>access-concentrator name limited to alphanumerical characters only (max. 100)</constraintErrorMessage> - </properties> <defaultValue>vyos-ac</defaultValue> </leafNode> <node name="authentication"> @@ -65,7 +59,7 @@ </children> </node> #include <include/accel-ppp/client-ipv6-pool.xml.i> - #include <include/accel-ppp/name-server.xml.i> + #include <include/name-server-ipv4-ipv6.xml.i> <tagNode name="interface"> <properties> <help>interface(s) to listen on</help> @@ -129,7 +123,7 @@ <constraint> <regex>[a-zA-Z0-9\-]{1,100}</regex> </constraint> - <constraintErrorMessage>servicename can contain aplhanumerical characters and dashes only (max. 100)</constraintErrorMessage> + <constraintErrorMessage>Service-name can contain aplhanumerical characters and dashes only (max. 100)</constraintErrorMessage> <multi/> </properties> </leafNode> @@ -246,7 +240,7 @@ <properties> <help>PADO delays</help> <valueHelp> - <format>1-999999</format> + <format>u32:1-999999</format> <description>Number in ms</description> </valueHelp> <constraint> @@ -259,7 +253,7 @@ <properties> <help>Number of sessions</help> <valueHelp> - <format>1-999999</format> + <format>u32:1-999999</format> <description>Number of sessions</description> </valueHelp> <constraint> diff --git a/interface-definitions/service_router-advert.xml.in b/interface-definitions/service_router-advert.xml.in index 750ae314c..0f4009f5c 100644 --- a/interface-definitions/service_router-advert.xml.in +++ b/interface-definitions/service_router-advert.xml.in @@ -20,12 +20,12 @@ <properties> <help>Set Hop Count field of the IP header for outgoing packets (default: 64)</help> <valueHelp> - <format>1-255</format> - <description>Value should represent current diameter of the Internet</description> + <format>u32:0</format> + <description>Unspecified (by this router)</description> </valueHelp> <valueHelp> - <format>0</format> - <description>Unspecified (by this router)</description> + <format>u32:1-255</format> + <description>Value should represent current diameter of the Internet</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-255"/> @@ -38,7 +38,7 @@ <properties> <help>Lifetime associated with the default router in units of seconds</help> <valueHelp> - <format>4-9000</format> + <format>u32:4-9000</format> <description>Router Lifetime in seconds</description> </valueHelp> <valueHelp> @@ -86,7 +86,7 @@ <properties> <help>Link MTU value placed in RAs, exluded in RAs if unset</help> <valueHelp> - <format>1280-9000</format> + <format>u32:1280-9000</format> <description>Link MTU value in RAs</description> </valueHelp> <constraint> @@ -110,7 +110,7 @@ <properties> <help>Maximum interval between unsolicited multicast RAs (default: 600)</help> <valueHelp> - <format>4-1800</format> + <format>u32:4-1800</format> <description>Maximum interval in seconds</description> </valueHelp> <constraint> @@ -124,7 +124,7 @@ <properties> <help>Minimum interval between unsolicited multicast RAs</help> <valueHelp> - <format>3-1350</format> + <format>u32:3-1350</format> <description>Minimum interval in seconds</description> </valueHelp> <constraint> @@ -135,19 +135,7 @@ </leafNode> </children> </node> - <leafNode name="name-server"> - <properties> - <help>IPv6 address of recursive DNS server</help> - <valueHelp> - <format>ipv6</format> - <description>IPv6 address of DNS name server</description> - </valueHelp> - <constraint> - <validator name="ipv6-address"/> - </constraint> - <multi/> - </properties> - </leafNode> + #include <include/name-server-ipv6.xml.i> <leafNode name="other-config-flag"> <properties> <help>Hosts use the administered (stateful) protocol for autoconfiguration of other (non-address) information</help> @@ -173,7 +161,7 @@ <list>infinity</list> </completionHelp> <valueHelp> - <format>1-4294967295</format> + <format>u32:1-4294967295</format> <description>Time in seconds that the route will remain valid</description> </valueHelp> <valueHelp> @@ -272,7 +260,7 @@ <list>infinity</list> </completionHelp> <valueHelp> - <format>1-4294967295</format> + <format>u32:1-4294967295</format> <description>Time in seconds that the prefix will remain valid</description> </valueHelp> <valueHelp> @@ -292,12 +280,12 @@ <properties> <help>Time, in milliseconds, that a node assumes a neighbor is reachable after having received a reachability confirmation</help> <valueHelp> - <format>1-3600000</format> - <description>Reachable Time value in RAs (in milliseconds)</description> + <format>u32:0</format> + <description>Reachable Time unspecified by this router</description> </valueHelp> <valueHelp> - <format>0</format> - <description>Reachable Time unspecified by this router</description> + <format>u32:1-3600000</format> + <description>Reachable Time value in RAs (in milliseconds)</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-0 --range 1-3600000"/> @@ -310,12 +298,12 @@ <properties> <help>Time in milliseconds between retransmitted Neighbor Solicitation messages</help> <valueHelp> - <format>1-4294967295</format> - <description>Minimum interval in milliseconds</description> + <format>u32:0</format> + <description>Time, in milliseconds, between retransmitted Neighbor Solicitation messages</description> </valueHelp> <valueHelp> - <format>0</format> - <description>Time, in milliseconds, between retransmitted Neighbor Solicitation messages</description> + <format>u32:1-4294967295</format> + <description>Minimum interval in milliseconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-0 --range 1-4294967295"/> diff --git a/interface-definitions/service_webproxy.xml.in b/interface-definitions/service_webproxy.xml.in index 7cb0f7ece..d61a95690 100644 --- a/interface-definitions/service_webproxy.xml.in +++ b/interface-definitions/service_webproxy.xml.in @@ -83,17 +83,8 @@ <valueless/> </properties> </leafNode> + #include <include/port-number.xml.i> <leafNode name="port"> - <properties> - <help>LDAP server port to use (default: 389)</help> - <valueHelp> - <format>u32:1-65535</format> - <description>Port number to use</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1-65535"/> - </constraint> - </properties> <defaultValue>389</defaultValue> </leafNode> <leafNode name="server"> @@ -214,7 +205,7 @@ <properties> <help>Cache peer options (default: "no-query default")</help> <valueHelp> - <format>text</format> + <format>txt</format> <description>Cache peer options</description> </valueHelp> </properties> @@ -513,6 +504,7 @@ <validator name="ipv4-prefix"/> <validator name="ipv4-range"/> </constraint> + <multi/> </properties> </leafNode> <leafNode name="description"> diff --git a/interface-definitions/snmp.xml.in b/interface-definitions/snmp.xml.in index 2654449a1..b0b7768d2 100644 --- a/interface-definitions/snmp.xml.in +++ b/interface-definitions/snmp.xml.in @@ -646,7 +646,7 @@ </tagNode> </children> </node> - #include <include/interface/interface-vrf.xml.i> + #include <include/interface/vrf.xml.i> </children> </node> </children> diff --git a/interface-definitions/ssh.xml.in b/interface-definitions/ssh.xml.in index 54742f1d0..e3b9d16e1 100644 --- a/interface-definitions/ssh.xml.in +++ b/interface-definitions/ssh.xml.in @@ -138,7 +138,7 @@ <properties> <help>Enable transmission of keepalives from server to client</help> <valueHelp> - <format>1-65535</format> + <format>u32:1-65535</format> <description>Time interval in seconds for keepalive message</description> </valueHelp> <constraint> @@ -146,7 +146,7 @@ </constraint> </properties> </leafNode> - #include <include/interface/interface-vrf.xml.i> + #include <include/interface/vrf.xml.i> </children> </node> </children> diff --git a/interface-definitions/system-conntrack.xml.in b/interface-definitions/system-conntrack.xml.in index fa73df3db..daa4177c9 100644 --- a/interface-definitions/system-conntrack.xml.in +++ b/interface-definitions/system-conntrack.xml.in @@ -37,65 +37,51 @@ </leafNode> <node name="modules"> <properties> - <help>Connection tracking modules settings</help> + <help>Connection tracking modules</help> </properties> <children> - <node name="ftp"> + <leafNode name="ftp"> <properties> - <help>FTP connection tracking settings</help> + <help>FTP connection tracking</help> + <valueless/> </properties> - <children> - #include <include/conntrack-module-disable.xml.i> - </children> - </node> - <node name="h323"> + </leafNode> + <leafNode name="h323"> <properties> - <help>H.323 connection tracking settings</help> + <help>H.323 connection tracking</help> + <valueless/> </properties> - <children> - #include <include/conntrack-module-disable.xml.i> - </children> - </node> - <node name="nfs"> + </leafNode> + <leafNode name="nfs"> <properties> - <help>NFS connection tracking settings</help> + <help>NFS connection tracking</help> + <valueless/> </properties> - <children> - #include <include/conntrack-module-disable.xml.i> - </children> - </node> - <node name="pptp"> + </leafNode> + <leafNode name="pptp"> <properties> - <help>PPTP connection tracking settings</help> + <help>PPTP connection tracking</help> + <valueless/> </properties> - <children> - #include <include/conntrack-module-disable.xml.i> - </children> - </node> - <node name="sip"> + </leafNode> + <leafNode name="sip"> <properties> - <help>SIP connection tracking settings</help> + <help>SIP connection tracking</help> + <valueless/> </properties> - <children> - #include <include/conntrack-module-disable.xml.i> - </children> - </node> - <node name="sqlnet"> + </leafNode> + <leafNode name="sqlnet"> <properties> - <help>SQLnet connection tracking settings</help> + <help>SQLnet connection tracking</help> + <valueless/> </properties> - <children> - #include <include/conntrack-module-disable.xml.i> - </children> - </node> - <node name="tftp"> + </leafNode> + <leafNode name="tftp"> <properties> - <help>TFTP connection tracking settings</help> + <help>TFTP connection tracking</help> + <valueless/> </properties> - <children> - #include <include/conntrack-module-disable.xml.i> - </children> - </node> + </leafNode> </children> </node> <leafNode name="table-size"> diff --git a/interface-definitions/system-login.xml.in b/interface-definitions/system-login.xml.in index 86db3f368..4bfe82268 100644 --- a/interface-definitions/system-login.xml.in +++ b/interface-definitions/system-login.xml.in @@ -12,7 +12,7 @@ <properties> <help>Local user account information</help> <constraint> - <regex>[a-zA-Z0-9\-_\.]{1,100}</regex> + <regex>^[-_a-zA-Z0-9.]{1,100}</regex> </constraint> <constraintErrorMessage>Username contains illegal characters or\nexceeds 100 character limitation.</constraintErrorMessage> </properties> @@ -52,7 +52,10 @@ <children> <leafNode name="key"> <properties> - <help>Public key value (base64-encoded)</help> + <help>Public key value (Base64 encoded)</help> + <constraint> + <validator name="base64"/> + </constraint> </properties> </leafNode> <leafNode name="options"> @@ -145,7 +148,7 @@ </leafNode> </children> </tagNode> - #include <include/interface/interface-vrf.xml.i> + #include <include/interface/vrf.xml.i> </children> </node> </children> diff --git a/interface-definitions/system-proxy.xml.in b/interface-definitions/system-proxy.xml.in index 791f41f2f..ade168522 100644 --- a/interface-definitions/system-proxy.xml.in +++ b/interface-definitions/system-proxy.xml.in @@ -15,18 +15,7 @@ </constraint> </properties> </leafNode> - <leafNode name="port"> - <properties> - <help>Proxy port</help> - <valueHelp> - <format>u32:1-65535</format> - <description>Numeric IP port</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1-65535"/> - </constraint> - </properties> - </leafNode> + #include <include/port-number.xml.i> <leafNode name="username"> <properties> <help>Proxy username</help> diff --git a/interface-definitions/system-syslog.xml.in b/interface-definitions/system-syslog.xml.in index f3dcae2f3..9280a43c8 100644 --- a/interface-definitions/system-syslog.xml.in +++ b/interface-definitions/system-syslog.xml.in @@ -195,19 +195,7 @@ </valueHelp> </properties> <children> - <leafNode name="port"> - <properties> - <help>Destination port</help> - <valueHelp> - <format>u32:1-65535</format> - <description>Destination port</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1-65535"/> - </constraint> - <constraintErrorMessage>Invalid destination port value</constraintErrorMessage> - </properties> - </leafNode> + #include <include/port-number.xml.i> <tagNode name="facility"> <properties> <help>Facility for logging</help> diff --git a/interface-definitions/tftp-server.xml.in b/interface-definitions/tftp-server.xml.in index e903e8f4e..037c097ca 100644 --- a/interface-definitions/tftp-server.xml.in +++ b/interface-definitions/tftp-server.xml.in @@ -20,17 +20,8 @@ <valueless/> </properties> </leafNode> + #include <include/port-number.xml.i> <leafNode name="port"> - <properties> - <help>Port number used to listen for connections</help> - <valueHelp> - <format>u32:1-65535</format> - <description>Numeric IP port</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1-65535"/> - </constraint> - </properties> <defaultValue>69</defaultValue> </leafNode> #include <include/listen-address.xml.i> diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in index b28c86ae6..164ba6618 100644 --- a/interface-definitions/vpn_ipsec.xml.in +++ b/interface-definitions/vpn_ipsec.xml.in @@ -83,7 +83,7 @@ </completionHelp> <valueHelp> <format>enable</format> - <description>Use Diffie-Hellman group 2 (modp1024) - default</description> + <description>Inherit Diffie-Hellman group from IKE group - default</description> </valueHelp> <valueHelp> <format>dh-group1</format> @@ -757,11 +757,15 @@ <properties> <help>Timeout to close connection if no data is transmitted</help> <valueHelp> - <format>u32:10-86400</format> + <format>u32:0</format> + <description>Disable inactivity checks</description> + </valueHelp> + <valueHelp> + <format>u32:1-86400</format> <description>Timeout in seconds (default 28800)</description> </valueHelp> <constraint> - <validator name="numeric" argument="--range 10-86400"/> + <validator name="numeric" argument="--range 0-86400"/> </constraint> </properties> <defaultValue>28800</defaultValue> @@ -771,11 +775,19 @@ <help>Pool name used for IP address assignments</help> <completionHelp> <path>vpn ipsec remote-access pool</path> - <list>dhcp</list> + <list>dhcp radius</list> </completionHelp> <valueHelp> <format>txt</format> - <description>Pool name</description> + <description>Name of predefined IP pool</description> + </valueHelp> + <valueHelp> + <format>dhcp</format> + <description>Forward requests for virtual IP addresses to a DHCP server</description> + </valueHelp> + <valueHelp> + <format>radius</format> + <description>Forward requests for virtual IP addresses to a RADIUS server</description> </valueHelp> <multi/> </properties> @@ -872,8 +884,7 @@ </constraint> </properties> </leafNode> - <!-- Include Accel-PPP definition here, maybe time for a rename? --> - #include <include/accel-ppp/name-server.xml.i> + #include <include/name-server-ipv4-ipv6.xml.i> </children> </tagNode> #include <include/radius-server-ipv4.xml.i> diff --git a/interface-definitions/vpn_l2tp.xml.in b/interface-definitions/vpn_l2tp.xml.in index cf31af70f..cbd5e38e7 100644 --- a/interface-definitions/vpn_l2tp.xml.in +++ b/interface-definitions/vpn_l2tp.xml.in @@ -5,6 +5,7 @@ <node name="l2tp" owner="${vyos_conf_scripts_dir}/vpn_l2tp.py"> <properties> <help>L2TP Virtual Private Network (VPN)</help> + <priority>902</priority> </properties> <children> <node name="remote-access"> @@ -22,7 +23,7 @@ </properties> </leafNode> #include <include/accel-ppp/gateway-address.xml.i> - #include <include/accel-ppp/name-server.xml.i> + #include <include/name-server-ipv4-ipv6.xml.i> <node name="lns"> <properties> <help>L2TP Network Server (LNS)</help> @@ -177,9 +178,9 @@ #include <include/accel-ppp/radius-additions-disable-accounting.xml.i> <leafNode name="fail-time"> <properties> - <help>Mark server unavailable for <n> seconds on failure</help> + <help>Mark server unavailable for N seconds on failure</help> <valueHelp> - <format>0-600</format> + <format>u32:0-600</format> <description>Fail time penalty</description> </valueHelp> <constraint> diff --git a/interface-definitions/vpn_openconnect.xml.in b/interface-definitions/vpn_openconnect.xml.in index a33ff67ea..0db5e79d0 100644 --- a/interface-definitions/vpn_openconnect.xml.in +++ b/interface-definitions/vpn_openconnect.xml.in @@ -170,7 +170,7 @@ </leafNode> </children> </node> - #include <include/accel-ppp/name-server.xml.i> + #include <include/name-server-ipv4-ipv6.xml.i> </children> </node> </children> diff --git a/interface-definitions/vpn_pptp.xml.in b/interface-definitions/vpn_pptp.xml.in index dab317f68..0d1690013 100644 --- a/interface-definitions/vpn_pptp.xml.in +++ b/interface-definitions/vpn_pptp.xml.in @@ -22,19 +22,7 @@ </constraint> </properties> </leafNode> - <leafNode name="name-server"> - <properties> - <help>Domain Name Server (DNS) propagated to client</help> - <valueHelp> - <format>ipv4</format> - <description>Domain Name Server (DNS) IPv4 address</description> - </valueHelp> - <constraint> - <validator name="ipv4-address"/> - </constraint> - <multi/> - </properties> - </leafNode> + #include <include/name-server-ipv4.xml.i> #include <include/accel-ppp/wins-server.xml.i> <node name="client-ip-pool"> <properties> diff --git a/interface-definitions/vpn_sstp.xml.in b/interface-definitions/vpn_sstp.xml.in index 3576bac90..9901a0cdf 100644 --- a/interface-definitions/vpn_sstp.xml.in +++ b/interface-definitions/vpn_sstp.xml.in @@ -25,9 +25,9 @@ </node> </children> </node> - #include <include/interface/interface-mtu-68-1500.xml.i> + #include <include/interface/mtu-68-1500.xml.i> #include <include/accel-ppp/gateway-address.xml.i> - #include <include/accel-ppp/name-server.xml.i> + #include <include/name-server-ipv4-ipv6.xml.i> <node name="client-ip-pool"> <properties> <help>Client IP pools and gateway setting</help> diff --git a/interface-definitions/vrf.xml.in b/interface-definitions/vrf.xml.in index 9d513945c..a82c0b2a6 100644 --- a/interface-definitions/vrf.xml.in +++ b/interface-definitions/vrf.xml.in @@ -26,8 +26,8 @@ </valueHelp> </properties> <children> - #include <include/interface/interface-description.xml.i> - #include <include/interface/interface-disable.xml.i> + #include <include/interface/description.xml.i> + #include <include/interface/disable.xml.i> <node name="protocols"> <properties> <help>Routing protocol parameters</help> @@ -76,7 +76,7 @@ <properties> <help>Routing table associated with this instance</help> <valueHelp> - <format>100-65535</format> + <format>u32:100-65535</format> <description>Routing table ID</description> </valueHelp> <constraint> @@ -85,7 +85,20 @@ <constraintErrorMessage>VRF routing table must be in range from 100 to 65535</constraintErrorMessage> </properties> </leafNode> - #include <include/vni.xml.i> + <leafNode name="vni" owner="${vyos_conf_scripts_dir}/vrf_vni.py $VAR(../@)"> + <properties> + <help>Virtual Network Identifier</help> + <!-- priority must be after BGP --> + <priority>822</priority> + <valueHelp> + <format>u32:0-16777214</format> + <description>VXLAN virtual network identifier</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-16777214"/> + </constraint> + </properties> + </leafNode> </children> </tagNode> </children> diff --git a/interface-definitions/vrrp.xml.in b/interface-definitions/vrrp.xml.in index bb551296f..44a9a1f54 100644 --- a/interface-definitions/vrrp.xml.in +++ b/interface-definitions/vrrp.xml.in @@ -35,6 +35,7 @@ <validator name="numeric" argument="--range 1-255"/> </constraint> </properties> + <defaultValue>1</defaultValue> </leafNode> <node name="authentication"> <properties> @@ -45,7 +46,7 @@ <properties> <help>VRRP password</help> <valueHelp> - <format>text</format> + <format>txt</format> <description>Password string (up to 8 characters)</description> </valueHelp> <constraint> @@ -60,6 +61,14 @@ <completionHelp> <list>plaintext-password ah</list> </completionHelp> + <valueHelp> + <format>plaintext-password</format> + <description>Simple password string</description> + </valueHelp> + <valueHelp> + <format>ah</format> + <description>AH - IPSEC (not recommended)</description> + </valueHelp> <constraint> <regex>^(plaintext-password|ah)$</regex> </constraint> @@ -68,11 +77,7 @@ </leafNode> </children> </node> - <leafNode name="description"> - <properties> - <help>Group description</help> - </properties> - </leafNode> + #include <include/generic-description.xml.i> #include <include/generic-disable-node.xml.i> <node name="health-check"> <properties> @@ -86,6 +91,7 @@ <validator name="numeric" argument="--positive" /> </constraint> </properties> + <defaultValue>3</defaultValue> </leafNode> <leafNode name="interval"> <properties> @@ -94,6 +100,7 @@ <validator name="numeric" argument="--positive"/> </constraint> </properties> + <defaultValue>60</defaultValue> </leafNode> <leafNode name="script"> <properties> @@ -156,6 +163,7 @@ <validator name="numeric" argument="--range 0-1000"/> </constraint> </properties> + <defaultValue>0</defaultValue> </leafNode> <leafNode name="priority"> <properties> @@ -168,64 +176,18 @@ <validator name="numeric" argument="--range 1-255"/> </constraint> </properties> + <defaultValue>100</defaultValue> </leafNode> <leafNode name="rfc3768-compatibility"> <properties> - <valueless/> <help>Use VRRP virtual MAC address as per RFC3768</help> + <valueless/> </properties> </leafNode> - <node name="transition-script"> - <properties> - <help>VRRP transition scripts</help> - </properties> - <children> - <leafNode name="master"> - <properties> - <help>Script to run on VRRP state transition to master</help> - <constraint> - <validator name="script"/> - </constraint> - </properties> - </leafNode> - <leafNode name="backup"> - <properties> - <help>Script to run on VRRP state transition to backup</help> - <constraint> - <validator name="script"/> - </constraint> - </properties> - </leafNode> - <leafNode name="fault"> - <properties> - <help>Script to run on VRRP state transition to fault</help> - <constraint> - <validator name="script"/> - </constraint> - </properties> - </leafNode> - <leafNode name="stop"> - <properties> - <help>Script to run on VRRP state transition to stop</help> - <constraint> - <validator name="script"/> - </constraint> - </properties> - </leafNode> - <leafNode name="mode-force"> - <properties> - <valueless/> - <help>Disable VRRP state checking (deprecated, will be removed in VyOS 1.4)</help> - <constraint> - <validator name="script"/> - </constraint> - </properties> - </leafNode> - </children> - </node> - <leafNode name="virtual-address"> + #include <include/vrrp-transition-script.xml.i> + <leafNode name="address"> <properties> - <help>Virtual address (IPv4 or IPv6, but they must not be mixed in one group)</help> + <help>Virtual IP address</help> <valueHelp> <format>ipv4</format> <description>IPv4 virtual address</description> @@ -238,11 +200,10 @@ <validator name="ipv4-host"/> <validator name="ipv6-host"/> </constraint> - <constraintErrorMessage>Virtual address must be a valid IPv4 or IPv6 address with prefix length (e.g. 192.0.2.3/24 or 2001:db8:ff::10/64)</constraintErrorMessage> <multi/> </properties> </leafNode> - <leafNode name="virtual-address-excluded"> + <leafNode name="excluded-address"> <properties> <help>Virtual address (If you need additional IPv4 and IPv6 in same group)</help> <valueHelp> @@ -285,7 +246,7 @@ <multi/> <help>Sync group member</help> <valueHelp> - <format>text</format> + <format>txt</format> <description>VRRP group name</description> </valueHelp> <completionHelp> @@ -293,45 +254,7 @@ </completionHelp> </properties> </leafNode> - <node name="transition-script"> - <properties> - <help>VRRP transition scripts</help> - </properties> - <children> - <leafNode name="master"> - <properties> - <help>Script to run on VRRP state transition to master</help> - <constraint> - <validator name="script"/> - </constraint> - </properties> - </leafNode> - <leafNode name="backup"> - <properties> - <help>Script to run on VRRP state transition to backup</help> - <constraint> - <validator name="script"/> - </constraint> - </properties> - </leafNode> - <leafNode name="fault"> - <properties> - <help>Script to run on VRRP state transition to fault</help> - <constraint> - <validator name="script"/> - </constraint> - </properties> - </leafNode> - <leafNode name="stop"> - <properties> - <help>Script to run on VRRP state transition to stop</help> - <constraint> - <validator name="script"/> - </constraint> - </properties> - </leafNode> - </children> - </node> + #include <include/vrrp-transition-script.xml.i> </children> </tagNode> </children> diff --git a/op-mode-definitions/containers.xml.in b/op-mode-definitions/containers.xml.in index a22549dd9..b2b318786 100644 --- a/op-mode-definitions/containers.xml.in +++ b/op-mode-definitions/containers.xml.in @@ -17,6 +17,19 @@ </node> </children> </node> + <node name="connect"> + <children> + <tagNode name="container"> + <properties> + <help>Attach to a running container</help> + <completionHelp> + <path>container name</path> + </completionHelp> + </properties> + <command>sudo podman exec --interactive --tty "$3" /bin/sh</command> + </tagNode> + </children> + </node> <node name="delete"> <children> <node name="container"> @@ -27,6 +40,9 @@ <tagNode name="image"> <properties> <help>Delete container image</help> + <completionHelp> + <script>sudo podman image ls -q</script> + </completionHelp> </properties> <command>sudo ${vyos_op_scripts_dir}/containers_op.py --remove "${4}"</command> </tagNode> @@ -48,6 +64,15 @@ </properties> <command>sudo ${vyos_op_scripts_dir}/containers_op.py --image</command> </leafNode> + <tagNode name="log"> + <properties> + <help>Show logs from a given container</help> + <completionHelp> + <path>container name</path> + </completionHelp> + </properties> + <command>sudo podman logs --names "$4"</command> + </tagNode> <leafNode name="network"> <properties> <help>Show available container networks</help> @@ -56,6 +81,52 @@ </leafNode> </children> </node> + <node name="log"> + <children> + <tagNode name="container"> + <properties> + <help>Show logs from a given container</help> + <completionHelp> + <path>container name</path> + </completionHelp> + </properties> + <command>sudo podman logs --names "$4"</command> + </tagNode> + </children> + </node> + </children> + </node> + <node name="restart"> + <children> + <tagNode name="container"> + <properties> + <help>Restart a given container</help> + <completionHelp> + <path>container name</path> + </completionHelp> + </properties> + <command>sudo podman restart "$3"</command> + </tagNode> + </children> + </node> + <node name="update"> + <children> + <node name="container"> + <properties> + <help>Update a container image</help> + </properties> + <children> + <tagNode name="image"> + <properties> + <help>Delete container image</help> + <completionHelp> + <path>container name</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/containers_op.py --update "${4}"</command> + </tagNode> + </children> + </node> </children> </node> </interfaceDefinition> diff --git a/op-mode-definitions/dhcp.xml.in b/op-mode-definitions/dhcp.xml.in index 1dacbd5ba..241cca0ce 100644 --- a/op-mode-definitions/dhcp.xml.in +++ b/op-mode-definitions/dhcp.xml.in @@ -22,7 +22,7 @@ <properties> <help>Show DHCP server leases for a specific pool</help> <completionHelp> - <script>sudo ${vyos_op_scripts_dir}/show_dhcp.py --allowed pool</script> + <path>service dhcp-server shared-network-name</path> </completionHelp> </properties> <command>sudo ${vyos_op_scripts_dir}/show_dhcp.py --leases --pool $6</command> @@ -57,7 +57,7 @@ <properties> <help>Show DHCP server statistics for a specific pool</help> <completionHelp> - <script>sudo ${vyos_op_scripts_dir}/show_dhcp.py --allowed pool</script> + <path>service dhcp-server shared-network-name</path> </completionHelp> </properties> <command>sudo ${vyos_op_scripts_dir}/show_dhcp.py --statistics --pool $6</command> @@ -123,18 +123,18 @@ <children> <node name="dhcp"> <properties> - <help>Restart DHCP server processes</help> + <help>Restart DHCP processes</help> </properties> <children> <node name="server"> <properties> - <help>Restart the DHCP server process</help> + <help>Restart DHCP server</help> </properties> - <command>sudo systemctl restart isc-dhcp-server.service</command> + <command>if cli-shell-api existsActive service dhcp-server; then sudo systemctl restart isc-dhcp-server.service; else echo "DHCP server not configured"; fi</command> </node> <node name="relay-agent"> <properties> - <help>Restart the DHCP server process</help> + <help>Restart DHCP relay-agent</help> </properties> <command>sudo ${vyos_op_scripts_dir}/restart_dhcp_relay.py --ipv4</command> </node> @@ -142,18 +142,18 @@ </node> <node name="dhcpv6"> <properties> - <help>Restart DHCPv6 server processes</help> + <help>Restart DHCPv6 processes</help> </properties> <children> <node name="server"> <properties> - <help>Restart the DHCPv6 server process</help> + <help>Restart DHCPv6 server</help> </properties> - <command>sudo systemctl restart isc-dhcp-server6.service</command> + <command>if cli-shell-api existsActive service dhcpv6-server; then sudo systemctl restart isc-dhcp-server6.service; else echo "DHCPv6 server not configured"; fi</command> </node> <node name="relay-agent"> <properties> - <help>Restart the DHCP server process</help> + <help>Restart DHCPv6 relay-agent</help> </properties> <command>sudo ${vyos_op_scripts_dir}/restart_dhcp_relay.py --ipv6</command> </node> diff --git a/op-mode-definitions/dns-forwarding.xml.in b/op-mode-definitions/dns-forwarding.xml.in index 36fe6b5ef..6574f2319 100644 --- a/op-mode-definitions/dns-forwarding.xml.in +++ b/op-mode-definitions/dns-forwarding.xml.in @@ -59,9 +59,6 @@ </children> </node> <node name="reset"> - <properties> - <help>Reset a service</help> - </properties> <children> <node name="dns"> <properties> diff --git a/op-mode-definitions/generate-ipsec-profile.xml.in b/op-mode-definitions/generate-ipsec-profile.xml.in index be9227971..8d1051b94 100644 --- a/op-mode-definitions/generate-ipsec-profile.xml.in +++ b/op-mode-definitions/generate-ipsec-profile.xml.in @@ -100,37 +100,6 @@ </completionHelp> </properties> <command>${vyos_op_scripts_dir}/ikev2_profile_generator.py --os windows --connection "$5" --remote "$7" --name "$9"</command> - <children> - <tagNode name="profile"> - <properties> - <help>Profile name as seen under system profiles</help> - <completionHelp> - <list><name></list> - </completionHelp> - </properties> - <command>${vyos_op_scripts_dir}/ikev2_profile_generator.py --os windows --connection "$5" --remote "$7" --name "$9" --profile "${11}"</command> - </tagNode> - </children> - </tagNode> - <tagNode name="profile"> - <properties> - <help>Profile name as seen under system profiles</help> - <completionHelp> - <list><name></list> - </completionHelp> - </properties> - <command>${vyos_op_scripts_dir}/ikev2_profile_generator.py --os windows --connection "$5" --remote "$7" --profile "$9"</command> - <children> - <tagNode name="name"> - <properties> - <help>Connection name as seen in the VPN application</help> - <completionHelp> - <list><name></list> - </completionHelp> - </properties> - <command>${vyos_op_scripts_dir}/ikev2_profile_generator.py --os windows --connection "$5" --remote "$7" --profile "$9" --name "${11}"</command> - </tagNode> - </children> </tagNode> </children> </tagNode> diff --git a/op-mode-definitions/generate-wireguard.xml.in b/op-mode-definitions/generate-wireguard.xml.in index 6557b463b..259c9a898 100644 --- a/op-mode-definitions/generate-wireguard.xml.in +++ b/op-mode-definitions/generate-wireguard.xml.in @@ -4,9 +4,27 @@ <children> <node name="wireguard"> <properties> - <help>Generate Wireguard keys</help> + <help>Generate WireGuard keys</help> </properties> <children> + <leafNode name="default-keypair"> + <properties> + <help>generates the wireguard default-keypair</help> + </properties> + <command>echo "This command is deprecated. Please use: \"generate pki wireguard key-pair\""</command> + </leafNode> + <leafNode name="preshared-key"> + <properties> + <help>generate a wireguard preshared key</help> + </properties> + <command>echo "This command is deprecated. Please use: \"generate pki wireguard pre-shared-key\""</command> + </leafNode> + <tagNode name="named-keypairs"> + <properties> + <help>Generates named wireguard keypairs</help> + </properties> + <command>echo "This command is deprecated. Please use: \"generate pki wireguard key-pair install wgN\""</command> + </tagNode> <tagNode name="client-config"> <properties> <help>Generate Client config QR code</help> @@ -59,12 +77,6 @@ </tagNode> </children> </tagNode> - <leafNode name="key-pair"> - <properties> - <help>Generate Wireguard key pair for use with server or peer</help> - </properties> - <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --wireguard --key "noname"</command> - </leafNode> </children> </node> </children> diff --git a/op-mode-definitions/include/bgp/afi-common.xml.i b/op-mode-definitions/include/bgp/afi-common.xml.i index 7fc59f3b0..4d5f56656 100644 --- a/op-mode-definitions/include/bgp/afi-common.xml.i +++ b/op-mode-definitions/include/bgp/afi-common.xml.i @@ -7,23 +7,33 @@ </completionHelp> </properties> <children> - <leafNode name="exact-match"> - <properties> - <help>Exact match of the communities</help> - </properties> - <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> - </leafNode> + #include <include/bgp/exact-match.xml.i> </children> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </tagNode> <tagNode name="large-community"> <properties> - <help>List of large-community numbers</help> + <help>Display routes matching the large-communities</help> <completionHelp> <list>AA:BB:CC</list> </completionHelp> </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/bgp/exact-match.xml.i> + </children> +</tagNode> +<tagNode name="large-community-list"> + <properties> + <help>Display routes matching the large-community-list</help> + <completionHelp> + <path>policy large-community-list</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/bgp/exact-match.xml.i> + </children> </tagNode> <leafNode name="statistics"> <properties> diff --git a/op-mode-definitions/include/bgp/afi-ipv4-ipv6-common.xml.i b/op-mode-definitions/include/bgp/afi-ipv4-ipv6-common.xml.i index f1b699347..a51595b7f 100644 --- a/op-mode-definitions/include/bgp/afi-ipv4-ipv6-common.xml.i +++ b/op-mode-definitions/include/bgp/afi-ipv4-ipv6-common.xml.i @@ -22,12 +22,7 @@ </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </leafNode> - <leafNode name="exact-match"> - <properties> - <help>Exact match of the communities</help> - </properties> - <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> - </leafNode> + #include <include/bgp/exact-match.xml.i> <leafNode name="graceful-shutdown"> <properties> <help>Graceful shutdown (well-known community)</help> @@ -105,12 +100,7 @@ </completionHelp> </properties> <children> - <leafNode name="exact-match"> - <properties> - <help>Show BGP routes exactly matching specified community list</help> - </properties> - <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> - </leafNode> + #include <include/bgp/exact-match.xml.i> </children> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </tagNode> diff --git a/op-mode-definitions/include/bgp/afi-ipv4-ipv6-vpn.xml.i b/op-mode-definitions/include/bgp/afi-ipv4-ipv6-vpn.xml.i new file mode 100644 index 000000000..ba6edb256 --- /dev/null +++ b/op-mode-definitions/include/bgp/afi-ipv4-ipv6-vpn.xml.i @@ -0,0 +1,23 @@ +<!-- included start from bgp/afi-ipv4-ipv6-vpn.xml.i --> +<tagNode name="vpn"> + <properties> + <help>Network in the BGP routing table to display</help> + <completionHelp> + <list><x.x.x.x> <x.x.x.x/x> <h:h:h:h:h:h:h:h> <h:h:h:h:h:h:h:h/x></list> + </completionHelp> + </properties> + <children> + #include <include/bgp/prefix-bestpath-multipath.xml.i> + </children> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</tagNode> +<node name="vpn"> + <properties> + <help>VPN Address Family modifier</help> + </properties> + <children> + #include <include/bgp/afi-common.xml.i> + #include <include/bgp/afi-ipv4-ipv6-common.xml.i> + </children> +</node> +<!-- included end --> diff --git a/op-mode-definitions/include/bgp/exact-match.xml.i b/op-mode-definitions/include/bgp/exact-match.xml.i new file mode 100644 index 000000000..49026db9b --- /dev/null +++ b/op-mode-definitions/include/bgp/exact-match.xml.i @@ -0,0 +1,8 @@ +<!-- included start from bgp/exact-match.xml.i --> +<leafNode name="exact-match"> + <properties> + <help>Exact match of the communities</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/bgp/show-bgp-common.xml.i b/op-mode-definitions/include/bgp/show-bgp-common.xml.i index b86b09056..e81b26b3e 100644 --- a/op-mode-definitions/include/bgp/show-bgp-common.xml.i +++ b/op-mode-definitions/include/bgp/show-bgp-common.xml.i @@ -20,7 +20,9 @@ <children> #include <include/bgp/afi-common.xml.i> #include <include/bgp/afi-ipv4-ipv6-common.xml.i> + #include <include/bgp/afi-ipv4-ipv6-vpn.xml.i> </children> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </node> <tagNode name="ipv6"> <properties> @@ -41,7 +43,9 @@ <children> #include <include/bgp/afi-common.xml.i> #include <include/bgp/afi-ipv4-ipv6-common.xml.i> + #include <include/bgp/afi-ipv4-ipv6-vpn.xml.i> </children> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </node> <node name="l2vpn"> <properties> diff --git a/op-mode-definitions/ipv4-route.xml.in b/op-mode-definitions/ipv4-route.xml.in index aab3df0f1..8f001d5bb 100644 --- a/op-mode-definitions/ipv4-route.xml.in +++ b/op-mode-definitions/ipv4-route.xml.in @@ -20,11 +20,7 @@ </node> </children> </node> - <node name="reset"> - <properties> - <help>Reset a service</help> - </properties> <children> <node name="ip"> <properties> @@ -56,7 +52,6 @@ </tagNode> </children> </node> - <node name="route"> <properties> <help>Reset IP route</help> @@ -68,7 +63,6 @@ </properties> <command>sudo ip route flush cache</command> </leafNode> - <tagNode name="cache"> <properties> <help>Flush the kernel route cache for a given route</help> diff --git a/op-mode-definitions/ipv6-route.xml.in b/op-mode-definitions/ipv6-route.xml.in index 7f188fdb2..5f20444d4 100644 --- a/op-mode-definitions/ipv6-route.xml.in +++ b/op-mode-definitions/ipv6-route.xml.in @@ -28,11 +28,7 @@ </node> </children> </node> - <node name="reset"> - <properties> - <help>Reset a service</help> - </properties> <children> <node name="ipv6"> <properties> @@ -64,7 +60,6 @@ </tagNode> </children> </node> - <node name="route"> <properties> <help>Reset IPv6 route</help> @@ -76,7 +71,6 @@ </properties> <command>sudo ip -f inet6 route flush cache</command> </leafNode> - <tagNode name="cache"> <properties> <help>Flush the kernel IPv6 route cache for a given route</help> diff --git a/op-mode-definitions/monitor-protocol.xml.in b/op-mode-definitions/monitor-protocol.xml.in index 6a6bd50f3..f3af3575c 100644 --- a/op-mode-definitions/monitor-protocol.xml.in +++ b/op-mode-definitions/monitor-protocol.xml.in @@ -263,13 +263,14 @@ </node> <node name="ospf"> <properties> - <help>Monitor the Open Shortest Path First (OSPF) protocol</help> + <help>Monitor Open Shortest Path First (OSPF) protocol</help> </properties> <children> #include <include/monitor-background.xml.i> - - <node name="disable"> + <properties> + <help>Disable Open Shortest Path First (OSPF) debugging</help> + </properties> <children> <node name="event"> <properties> @@ -458,6 +459,9 @@ </children> </node> <node name="enable"> + <properties> + <help>Enable Open Shortest Path First (OSPF) debugging</help> + </properties> <children> <node name="event"> <properties> diff --git a/op-mode-definitions/nhrp.xml.in b/op-mode-definitions/nhrp.xml.in index 9e746cc35..89508e2be 100644 --- a/op-mode-definitions/nhrp.xml.in +++ b/op-mode-definitions/nhrp.xml.in @@ -50,13 +50,13 @@ <properties> <help>Show NHRP interface connection information</help> </properties> - <command>if [ -f /var/run/opennhrp.pid ]; then sudo opennhrpctl interface show; else echo OpenNHRP is not running.; fi</command> + <command>if pgrep opennhrp >/dev/null; then sudo opennhrpctl interface show; else echo OpenNHRP is not running; fi</command> </leafNode> <leafNode name="tunnel"> <properties> <help>Show NHRP tunnel connection information</help> </properties> - <command>if [ -f /var/run/opennhrp.pid ]; then sudo opennhrpctl show ; else echo OpenNHRP is not running.; fi</command> + <command>if pgrep opennhrp >/dev/null; then sudo opennhrpctl show ; else echo OpenNHRP is not running; fi</command> </leafNode> </children> </node> diff --git a/op-mode-definitions/openvpn.xml.in b/op-mode-definitions/openvpn.xml.in index 7243d69fd..301688271 100644 --- a/op-mode-definitions/openvpn.xml.in +++ b/op-mode-definitions/openvpn.xml.in @@ -1,11 +1,11 @@ <?xml version="1.0"?> <interfaceDefinition> <node name="reset"> - <properties> - <help>Reset a service</help> - </properties> <children> <node name="openvpn"> + <properties> + <help>Reset OpenVPN client/server connections</help> + </properties> <children> <tagNode name="client"> <properties> diff --git a/op-mode-definitions/pki.xml.in b/op-mode-definitions/pki.xml.in index a11814c8a..a1c55dcf4 100644 --- a/op-mode-definitions/pki.xml.in +++ b/op-mode-definitions/pki.xml.in @@ -282,60 +282,66 @@ </node> <node name="wireguard"> <properties> - <help>Generate Wireguard keys</help> + <help>Generate WireGuard keys</help> </properties> <children> <node name="key-pair"> <properties> - <help>Generate Wireguard key pair for use with server or peer</help> + <help>Generate WireGuard public/private key-pair</help> </properties> <children> - <tagNode name="file"> - <properties> - <help>Write generated Wireguard keys into the specified filename</help> - <completionHelp> - <list><filename></list> - </completionHelp> - </properties> - <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --wireguard --key "$6" --file</command> - </tagNode> - <tagNode name="install"> + <node name="install"> <properties> - <help>Commands for installing generated Wireguard key into running configuration</help> - <completionHelp> - <list><interface> <peer></list> - </completionHelp> + <help>Generate CLI commands to install WireGuard key to configuration</help> </properties> - <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --wireguard --key "$6" --install</command> - </tagNode> + <children> + <tagNode name="interface"> + <properties> + <help>WireGuard Interface used in install command</help> + <completionHelp> + <path>interfaces wireguard</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --wireguard --key --interface "$7" --install</command> + </tagNode> + </children> + </node> </children> - <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --wireguard --key "noname"</command> + <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --wireguard --key</command> </node> - <node name="pre-shared-key"> + <node name="preshared-key"> <properties> - <help>Generate pre-shared key for use with a Wireguard peer</help> + <help>Generate WireGuard pre-shared key</help> </properties> <children> - <tagNode name="file"> + <node name="install"> <properties> - <help>Write generated Wireguard PSK into the specified filename</help> - <completionHelp> - <list><filename></list> - </completionHelp> + <help>Generate CLI commands to install WireGuard key to configuration</help> </properties> - <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --wireguard --psk "$6" --file</command> - </tagNode> - <tagNode name="install"> - <properties> - <help>Commands for installing generated Wireguard PSK on specified peer into running configuration</help> - <completionHelp> - <list><peer></list> - </completionHelp> - </properties> - <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --wireguard --psk "$6" --install</command> - </tagNode> + <children> + <tagNode name="interface"> + <properties> + <help>WireGuard Interface used in install command</help> + <completionHelp> + <path>interfaces wireguard</path> + </completionHelp> + </properties> + <children> + <tagNode name="peer"> + <properties> + <help>Interface used for install command</help> + <completionHelp> + <path>interfaces wireguard ${COMP_WORDS[COMP_CWORD-2]} peer</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --wireguard --psk --interface "$7" --peer "$9" --install</command> + </tagNode> + </children> + </tagNode> + </children> + </node> </children> - <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --wireguard --psk "noname"</command> + <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --wireguard --psk</command> </node> </children> </node> @@ -347,60 +353,45 @@ <children> <node name="pki"> <properties> - <help>Show PKI certificates</help> + <help>Show PKI x509 certificates</help> </properties> <children> - <node name="ca"> + <leafNode name="ca"> <properties> - <help>Show CA certificates</help> + <help>Show x509 CA certificates</help> </properties> - <children> - <leafNode name="name"> - <properties> - <help>Show CA certificate by name</help> - <completionHelp> - <path>pki ca</path> - </completionHelp> - </properties> - <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --ca "$5"</command> - </leafNode> - </children> <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --ca "all"</command> - </node> - <node name="certificate"> + </leafNode> + <tagNode name="ca"> <properties> - <help>Show certificates</help> + <help>Show x509 CA certificate by name</help> + <completionHelp> + <path>pki ca</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --ca "$4"</command> + </tagNode> + <leafNode name="certificate"> + <properties> + <help>Show x509 certificates</help> </properties> - <children> - <leafNode name="name"> - <properties> - <help>Show certificate by name</help> - <completionHelp> - <path>pki certificate</path> - </completionHelp> - </properties> - <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --certificate "$5"</command> - </leafNode> - </children> <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --certificate "all"</command> - </node> - <node name="crl"> + </leafNode> + <tagNode name="certificate"> <properties> - <help>Show certificate revocation lists</help> + <help>Show x509 certificate by name</help> + <completionHelp> + <path>pki certificate</path> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --certificate "$4"</command> + </tagNode> + <leafNode name="crl"> + <properties> + <help>Show x509 certificate revocation lists</help> </properties> - <children> - <leafNode name="name"> - <properties> - <help>Show certificate revocation lists from specified CA</help> - <completionHelp> - <path>pki ca</path> - </completionHelp> - </properties> - <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --crl "$5"</command> - </leafNode> - </children> <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --crl "all"</command> - </node> + </leafNode> </children> <command>sudo ${vyos_op_scripts_dir}/pki.py --action show</command> </node> diff --git a/op-mode-definitions/pppoe-server.xml.in b/op-mode-definitions/pppoe-server.xml.in index 6efdc5a48..835e03aab 100644 --- a/op-mode-definitions/pppoe-server.xml.in +++ b/op-mode-definitions/pppoe-server.xml.in @@ -40,9 +40,6 @@ </children> </node> <node name="reset"> - <properties> - <help>Reset a service</help> - </properties> <children> <node name="pppoe-server"> <properties> diff --git a/op-mode-definitions/reset-conntrack.xml.in b/op-mode-definitions/reset-conntrack.xml.in index 827ba4af4..9c8265f77 100644 --- a/op-mode-definitions/reset-conntrack.xml.in +++ b/op-mode-definitions/reset-conntrack.xml.in @@ -1,9 +1,6 @@ <?xml version="1.0"?> <interfaceDefinition> <node name="reset"> - <properties> - <help>Reset a service</help> - </properties> <children> <node name="conntrack"> <properties> diff --git a/op-mode-definitions/reset-vpn.xml.in b/op-mode-definitions/reset-vpn.xml.in index 71dbb4ed9..94ee1c7df 100644 --- a/op-mode-definitions/reset-vpn.xml.in +++ b/op-mode-definitions/reset-vpn.xml.in @@ -1,9 +1,6 @@ <?xml version="1.0"?> <interfaceDefinition> <node name="reset"> - <properties> - <help>Reset a service</help> - </properties> <children> <node name="vpn"> <properties> diff --git a/op-mode-definitions/restart-frr.xml.in b/op-mode-definitions/restart-frr.xml.in index 96ad1a650..475bd1ee8 100644 --- a/op-mode-definitions/restart-frr.xml.in +++ b/op-mode-definitions/restart-frr.xml.in @@ -2,62 +2,66 @@ <interfaceDefinition> <node name="restart"> <children> - <node name="frr"> + <leafNode name="all"> <properties> - <help>Restart FRRouting daemons</help> + <help>Restart all routing daemons</help> </properties> <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart</command> - <children> - <leafNode name="bfdd"> - <properties> - <help>Restart Bidirectional Forwarding Detection daemon</help> - </properties> - <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon bfdd</command> - </leafNode> - <leafNode name="bgpd"> - <properties> - <help>Restart Border Gateway Protocol daemon</help> - </properties> - <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon bgpd</command> - </leafNode> - <leafNode name="ospfd"> - <properties> - <help>Restart OSPFv2 daemon</help> - </properties> - <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon ospfd</command> - </leafNode> - <leafNode name="ospf6d"> - <properties> - <help>Restart OSPFv3 daemon</help> - </properties> - <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon ospf6d</command> - </leafNode> - <leafNode name="ripd"> - <properties> - <help>Restart Routing Information Protocol daemon</help> - </properties> - <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon ripd</command> - </leafNode> - <leafNode name="ripngd"> - <properties> - <help>Restart RIPng daemon</help> - </properties> - <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon ripngd</command> - </leafNode> - <leafNode name="staticd"> - <properties> - <help>Restart Static Route daemon</help> - </properties> - <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon staticd</command> - </leafNode> - <leafNode name="zebra"> - <properties> - <help>Restart IP routing manager daemon</help> - </properties> - <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon zebra</command> - </leafNode> - </children> - </node> + </leafNode> + <leafNode name="bfd"> + <properties> + <help>Restart Bidirectional Forwarding Detection (BFD) daemon</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon bfdd</command> + </leafNode> + <leafNode name="bgp"> + <properties> + <help>Restart Border Gateway Protocol (BGP) routing daemon</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon bgpd</command> + </leafNode> + <leafNode name="isis"> + <properties> + <help>Restart Intermediate System to Intermediate System (IS-IS) routing daemon</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon isisd</command> + </leafNode> + <leafNode name="ospf"> + <properties> + <help>Restart Open Shortest Path First (OSPF) routing daemon</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon ospfd</command> + </leafNode> + <leafNode name="ospfv3"> + <properties> + <help>Restart IPv6 Open Shortest Path First (OSPFv3) routing daemon</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon ospf6d</command> + </leafNode> + <leafNode name="rip"> + <properties> + <help>Restart Routing Information Protocol (RIP) routing daemon</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon ripd</command> + </leafNode> + <leafNode name="ripng"> + <properties> + <help>Restart Routing Information Protocol NG (RIPng) routing daemon</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon ripngd</command> + </leafNode> + <leafNode name="static"> + <properties> + <help>Restart static routing daemon</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon staticd</command> + </leafNode> + <leafNode name="zebra"> + <properties> + <help>Restart Routing Information Base (RIB) manager daemon</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon zebra</command> + </leafNode> </children> </node> </interfaceDefinition> diff --git a/op-mode-definitions/show-interfaces-bonding.xml.in b/op-mode-definitions/show-interfaces-bonding.xml.in index 08ce78296..c5f82b70e 100644 --- a/op-mode-definitions/show-interfaces-bonding.xml.in +++ b/op-mode-definitions/show-interfaces-bonding.xml.in @@ -6,7 +6,7 @@ <children> <tagNode name="bonding"> <properties> - <help>Show bonding interface information</help> + <help>Show specified Bonding interface information</help> <completionHelp> <path>interfaces bonding</path> </completionHelp> @@ -58,7 +58,7 @@ </tagNode> <node name="bonding"> <properties> - <help>Show bonding interface information</help> + <help>Show Bonding interface information</help> </properties> <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=bonding --action=show-brief</command> <children> diff --git a/op-mode-definitions/show-interfaces-bridge.xml.in b/op-mode-definitions/show-interfaces-bridge.xml.in index 85fde95b5..e1444bd84 100644 --- a/op-mode-definitions/show-interfaces-bridge.xml.in +++ b/op-mode-definitions/show-interfaces-bridge.xml.in @@ -6,7 +6,7 @@ <children> <tagNode name="bridge"> <properties> - <help>Show bridge interface information</help> + <help>Show specified Bridge interface information</help> <completionHelp> <path>interfaces bridge</path> </completionHelp> @@ -23,7 +23,7 @@ </tagNode> <node name="bridge"> <properties> - <help>Show bridge interface information</help> + <help>Show Bridge interface information</help> </properties> <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=bridge --action=show-brief</command> <children> diff --git a/op-mode-definitions/show-interfaces-dummy.xml.in b/op-mode-definitions/show-interfaces-dummy.xml.in index 7c24c6921..52d2cc7ee 100644 --- a/op-mode-definitions/show-interfaces-dummy.xml.in +++ b/op-mode-definitions/show-interfaces-dummy.xml.in @@ -6,7 +6,7 @@ <children> <tagNode name="dummy"> <properties> - <help>Show dummy interface information</help> + <help>Show specified Dummy interface information</help> <completionHelp> <path>interfaces dummy</path> </completionHelp> @@ -23,7 +23,7 @@ </tagNode> <node name="dummy"> <properties> - <help>Show dummy interface information</help> + <help>Show Dummy interface information</help> </properties> <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=dummy --action=show-brief</command> <children> diff --git a/op-mode-definitions/show-interfaces-ethernet.xml.in b/op-mode-definitions/show-interfaces-ethernet.xml.in index fc79f44bf..f8d1c9395 100644 --- a/op-mode-definitions/show-interfaces-ethernet.xml.in +++ b/op-mode-definitions/show-interfaces-ethernet.xml.in @@ -6,7 +6,7 @@ <children> <tagNode name="ethernet"> <properties> - <help>Show ethernet interface information</help> + <help>Show specified Ethernet interface information</help> <completionHelp> <path>interfaces ethernet</path> </completionHelp> @@ -23,19 +23,19 @@ <properties> <help>Visually identify specified ethernet interface</help> </properties> - <command>echo "Blinking interface $4 for 30 seconds."; /sbin/ethtool --identify "$4" 30</command> + <command>echo "Blinking interface $4 for 30 seconds."; ethtool --identify "$4" 30</command> </leafNode> <node name="physical"> <properties> <help>Show physical device information for specified ethernet interface</help> </properties> - <command>/sbin/ethtool "$4"; /sbin/ethtool -i "$4"</command> + <command>ethtool "$4"; ethtool --show-ring "$4"; ethtool --driver "$4"</command> <children> <leafNode name="offload"> <properties> <help>Show physical device offloading capabilities</help> </properties> - <command>/sbin/ethtool -k "$4" | sed -e 1d -e '/fixed/d' -e 's/^\t*//g' -e 's/://' | column -t -s' '</command> + <command>ethtool --show-features "$4" | sed -e 1d -e '/fixed/d' -e 's/^\t*//g' -e 's/://' | column -t -s' '</command> </leafNode> </children> </node> @@ -43,13 +43,13 @@ <properties> <help>Show physical device statistics for specified ethernet interface</help> </properties> - <command>/sbin/ethtool -S "$4"</command> + <command>ethtool --statistics "$4"</command> </leafNode> <leafNode name="transceiver"> <properties> <help>Show transceiver information from modules (e.g SFP+, QSFP)</help> </properties> - <command>/sbin/ethtool -m "$4"</command> + <command>ethtool --module-info "$4"</command> </leafNode> <tagNode name="vif"> <properties> @@ -78,7 +78,7 @@ </tagNode> <node name="ethernet"> <properties> - <help>Show ethernet interface information</help> + <help>Show Ethernet interface information</help> </properties> <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=ethernet --action=show-brief</command> <children> diff --git a/op-mode-definitions/show-interfaces-input.xml.in b/op-mode-definitions/show-interfaces-input.xml.in index 15e8203e5..9ae3828c8 100644 --- a/op-mode-definitions/show-interfaces-input.xml.in +++ b/op-mode-definitions/show-interfaces-input.xml.in @@ -6,7 +6,7 @@ <children> <tagNode name="input"> <properties> - <help>Show input interface information</help> + <help>Show specified Input interface information</help> <completionHelp> <path>interfaces input</path> </completionHelp> @@ -23,7 +23,7 @@ </tagNode> <node name="input"> <properties> - <help>Show input interface information</help> + <help>Show Input (ifb) interface information</help> </properties> <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=input --action=show-brief</command> <children> diff --git a/op-mode-definitions/show-interfaces-l2tpv3.xml.in b/op-mode-definitions/show-interfaces-l2tpv3.xml.in index 60fee34a1..2a1d6a1c6 100644 --- a/op-mode-definitions/show-interfaces-l2tpv3.xml.in +++ b/op-mode-definitions/show-interfaces-l2tpv3.xml.in @@ -6,7 +6,7 @@ <children> <tagNode name="l2tpv3"> <properties> - <help>Show L2TPv3 interface information</help> + <help>Show specified L2TPv3 interface information</help> <completionHelp> <path>interfaces l2tpv3</path> </completionHelp> diff --git a/op-mode-definitions/show-interfaces-loopback.xml.in b/op-mode-definitions/show-interfaces-loopback.xml.in index b30b57909..25a75ffff 100644 --- a/op-mode-definitions/show-interfaces-loopback.xml.in +++ b/op-mode-definitions/show-interfaces-loopback.xml.in @@ -6,7 +6,7 @@ <children> <tagNode name="loopback"> <properties> - <help>Show loopback interface information</help> + <help>Show specified Loopback interface information</help> <completionHelp> <path>interfaces loopback</path> </completionHelp> @@ -23,7 +23,7 @@ </tagNode> <node name="loopback"> <properties> - <help>Show loopback interface information</help> + <help>Show Loopback interface information</help> </properties> <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=loopback --action=show-brief</command> <children> diff --git a/op-mode-definitions/show-interfaces-pppoe.xml.in b/op-mode-definitions/show-interfaces-pppoe.xml.in index 18697a275..767836abf 100644 --- a/op-mode-definitions/show-interfaces-pppoe.xml.in +++ b/op-mode-definitions/show-interfaces-pppoe.xml.in @@ -6,7 +6,7 @@ <children> <tagNode name="pppoe"> <properties> - <help>Show PPPoE interface information</help> + <help>Show specified PPPoE interface information</help> <completionHelp> <path>interfaces pppoe</path> </completionHelp> diff --git a/op-mode-definitions/show-interfaces-pseudo-ethernet.xml.in b/op-mode-definitions/show-interfaces-pseudo-ethernet.xml.in index 195944745..2ae4b5a9e 100644 --- a/op-mode-definitions/show-interfaces-pseudo-ethernet.xml.in +++ b/op-mode-definitions/show-interfaces-pseudo-ethernet.xml.in @@ -6,7 +6,7 @@ <children> <tagNode name="pseudo-ethernet"> <properties> - <help>Show pseudo-ethernet/MACvlan interface information</help> + <help>Show specified Pseudo-Ethernet/MACvlan interface information</help> <completionHelp> <path>interfaces pseudo-ethernet</path> </completionHelp> @@ -23,7 +23,7 @@ </tagNode> <node name="pseudo-ethernet"> <properties> - <help>Show pseudo-ethernet/MACvlan interface information</help> + <help>Show Pseudo-Ethernet/MACvlan interface information</help> </properties> <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=pseudo-ethernet --action=show-brief</command> <children> diff --git a/op-mode-definitions/show-interfaces-tunnel.xml.in b/op-mode-definitions/show-interfaces-tunnel.xml.in index 416de0299..51b25efd9 100644 --- a/op-mode-definitions/show-interfaces-tunnel.xml.in +++ b/op-mode-definitions/show-interfaces-tunnel.xml.in @@ -6,7 +6,7 @@ <children> <tagNode name="tunnel"> <properties> - <help>Show tunnel interface information</help> + <help>Show specified Tunnel interface information</help> <completionHelp> <path>interfaces tunnel</path> </completionHelp> @@ -23,7 +23,7 @@ </tagNode> <node name="tunnel"> <properties> - <help>Show tunnel interface information</help> + <help>Show Tunnel interface information</help> </properties> <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=tunnel --action=show-brief</command> <children> diff --git a/op-mode-definitions/show-interfaces-vti.xml.in b/op-mode-definitions/show-interfaces-vti.xml.in index f51be2d19..b436b8414 100644 --- a/op-mode-definitions/show-interfaces-vti.xml.in +++ b/op-mode-definitions/show-interfaces-vti.xml.in @@ -6,7 +6,7 @@ <children> <tagNode name="vti"> <properties> - <help>Show vti interface information</help> + <help>Show specified VTI interface information</help> <completionHelp> <path>interfaces vti</path> </completionHelp> @@ -23,7 +23,7 @@ </tagNode> <node name="vti"> <properties> - <help>Show vti interface information</help> + <help>Show VTI interface information</help> </properties> <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=vti --action=show-brief</command> <children> diff --git a/op-mode-definitions/show-interfaces-vxlan.xml.in b/op-mode-definitions/show-interfaces-vxlan.xml.in index 4e3cb93cd..1befd428c 100644 --- a/op-mode-definitions/show-interfaces-vxlan.xml.in +++ b/op-mode-definitions/show-interfaces-vxlan.xml.in @@ -6,7 +6,7 @@ <children> <tagNode name="vxlan"> <properties> - <help>Show VXLAN interface information</help> + <help>Show specified VXLAN interface information</help> <completionHelp> <path>interfaces vxlan</path> </completionHelp> diff --git a/op-mode-definitions/show-interfaces-wireguard.xml.in b/op-mode-definitions/show-interfaces-wireguard.xml.in index 863357ef7..c9b754dcd 100644 --- a/op-mode-definitions/show-interfaces-wireguard.xml.in +++ b/op-mode-definitions/show-interfaces-wireguard.xml.in @@ -6,7 +6,7 @@ <children> <tagNode name="wireguard"> <properties> - <help>Show Wireguard interface information</help> + <help>Show specified WireGuard interface information</help> <completionHelp> <script>${vyos_completion_dir}/list_interfaces.py --type wireguard</script> </completionHelp> @@ -47,7 +47,7 @@ </tagNode> <node name="wireguard"> <properties> - <help>Show Wireguard interface information</help> + <help>Show WireGuard interface information</help> </properties> <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=wireguard --action=show-brief</command> <children> diff --git a/op-mode-definitions/show-interfaces-wireless.xml.in b/op-mode-definitions/show-interfaces-wireless.xml.in new file mode 100644 index 000000000..4a37417aa --- /dev/null +++ b/op-mode-definitions/show-interfaces-wireless.xml.in @@ -0,0 +1,82 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="interfaces"> + <children> + <node name="wireless"> + <properties> + <help>Show Wireless (WLAN) interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=wireless --action=show-brief</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed wireless interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=wireless --action=show</command> + </leafNode> + <leafNode name="info"> + <properties> + <help>Show wireless interface configuration</help> + </properties> + <command>${vyos_op_scripts_dir}/show_wireless.py --brief</command> + </leafNode> + </children> + </node> + <tagNode name="wireless"> + <properties> + <help>Show specified wireless interface information</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces.py --type wireless</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command> + <children> + <leafNode name="brief"> + <properties> + <help>Show summary of the specified wireless interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command> + </leafNode> + <node name="scan"> + <properties> + <help>Show summary of the specified wireless interface information</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/show_wireless.py --scan "$4"</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed scan results</help> + </properties> + <command>sudo /sbin/iw dev "$4" scan ap-force</command> + </leafNode> + </children> + </node> + <leafNode name="stations"> + <properties> + <help>Show specified Wireless interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/show_wireless.py --stations "$4"</command> + </leafNode> + <tagNode name="vif"> + <properties> + <help>Show specified virtual network interface (vif) information</help> + </properties> + <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4.$6"</command> + <children> + <leafNode name="brief"> + <properties> + <help>Show summary of specified virtual network interface (vif) information</help> + </properties> + <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4.$6" --action=show-brief</command> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-interfaces-wwan.xml.in b/op-mode-definitions/show-interfaces-wwan.xml.in index d57e17a13..3cd29b38a 100644 --- a/op-mode-definitions/show-interfaces-wwan.xml.in +++ b/op-mode-definitions/show-interfaces-wwan.xml.in @@ -6,7 +6,7 @@ <children> <tagNode name="wwan"> <properties> - <help>Show Wireless Wire Area Network (WWAN) interface information</help> + <help>Show specified Wireless Wire Area Network (WWAN) interface information</help> <completionHelp> <path>interfaces wwan</path> <script>cd /sys/class/net; ls -d wwan*</script> @@ -68,9 +68,9 @@ </properties> <command>sudo ${vyos_op_scripts_dir}/show_wwan.py --interface=$4 --sim</command> </leafNode> - <leafNode name="summary"> + <leafNode name="detail"> <properties> - <help>Show WWAN module information summary</help> + <help>Show WWAN module detailed information summary</help> </properties> <command>mmcli --modem ${4#wwan}</command> </leafNode> diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in index 92c1cf016..4c0a7913b 100644 --- a/op-mode-definitions/show-log.xml.in +++ b/op-mode-definitions/show-log.xml.in @@ -6,7 +6,7 @@ <properties> <help>Show contents of current master log file</help> </properties> - <command>/bin/journalctl</command> + <command>journalctl --no-hostname --boot</command> <children> <leafNode name="all"> <properties> @@ -18,7 +18,7 @@ <properties> <help>Show listing of authorization attempts</help> </properties> - <command>/bin/journalctl --quiet SYSLOG_FACILITY=10 SYSLOG_FACILITY=4</command> + <command>journalctl --no-hostname --boot --quiet SYSLOG_FACILITY=10 SYSLOG_FACILITY=4</command> </leafNode> <leafNode name="cluster"> <properties> @@ -30,14 +30,68 @@ <properties> <help>Show log for Conntrack-sync</help> </properties> - <command>cat $(printf "%s\n" /var/log/messages* | sort -nr ) | grep -e conntrackd</command> + <command>journalctl --no-hostname --boot --unit conntrackd.service</command> </leafNode> - <leafNode name="dhcp"> + <node name="dhcp"> <properties> <help>Show log for Dynamic Host Control Protocol (DHCP)</help> </properties> - <command>cat $(printf "%s\n" /var/log/messages* | sort -nr) | grep dhcpd</command> - </leafNode> + <children> + <node name="server"> + <properties> + <help>Show log for DHCP server</help> + </properties> + <command>journalctl --no-hostname --boot --unit isc-dhcp-server.service</command> + </node> + <node name="client"> + <properties> + <help>Show DHCP client logs</help> + </properties> + <command>journalctl --no-hostname --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 --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 --boot --unit isc-dhcp-server6.service</command> + </node> + <node name="client"> + <properties> + <help>Show DHCPv6 client logs</help> + </properties> + <command>journalctl --no-hostname --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 --boot --unit "dhcp6c@$6.service"</command> + </tagNode> + </children> + </node> + </children> + </node> <node name="firewall"> <properties> <help>Show log for Firewall</help> @@ -89,7 +143,7 @@ <properties> <help>Show log for HTTPs</help> </properties> - <command>cat $(printf "%s\n" /var/log/messages* | sort -nr) | grep -e nginx</command> + <command>journalctl --no-hostname --boot --unit nginx.service</command> </leafNode> <tagNode name="image"> <properties> @@ -119,7 +173,7 @@ <list><NUMBER></list> </completionHelp> </properties> - <command>tail -n "$6" /lib/live/mount/persistence/boot/$4/rw/var/log/messages | ${VYATTA_PAGER:-cat}</command> + <command>tail -n "$6" /lib/live/mount/persistence/boot/$4/rw/var/log/messages | ${VYATTA_PAGER:-cat}</command> </tagNode> </children> </tagNode> @@ -133,7 +187,7 @@ <properties> <help>Show log for LLDP</help> </properties> - <command>cat $(printf "%s\n" /var/log/messages* | sort -nr) | grep -e lldpd</command> + <command>journalctl --no-hostname --boot --unit lldpd.service</command> </leafNode> <leafNode name="nat"> <properties> @@ -141,17 +195,28 @@ </properties> <command>egrep -i "kernel:.*\[NAT-[A-Z]{3,}-[0-9]+(-MASQ)?\]" $(find /var/log -maxdepth 1 -type f -name messages\* | sort -t. -k2nr)</command> </leafNode> - <leafNode name="openvpn"> + <node name="openvpn"> <properties> <help>Show log for OpenVPN</help> </properties> - <command>cat $(printf "%s\n" /var/log/messages* | sort -nr) | grep -e openvpn</command> - </leafNode> + <command>journalctl --no-hostname --boot --unit openvpn@*.service</command> + <children> + <tagNode name="interface"> + <properties> + <help>Show OpenVPN log on specific interface</help> + <completionHelp> + <path>interfaces openvpn</path> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot --unit openvpn@$5.service</command> + </tagNode> + </children> + </node> <leafNode name="snmp"> <properties> <help>Show log for Simple Network Monitoring Protocol (SNMP)</help> </properties> - <command>cat $(printf "%s\n" /var/log/messages* | sort -nr) | grep -e snmpd</command> + <command>journalctl --no-hostname --boot --unit snmpd.service</command> </leafNode> <tagNode name="tail"> <properties> @@ -195,13 +260,13 @@ <properties> <help>Show log for PPTP</help> </properties> - <command>cat $(printf "%s\n" /var/log/messages* | sort -nr) | grep -e accel-pptp -e ppp</command> + <command>journalctl --no-hostname --boot --unit accel-ppp@pptp.service</command> </leafNode> <leafNode name="sstp"> <properties> <help>Show log for SSTP</help> </properties> - <command>cat $(printf "%s\n" /var/log/messages* | sort -nr) | grep -e accel-sstp -e ppp</command> + <command>journalctl --no-hostname --boot --unit accel-ppp@sstp.service</command> </leafNode> </children> </node> @@ -209,13 +274,13 @@ <properties> <help>Show log for Virtual Router Redundancy Protocol (VRRP)</help> </properties> - <command>cat $(printf "%s\n" /var/log/messages* | sort -nr) | grep -e Keepalived_vrrp</command> + <command>journalctl --no-hostname --boot --unit keepalived.service</command> </leafNode> <leafNode name="webproxy"> <properties> <help>Show log for Webproxy</help> </properties> - <command>cat $(printf "%s\n" /var/log/messages* | sort -nr) | grep -e "squid"</command> + <command>journalctl --no-hostname --boot --unit squid.service</command> </leafNode> </children> </node> diff --git a/op-mode-definitions/show-system.xml.in b/op-mode-definitions/show-system.xml.in index 5e9bf719e..18a28868d 100644 --- a/op-mode-definitions/show-system.xml.in +++ b/op-mode-definitions/show-system.xml.in @@ -55,12 +55,6 @@ </properties> <command>${vyos_op_scripts_dir}/show_cpu.py</command> </leafNode> - <leafNode name= "integrity"> - <properties> - <help>Checks overall system integrity</help> - </properties> - <command>sudo ${vyos_op_scripts_dir}/show_system_integrity.py</command> - </leafNode> <leafNode name="kernel-messages"> <properties> <help>Show messages in kernel ring buffer</help> diff --git a/op-mode-definitions/show-vpn.xml.in b/op-mode-definitions/show-vpn.xml.in deleted file mode 100644 index 3fbc74ad1..000000000 --- a/op-mode-definitions/show-vpn.xml.in +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0"?> -<interfaceDefinition> - <node name="show"> - <children> - <node name="vpn"> - <properties> - <help>Show active remote access Virtual Private Network (VPN) sessions</help> - </properties> - <children> - <leafNode name="remote-access"> - <properties> - <help>Show active VPN server sessions</help> - </properties> - <command>${vyos_op_scripts_dir}/show_vpn_ra.py</command> - </leafNode> - </children> - </node> - </children> - </node> -</interfaceDefinition> diff --git a/op-mode-definitions/terminal.xml.in b/op-mode-definitions/terminal.xml.in index 9c4e629cb..2a76de146 100644 --- a/op-mode-definitions/terminal.xml.in +++ b/op-mode-definitions/terminal.xml.in @@ -40,7 +40,6 @@ </properties> <command>builtin $3</command> </tagNode> - <node name="console"> <properties> <help>Control console behaviors</help> @@ -54,13 +53,11 @@ </leafNode> </children> </node> - <node name="terminal"> <properties> <help>Control terminal behaviors</help> </properties> <children> - <node name="key"> <properties> <help>Set key behaviors</help> @@ -77,7 +74,6 @@ </tagNode> </children> </node> - <node name="pager"> <properties> <help>Set terminal pager to default (less)</help> @@ -93,7 +89,6 @@ </properties> <command>VYATTA_PAGER=$4</command> </tagNode> - <tagNode name="length"> <properties> <help>Set terminal to given number of rows (0 disables paging)</help> @@ -103,7 +98,6 @@ </properties> <command>if [ "$4" -eq 0 ]; then VYATTA_PAGER=cat; else VYATTA_PAGER=${_vyatta_default_pager}; stty rows $4; fi</command> </tagNode> - <tagNode name="width"> <properties> <help>Set terminal to given number of columns</help> @@ -117,6 +111,4 @@ </node> </children> </node> - - </interfaceDefinition> diff --git a/op-mode-definitions/vpn-ipsec.xml.in b/op-mode-definitions/vpn-ipsec.xml.in index 20f275e9b..3d997c143 100644 --- a/op-mode-definitions/vpn-ipsec.xml.in +++ b/op-mode-definitions/vpn-ipsec.xml.in @@ -140,6 +140,12 @@ </properties> <command>sudo ip xfrm policy list</command> </node> + <leafNode name="remote-access"> + <properties> + <help>Show active VPN server sessions</help> + </properties> + <command>${vyos_op_scripts_dir}/show_vpn_ra.py</command> + </leafNode> <node name="sa"> <properties> <help>Show all active IPSec Security Associations (SA)</help> @@ -178,7 +184,7 @@ <command>if pgrep charon >/dev/null ; then sudo /usr/sbin/ipsec statusall ; else echo "IPSec process not running" ; fi</command> </node> </children> - <command>if pgrep charon >/dev/null ; then sudo /usr/libexec/vyos/op_mode/show_ipsec_sa.py ; else echo "IPSec process not running" ; fi</command> + <command>if pgrep charon >/dev/null ; then sudo ${vyos_op_scripts_dir}/show_ipsec_sa.py ; else echo "IPSec process not running" ; fi</command> </node> <node name="state"> <properties> diff --git a/op-mode-definitions/wireless.xml.in b/op-mode-definitions/wireless.xml.in index a3a9d1f55..5d9db1544 100644 --- a/op-mode-definitions/wireless.xml.in +++ b/op-mode-definitions/wireless.xml.in @@ -37,83 +37,4 @@ </node> </children> </node> - <node name="show"> - <children> - <node name="interfaces"> - <children> - <node name="wireless"> - <properties> - <help>Show wireless interface information</help> - </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=wireless --action=show-brief</command> - <children> - <leafNode name="detail"> - <properties> - <help>Show detailed wireless interface information</help> - </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=wireless --action=show</command> - </leafNode> - <leafNode name="info"> - <properties> - <help>Show wireless interface configuration</help> - </properties> - <command>${vyos_op_scripts_dir}/show_wireless.py --brief</command> - </leafNode> - </children> - </node> - <tagNode name="wireless"> - <properties> - <help>Show specified wireless interface information</help> - <completionHelp> - <script>${vyos_completion_dir}/list_interfaces.py --type wireless</script> - </completionHelp> - </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command> - <children> - <leafNode name="brief"> - <properties> - <help>Show summary of the specified wireless interface information</help> - </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command> - </leafNode> - <node name="scan"> - <properties> - <help>Show summary of the specified wireless interface information</help> - </properties> - <command>sudo ${vyos_op_scripts_dir}/show_wireless.py --scan "$4"</command> - <children> - <leafNode name="detail"> - <properties> - <help>Show detailed scan results</help> - </properties> - <command>sudo /sbin/iw dev "$4" scan ap-force</command> - </leafNode> - </children> - </node> - <leafNode name="stations"> - <properties> - <help>Show specified wireless interface information</help> - </properties> - <command>${vyos_op_scripts_dir}/show_wireless.py --stations "$4"</command> - </leafNode> - <tagNode name="vif"> - <properties> - <help>Show specified virtual network interface (vif) information</help> - </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4.$6"</command> - <children> - <leafNode name="brief"> - <properties> - <help>Show summary of specified virtual network interface (vif) information</help> - </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4.$6" --action=show-brief</command> - </leafNode> - </children> - </tagNode> - </children> - </tagNode> - </children> - </node> - </children> - </node> </interfaceDefinition> diff --git a/python/vyos/airbag.py b/python/vyos/airbag.py index a20f44207..3c7a144b7 100644 --- a/python/vyos/airbag.py +++ b/python/vyos/airbag.py @@ -125,14 +125,14 @@ def _intercepting_exceptions(_singleton=[False]): # if the key before the value has not time, syslog takes that as the source of the message FAULT = """\ -Report Time: {date} -Image Version: VyOS {version} -Release Train: {release_train} +Report time: {date} +Image version: VyOS {version} +Release train: {release_train} Built by: {built_by} Built on: {built_on} Build UUID: {build_uuid} -Build Commit ID: {build_git} +Build commit ID: {build_git} Architecture: {system_arch} Boot via: {boot_via} diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 0969a5353..5c6836e97 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -108,16 +108,20 @@ def leaf_node_changed(conf, path): """ Check if a leaf node was altered. If it has been altered - values has been changed, or it was added/removed, we will return a list containing the old - value(s). If nothing has been changed, None is returned + value(s). If nothing has been changed, None is returned. + + NOTE: path must use the real CLI node name (e.g. with a hyphen!) """ 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 old is None: + return [] if isinstance(old, str): return [old] - elif isinstance(old, list): + if isinstance(old, list): if isinstance(new, str): new = [new] elif isinstance(new, type(None)): @@ -343,8 +347,8 @@ def get_interface_dict(config, base, ifname=''): # 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([], key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True) # Check if interface has been removed. We must use exists() as # get_config_dict() will always return {} - even when an empty interface @@ -371,6 +375,9 @@ 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']) + if address: dict.update({'address_old' : address}) + # Check if we are a member of a bridge device bridge = is_member(config, ifname, 'bridge') if bridge: dict.update({'is_bridge_member' : bridge}) diff --git a/python/vyos/configsource.py b/python/vyos/configsource.py index 50222e385..b0981d25e 100644 --- a/python/vyos/configsource.py +++ b/python/vyos/configsource.py @@ -161,7 +161,7 @@ class ConfigSourceSession(ConfigSource): if p.returncode != 0: raise VyOSError() else: - return out.decode('ascii') + return out.decode('ascii', 'ignore') def set_level(self, path): """ diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 58028b604..8aca76568 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -67,22 +67,22 @@ def verify_mtu_ipv6(config): min_mtu = 1280 if int(config['mtu']) < min_mtu: interface = config['ifname'] - error_msg = f'IPv6 address will be configured on interface "{interface}" ' \ - f'thus the minimum MTU requirement is {min_mtu}!' + error_msg = f'IPv6 address will be configured on interface "{interface}",\n' \ + f'the required minimum MTU is {min_mtu}!' - for address in (dict_search('address', config) or []): - if address in ['dhcpv6'] or is_ipv6(address): - raise ConfigError(error_msg) + if 'address' in config: + for address in config['address']: + if address in ['dhcpv6'] or is_ipv6(address): + raise ConfigError(error_msg) - tmp = dict_search('ipv6.address', config) - if tmp and 'no_default_link_local' not in tmp: - raise ConfigError('link-local ' + error_msg) + tmp = dict_search('ipv6.address.no_default_link_local', config) + if tmp == None: raise ConfigError('link-local ' + error_msg) - if tmp and 'autoconf' in tmp: - raise ConfigError(error_msg) + tmp = dict_search('ipv6.address.autoconf', config) + if tmp != None: raise ConfigError(error_msg) - if tmp and 'eui64' in tmp: - raise ConfigError(error_msg) + tmp = dict_search('ipv6.address.eui64', config) + if tmp != None: raise ConfigError(error_msg) def verify_vrf(config): """ @@ -152,11 +152,10 @@ def verify_eapol(config): if 'certificate' not in config['eapol']: raise ConfigError('Certificate must be specified when using EAPoL!') - if 'certificate' not in config['pki']: + if 'pki' not in config or 'certificate' not in config['pki']: raise ConfigError('Invalid certificate specified for EAPoL') cert_name = config['eapol']['certificate'] - if cert_name not in config['pki']['certificate']: raise ConfigError('Invalid certificate specified for EAPoL') @@ -237,8 +236,8 @@ def verify_interface_exists(ifname): Common helper function used by interface implementations to perform recurring validation if an interface actually exists. """ - from netifaces import interfaces - if ifname not in interfaces(): + import os + if not os.path.exists(f'/sys/class/net/{ifname}'): raise ConfigError(f'Interface "{ifname}" does not exist!') def verify_source_interface(config): @@ -344,7 +343,7 @@ def verify_accel_ppp_base_service(config): # vertify auth settings if dict_search('authentication.mode', config) == 'local': if not dict_search('authentication.local_users', config): - raise ConfigError('PPPoE local auth mode requires local users to be configured!') + raise ConfigError('Authentication mode local requires local users to be configured!') for user in dict_search('authentication.local_users.username', config): user_config = config['authentication']['local_users']['username'][user] @@ -368,7 +367,7 @@ def verify_accel_ppp_base_service(config): raise ConfigError(f'Missing RADIUS secret key for server "{server}"') if 'gateway_address' not in config: - raise ConfigError('PPPoE server requires gateway-address to be configured!') + raise ConfigError('Server requires gateway-address to be configured!') if 'name_server_ipv4' in config: if len(config['name_server_ipv4']) > 2: diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py index 03006c383..00b14a985 100644 --- a/python/vyos/defaults.py +++ b/python/vyos/defaults.py @@ -13,6 +13,7 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see <http://www.gnu.org/licenses/>. +import os directories = { "data": "/usr/share/vyos/", @@ -24,8 +25,8 @@ directories = { "templates": "/usr/share/vyos/templates/", "certbot": "/config/auth/letsencrypt", "api_schema": "/usr/libexec/vyos/services/api/graphql/graphql/schema/", - "api_templates": "/usr/libexec/vyos/services/api/graphql/recipes/templates/" - + "api_templates": "/usr/libexec/vyos/services/api/graphql/recipes/templates/", + "vyos_udev_dir": "/run/udev/vyos" } cfg_group = 'vyattacfg' @@ -34,7 +35,7 @@ cfg_vintage = 'vyos' commit_lock = '/opt/vyatta/config/.lock' -version_file = '/usr/share/vyos/component-versions.json' +component_version_json = os.path.join(directories['data'], 'component-versions.json') https_data = { 'listen_addresses' : { '*': ['_'] } diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py index bc103959a..eb5b0a456 100644 --- a/python/vyos/ethtool.py +++ b/python/vyos/ethtool.py @@ -13,44 +13,92 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see <http://www.gnu.org/licenses/>. +import os +import re + from vyos.util import popen class Ethtool: """ Class is used to retrive and cache information about an ethernet adapter """ - # dictionary containing driver featurs, it will be populated on demand and # the content will look like: # { - # 'tls-hw-tx-offload': {'fixed': True, 'on': False}, - # 'tx-checksum-fcoe-crc': {'fixed': True, 'on': False}, - # 'tx-checksum-ip-generic': {'fixed': False, 'on': True}, - # 'tx-checksum-ipv4': {'fixed': True, 'on': False}, - # 'tx-checksum-ipv6': {'fixed': True, 'on': False}, - # 'tx-checksum-sctp': {'fixed': True, 'on': False}, - # 'tx-checksumming': {'fixed': False, 'on': True}, - # 'tx-esp-segmentation': {'fixed': True, 'on': False}, + # 'tls-hw-tx-offload': {'fixed': True, 'enabled': False}, + # 'tx-checksum-fcoe-crc': {'fixed': True, 'enabled': False}, + # 'tx-checksum-ip-generic': {'fixed': False, 'enabled': True}, + # 'tx-checksum-ipv4': {'fixed': True, 'enabled': False}, + # 'tx-checksum-ipv6': {'fixed': True, 'enabled': False}, + # 'tx-checksum-sctp': {'fixed': True, 'enabled': False}, + # 'tx-checksumming': {'fixed': False, 'enabled': True}, + # 'tx-esp-segmentation': {'fixed': True, 'enabled': False}, # } - features = { } - ring_buffers = { } + _features = { } + # dictionary containing available interface speed and duplex settings + # { + # '10' : {'full': '', 'half': ''}, + # '100' : {'full': '', 'half': ''}, + # '1000': {'full': ''} + # } + _speed_duplex = { } + _ring_buffers = { } + _ring_buffers_max = { } + _driver_name = None + _auto_negotiation = False + _flow_control = False + _flow_control_enabled = None def __init__(self, ifname): + # Get driver used for interface + sysfs_file = f'/sys/class/net/{ifname}/device/driver/module' + if os.path.exists(sysfs_file): + link = os.readlink(sysfs_file) + self._driver_name = os.path.basename(link) + + if not self._driver_name: + raise ValueError(f'Could not determine driver for interface {ifname}!') + + # Build a dictinary of supported link-speed and dupley settings. + out, err = popen(f'ethtool {ifname}') + reading = False + pattern = re.compile(r'\d+base.*') + for line in out.splitlines()[1:]: + line = line.lstrip() + if 'Supported link modes:' in line: + reading = True + if 'Supported pause frame use:' in line: + reading = False + if reading: + for block in line.split(): + if pattern.search(block): + speed = block.split('base')[0] + duplex = block.split('/')[-1].lower() + if speed not in self._speed_duplex: + self._speed_duplex.update({ speed : {}}) + if duplex not in self._speed_duplex[speed]: + self._speed_duplex[speed].update({ duplex : ''}) + if 'Auto-negotiation:' in line: + # Split the following string: Auto-negotiation: off + # we are only interested in off or on + tmp = line.split()[-1] + self._auto_negotiation = bool(tmp == 'on') + # Now populate features dictionaty - out, err = popen(f'ethtool -k {ifname}') + out, err = popen(f'ethtool --show-features {ifname}') # skip the first line, it only says: "Features for eth0": for line in out.splitlines()[1:]: if ":" in line: key, value = [s.strip() for s in line.strip().split(":", 1)] - fixed = "fixed" in value + fixed = bool('fixed' in value) if fixed: value = value.split()[0].strip() - self.features[key.strip()] = { - "on": value == "on", - "fixed": fixed + self._features[key.strip()] = { + 'enabled' : bool(value == 'on'), + 'fixed' : fixed } - out, err = popen(f'ethtool -g {ifname}') + out, err = popen(f'ethtool --show-ring {ifname}') # We are only interested in line 2-5 which contains the device maximum # ringbuffers for line in out.splitlines()[2:6]: @@ -61,45 +109,104 @@ class Ethtool: # output format from 0 -> n/a. As we are only interested in the # tx/rx keys we do not care about RX Mini/Jumbo. if value.isdigit(): - self.ring_buffers[key] = int(value) + self._ring_buffers_max[key] = value + # Now we wan't to get the current RX/TX ringbuffer values - used for + for line in out.splitlines()[7:11]: + if ':' in line: + key, value = [s.strip() for s in line.strip().split(":", 1)] + key = key.lower().replace(' ', '_') + # T3645: ethtool version used on Debian Bullseye changed the + # output format from 0 -> n/a. As we are only interested in the + # tx/rx keys we do not care about RX Mini/Jumbo. + if value.isdigit(): + self._ring_buffers[key] = value + + # Get current flow control settings, but this is not supported by + # all NICs (e.g. vmxnet3 does not support is) + out, err = popen(f'ethtool --show-pause {ifname}') + if len(out.splitlines()) > 1: + self._flow_control = True + # read current flow control setting, this returns: + # ['Autonegotiate:', 'on'] + self._flow_control_enabled = out.splitlines()[1].split()[-1] + + def get_auto_negotiation(self): + return self._auto_negotiation + + def get_driver_name(self): + return self._driver_name + def _get_generic(self, feature): + """ + Generic method to read self._features and return a tuple for feature + enabled and feature is fixed. - def is_fixed_lro(self): - # in case of a missing configuration, rather return "fixed". In Ethtool - # terminology "fixed" means the setting can not be changed by the user. - return self.features.get('large-receive-offload', True).get('fixed', True) + In case of a missing key, return "fixed = True and enabled = False" + """ + fixed = True + enabled = False + if feature in self._features: + if 'enabled' in self._features[feature]: + enabled = self._features[feature]['enabled'] + if 'fixed' in self._features[feature]: + fixed = self._features[feature]['fixed'] + return enabled, fixed - def is_fixed_gro(self): - # in case of a missing configuration, rather return "fixed". In Ethtool - # terminology "fixed" means the setting can not be changed by the user. - return self.features.get('generic-receive-offload', True).get('fixed', True) + def get_generic_receive_offload(self): + return self._get_generic('generic-receive-offload') - def is_fixed_gso(self): - # in case of a missing configuration, rather return "fixed". In Ethtool - # terminology "fixed" means the setting can not be changed by the user. - return self.features.get('generic-segmentation-offload', True).get('fixed', True) + def get_generic_segmentation_offload(self): + return self._get_generic('generic-segmentation-offload') - def is_fixed_sg(self): - # in case of a missing configuration, rather return "fixed". In Ethtool - # terminology "fixed" means the setting can not be changed by the user. - return self.features.get('scatter-gather', True).get('fixed', True) + def get_large_receive_offload(self): + return self._get_generic('large-receive-offload') - def is_fixed_tso(self): - # in case of a missing configuration, rather return "fixed". In Ethtool - # terminology "fixed" means the setting can not be changed by the user. - return self.features.get('tcp-segmentation-offload', True).get('fixed', True) + def get_scatter_gather(self): + return self._get_generic('scatter-gather') - def is_fixed_ufo(self): - # in case of a missing configuration, rather return "fixed". In Ethtool - # terminology "fixed" means the setting can not be changed by the user. - return self.features.get('udp-fragmentation-offload', True).get('fixed', True) + def get_tcp_segmentation_offload(self): + return self._get_generic('tcp-segmentation-offload') - def get_rx_buffer(self): - # Configuration of RX ring-buffers is not supported on every device, + def get_ring_buffer_max(self, rx_tx): + # Configuration of RX/TX ring-buffers is not supported on every device, # thus when it's impossible return None - return self.ring_buffers.get('rx', None) + if rx_tx not in ['rx', 'tx']: + ValueError('Ring-buffer type must be either "rx" or "tx"') + return self._ring_buffers_max.get(rx_tx, None) - def get_tx_buffer(self): - # Configuration of TX ring-buffers is not supported on every device, + def get_ring_buffer(self, rx_tx): + # Configuration of RX/TX ring-buffers is not supported on every device, # thus when it's impossible return None - return self.ring_buffers.get('tx', None) + if rx_tx not in ['rx', 'tx']: + ValueError('Ring-buffer type must be either "rx" or "tx"') + return str(self._ring_buffers.get(rx_tx, None)) + + def check_speed_duplex(self, speed, duplex): + """ Check if the passed speed and duplex combination is supported by + the underlaying network adapter. """ + if isinstance(speed, int): + speed = str(speed) + if speed != 'auto' and not speed.isdigit(): + raise ValueError(f'Value "{speed}" for speed is invalid!') + if duplex not in ['auto', 'full', 'half']: + raise ValueError(f'Value "{duplex}" for duplex is invalid!') + + if self.get_driver_name() in ['vmxnet3', 'virtio_net', 'xen_netfront']: + return False + + if speed in self._speed_duplex: + if duplex in self._speed_duplex[speed]: + return True + return False + + def check_flow_control(self): + """ Check if the NIC supports flow-control """ + if self.get_driver_name() in ['vmxnet3', 'virtio_net', 'xen_netfront']: + return False + return self._flow_control + + def get_flow_control(self): + if self._flow_control_enabled == None: + raise ValueError('Interface does not support changing '\ + 'flow-control settings!') + return self._flow_control_enabled diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index 14f64a8de..27073b266 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -366,5 +366,4 @@ class BridgeIf(Interface): cmd = f'bridge vlan add dev {interface} vid {native_vlan_id} pvid untagged master' self._cmd(cmd) - # call base class first super().update(config) diff --git a/python/vyos/ifconfig/control.py b/python/vyos/ifconfig/control.py index d41dfef47..7a6b36e7c 100644 --- a/python/vyos/ifconfig/control.py +++ b/python/vyos/ifconfig/control.py @@ -18,11 +18,12 @@ import os from inspect import signature from inspect import _empty -from vyos import debug +from vyos.ifconfig.section import Section from vyos.util import popen from vyos.util import cmd -from vyos.ifconfig.section import Section - +from vyos.util import read_file +from vyos.util import write_file +from vyos import debug class Control(Section): _command_get = {} @@ -116,20 +117,18 @@ class Control(Section): Provide a single primitive w/ error checking for reading from sysfs. """ value = None - with open(filename, 'r') as f: - value = f.read().rstrip('\n') - - self._debug_msg("read '{}' < '{}'".format(value, filename)) + if os.path.exists(filename): + value = read_file(filename) + self._debug_msg("read '{}' < '{}'".format(value, filename)) return value def _write_sysfs(self, filename, value): """ Provide a single primitive w/ error checking for writing to sysfs. """ - self._debug_msg("write '{}' > '{}'".format(value, filename)) if os.path.isfile(filename): - with open(filename, 'w') as f: - f.write(str(value)) + write_file(filename, str(value)) + self._debug_msg("write '{}' > '{}'".format(value, filename)) return True return False diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 07b31a12a..2e59a7afc 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -16,9 +16,11 @@ import os import re +from vyos.ethtool import Ethtool from vyos.ifconfig.interface import Interface from vyos.util import run from vyos.util import dict_search +from vyos.util import read_file from vyos.validate import assert_list @Interface.register @@ -41,39 +43,29 @@ class EthernetIf(Interface): @staticmethod def feature(ifname, option, value): - run(f'ethtool -K {ifname} {option} {value}','ifconfig') + run(f'ethtool --features {ifname} {option} {value}') return False _command_set = {**Interface._command_set, **{ 'gro': { 'validate': lambda v: assert_list(v, ['on', 'off']), 'possible': lambda i, v: EthernetIf.feature(i, 'gro', v), - # 'shellcmd': 'ethtool -K {ifname} gro {value}', }, 'gso': { 'validate': lambda v: assert_list(v, ['on', 'off']), 'possible': lambda i, v: EthernetIf.feature(i, 'gso', v), - # 'shellcmd': 'ethtool -K {ifname} gso {value}', }, 'lro': { 'validate': lambda v: assert_list(v, ['on', 'off']), 'possible': lambda i, v: EthernetIf.feature(i, 'lro', v), - # 'shellcmd': 'ethtool -K {ifname} lro {value}', }, 'sg': { 'validate': lambda v: assert_list(v, ['on', 'off']), 'possible': lambda i, v: EthernetIf.feature(i, 'sg', v), - # 'shellcmd': 'ethtool -K {ifname} sg {value}', }, 'tso': { 'validate': lambda v: assert_list(v, ['on', 'off']), 'possible': lambda i, v: EthernetIf.feature(i, 'tso', v), - # 'shellcmd': 'ethtool -K {ifname} tso {value}', - }, - 'ufo': { - 'validate': lambda v: assert_list(v, ['on', 'off']), - 'possible': lambda i, v: EthernetIf.feature(i, 'ufo', v), - # 'shellcmd': 'ethtool -K {ifname} ufo {value}', }, }} @@ -84,24 +76,9 @@ class EthernetIf(Interface): }, }} - def get_driver_name(self): - """ - Return the driver name used by NIC. Some NICs don't support all - features e.g. changing link-speed, duplex - - Example: - >>> from vyos.ifconfig import EthernetIf - >>> i = EthernetIf('eth0') - >>> i.get_driver_name() - 'vmxnet3' - """ - ifname = self.config['ifname'] - sysfs_file = f'/sys/class/net/{ifname}/device/driver/module' - if os.path.exists(sysfs_file): - link = os.readlink(sysfs_file) - return os.path.basename(link) - else: - return None + def __init__(self, ifname, **kargs): + super().__init__(ifname, **kargs) + self.ethtool = Ethtool(ifname) def set_flow_control(self, enable): """ @@ -119,44 +96,20 @@ class EthernetIf(Interface): if enable not in ['on', 'off']: raise ValueError("Value out of range") - driver_name = self.get_driver_name() - if driver_name in ['vmxnet3', 'virtio_net', 'xen_netfront']: - self._debug_msg(f'{driver_name} driver does not support changing '\ - 'flow control settings!') - return - - # Get current flow control settings: - cmd = f'ethtool --show-pause {ifname}' - output, code = self._popen(cmd) - if code == 76: - # the interface does not support it - return '' - if code: - # never fail here as it prevent vyos to boot - print(f'unexpected return code {code} from {cmd}') - return '' - - # The above command returns - with tabs: - # - # Pause parameters for eth0: - # Autonegotiate: on - # RX: off - # TX: off - if re.search("Autonegotiate:\ton", output): - if enable == "on": - # flowcontrol is already enabled - no need to re-enable it again - # this will prevent the interface from flapping as applying the - # flow-control settings will take the interface down and bring - # it back up every time. - return '' - - # Assemble command executed on system. Unfortunately there is no way - # to change this setting via sysfs - cmd = f'ethtool --pause {ifname} autoneg {enable} tx {enable} rx {enable}' - output, code = self._popen(cmd) - if code: - print(f'could not set flowcontrol for {ifname}') - return output + if not self.ethtool.check_flow_control(): + self._debug_msg(f'NIC driver does not support changing flow control settings!') + return False + + current = self.ethtool.get_flow_control() + if current != enable: + # Assemble command executed on system. Unfortunately there is no way + # to change this setting via sysfs + cmd = f'ethtool --pause {ifname} autoneg {enable} tx {enable} rx {enable}' + output, code = self._popen(cmd) + if code: + print(f'Could not set flowcontrol for {ifname}') + return output + return None def set_speed_duplex(self, speed, duplex): """ @@ -178,40 +131,28 @@ class EthernetIf(Interface): if duplex not in ['auto', 'full', 'half']: raise ValueError("Value out of range (duplex)") - driver_name = self.get_driver_name() - if driver_name in ['vmxnet3', 'virtio_net', 'xen_netfront']: - self._debug_msg(f'{driver_name} driver does not support changing '\ - 'speed/duplex settings!') + if not self.ethtool.check_speed_duplex(speed, duplex): + self._debug_msg(f'NIC driver does not support changing speed/duplex settings!') return # Get current speed and duplex settings: ifname = self.config['ifname'] - cmd = f'ethtool {ifname}' - tmp = self._cmd(cmd) - - if re.search("\tAuto-negotiation: on", tmp): + if self.ethtool.get_auto_negotiation(): if speed == 'auto' and duplex == 'auto': # bail out early as nothing is to change return else: - # read in current speed and duplex settings - cur_speed = 0 - cur_duplex = '' - for line in tmp.splitlines(): - if line.lstrip().startswith("Speed:"): - non_decimal = re.compile(r'[^\d.]+') - cur_speed = non_decimal.sub('', line) - continue - - if line.lstrip().startswith("Duplex:"): - cur_duplex = line.split()[-1].lower() - break - + # XXX: read in current speed and duplex settings + # There are some "nice" NICs like AX88179 which do not support + # reading the speed thus we simply fallback to the supplied speed + # to not cause any change here and raise an exception. + cur_speed = read_file(f'/sys/class/net/{ifname}/speed', speed) + cur_duplex = read_file(f'/sys/class/net/{ifname}/duplex', duplex) if (cur_speed == speed) and (cur_duplex == duplex): # bail out early as nothing is to change return - cmd = f'ethtool -s {ifname}' + cmd = f'ethtool --change {ifname}' if speed == 'auto' or duplex == 'auto': cmd += ' autoneg on' else: @@ -228,8 +169,15 @@ class EthernetIf(Interface): >>> i.set_gro(True) """ if not isinstance(state, bool): - raise ValueError("Value out of range") - return self.set_interface('gro', 'on' if state else 'off') + raise ValueError('Value out of range') + + enabled, fixed = self.ethtool.get_generic_receive_offload() + if enabled != state: + if not fixed: + return self.set_interface('gro', 'on' if state else 'off') + else: + print('Adapter does not support changing generic-receive-offload settings!') + return False def set_gso(self, state): """ @@ -240,8 +188,15 @@ class EthernetIf(Interface): >>> i.set_gso(True) """ if not isinstance(state, bool): - raise ValueError("Value out of range") - return self.set_interface('gso', 'on' if state else 'off') + raise ValueError('Value out of range') + + enabled, fixed = self.ethtool.get_generic_segmentation_offload() + if enabled != state: + if not fixed: + return self.set_interface('gso', 'on' if state else 'off') + else: + print('Adapter does not support changing generic-segmentation-offload settings!') + return False def set_lro(self, state): """ @@ -252,12 +207,19 @@ class EthernetIf(Interface): >>> i.set_lro(True) """ if not isinstance(state, bool): - raise ValueError("Value out of range") - return self.set_interface('lro', 'on' if state else 'off') + raise ValueError('Value out of range') + + enabled, fixed = self.ethtool.get_large_receive_offload() + if enabled != state: + if not fixed: + return self.set_interface('gro', 'on' if state else 'off') + else: + print('Adapter does not support changing large-receive-offload settings!') + return False def set_rps(self, state): if not isinstance(state, bool): - raise ValueError("Value out of range") + raise ValueError('Value out of range') rps_cpus = '0' if state: @@ -282,8 +244,15 @@ class EthernetIf(Interface): >>> i.set_sg(True) """ if not isinstance(state, bool): - raise ValueError("Value out of range") - return self.set_interface('sg', 'on' if state else 'off') + raise ValueError('Value out of range') + + enabled, fixed = self.ethtool.get_scatter_gather() + if enabled != state: + if not fixed: + return self.set_interface('gro', 'on' if state else 'off') + else: + print('Adapter does not support changing scatter-gather settings!') + return False def set_tso(self, state): """ @@ -295,40 +264,38 @@ class EthernetIf(Interface): >>> i.set_tso(False) """ if not isinstance(state, bool): - raise ValueError("Value out of range") - return self.set_interface('tso', 'on' if state else 'off') - - def set_ufo(self, state): - """ - Enable UDP fragmentation offloading. State can be either True or False. - - Example: - >>> from vyos.ifconfig import EthernetIf - >>> i = EthernetIf('eth0') - >>> i.set_udp_offload(True) - """ - if not isinstance(state, bool): - raise ValueError("Value out of range") - return self.set_interface('ufo', 'on' if state else 'off') + raise ValueError('Value out of range') + + enabled, fixed = self.ethtool.get_tcp_segmentation_offload() + if enabled != state: + if not fixed: + return self.set_interface('gro', 'on' if state else 'off') + else: + print('Adapter does not support changing tcp-segmentation-offload settings!') + return False - def set_ring_buffer(self, b_type, b_size): + def set_ring_buffer(self, rx_tx, size): """ Example: >>> from vyos.ifconfig import EthernetIf >>> i = EthernetIf('eth0') >>> i.set_ring_buffer('rx', '4096') """ + current_size = self.ethtool.get_ring_buffer(rx_tx) + if current_size == size: + # bail out early if nothing is about to change + return None + ifname = self.config['ifname'] - cmd = f'ethtool -G {ifname} {b_type} {b_size}' + cmd = f'ethtool --set-ring {ifname} {rx_tx} {size}' output, code = self._popen(cmd) # ethtool error codes: # 80 - value already setted # 81 - does not possible to set value if code and code != 80: - print(f'could not set "{b_type}" ring-buffer for {ifname}') + print(f'could not set "{rx_tx}" ring-buffer for {ifname}') return output - def update(self, config): """ General helper function which works on a dictionary retrived by get_config_dict(). It's main intention is to consolidate the scattered @@ -357,9 +324,6 @@ class EthernetIf(Interface): # TSO (TCP segmentation offloading) self.set_tso(dict_search('offload.tso', config) != None) - # UDP fragmentation offloading - self.set_ufo(dict_search('offload.ufo', config) != None) - # Set physical interface speed and duplex if {'speed', 'duplex'} <= set(config): speed = config.get('speed') @@ -368,8 +332,8 @@ class EthernetIf(Interface): # Set interface ring buffer if 'ring_buffer' in config: - for b_type in config['ring_buffer']: - self.set_ring_buffer(b_type, config['ring_buffer'][b_type]) + for rx_tx, size in config['ring_buffer'].items(): + self.set_ring_buffer(rx_tx, size) # call base class first super().update(config) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index a1928ba51..e6dbd861b 100755 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -37,7 +37,9 @@ from vyos.util import mac2eui64 from vyos.util import dict_search from vyos.util import read_file from vyos.util import get_interface_config +from vyos.util import is_systemd_service_active from vyos.template import is_ipv4 +from vyos.template import is_ipv6 from vyos.validate import is_intf_addr_assigned from vyos.validate import is_ipv6_link_local from vyos.validate import assert_boolean @@ -52,6 +54,9 @@ from vyos.ifconfig.vrrp import VRRP from vyos.ifconfig.operational import Operational from vyos.ifconfig import Section +from netaddr import EUI +from netaddr import mac_unix_expanded + class Interface(Control): # This is the class which will be used to create # self.operational, it allows subclasses, such as @@ -103,6 +108,10 @@ class Interface(Control): 'shellcmd': 'ip -json -detail link list dev {ifname}', 'format': lambda j: jmespath.search('[*].operstate | [0]', json.loads(j)), }, + 'vrf': { + 'shellcmd': 'ip -json -detail link list dev {ifname}', + 'format': lambda j: jmespath.search('[*].master | [0]', json.loads(j)), + }, } _command_set = { @@ -134,7 +143,6 @@ class Interface(Control): _sysfs_set = { 'arp_cache_tmo': { - 'convert': lambda tmo: (int(tmo) * 1000), 'location': '/proc/sys/net/ipv4/neigh/{ifname}/base_reachable_time_ms', }, 'arp_filter': { @@ -204,6 +212,51 @@ class Interface(Control): }, } + _sysfs_get = { + 'arp_cache_tmo': { + 'location': '/proc/sys/net/ipv4/neigh/{ifname}/base_reachable_time_ms', + }, + 'arp_filter': { + 'location': '/proc/sys/net/ipv4/conf/{ifname}/arp_filter', + }, + 'arp_accept': { + 'location': '/proc/sys/net/ipv4/conf/{ifname}/arp_accept', + }, + 'arp_announce': { + 'location': '/proc/sys/net/ipv4/conf/{ifname}/arp_announce', + }, + 'arp_ignore': { + 'location': '/proc/sys/net/ipv4/conf/{ifname}/arp_ignore', + }, + 'ipv4_forwarding': { + 'location': '/proc/sys/net/ipv4/conf/{ifname}/forwarding', + }, + 'rp_filter': { + 'location': '/proc/sys/net/ipv4/conf/{ifname}/rp_filter', + }, + 'ipv6_accept_ra': { + 'location': '/proc/sys/net/ipv6/conf/{ifname}/accept_ra', + }, + 'ipv6_autoconf': { + 'location': '/proc/sys/net/ipv6/conf/{ifname}/autoconf', + }, + 'ipv6_forwarding': { + 'location': '/proc/sys/net/ipv6/conf/{ifname}/forwarding', + }, + 'ipv6_dad_transmits': { + 'location': '/proc/sys/net/ipv6/conf/{ifname}/dad_transmits', + }, + 'proxy_arp': { + 'location': '/proc/sys/net/ipv4/conf/{ifname}/proxy_arp', + }, + 'proxy_arp_pvlan': { + 'location': '/proc/sys/net/ipv4/conf/{ifname}/proxy_arp_pvlan', + }, + 'link_detect': { + 'location': '/proc/sys/net/ipv4/conf/{ifname}/link_filter', + }, + } + @classmethod def exists(cls, ifname): return os.path.exists(f'/sys/class/net/{ifname}') @@ -322,9 +375,7 @@ class Interface(Control): 'info_data', {}).get('table') # Add map element with interface and zone ID if vrf_table_id: - self._cmd( - f'nft add element inet vrf_zones ct_iface_map {{ "{self.ifname}" : {vrf_table_id} }}' - ) + self._cmd(f'nft add element inet vrf_zones ct_iface_map {{ "{self.ifname}" : {vrf_table_id} }}') else: nft_del_element = f'delete element inet vrf_zones ct_iface_map {{ "{self.ifname}" }}' # Check if deleting is possible first to avoid raising errors @@ -376,6 +427,9 @@ class Interface(Control): >>> Interface('eth0').get_mtu() '1400' """ + tmp = self.get_interface('mtu') + if str(tmp) == mtu: + return None return self.set_interface('mtu', mtu) def get_mac(self): @@ -389,6 +443,47 @@ class Interface(Control): """ return self.get_interface('mac') + def get_mac_synthetic(self): + """ + Get a synthetic MAC address. This is a common method which can be called + from derived classes to overwrite the get_mac() call in a generic way. + + NOTE: Tunnel interfaces have no "MAC" address by default. The content + of the 'address' file in /sys/class/net/device contains the + local-ip thus we generate a random MAC address instead + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').get_mac() + '00:50:ab:cd:ef:00' + """ + from hashlib import sha256 + + # Get processor ID number + cpu_id = self._cmd('sudo dmidecode -t 4 | grep ID | head -n1 | sed "s/.*ID://;s/ //g"') + # Get system eth0 base MAC address - every system has eth0 + eth0_mac = Interface('eth0').get_mac() + + sha = sha256() + # Calculate SHA256 sum based on the CPU ID number, eth0 mac address and + # this interface identifier - this is as predictable as an interface + # MAC address and thus can be used in the same way + sha.update(cpu_id.encode()) + sha.update(eth0_mac.encode()) + sha.update(self.ifname.encode()) + # take the most significant 48 bits from the SHA256 string + tmp = sha.hexdigest()[:12] + # Convert pseudo random string into EUI format which now represents a + # MAC address + tmp = EUI(tmp).value + # set locally administered bit in MAC address + tmp |= 0xf20000000000 + # convert integer to "real" MAC address representation + mac = EUI(hex(tmp).split('x')[-1]) + # change dialect to use : as delimiter instead of - + mac.dialect = mac_unix_expanded + return str(mac) + def set_mac(self, mac): """ Set interface MAC (Media Access Contrl) address to given value. @@ -413,7 +508,7 @@ class Interface(Control): if prev_state == 'up': self.set_admin_state('up') - def set_vrf(self, vrf=''): + def set_vrf(self, vrf): """ Add/Remove interface from given VRF instance. @@ -422,6 +517,11 @@ class Interface(Control): >>> Interface('eth0').set_vrf('foo') >>> Interface('eth0').set_vrf() """ + + tmp = self.get_interface('vrf') + if tmp == vrf: + return None + self.set_interface('vrf', vrf) self._set_vrf_ct_zone(vrf) @@ -434,8 +534,68 @@ class Interface(Control): >>> from vyos.ifconfig import Interface >>> Interface('eth0').set_arp_cache_tmo(40) """ + tmo = str(int(tmo) * 1000) + tmp = self.get_interface('arp_cache_tmo') + if tmp == tmo: + return None return self.set_interface('arp_cache_tmo', tmo) + def set_tcp_ipv4_mss(self, mss): + """ + Set IPv4 TCP MSS value advertised when TCP SYN packets leave this + interface. Value is in bytes. + + A value of 0 will disable the MSS adjustment + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').set_tcp_ipv4_mss(1340) + """ + iptables_bin = 'iptables' + base_options = f'-A FORWARD -o {self.ifname} -p tcp -m tcp --tcp-flags SYN,RST SYN' + out = self._cmd(f'{iptables_bin}-save -t mangle') + for line in out.splitlines(): + if line.startswith(base_options): + # remove OLD MSS mangling configuration + line = line.replace('-A FORWARD', '-D FORWARD') + self._cmd(f'{iptables_bin} -t mangle {line}') + + cmd_mss = f'{iptables_bin} -t mangle {base_options} --jump TCPMSS' + if mss == 'clamp-mss-to-pmtu': + self._cmd(f'{cmd_mss} --clamp-mss-to-pmtu') + elif int(mss) > 0: + # probably add option to clamp only if bigger: + low_mss = str(int(mss) + 1) + self._cmd(f'{cmd_mss} -m tcpmss --mss {low_mss}:65535 --set-mss {mss}') + + def set_tcp_ipv6_mss(self, mss): + """ + Set IPv6 TCP MSS value advertised when TCP SYN packets leave this + interface. Value is in bytes. + + A value of 0 will disable the MSS adjustment + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').set_tcp_mss(1320) + """ + iptables_bin = 'ip6tables' + base_options = f'-A FORWARD -o {self.ifname} -p tcp -m tcp --tcp-flags SYN,RST SYN' + out = self._cmd(f'{iptables_bin}-save -t mangle') + for line in out.splitlines(): + if line.startswith(base_options): + # remove OLD MSS mangling configuration + line = line.replace('-A FORWARD', '-D FORWARD') + self._cmd(f'{iptables_bin} -t mangle {line}') + + cmd_mss = f'{iptables_bin} -t mangle {base_options} --jump TCPMSS' + if mss == 'clamp-mss-to-pmtu': + self._cmd(f'{cmd_mss} --clamp-mss-to-pmtu') + elif int(mss) > 0: + # probably add option to clamp only if bigger: + low_mss = str(int(mss) + 1) + self._cmd(f'{cmd_mss} -m tcpmss --mss {low_mss}:65535 --set-mss {mss}') + def set_arp_filter(self, arp_filter): """ Filter ARP requests @@ -454,6 +614,9 @@ class Interface(Control): particular interfaces. Only for more complex setups like load- balancing, does this behaviour cause problems. """ + tmp = self.get_interface('arp_filter') + if tmp == arp_filter: + return None return self.set_interface('arp_filter', arp_filter) def set_arp_accept(self, arp_accept): @@ -470,6 +633,9 @@ class Interface(Control): gratuitous arp frame, the arp table will be updated regardless if this setting is on or off. """ + tmp = self.get_interface('arp_accept') + if tmp == arp_accept: + return None return self.set_interface('arp_accept', arp_accept) def set_arp_announce(self, arp_announce): @@ -491,6 +657,9 @@ class Interface(Control): receiving answer from the resolved target while decreasing the level announces more valid sender's information. """ + tmp = self.get_interface('arp_announce') + if tmp == arp_announce: + return None return self.set_interface('arp_announce', arp_announce) def set_arp_ignore(self, arp_ignore): @@ -503,12 +672,16 @@ class Interface(Control): 1 - reply only if the target IP address is local address configured on the incoming interface """ + tmp = self.get_interface('arp_ignore') + if tmp == arp_ignore: + return None return self.set_interface('arp_ignore', arp_ignore) def set_ipv4_forwarding(self, forwarding): - """ - Configure IPv4 forwarding. - """ + """ Configure IPv4 forwarding. """ + tmp = self.get_interface('ipv4_forwarding') + if tmp == forwarding: + return None return self.set_interface('ipv4_forwarding', forwarding) def set_ipv4_source_validation(self, value): @@ -537,6 +710,9 @@ class Interface(Control): print(f'WARNING: Global source-validation is set to "{global_setting}\n"' \ 'this overrides per interface setting!') + tmp = self.get_interface('rp_filter') + if int(tmp) == value: + return None return self.set_interface('rp_filter', value) def set_ipv6_accept_ra(self, accept_ra): @@ -552,6 +728,9 @@ class Interface(Control): 2 - Overrule forwarding behaviour. Accept Router Advertisements even if forwarding is enabled. """ + tmp = self.get_interface('ipv6_accept_ra') + if tmp == accept_ra: + return None return self.set_interface('ipv6_accept_ra', accept_ra) def set_ipv6_autoconf(self, autoconf): @@ -559,6 +738,9 @@ class Interface(Control): Autoconfigure addresses using Prefix Information in Router Advertisements. """ + tmp = self.get_interface('ipv6_autoconf') + if tmp == autoconf: + return None return self.set_interface('ipv6_autoconf', autoconf) def add_ipv6_eui64_address(self, prefix): @@ -582,9 +764,10 @@ class Interface(Control): Delete the address based on the interface's MAC-based EUI64 combined with the prefix address. """ - eui64 = mac2eui64(self.get_mac(), prefix) - prefixlen = prefix.split('/')[1] - self.del_addr(f'{eui64}/{prefixlen}') + if is_ipv6(prefix): + eui64 = mac2eui64(self.get_mac(), prefix) + prefixlen = prefix.split('/')[1] + self.del_addr(f'{eui64}/{prefixlen}') def set_ipv6_forwarding(self, forwarding): """ @@ -611,6 +794,9 @@ class Interface(Control): 3. Router Advertisements are ignored unless accept_ra is 2. 4. Redirects are ignored. """ + tmp = self.get_interface('ipv6_forwarding') + if tmp == forwarding: + return None return self.set_interface('ipv6_forwarding', forwarding) def set_ipv6_dad_messages(self, dad): @@ -618,6 +804,9 @@ class Interface(Control): The amount of Duplicate Address Detection probes to send. Default: 1 """ + tmp = self.get_interface('ipv6_dad_transmits') + if tmp == dad: + return None return self.set_interface('ipv6_dad_transmits', dad) def set_link_detect(self, link_filter): @@ -640,6 +829,9 @@ class Interface(Control): >>> from vyos.ifconfig import Interface >>> Interface('eth0').set_link_detect(1) """ + tmp = self.get_interface('link_detect') + if tmp == link_filter: + return None return self.set_interface('link_detect', link_filter) def get_alias(self): @@ -664,6 +856,9 @@ class Interface(Control): >>> Interface('eth0').set_ifalias('') """ + tmp = self.get_interface('alias') + if tmp == ifalias: + return None self.set_interface('alias', ifalias) def get_admin_state(self): @@ -739,6 +934,9 @@ class Interface(Control): >>> from vyos.ifconfig import Interface >>> Interface('eth0').set_proxy_arp(1) """ + tmp = self.get_interface('proxy_arp') + if tmp == enable: + return None self.set_interface('proxy_arp', enable) def set_proxy_arp_pvlan(self, enable): @@ -765,6 +963,9 @@ class Interface(Control): >>> from vyos.ifconfig import Interface >>> Interface('eth0').set_proxy_arp_pvlan(1) """ + tmp = self.get_interface('proxy_arp_pvlan') + if tmp == enable: + return None self.set_interface('proxy_arp_pvlan', enable) def get_addr_v4(self): @@ -899,6 +1100,8 @@ class Interface(Control): >>> j.get_addr() ['2001:db8::ffff/64'] """ + if not addr: + raise ValueError() # remove from interface if addr == 'dhcp': @@ -1005,7 +1208,9 @@ class Interface(Control): lease_file = f'{config_base}_{ifname}.leases' # Stop client with old config files to get the right IF_METRIC. - self._cmd(f'systemctl stop dhclient@{ifname}.service') + systemd_service = f'dhclient@{ifname}.service' + if is_systemd_service_active(systemd_service): + self._cmd(f'systemctl stop {systemd_service}') if enable and 'disable' not in self._config: if dict_search('dhcp_options.host_name', self._config) == None: @@ -1025,7 +1230,7 @@ class Interface(Control): # 'up' check is mandatory b/c even if the interface is A/D, as soon as # the DHCP client is started the interface will be placed in u/u state. # This is not what we intended to do when disabling an interface. - return self._cmd(f'systemctl start dhclient@{ifname}.service') + return self._cmd(f'systemctl restart {systemd_service}') else: # cleanup old config files for file in [config_file, options_file, pid_file, lease_file]: @@ -1042,17 +1247,18 @@ class Interface(Control): ifname = self.ifname config_file = f'/run/dhcp6c/dhcp6c.{ifname}.conf' + systemd_service = f'dhcp6c@{ifname}.service' if enable and 'disable' not in self._config: render(config_file, 'dhcp-client/ipv6.tmpl', self._config) - # We must ignore any return codes. This is required to enable DHCPv6-PD - # for interfaces which are yet not up and running. - return self._popen(f'systemctl restart dhcp6c@{ifname}.service') + # We must ignore any return codes. This is required to enable + # DHCPv6-PD for interfaces which are yet not up and running. + return self._popen(f'systemctl restart {systemd_service}') else: - self._popen(f'systemctl stop dhcp6c@{ifname}.service') - + if is_systemd_service_active(systemd_service): + self._cmd(f'systemctl stop {systemd_service}') if os.path.isfile(config_file): os.remove(config_file) @@ -1069,12 +1275,14 @@ class Interface(Control): source_if = next(iter(self._config['is_mirror_intf'])) config = self._config['is_mirror_intf'][source_if].get('mirror', None) - # Please do not clear the 'set $? = 0 '. It's meant to force a return of 0 - # Remove existing mirroring rules - delete_tc_cmd = f'tc qdisc del dev {source_if} handle ffff: ingress 2> /dev/null;' - delete_tc_cmd += f'tc qdisc del dev {source_if} handle 1: root prio 2> /dev/null;' - delete_tc_cmd += 'set $?=0' - self._popen(delete_tc_cmd) + # Check configuration stored by old perl code before delete T3782 + if not 'redirect' in self._config: + # Please do not clear the 'set $? = 0 '. It's meant to force a return of 0 + # Remove existing mirroring rules + delete_tc_cmd = f'tc qdisc del dev {source_if} handle ffff: ingress 2> /dev/null;' + delete_tc_cmd += f'tc qdisc del dev {source_if} handle 1: root prio 2> /dev/null;' + delete_tc_cmd += 'set $?=0' + self._popen(delete_tc_cmd) # Bail out early if nothing needs to be configured if not config: @@ -1097,7 +1305,6 @@ class Interface(Control): mirror_cmd += f'tc filter add dev {source_if} parent {parent} protocol all prio 10 u32 match u32 0 0 flowid 1:1 action mirred egress mirror dev {mirror_if}' self._popen(mirror_cmd) - def set_xdp(self, state): """ Enable Kernel XDP support. State can be either True or False. @@ -1174,16 +1381,16 @@ class Interface(Control): # determine IP addresses which are assigned to the interface and build a # list of addresses which are no longer in the dict so they can be removed - cur_addr = self.get_addr() - for addr in list_diff(cur_addr, new_addr): - # we will delete all interface specific IP addresses if they are not - # explicitly configured on the CLI - if is_ipv6_link_local(addr): - eui64 = mac2eui64(self.get_mac(), 'fe80::/64') - if addr != f'{eui64}/64': + if 'address_old' in config: + for addr in list_diff(config['address_old'], new_addr): + # we will delete all interface specific IP addresses if they are not + # explicitly configured on the CLI + if is_ipv6_link_local(addr): + eui64 = mac2eui64(self.get_mac(), 'fe80::/64') + if addr != f'{eui64}/64': + self.del_addr(addr) + else: self.del_addr(addr) - else: - self.del_addr(addr) for addr in new_addr: self.add_addr(addr) @@ -1202,6 +1409,16 @@ class Interface(Control): # checked before self.set_vrf(config.get('vrf', '')) + # Configure MSS value for IPv4 TCP connections + tmp = dict_search('ip.adjust_mss', config) + value = tmp if (tmp != None) else '0' + self.set_tcp_ipv4_mss(value) + + # Configure MSS value for IPv6 TCP connections + tmp = dict_search('ipv6.adjust_mss', config) + value = tmp if (tmp != None) else '0' + self.set_tcp_ipv6_mss(value) + # Configure ARP cache timeout in milliseconds - has default value tmp = dict_search('ip.arp_cache_timeout', config) value = tmp if (tmp != None) else '30' @@ -1274,16 +1491,11 @@ class Interface(Control): self.set_mtu(config.get('mtu')) # Delete old IPv6 EUI64 addresses before changing MAC - tmp = dict_search('ipv6.address.eui64_old', config) - if tmp: - for addr in tmp: - self.del_ipv6_eui64_address(addr) + for addr in (dict_search('ipv6.address.eui64_old', config) or []): + self.del_ipv6_eui64_address(addr) # Manage IPv6 link-local addresses - tmp = dict_search('ipv6.address.no_default_link_local', config) - # we must check explicitly for None type as if the key is set we will - # get an empty dict (<class 'dict'>) - if isinstance(tmp, dict): + if dict_search('ipv6.address.no_default_link_local', config) != None: self.del_ipv6_eui64_address('fe80::/64') else: self.add_ipv6_eui64_address('fe80::/64') diff --git a/python/vyos/ifconfig/pppoe.py b/python/vyos/ifconfig/pppoe.py index 65575cf99..1d13264bf 100644 --- a/python/vyos/ifconfig/pppoe.py +++ b/python/vyos/ifconfig/pppoe.py @@ -1,4 +1,4 @@ -# Copyright 2020 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2020-2021 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 @@ -14,12 +14,11 @@ # License along with this library. If not, see <http://www.gnu.org/licenses/>. from vyos.ifconfig.interface import Interface +from vyos.util import get_interface_config @Interface.register class PPPoEIf(Interface): - default = { - 'type': 'pppoe', - } + iftype = 'pppoe' definition = { **Interface.definition, **{ @@ -28,7 +27,31 @@ class PPPoEIf(Interface): }, } - # stub this interface is created in the configure script + def _remove_routes(self, vrf=''): + # Always delete default routes when interface is removed + 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"') + + def remove(self): + """ + Remove interface from operating system. Removing the interface + deconfigures all assigned IP addresses and clear possible DHCP(v6) + client processes. + Example: + >>> from vyos.ifconfig import Interface + >>> i = Interface('pppoe0') + >>> i.remove() + """ + + tmp = get_interface_config(self.ifname) + vrf = '' + if 'master' in tmp: + self._remove_routes(tmp['master']) + + # remove bond master which places members in disabled state + super().remove() def _create(self): # we can not create this interface as it is managed outside @@ -37,3 +60,92 @@ class PPPoEIf(Interface): def _delete(self): # we can not create this interface as it is managed outside pass + + def del_addr(self, addr): + # we can not create this interface as it is managed outside + pass + + def get_mac(self): + """ Get a synthetic MAC address. """ + return self.get_mac_synthetic() + + def update(self, config): + """ General helper function which works on a dictionary retrived by + get_config_dict(). It's main intention is to consolidate the scattered + interface setup code and provide a single point of entry when workin + on any interface. """ + + # Cache the configuration - it will be reused inside e.g. DHCP handler + # XXX: maybe pass the option via __init__ in the future and rename this + # method to apply()? + # + # We need to copy this from super().update() as we utilize self.set_dhcpv6() + # before this is done by the base class. + 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) + + # DHCPv6 PD handling is a bit different on PPPoE interfaces, as we do + # not require an 'address dhcpv6' CLI option as with other interfaces + if 'dhcpv6_options' in config and 'pd' in config['dhcpv6_options']: + self.set_dhcpv6(True) + else: + self.set_dhcpv6(False) + + 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 + 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"') diff --git a/python/vyos/ifconfig/section.py b/python/vyos/ifconfig/section.py index 173a90bb4..0e4447b9e 100644 --- a/python/vyos/ifconfig/section.py +++ b/python/vyos/ifconfig/section.py @@ -46,7 +46,7 @@ class Section: return klass @classmethod - def _basename (cls, name, vlan): + def _basename(cls, name, vlan, vrrp): """ remove the number at the end of interface name name: name of the interface @@ -56,16 +56,18 @@ class Section: name = name.rstrip('.') if vlan: name = name.rstrip('0123456789.') + if vrrp: + name = name.rstrip('0123456789v') return name @classmethod - def section(cls, name, vlan=True): + def section(cls, name, vlan=True, vrrp=True): """ return the name of a section an interface should be under name: name of the interface (eth0, dum1, ...) vlan: should we try try to remove the VLAN from the number """ - name = cls._basename(name, vlan) + name = cls._basename(name, vlan, vrrp) if name in cls._prefixes: return cls._prefixes[name].definition['section'] @@ -79,8 +81,8 @@ class Section: return list(set([cls._prefixes[_].definition['section'] for _ in cls._prefixes])) @classmethod - def klass(cls, name, vlan=True): - name = cls._basename(name, vlan) + def klass(cls, name, vlan=True, vrrp=True): + name = cls._basename(name, vlan, vrrp) if name in cls._prefixes: return cls._prefixes[name] raise ValueError(f'No type found for interface name: {name}') diff --git a/python/vyos/ifconfig/tunnel.py b/python/vyos/ifconfig/tunnel.py index 64c735824..5258a2cb1 100644 --- a/python/vyos/ifconfig/tunnel.py +++ b/python/vyos/ifconfig/tunnel.py @@ -16,10 +16,6 @@ # https://developers.redhat.com/blog/2019/05/17/an-introduction-to-linux-virtual-interfaces-tunnels/ # https://community.hetzner.com/tutorials/linux-setup-gre-tunnel -from netaddr import EUI -from netaddr import mac_unix_expanded -from random import getrandbits - from vyos.ifconfig.interface import Interface from vyos.util import dict_search from vyos.validate import assert_list @@ -163,28 +159,8 @@ class TunnelIf(Interface): self._cmd(cmd.format(**self.config)) def get_mac(self): - """ - Get current interface MAC (Media Access Contrl) address used. - - NOTE: Tunnel interfaces have no "MAC" address by default. The content - of the 'address' file in /sys/class/net/device contains the - local-ip thus we generate a random MAC address instead - - Example: - >>> from vyos.ifconfig import Interface - >>> Interface('eth0').get_mac() - '00:50:ab:cd:ef:00' - """ - # we choose 40 random bytes for the MAC address, this gives - # us e.g. EUI('00-EA-EE-D6-A3-C8') or EUI('00-41-B9-0D-F2-2A') - tmp = EUI(getrandbits(48)).value - # set locally administered bit in MAC address - tmp |= 0xf20000000000 - # convert integer to "real" MAC address representation - mac = EUI(hex(tmp).split('x')[-1]) - # change dialect to use : as delimiter instead of - - mac.dialect = mac_unix_expanded - return str(mac) + """ Get a synthetic MAC address. """ + return self.get_mac_synthetic() def update(self, config): """ General helper function which works on a dictionary retrived by diff --git a/python/vyos/ifconfig/vrrp.py b/python/vyos/ifconfig/vrrp.py index b522cc1ab..47aaadecd 100644 --- a/python/vyos/ifconfig/vrrp.py +++ b/python/vyos/ifconfig/vrrp.py @@ -22,6 +22,7 @@ from time import sleep from tabulate import tabulate from vyos import util +from vyos.configquery import ConfigTreeQuery class VRRPError(Exception): pass @@ -32,14 +33,13 @@ class VRRPNoData(VRRPError): class VRRP(object): _vrrp_prefix = '00:00:5E:00:01:' location = { - 'pid': '/run/keepalived.pid', - 'fifo': '/run/keepalived_notify_fifo', + 'pid': '/run/keepalived/keepalived.pid', + 'fifo': '/run/keepalived/keepalived_notify_fifo', 'state': '/tmp/keepalived.data', 'stats': '/tmp/keepalived.stats', 'json': '/tmp/keepalived.json', 'daemon': '/etc/default/keepalived', - 'config': '/etc/keepalived/keepalived.conf', - 'vyos': '/run/keepalived_config.dict', + 'config': '/run/keepalived/keepalived.conf', } _signal = { @@ -111,17 +111,20 @@ class VRRP(object): @classmethod def disabled(cls): - if not os.path.exists(cls.location['vyos']): - return [] - disabled = [] - config = json.loads(util.read_file(cls.location['vyos'])) - - # add disabled groups to the list - for group in config['vrrp_groups']: - if group['disable']: - disabled.append( - [group['name'], group['interface'], group['vrid'], 'DISABLED', '']) + base = ['high-availability', 'vrrp'] + conf = ConfigTreeQuery() + if conf.exists(base): + # Read VRRP configuration directly from CLI + vrrp_config_dict = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True) + + # add disabled groups to the list + if 'group' in vrrp_config_dict: + for group, group_config in vrrp_config_dict['group'].items(): + if 'disable' not in group_config: + continue + disabled.append([group, group_config['interface'], group_config['vrid'], 'DISABLED', '']) # return list with disabled instances return disabled diff --git a/python/vyos/ifconfig/vti.py b/python/vyos/ifconfig/vti.py index 470ebbff3..c50cd5ce9 100644 --- a/python/vyos/ifconfig/vti.py +++ b/python/vyos/ifconfig/vti.py @@ -35,8 +35,11 @@ class VTIIf(Interface): mapping = { 'source_interface' : 'dev', } - if_id = self.ifname.lstrip('vti') + # The key defaults to 0 and will match any policies which similarly do + # not have a lookup key configuration - thus we shift the key by one + # to also support a vti0 interface + if_id = str(int(if_id) +1) cmd = f'ip link add {self.ifname} type xfrm if_id {if_id}' for vyos_key, iproute2_key in mapping.items(): # dict_search will return an empty dict "{}" for valueless nodes like diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py index c4cf2fbbf..28b5e2991 100644 --- a/python/vyos/ifconfig/wireguard.py +++ b/python/vyos/ifconfig/wireguard.py @@ -17,9 +17,6 @@ import os import time from datetime import timedelta -from netaddr import EUI -from netaddr import mac_unix_expanded -from random import getrandbits from hurry.filesize import size from hurry.filesize import alternative @@ -159,28 +156,8 @@ class WireGuardIf(Interface): } def get_mac(self): - """ - Get current interface MAC (Media Access Contrl) address used. - - NOTE: Tunnel interfaces have no "MAC" address by default. The content - of the 'address' file in /sys/class/net/device contains the - local-ip thus we generate a random MAC address instead - - Example: - >>> from vyos.ifconfig import Interface - >>> Interface('eth0').get_mac() - '00:50:ab:cd:ef:00' - """ - # we choose 40 random bytes for the MAC address, this gives - # us e.g. EUI('00-EA-EE-D6-A3-C8') or EUI('00-41-B9-0D-F2-2A') - tmp = EUI(getrandbits(48)).value - # set locally administered bit in MAC address - tmp |= 0xf20000000000 - # convert integer to "real" MAC address representation - mac = EUI(hex(tmp).split('x')[-1]) - # change dialect to use : as delimiter instead of - - mac.dialect = mac_unix_expanded - return str(mac) + """ Get a synthetic MAC address. """ + return self.get_mac_synthetic() def update(self, config): """ General helper function which works on a dictionary retrived by diff --git a/python/vyos/migrator.py b/python/vyos/migrator.py index 9a5fdef2f..4574bb6d1 100644 --- a/python/vyos/migrator.py +++ b/python/vyos/migrator.py @@ -15,6 +15,7 @@ import sys import os +import json import subprocess import vyos.version import vyos.defaults @@ -165,6 +166,20 @@ class Migrator(object): versions_string, os_version_string) + def save_json_record(self, component_versions: dict): + """ + Write component versions to a json file + """ + mask = os.umask(0o113) + version_file = vyos.defaults.component_version_json + try: + with open(version_file, 'w') as f: + f.write(json.dumps(component_versions, indent=2, sort_keys=True)) + except OSError: + pass + finally: + os.umask(mask) + def run(self): """ Gather component versions from config file and system. @@ -182,6 +197,9 @@ class Migrator(object): sys_versions = systemversions.get_system_versions() + # save system component versions in json file for easy reference + self.save_json_record(sys_versions) + rev_versions = self.run_migration_scripts(cfg_versions, sys_versions) if rev_versions != cfg_versions: diff --git a/python/vyos/systemversions.py b/python/vyos/systemversions.py index 5c4deca29..9b3f4f413 100644 --- a/python/vyos/systemversions.py +++ b/python/vyos/systemversions.py @@ -16,15 +16,12 @@ import os import re import sys -import json - import vyos.defaults def get_system_versions(): """ - Get component versions from running system: read vyatta directory - structure for versions, then read vyos JSON file. It is a critical - error if either migration directory or JSON file is unreadable. + Get component versions from running system; critical failure if + unable to read migration directory. """ system_versions = {} @@ -39,25 +36,4 @@ def get_system_versions(): pair = info.split('@') system_versions[pair[0]] = int(pair[1]) - version_dict = {} - path = vyos.defaults.version_file - - if os.path.isfile(path): - with open(path, 'r') as f: - try: - version_dict = json.load(f) - except ValueError as err: - print(f"\nValue error in {path}: {err}") - sys.exit(1) - - for k, v in version_dict.items(): - if not isinstance(v, int): - print(f"\nType error in {path}; expecting Dict[str, int]") - sys.exit(1) - existing = system_versions.get(k) - if existing is None: - system_versions[k] = v - elif v > existing: - system_versions[k] = v - return system_versions diff --git a/python/vyos/template.py b/python/vyos/template.py index 08a5712af..d13915766 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -393,8 +393,15 @@ def get_ip(interface): from vyos.ifconfig import Interface return Interface(interface).get_addr() +def get_first_ike_dh_group(ike_group): + if ike_group and 'proposal' in ike_group: + for priority, proposal in ike_group['proposal'].items(): + if 'dh_group' in proposal: + return 'dh-group' + proposal['dh_group'] + return 'dh-group2' # Fallback on dh-group2 + @register_filter('get_esp_ike_cipher') -def get_esp_ike_cipher(group_config): +def get_esp_ike_cipher(group_config, ike_group=None): pfs_lut = { 'dh-group1' : 'modp768', 'dh-group2' : 'modp1024', @@ -406,7 +413,7 @@ def get_esp_ike_cipher(group_config): 'dh-group18' : 'modp8192', 'dh-group19' : 'ecp256', 'dh-group20' : 'ecp384', - 'dh-group21' : 'ecp512', + 'dh-group21' : 'ecp521', 'dh-group22' : 'modp1024s160', 'dh-group23' : 'modp2048s224', 'dh-group24' : 'modp2048s256', @@ -433,7 +440,7 @@ def get_esp_ike_cipher(group_config): elif 'pfs' in group_config and group_config['pfs'] != 'disable': group = group_config['pfs'] if group_config['pfs'] == 'enable': - group = 'dh-group2' + group = get_first_ike_dh_group(ike_group) tmp += '-' + pfs_lut[group] ciphers.append(tmp) diff --git a/python/vyos/util.py b/python/vyos/util.py index 05643a223..849b27d3b 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -562,12 +562,13 @@ def commit_in_progress(): # Since this will be used in scripts that modify the config outside of the CLI # framework, those knowingly have root permissions. # For everything else, we add a safeguard. - from psutil import process_iter, NoSuchProcess + from psutil import process_iter + from psutil import NoSuchProcess + from getpass import getuser from vyos.defaults import commit_lock - idu = cmd('/usr/bin/id -u') - if idu != '0': - raise OSError("This functions needs root permissions to return correct results") + if getuser() != 'root': + raise OSError('This functions needs to be run as root to return correct results!') for proc in process_iter(): try: @@ -691,21 +692,21 @@ def find_device_file(device): return None -def dict_search(path, my_dict): - """ Traverse Python dictionary (my_dict) delimited by dot (.). +def dict_search(path, dict_object): + """ Traverse Python dictionary (dict_object) delimited by dot (.). Return value of key if found, None otherwise. - This is faster implementation then jmespath.search('foo.bar', my_dict)""" - if not isinstance(my_dict, dict) or not path: + This is faster implementation then jmespath.search('foo.bar', dict_object)""" + if not isinstance(dict_object, dict) or not path: return None parts = path.split('.') inside = parts[:-1] if not inside: - if path not in my_dict: + if path not in dict_object: return None - return my_dict[path] - c = my_dict + return dict_object[path] + c = dict_object for p in parts[:-1]: c = c.get(p, {}) return c.get(parts[-1], None) @@ -723,6 +724,23 @@ def dict_search_args(dict_object, *path): dict_object = dict_object[item] return dict_object +def dict_search_recursive(dict_object, key): + """ Traverse a dictionary recurisvely and return the value of the key + we are looking for. + + Thankfully copied from https://stackoverflow.com/a/19871956 + """ + if isinstance(dict_object, list): + for i in dict_object: + for x in dict_search_recursive(i, key): + yield x + elif isinstance(dict_object, dict): + if key in dict_object: + yield dict_object[key] + for j in dict_object.values(): + for x in dict_search_recursive(j, key): + yield x + def get_interface_config(interface): """ Returns the used encapsulation protocol for given interface. If interface does not exist, None is returned. @@ -805,8 +823,49 @@ def make_incremental_progressbar(increment: float): while True: yield +def is_systemd_service_active(service): + """ Test is a specified systemd service is activated. + Returns True if service is active, false otherwise. + Copied from: https://unix.stackexchange.com/a/435317 """ + tmp = cmd(f'systemctl show --value -p ActiveState {service}') + return bool((tmp == 'active')) + def is_systemd_service_running(service): """ Test is a specified systemd service is actually running. - Returns True if service is running, false otherwise. """ - tmp = run(f'systemctl is-active --quiet {service}') - return bool((tmp == 0)) + Returns True if service is running, false otherwise. + Copied from: https://unix.stackexchange.com/a/435317 """ + tmp = cmd(f'systemctl show --value -p SubState {service}') + return bool((tmp == 'running')) + +def check_port_availability(ipaddress, port, protocol): + """ + Check if port is available and not used by any service + Return False if a port is busy or IP address does not exists + Should be used carefully for services that can start listening + dynamically, because IP address may be dynamic too + """ + from socketserver import TCPServer, UDPServer + from ipaddress import ip_address + + # verify arguments + try: + ipaddress = ip_address(ipaddress).compressed + except: + raise ValueError(f'The {ipaddress} is not a valid IPv4 or IPv6 address') + if port not in range(1, 65536): + raise ValueError(f'The port number {port} is not in the 1-65535 range') + if protocol not in ['tcp', 'udp']: + raise ValueError( + f'The protocol {protocol} is not supported. Only tcp and udp are allowed' + ) + + # check port availability + try: + if protocol == 'tcp': + server = TCPServer((ipaddress, port), None, bind_and_activate=True) + if protocol == 'udp': + server = UDPServer((ipaddress, port), None, bind_and_activate=True) + server.server_close() + return True + except: + return False diff --git a/python/vyos/xml/__init__.py b/python/vyos/xml/__init__.py index 0ef0c85ce..e0eacb2d1 100644 --- a/python/vyos/xml/__init__.py +++ b/python/vyos/xml/__init__.py @@ -46,6 +46,8 @@ def is_tag(lpath): def is_leaf(lpath, flat=True): return load_configuration().is_leaf(lpath, flat) +def component_versions(): + return load_configuration().component_versions() def defaults(lpath, flat=False): return load_configuration().defaults(lpath, flat) diff --git a/python/vyos/xml/definition.py b/python/vyos/xml/definition.py index f556c5ced..5e0d5282c 100644 --- a/python/vyos/xml/definition.py +++ b/python/vyos/xml/definition.py @@ -30,6 +30,7 @@ class XML(dict): self[kw.owners] = {} self[kw.default] = {} self[kw.tags] = [] + self[kw.component_version] = {} dict.__init__(self) @@ -248,6 +249,11 @@ class XML(dict): # @lru_cache(maxsize=100) # XXX: need to use cachetool instead - for later + def component_versions(self) -> dict: + sort_component = sorted(self[kw.component_version].items(), + key = lambda kv: kv[0]) + return dict(sort_component) + def defaults(self, lpath, flat): d = self[kw.default] for k in lpath: diff --git a/python/vyos/xml/kw.py b/python/vyos/xml/kw.py index 58d47e751..48226ce96 100644 --- a/python/vyos/xml/kw.py +++ b/python/vyos/xml/kw.py @@ -32,6 +32,7 @@ priorities = '[priorities]' owners = '[owners]' tags = '[tags]' default = '[default]' +component_version = '[component_version]' # nodes diff --git a/python/vyos/xml/load.py b/python/vyos/xml/load.py index 37479c6e1..c3022f3d6 100644 --- a/python/vyos/xml/load.py +++ b/python/vyos/xml/load.py @@ -115,7 +115,12 @@ def _format_nodes(inside, conf, xml): nodetype = 'tagNode' nodename = kw.tagNode elif 'syntaxVersion' in conf.keys(): - conf.pop('syntaxVersion') + sv = conf.pop('syntaxVersion') + if isinstance(sv, list): + for v in sv: + xml[kw.component_version][v['@component']] = v['@version'] + else: + xml[kw.component_version][sv['@component']] = sv['@version'] continue else: _fatal(conf.keys()) @@ -125,14 +130,20 @@ def _format_nodes(inside, conf, xml): for node in nodes: name = node.pop('@name') into = inside + [name] - r[name] = _format_node(into, node, xml) + if name in r: + r[name].update(_format_node(into, node, xml)) + else: + r[name] = _format_node(into, node, xml) r[name][kw.node] = nodename xml[kw.tags].append(' '.join(into)) else: node = nodes name = node.pop('@name') into = inside + [name] - r[name] = _format_node(inside + [name], node, xml) + if name in r: + r[name].update(_format_node(inside + [name], node, xml)) + else: + r[name] = _format_node(inside + [name], node, xml) r[name][kw.node] = nodename xml[kw.tags].append(' '.join(into)) return r diff --git a/scripts/build-command-op-templates b/scripts/build-command-op-templates index c285ee594..d4515b8db 100755 --- a/scripts/build-command-op-templates +++ b/scripts/build-command-op-templates @@ -29,13 +29,10 @@ import functools from lxml import etree as ET # Defaults - validator_dir = "/opt/vyatta/libexec/validators" default_constraint_err_msg = "Invalid value" - ## Get arguments - parser = argparse.ArgumentParser(description='Converts new-style XML interface definitions to old-style command templates') parser.add_argument('--debug', help='Enable debug information output', action='store_true') parser.add_argument('INPUT_FILE', type=str, help="XML interface definition file") @@ -50,11 +47,10 @@ output_dir = args.OUTPUT_DIR debug = args.debug ## Load and validate the inputs - try: xml = ET.parse(input_file) except Exception as e: - print("Failed to load interface definition file {0}".format(input_file)) + print(f"Failed to load interface definition file {input_file}") print(e) sys.exit(1) @@ -64,19 +60,18 @@ try: if not validator.validate(xml): print(validator.error_log) - print("Interface definition file {0} does not match the schema!".format(input_file)) + print(f"Interface definition file {input_file} does not match the schema!") sys.exit(1) except Exception as e: - print("Failed to load the XML schema {0}".format(schema_file)) + print(f"Failed to load the XML schema {schema_file}") print(e) sys.exit(1) if not os.access(output_dir, os.W_OK): - print("The output directory {0} is not writeable".format(output_dir)) + print(f"The output directory {output_dir} is not writeable") sys.exit(1) ## If we got this far, everything must be ok and we can convert the file - def make_path(l): path = functools.reduce(os.path.join, l) if debug: @@ -125,21 +120,14 @@ def get_properties(p): def make_node_def(props, command): # XXX: replace with a template processor if it grows # out of control - node_def = "" if "help" in props: node_def += "help: {0}\n".format(props["help"]) - - if "comp_help" in props: node_def += "allowed: {0}\n".format(props["comp_help"]) - - if command is not None: node_def += "run: {0}\n".format(command.text) - - if debug: print("The contents of the node.def file:\n", node_def) @@ -152,7 +140,6 @@ def process_node(n, tmpl_dir): props_elem = n.find("properties") children = n.find("children") command = n.find("command") - name = n.get("name") node_type = n.tag @@ -160,16 +147,16 @@ def process_node(n, tmpl_dir): my_tmpl_dir.append(name) if debug: - print("Name of the node: {};\n Created directory: ".format(name), end="") + print(f"Name of the node: {name};\n Created directory: ", end="") os.makedirs(make_path(my_tmpl_dir), exist_ok=True) props = get_properties(props_elem) + nodedef_path = os.path.join(make_path(my_tmpl_dir), "node.def") if node_type == "node": if debug: - print("Processing node {}".format(name)) + print(f"Processing node {name}") - nodedef_path = os.path.join(make_path(my_tmpl_dir), "node.def") # Only create the "node.def" file if it exists but is empty, or if it # does not exist at all. if not os.path.exists(nodedef_path) or os.path.getsize(nodedef_path) == 0: @@ -180,19 +167,17 @@ def process_node(n, tmpl_dir): inner_nodes = children.iterfind("*") for inner_n in inner_nodes: process_node(inner_n, my_tmpl_dir) - if node_type == "tagNode": + elif node_type == "tagNode": if debug: - print("Processing tag node {}".format(name)) + print(f"Processing tagNode {name}") os.makedirs(make_path(my_tmpl_dir), exist_ok=True) - nodedef_path = os.path.join(make_path(my_tmpl_dir), "node.def") - if not os.path.exists(nodedef_path): + # Only create the "node.def" file if it exists but is empty, or if it + # does not exist at all. + if not os.path.exists(nodedef_path) or os.path.getsize(nodedef_path) == 0: with open(nodedef_path, "w") as f: f.write('help: {0}\n'.format(props['help'])) - else: - # Something has already generated this file - pass # Create the inner node.tag part my_tmpl_dir.append("node.tag") @@ -201,24 +186,67 @@ def process_node(n, tmpl_dir): print("Created path for the tagNode: {}".format(make_path(my_tmpl_dir)), end="") # Not sure if we want partially defined tag nodes, write the file unconditionally - with open(os.path.join(make_path(my_tmpl_dir), "node.def"), "w") as f: - f.write(make_node_def(props, command)) + nodedef_path = os.path.join(make_path(my_tmpl_dir), "node.def") + # Only create the "node.def" file if it exists but is empty, or if it + # does not exist at all. + if not os.path.exists(nodedef_path) or os.path.getsize(nodedef_path) == 0: + with open(nodedef_path, "w") as f: + f.write(make_node_def(props, command)) if children is not None: inner_nodes = children.iterfind("*") for inner_n in inner_nodes: process_node(inner_n, my_tmpl_dir) - else: + elif node_type == "leafNode": # This is a leaf node if debug: - print("Processing leaf node {}".format(name)) - - with open(os.path.join(make_path(my_tmpl_dir), "node.def"), "w") as f: - f.write(make_node_def(props, command)) + print(f"Processing leaf node {name}") + if not os.path.exists(nodedef_path) or os.path.getsize(nodedef_path) == 0: + with open(nodedef_path, "w") as f: + f.write(make_node_def(props, command)) + else: + print(f"Unknown node_type: {node_type}") + + +def get_node_key(node, attr=None): + """ Return the sorting key of an xml node using tag and attributes """ + if attr is None: + return '%s' % node.tag + ':'.join([node.get(attr) + for attr in sorted(node.attrib)]) + if attr in node.attrib: + return '%s:%s' % (node.tag, node.get(attr)) + return '%s' % node.tag + + +def sort_children(node, attr=None): + """ Sort children along tag and given attribute. if attr is None, sort + along all attributes """ + if not isinstance(node.tag, str): # PYTHON 2: use basestring instead + # not a TAG, it is comment or DATA + # no need to sort + return + # sort child along attr + node[:] = sorted(node, key=lambda child: get_node_key(child, attr)) + # and recurse + for child in node: + sort_children(child, attr) root = xml.getroot() +# process_node() processes the XML tree in a fixed order, "node" before "tagNode" +# before "leafNode". If the generator created a "node.def" file, it can no longer +# be overwritten - else we would have some stale "node.def" files with an empty +# help string (T2555). Without the fixed order this would resulted in a case +# where we get a node and a tagNode with the same name, e.g. "show interfaces +# ethernet" and "show interfaces ethernet eth0" that the node implementation +# was not callable from the CLI, rendering this command useless (T3807). +# +# This can be fixed by forcing the "node", "tagNode", "leafNode" order by sorting +# the input XML file automatically (sorting from https://stackoverflow.com/a/46128043) +# thus adding no additional overhead to the user. +sort_children(root, 'name') + nodes = root.iterfind("*") for n in nodes: process_node(n, [output_dir]) diff --git a/scripts/build-component-versions b/scripts/build-component-versions deleted file mode 100755 index 5362dbdd4..000000000 --- a/scripts/build-component-versions +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python3 - -import sys -import os -import argparse -import json - -from lxml import etree as ET - -parser = argparse.ArgumentParser() -parser.add_argument('INPUT_DIR', type=str, - help="Directory containing XML interface definition files") -parser.add_argument('OUTPUT_DIR', type=str, - help="Output directory for JSON file") - -args = parser.parse_args() - -input_dir = args.INPUT_DIR -output_dir = args.OUTPUT_DIR - -version_dict = {} - -for filename in os.listdir(input_dir): - filepath = os.path.join(input_dir, filename) - print(filepath) - try: - xml = ET.parse(filepath) - except Exception as e: - print("Failed to load interface definition file {0}".format(filename)) - print(e) - sys.exit(1) - - root = xml.getroot() - version_data = root.iterfind("syntaxVersion") - for ver in version_data: - component = ver.get("component") - version = int(ver.get("version")) - - v = version_dict.get(component) - if v is None: - version_dict[component] = version - elif version > v: - version_dict[component] = version - -out_file = os.path.join(output_dir, 'component-versions.json') -with open(out_file, 'w') as f: - json.dump(version_dict, f, indent=4, sort_keys=True) diff --git a/scripts/override-default b/scripts/override-default index c8a0ff1da..0c49087c8 100755 --- a/scripts/override-default +++ b/scripts/override-default @@ -27,6 +27,7 @@ import sys import glob import logging +from copy import deepcopy from lxml import etree debug = False @@ -60,30 +61,55 @@ def override_element(l: list): for el in parents: el.getparent().remove(el) +def merge_remaining(l: list, elementtree): + """ + Merge (now) single leaf node containing 'defaultValue' with leaf nodes + of same path and no 'defaultValue'. + """ + for p in l: + p = p.split() + path_str = f'/interfaceDefinition/*' + path_list = [] + for i in range(len(p)): + path_list.append(f'[@name="{p[i]}"]') + path_str += '/children/*'.join(path_list) + rp = elementtree.xpath(path_str) + if len(rp) > 1: + for el in rp[1:]: + # in practice there will only be one child of the path, + # either defaultValue or Properties, since + # override_element() has already run + for child in el: + rp[0].append(deepcopy(child)) + el.getparent().remove(el) + def collect_and_override(dir_name): """ - Collect elements with defaultValue tag into dictionary indexed by tuple - of (name: str, ancestor path: str). + Collect elements with defaultValue tag into dictionary indexed by name + attributes of ancestor path. """ for fname in glob.glob(f'{dir_name}/*.xml'): tree = etree.parse(fname) root = tree.getroot() defv = {} - xpath_str = f'//defaultValue' + xpath_str = '//defaultValue' xp = tree.xpath(xpath_str) for element in xp: ap = element.xpath('ancestor::*[@name]') ap_name = [el.get("name") for el in ap] - ap_path_str = ' '.join(ap_name[:-1]) - defv.setdefault((ap_name[-1], ap_path_str), []).append(element) + ap_path_str = ' '.join(ap_name) + defv.setdefault(ap_path_str, []).append(element) for k, v in defv.items(): if len(v) > 1: - logger.info(f"overridding default in {k[0]}, path '{k[1]}'") + logger.info(f"overridding default in path '{k}'") override_element(v) + to_merge = list(defv) + merge_remaining(to_merge, tree) + revised_str = etree.tostring(root, encoding='unicode', pretty_print=True) with open(f'{fname}', 'w') as f: diff --git a/smoketest/configs/bgp-small-ipv4-unicast b/smoketest/configs/bgp-small-ipv4-unicast new file mode 100644 index 000000000..83f1effd2 --- /dev/null +++ b/smoketest/configs/bgp-small-ipv4-unicast @@ -0,0 +1,77 @@ +interfaces { + ethernet eth0 { + address 192.0.2.1/24 + address 2001:db8::1/64 + } + loopback lo { + } +} +protocols { + bgp 65001 { + address-family { + ipv4-unicast { + network 10.0.150.0/23 { + } + } + ipv6-unicast { + network 2001:db8:200::/40 { + } + } + } + neighbor 192.0.2.10 { + remote-as 65010 + } + neighbor 192.0.2.11 { + remote-as 65011 + } + neighbor 2001:db8::10 { + remote-as 65010 + } + neighbor 2001:db8::11 { + remote-as 65011 + } + parameters { + log-neighbor-changes + } + } +} +service { + ssh { + disable-host-validation + port 22 + } +} +system { + config-management { + commit-revisions 200 + } + console { + device ttyS0 { + speed 115200 + } + } + domain-name vyos.net + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 + plaintext-password "" + } + } + } + syslog { + global { + facility all { + level notice + } + facility protocols { + level debug + } + } + } +} + +/* Warning: Do not remove the following line. */ +/* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pptp@1:qos@1:quagga@6:snmp@1:ssh@1:system@9:vrrp@2:wanloadbalance@3:webgui@1:webproxy@1:webproxy@2:zone-policy@1" === */ +/* Release version: 1.2.5 */ diff --git a/smoketest/configs/dialup-router-medium-vpn b/smoketest/configs/dialup-router-medium-vpn index dfb3d9621..af7c075e4 100644 --- a/smoketest/configs/dialup-router-medium-vpn +++ b/smoketest/configs/dialup-router-medium-vpn @@ -624,6 +624,7 @@ system { } } name-server 192.168.0.1 + name-servers-dhcp pppoe0 ntp { allow-clients { address 192.168.0.0/16 diff --git a/smoketest/configs/isis-small b/smoketest/configs/isis-small index 2c42ac9c4..247ae32b5 100644 --- a/smoketest/configs/isis-small +++ b/smoketest/configs/isis-small @@ -102,4 +102,3 @@ system { // Warning: Do not remove the following line. // vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@1:conntrack-sync@1:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@18:ipoe-server@1:ipsec@5:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@7:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@20:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" // Release version: 1.3.0-rc1 - diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py index 7f69b8444..90c534796 100644 --- a/smoketest/scripts/cli/base_interfaces_test.py +++ b/smoketest/scripts/cli/base_interfaces_test.py @@ -214,7 +214,7 @@ class BasicInterfaceTest: self.cli_commit() for interface in self._interfaces: - self.assertTrue(AF_INET6 not in ifaddresses(interface)) + self.assertNotIn(AF_INET6, ifaddresses(interface)) def test_interface_mtu(self): if not self._test_mtu: @@ -246,11 +246,19 @@ class BasicInterfaceTest: for intf in self._interfaces: base = self._base_path + [intf] self.cli_set(base + ['mtu', self._mtu]) - self.cli_set(base + ['ipv6', 'address', 'no-default-link-local']) for option in self._options.get(intf, []): self.cli_set(base + option.split()) + # check validate() - can not set low MTU if 'no-default-link-local' + # is not set on CLI + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + for intf in self._interfaces: + base = self._base_path + [intf] + self.cli_set(base + ['ipv6', 'address', 'no-default-link-local']) + # commit interface changes self.cli_commit() @@ -278,30 +286,12 @@ class BasicInterfaceTest: base = self._base_path + [interface, 'vif', vlan] for address in self._test_addr: self.cli_set(base + ['address', address]) - self.cli_set(base + ['ingress-qos', '0:1']) - self.cli_set(base + ['egress-qos', '1:6']) self.cli_commit() for intf in self._interfaces: for vlan in self._vlan_range: vif = f'{intf}.{vlan}' - tmp = get_interface_config(f'{vif}') - - tmp2 = dict_search('linkinfo.info_data.ingress_qos', tmp) - for item in tmp2 if tmp2 else []: - from_key = item['from'] - to_key = item['to'] - self.assertEqual(from_key, 0) - self.assertEqual(to_key, 1) - - tmp2 = dict_search('linkinfo.info_data.egress_qos', tmp) - for item in tmp2 if tmp2 else []: - from_key = item['from'] - to_key = item['to'] - self.assertEqual(from_key, 1) - self.assertEqual(to_key, 6) - for address in self._test_addr: self.assertTrue(is_intf_addr_assigned(vif, address)) @@ -369,8 +359,6 @@ class BasicInterfaceTest: for vlan in self._vlan_range: base = self._base_path + [interface, 'vif', vlan] - for address in self._test_addr: - self.cli_set(base + ['address', address]) self.cli_set(base + ['ingress-qos', '0:1']) self.cli_set(base + ['egress-qos', '1:6']) @@ -395,9 +383,6 @@ class BasicInterfaceTest: self.assertEqual(from_key, 1) self.assertEqual(to_key, 6) - for address in self._test_addr: - self.assertTrue(is_intf_addr_assigned(vif, address)) - self.assertEqual(Interface(vif).get_admin_state(), 'up') new_ingress_qos_from = 1 @@ -408,8 +393,6 @@ class BasicInterfaceTest: base = self._base_path + [interface] for vlan in self._vlan_range: base = self._base_path + [interface, 'vif', vlan] - self.cli_delete(base + ['ingress-qos', '0:1']) - self.cli_delete(base + ['egress-qos', '1:6']) self.cli_set(base + ['ingress-qos', f'{new_ingress_qos_from}:{new_ingress_qos_to}']) self.cli_set(base + ['egress-qos', f'{new_egress_qos_from}:{new_egress_qos_to}']) @@ -556,13 +539,16 @@ class BasicInterfaceTest: if not self._test_ip: self.skipTest('not supported') + arp_tmo = '300' + mss = '1420' + for interface in self._interfaces: - arp_tmo = '300' path = self._base_path + [interface] for option in self._options.get(interface, []): self.cli_set(path + option.split()) # Options + self.cli_set(path + ['ip', 'adjust-mss', mss]) self.cli_set(path + ['ip', 'arp-cache-timeout', arp_tmo]) self.cli_set(path + ['ip', 'disable-arp-filter']) self.cli_set(path + ['ip', 'disable-forwarding']) @@ -576,54 +562,73 @@ class BasicInterfaceTest: self.cli_commit() for interface in self._interfaces: + base_options = f'-A FORWARD -o {interface} -p tcp -m tcp --tcp-flags SYN,RST SYN' + out = cmd('sudo iptables-save -t mangle') + for line in out.splitlines(): + if line.startswith(base_options): + self.assertIn(f'--set-mss {mss}', line) + tmp = read_file(f'/proc/sys/net/ipv4/neigh/{interface}/base_reachable_time_ms') self.assertEqual(tmp, str((int(arp_tmo) * 1000))) # tmo value is in milli seconds - tmp = read_file(f'/proc/sys/net/ipv4/conf/{interface}/arp_filter') + proc_base = f'/proc/sys/net/ipv4/conf/{interface}' + + tmp = read_file(f'{proc_base}/arp_filter') self.assertEqual('0', tmp) - tmp = read_file(f'/proc/sys/net/ipv4/conf/{interface}/arp_accept') + tmp = read_file(f'{proc_base}/arp_accept') self.assertEqual('1', tmp) - tmp = read_file(f'/proc/sys/net/ipv4/conf/{interface}/arp_announce') + tmp = read_file(f'{proc_base}/arp_announce') self.assertEqual('1', tmp) - tmp = read_file(f'/proc/sys/net/ipv4/conf/{interface}/arp_ignore') + tmp = read_file(f'{proc_base}/arp_ignore') self.assertEqual('1', tmp) - tmp = read_file(f'/proc/sys/net/ipv4/conf/{interface}/forwarding') + tmp = read_file(f'{proc_base}/forwarding') self.assertEqual('0', tmp) - tmp = read_file(f'/proc/sys/net/ipv4/conf/{interface}/proxy_arp') + tmp = read_file(f'{proc_base}/proxy_arp') self.assertEqual('1', tmp) - tmp = read_file(f'/proc/sys/net/ipv4/conf/{interface}/proxy_arp_pvlan') + tmp = read_file(f'{proc_base}/proxy_arp_pvlan') self.assertEqual('1', tmp) - tmp = read_file(f'/proc/sys/net/ipv4/conf/{interface}/rp_filter') + tmp = read_file(f'{proc_base}/rp_filter') self.assertEqual('2', tmp) def test_interface_ipv6_options(self): if not self._test_ipv6: self.skipTest('not supported') + mss = '1400' + dad_transmits = '10' + for interface in self._interfaces: - dad_transmits = '10' path = self._base_path + [interface] for option in self._options.get(interface, []): self.cli_set(path + option.split()) # Options + self.cli_set(path + ['ipv6', 'adjust-mss', mss]) self.cli_set(path + ['ipv6', 'disable-forwarding']) self.cli_set(path + ['ipv6', 'dup-addr-detect-transmits', dad_transmits]) self.cli_commit() for interface in self._interfaces: - tmp = read_file(f'/proc/sys/net/ipv6/conf/{interface}/forwarding') + base_options = f'-A FORWARD -o {interface} -p tcp -m tcp --tcp-flags SYN,RST SYN' + out = cmd('sudo ip6tables-save -t mangle') + for line in out.splitlines(): + if line.startswith(base_options): + self.assertIn(f'--set-mss {mss}', line) + + proc_base = f'/proc/sys/net/ipv6/conf/{interface}' + + tmp = read_file(f'{proc_base}/forwarding') self.assertEqual('0', tmp) - tmp = read_file(f'/proc/sys/net/ipv6/conf/{interface}/dad_transmits') + tmp = read_file(f'{proc_base}/dad_transmits') self.assertEqual(dad_transmits, tmp) def test_dhcpv6_client_options(self): diff --git a/smoketest/scripts/cli/base_vyostest_shim.py b/smoketest/scripts/cli/base_vyostest_shim.py index 18e49f47f..50f80e7d1 100644 --- a/smoketest/scripts/cli/base_vyostest_shim.py +++ b/smoketest/scripts/cli/base_vyostest_shim.py @@ -20,7 +20,9 @@ from time import sleep from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError from vyos import ConfigError +from vyos.defaults import commit_lock from vyos.util import cmd +from vyos.util import run save_config = '/tmp/vyos-smoketest-save' @@ -70,21 +72,16 @@ class VyOSUnitTestSHIM: def cli_commit(self): self._session.commit() + # during a commit there is a process opening commit_lock, and run() returns 0 + while run(f'sudo lsof | grep -q {commit_lock}') == 0: + sleep(0.250) - def getFRRconfig(self, string, end='$', endsection='^!'): + def getFRRconfig(self, string, end='$', endsection='^!', daemon=''): """ Retrieve current "running configuration" from FRR """ - command = f'vtysh -c "show run" | sed -n "/^{string}{end}/,/{endsection}/p"' - - count = 0 - tmp = '' - while count < 10 and tmp == '': - # Let FRR settle after a config change first before harassing it again - sleep(1) - tmp = cmd(command) - count += 1 - - if self.debug or tmp == '': + command = f'vtysh -c "show run {daemon} no-header" | sed -n "/^{string}{end}/,/{endsection}/p"' + out = cmd(command) + if self.debug: import pprint print(f'\n\ncommand "{command}" returned:\n') - pprint.pprint(tmp) - return tmp + pprint.pprint(out) + return out diff --git a/smoketest/scripts/cli/test_ha_vrrp.py b/smoketest/scripts/cli/test_ha_vrrp.py new file mode 100755 index 000000000..8c5bb86d8 --- /dev/null +++ b/smoketest/scripts/cli/test_ha_vrrp.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError +from vyos.ifconfig.vrrp import VRRP +from vyos.util import cmd +from vyos.util import process_named_running +from vyos.util import read_file +from vyos.template import inc_ip + +PROCESS_NAME = 'keepalived' +KEEPALIVED_CONF = VRRP.location['config'] +base_path = ['high-availability', 'vrrp'] + +vrrp_interface = 'eth1' +groups = ['VLAN77', 'VLAN78', 'VLAN201'] + +def getConfig(string, end='}'): + command = f'cat {KEEPALIVED_CONF} | sed -n "/^{string}/,/^{end}/p"' + out = cmd(command) + return out + +class TestVRRP(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + for group in groups: + vlan_id = group.lstrip('VLAN') + self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id]) + + self.cli_delete(base_path) + self.cli_commit() + + # Process must be terminated after deleting the config + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_01_default_values(self): + for group in groups: + vlan_id = group.lstrip('VLAN') + vip = f'100.64.{vlan_id}.1/24' + group_base = base_path + ['group', group] + + self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', inc_ip(vip, 1) + '/' + vip.split('/')[-1]]) + + self.cli_set(group_base + ['description', group]) + self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}']) + self.cli_set(group_base + ['address', vip]) + self.cli_set(group_base + ['vrid', vlan_id]) + + # commit changes + self.cli_commit() + + for group in groups: + vlan_id = group.lstrip('VLAN') + vip = f'100.64.{vlan_id}.1/24' + + config = getConfig(f'vrrp_instance {group}') + + self.assertIn(f'# {group}', config) + self.assertIn(f'interface {vrrp_interface}.{vlan_id}', config) + self.assertIn(f'virtual_router_id {vlan_id}', config) + self.assertIn(f'priority 100', config) # default value + self.assertIn(f'advert_int 1', config) # default value + self.assertIn(f'preempt_delay 0', config) # default value + self.assertNotIn(f'use_vmac', config) + self.assertIn(f' {vip}', config) + + def test_02_simple_options(self): + advertise_interval = '77' + priority = '123' + preempt_delay = '400' + + for group in groups: + vlan_id = group.lstrip('VLAN') + vip = f'100.64.{vlan_id}.1/24' + group_base = base_path + ['group', group] + + self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', inc_ip(vip, 1) + '/' + vip.split('/')[-1]]) + + self.cli_set(group_base + ['description', group]) + self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}']) + self.cli_set(group_base + ['address', vip]) + self.cli_set(group_base + ['vrid', vlan_id]) + + self.cli_set(group_base + ['advertise-interval', advertise_interval]) + self.cli_set(group_base + ['priority', priority]) + self.cli_set(group_base + ['preempt-delay', preempt_delay]) + + self.cli_set(group_base + ['rfc3768-compatibility']) + + # Authentication + self.cli_set(group_base + ['authentication', 'type', 'plaintext-password']) + self.cli_set(group_base + ['authentication', 'password', f'vyos-{group}']) + + # commit changes + self.cli_commit() + + for group in groups: + vlan_id = group.lstrip('VLAN') + vip = f'100.64.{vlan_id}.1/24' + + config = getConfig(f'vrrp_instance {group}') + self.assertIn(f'# {group}', config) + self.assertIn(f'state BACKUP', config) + self.assertIn(f'interface {vrrp_interface}.{vlan_id}', config) + self.assertIn(f'virtual_router_id {vlan_id}', config) + self.assertIn(f'priority {priority}', config) + self.assertIn(f'advert_int {advertise_interval}', config) + self.assertIn(f'preempt_delay {preempt_delay}', config) + self.assertIn(f'use_vmac {vrrp_interface}.{vlan_id}v{vlan_id}', config) + self.assertIn(f' {vip}', config) + + # Authentication + self.assertIn(f'auth_pass "vyos-{group}"', config) + self.assertIn(f'auth_type PASS', config) + + def test_03_sync_group(self): + sync_group = 'VyOS' + + for group in groups: + vlan_id = group.lstrip('VLAN') + vip = f'100.64.{vlan_id}.1/24' + group_base = base_path + ['group', group] + + self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', inc_ip(vip, 1) + '/' + vip.split('/')[-1]]) + + self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}']) + self.cli_set(group_base + ['address', vip]) + self.cli_set(group_base + ['vrid', vlan_id]) + + self.cli_set(base_path + ['sync-group', sync_group, 'member', group]) + + # commit changes + self.cli_commit() + + for group in groups: + vlan_id = group.lstrip('VLAN') + vip = f'100.64.{vlan_id}.1/24' + config = getConfig(f'vrrp_instance {group}') + + self.assertIn(f'interface {vrrp_interface}.{vlan_id}', config) + self.assertIn(f'virtual_router_id {vlan_id}', config) + self.assertNotIn(f'use_vmac', config) + self.assertIn(f' {vip}', config) + + config = getConfig(f'vrrp_sync_group {sync_group}') + self.assertIn(r'group {', config) + for group in groups: + self.assertIn(f'{group}', config) + +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 a9cdab16a..6d80e4c96 100755 --- a/smoketest/scripts/cli/test_interfaces_ethernet.py +++ b/smoketest/scripts/cli/test_interfaces_ethernet.py @@ -25,9 +25,26 @@ from vyos.util import cmd from vyos.util import process_named_running from vyos.util import read_file -pki_path = ['pki'] -cert_data = 'MIICFDCCAbugAwIBAgIUfMbIsB/ozMXijYgUYG80T1ry+mcwCgYIKoZIzj0EAwIwWTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MB4XDTIxMDcyMDEyNDUxMloXDTI2MDcxOTEyNDUxMlowWTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE01HrLcNttqq4/PtoMua8rMWEkOdBu7vP94xzDO7A8C92ls1v86eePy4QllKCzIw3QxBIoCuH2peGRfWgPRdFsKNhMF8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMB0GA1UdDgQWBBSu+JnU5ZC4mkuEpqg2+Mk4K79oeDAKBggqhkjOPQQDAgNHADBEAiBEFdzQ/Bc3LftzngrY605UhA6UprHhAogKgROv7iR4QgIgEFUxTtW3xXJcnUPWhhUFhyZoqfn8dE93+dm/LDnp7C0=' -key_data = 'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPLpD0Ohhoq0g4nhx2KMIuze7ucKUt/lBEB2wc03IxXyhRANCAATTUestw222qrj8+2gy5rysxYSQ50G7u8/3jHMM7sDwL3aWzW/zp54/LhCWUoLMjDdDEEigK4fal4ZF9aA9F0Ww' +cert_data = """ +MIICFDCCAbugAwIBAgIUfMbIsB/ozMXijYgUYG80T1ry+mcwCgYIKoZIzj0EAwIw +WTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNv +bWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MB4XDTIx +MDcyMDEyNDUxMloXDTI2MDcxOTEyNDUxMlowWTELMAkGA1UEBhMCR0IxEzARBgNV +BAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlP +UzESMBAGA1UEAwwJVnlPUyBUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE +01HrLcNttqq4/PtoMua8rMWEkOdBu7vP94xzDO7A8C92ls1v86eePy4QllKCzIw3 +QxBIoCuH2peGRfWgPRdFsKNhMF8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E +BAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMB0GA1UdDgQWBBSu ++JnU5ZC4mkuEpqg2+Mk4K79oeDAKBggqhkjOPQQDAgNHADBEAiBEFdzQ/Bc3Lftz +ngrY605UhA6UprHhAogKgROv7iR4QgIgEFUxTtW3xXJcnUPWhhUFhyZoqfn8dE93 ++dm/LDnp7C0= +""" + +key_data = """ +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPLpD0Ohhoq0g4nhx +2KMIuze7ucKUt/lBEB2wc03IxXyhRANCAATTUestw222qrj8+2gy5rysxYSQ50G7 +u8/3jHMM7sDwL3aWzW/zp54/LhCWUoLMjDdDEEigK4fal4ZF9aA9F0Ww +""" def get_wpa_supplicant_value(interface, key): tmp = read_file(f'/run/wpa_supplicant/{interface}.conf') @@ -64,10 +81,7 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase): # call base-classes classmethod super(cls, cls).setUpClass() - def tearDown(self): - self.cli_delete(pki_path) - for interface in self._interfaces: # when using a dedicated interface to test via TEST_ETH environment # variable only this one will be cleared in the end - usable to test @@ -151,14 +165,17 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase): self.cli_commit() def test_eapol_support(self): - self.cli_set(pki_path + ['ca', 'eapol', 'certificate', cert_data]) - self.cli_set(pki_path + ['certificate', 'eapol', 'certificate', cert_data]) - self.cli_set(pki_path + ['certificate', 'eapol', 'private', 'key', key_data]) + ca_name = 'eapol' + cert_name = 'eapol' + + self.cli_set(['pki', 'ca', ca_name, 'certificate', cert_data.replace('\n','')]) + self.cli_set(['pki', 'certificate', cert_name, 'certificate', cert_data.replace('\n','')]) + self.cli_set(['pki', 'certificate', cert_name, 'private', 'key', key_data.replace('\n','')]) for interface in self._interfaces: # Enable EAPoL - self.cli_set(self._base_path + [interface, 'eapol', 'ca-certificate', 'eapol']) - self.cli_set(self._base_path + [interface, 'eapol', 'certificate', 'eapol']) + self.cli_set(self._base_path + [interface, 'eapol', 'ca-certificate', ca_name]) + self.cli_set(self._base_path + [interface, 'eapol', 'certificate', cert_name]) self.cli_commit() @@ -189,5 +206,8 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase): tmp = get_wpa_supplicant_value(interface, 'identity') self.assertEqual(f'"{mac}"', tmp) + self.cli_delete(['pki', 'ca', ca_name]) + self.cli_delete(['pki', 'certificate', cert_name]) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_pppoe.py b/smoketest/scripts/cli/test_interfaces_pppoe.py index 3412ebae0..67edce2a0 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-2020 VyOS maintainers and contributors +# Copyright (C) 2019-2021 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -22,10 +22,8 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError -from vyos.util import read_file config_file = '/etc/ppp/peers/{}' -dhcp6c_config_file = '/run/dhcp6c/dhcp6c.{}.conf' base_path = ['interfaces', 'pppoe'] def get_config_value(interface, key): @@ -35,25 +33,26 @@ def get_config_value(interface, key): return list(line.split()) return [] -def get_dhcp6c_config_value(interface, key): - tmp = read_file(dhcp6c_config_file.format(interface)) - tmp = re.findall(r'\n?{}\s+(.*)'.format(key), tmp) - - out = [] - for item in tmp: - out.append(item.replace(';','')) - return out - +# 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' def tearDown(self): + # Validate PPPoE client process + for interface in self._interfaces: + running = False + for proc in process_iter(): + if interface in proc.cmdline(): + running = True + break + self.assertTrue(running) + self.cli_delete(base_path) self.cli_commit() - def test_pppoe_client(self): + def test_01_pppoe_client(self): # Check if PPPoE dialer can be configured and runs for interface in self._interfaces: user = 'VyOS-user-' + interface @@ -71,8 +70,8 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase): self.cli_commit() self.cli_set(base_path + [interface, 'source-interface', self._source_interface]) - # commit changes - self.cli_commit() + # commit changes + self.cli_commit() # verify configuration file(s) for interface in self._interfaces: @@ -88,17 +87,7 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase): tmp = get_config_value(interface, 'ifname')[1] self.assertEqual(tmp, interface) - # Check if ppp process is running in the interface in question - running = False - for p in process_iter(): - if "pppd" in p.name(): - if interface in p.cmdline(): - running = True - - self.assertTrue(running) - - - def test_pppoe_clent_disabled_interface(self): + def test_02_pppoe_client_disabled_interface(self): # Check if PPPoE Client can be disabled for interface in self._interfaces: self.cli_set(base_path + [interface, 'authentication', 'user', 'vyos']) @@ -106,23 +95,45 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + [interface, 'source-interface', self._source_interface]) self.cli_set(base_path + [interface, 'disable']) - self.cli_commit() + self.cli_commit() - # Validate PPPoE client process - running = False + # Validate PPPoE client process - must not run as interfaces are disabled for interface in self._interfaces: + running = False for proc in process_iter(): if interface in proc.cmdline(): running = True + break + self.assertFalse(running) + + # enable PPPoE interfaces + for interface in self._interfaces: + self.cli_delete(base_path + [interface, 'disable']) + + self.cli_commit() + - self.assertFalse(running) + def test_03_pppoe_authentication(self): + # When username or password is set - so must be the other + for interface in self._interfaces: + self.cli_set(base_path + [interface, 'authentication', 'user', 'vyos']) + self.cli_set(base_path + [interface, 'source-interface', self._source_interface]) + self.cli_set(base_path + [interface, 'ipv6', 'address', 'autoconf']) + # check validate() - if user is set, so must be the password + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + [interface, 'authentication', 'password', 'vyos']) + + self.cli_commit() - def test_pppoe_dhcpv6pd(self): + def test_04_pppoe_dhcpv6pd(self): # Check if PPPoE dialer can be configured with DHCPv6-PD address = '1' sla_id = '0' sla_len = '8' + for interface in self._interfaces: self.cli_set(base_path + [interface, 'authentication', 'user', 'vyos']) self.cli_set(base_path + [interface, 'authentication', 'password', 'vyos']) @@ -147,51 +158,8 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase): self.assertEqual(tmp, 'vyos') tmp = get_config_value(interface, 'password')[1].replace('"', '') self.assertEqual(tmp, 'vyos') - - for param in ['+ipv6', 'ipv6cp-use-ipaddr']: - tmp = get_config_value(interface, param)[0] - self.assertEqual(tmp, param) - - # verify DHCPv6 prefix delegation - # will return: ['delegation', '::/56 infinity;'] - tmp = get_dhcp6c_config_value(interface, 'prefix')[1].split()[0] # mind the whitespace - self.assertEqual(tmp, '::/56') - tmp = get_dhcp6c_config_value(interface, 'prefix-interface')[0].split()[0] - self.assertEqual(tmp, self._source_interface) - tmp = get_dhcp6c_config_value(interface, 'ifid')[0] - self.assertEqual(tmp, address) - tmp = get_dhcp6c_config_value(interface, 'sla-id')[0] - self.assertEqual(tmp, sla_id) - tmp = get_dhcp6c_config_value(interface, 'sla-len')[0] - self.assertEqual(tmp, sla_len) - - # Check if ppp process is running in the interface in question - running = False - for p in process_iter(): - if "pppd" in p.name(): - running = True - self.assertTrue(running) - - # We can not check if wide-dhcpv6 process is running as it is started - # after the PPP interface gets a link to the ISP - but we can see if - # it would be started by the scripts - tmp = read_file(f'/etc/ppp/ipv6-up.d/1000-vyos-pppoe-{interface}') - tmp = re.findall(f'systemctl restart dhcp6c@{interface}.service', tmp) - self.assertTrue(tmp) - - def test_pppoe_authentication(self): - # When username or password is set - so must be the other - interface = 'pppoe0' - self.cli_set(base_path + [interface, 'authentication', 'user', 'vyos']) - self.cli_set(base_path + [interface, 'source-interface', self._source_interface]) - self.cli_set(base_path + [interface, 'ipv6', 'address', 'autoconf']) - - # check validate() - if user is set, so must be the password - with self.assertRaises(ConfigSessionError): - self.cli_commit() - - self.cli_set(base_path + [interface, 'authentication', 'password', 'vyos']) - self.cli_commit() + tmp = get_config_value(interface, '+ipv6 ipv6cp-use-ipaddr') + self.assertListEqual(tmp, ['+ipv6', 'ipv6cp-use-ipaddr']) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_nat66.py b/smoketest/scripts/cli/test_nat66.py index dca92c97d..7721105e0 100755 --- a/smoketest/scripts/cli/test_nat66.py +++ b/smoketest/scripts/cli/test_nat66.py @@ -31,10 +31,13 @@ src_path = base_path + ['source'] dst_path = base_path + ['destination'] class TestNAT66(VyOSUnitTestSHIM.TestCase): - def setUp(self): + @classmethod + def setUpClass(cls): + super(cls, cls).setUpClass() + # ensure we can also run this test on a live system - so lets clean # out the current configuration :) - self.cli_delete(base_path) + cls.cli_delete(cls, base_path) def tearDown(self): self.cli_delete(base_path) @@ -183,4 +186,4 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase): self.cli_commit() if __name__ == '__main__': - unittest.main(verbosity=2) + unittest.main(verbosity=2, failfast=True) diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py index 2d7b78048..c2288a86a 100755 --- a/smoketest/scripts/cli/test_policy.py +++ b/smoketest/scripts/cli/test_policy.py @@ -804,6 +804,19 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): }, }, }, + 'evpn-configuration' : { + 'rule' : { + '10' : { + 'action' : 'permit', + 'match' : { + 'evpn-default-route' : '', + 'evpn-rd' : '100:300', + 'evpn-route-type' : 'prefix', + 'evpn-vni' : '1234', + }, + }, + }, + }, } self.cli_set(['policy', 'access-list', access_list, 'rule', '10', 'action', 'permit']) @@ -847,6 +860,14 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): if 'community' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'community', 'community-list', rule_config['match']['community']]) self.cli_set(path + ['rule', rule, 'match', 'community', 'exact-match']) + if 'evpn-default-route' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'evpn', 'default-route']) + if 'evpn-rd' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'evpn', 'rd', rule_config['match']['evpn-rd']]) + if 'evpn-route-type' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'evpn', 'route-type', rule_config['match']['evpn-route-type']]) + if 'evpn-vni' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'evpn', 'vni', rule_config['match']['evpn-vni']]) if 'extcommunity' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'extcommunity', rule_config['match']['extcommunity']]) if 'interface' in rule_config['match']: @@ -967,6 +988,18 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): if 'community' in rule_config['match']: tmp = f'match community {rule_config["match"]["community"]} exact-match' self.assertIn(tmp, config) + if 'evpn-default-route' in rule_config['match']: + tmp = f'match evpn default-route' + self.assertIn(tmp, config) + if 'evpn-rd' in rule_config['match']: + tmp = f'match evpn rd {rule_config["match"]["evpn-rd"]}' + self.assertIn(tmp, config) + if 'evpn-route-type' in rule_config['match']: + tmp = f'match evpn route-type {rule_config["match"]["evpn-route-type"]}' + self.assertIn(tmp, config) + if 'evpn-vni' in rule_config['match']: + tmp = f'match evpn vni {rule_config["match"]["evpn-vni"]}' + self.assertIn(tmp, config) if 'extcommunity' in rule_config['match']: tmp = f'match extcommunity {rule_config["match"]["extcommunity"]}' self.assertIn(tmp, config) @@ -1116,5 +1149,58 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): self.assertEqual(tmp, original) + # Test set table for fwmark + def test_fwmark_table_id(self): + path = base_path + ['local-route'] + + fwmk = '24' + rule = '101' + table = '154' + + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'fwmark', fwmk]) + + self.cli_commit() + + # Check generated configuration + + # Expected values + original = """ + 101: from all fwmark 0x18 lookup 154 + """ + tmp = cmd('ip rule show prio 101') + original = original.split() + tmp = tmp.split() + + self.assertEqual(tmp, original) + + # Test set table for sources with fwmark + def test_fwmark_sources_table_id(self): + path = base_path + ['local-route'] + + sources = ['203.0.113.11', '203.0.113.12'] + fwmk = '23' + rule = '100' + table = '150' + for src in sources: + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'source', src]) + self.cli_set(path + ['rule', rule, 'fwmark', fwmk]) + + self.cli_commit() + + # Check generated configuration + + # Expected values + original = """ + 100: from 203.0.113.11 fwmark 0x17 lookup 150 + 100: from 203.0.113.12 fwmark 0x17 lookup 150 + """ + tmp = cmd('ip rule show prio 100') + original = original.split() + tmp = tmp.split() + + self.assertEqual(tmp, original) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index c3a2ffbf9..16284ed01 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -78,6 +78,7 @@ neighbor_config = { 'cap_over' : '', 'ttl_security' : '5', 'local_as' : '300', + 'solo' : '', 'route_map_in' : route_map_in, 'route_map_out': route_map_out, 'no_send_comm_std' : '', @@ -164,7 +165,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): if 'multi_hop' in peer_config: self.assertIn(f' neighbor {peer} ebgp-multihop {peer_config["multi_hop"]}', frrconfig) if 'local_as' in peer_config: - self.assertIn(f' neighbor {peer} local-as {peer_config["local_as"]}', frrconfig) + self.assertIn(f' neighbor {peer} local-as {peer_config["local_as"]} no-prepend replace-as', frrconfig) if 'cap_over' in peer_config: self.assertIn(f' neighbor {peer} override-capability', frrconfig) if 'passive' in peer_config: @@ -173,6 +174,8 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' neighbor {peer} password {peer_config["password"]}', frrconfig) if 'remote_as' in peer_config: self.assertIn(f' neighbor {peer} remote-as {peer_config["remote_as"]}', frrconfig) + if 'solo' in peer_config: + self.assertIn(f' neighbor {peer} solo', frrconfig) if 'shutdown' in peer_config: self.assertIn(f' neighbor {peer} shutdown', frrconfig) if 'ttl_security' in peer_config: @@ -218,8 +221,6 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): # Default local preference (higher = more preferred, default value is 100) self.cli_set(base_path + ['parameters', 'default', 'local-pref', local_pref]) - # Deactivate IPv4 unicast for a peer by default - self.cli_set(base_path + ['parameters', 'default', 'no-ipv4-unicast']) self.cli_set(base_path + ['parameters', 'graceful-restart', 'stalepath-time', stalepath_time]) self.cli_set(base_path + ['parameters', 'graceful-shutdown']) self.cli_set(base_path + ['parameters', 'ebgp-requires-policy']) @@ -243,7 +244,6 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' bgp router-id {router_id}', frrconfig) self.assertIn(f' bgp log-neighbor-changes', frrconfig) self.assertIn(f' bgp default local-preference {local_pref}', frrconfig) - self.assertIn(f' no bgp default ipv4-unicast', frrconfig) self.assertIn(f' bgp graceful-restart stalepath-time {stalepath_time}', frrconfig) self.assertIn(f' bgp graceful-shutdown', frrconfig) self.assertIn(f' bgp bestpath as-path multipath-relax', frrconfig) @@ -281,7 +281,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): if 'multi_hop' in peer_config: self.cli_set(base_path + ['neighbor', peer, 'ebgp-multihop', peer_config["multi_hop"]]) if 'local_as' in peer_config: - self.cli_set(base_path + ['neighbor', peer, 'local-as', peer_config["local_as"]]) + self.cli_set(base_path + ['neighbor', peer, 'local-as', peer_config["local_as"], 'no-prepend', 'replace-as']) if 'cap_over' in peer_config: self.cli_set(base_path + ['neighbor', peer, 'override-capability']) if 'passive' in peer_config: @@ -296,6 +296,8 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['neighbor', peer, 'strict-capability-match']) if 'shutdown' in peer_config: self.cli_set(base_path + ['neighbor', peer, 'shutdown']) + if 'solo' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'solo']) if 'ttl_security' in peer_config: self.cli_set(base_path + ['neighbor', peer, 'ttl-security', 'hops', peer_config["ttl_security"]]) if 'update_src' in peer_config: @@ -348,7 +350,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): if 'multi_hop' in config: self.cli_set(base_path + ['peer-group', peer_group, 'ebgp-multihop', config["multi_hop"]]) if 'local_as' in config: - self.cli_set(base_path + ['peer-group', peer_group, 'local-as', config["local_as"]]) + self.cli_set(base_path + ['peer-group', peer_group, 'local-as', config["local_as"], 'no-prepend', 'replace-as']) if 'cap_over' in config: self.cli_set(base_path + ['peer-group', peer_group, 'override-capability']) if 'passive' in config: @@ -628,6 +630,9 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): # templates and Jinja2 FRR template. table = '1000' + self.cli_set(base_path + ['local-as', ASN]) + # testing only one AFI is sufficient as it's generic code + for vrf in vrfs: vrf_base = ['vrf', 'name', vrf] self.cli_set(vrf_base + ['table', table]) @@ -636,15 +641,26 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_set(vrf_base + ['protocols', 'bgp', 'route-map', route_map_in]) table = str(int(table) + 1000) + # import VRF routes do main RIB + self.cli_set(base_path + ['address-family', 'ipv6-unicast', 'import', 'vrf', vrf]) + self.cli_commit() + # Verify FRR bgpd configuration + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f' address-family ipv6 unicast', frrconfig) + + for vrf in vrfs: + self.assertIn(f' import vrf {vrf}', frrconfig) + # Verify FRR bgpd configuration - frrconfig = self.getFRRconfig(f'router bgp {ASN} vrf {vrf}') - self.assertIn(f'router bgp {ASN} vrf {vrf}', frrconfig) - self.assertIn(f' bgp router-id {router_id}', frrconfig) + frr_vrf_config = self.getFRRconfig(f'router bgp {ASN} vrf {vrf}') + self.assertIn(f'router bgp {ASN} vrf {vrf}', frr_vrf_config) + self.assertIn(f' bgp router-id {router_id}', frr_vrf_config) - # CCC: Currently this is not working as FRR() class does not support + # XXX: Currently this is not working as FRR() class does not support # route-maps for multiple vrfs because the modify_section() only works # on lines and not text blocks. # @@ -694,13 +710,27 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' neighbor {interface} activate', frrconfig) self.assertIn(f' exit-address-family', frrconfig) - def test_bgp_13_solo(self): + + def test_bgp_13_vpn(self): remote_asn = str(int(ASN) + 150) neighbor = '192.0.2.55' + vrf_name = 'red' + label = 'auto' + rd = f'{neighbor}:{ASN}' + rt_export = f'{neighbor}:1002 1.2.3.4:567' + rt_import = f'{neighbor}:1003 500:100' 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, 'solo']) + # testing only one AFI is sufficient as it's generic code + for afi in ['ipv4-unicast', 'ipv6-unicast']: + self.cli_set(base_path + ['address-family', afi, 'export', 'vpn']) + self.cli_set(base_path + ['address-family', afi, 'import', 'vpn']) + self.cli_set(base_path + ['address-family', afi, 'label', 'vpn', 'export', label]) + self.cli_set(base_path + ['address-family', afi, 'rd', 'vpn', 'export', rd]) + self.cli_set(base_path + ['address-family', afi, 'route-map', 'vpn', 'export', route_map_out]) + self.cli_set(base_path + ['address-family', afi, 'route-map', 'vpn', 'import', route_map_in]) + self.cli_set(base_path + ['address-family', afi, 'route-target', 'vpn', 'export', rt_export]) + self.cli_set(base_path + ['address-family', afi, 'route-target', 'vpn', 'import', rt_import]) # commit changes self.cli_commit() @@ -708,7 +738,19 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): # Verify FRR bgpd configuration frrconfig = self.getFRRconfig(f'router bgp {ASN}') self.assertIn(f'router bgp {ASN}', frrconfig) - self.assertIn(f' neighbor {neighbor} solo', frrconfig) + + for afi in ['ipv4', 'ipv6']: + afi_config = self.getFRRconfig(f' address-family {afi} unicast', endsection='exit-address-family', daemon='bgpd') + self.assertIn(f'address-family {afi} unicast', afi_config) + self.assertIn(f' export vpn', afi_config) + self.assertIn(f' import vpn', afi_config) + self.assertIn(f' label vpn export {label}', afi_config) + self.assertIn(f' rd vpn export {rd}', afi_config) + self.assertIn(f' route-map vpn export {route_map_out}', afi_config) + self.assertIn(f' route-map vpn import {route_map_in}', afi_config) + self.assertIn(f' rt vpn export {rt_export}', afi_config) + self.assertIn(f' rt vpn import {rt_import}', afi_config) + self.assertIn(f' exit-address-family', afi_config) if __name__ == '__main__': unittest.main(verbosity=2)
\ No newline at end of file diff --git a/smoketest/scripts/cli/test_protocols_isis.py b/smoketest/scripts/cli/test_protocols_isis.py index 9b6d4a4ec..8170f2b56 100755 --- a/smoketest/scripts/cli/test_protocols_isis.py +++ b/smoketest/scripts/cli/test_protocols_isis.py @@ -199,5 +199,58 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): self.assertIn(f' area-password clear {password}', tmp) + def test_isis_06_spf_delay(self): + self.isis_base_config() + + network = 'point-to-point' + holddown = '10' + init_delay = '50' + long_delay = '200' + short_delay = '100' + time_to_learn = '75' + + for interface in self._interfaces: + self.cli_set(base_path + ['interface', interface, 'network', network]) + + self.cli_set(base_path + ['spf-delay-ietf', 'holddown', holddown]) + # verify() - All types of spf-delay must be configured + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + ['spf-delay-ietf', 'init-delay', init_delay]) + # verify() - All types of spf-delay must be configured + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + ['spf-delay-ietf', 'long-delay', long_delay]) + # verify() - All types of spf-delay must be configured + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + ['spf-delay-ietf', 'long-delay', long_delay]) + # verify() - All types of spf-delay must be configured + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + ['spf-delay-ietf', 'short-delay', short_delay]) + # verify() - All types of spf-delay must be configured + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['spf-delay-ietf', 'time-to-learn', time_to_learn]) + + # Commit all changes + self.cli_commit() + + # Verify all changes + tmp = self.getFRRconfig(f'router isis {domain}') + self.assertIn(f' net {net}', tmp) + self.assertIn(f' spf-delay-ietf init-delay {init_delay} short-delay {short_delay} long-delay {long_delay} holddown {holddown} time-to-learn {time_to_learn}', tmp) + + for interface in self._interfaces: + tmp = self.getFRRconfig(f'interface {interface}') + self.assertIn(f' ip router isis {domain}', tmp) + self.assertIn(f' ipv6 router isis {domain}', tmp) + self.assertIn(f' isis network {network}', tmp) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py index 623d40497..0529eefbd 100755 --- a/smoketest/scripts/cli/test_protocols_ospf.py +++ b/smoketest/scripts/cli/test_protocols_ospf.py @@ -14,10 +14,13 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +import logging +import sys import unittest from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section from vyos.util import process_named_running from vyos.util import cmd @@ -27,6 +30,8 @@ base_path = ['protocols', 'ospf'] route_map = 'foo-bar-baz10' +log = logging.getLogger('TestProtocolsOSPF') + class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): def setUp(self): self.cli_set(['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit']) @@ -202,10 +207,11 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): for interface in interfaces: self.assertIn(f' no passive-interface {interface}', frrconfig) # default except: - tmp1 = cmd('sudo dmesg') - tmp2 = cmd('tail -n 250 /var/log/messages') - tmp3 = cmd('vtysh -c "show run"') - self.fail(f'Now we can hopefully see why OSPF fails:\n{tmp1}\n\n{tmp2}\n\n{tmp3}') + log.debug(frrconfig) + log.debug(cmd('sudo dmesg')) + log.debug(cmd('sudo cat /var/log/messages')) + log.debug(cmd('vtysh -c "show run"')) + self.fail('Now we can hopefully see why OSPF fails!') def test_ospf_08_redistribute(self): metric = '15' @@ -215,21 +221,22 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): for protocol in redistribute: self.cli_set(base_path + ['redistribute', protocol, 'metric', metric]) self.cli_set(base_path + ['redistribute', protocol, 'route-map', route_map]) - if protocol not in ['kernel', 'static']: - self.cli_set(base_path + ['redistribute', protocol, 'metric-type', metric_type]) + self.cli_set(base_path + ['redistribute', protocol, 'metric-type', metric_type]) # commit changes self.cli_commit() # Verify FRR ospfd configuration frrconfig = self.getFRRconfig('router ospf') - self.assertIn(f'router ospf', frrconfig) - for protocol in redistribute: - if protocol in ['kernel', 'static']: - self.assertIn(f' redistribute {protocol} metric {metric} route-map {route_map}', frrconfig) - else: + try: + self.assertIn(f'router ospf', frrconfig) + for protocol in redistribute: self.assertIn(f' redistribute {protocol} metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) - + except: + log.debug(frrconfig) + log.debug(cmd('sudo cat /var/log/messages')) + log.debug(cmd('vtysh -c "show run"')) + self.fail('Now we can hopefully see why OSPF fails!') def test_ospf_09_virtual_link(self): networks = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'] @@ -261,7 +268,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.assertIn(f' network {network} area {area}', frrconfig) - def test_ospf_10_interface_configureation(self): + def test_ospf_10_interface_configuration(self): interfaces = Section.interfaces('ethernet') password = 'vyos1234' bandwidth = '10000' @@ -344,5 +351,30 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): frrconfig = self.getFRRconfig(zebra_route_map) self.assertNotIn(zebra_route_map, frrconfig) + def test_ospf_13_interface_area(self): + area = '0' + interfaces = Section.interfaces('ethernet') + + self.cli_set(base_path + ['area', area, 'network', '10.0.0.0/8']) + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, 'area', area]) + + # we can not have bot area network and interface area set + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['area', area, 'network']) + + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf') + self.assertIn(f'router ospf', frrconfig) + + for interface in interfaces: + config = self.getFRRconfig(f'interface {interface}') + self.assertIn(f'interface {interface}', config) + self.assertIn(f' ip ospf area {area}', config) + if __name__ == '__main__': + logging.basicConfig(stream=sys.stderr, level=logging.DEBUG) 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 815bd333a..301f8fa31 100755 --- a/smoketest/scripts/cli/test_service_dhcp-server.py +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-2021 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -37,12 +37,19 @@ dns_2 = inc_ip(subnet, 3) domain_name = 'vyos.net' class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): - def setUp(self): + @classmethod + def setUpClass(cls): + super(cls, cls).setUpClass() + cidr_mask = subnet.split('/')[-1] - self.cli_set(['interfaces', 'dummy', 'dum8765', 'address', f'{router}/{cidr_mask}']) + cls.cli_set(cls, ['interfaces', 'dummy', 'dum8765', 'address', f'{router}/{cidr_mask}']) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['interfaces', 'dummy', 'dum8765']) + super(cls, cls).tearDownClass() def tearDown(self): - self.cli_delete(['interfaces', 'dummy', 'dum8765']) self.cli_delete(base_path) self.cli_commit() @@ -59,9 +66,10 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] # we use the first subnet IP address as default gateway self.cli_set(pool + ['default-router', router]) - self.cli_set(pool + ['dns-server', dns_1]) - self.cli_set(pool + ['dns-server', dns_2]) + self.cli_set(pool + ['name-server', dns_1]) + self.cli_set(pool + ['name-server', dns_2]) self.cli_set(pool + ['domain-name', domain_name]) + self.cli_set(pool + ['ping-check']) # check validate() - No DHCP address range or active static-mapping set with self.assertRaises(ConfigSessionError): @@ -84,6 +92,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): self.assertIn(f'option domain-name "{domain_name}";', config) self.assertIn(f'default-lease-time 86400;', config) self.assertIn(f'max-lease-time 86400;', config) + self.assertIn(f'ping-check true;', config) self.assertIn(f'range {range_0_start} {range_0_stop};', config) self.assertIn(f'range {range_1_start} {range_1_stop};', config) self.assertIn(f'set shared-networkname = "{shared_net_name}";', config) @@ -108,8 +117,8 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] # we use the first subnet IP address as default gateway self.cli_set(pool + ['default-router', router]) - self.cli_set(pool + ['dns-server', dns_1]) - self.cli_set(pool + ['dns-server', dns_2]) + self.cli_set(pool + ['name-server', dns_1]) + self.cli_set(pool + ['name-server', dns_2]) self.cli_set(pool + ['domain-name', domain_name]) self.cli_set(pool + ['ip-forwarding']) self.cli_set(pool + ['smtp-server', smtp_server]) @@ -123,8 +132,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): self.cli_set(pool + ['wpad-url', wpad]) self.cli_set(pool + ['server-identifier', server_identifier]) - self.cli_set(pool + ['static-route', 'destination-subnet', '10.0.0.0/24']) - self.cli_set(pool + ['static-route', 'router', '192.0.2.1']) + self.cli_set(pool + ['static-route', '10.0.0.0/24', 'next-hop', '192.0.2.1']) # check validate() - No DHCP address range or active static-mapping set with self.assertRaises(ConfigSessionError): @@ -132,34 +140,11 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): self.cli_set(pool + ['range', '0', 'start', range_0_start]) self.cli_set(pool + ['range', '0', 'stop', range_0_stop]) - # failover - failover_local = router - failover_remote = inc_ip(router, 1) - - self.cli_set(pool + ['failover', 'local-address', failover_local]) - self.cli_set(pool + ['failover', 'name', shared_net_name]) - self.cli_set(pool + ['failover', 'peer-address', failover_remote]) - self.cli_set(pool + ['failover', 'status', 'primary']) - # commit changes self.cli_commit() config = read_file(DHCPD_CONF) - self.assertIn(f'failover peer "{shared_net_name}"' + r' {', config) - self.assertIn(f'primary;', config) - self.assertIn(f'mclt 1800;', config) - self.assertIn(f'mclt 1800;', config) - self.assertIn(f'split 128;', config) - self.assertIn(f'port 520;', config) - self.assertIn(f'peer port 520;', config) - self.assertIn(f'max-response-delay 30;', config) - self.assertIn(f'max-unacked-updates 10;', config) - self.assertIn(f'load balance max seconds 3;', config) - self.assertIn(f'peer port 520;', config) - self.assertIn(f'address {failover_local};', config) - self.assertIn(f'peer address {failover_remote};', config) - network = address_from_cidr(subnet) netmask = netmask_from_cidr(subnet) self.assertIn(f'ddns-update-style none;', config) @@ -185,8 +170,6 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): self.assertIn(f'max-lease-time 86400;', config) self.assertIn(f'range {range_0_start} {range_0_stop};', config) self.assertIn(f'set shared-networkname = "{shared_net_name}";', config) - self.assertIn(f'failover peer "{shared_net_name}";', config) - self.assertIn(f'deny dynamic bootp clients;', config) # weird syntax for those static routes self.assertIn(f'option rfc3442-static-route 24,10,0,0,192,0,2,1, 0,192,0,2,1;', config) @@ -202,8 +185,8 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] # we use the first subnet IP address as default gateway self.cli_set(pool + ['default-router', router]) - self.cli_set(pool + ['dns-server', dns_1]) - self.cli_set(pool + ['dns-server', dns_2]) + self.cli_set(pool + ['name-server', dns_1]) + self.cli_set(pool + ['name-server', dns_2]) self.cli_set(pool + ['domain-name', domain_name]) # check validate() - No DHCP address range or active static-mapping set @@ -262,7 +245,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] # we use the first subnet IP address as default gateway self.cli_set(pool + ['default-router', router]) - self.cli_set(pool + ['dns-server', dns_1]) + self.cli_set(pool + ['name-server', dns_1]) self.cli_set(pool + ['domain-name', domain_name]) self.cli_set(pool + ['lease', lease_time]) @@ -438,5 +421,68 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) + def test_dhcp_failover(self): + shared_net_name = 'FAILOVER' + failover_name = 'VyOS-Failover' + + range_0_start = inc_ip(subnet, 10) + range_0_stop = inc_ip(subnet, 20) + + pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] + # we use the first subnet IP address as default gateway + self.cli_set(pool + ['default-router', router]) + + # check validate() - No DHCP address range or active static-mapping set + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(pool + ['range', '0', 'start', range_0_start]) + self.cli_set(pool + ['range', '0', 'stop', range_0_stop]) + + # failover + failover_local = router + failover_remote = inc_ip(router, 1) + + self.cli_set(base_path + ['failover', 'source-address', failover_local]) + self.cli_set(base_path + ['failover', 'name', failover_name]) + self.cli_set(base_path + ['failover', 'remote', failover_remote]) + self.cli_set(base_path + ['failover', 'status', 'primary']) + + # check validate() - failover needs to be enabled for at least one subnet + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(pool + ['enable-failover']) + + # commit changes + self.cli_commit() + + config = read_file(DHCPD_CONF) + + self.assertIn(f'failover peer "{failover_name}"' + r' {', config) + self.assertIn(f'primary;', config) + self.assertIn(f'mclt 1800;', config) + self.assertIn(f'mclt 1800;', config) + self.assertIn(f'split 128;', config) + self.assertIn(f'port 520;', config) + self.assertIn(f'peer port 520;', config) + self.assertIn(f'max-response-delay 30;', config) + self.assertIn(f'max-unacked-updates 10;', config) + self.assertIn(f'load balance max seconds 3;', config) + self.assertIn(f'peer port 520;', config) + self.assertIn(f'address {failover_local};', config) + self.assertIn(f'peer address {failover_remote};', config) + + network = address_from_cidr(subnet) + netmask = netmask_from_cidr(subnet) + self.assertIn(f'ddns-update-style none;', config) + self.assertIn(f'subnet {network} netmask {netmask}' + r' {', config) + self.assertIn(f'option routers {router};', config) + self.assertIn(f'range {range_0_start} {range_0_stop};', config) + self.assertIn(f'set shared-networkname = "{shared_net_name}";', config) + self.assertIn(f'failover peer "{failover_name}";', config) + self.assertIn(f'deny dynamic bootp clients;', config) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_ssh.py b/smoketest/scripts/cli/test_service_ssh.py index c76f709b1..ded4d8301 100755 --- a/smoketest/scripts/cli/test_service_ssh.py +++ b/smoketest/scripts/cli/test_service_ssh.py @@ -41,10 +41,13 @@ def get_config_value(key): return tmp class TestServiceSSH(VyOSUnitTestSHIM.TestCase): - def setUp(self): + @classmethod + def setUpClass(cls): + super(cls, cls).setUpClass() + # ensure we can also run this test on a live system - so lets clean # out the current configuration :) - self.cli_delete(base_path) + cls.cli_delete(cls, base_path) def tearDown(self): # delete testing SSH config diff --git a/smoketest/scripts/cli/test_service_webproxy.py b/smoketest/scripts/cli/test_service_webproxy.py index d47bd452d..6780a93f9 100755 --- a/smoketest/scripts/cli/test_service_webproxy.py +++ b/smoketest/scripts/cli/test_service_webproxy.py @@ -52,8 +52,6 @@ class TestServiceWebProxy(VyOSUnitTestSHIM.TestCase): self.assertIn(f'access_log /var/log/squid/access.log squid', config) # ACL verification - self.assertIn(f'acl localhost src 127.0.0.1/32', config) - self.assertIn(f'acl to_localhost dst 127.0.0.0/8', config) self.assertIn(f'acl net src all', config) self.assertIn(f'acl SSL_ports port 443', config) @@ -234,8 +232,8 @@ class TestServiceWebProxy(VyOSUnitTestSHIM.TestCase): config = read_file(PROXY_CONF) self.assertIn(f'http_port {listen_ip}:3128 intercept', config) - self.assertIn(f'redirect_program /usr/bin/squidGuard -c /etc/squidguard/squidGuard.conf', config) - self.assertIn(f'redirect_children 8', config) + self.assertIn(f'url_rewrite_program /usr/bin/squidGuard -c /etc/squidguard/squidGuard.conf', config) + self.assertIn(f'url_rewrite_children 8', config) # Check SquidGuard config sg_config = read_file('/etc/squidguard/squidGuard.conf') diff --git a/smoketest/scripts/cli/test_system_conntrack.py b/smoketest/scripts/cli/test_system_conntrack.py index 21d626d2f..a2380981b 100755 --- a/smoketest/scripts/cli/test_system_conntrack.py +++ b/smoketest/scripts/cli/test_system_conntrack.py @@ -147,8 +147,8 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase): self.assertEqual(get_sysctl(f'{parameter}'), parameter_config['default_value']) - def test_conntrack_module_disable(self): - # Some features are disabled by onloading the kernel helper module(s) + def test_conntrack_module_enable(self): + # conntrack helper modules are disabled by default modules = { 'ftp' : { 'driver' : ['nf_nat_ftp', 'nf_conntrack_ftp'], @@ -176,38 +176,39 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase): }, } + # load modules for module in modules: - self.cli_set(base_path + ['modules', module, 'disable']) + self.cli_set(base_path + ['modules', module]) # commit changes self.cli_commit() - # verify modules are no longer loaded on the system + # verify modules are loaded on the system for module, module_options in modules.items(): if 'driver' in module_options: for driver in module_options['driver']: - self.assertFalse(os.path.isdir(f'/sys/module/{driver}')) + self.assertTrue(os.path.isdir(f'/sys/module/{driver}')) if 'iptables' in module_options: rules = cmd('sudo iptables-save -t raw') for ruleset in module_options['iptables']: - self.assertNotIn(ruleset, rules) + self.assertIn(ruleset, rules) - # reload modules + # unload modules for module in modules: - self.cli_delete(base_path + ['modules', module, 'disable']) + self.cli_delete(base_path + ['modules', module]) # commit changes self.cli_commit() - # verify modules are again loaded on the system + # verify modules are not loaded on the system for module, module_options in modules.items(): if 'driver' in module_options: for driver in module_options['driver']: - self.assertTrue(os.path.isdir(f'/sys/module/{driver}')) + self.assertFalse(os.path.isdir(f'/sys/module/{driver}')) if 'iptables' in module_options: rules = cmd('sudo iptables-save -t raw') for ruleset in module_options['iptables']: - self.assertIn(ruleset, rules) + self.assertNotIn(ruleset, rules) def test_conntrack_hash_size(self): hash_size = '65536' diff --git a/smoketest/scripts/cli/test_system_login.py b/smoketest/scripts/cli/test_system_login.py index 8327235fb..0addd630e 100755 --- a/smoketest/scripts/cli/test_system_login.py +++ b/smoketest/scripts/cli/test_system_login.py @@ -31,7 +31,19 @@ from vyos.util import read_file from vyos.template import inc_ip base_path = ['system', 'login'] -users = ['vyos1', 'vyos2'] +users = ['vyos1', 'vyos-roxx123', 'VyOS-123_super.Nice'] + +ssh_pubkey = """ +AAAAB3NzaC1yc2EAAAADAQABAAABgQD0NuhUOEtMIKnUVFIHoFatqX/c4mjerXyF +TlXYfVt6Ls2NZZsUSwHbnhK4BKDrPvVZMW/LycjQPzWW6TGtk6UbZP1WqdviQ9hP +jsEeKJSTKciMSvQpjBWyEQQPXSKYQC7ryQQilZDqnJgzqwzejKEe+nhhOdBvjuZc +uukxjT69E0UmWAwLxzvfiurwiQaC7tG+PwqvtfHOPL3i6yRO2C5ORpFarx8PeGDS +IfIXJCr3LoUbLHeuE7T2KaOKQcX0UsWJ4CoCapRLpTVYPDB32BYfgq7cW1Sal1re +EGH2PzuXBklinTBgCHA87lHjpwDIAqdmvMj7SXIW9LxazLtP+e37sexE7xEs0cpN +l68txdDbY2P2Kbz5mqGFfCvBYKv9V2clM5vyWNy/Xp5TsCis89nn83KJmgFS7sMx +pHJz8umqkxy3hfw0K7BRFtjWd63sbOP8Q/SDV7LPaIfIxenA9zv2rY7y+AIqTmSr +TTSb0X1zPGxPIRFy5GoGtO9Mm5h4OZk= +""" class TestSystemLogin(VyOSUnitTestSHIM.TestCase): def tearDown(self): @@ -42,6 +54,8 @@ class TestSystemLogin(VyOSUnitTestSHIM.TestCase): self.cli_commit() def test_add_linux_system_user(self): + # We are not allowed to re-use a username already taken by the Linux + # base system system_user = 'backup' self.cli_set(base_path + ['user', system_user, 'authentication', 'plaintext-password', system_user]) @@ -75,9 +89,30 @@ class TestSystemLogin(VyOSUnitTestSHIM.TestCase): (stdout, stderr) = proc.communicate() # stdout is something like this: - # b'Linux vyos 4.19.101-amd64-vyos #1 SMP Sun Feb 2 10:18:07 UTC 2020 x86_64 GNU/Linux\n' + # b'Linux LR1.wue3 5.10.61-amd64-vyos #1 SMP Fri Aug 27 08:55:46 UTC 2021 x86_64 GNU/Linux\n' self.assertTrue(len(stdout) > 40) + def test_system_user_ssh_key(self): + ssh_user = 'ssh-test_user' + public_keys = 'vyos_test@domain-foo.com' + type = 'ssh-rsa' + + self.cli_set(base_path + ['user', ssh_user, 'authentication', 'public-keys', public_keys, 'key', ssh_pubkey.replace('\n','')]) + + # check validate() - missing type for public-key + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['user', ssh_user, 'authentication', 'public-keys', public_keys, 'type', type]) + + self.cli_commit() + + # Check that SSH key was written properly + tmp = cmd(f'sudo cat /home/{ssh_user}/.ssh/authorized_keys') + key = f'{type} ' + ssh_pubkey.replace('\n','') + self.assertIn(key, tmp) + + self.cli_delete(base_path + ['user', ssh_user]) + def test_radius_kernel_features(self): # T2886: RADIUS requires some Kernel options to be present kernel = platform.release() @@ -201,4 +236,4 @@ class TestSystemLogin(VyOSUnitTestSHIM.TestCase): self.assertTrue(tmp) if __name__ == '__main__': - unittest.main(verbosity=2) + unittest.main(verbosity=2, failfast=True) diff --git a/smoketest/scripts/cli/test_vpn_ipsec.py b/smoketest/scripts/cli/test_vpn_ipsec.py index a34387dc9..93569c4ec 100755 --- a/smoketest/scripts/cli/test_vpn_ipsec.py +++ b/smoketest/scripts/cli/test_vpn_ipsec.py @@ -126,9 +126,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): self.assertTrue(process_named_running('charon')) self.cli_delete(base_path) - self.cli_delete(nhrp_path) self.cli_delete(tunnel_path) - self.cli_delete(vti_path) self.cli_delete(ethernet_path) self.cli_commit() @@ -182,8 +180,10 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): swanctl_conf_lines = [ f'version = 2', f'auth = psk', + f'rekey_time = 28800s', # default value f'proposals = aes128-sha1-modp1024', f'esp_proposals = aes128-sha1-modp1024', + f'life_time = 3600s', # default value f'local_addrs = {local_address} # dhcp:no', f'remote_addrs = {peer_ip}', f'mode = tunnel', @@ -226,6 +226,11 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): self.cli_commit() swanctl_conf = read_file(swanctl_file) + if_id = vti.lstrip('vti') + # The key defaults to 0 and will match any policies which similarly do + # not have a lookup key configuration - thus we shift the key by one + # to also support a vti0 interface + if_id = str(int(if_id) +1) swanctl_conf_lines = [ f'version = 2', f'auth = psk', @@ -236,9 +241,9 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): f'mode = tunnel', f'local_ts = 172.16.10.0/24,172.16.11.0/24', f'remote_ts = 172.17.10.0/24,172.17.11.0/24', - f'if_id_in = {vti.lstrip("vti")}', # will be 10 for vti10 - f'if_id_out = {vti.lstrip("vti")}', - f'updown = "/etc/ipsec.d/vti-up-down {vti} no"' + f'if_id_in = {if_id}', # will be 11 for vti10 - shifted by one + f'if_id_out = {if_id}', + f'updown = "/etc/ipsec.d/vti-up-down {vti}"' ] for line in swanctl_conf_lines: self.assertIn(line, swanctl_conf) @@ -255,6 +260,8 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): def test_04_dmvpn(self): tunnel_if = 'tun100' nhrp_secret = 'secret' + ike_lifetime = '3600' + esp_lifetime = '1800' # Tunnel self.cli_set(tunnel_path + [tunnel_if, 'address', '172.16.253.134/29']) @@ -272,7 +279,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): # IKE/ESP Groups self.cli_set(base_path + ['esp-group', esp_group, 'compression', 'disable']) - self.cli_set(base_path + ['esp-group', esp_group, 'lifetime', '1800']) + self.cli_set(base_path + ['esp-group', esp_group, 'lifetime', esp_lifetime]) self.cli_set(base_path + ['esp-group', esp_group, 'mode', 'transport']) self.cli_set(base_path + ['esp-group', esp_group, 'pfs', 'dh-group2']) self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'encryption', 'aes256']) @@ -282,7 +289,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['ike-group', ike_group, 'ikev2-reauth', 'no']) self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev1']) - self.cli_set(base_path + ['ike-group', ike_group, 'lifetime', '3600']) + self.cli_set(base_path + ['ike-group', ike_group, 'lifetime', ike_lifetime]) self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'dh-group', '2']) self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'encryption', 'aes256']) self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'hash', 'sha1']) @@ -300,7 +307,8 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): swanctl_lines = [ f'proposals = aes128-sha1-modp1024,aes256-sha1-modp1024', f'version = 1', - f'rekey_time = 3600s', + f'life_time = {ike_lifetime}s', + f'rekey_time = {esp_lifetime}s', f'esp_proposals = aes128-sha1-modp1024,aes256-sha1-modp1024,3des-md5-modp1024', f'local_ts = dynamic[gre]', f'remote_ts = dynamic[gre]', @@ -310,6 +318,9 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): for line in swanctl_lines: self.assertIn(line, swanctl_conf) + # There is only one NHRP test so no need to delete this globally in tearDown() + self.cli_delete(nhrp_path) + def test_05_x509_site2site(self): # Enable PKI peer_name = 'peer1' @@ -341,6 +352,11 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): swanctl_conf = read_file(swanctl_file) tmp = peer_ip.replace('.', '-') + if_id = vti.lstrip('vti') + # The key defaults to 0 and will match any policies which similarly do + # not have a lookup key configuration - thus we shift the key by one + # to also support a vti0 interface + if_id = str(int(if_id) +1) swanctl_lines = [ f'peer_{tmp}', f'version = 0', # key-exchange not set - defaulting to 0 for ikev1 and ikev2 @@ -356,9 +372,9 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): f'remote_addrs = {peer_ip}', f'local_ts = 0.0.0.0/0,::/0', f'remote_ts = 0.0.0.0/0,::/0', - f'updown = "/etc/ipsec.d/vti-up-down {vti} no"', - f'if_id_in = {vti.lstrip("vti")}', # will be 10 for vti10 - f'if_id_out = {vti.lstrip("vti")}', + f'updown = "/etc/ipsec.d/vti-up-down {vti}"', + f'if_id_in = {if_id}', # will be 11 for vti10 + f'if_id_out = {if_id}', f'ipcomp = no', f'mode = tunnel', f'start_action = start', @@ -373,5 +389,8 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): for line in swanctl_secrets_lines: self.assertIn(line, swanctl_conf) + # There is only one VTI test so no need to delete this globally in tearDown() + self.cli_delete(vti_path) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/conntrack.py b/src/conf_mode/conntrack.py index 4e6e39c0f..68877f794 100755 --- a/src/conf_mode/conntrack.py +++ b/src/conf_mode/conntrack.py @@ -97,7 +97,7 @@ def apply(conntrack): # Depending on the enable/disable state of the ALG (Application Layer Gateway) # modules we need to either insmod or rmmod the helpers. for module, module_config in module_map.items(): - if dict_search(f'modules.{module}.disable', conntrack) != None: + if dict_search(f'modules.{module}', conntrack) is None: if 'ko' in module_config: for mod in module_config['ko']: # Only remove the module if it's loaded @@ -105,8 +105,9 @@ def apply(conntrack): cmd(f'rmmod {mod}') if 'iptables' in module_config: for rule in module_config['iptables']: - print(f'iptables --delete {rule}') - cmd(f'iptables --delete {rule}') + # Only install iptables rule if it does not exist + tmp = run(f'iptables --check {rule}') + if tmp == 0: cmd(f'iptables --delete {rule}') else: if 'ko' in module_config: for mod in module_config['ko']: @@ -115,9 +116,7 @@ def apply(conntrack): for rule in module_config['iptables']: # Only install iptables rule if it does not exist tmp = run(f'iptables --check {rule}') - if tmp > 0: - cmd(f'iptables --insert {rule}') - + if tmp > 0: cmd(f'iptables --insert {rule}') if process_named_running('conntrackd'): # Reload conntrack-sync daemon to fetch new sysctl values diff --git a/src/conf_mode/containers.py b/src/conf_mode/containers.py index 21b47f42a..1e0197a13 100755 --- a/src/conf_mode/containers.py +++ b/src/conf_mode/containers.py @@ -19,15 +19,23 @@ import json from ipaddress import ip_address from ipaddress import ip_network +from time import sleep +from json import dumps as json_write from vyos.config import Config from vyos.configdict import dict_merge from vyos.configdict import node_changed +from vyos.util import call from vyos.util import cmd -from vyos.util import popen -from vyos.template import render +from vyos.util import run +from vyos.util import read_file +from vyos.util import write_file +from vyos.util import is_systemd_service_active +from vyos.util import is_systemd_service_running +from vyos.template import inc_ip from vyos.template import is_ipv4 from vyos.template import is_ipv6 +from vyos.template import render from vyos.xml import defaults from vyos import ConfigError from vyos import airbag @@ -41,27 +49,7 @@ def _cmd(command): print(command) return cmd(command) -# Container management functions -def container_exists(name): - ''' - https://docs.podman.io/en/latest/_static/api.html#operation/ContainerExistsLibpod - Check if container exists. Response codes. - 204 - container exists - 404 - no such container - ''' - tmp = _cmd(f"curl --unix-socket /run/podman/podman.sock 'http://d/v3.0.0/libpod/containers/{name}/exists'") - # If container exists it return status code "0" - code can not be displayed - return (tmp == "") - -def container_status(name): - ''' - https://docs.podman.io/en/latest/_static/api.html#operation/ContainerInspectLibpod - ''' - tmp = _cmd(f"curl --unix-socket /run/podman/podman.sock 'http://d/v3.0.0/libpod/containers/{name}/json'") - data = json.loads(tmp) - return data['State']['Status'] - -def ctnr_network_exists(name): +def network_exists(name): # Check explicit name for network, returns True if network exists c = _cmd(f'podman network ls --quiet --filter name=^{name}$') return bool(c) @@ -79,11 +67,20 @@ def get_config(config=None): # 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) + # container base default values can not be merged here - remove and add them later + if 'name' in default_values: + del default_values['name'] container = dict_merge(default_values, container) + # Merge per-container default values + if 'name' in container: + default_values = defaults(base + ['name']) + for name in container['name']: + container['name'][name] = dict_merge(default_values, container['name'][name]) + # Delete container network, delete containers tmp = node_changed(conf, ['container', 'network']) - if tmp: container.update({'net_remove' : tmp}) + if tmp: container.update({'network_remove' : tmp}) tmp = node_changed(conf, ['container', 'name']) if tmp: container.update({'container_remove' : tmp}) @@ -102,7 +99,6 @@ def verify(container): if len(container_config['network']) > 1: raise ConfigError(f'Only one network can be specified for container "{name}"!') - # Check if the specified container network exists network_name = list(container_config['network'])[0] if network_name not in container['network']: @@ -125,8 +121,25 @@ def verify(container): # We can not use the first IP address of a network prefix as this is used by podman if ip_address(address) == ip_network(network)[1]: - raise ConfigError(f'Address "{address}" reserved for the container engine!') + raise ConfigError(f'IP address "{address}" can not be used for a container, '\ + 'reserved for the container engine!') + if 'environment' in container_config: + for var, cfg in container_config['environment'].items(): + if 'value' not in cfg: + raise ConfigError(f'Environment variable {var} has no value assigned!') + + if 'volume' in container_config: + for volume, volume_config in container_config['volume'].items(): + if 'source' not in volume_config: + raise ConfigError(f'Volume "{volume}" has no source path configured!') + + if 'destination' not in volume_config: + raise ConfigError(f'Volume "{volume}" has no destination path configured!') + + source = volume_config['source'] + if not os.path.exists(source): + raise ConfigError(f'Volume "{volume}" source path "{source}" does not exist!') # Container image is a mandatory option if 'image' not in container_config: @@ -142,9 +155,9 @@ def verify(container): # Add new network if 'network' in container: - v4_prefix = 0 - v6_prefix = 0 for network, network_config in container['network'].items(): + v4_prefix = 0 + v6_prefix = 0 # If ipv4-prefix not defined for user-defined network if 'prefix' not in network_config: raise ConfigError(f'prefix for network "{net}" must be defined!') @@ -160,8 +173,8 @@ def verify(container): # A network attached to a container can not be deleted - if {'net_remove', 'name'} <= set(container): - for network in container['net_remove']: + if {'network_remove', 'name'} <= set(container): + for network in container['network_remove']: for container, container_config in container['name'].items(): if 'network' in container_config and network in container_config['network']: raise ConfigError(f'Can not remove network "{network}", used by container "{container}"!') @@ -173,6 +186,37 @@ def generate(container): if not container: return None + if 'network' in container: + for network, network_config in container['network'].items(): + tmp = { + 'cniVersion' : '0.4.0', + 'name' : network, + 'plugins' : [{ + 'type': 'bridge', + 'bridge': f'cni-{network}', + 'isGateway': True, + 'ipMasq': False, + 'hairpinMode': False, + 'ipam' : { + 'type': 'host-local', + 'routes': [], + 'ranges' : [], + }, + }] + } + + for prefix in network_config['prefix']: + net = [{'gateway' : inc_ip(prefix, 1), 'subnet' : prefix}] + tmp['plugins'][0]['ipam']['ranges'].append(net) + + # install per address-family default orutes + default_route = '0.0.0.0/0' + if is_ipv6(prefix): + default_route = '::/0' + tmp['plugins'][0]['ipam']['routes'].append({'dst': default_route}) + + write_file(f'/etc/cni/net.d/{network}.conflist', json_write(tmp, indent=2)) + render(config_containers_registry, 'containers/registry.tmpl', container) render(config_containers_storage, 'containers/storage.tmpl', container) @@ -183,79 +227,90 @@ def apply(container): # Option "--force" allows to delete containers with any status if 'container_remove' in container: for name in container['container_remove']: - if container_status(name) == 'running': - _cmd(f'podman stop {name}') - _cmd(f'podman rm --force {name}') + call(f'podman stop {name}') + call(f'podman rm --force {name}') # Delete old networks if needed - if 'net_remove' in container: - for network in container['net_remove']: - _cmd(f'podman network rm {network}') - - # Add network - if 'network' in container: - for network, network_config in container['network'].items(): - # Check if the network has already been created - if not ctnr_network_exists(network) and 'prefix' in network_config: - tmp = f'podman network create {network}' - # we can not use list comprehension here as the --ipv6 option - # must immediately follow the specified subnet!!! - for prefix in sorted(network_config['prefix']): - tmp += f' --subnet={prefix}' - if is_ipv6(prefix): - tmp += ' --ipv6' - _cmd(tmp) + if 'network_remove' in container: + for network in container['network_remove']: + tmp = f'/etc/cni/net.d/{network}.conflist' + if os.path.exists(tmp): + os.unlink(tmp) + + service_name = 'podman.service' + if 'network' in container or 'name' in container: + # Start podman if it's required and not yet running + if not is_systemd_service_active(service_name): + _cmd(f'systemctl start {service_name}') + # Wait for podman to be running + while not is_systemd_service_running(service_name): + sleep(0.250) + else: + _cmd(f'systemctl stop {service_name}') # Add container if 'name' in container: for name, container_config in container['name'].items(): - # Check if the container has already been created - if not container_exists(name): - image = container_config['image'] - # Currently the best way to run a command and immediately print stdout - print(os.system(f'podman pull {image}')) - - # Check/set environment options "-e foo=bar" - env_opt = '' - if 'environment' in container_config: - env_opt = '-e ' - env_opt += " -e ".join(f"{k}={v['value']}" for k, v in container_config['environment'].items()) - - # Publish ports - port = '' - if 'port' in container_config: - protocol = '' - for portmap in container_config['port']: - if 'protocol' in container_config['port'][portmap]: - protocol = container_config['port'][portmap]['protocol'] - protocol = f'/{protocol}' - else: - protocol = '/tcp' - sport = container_config['port'][portmap]['source'] - dport = container_config['port'][portmap]['destination'] - port += f' -p {sport}:{dport}{protocol}' - - # Bind volume - volume = '' - if 'volume' in container_config: - for vol in container_config['volume']: - svol = container_config['volume'][vol]['source'] - dvol = container_config['volume'][vol]['destination'] - volume += f' -v {svol}:{dvol}' - - if 'allow_host_networks' in container_config: - _cmd(f'podman run -dit --name {name} --net host {port} {volume} {env_opt} {image}') - else: - for network in container_config['network']: - ipparam = '' - if 'address' in container_config['network'][network]: - ipparam = '--ip ' + container_config['network'][network]['address'] - _cmd(f'podman run --name {name} -dit --net {network} {ipparam} {port} {volume} {env_opt} {image}') - - # Else container is already created. Just start it. - # It's needed after reboot. - elif container_status(name) != 'running': - _cmd(f'podman start {name}') + image = container_config['image'] + + if 'disable' in container_config: + # check if there is a container by that name running + tmp = _cmd('podman ps -a --format "{{.Names}}"') + if name in tmp: + _cmd(f'podman stop {name}') + _cmd(f'podman rm --force {name}') + continue + + memory = container_config['memory'] + restart = container_config['restart'] + + # Check if requested container image exists locally. If it does not, we + # pull it. print() is the best way to have a good response from the + # polling process to the user to display progress. If the image exists + # locally, a user can update it running `update container image <name>` + tmp = run(f'podman image exists {image}') + if tmp != 0: print(os.system(f'podman pull {image}')) + + # Check/set environment options "-e foo=bar" + env_opt = '' + if 'environment' in container_config: + for k, v in container_config['environment'].items(): + env_opt += f" -e \"{k}={v['value']}\"" + + # Publish ports + port = '' + if 'port' in container_config: + protocol = '' + for portmap in container_config['port']: + if 'protocol' in container_config['port'][portmap]: + protocol = container_config['port'][portmap]['protocol'] + protocol = f'/{protocol}' + else: + protocol = '/tcp' + sport = container_config['port'][portmap]['source'] + dport = container_config['port'][portmap]['destination'] + port += f' -p {sport}:{dport}{protocol}' + + # Bind volume + volume = '' + if 'volume' in container_config: + for vol, vol_config in container_config['volume'].items(): + svol = vol_config['source'] + dvol = vol_config['destination'] + volume += f' -v {svol}:{dvol}' + + container_base_cmd = f'podman run --detach --interactive --tty --replace ' \ + f'--memory {memory}m --memory-swap 0 --restart {restart} ' \ + f'--name {name} {port} {volume} {env_opt}' + if 'allow_host_networks' in container_config: + _cmd(f'{container_base_cmd} --net host {image}') + else: + for network in container_config['network']: + ipparam = '' + if 'address' in container_config['network'][network]: + address = container_config['network'][network]['address'] + ipparam = f'--ip {address}' + _cmd(f'{container_base_cmd} --net {network} {ipparam} {image}') return None diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py index cdee72e09..28f2a4ca5 100755 --- a/src/conf_mode/dhcp_server.py +++ b/src/conf_mode/dhcp_server.py @@ -148,9 +148,9 @@ def verify(dhcp): 'At least one DHCP shared network must be configured.') # Inspect shared-network/subnet - failover_names = [] listen_ok = False subnets = [] + failover_ok = False # A shared-network requires a subnet definition for network, network_config in dhcp['shared_network_name'].items(): @@ -159,9 +159,18 @@ def verify(dhcp): 'lease subnet must be configured.') for subnet, subnet_config in network_config['subnet'].items(): - if 'static_route' in subnet_config and len(subnet_config['static_route']) != 2: - raise ConfigError('Missing DHCP static-route parameter(s):\n' \ - 'destination-subnet | router must be defined!') + # All delivered static routes require a next-hop to be set + if 'static_route' in subnet_config: + for route, route_option in subnet_config['static_route'].items(): + if 'next_hop' not in route_option: + raise ConfigError(f'DHCP static-route "{route}" requires router to be defined!') + + # DHCP failover needs at least one subnet that uses it + if 'enable_failover' in subnet_config: + if 'failover' not in dhcp: + raise ConfigError(f'Can not enable failover for "{subnet}" in "{network}".\n' \ + 'Failover is not configured globally!') + failover_ok = True # Check if DHCP address range is inside configured subnet declaration if 'range' in subnet_config: @@ -191,23 +200,6 @@ def verify(dhcp): tmp = IPRange(range_config['start'], range_config['stop']) networks.append(tmp) - if 'failover' in subnet_config: - for key in ['local_address', 'peer_address', 'name', 'status']: - if key not in subnet_config['failover']: - raise ConfigError(f'Missing DHCP failover parameter "{key}"!') - - # Failover names must be uniquie - if subnet_config['failover']['name'] in failover_names: - name = subnet_config['failover']['name'] - raise ConfigError(f'DHCP failover names must be unique:\n' \ - f'{name} has already been configured!') - failover_names.append(subnet_config['failover']['name']) - - # Failover requires start/stop ranges for pool - if 'range' not in subnet_config: - raise ConfigError(f'DHCP failover requires at least one start-stop range to be configured\n'\ - f'within shared-network "{network}, {subnet}" for using failover!') - # Exclude addresses must be in bound if 'exclude' in subnet_config: for exclude in subnet_config['exclude']: @@ -251,6 +243,15 @@ def verify(dhcp): if net.overlaps(net2): raise ConfigError('Conflicting subnet ranges: "{net}" overlaps "{net2}"!') + if 'failover' in dhcp: + if not failover_ok: + raise ConfigError('DHCP failover must be enabled for at least one subnet!') + + for key in ['name', 'remote', 'source_address', 'status']: + if key not in dhcp['failover']: + tmp = key.replace('_', '-') + raise ConfigError(f'DHCP failover requires "{tmp}" to be specified!') + for address in (dict_search('listen_address', dhcp) or []): if is_addr_assigned(address): listen_ok = True diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index c44e6c974..06366362a 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -66,21 +66,6 @@ def get_config(config=None): if conf.exists(base_nameservers_dhcp): dns.update({'system_name_server_dhcp': conf.return_values(base_nameservers_dhcp)}) - # Split the source_address property into separate IPv4 and IPv6 lists - # NOTE: In future versions of pdns-recursor (> 4.4.0), this logic can be removed - # as both IPv4 and IPv6 addresses can be specified in a single setting. - source_address_v4 = [] - source_address_v6 = [] - - for source_address in dns['source_address']: - if is_ipv6(source_address): - source_address_v6.append(source_address) - else: - source_address_v4.append(source_address) - - dns.update({'source_address_v4': source_address_v4}) - dns.update({'source_address_v6': source_address_v6}) - return dns def verify(dns): diff --git a/src/conf_mode/firewall_options.py b/src/conf_mode/firewall_options.py deleted file mode 100755 index 67bf5d0e2..000000000 --- a/src/conf_mode/firewall_options.py +++ /dev/null @@ -1,150 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# - -import sys -import os -import copy - -from vyos.config import Config -from vyos import ConfigError -from vyos.util import call - -from vyos import airbag -airbag.enable() - -default_config_data = { - 'intf_opts': [], - 'new_chain4': False, - 'new_chain6': False -} - -def get_config(config=None): - opts = copy.deepcopy(default_config_data) - if config: - conf = config - else: - conf = Config() - if not conf.exists('firewall options'): - # bail out early - return opts - else: - conf.set_level('firewall options') - - # Parse configuration of each individual instance - if conf.exists('interface'): - for intf in conf.list_nodes('interface'): - conf.set_level('firewall options interface {0}'.format(intf)) - config = { - 'intf': intf, - 'disabled': False, - 'mss4': '', - 'mss6': '' - } - - # Check if individual option is disabled - if conf.exists('disable'): - config['disabled'] = True - - # - # Get MSS value IPv4 - # - if conf.exists('adjust-mss'): - config['mss4'] = conf.return_value('adjust-mss') - - # We need a marker that a new iptables chain needs to be generated - if not opts['new_chain4']: - opts['new_chain4'] = True - - # - # Get MSS value IPv6 - # - if conf.exists('adjust-mss6'): - config['mss6'] = conf.return_value('adjust-mss6') - - # We need a marker that a new ip6tables chain needs to be generated - if not opts['new_chain6']: - opts['new_chain6'] = True - - # Append interface options to global list - opts['intf_opts'].append(config) - - return opts - -def verify(tcp): - # syntax verification is done via cli - return None - -def apply(tcp): - target = 'VYOS_FW_OPTIONS' - - # always cleanup iptables - call('iptables --table mangle --delete FORWARD --jump {} >&/dev/null'.format(target)) - call('iptables --table mangle --flush {} >&/dev/null'.format(target)) - call('iptables --table mangle --delete-chain {} >&/dev/null'.format(target)) - - # always cleanup ip6tables - call('ip6tables --table mangle --delete FORWARD --jump {} >&/dev/null'.format(target)) - call('ip6tables --table mangle --flush {} >&/dev/null'.format(target)) - call('ip6tables --table mangle --delete-chain {} >&/dev/null'.format(target)) - - # Setup new iptables rules - if tcp['new_chain4']: - call('iptables --table mangle --new-chain {} >&/dev/null'.format(target)) - call('iptables --table mangle --append FORWARD --jump {} >&/dev/null'.format(target)) - - for opts in tcp['intf_opts']: - intf = opts['intf'] - mss = opts['mss4'] - - # Check if this rule iis disabled - if opts['disabled']: - continue - - # adjust TCP MSS per interface - if mss: - call('iptables --table mangle --append {} --out-interface {} --protocol tcp ' - '--tcp-flags SYN,RST SYN --jump TCPMSS --set-mss {} >&/dev/null'.format(target, intf, mss)) - - # Setup new ip6tables rules - if tcp['new_chain6']: - call('ip6tables --table mangle --new-chain {} >&/dev/null'.format(target)) - call('ip6tables --table mangle --append FORWARD --jump {} >&/dev/null'.format(target)) - - for opts in tcp['intf_opts']: - intf = opts['intf'] - mss = opts['mss6'] - - # Check if this rule iis disabled - if opts['disabled']: - continue - - # adjust TCP MSS per interface - if mss: - call('ip6tables --table mangle --append {} --out-interface {} --protocol tcp ' - '--tcp-flags SYN,RST SYN --jump TCPMSS --set-mss {} >&/dev/null'.format(target, intf, mss)) - - return None - -if __name__ == '__main__': - - try: - c = get_config() - verify(c) - apply(c) - except ConfigError as e: - print(e) - sys.exit(1) diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index f4c75c257..a7135911d 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2020 VyOS maintainers and contributors +# Copyright (C) 2018-2021 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -14,10 +14,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -""" -conf-mode script for 'system host-name' and 'system domain-name'. -""" - import re import sys import copy @@ -25,10 +21,13 @@ import copy import vyos.util import vyos.hostsd_client -from vyos.config import Config from vyos import ConfigError -from vyos.util import cmd, call, process_named_running - +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 airbag airbag.enable() @@ -37,7 +36,7 @@ default_config_data = { 'domain_name': '', 'domain_search': [], 'nameserver': [], - 'nameservers_dhcp_interfaces': [], + 'nameservers_dhcp_interfaces': {}, 'static_host_mapping': {} } @@ -51,29 +50,37 @@ def get_config(config=None): hosts = copy.deepcopy(default_config_data) - hosts['hostname'] = conf.return_value("system host-name") + hosts['hostname'] = conf.return_value(['system', 'host-name']) # This may happen if the config is not loaded yet, # e.g. if run by cloud-init if not hosts['hostname']: hosts['hostname'] = default_config_data['hostname'] - if conf.exists("system domain-name"): - hosts['domain_name'] = conf.return_value("system domain-name") + if conf.exists(['system', 'domain-name']): + hosts['domain_name'] = conf.return_value(['system', 'domain-name']) hosts['domain_search'].append(hosts['domain_name']) - for search in conf.return_values("system domain-search domain"): + for search in conf.return_values(['system', 'domain-search', 'domain']): hosts['domain_search'].append(search) - hosts['nameserver'] = conf.return_values("system name-server") + if conf.exists(['system', 'name-server']): + for ns in conf.return_values(['system', 'name-server']): + if is_ip(ns): + hosts['nameserver'].append(ns) + else: + tmp = '' + if_type = Section.section(ns) + if conf.exists(['interfaces', if_type, ns, 'address']): + tmp = conf.return_values(['interfaces', if_type, ns, 'address']) - hosts['nameservers_dhcp_interfaces'] = conf.return_values("system name-servers-dhcp") + hosts['nameservers_dhcp_interfaces'].update({ ns : tmp }) # system static-host-mapping - for hn in conf.list_nodes('system static-host-mapping host-name'): + for hn in conf.list_nodes(['system', 'static-host-mapping', 'host-name']): hosts['static_host_mapping'][hn] = {} - hosts['static_host_mapping'][hn]['address'] = conf.return_value(f'system static-host-mapping host-name {hn} inet') - hosts['static_host_mapping'][hn]['aliases'] = conf.return_values(f'system static-host-mapping host-name {hn} alias') + hosts['static_host_mapping'][hn]['address'] = conf.return_value(['system', 'static-host-mapping', 'host-name', hn, 'inet']) + hosts['static_host_mapping'][hn]['aliases'] = conf.return_values(['system', 'static-host-mapping', 'host-name', hn, 'alias']) return hosts @@ -103,8 +110,10 @@ def verify(hosts): if not hostname_regex.match(a) and len(a) != 0: raise ConfigError(f'Invalid alias "{a}" in static-host-mapping "{host}"') - # TODO: add warnings for nameservers_dhcp_interfaces if interface doesn't - # exist or doesn't have address dhcp(v6) + 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!') return None diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 78c24952b..e7250fb49 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -37,6 +37,7 @@ from vyos.pki import wrap_private_key from vyos.template import render from vyos.util import call from vyos.util import dict_search +from vyos.util import write_file from vyos import ConfigError from vyos import airbag airbag.enable() @@ -54,15 +55,17 @@ def get_config(config=None): conf = config else: conf = Config() - base = ['interfaces', 'ethernet'] - tmp_pki = conf.get_config_dict(['pki'], key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) + # This must be called prior to get_interface_dict(), as this function will + # alter the config level (config.set_level()) + pki = conf.get_config_dict(['pki'], key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) + base = ['interfaces', 'ethernet'] ethernet = get_interface_dict(conf, base) if 'deleted' not in ethernet: - ethernet['pki'] = tmp_pki + if pki: ethernet['pki'] = pki return ethernet @@ -72,12 +75,6 @@ def verify(ethernet): ifname = ethernet['ifname'] verify_interface_exists(ifname) - - # No need to check speed and duplex keys as both have default values. - if ((ethernet['speed'] == 'auto' and ethernet['duplex'] != 'auto') or - (ethernet['speed'] != 'auto' and ethernet['duplex'] == 'auto')): - raise ConfigError('Speed/Duplex missmatch. Must be both auto or manually configured') - verify_mtu(ethernet) verify_mtu_ipv6(ethernet) verify_dhcpv6(ethernet) @@ -86,25 +83,31 @@ def verify(ethernet): verify_eapol(ethernet) verify_mirror(ethernet) - # verify offloading capabilities - if dict_search('offload.rps', ethernet) != None: - if not os.path.exists(f'/sys/class/net/{ifname}/queues/rx-0/rps_cpus'): - raise ConfigError('Interface does not suport RPS!') + ethtool = Ethtool(ifname) + # No need to check speed and duplex keys as both have default values. + if ((ethernet['speed'] == 'auto' and ethernet['duplex'] != 'auto') or + (ethernet['speed'] != 'auto' and ethernet['duplex'] == 'auto')): + raise ConfigError('Speed/Duplex missmatch. Must be both auto or manually configured') - driver = EthernetIf(ifname).get_driver_name() - # T3342 - Xen driver requires special treatment - if driver == 'vif': - if int(ethernet['mtu']) > 1500 and dict_search('offload.sg', ethernet) == None: - raise ConfigError('Xen netback drivers requires scatter-gatter offloading '\ - 'for MTU size larger then 1500 bytes') + if ethernet['speed'] != 'auto' and ethernet['duplex'] != 'auto': + # We need to verify if the requested speed and duplex setting is + # supported by the underlaying NIC. + speed = ethernet['speed'] + duplex = ethernet['duplex'] + if not ethtool.check_speed_duplex(speed, duplex): + raise ConfigError(f'Adapter does not support changing speed and duplex '\ + f'settings to: {speed}/{duplex}!') + + if 'disable_flow_control' in ethernet: + if not ethtool.check_flow_control(): + raise ConfigError('Adapter does not support changing flow-control settings!') - ethtool = Ethtool(ifname) if 'ring_buffer' in ethernet: - max_rx = ethtool.get_rx_buffer() + max_rx = ethtool.get_ring_buffer_max('rx') if not max_rx: raise ConfigError('Driver does not support RX ring-buffer configuration!') - max_tx = ethtool.get_tx_buffer() + max_tx = ethtool.get_ring_buffer_max('tx') if not max_tx: raise ConfigError('Driver does not support TX ring-buffer configuration!') @@ -118,6 +121,18 @@ def verify(ethernet): raise ConfigError(f'Driver only supports a maximum TX ring-buffer '\ f'size of "{max_tx}" bytes!') + # verify offloading capabilities + if dict_search('offload.rps', ethernet) != None: + if not os.path.exists(f'/sys/class/net/{ifname}/queues/rx-0/rps_cpus'): + raise ConfigError('Interface does not suport RPS!') + + driver = ethtool.get_driver_name() + # T3342 - Xen driver requires special treatment + if driver == 'vif': + if int(ethernet['mtu']) > 1500 and dict_search('offload.sg', ethernet) == None: + raise ConfigError('Xen netback drivers requires scatter-gatter offloading '\ + 'for MTU size larger then 1500 bytes') + # XDP requires multiple TX queues if 'xdp' in ethernet: queues = glob(f'/sys/class/net/{ifname}/queues/tx-*') @@ -136,7 +151,7 @@ def generate(ethernet): if 'eapol' in ethernet: render(wpa_suppl_conf.format(**ethernet), 'ethernet/wpa_supplicant.conf.tmpl', ethernet) - + ifname = ethernet['ifname'] cert_file_path = os.path.join(cfg_dir, f'{ifname}_cert.pem') cert_key_path = os.path.join(cfg_dir, f'{ifname}_cert.key') @@ -144,19 +159,16 @@ def generate(ethernet): cert_name = ethernet['eapol']['certificate'] pki_cert = ethernet['pki']['certificate'][cert_name] - with open(cert_file_path, 'w') as f: - f.write(wrap_certificate(pki_cert['certificate'])) - - with open(cert_key_path, 'w') as f: - f.write(wrap_private_key(pki_cert['private']['key'])) + write_file(cert_file_path, wrap_certificate(pki_cert['certificate'])) + write_file(cert_key_path, wrap_private_key(pki_cert['private']['key'])) if 'ca_certificate' in ethernet['eapol']: ca_cert_file_path = os.path.join(cfg_dir, f'{ifname}_ca.pem') ca_cert_name = ethernet['eapol']['ca_certificate'] pki_ca_cert = ethernet['pki']['ca'][cert_name] - with open(ca_cert_file_path, 'w') as f: - f.write(wrap_certificate(pki_ca_cert['certificate'])) + write_file(ca_cert_file_path, + wrap_certificate(pki_ca_cert['certificate'])) else: # delete configuration on interface removal if os.path.isfile(wpa_suppl_conf.format(**ethernet)): diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 4bd0b22a9..2533a5b02 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# Copyright (C) 2019-2021 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -100,7 +100,7 @@ def get_config(config=None): # need to check this first and drop those keys if 'totp' not in tmp_openvpn['server']: del openvpn['server']['mfa']['totp'] - + return openvpn def is_ec_private_key(pki, cert_name): @@ -295,6 +295,9 @@ def verify(openvpn): if openvpn['protocol'] == 'tcp-active': raise ConfigError('Protocol "tcp-active" is not valid in server mode') + if dict_search('authentication.username', openvpn) or dict_search('authentication.password', openvpn): + raise ConfigError('Cannot specify "authentication" in server mode') + if 'remote_port' in openvpn: raise ConfigError('Cannot specify "remote-port" in server mode') diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index 6c4c6c95b..584adc75e 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# Copyright (C) 2019-2021 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -22,12 +22,16 @@ 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.configverify import verify_authentication from vyos.configverify import verify_source_interface +from vyos.configverify import verify_interface_exists from vyos.configverify import verify_vrf from vyos.configverify import verify_mtu_ipv6 +from vyos.ifconfig import PPPoEIf from vyos.template import render from vyos.util import call +from vyos.util import is_systemd_service_running from vyos import ConfigError from vyos import airbag airbag.enable() @@ -44,6 +48,32 @@ def get_config(config=None): base = ['interfaces', 'pppoe'] 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': {}}) + return pppoe def verify(pppoe): @@ -66,57 +96,42 @@ def generate(pppoe): # rendered into ifname = pppoe['ifname'] config_pppoe = f'/etc/ppp/peers/{ifname}' - script_pppoe_pre_up = f'/etc/ppp/ip-pre-up.d/1000-vyos-pppoe-{ifname}' - script_pppoe_ip_up = f'/etc/ppp/ip-up.d/1000-vyos-pppoe-{ifname}' - script_pppoe_ip_down = f'/etc/ppp/ip-down.d/1000-vyos-pppoe-{ifname}' - script_pppoe_ipv6_up = f'/etc/ppp/ipv6-up.d/1000-vyos-pppoe-{ifname}' - config_wide_dhcp6c = f'/run/dhcp6c/dhcp6c.{ifname}.conf' - - config_files = [config_pppoe, script_pppoe_pre_up, script_pppoe_ip_up, - script_pppoe_ip_down, script_pppoe_ipv6_up, config_wide_dhcp6c] if 'deleted' in pppoe or 'disable' in pppoe: - # stop DHCPv6-PD client - call(f'systemctl stop dhcp6c@{ifname}.service') - # Hang-up PPPoE connection - call(f'systemctl stop ppp@{ifname}.service') - - # Delete PPP configuration files - for file in config_files: - if os.path.exists(file): - os.unlink(file) + if os.path.exists(config_pppoe): + os.unlink(config_pppoe) return None # Create PPP configuration files - render(config_pppoe, 'pppoe/peer.tmpl', pppoe, permission=0o755) - - # Create script for ip-pre-up.d - render(script_pppoe_pre_up, 'pppoe/ip-pre-up.script.tmpl', pppoe, - permission=0o755) - # Create script for ip-up.d - render(script_pppoe_ip_up, 'pppoe/ip-up.script.tmpl', pppoe, - permission=0o755) - # Create script for ip-down.d - render(script_pppoe_ip_down, 'pppoe/ip-down.script.tmpl', pppoe, - permission=0o755) - # Create script for ipv6-up.d - render(script_pppoe_ipv6_up, 'pppoe/ipv6-up.script.tmpl', pppoe, - permission=0o755) - - if 'dhcpv6_options' in pppoe and 'pd' in pppoe['dhcpv6_options']: - # ipv6.tmpl relies on ifname - this should be made consitent in the - # future better then double key-ing the same value - render(config_wide_dhcp6c, 'dhcp-client/ipv6.tmpl', pppoe) + render(config_pppoe, 'pppoe/peer.tmpl', pppoe, permission=0o640) return None def apply(pppoe): + ifname = pppoe['ifname'] if 'deleted' in pppoe or 'disable' in pppoe: - call('systemctl stop ppp@{ifname}.service'.format(**pppoe)) + if os.path.isdir(f'/sys/class/net/{ifname}'): + p = PPPoEIf(ifname) + p.remove() + call(f'systemctl stop ppp@{ifname}.service') return None - call('systemctl restart ppp@{ifname}.service'.format(**pppoe)) + # reconnect should only be necessary when certain config options change, + # like ACS name, authentication, no-peer-dns, source-interface + if ((not is_systemd_service_running(f'ppp@{ifname}.service')) or + 'shutdown_required' in pppoe): + + # cleanup system (e.g. FRR routes first) + if os.path.isdir(f'/sys/class/net/{ifname}'): + p = PPPoEIf(ifname) + p.remove() + + call(f'systemctl restart ppp@{ifname}.service') + else: + if os.path.isdir(f'/sys/class/net/{ifname}'): + p = PPPoEIf(ifname) + p.update(pppoe) return None diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index 294da8ef9..ef385d2e7 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -18,6 +18,7 @@ import os from sys import exit from netifaces import interfaces +from ipaddress import IPv4Address from vyos.config import Config from vyos.configdict import dict_merge @@ -31,6 +32,7 @@ from vyos.configverify import verify_mtu_ipv6 from vyos.configverify import verify_vrf from vyos.configverify import verify_tunnel from vyos.ifconfig import Interface +from vyos.ifconfig import Section from vyos.ifconfig import TunnelIf from vyos.template import is_ipv4 from vyos.template import is_ipv6 @@ -94,6 +96,38 @@ def verify(tunnel): if 'direction' not in tunnel['parameters']['erspan']: raise ConfigError('ERSPAN version 2 requires direction to be set!') + # If tunnel source address any and key not set + if tunnel['encapsulation'] in ['gre'] and \ + tunnel['source_address'] == '0.0.0.0' and \ + dict_search('parameters.ip.key', tunnel) == None: + raise ConfigError('Tunnel parameters ip key must be set!') + + if tunnel['encapsulation'] in ['gre', 'gretap']: + if dict_search('parameters.ip.key', tunnel) != None: + # Check pairs tunnel source-address/encapsulation/key with exists tunnels. + # Prevent the same key for 2 tunnels with same source-address/encap. T2920 + for tunnel_if in Section.interfaces('tunnel'): + tunnel_cfg = get_interface_config(tunnel_if) + exist_encap = tunnel_cfg['linkinfo']['info_kind'] + exist_source_address = tunnel_cfg['address'] + exist_key = tunnel_cfg['linkinfo']['info_data']['ikey'] + new_source_address = tunnel['source_address'] + # Convert tunnel key to ip key, format "ip -j link show" + # 1 => 0.0.0.1, 999 => 0.0.3.231 + orig_new_key = int(tunnel['parameters']['ip']['key']) + new_key = IPv4Address(orig_new_key) + new_key = str(new_key) + if tunnel['encapsulation'] == exist_encap and \ + new_source_address == exist_source_address and \ + new_key == exist_key: + raise ConfigError(f'Key "{orig_new_key}" for source-address "{new_source_address}" ' \ + f'is already used for tunnel "{tunnel_if}"!') + + # Keys are not allowed with ipip and sit tunnels + if tunnel['encapsulation'] in ['ipip', 'sit']: + if dict_search('parameters.ip.key', tunnel) != None: + raise ConfigError('Keys are not allowed with ipip and sit tunnels!') + verify_mtu_ipv6(tunnel) verify_address(tunnel) verify_vrf(tunnel) diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index 4c566a5ad..da64dd076 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -30,6 +30,7 @@ from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_mtu_ipv6 from vyos.ifconfig import WireGuardIf from vyos.util import check_kmod +from vyos.util import check_port_availability from vyos import ConfigError from vyos import airbag airbag.enable() @@ -46,6 +47,9 @@ def get_config(config=None): base = ['interfaces', 'wireguard'] wireguard = get_interface_dict(conf, base) + # Check if a port was changed + wireguard['port_changed'] = leaf_node_changed(conf, ['port']) + # Determine which Wireguard peer has been removed. # Peers can only be removed with their public key! dict = {} @@ -73,6 +77,13 @@ def verify(wireguard): if 'peer' not in wireguard: raise ConfigError('At least one Wireguard peer is required!') + if 'port' in wireguard and wireguard['port_changed']: + listen_port = int(wireguard['port']) + if check_port_availability('0.0.0.0', listen_port, 'udp') is not True: + raise ConfigError( + f'The UDP port {listen_port} is busy or unavailable and cannot be used for the interface' + ) + # run checks on individual configured WireGuard peer for tmp in wireguard['peer']: peer = wireguard['peer'][tmp] diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py index 31c599145..faa5eb628 100755 --- a/src/conf_mode/interfaces-wwan.py +++ b/src/conf_mode/interfaces-wwan.py @@ -26,7 +26,6 @@ from vyos.configverify import verify_vrf from vyos.ifconfig import WWANIf from vyos.util import cmd from vyos.util import dict_search -from vyos.template import render from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index dae958774..59939d0fb 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -139,12 +139,10 @@ def verify(nat): for rule, config in dict_search('source.rule', nat).items(): err_msg = f'Source NAT configuration error in rule {rule}:' if 'outbound_interface' not in config: - raise ConfigError(f'{err_msg}\n' \ - 'outbound-interface not specified') - else: - 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') + 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') addr = dict_search('translation.address', config) if addr != None: diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py index e2bd6417d..fb376a434 100755 --- a/src/conf_mode/nat66.py +++ b/src/conf_mode/nat66.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-2021 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -55,7 +55,7 @@ def get_config(config=None): conf = config else: conf = Config() - + base = ['nat66'] nat = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) @@ -90,7 +90,7 @@ def get_config(config=None): # be done only once if not get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK'): nat['helper_functions'] = 'add' - + # Retrieve current table handler positions nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_IGNORE') nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_PREROUTING_HOOK') @@ -109,21 +109,22 @@ def verify(nat): if 'helper_functions' in nat and nat['helper_functions'] != 'has': if not (nat['pre_ct_conntrack'] or nat['out_ct_conntrack']): raise Exception('could not determine nftable ruleset handlers') - + if dict_search('source.rule', nat): for rule, config in dict_search('source.rule', nat).items(): err_msg = f'Source NAT66 configuration error in rule {rule}:' if 'outbound_interface' not in config: - raise ConfigError(f'{err_msg}\n' \ - 'outbound-interface not specified') - else: - if config['outbound_interface'] not in interfaces(): - print(f'WARNING: rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system') + 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') 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') + else: + raise ConfigError(f'{err_msg} translation address not specified') prefix = dict_search('source.prefix', config) if prefix != None: @@ -145,7 +146,7 @@ def verify(nat): def generate(nat): render(iptables_nat_config, 'firewall/nftables-nat66.tmpl', nat, permission=0o755) - render(ndppd_config, 'proxy-ndp/ndppd.conf.tmpl', nat, permission=0o755) + render(ndppd_config, 'ndppd/ndppd.conf.tmpl', nat, permission=0o755) return None def apply(nat): diff --git a/src/conf_mode/pki.py b/src/conf_mode/pki.py index ef1b57650..efa3578b4 100755 --- a/src/conf_mode/pki.py +++ b/src/conf_mode/pki.py @@ -16,8 +16,11 @@ from sys import exit +import jmespath + from vyos.config import Config from vyos.configdict import dict_merge +from vyos.configdict import node_changed from vyos.pki import is_ca_certificate from vyos.pki import load_certificate from vyos.pki import load_certificate_request @@ -26,6 +29,7 @@ from vyos.pki import load_private_key from vyos.pki import load_crl from vyos.pki import load_dh_parameters from vyos.util import ask_input +from vyos.util import dict_search_recursive from vyos.xml import defaults from vyos import ConfigError from vyos import airbag @@ -37,14 +41,29 @@ def get_config(config=None): else: conf = Config() base = ['pki'] - if not conf.exists(base): - return None pki = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) + get_first_key=True, + no_tag_node_value_mangle=True) + + pki['changed'] = {} + tmp = node_changed(conf, base + ['ca'], key_mangling=('-', '_')) + if tmp: pki['changed'].update({'ca' : tmp}) + + tmp = node_changed(conf, base + ['certificate'], key_mangling=('-', '_')) + if tmp: pki['changed'].update({'certificate' : tmp}) + + # We only merge on the defaults of there is a configuration at all + if conf.exists(base): + default_values = defaults(base) + pki = dict_merge(default_values, pki) + + # We need to get the entire system configuration to verify that we are not + # deleting a certificate that is still referenced somewhere! + pki['system'] = conf.get_config_dict([], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) - default_values = defaults(base) - pki = dict_merge(default_values, pki) return pki def is_valid_certificate(raw_data): @@ -142,6 +161,21 @@ def verify(pki): if len(country) != 2 or not country.isalpha(): raise ConfigError(f'Invalid default country value. Value must be 2 alpha characters.') + if 'changed' in pki: + # if the list is getting longer, we can move to a dict() and also embed the + # search key as value from line 173 or 176 + for cert_type in ['ca', 'certificate']: + if not cert_type in pki['changed']: + continue + for certificate in pki['changed'][cert_type]: + if cert_type not in pki or certificate not in pki['changed'][cert_type]: + if cert_type == 'ca': + if certificate in dict_search_recursive(pki['system'], 'ca_certificate'): + raise ConfigError(f'CA certificate "{certificate}" is still in use!') + elif cert_type == 'certificate': + if certificate in dict_search_recursive(pki['system'], 'certificate'): + raise ConfigError(f'Certificate "{certificate}" is still in use!') + return None def generate(pki): @@ -154,6 +188,8 @@ def apply(pki): if not pki: return None + # XXX: restart services if the content of a certificate changes + return None if __name__ == '__main__': diff --git a/src/conf_mode/policy-local-route.py b/src/conf_mode/policy-local-route.py index 013f22665..539189442 100755 --- a/src/conf_mode/policy-local-route.py +++ b/src/conf_mode/policy-local-route.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-2021 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -44,17 +44,26 @@ def get_config(config=None): if tmp: for rule in (tmp or []): src = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'source']) + fwmk = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'fwmark']) if src: dict = dict_merge({'rule_remove' : {rule : {'source' : src}}}, dict) pbr.update(dict) + if fwmk: + dict = dict_merge({'rule_remove' : {rule : {'fwmark' : fwmk}}}, dict) + pbr.update(dict) # delete policy local-route rule x source x.x.x.x + # delete policy local-route rule x fwmark x if 'rule' in pbr: for rule in pbr['rule']: src = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'source']) + fwmk = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'fwmark']) if src: dict = dict_merge({'rule_remove' : {rule : {'source' : src}}}, dict) pbr.update(dict) + if fwmk: + dict = dict_merge({'rule_remove' : {rule : {'fwmark' : fwmk}}}, dict) + pbr.update(dict) return pbr @@ -65,8 +74,8 @@ def verify(pbr): if 'rule' in pbr: for rule in pbr['rule']: - if 'source' not in pbr['rule'][rule]: - raise ConfigError('Source address required!') + if 'source' not in pbr['rule'][rule] and 'fwmark' not in pbr['rule'][rule]: + raise ConfigError('Source address or fwmark is required!') else: if 'set' not in pbr['rule'][rule] or 'table' not in pbr['rule'][rule]['set']: raise ConfigError('Table set is required!') @@ -86,16 +95,34 @@ def apply(pbr): # Delete old rule if needed if 'rule_remove' in pbr: for rule in pbr['rule_remove']: - for src in pbr['rule_remove'][rule]['source']: - call(f'ip rule del prio {rule} from {src}') + if 'source' in pbr['rule_remove'][rule]: + for src in pbr['rule_remove'][rule]['source']: + call(f'ip rule del prio {rule} from {src}') + if 'fwmark' in pbr['rule_remove'][rule]: + for fwmk in pbr['rule_remove'][rule]['fwmark']: + call(f'ip rule del prio {rule} from all fwmark {fwmk}') # Generate new config if 'rule' in pbr: for rule in pbr['rule']: table = pbr['rule'][rule]['set']['table'] - if pbr['rule'][rule]['source']: + # Only source in the rule + # set policy local-route rule 100 source '203.0.113.1' + if 'source' in pbr['rule'][rule] and not 'fwmark' in pbr['rule'][rule]: for src in pbr['rule'][rule]['source']: call(f'ip rule add prio {rule} from {src} lookup {table}') + # Only fwmark in the rule + # set policy local-route rule 101 fwmark '23' + if 'fwmark' in pbr['rule'][rule] and not 'source' in pbr['rule'][rule]: + fwmk = pbr['rule'][rule]['fwmark'] + call(f'ip rule add prio {rule} from all fwmark {fwmk} lookup {table}') + # Source and fwmark in the rule + # set policy local-route rule 100 source '203.0.113.1' + # set policy local-route rule 100 fwmark '23' + if 'source' in pbr['rule'][rule] and 'fwmark' in pbr['rule'][rule]: + fwmk = pbr['rule'][rule]['fwmark'] + for src in pbr['rule'][rule]['source']: + call(f'ip rule add prio {rule} from {src} fwmark {fwmk} lookup {table}') return None diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py index d56bae9e9..1a03d520b 100755 --- a/src/conf_mode/policy.py +++ b/src/conf_mode/policy.py @@ -190,6 +190,7 @@ def apply(policy): frr_cfg.modify_section(r'^bgp community-list .*') frr_cfg.modify_section(r'^bgp extcommunity-list .*') frr_cfg.modify_section(r'^bgp large-community-list .*') + frr_cfg.modify_section(r'^route-map .*') frr_cfg.add_before('^line vty', policy['new_frr_config']) frr_cfg.commit_configuration(bgp_daemon) diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index 348bae59f..539fd7b8e 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -92,7 +92,7 @@ def generate(bfd): bfd['new_frr_config'] = '' return None - bfd['new_frr_config'] = render_to_string('frr/bfd.frr.tmpl', bfd) + bfd['new_frr_config'] = render_to_string('frr/bfdd.frr.tmpl', bfd) def apply(bfd): # Save original configuration prior to starting any commit actions diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index 9ecfd07fe..68284e0f9 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -23,6 +23,7 @@ from vyos.config import Config from vyos.configdict import dict_merge from vyos.configverify import verify_prefix_list from vyos.configverify import verify_route_map +from vyos.configverify import verify_vrf from vyos.template import is_ip from vyos.template import is_interface from vyos.template import render_to_string @@ -129,7 +130,7 @@ def verify(bgp): if 'local_as' in peer_config: if len(peer_config['local_as']) > 1: - raise ConfigError('Only one local-as number may be specified!') + raise ConfigError(f'Only one local-as number can be specified for peer "{peer}"!') # Neighbor local-as override can not be the same as the local-as # we use for this BGP instane! @@ -139,7 +140,7 @@ def verify(bgp): # 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\'t set both ebgp-multihop and ttl-security hops') + 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. if 'override_capability' in peer_config and 'strict_capability_match' in peer_config: @@ -147,7 +148,7 @@ def verify(bgp): # Check spaces in the password if 'password' in peer_config and ' ' in peer_config['password']: - raise ConfigError('You can\'t use spaces in the password') + raise ConfigError('Whitespace is not allowed in passwords!') # Some checks can/must only be done on a neighbor and not a peer-group if neighbor == 'neighbor': @@ -221,27 +222,47 @@ def verify(bgp): raise ConfigError(f'Peer-group "{peer_group}" requires remote-as to be set!') # Throw an error if the global administrative distance parameters aren't all filled out. - if dict_search('parameters.distance', bgp) == None: - pass - else: - if dict_search('parameters.distance.global', bgp): - for key in ['external', 'internal', 'local']: - if dict_search(f'parameters.distance.global.{key}', bgp) == None: - raise ConfigError('Missing mandatory configuration option for '\ - f'global administrative distance {key}!') - - # Throw an error if the address family specific administrative distance parameters aren't all filled out. - if dict_search('address_family', bgp) == None: - pass - else: - for address_family_name in dict_search('address_family', bgp): - if dict_search(f'address_family.{address_family_name}.distance', bgp) == None: - pass - else: + if dict_search('parameters.distance.global', bgp) != None: + for key in ['external', 'internal', 'local']: + if dict_search(f'parameters.distance.global.{key}', bgp) == None: + raise ConfigError('Missing mandatory configuration option for '\ + f'global administrative distance {key}!') + + # Address Family specific validation + if 'address_family' in bgp: + for afi, afi_config in bgp['address_family'].items(): + if 'distance' in afi_config: + # Throw an error if the address family specific administrative + # distance parameters aren't all filled out. for key in ['external', 'internal', 'local']: - if dict_search(f'address_family.{address_family_name}.distance.{key}', bgp) == None: + if key not in afi_config['distance']: raise ConfigError('Missing mandatory configuration option for '\ - f'{address_family_name} administrative distance {key}!') + f'{afi} administrative distance {key}!') + + if afi in ['ipv4_unicast', 'ipv6_unicast']: + if 'import' in afi_config and 'vrf' in afi_config['import']: + # Check if VRF exists + verify_vrf(afi_config['import']['vrf']) + + # FRR error: please unconfigure vpn to vrf commands before + # using import vrf commands + if 'vpn' in afi_config['import'] or dict_search('export.vpn', afi_config) != None: + raise ConfigError('Please unconfigure VPN to VRF commands before '\ + 'using "import vrf" commands!') + + # Verify that the export/import route-maps do exist + for export_import in ['export', 'import']: + tmp = dict_search(f'route_map.vpn.{export_import}', afi_config) + if tmp: verify_route_map(tmp, bgp) + + if afi in ['l2vpn_evpn'] and 'vrf' not in bgp: + # Some L2VPN EVPN AFI options are only supported under VRF + if 'vni' in afi_config: + for vni, vni_config in afi_config['vni'].items(): + if 'rd' in vni_config: + raise ConfigError('VNI route-distinguisher is only supported under EVPN VRF') + if 'route_target' in vni_config: + raise ConfigError('VNI route-target is only supported under EVPN VRF') return None diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py index d4c82249b..4505e2496 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -113,9 +113,13 @@ def verify(isis): # Interface MTU must be >= configured lsp-mtu mtu = Interface(interface).get_mtu() area_mtu = isis['lsp_mtu'] - if mtu < int(area_mtu): - raise ConfigError(f'Interface {interface} has MTU {mtu}, minimum ' \ - f'area MTU is {area_mtu}!') + # Recommended maximum PDU size = interface MTU - 3 bytes + recom_area_mtu = mtu - 3 + if mtu < int(area_mtu) or int(area_mtu) > recom_area_mtu: + raise ConfigError(f'Interface {interface} has MTU {mtu}, ' \ + f'current area MTU is {area_mtu}! \n' \ + f'Recommended area lsp-mtu {recom_area_mtu} or less ' \ + '(calculated on MTU size).') if 'vrf' in isis: # If interface specific options are set, we must ensure that the @@ -144,7 +148,7 @@ def verify(isis): exist_timers = set(required_timers).difference(set(exist_timers)) if len(exist_timers) > 0: - raise ConfigError('All types of delay must be specified: ' + ', '.join(exist_timers).replace('_', '-')) + raise ConfigError('All types of spf-delay must be configured. Missing: ' + ', '.join(exist_timers).replace('_', '-')) # If Redistribute set, but level don't set if 'redistribute' in isis: diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py index 78c1c82bd..6ccda2e5a 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -87,7 +87,13 @@ def get_config(config=None): del default_values['area']['area_type']['nssa'] if 'mpls_te' not in ospf: del default_values['mpls_te'] - for protocol in ['bgp', 'connected', 'isis', 'kernel', 'rip', 'static']: + + for protocol in ['bgp', 'connected', 'isis', 'kernel', 'rip', 'static', 'table']: + # table is a tagNode thus we need to clean out all occurances for the + # default values and load them in later individually + if protocol == 'table': + del default_values['redistribute']['table'] + continue if dict_search(f'redistribute.{protocol}', ospf) is None: del default_values['redistribute'][protocol] @@ -109,7 +115,6 @@ def get_config(config=None): default_values = defaults(base + ['area', 'virtual-link']) for area, area_config in ospf['area'].items(): if 'virtual_link' in area_config: - print(default_values) for virtual_link in area_config['virtual_link']: ospf['area'][area]['virtual_link'][virtual_link] = dict_merge( default_values, ospf['area'][area]['virtual_link'][virtual_link]) @@ -127,6 +132,12 @@ def get_config(config=None): ospf['interface'][interface] = dict_merge(default_values, ospf['interface'][interface]) + if 'redistribute' in ospf and 'table' in ospf['redistribute']: + default_values = defaults(base + ['redistribute', 'table']) + for table in ospf['redistribute']['table']: + ospf['redistribute']['table'][table] = dict_merge(default_values, + ospf['redistribute']['table'][table]) + # We also need some additional information from the config, prefix-lists # and route-maps for instance. They will be used in verify(). # @@ -149,14 +160,23 @@ def verify(ospf): if route_map_name: verify_route_map(route_map_name, ospf) if 'interface' in ospf: - for interface in ospf['interface']: + for interface, interface_config in ospf['interface'].items(): verify_interface_exists(interface) # One can not use dead-interval and hello-multiplier at the same # time. FRR will only activate the last option set via CLI. - if {'hello_multiplier', 'dead_interval'} <= set(ospf['interface'][interface]): + if {'hello_multiplier', 'dead_interval'} <= set(interface_config): raise ConfigError(f'Can not use hello-multiplier and dead-interval ' \ f'concurrently for {interface}!') + # One can not use the "network <prefix> area <id>" command and an + # per interface area assignment at the same time. FRR will error + # out using: "Please remove all network commands first." + if 'area' in ospf and 'area' in interface_config: + for area, area_config in ospf['area'].items(): + if 'network' in area_config: + raise ConfigError('Can not use OSPF interface area and area ' \ + 'network configuration at the same time!') + if 'vrf' in ospf: # If interface specific options are set, we must ensure that the # interface is bound to our requesting VRF. Due to the VyOS @@ -177,7 +197,7 @@ def generate(ospf): ospf['protocol'] = 'ospf' # required for frr/vrf.route-map.frr.tmpl ospf['frr_zebra_config'] = render_to_string('frr/vrf.route-map.frr.tmpl', ospf) - ospf['frr_ospfd_config'] = render_to_string('frr/ospf.frr.tmpl', ospf) + ospf['frr_ospfd_config'] = render_to_string('frr/ospfd.frr.tmpl', ospf) return None def apply(ospf): diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py index fef0f509b..536ffa690 100755 --- a/src/conf_mode/protocols_ospfv3.py +++ b/src/conf_mode/protocols_ospfv3.py @@ -65,7 +65,7 @@ def verify(ospfv3): if 'ifmtu' in if_config: mtu = Interface(ifname).get_mtu() if int(if_config['ifmtu']) > int(mtu): - raise ConfigError(f'OSPFv3 ifmtu cannot go beyond physical MTU of "{mtu}"') + raise ConfigError(f'OSPFv3 ifmtu can not exceed physical MTU of "{mtu}"') return None @@ -74,7 +74,7 @@ def generate(ospfv3): ospfv3['new_frr_config'] = '' return None - ospfv3['new_frr_config'] = render_to_string('frr/ospfv3.frr.tmpl', ospfv3) + ospfv3['new_frr_config'] = render_to_string('frr/ospf6d.frr.tmpl', ospfv3) return None def apply(ospfv3): diff --git a/src/conf_mode/protocols_rip.py b/src/conf_mode/protocols_rip.py index e56eb1f56..6b78f6f2d 100755 --- a/src/conf_mode/protocols_rip.py +++ b/src/conf_mode/protocols_rip.py @@ -93,7 +93,7 @@ def generate(rip): rip['new_frr_config'] = '' return None - rip['new_frr_config'] = render_to_string('frr/rip.frr.tmpl', rip) + rip['new_frr_config'] = render_to_string('frr/ripd.frr.tmpl', rip) return None diff --git a/src/conf_mode/protocols_ripng.py b/src/conf_mode/protocols_ripng.py index aaec5dacb..bc4954f63 100755 --- a/src/conf_mode/protocols_ripng.py +++ b/src/conf_mode/protocols_ripng.py @@ -95,7 +95,7 @@ def generate(ripng): ripng['new_frr_config'] = '' return None - ripng['new_frr_config'] = render_to_string('frr/ripng.frr.tmpl', ripng) + ripng['new_frr_config'] = render_to_string('frr/ripngd.frr.tmpl', ripng) return None def apply(ripng): diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py index 338247e30..597fcc443 100755 --- a/src/conf_mode/protocols_static.py +++ b/src/conf_mode/protocols_static.py @@ -80,7 +80,7 @@ def verify(static): return None def generate(static): - static['new_frr_config'] = render_to_string('frr/static.frr.tmpl', static) + static['new_frr_config'] = render_to_string('frr/staticd.frr.tmpl', static) return None def apply(static): diff --git a/src/conf_mode/service_webproxy.py b/src/conf_mode/service_webproxy.py index cbbd2e0bc..a16cc4aeb 100755 --- a/src/conf_mode/service_webproxy.py +++ b/src/conf_mode/service_webproxy.py @@ -23,6 +23,7 @@ from vyos.config import Config from vyos.configdict import dict_merge from vyos.template import render from vyos.util import call +from vyos.util import chmod_755 from vyos.util import dict_search from vyos.util import write_file from vyos.validate import is_addr_assigned @@ -192,6 +193,8 @@ def apply(proxy): return None + if os.path.exists(squidguard_db_dir): + chmod_755(squidguard_db_dir) call('systemctl restart squid.service') return None diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py index f0b92aea8..4dd7f936d 100755 --- a/src/conf_mode/system-login.py +++ b/src/conf_mode/system-login.py @@ -59,7 +59,7 @@ def get_config(config=None): conf = Config() base = ['system', 'login'] login = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True) + no_tag_node_value_mangle=True, get_first_key=True) # users no longer existing in the running configuration need to be deleted local_users = get_local_users() @@ -80,12 +80,6 @@ def get_config(config=None): login['radius']['server'][server] = dict_merge(default_values, login['radius']['server'][server]) - # XXX: for a yet unknown reason when we only have one source-address - # get_config_dict() will show a string over a string - if 'radius' in login and 'source_address' in login['radius']: - if isinstance(login['radius']['source_address'], str): - login['radius']['source_address'] = [login['radius']['source_address']] - # create a list of all users, cli and users all_users = list(set(local_users + cli_users)) # We will remove any normal users that dos not exist in the current @@ -246,7 +240,9 @@ def apply(login): # XXX: Should we deny using root at all? home_dir = getpwnam(user).pw_dir render(f'{home_dir}/.ssh/authorized_keys', 'login/authorized_keys.tmpl', - user_config, permission=0o600, user=user, group='users') + user_config, permission=0o600, + formater=lambda _: _.replace(""", '"'), + user=user, group='users') except Exception as e: raise ConfigError(f'Adding user "{user}" raised exception: "{e}"') diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py index d3065fc47..99b82ca2d 100755 --- a/src/conf_mode/vpn_ipsec.py +++ b/src/conf_mode/vpn_ipsec.py @@ -286,20 +286,34 @@ def verify(ipsec): if 'pre_shared_secret' not in ra_conf['authentication']: raise ConfigError(f"Missing pre-shared-key on {name} remote-access config") + if 'client_mode' not in ra_conf['authentication']: + raise ConfigError('Client authentication method is required!') - if 'client_mode' in ra_conf['authentication']: - if ra_conf['authentication']['client_mode'] == 'eap-radius': - if 'radius' not in ipsec['remote_access'] or 'server' not in ipsec['remote_access']['radius'] or len(ipsec['remote_access']['radius']['server']) == 0: - raise ConfigError('RADIUS authentication requires at least one server') + if dict_search('authentication.client_mode', ra_conf) == 'eap-radius': + if dict_search('remote_access.radius.server', ipsec) == None: + raise ConfigError('RADIUS authentication requires at least one server') if 'pool' in ra_conf: + if {'dhcp', 'radius'} <= set(ra_conf['pool']): + raise ConfigError(f'Can not use both DHCP and RADIUS for address allocation '\ + f'at the same time for "{name}"!') + if 'dhcp' in ra_conf['pool'] and len(ra_conf['pool']) > 1: - raise ConfigError(f'Can not use both DHCP and a predefined address pool for "{name}"!') + raise ConfigError(f'Can not use DHCP and a predefined address pool for "{name}"!') + + if 'radius' in ra_conf['pool'] and len(ra_conf['pool']) > 1: + raise ConfigError(f'Can not use RADIUS and a predefined address pool for "{name}"!') for pool in ra_conf['pool']: if pool == 'dhcp': if dict_search('remote_access.dhcp.server', ipsec) == None: raise ConfigError('IPSec DHCP server is not configured!') + elif pool == 'radius': + if dict_search('remote_access.radius.server', ipsec) == None: + raise ConfigError('IPSec RADIUS server is not configured!') + + if dict_search('authentication.client_mode', ra_conf) != 'eap-radius': + raise ConfigError('RADIUS IP pool requires eap-radius client authentication!') elif 'pool' not in ipsec['remote_access'] or pool not in ipsec['remote_access']['pool']: raise ConfigError(f'Requested pool "{pool}" does not exist!') @@ -348,6 +362,9 @@ def verify(ipsec): if 'authentication' not in peer_conf or 'mode' not in peer_conf['authentication']: raise ConfigError(f"Missing authentication on site-to-site peer {peer}") + if {'id', 'use_x509_id'} <= set(peer_conf['authentication']): + raise ConfigError(f"Manually set peer id and use-x509-id are mutually exclusive!") + if peer_conf['authentication']['mode'] == 'x509': if 'x509' not in peer_conf['authentication']: raise ConfigError(f"Missing x509 settings on site-to-site peer {peer}") diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index c1cfc1dcb..919083ac4 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -24,7 +24,6 @@ from vyos.config import Config from vyos.configdict import node_changed from vyos.ifconfig import Interface from vyos.template import render -from vyos.template import render_to_string from vyos.util import call from vyos.util import cmd from vyos.util import dict_search @@ -32,12 +31,9 @@ from vyos.util import get_interface_config from vyos.util import popen from vyos.util import run from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() -frr_daemon = 'zebra' - config_file = r'/etc/iproute2/rt_tables.d/vyos-vrf.conf' def list_rules(): @@ -131,7 +127,6 @@ def verify(vrf): def generate(vrf): render(config_file, 'vrf/vrf.conf.tmpl', vrf) - vrf['new_frr_config'] = render_to_string('frr/vrf.frr.tmpl', vrf) # Render nftables zones config vrf['nft_vrf_zones'] = NamedTemporaryFile().name render(vrf['nft_vrf_zones'], 'firewall/nftables-vrf-zones.tmpl', vrf) @@ -242,21 +237,6 @@ def apply(vrf): if tmp == 0: cmd('nft delete table inet vrf_zones') - # T3694: Somehow we hit a priority inversion here as we need to remove the - # VRF assigned VNI before we can remove a BGP bound VRF instance. Maybe - # move this to an individual helper script that set's up the VNI for the - # given VRF after any routing protocol. - # - # # add configuration to FRR - # frr_cfg = frr.FRRConfig() - # frr_cfg.load_configuration(frr_daemon) - # frr_cfg.modify_section(f'^vrf [a-zA-Z-]*$', '') - # frr_cfg.add_before(r'(interface .*|line vty)', vrf['new_frr_config']) - # frr_cfg.commit_configuration(frr_daemon) - # - # # Save configuration to /run/frr/config/frr.conf - # frr.save_configuration() - return None if __name__ == '__main__': diff --git a/src/conf_mode/vrf_vni.py b/src/conf_mode/vrf_vni.py new file mode 100755 index 000000000..87ee8f2d1 --- /dev/null +++ b/src/conf_mode/vrf_vni.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from sys import argv +from sys import exit + +from vyos.config import Config +from vyos.template import render_to_string +from vyos import ConfigError +from vyos import frr +from vyos import airbag +airbag.enable() + +frr_daemon = 'zebra' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + # This script only works with a passed VRF name + if len(argv) < 1: + raise NotImplementedError + vrf = argv[1] + + # "assemble" dict - easier here then use a full blown get_config_dict() + # on a single leafNode + vni = { 'vrf' : vrf } + tmp = conf.return_value(['vrf', 'name', vrf, 'vni']) + if tmp: vni.update({ 'vni' : tmp }) + + return vni + +def verify(vni): + return None + +def generate(vni): + vni['new_frr_config'] = render_to_string('frr/vrf-vni.frr.tmpl', vni) + return None + +def apply(vni): + # add configuration to FRR + frr_cfg = frr.FRRConfig() + frr_cfg.load_configuration(frr_daemon) + frr_cfg.modify_section(f'^vrf [a-zA-Z-]*$', '') + frr_cfg.add_before(r'(interface .*|line vty)', vni['new_frr_config']) + frr_cfg.commit_configuration(frr_daemon) + + # Save configuration to /run/frr/config/frr.conf + frr.save_configuration() + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/vrrp.py b/src/conf_mode/vrrp.py index 680a80859..e8f1c1f99 100755 --- a/src/conf_mode/vrrp.py +++ b/src/conf_mode/vrrp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2020 VyOS maintainers and contributors +# Copyright (C) 2018-2021 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -17,244 +17,131 @@ import os from sys import exit -from ipaddress import ip_address, ip_interface, IPv4Interface, IPv6Interface, IPv4Address, IPv6Address -from json import dumps -from pathlib import Path - -import vyos.config - -from vyos import ConfigError -from vyos.util import call -from vyos.template import render +from ipaddress import ip_interface +from ipaddress import IPv4Interface +from ipaddress import IPv6Interface +from vyos.config import Config +from vyos.configdict import dict_merge from vyos.ifconfig.vrrp import VRRP - +from vyos.template import render +from vyos.template import is_ipv4 +from vyos.template import is_ipv6 +from vyos.util import call +from vyos.xml import defaults +from vyos import ConfigError from vyos import airbag airbag.enable() def get_config(config=None): - vrrp_groups = [] - sync_groups = [] - if config: - config = config + conf = config else: - config = vyos.config.Config() - - # Get the VRRP groups - for group_name in config.list_nodes("high-availability vrrp group"): - config.set_level("high-availability vrrp group {0}".format(group_name)) - - # Retrieve the values - group = {"preempt": True, "use_vmac": False, "disable": False} - - if config.exists("disable"): - group["disable"] = True - - group["name"] = group_name - group["vrid"] = config.return_value("vrid") - group["interface"] = config.return_value("interface") - group["description"] = config.return_value("description") - group["advertise_interval"] = config.return_value("advertise-interval") - group["priority"] = config.return_value("priority") - group["hello_source"] = config.return_value("hello-source-address") - group["peer_address"] = config.return_value("peer-address") - group["sync_group"] = config.return_value("sync-group") - group["preempt_delay"] = config.return_value("preempt-delay") - group["virtual_addresses"] = config.return_values("virtual-address") - group["virtual_addresses_excluded"] = config.return_values("virtual-address-excluded") - - group["auth_password"] = config.return_value("authentication password") - group["auth_type"] = config.return_value("authentication type") - - group["health_check_script"] = config.return_value("health-check script") - group["health_check_interval"] = config.return_value("health-check interval") - group["health_check_count"] = config.return_value("health-check failure-count") - - group["master_script"] = config.return_value("transition-script master") - group["backup_script"] = config.return_value("transition-script backup") - group["fault_script"] = config.return_value("transition-script fault") - group["stop_script"] = config.return_value("transition-script stop") - group["script_mode_force"] = config.exists("transition-script mode-force") - - if config.exists("no-preempt"): - group["preempt"] = False - if config.exists("rfc3768-compatibility"): - group["use_vmac"] = True - - # Substitute defaults where applicable - if not group["advertise_interval"]: - group["advertise_interval"] = 1 - if not group["priority"]: - group["priority"] = 100 - if not group["preempt_delay"]: - group["preempt_delay"] = 0 - if not group["health_check_interval"]: - group["health_check_interval"] = 60 - if not group["health_check_count"]: - group["health_check_count"] = 3 - - # FIXUP: translate our option for auth type to keepalived's syntax - # for simplicity - if group["auth_type"]: - if group["auth_type"] == "plaintext-password": - group["auth_type"] = "PASS" - else: - group["auth_type"] = "AH" - - vrrp_groups.append(group) - - config.set_level("") - - # Get the sync group used for conntrack-sync - conntrack_sync_group = None - if config.exists("service conntrack-sync failover-mechanism vrrp"): - conntrack_sync_group = config.return_value("service conntrack-sync failover-mechanism vrrp sync-group") - - # Get the sync groups - for sync_group_name in config.list_nodes("high-availability vrrp sync-group"): - config.set_level("high-availability vrrp sync-group {0}".format(sync_group_name)) - - sync_group = {"conntrack_sync": False} - sync_group["name"] = sync_group_name - sync_group["members"] = config.return_values("member") - if conntrack_sync_group: - if conntrack_sync_group == sync_group_name: - sync_group["conntrack_sync"] = True - - # add transition script configuration - sync_group["master_script"] = config.return_value("transition-script master") - sync_group["backup_script"] = config.return_value("transition-script backup") - sync_group["fault_script"] = config.return_value("transition-script fault") - sync_group["stop_script"] = config.return_value("transition-script stop") - - sync_groups.append(sync_group) - - # create a file with dict with proposed configuration - with open("{}.temp".format(VRRP.location['vyos']), 'w') as dict_file: - dict_file.write(dumps({'vrrp_groups': vrrp_groups, 'sync_groups': sync_groups})) - - return (vrrp_groups, sync_groups) - - -def verify(data): - vrrp_groups, sync_groups = data - - for group in vrrp_groups: - # Check required fields - if not group["vrid"]: - raise ConfigError("vrid is required but not set in VRRP group {0}".format(group["name"])) - if not group["interface"]: - raise ConfigError("interface is required but not set in VRRP group {0}".format(group["name"])) - if not group["virtual_addresses"]: - raise ConfigError("virtual-address is required but not set in VRRP group {0}".format(group["name"])) - - if group["auth_password"] and (not group["auth_type"]): - raise ConfigError("authentication type is required but not set in VRRP group {0}".format(group["name"])) - - # Keepalived doesn't allow mixing IPv4 and IPv6 in one group, so we mirror that restriction - - # XXX: filter on map object is destructive, so we force it to list. - # Additionally, filter objects always evaluate to True, empty or not, - # so we force them to lists as well. - vaddrs = list(map(lambda i: ip_interface(i), group["virtual_addresses"])) - vaddrs4 = list(filter(lambda x: isinstance(x, IPv4Interface), vaddrs)) - vaddrs6 = list(filter(lambda x: isinstance(x, IPv6Interface), vaddrs)) - - if vaddrs4 and vaddrs6: - raise ConfigError("VRRP group {0} mixes IPv4 and IPv6 virtual addresses, this is not allowed. Create separate groups for IPv4 and IPv6".format(group["name"])) - - if vaddrs4: - if group["hello_source"]: - hsa = ip_address(group["hello_source"]) - if isinstance(hsa, IPv6Address): - raise ConfigError("VRRP group {0} uses IPv4 but its hello-source-address is IPv6".format(group["name"])) - if group["peer_address"]: - pa = ip_address(group["peer_address"]) - if isinstance(pa, IPv6Address): - raise ConfigError("VRRP group {0} uses IPv4 but its peer-address is IPv6".format(group["name"])) - - if vaddrs6: - if group["hello_source"]: - hsa = ip_address(group["hello_source"]) - if isinstance(hsa, IPv4Address): - raise ConfigError("VRRP group {0} uses IPv6 but its hello-source-address is IPv4".format(group["name"])) - if group["peer_address"]: - pa = ip_address(group["peer_address"]) - if isinstance(pa, IPv4Address): - raise ConfigError("VRRP group {0} uses IPv6 but its peer-address is IPv4".format(group["name"])) - - # Warn the user about the deprecated mode-force option - if group['script_mode_force']: - print("""Warning: "transition-script mode-force" VRRP option is deprecated and will be removed in VyOS 1.4.""") - print("""It's no longer necessary, so you can safely remove it from your config now.""") - - # Disallow same VRID on multiple interfaces - _groups = sorted(vrrp_groups, key=(lambda x: x["interface"])) - count = len(_groups) - 1 - index = 0 - while (index < count): - if (_groups[index]["vrid"] == _groups[index + 1]["vrid"]) and (_groups[index]["interface"] == _groups[index + 1]["interface"]): - raise ConfigError("VRID {0} is used in groups {1} and {2} that both use interface {3}. Groups on the same interface must use different VRIDs".format( - _groups[index]["vrid"], _groups[index]["name"], _groups[index + 1]["name"], _groups[index]["interface"])) - else: - index += 1 - + conf = Config() + + base = ['high-availability', 'vrrp'] + if not conf.exists(base): + return None + + vrrp = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + if 'group' in vrrp: + default_values = defaults(base + ['group']) + for group in vrrp['group']: + vrrp['group'][group] = dict_merge(default_values, vrrp['group'][group]) + + ## Get the sync group used for conntrack-sync + conntrack_path = ['service', 'conntrack-sync', 'failover-mechanism', 'vrrp', 'sync-group'] + if conf.exists(conntrack_path): + vrrp['conntrack_sync_group'] = conf.return_value(conntrack_path) + + return vrrp + +def verify(vrrp): + if not vrrp: + return None + + used_vrid_if = [] + if 'group' in vrrp: + for group, group_config in vrrp['group'].items(): + # Check required fields + if 'vrid' not in group_config: + raise ConfigError(f'VRID is required but not set in VRRP group "{group}"') + + if 'interface' not in group_config: + raise ConfigError(f'Interface is required but not set in VRRP group "{group}"') + + if 'address' not in group_config: + raise ConfigError(f'Virtual IP address is required but not set in VRRP group "{group}"') + + if 'authentication' in group_config: + if not {'password', 'type'} <= set(group_config['authentication']): + raise ConfigError(f'Authentication requires both type and passwortd to be set in VRRP group "{group}"') + + # We can not use a VRID once per interface + interface = group_config['interface'] + vrid = group_config['vrid'] + tmp = {'interface': interface, 'vrid': vrid} + if tmp in used_vrid_if: + raise ConfigError(f'VRID "{vrid}" can only be used once on interface "{interface}"!') + used_vrid_if.append(tmp) + + # Keepalived doesn't allow mixing IPv4 and IPv6 in one group, so we mirror that restriction + + # XXX: filter on map object is destructive, so we force it to list. + # Additionally, filter objects always evaluate to True, empty or not, + # so we force them to lists as well. + vaddrs = list(map(lambda i: ip_interface(i), group_config['address'])) + vaddrs4 = list(filter(lambda x: isinstance(x, IPv4Interface), vaddrs)) + vaddrs6 = list(filter(lambda x: isinstance(x, IPv6Interface), vaddrs)) + + if vaddrs4 and vaddrs6: + raise ConfigError(f'VRRP group "{group}" mixes IPv4 and IPv6 virtual addresses, this is not allowed.\n' \ + 'Create individual groups for IPv4 and IPv6!') + if vaddrs4: + if 'hello_source_address' in group_config: + if is_ipv6(group_config['hello_source_address']): + raise ConfigError(f'VRRP group "{group}" uses IPv4 but hello-source-address is IPv6!') + + if 'peer_address' in group_config: + if is_ipv6(group_config['peer_address']): + raise ConfigError(f'VRRP group "{group}" uses IPv4 but peer-address is IPv6!') + + if vaddrs6: + if 'hello_source_address' in group_config: + if is_ipv4(group_config['hello_source_address']): + raise ConfigError(f'VRRP group "{group}" uses IPv6 but hello-source-address is IPv4!') + + if 'peer_address' in group_config: + if is_ipv4(group_config['peer_address']): + raise ConfigError(f'VRRP group "{group}" uses IPv6 but peer-address is IPv4!') # Check sync groups - vrrp_group_names = list(map(lambda x: x["name"], vrrp_groups)) - - for sync_group in sync_groups: - for m in sync_group["members"]: - if not (m in vrrp_group_names): - raise ConfigError("VRRP sync-group {0} refers to VRRP group {1}, but group {1} does not exist".format(sync_group["name"], m)) - - -def generate(data): - vrrp_groups, sync_groups = data - - # Remove disabled groups from the sync group member lists - for sync_group in sync_groups: - for member in sync_group["members"]: - g = list(filter(lambda x: x["name"] == member, vrrp_groups))[0] - if g["disable"]: - print("Warning: ignoring disabled VRRP group {0} in sync-group {1}".format(g["name"], sync_group["name"])) - # Filter out disabled groups - vrrp_groups = list(filter(lambda x: x["disable"] is not True, vrrp_groups)) - - render(VRRP.location['config'], 'vrrp/keepalived.conf.tmpl', - {"groups": vrrp_groups, "sync_groups": sync_groups}) - render(VRRP.location['daemon'], 'vrrp/daemon.tmpl', {}) + if 'sync_group' in vrrp: + for sync_group, sync_config in vrrp['sync_group'].items(): + if 'member' in sync_config: + for member in sync_config['member']: + if member not in vrrp['group']: + raise ConfigError(f'VRRP sync-group "{sync_group}" refers to VRRP group "{member}", '\ + 'but it does not exist!') + +def generate(vrrp): + if not vrrp: + return None + + render(VRRP.location['config'], 'vrrp/keepalived.conf.tmpl', vrrp) return None +def apply(vrrp): + service_name = 'keepalived.service' + if not vrrp: + call(f'systemctl stop {service_name}') + return None -def apply(data): - vrrp_groups, sync_groups = data - if vrrp_groups: - # safely rename a temporary file with configuration dict - try: - dict_file = Path("{}.temp".format(VRRP.location['vyos'])) - dict_file.rename(Path(VRRP.location['vyos'])) - except Exception as err: - print("Unable to rename the file with keepalived config for FIFO pipe: {}".format(err)) - - if not VRRP.is_running(): - print("Starting the VRRP process") - ret = call("systemctl restart keepalived.service") - else: - print("Reloading the VRRP process") - ret = call("systemctl reload keepalived.service") - - if ret != 0: - raise ConfigError("keepalived failed to start") - else: - # VRRP is removed in the commit - print("Stopping the VRRP process") - call("systemctl stop keepalived.service") - os.unlink(VRRP.location['daemon']) - + call(f'systemctl restart {service_name}') return None - if __name__ == '__main__': try: c = get_config() @@ -262,5 +149,5 @@ if __name__ == '__main__': generate(c) apply(c) except ConfigError as e: - print("VRRP error: {0}".format(str(e))) + print(e) exit(1) diff --git a/src/etc/dhcp/dhclient-exit-hooks.d/ipsec-dhclient-hook b/src/etc/dhcp/dhclient-exit-hooks.d/ipsec-dhclient-hook index a7a9a2ce6..61a89e62a 100755 --- a/src/etc/dhcp/dhclient-exit-hooks.d/ipsec-dhclient-hook +++ b/src/etc/dhcp/dhclient-exit-hooks.d/ipsec-dhclient-hook @@ -35,19 +35,14 @@ fi python3 - <<PYEND import os import re + from vyos.util import call from vyos.util import cmd +from vyos.util import read_file +from vyos.util import write_file SWANCTL_CONF="/etc/swanctl/swanctl.conf" -def getlines(file): - with open(file, 'r') as f: - return f.readlines() - -def writelines(file, lines): - with open(file, 'w') as f: - f.writelines(lines) - def ipsec_down(ip_address): # This prevents the need to restart ipsec and kill all active connections, only the stale connection is closed status = cmd('sudo ipsec statusall') @@ -66,23 +61,26 @@ if __name__ == '__main__': new_ip = os.getenv('new_ip_address') old_ip = os.getenv('old_ip_address') - conf_lines = getlines(SWANCTL_CONF) - found = False - to_match = f'# dhcp:{interface}' + if os.path.exists(SWANCTL_CONF): + conf_lines = read_file(SWANCTL_CONF) + found = False + to_match = f'# dhcp:{interface}' + + for i, line in enumerate(conf_lines): + if line.find(to_match) > 0: + conf_lines[i] = line.replace(old_ip, new_ip) + found = True - for i, line in enumerate(conf_lines): - if line.find(to_match) > 0: - conf_lines[i] = line.replace(old_ip, new_ip) - found = True + for i, line in enumerate(secrets_lines): + if line.find(to_match) > 0: + secrets_lines[i] = line.replace(old_ip, new_ip) - for i, line in enumerate(secrets_lines): - if line.find(to_match) > 0: - secrets_lines[i] = line.replace(old_ip, new_ip) + if found: + write_file(SWANCTL_CONF, conf_lines) + ipsec_down(old_ip) + call('sudo ipsec rereadall') + call('sudo ipsec reload') + call('sudo swanctl -q') - if found: - writelines(SWANCTL_CONF, conf_lines) - ipsec_down(old_ip) - call('sudo ipsec rereadall') - call('sudo ipsec reload') - call('sudo swanctl -q') + exit(0) PYEND
\ No newline at end of file diff --git a/src/etc/ipsec.d/vti-up-down b/src/etc/ipsec.d/vti-up-down index 281c9bf2b..1ffb32955 100755 --- a/src/etc/ipsec.d/vti-up-down +++ b/src/etc/ipsec.d/vti-up-down @@ -29,19 +29,10 @@ from vyos.util import call from vyos.util import get_interface_config from vyos.util import get_interface_address -def get_dhcp_address(interface): - addr = get_interface_address(interface) - if not addr: - return None - if len(addr['addr_info']) == 0: - return None - return addr['addr_info'][0]['local'] - if __name__ == '__main__': verb = os.getenv('PLUTO_VERB') connection = os.getenv('PLUTO_CONNECTION') interface = sys.argv[1] - dhcp_interface = sys.argv[2] openlog(ident=f'vti-up-down', logoption=LOG_PID, facility=LOG_INFO) syslog(f'Interface {interface} {verb} {connection}') @@ -55,7 +46,7 @@ if __name__ == '__main__': syslog(f'Interface {interface} not found') sys.exit(0) - vti_link_up = (vti_link['operstate'] == 'UP' if 'operstate' in vti_link else False) + vti_link_up = (vti_link['operstate'] != 'DOWN' if 'operstate' in vti_link else False) config = ConfigTreeQuery() vti_dict = config.get_config_dict(['interfaces', 'vti', interface], @@ -63,9 +54,6 @@ if __name__ == '__main__': if verb in ['up-client', 'up-host']: if not vti_link_up: - if dhcp_interface != 'no': - local_ip = get_dhcp_address(dhcp_interface) - call(f'sudo ip tunnel change {interface} local {local_ip}') if 'disable' not in vti_dict: call(f'sudo ip link set {interface} up') else: diff --git a/src/etc/ppp/ip-up.d/99-vyos-pppoe-callback b/src/etc/ppp/ip-up.d/99-vyos-pppoe-callback new file mode 100755 index 000000000..bb918a468 --- /dev/null +++ b/src/etc/ppp/ip-up.d/99-vyos-pppoe-callback @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# This is a Python hook script which is invoked whenever a PPPoE session goes +# "ip-up". It will call into our vyos.ifconfig library and will then execute +# common tasks for the PPPoE interface. The reason we have to "hook" this is +# that we can not create a pppoeX interface in advance in linux and then connect +# pppd to this already existing interface. + +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.ifconfig import PPPoEIf +from vyos.util import read_file + +# When the ppp link comes up, this script is called with the following +# parameters +# $1 the interface name used by pppd (e.g. ppp3) +# $2 the tty device name +# $3 the tty device speed +# $4 the local IP address for the interface +# $5 the remote IP address +# $6 the parameter specified by the 'ipparam' option to pppd + +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] + +p = PPPoEIf(pppoe['ifname']) +p.update(pppoe) diff --git a/src/etc/sysctl.d/32-vyos-podman.conf b/src/etc/sysctl.d/32-vyos-podman.conf new file mode 100644 index 000000000..7068bf88d --- /dev/null +++ b/src/etc/sysctl.d/32-vyos-podman.conf @@ -0,0 +1,5 @@ +# Increase inotify watchers as per https://bugzilla.redhat.com/show_bug.cgi?id=1829596 +fs.inotify.max_queued_events = 1048576 +fs.inotify.max_user_instances = 1048576 +fs.inotify.max_user_watches = 1048576 + diff --git a/src/etc/systemd/system/keepalived.service.d/override.conf b/src/etc/systemd/system/keepalived.service.d/override.conf index 9fcabf652..1c68913f2 100644 --- a/src/etc/systemd/system/keepalived.service.d/override.conf +++ b/src/etc/systemd/system/keepalived.service.d/override.conf @@ -1,2 +1,13 @@ +[Unit] +ConditionPathExists= +ConditionPathExists=/run/keepalived/keepalived.conf +After= +After=vyos-router.service + [Service] KillMode=process +EnvironmentFile= +ExecStart= +ExecStart=/usr/sbin/keepalived --use-file /run/keepalived/keepalived.conf --pid /run/keepalived/keepalived.pid --dont-fork --snmp +PIDFile= +PIDFile=/run/keepalived/keepalived.pid diff --git a/src/etc/udev/rules.d/65-vyatta-net.rules b/src/etc/udev/rules.d/65-vyatta-net.rules deleted file mode 100644 index 2b48c1213..000000000 --- a/src/etc/udev/rules.d/65-vyatta-net.rules +++ /dev/null @@ -1,26 +0,0 @@ -# These rules use vyatta_net_name to persistently name network interfaces -# per "hwid" association in the Vyatta configuration file. - -ACTION!="add", GOTO="vyatta_net_end" -SUBSYSTEM!="net", GOTO="vyatta_net_end" - -# ignore the interface if a name has already been set -NAME=="?*", GOTO="vyatta_net_end" - -# Do name change for ethernet and wireless devices only -KERNEL!="eth*|wlan*", GOTO="vyatta_net_end" - -# ignore "secondary" monitor interfaces of mac80211 drivers -KERNEL=="wlan*", ATTRS{type}=="803", GOTO="vyatta_net_end" - -# If using VyOS predefined names -ENV{VYOS_IFNAME}!="eth*", GOTO="end_vyos_predef_names" - -DRIVERS=="?*", PROGRAM="vyatta_net_name %k $attr{address} $env{VYOS_IFNAME}", NAME="%c", GOTO="vyatta_net_end" - -LABEL="end_vyos_predef_names" - -# ignore interfaces without a driver link like bridges and VLANs -DRIVERS=="?*", PROGRAM="vyatta_net_name %k $attr{address}", NAME="%c" - -LABEL="vyatta_net_end" diff --git a/src/etc/udev/rules.d/65-vyos-net.rules b/src/etc/udev/rules.d/65-vyos-net.rules new file mode 100644 index 000000000..c8d5750dd --- /dev/null +++ b/src/etc/udev/rules.d/65-vyos-net.rules @@ -0,0 +1,26 @@ +# These rules use vyos_net_name to persistently name network interfaces +# per "hwid" association in the VyOS configuration file. + +ACTION!="add", GOTO="vyos_net_end" +SUBSYSTEM!="net", GOTO="vyos_net_end" + +# ignore the interface if a name has already been set +NAME=="?*", GOTO="vyos_net_end" + +# Do name change for ethernet and wireless devices only +KERNEL!="eth*|wlan*", GOTO="vyos_net_end" + +# ignore "secondary" monitor interfaces of mac80211 drivers +KERNEL=="wlan*", ATTRS{type}=="803", GOTO="vyos_net_end" + +# If using VyOS predefined names +ENV{VYOS_IFNAME}!="eth*", GOTO="end_vyos_predef_names" + +DRIVERS=="?*", PROGRAM="vyos_net_name %k $attr{address} $env{VYOS_IFNAME}", NAME="%c", GOTO="vyos_net_end" + +LABEL="end_vyos_predef_names" + +# ignore interfaces without a driver link like bridges and VLANs +DRIVERS=="?*", PROGRAM="vyos_net_name %k $attr{address}", NAME="%c" + +LABEL="vyos_net_end" diff --git a/src/etc/udev/rules.d/90-vyos-serial.rules b/src/etc/udev/rules.d/90-vyos-serial.rules index 3f10f4924..872fd4fea 100644 --- a/src/etc/udev/rules.d/90-vyos-serial.rules +++ b/src/etc/udev/rules.d/90-vyos-serial.rules @@ -8,7 +8,7 @@ SUBSYSTEMS=="pci", IMPORT{builtin}="hwdb --subsystem=pci" SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id", IMPORT{builtin}="hwdb --subsystem=usb" # /dev/serial/by-path/, /dev/serial/by-id/ for USB devices -KERNEL!="ttyUSB[0-9]*|ttyACM[0-9]*", GOTO="serial_end" +KERNEL!="ttyUSB[0-9]*", GOTO="serial_end" SUBSYSTEMS=="usb-serial", ENV{.ID_PORT}="$attr{port_number}" @@ -18,11 +18,11 @@ IMPORT{builtin}="path_id", IMPORT{builtin}="usb_id" # # - $env{ID_PATH} usually is a name like: "pci-0000:00:10.0-usb-0:2.3.3.4:1.0-port0" so we strip the "pci-*" # portion and only use the usb part -# - Transform the USB "speach" to the tree like structure so we start with "usb0" as root-complex 0. +# - Transform the USB "speech" to the tree like structure so we start with "usb0" as root-complex 0. # (tr -d -) does the replacement # - Replace the first group after ":" to represent the bus relation (sed -e 0,/:/s//b/) indicated by "b" # - Replace the next group after ":" to represent the port relation (sed -e 0,/:/s//p/) indicated by "p" -ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="", PROGRAM="/bin/sh -c 'D=$env{ID_PATH}; echo ${D:17} | tr -d - | sed -e 0,/:/s//b/ | sed -e 0,/:/s//p/'", SYMLINK+="serial/by-bus/$result" -ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="?*", PROGRAM="/bin/sh -c 'D=$env{ID_PATH}; echo ${D:17} | tr -d - | sed -e 0,/:/s//b/ | sed -e 0,/:/s//p/'", SYMLINK+="serial/by-bus/$result" +ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="", PROGRAM="/bin/sh -c 'echo $env{ID_PATH:17} | tr -d - | sed -e 0,/:/s//b/ | sed -e 0,/:/s//p/'", SYMLINK+="serial/by-bus/$result" +ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="?*", PROGRAM="/bin/sh -c 'echo $env{ID_PATH:17} | tr -d - | sed -e 0,/:/s//b/ | sed -e 0,/:/s//p/'", SYMLINK+="serial/by-bus/$result" LABEL="serial_end" diff --git a/src/etc/update-motd.d/99-reboot b/src/etc/update-motd.d/99-reboot new file mode 100755 index 000000000..718be1a7a --- /dev/null +++ b/src/etc/update-motd.d/99-reboot @@ -0,0 +1,7 @@ +#!/bin/vbash +source /opt/vyatta/etc/functions/script-template +if [ -f /run/systemd/shutdown/scheduled ]; then + echo + run show reboot +fi +exit diff --git a/src/helpers/strip-private.py b/src/helpers/strip-private.py index c165d2cba..c74a379aa 100755 --- a/src/helpers/strip-private.py +++ b/src/helpers/strip-private.py @@ -47,7 +47,7 @@ ipv4_re = re.compile(r'(\d{1,3}\.){2}(\d{1,3}\.\d{1,3})') ipv4_subst = r'xxx.xxx.\2' # Censor all but the first two fields. -ipv6_re = re.compile(r'([0-9a-fA-F]{1,4}\:){2}(\S+)') +ipv6_re = re.compile(r'([0-9a-fA-F]{1,4}\:){2}([0-9a-fA-F:]+)') ipv6_subst = r'xxxx:xxxx:\2' def ip_match(match: re.Match, subst: str) -> str: @@ -96,12 +96,12 @@ if __name__ == "__main__": args = parser.parse_args() # Strict mode is the default and the absence of loose mode implies presence of strict mode. if not args.loose: - for arg in [args.mac, args.domain, args.hostname, args.username, args.dhcp, args.asn, args.snmp, args.lldp]: - arg = True + args.mac = args.domain = args.hostname = args.username = args.dhcp = args.asn = args.snmp = args.lldp = True if not args.public_address and not args.keep_address: args.address = True elif not args.address and not args.public_address: args.keep_address = True + # (condition, precompiled regexp, substitution string) stripping_rules = [ # Strip passwords @@ -120,7 +120,7 @@ if __name__ == "__main__": (True, re.compile(r'private-key \S+'), 'private-key xxxxxx'), # Strip MAC addresses - (args.mac, re.compile(r'([0-9a-fA-F]{2}\:){5}([0-9a-fA-F]{2}((\:{0,1})){3})'), r'XX:XX:XX:XX:XX:\2'), + (args.mac, re.compile(r'([0-9a-fA-F]{2}\:){5}([0-9a-fA-F]{2}((\:{0,1})){3})'), r'xx:xx:xx:xx:xx:\2'), # Strip host-name, domain-name, and domain-search (args.hostname, re.compile(r'(host-name|domain-name|domain-search) \S+'), r'\1 xxxxxx'), diff --git a/src/helpers/vyos-interface-rescan.py b/src/helpers/vyos-interface-rescan.py new file mode 100755 index 000000000..1ac1810e0 --- /dev/null +++ b/src/helpers/vyos-interface-rescan.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# + +import os +import stat +import argparse +import logging +import netaddr + +from vyos.configtree import ConfigTree +from vyos.defaults import directories +from vyos.util import get_cfg_group_id + +debug = False + +vyos_udev_dir = directories['vyos_udev_dir'] +vyos_log_dir = directories['log'] +log_file = os.path.splitext(os.path.basename(__file__))[0] +vyos_log_file = os.path.join(vyos_log_dir, log_file) + +logger = logging.getLogger(__name__) +handler = logging.FileHandler(vyos_log_file, mode='a') +formatter = logging.Formatter('%(levelname)s: %(message)s') +handler.setFormatter(formatter) +logger.addHandler(handler) + +passlist = { + '02:07:01' : 'Interlan', + '02:60:60' : '3Com', + '02:60:8c' : '3Com', + '02:a0:c9' : 'Intel', + '02:aa:3c' : 'Olivetti', + '02:cf:1f' : 'CMC', + '02:e0:3b' : 'Prominet', + '02:e6:d3' : 'BTI', + '52:54:00' : 'Realtek', + '52:54:4c' : 'Novell 2000', + '52:54:ab' : 'Realtec', + 'e2:0c:0f' : 'Kingston Technologies' +} + +def is_multicast(addr: netaddr.eui.EUI) -> bool: + return bool(addr.words[0] & 0b1) + +def is_locally_administered(addr: netaddr.eui.EUI) -> bool: + return bool(addr.words[0] & 0b10) + +def is_on_passlist(hwid: str) -> bool: + top = hwid.rsplit(':', 3)[0] + if top in list(passlist): + return True + return False + +def is_persistent(hwid: str) -> bool: + addr = netaddr.EUI(hwid) + if is_multicast(addr): + return False + if is_locally_administered(addr) and not is_on_passlist(hwid): + return False + return True + +def get_wireless_physical_device(intf: str) -> str: + if 'wlan' not in intf: + return '' + try: + tmp = os.readlink(f'/sys/class/net/{intf}/phy80211') + except OSError: + logger.critical(f"Failed to read '/sys/class/net/{intf}/phy80211'") + return '' + phy = os.path.basename(tmp) + logger.info(f"wireless phy is {phy}") + return phy + +def get_interface_type(intf: str) -> str: + if 'eth' in intf: + intf_type = 'ethernet' + elif 'wlan' in intf: + intf_type = 'wireless' + else: + logger.critical('Unrecognized interface type!') + intf_type = '' + return intf_type + +def get_new_interfaces() -> dict: + """ Read any new interface data left in /run/udev/vyos by vyos_net_name + """ + interfaces = {} + + for intf in os.listdir(vyos_udev_dir): + path = os.path.join(vyos_udev_dir, intf) + try: + with open(path) as f: + hwid = f.read().rstrip() + except OSError as e: + logger.error(f"OSError {e}") + continue + interfaces[intf] = hwid + + # reverse sort to simplify insertion in config + interfaces = {key: value for key, value in sorted(interfaces.items(), + reverse=True)} + return interfaces + +def filter_interfaces(intfs: dict) -> dict: + """ Ignore no longer existing interfaces or non-persistent mac addresses + """ + filtered = {} + + for intf, hwid in intfs.items(): + if not os.path.isdir(os.path.join('/sys/class/net', intf)): + continue + if not is_persistent(hwid): + continue + filtered[intf] = hwid + + return filtered + +def interface_rescan(config_path: str): + """ Read new data and update config file + """ + interfaces = get_new_interfaces() + + logger.debug(f"interfaces from udev: {interfaces}") + + interfaces = filter_interfaces(interfaces) + + logger.debug(f"filtered interfaces: {interfaces}") + + try: + with open(config_path) as f: + config_file = f.read() + except OSError as e: + logger.critical(f"OSError {e}") + exit(1) + + config = ConfigTree(config_file) + + for intf, hwid in interfaces.items(): + logger.info(f"Writing '{intf}' '{hwid}' to config file") + intf_type = get_interface_type(intf) + if not intf_type: + continue + if not config.exists(['interfaces', intf_type]): + config.set(['interfaces', intf_type]) + config.set_tag(['interfaces', intf_type]) + config.set(['interfaces', intf_type, intf, 'hw-id'], value=hwid) + + if intf_type == 'wireless': + phy = get_wireless_physical_device(intf) + if not phy: + continue + config.set(['interfaces', intf_type, intf, 'physical-device'], + value=phy) + + try: + with open(config_path, 'w') as f: + f.write(config.to_string()) + except OSError as e: + logger.critical(f"OSError {e}") + +def main(): + global debug + + argparser = argparse.ArgumentParser( + formatter_class=argparse.RawTextHelpFormatter) + argparser.add_argument('configfile', type=str) + argparser.add_argument('--debug', action='store_true') + args = argparser.parse_args() + + if args.debug: + debug = True + logger.setLevel(logging.DEBUG) + else: + logger.setLevel(logging.INFO) + + configfile = args.configfile + + # preserve vyattacfg group write access to running config + os.setgid(get_cfg_group_id()) + os.umask(0o002) + + # log file perms are not automatic; this could be cleaner by moving to a + # logging config file + os.chown(vyos_log_file, 0, get_cfg_group_id()) + os.chmod(vyos_log_file, + stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH) + + interface_rescan(configfile) + +if __name__ == '__main__': + main() diff --git a/src/helpers/vyos_net_name b/src/helpers/vyos_net_name new file mode 100755 index 000000000..0652e98b1 --- /dev/null +++ b/src/helpers/vyos_net_name @@ -0,0 +1,229 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# + +import os +import re +import time +import logging +import threading +from sys import argv + +from vyos.configtree import ConfigTree +from vyos.defaults import directories +from vyos.util import cmd + +vyos_udev_dir = directories['vyos_udev_dir'] +vyos_log_dir = '/run/udev/log' +vyos_log_file = os.path.join(vyos_log_dir, 'vyos-net-name') + +config_path = '/opt/vyatta/etc/config/config.boot' +config_status = '/tmp/vyos-config-status' + +lock = threading.Lock() + +try: + os.mkdir(vyos_log_dir) +except FileExistsError: + pass + +logging.basicConfig(filename=vyos_log_file, level=logging.DEBUG) + +def boot_configuration_complete() -> bool: + """ Check if vyos-router has completed, hence hotplug event + """ + if os.path.isfile(config_status): + return True + return False + +def is_available(intfs: dict, intf_name: str) -> bool: + """ Check if interface name is already assigned + """ + if intf_name in list(intfs.values()): + return False + return True + +def find_available(intfs: dict, prefix: str) -> str: + """ Find lowest indexed iterface name that is not assigned + """ + index_list = [int(x.replace(prefix, '')) for x in list(intfs.values()) if prefix in x] + index_list.sort() + # find 'holes' in list, if any + missing = sorted(set(range(index_list[0], index_list[-1])) - set(index_list)) + if missing: + return f'{prefix}{missing[0]}' + + return f'{prefix}{len(index_list)}' + +def get_biosdevname(ifname: str) -> str: + """ Use legacy vyatta-biosdevname to query for name + + This is carried over for compatability only, and will likely be dropped + going forward. + XXX: This throws an error, and likely has for a long time, unnoticed + since vyatta_net_name redirected stderr to /dev/null. + """ + if 'eth' not in ifname: + return ifname + if os.path.isdir('/proc/xen'): + return ifname + + time.sleep(1) + + try: + biosname = cmd(f'/sbin/biosdevname --policy all_ethN -i {ifname}') + except Exception as e: + logging.error(f'biosdevname error: {e}') + biosname = '' + + return ifname if biosname == '' else biosname + +def leave_rescan_hint(intf_name: str, hwid: str): + """Write interface information reported by udev + + This script is called while the root mount is still read-only. Leave + information in /run/udev: file name, the interface; contents, the + hardware id. + """ + try: + os.mkdir(vyos_udev_dir) + except FileExistsError: + pass + except Exception as e: + logging.critical(f"Error creating rescan hint directory: {e}") + exit(1) + + try: + with open(os.path.join(vyos_udev_dir, intf_name), 'w') as f: + f.write(hwid) + except OSError as e: + logging.critical(f"OSError {e}") + +def get_configfile_interfaces() -> dict: + """Read existing interfaces from config file + """ + interfaces: dict = {} + + if not os.path.isfile(config_path): + # If the case, then we are running off of livecd; return empty + return interfaces + + try: + with open(config_path) as f: + config_file = f.read() + except OSError as e: + logging.critical(f"OSError {e}") + exit(1) + + config = ConfigTree(config_file) + + base = ['interfaces', 'ethernet'] + if config.exists(base): + eth_intfs = config.list_nodes(base) + for intf in eth_intfs: + path = base + [intf, 'hw-id'] + if not config.exists(path): + logging.warning(f"no 'hw-id' entry for {intf}") + continue + hwid = config.return_value(path) + if hwid in list(interfaces): + logging.warning(f"multiple entries for {hwid}: {interfaces[hwid]}, {intf}") + continue + interfaces[hwid] = intf + + base = ['interfaces', 'wireless'] + if config.exists(base): + wlan_intfs = config.list_nodes(base) + for intf in wlan_intfs: + path = base + [intf, 'hw-id'] + if not config.exists(path): + logging.warning(f"no 'hw-id' entry for {intf}") + continue + hwid = config.return_value(path) + if hwid in list(interfaces): + logging.warning(f"multiple entries for {hwid}: {interfaces[hwid]}, {intf}") + continue + interfaces[hwid] = intf + + logging.debug(f"config file entries: {interfaces}") + + return interfaces + +def add_assigned_interfaces(intfs: dict): + """Add interfaces found by previous invocation of udev rule + """ + if not os.path.isdir(vyos_udev_dir): + return + + for intf in os.listdir(vyos_udev_dir): + path = os.path.join(vyos_udev_dir, intf) + try: + with open(path) as f: + hwid = f.read().rstrip() + except OSError as e: + logging.error(f"OSError {e}") + continue + intfs[hwid] = intf + +def on_boot_event(intf_name: str, hwid: str, predefined: str = '') -> str: + """Called on boot by vyos-router: 'coldplug' in vyatta_net_name + """ + logging.info(f"lookup {intf_name}, {hwid}") + interfaces = get_configfile_interfaces() + logging.debug(f"config file interfaces are {interfaces}") + + if hwid in list(interfaces) and intf_name == interfaces[hwid]: + logging.info(f"use mapping from config file: '{hwid}' -> '{intf_name}'") + return intf_name + + add_assigned_interfaces(interfaces) + logging.debug(f"adding assigned interfaces: {interfaces}") + + if predefined: + newname = predefined + logging.info(f"predefined interface name for '{intf_name}' is '{newname}'") + else: + newname = get_biosdevname(intf_name) + logging.info(f"biosdevname returned '{newname}' for '{intf_name}'") + + if not is_available(interfaces, newname): + prefix = re.sub(r'\d+$', '', newname) + newname = find_available(interfaces, prefix) + + logging.info(f"new name for '{intf_name}' is '{newname}'") + + leave_rescan_hint(newname, hwid) + + return newname + +def hotplug_event(): + # Not yet implemented, since interface-rescan will only be run on boot. + pass + +if len(argv) > 3: + predef_name = argv[3] +else: + predef_name = '' + +lock.acquire() +if not boot_configuration_complete(): + res = on_boot_event(argv[1], argv[2], predefined=predef_name) + logging.debug(f"on boot, returned name is {res}") +else: + logging.debug("boot configuration complete") +lock.release() + diff --git a/src/migration-scripts/bgp/1-to-2 b/src/migration-scripts/bgp/1-to-2 new file mode 100755 index 000000000..4c6d5ceb8 --- /dev/null +++ b/src/migration-scripts/bgp/1-to-2 @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# T3741: no-ipv4-unicast is now enabled by default + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree +from vyos.template import is_ipv4 + +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 = ['protocols', 'bgp'] +config = ConfigTree(config_file) + +if not config.exists(base): + # Nothing to do + exit(0) + +# This is now a default option - simply delete it. +# As it was configured explicitly - we can also bail out early as we need to +# do nothing! +if config.exists(base + ['parameters', 'default', 'no-ipv4-unicast']): + config.delete(base + ['parameters', 'default', 'no-ipv4-unicast']) + + # Check if the "default" node is now empty, if so - remove it + if len(config.list_nodes(base + ['parameters', 'default'])) == 0: + config.delete(base + ['parameters', 'default']) + + # Check if the "default" node is now empty, if so - remove it + if len(config.list_nodes(base + ['parameters'])) == 0: + config.delete(base + ['parameters']) + + exit(0) + +# As we now install a new default option into BGP we need to migrate all +# existing BGP neighbors and restore the old behavior +if config.exists(base + ['neighbor']): + for neighbor in config.list_nodes(base + ['neighbor']): + peer_group = base + ['neighbor', neighbor, 'peer-group'] + if config.exists(peer_group): + peer_group_name = config.return_value(peer_group) + # peer group enables old behavior for neighbor - bail out + if config.exists(base + ['peer-group', peer_group_name, 'address-family', 'ipv4-unicast']): + continue + + afi_ipv4 = base + ['neighbor', neighbor, 'address-family', 'ipv4-unicast'] + if not config.exists(afi_ipv4): + config.set(afi_ipv4) + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print(f'Failed to save the modified config: {e}') + exit(1) diff --git a/src/migration-scripts/conntrack/2-to-3 b/src/migration-scripts/conntrack/2-to-3 new file mode 100755 index 000000000..8a8b43279 --- /dev/null +++ b/src/migration-scripts/conntrack/2-to-3 @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 + +# Conntrack syntax version 3 +# Enables all conntrack modules (previous default behaviour) and omits manually disabled modules. + +import sys + +from vyos.configtree import ConfigTree +from vyos.version import get_version + +if len(sys.argv) < 1: + print('Must specify file name!') + sys.exit(1) + +filename = sys.argv[1] + +with open(filename, 'r') as f: + config = ConfigTree(f.read()) + +module_path = ['system', 'conntrack', 'modules'] + +# Go over all conntrack modules available as of v1.3.0. +for module in ['ftp', 'h323', 'nfs', 'pptp', 'sip', 'sqlnet', 'tftp']: + # 'disable' is being phased out. + if config.exists(module_path + [module, 'disable']): + config.delete(module_path + [module]) + # If it wasn't manually 'disable'd, it was enabled by default. + else: + config.set(module_path + [module]) + +try: + if config.exists(module_path): + with open(filename, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print(f'Failed to save the modified config: {e}') + sys.exit(1) diff --git a/src/migration-scripts/dhcp-server/5-to-6 b/src/migration-scripts/dhcp-server/5-to-6 new file mode 100755 index 000000000..aefe84737 --- /dev/null +++ b/src/migration-scripts/dhcp-server/5-to-6 @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# T1968: allow multiple static-routes to be configured +# T3838: rename dns-server -> name-server + +import sys +from vyos.configtree import ConfigTree + +if (len(sys.argv) < 1): + print("Must specify file name!") + sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +base = ['service', 'dhcp-server'] +config = ConfigTree(config_file) + +if not config.exists(base + ['shared-network-name']): + # Nothing to do + exit(0) + +# Run this for every instance if 'shared-network-name' +for network in config.list_nodes(base + ['shared-network-name']): + base_network = base + ['shared-network-name', network] + + if not config.exists(base_network + ['subnet']): + continue + + # Run this for every specified 'subnet' + for subnet in config.list_nodes(base_network + ['subnet']): + base_subnet = base_network + ['subnet', subnet] + + # T1968: allow multiple static-routes to be configured + if config.exists(base_subnet + ['static-route']): + prefix = config.return_value(base_subnet + ['static-route', 'destination-subnet']) + router = config.return_value(base_subnet + ['static-route', 'router']) + config.delete(base_subnet + ['static-route']) + + config.set(base_subnet + ['static-route', prefix, 'next-hop'], value=router) + config.set_tag(base_subnet + ['static-route']) + + # T3838: rename dns-server -> name-server + if config.exists(base_subnet + ['dns-server']): + config.rename(base_subnet + ['dns-server'], 'name-server') + + + # T3672: ISC DHCP server only supports one failover peer + if config.exists(base_subnet + ['failover']): + # There can only be one failover configuration, if none is present + # we add the first one + if not config.exists(base + ['failover']): + local = config.return_value(base_subnet + ['failover', 'local-address']) + remote = config.return_value(base_subnet + ['failover', 'peer-address']) + status = config.return_value(base_subnet + ['failover', 'status']) + name = config.return_value(base_subnet + ['failover', 'name']) + + config.set(base + ['failover', 'remote'], value=remote) + config.set(base + ['failover', 'source-address'], value=local) + config.set(base + ['failover', 'status'], value=status) + config.set(base + ['failover', 'name'], value=name) + + config.delete(base_subnet + ['failover']) + config.set(base_subnet + ['enable-failover']) + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print("Failed to save the modified config: {}".format(e)) + exit(1) diff --git a/src/migration-scripts/dns-forwarding/1-to-2 b/src/migration-scripts/dns-forwarding/1-to-2 index 8c4f4b5c7..ba10c26f2 100755 --- a/src/migration-scripts/dns-forwarding/1-to-2 +++ b/src/migration-scripts/dns-forwarding/1-to-2 @@ -67,8 +67,14 @@ if config.exists(base + ['listen-on']): # retrieve corresponding interface addresses in CIDR format # those need to be converted in pure IP addresses without network information path = ['interfaces', section, intf, 'address'] - for addr in config.return_values(path): - listen_addr.append( ip_interface(addr).ip ) + try: + for addr in config.return_values(path): + listen_addr.append( ip_interface(addr).ip ) + except: + # Some interface types do not use "address" option (e.g. OpenVPN) + # and may not even have a fixed address + print("Could not retrieve the address of the interface {} from the config".format(intf)) + print("You will need to update your DNS forwarding configuration manually") for addr in listen_addr: config.set(base + ['listen-address'], value=addr, replace=False) diff --git a/src/migration-scripts/firewall/5-to-6 b/src/migration-scripts/firewall/5-to-6 new file mode 100755 index 000000000..ccb86830a --- /dev/null +++ b/src/migration-scripts/firewall/5-to-6 @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# T3090: migrate "firewall options interface <name> adjust-mss" to the +# individual interface. + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree +from vyos.ifconfig import Section + +if (len(argv) < 1): + print("Must specify file name!") + exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +base = ['firewall', 'options', 'interface'] +config = ConfigTree(config_file) + +if not config.exists(base): + # Nothing to do + exit(0) + +for interface in config.list_nodes(base): + if config.exists(base + [interface, 'disable']): + continue + + if config.exists(base + [interface, 'adjust-mss']): + section = Section.section(interface) + tmp = config.return_value(base + [interface, 'adjust-mss']) + config.set(['interfaces', section, interface, 'ip', 'adjust-mss'], value=tmp) + + if config.exists(base + [interface, 'adjust-mss6']): + section = Section.section(interface) + tmp = config.return_value(base + [interface, 'adjust-mss6']) + config.set(['interfaces', section, interface, 'ipv6', 'adjust-mss'], value=tmp) + +config.delete(['firewall', 'options']) + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print("Failed to save the modified config: {}".format(e)) + exit(1) diff --git a/src/migration-scripts/interfaces/20-to-21 b/src/migration-scripts/interfaces/20-to-21 index 06e07572f..0bd858760 100755 --- a/src/migration-scripts/interfaces/20-to-21 +++ b/src/migration-scripts/interfaces/20-to-21 @@ -14,132 +14,107 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +# T3619: mirror Linux Kernel defaults for ethernet offloading options into VyOS +# CLI. See https://phabricator.vyos.net/T3619#102254 for all the details. +# T3787: Remove deprecated UDP fragmentation offloading option + from sys import argv -from sys import exit + +from vyos.ethtool import Ethtool from vyos.configtree import ConfigTree -def migrate_ospf(config, path, interface): - path = path + ['ospf'] - if config.exists(path): - new_base = ['protocols', 'ospf', 'interface'] - config.set(new_base) - config.set_tag(new_base) - config.copy(path, new_base + [interface]) - config.delete(path) - - # if "ip ospf" was the only setting, we can clean out the empty - # ip node afterwards - if len(config.list_nodes(path[:-1])) == 0: - config.delete(path[:-1]) - -def migrate_ospfv3(config, path, interface): - path = path + ['ospfv3'] - if config.exists(path): - new_base = ['protocols', 'ospfv3', 'interface'] - config.set(new_base) - config.set_tag(new_base) - config.copy(path, new_base + [interface]) - config.delete(path) - - # if "ipv6 ospfv3" was the only setting, we can clean out the empty - # ip node afterwards - if len(config.list_nodes(path[:-1])) == 0: - config.delete(path[:-1]) - -def migrate_rip(config, path, interface): - path = path + ['rip'] - if config.exists(path): - new_base = ['protocols', 'rip', 'interface'] - config.set(new_base) - config.set_tag(new_base) - config.copy(path, new_base + [interface]) - config.delete(path) - - # if "ip rip" was the only setting, we can clean out the empty - # ip node afterwards - if len(config.list_nodes(path[:-1])) == 0: - config.delete(path[:-1]) - -def migrate_ripng(config, path, interface): - path = path + ['ripng'] - if config.exists(path): - new_base = ['protocols', 'ripng', 'interface'] - config.set(new_base) - config.set_tag(new_base) - config.copy(path, new_base + [interface]) - config.delete(path) - - # if "ipv6 ripng" was the only setting, we can clean out the empty - # ip node afterwards - if len(config.list_nodes(path[:-1])) == 0: - config.delete(path[:-1]) - -if __name__ == '__main__': - if (len(argv) < 1): - print("Must specify file name!") - exit(1) - - file_name = argv[1] - with open(file_name, 'r') as f: - config_file = f.read() - - config = ConfigTree(config_file) - - # - # Migrate "interface ethernet eth0 ip ospf" to "protocols ospf interface eth0" - # - for type in config.list_nodes(['interfaces']): - for interface in config.list_nodes(['interfaces', type]): - ip_base = ['interfaces', type, interface, 'ip'] - ipv6_base = ['interfaces', type, interface, 'ipv6'] - migrate_rip(config, ip_base, interface) - migrate_ripng(config, ipv6_base, interface) - migrate_ospf(config, ip_base, interface) - migrate_ospfv3(config, ipv6_base, interface) - - vif_path = ['interfaces', type, interface, 'vif'] - if config.exists(vif_path): - for vif in config.list_nodes(vif_path): - vif_ip_base = vif_path + [vif, 'ip'] - vif_ipv6_base = vif_path + [vif, 'ipv6'] - ifname = f'{interface}.{vif}' - - migrate_rip(config, vif_ip_base, ifname) - migrate_ripng(config, vif_ipv6_base, ifname) - migrate_ospf(config, vif_ip_base, ifname) - migrate_ospfv3(config, vif_ipv6_base, ifname) - - - vif_s_path = ['interfaces', type, interface, 'vif-s'] - if config.exists(vif_s_path): - for vif_s in config.list_nodes(vif_s_path): - vif_s_ip_base = vif_s_path + [vif_s, 'ip'] - vif_s_ipv6_base = vif_s_path + [vif_s, 'ipv6'] - - # vif-c interfaces MUST be migrated before their parent vif-s - # interface as the migrate_*() functions delete the path! - vif_c_path = ['interfaces', type, interface, 'vif-s', vif_s, 'vif-c'] - if config.exists(vif_c_path): - for vif_c in config.list_nodes(vif_c_path): - vif_c_ip_base = vif_c_path + [vif_c, 'ip'] - vif_c_ipv6_base = vif_c_path + [vif_c, 'ipv6'] - ifname = f'{interface}.{vif_s}.{vif_c}' - - migrate_rip(config, vif_c_ip_base, ifname) - migrate_ripng(config, vif_c_ipv6_base, ifname) - migrate_ospf(config, vif_c_ip_base, ifname) - migrate_ospfv3(config, vif_c_ipv6_base, ifname) - - - ifname = f'{interface}.{vif_s}' - migrate_rip(config, vif_s_ip_base, ifname) - migrate_ripng(config, vif_s_ipv6_base, ifname) - migrate_ospf(config, vif_s_ip_base, ifname) - migrate_ospfv3(config, vif_s_ipv6_base, ifname) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +if (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', 'ethernet'] +config = ConfigTree(config_file) + +if not config.exists(base): + exit(0) + +for ifname in config.list_nodes(base): + eth = Ethtool(ifname) + + # If GRO is enabled by the Kernel - we reflect this on the CLI. If GRO is + # enabled via CLI but not supported by the NIC - we remove it from the CLI + configured = config.exists(base + [ifname, 'offload', 'gro']) + enabled, fixed = eth.get_generic_receive_offload() + if configured and fixed: + config.delete(base + [ifname, 'offload', 'gro']) + elif enabled and not fixed: + config.set(base + [ifname, 'offload', 'gro']) + + # If GSO is enabled by the Kernel - we reflect this on the CLI. If GSO is + # enabled via CLI but not supported by the NIC - we remove it from the CLI + configured = config.exists(base + [ifname, 'offload', 'gso']) + enabled, fixed = eth.get_generic_segmentation_offload() + if configured and fixed: + config.delete(base + [ifname, 'offload', 'gso']) + elif enabled and not fixed: + config.set(base + [ifname, 'offload', 'gso']) + + # If LRO is enabled by the Kernel - we reflect this on the CLI. If LRO is + # enabled via CLI but not supported by the NIC - we remove it from the CLI + configured = config.exists(base + [ifname, 'offload', 'lro']) + enabled, fixed = eth.get_large_receive_offload() + if configured and fixed: + config.delete(base + [ifname, 'offload', 'lro']) + elif enabled and not fixed: + config.set(base + [ifname, 'offload', 'lro']) + + # If SG is enabled by the Kernel - we reflect this on the CLI. If SG is + # enabled via CLI but not supported by the NIC - we remove it from the CLI + configured = config.exists(base + [ifname, 'offload', 'sg']) + enabled, fixed = eth.get_scatter_gather() + if configured and fixed: + config.delete(base + [ifname, 'offload', 'sg']) + elif enabled and not fixed: + config.set(base + [ifname, 'offload', 'sg']) + + # If TSO is enabled by the Kernel - we reflect this on the CLI. If TSO is + # enabled via CLI but not supported by the NIC - we remove it from the CLI + configured = config.exists(base + [ifname, 'offload', 'tso']) + enabled, fixed = eth.get_tcp_segmentation_offload() + if configured and fixed: + config.delete(base + [ifname, 'offload', 'tso']) + elif enabled and not fixed: + config.set(base + [ifname, 'offload', 'tso']) + + # Remove deprecated UDP fragmentation offloading option + if config.exists(base + [ifname, 'offload', 'ufo']): + config.delete(base + [ifname, 'offload', 'ufo']) + + # Also while processing the interface configuration, not all adapters support + # changing the speed and duplex settings. If the desired speed and duplex + # values do not work for the NIC driver, we change them back to the default + # value of "auto" - which will be applied if the CLI node is deleted. + speed_path = base + [ifname, 'speed'] + duplex_path = base + [ifname, 'duplex'] + # speed and duplex must always be set at the same time if not set to "auto" + if config.exists(speed_path) and config.exists(duplex_path): + speed = config.return_value(speed_path) + duplex = config.return_value(duplex_path) + if speed != 'auto' and duplex != 'auto': + if not eth.check_speed_duplex(speed, duplex): + config.delete(speed_path) + config.delete(duplex_path) + + # Also while processing the interface configuration, not all adapters support + # changing disabling flow-control - or change this setting. If disabling + # flow-control is not supported by the NIC, we remove the setting from CLI + flow_control_path = base + [ifname, 'disable-flow-control'] + if config.exists(flow_control_path): + if not eth.check_flow_control(): + config.delete(flow_control_path) + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print("Failed to save the modified config: {}".format(e)) + exit(1) diff --git a/src/migration-scripts/interfaces/21-to-22 b/src/migration-scripts/interfaces/21-to-22 index d1ec2ad3e..06e07572f 100755 --- a/src/migration-scripts/interfaces/21-to-22 +++ b/src/migration-scripts/interfaces/21-to-22 @@ -14,47 +14,132 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -# A VTI interface also requires an IPSec configuration - VyOS 1.2 supported -# having a VTI interface in the CLI but no IPSec configuration - drop VTI -# configuration if this is the case for VyOS 1.4 - -import sys +from sys import argv +from sys import exit from vyos.configtree import ConfigTree +def migrate_ospf(config, path, interface): + path = path + ['ospf'] + if config.exists(path): + new_base = ['protocols', 'ospf', 'interface'] + config.set(new_base) + config.set_tag(new_base) + config.copy(path, new_base + [interface]) + config.delete(path) + + # if "ip ospf" was the only setting, we can clean out the empty + # ip node afterwards + if len(config.list_nodes(path[:-1])) == 0: + config.delete(path[:-1]) + +def migrate_ospfv3(config, path, interface): + path = path + ['ospfv3'] + if config.exists(path): + new_base = ['protocols', 'ospfv3', 'interface'] + config.set(new_base) + config.set_tag(new_base) + config.copy(path, new_base + [interface]) + config.delete(path) + + # if "ipv6 ospfv3" was the only setting, we can clean out the empty + # ip node afterwards + if len(config.list_nodes(path[:-1])) == 0: + config.delete(path[:-1]) + +def migrate_rip(config, path, interface): + path = path + ['rip'] + if config.exists(path): + new_base = ['protocols', 'rip', 'interface'] + config.set(new_base) + config.set_tag(new_base) + config.copy(path, new_base + [interface]) + config.delete(path) + + # if "ip rip" was the only setting, we can clean out the empty + # ip node afterwards + if len(config.list_nodes(path[:-1])) == 0: + config.delete(path[:-1]) + +def migrate_ripng(config, path, interface): + path = path + ['ripng'] + if config.exists(path): + new_base = ['protocols', 'ripng', 'interface'] + config.set(new_base) + config.set_tag(new_base) + config.copy(path, new_base + [interface]) + config.delete(path) + + # if "ipv6 ripng" was the only setting, we can clean out the empty + # ip node afterwards + if len(config.list_nodes(path[:-1])) == 0: + config.delete(path[:-1]) + if __name__ == '__main__': - if (len(sys.argv) < 1): + if (len(argv) < 1): print("Must specify file name!") - sys.exit(1) - - file_name = sys.argv[1] + exit(1) + file_name = argv[1] with open(file_name, 'r') as f: config_file = f.read() config = ConfigTree(config_file) - base = ['interfaces', 'vti'] - if not config.exists(base): - # Nothing to do - sys.exit(0) - - ipsec_base = ['vpn', 'ipsec', 'site-to-site', 'peer'] - for interface in config.list_nodes(base): - found = False - if config.exists(ipsec_base): - for peer in config.list_nodes(ipsec_base): - if config.exists(ipsec_base + [peer, 'vti', 'bind']): - tmp = config.return_value(ipsec_base + [peer, 'vti', 'bind']) - if tmp == interface: - # Interface was found and we no longer need to search - # for it in our IPSec peers - found = True - break - if not found: - config.delete(base + [interface]) + + # + # Migrate "interface ethernet eth0 ip ospf" to "protocols ospf interface eth0" + # + for type in config.list_nodes(['interfaces']): + for interface in config.list_nodes(['interfaces', type]): + ip_base = ['interfaces', type, interface, 'ip'] + ipv6_base = ['interfaces', type, interface, 'ipv6'] + migrate_rip(config, ip_base, interface) + migrate_ripng(config, ipv6_base, interface) + migrate_ospf(config, ip_base, interface) + migrate_ospfv3(config, ipv6_base, interface) + + vif_path = ['interfaces', type, interface, 'vif'] + if config.exists(vif_path): + for vif in config.list_nodes(vif_path): + vif_ip_base = vif_path + [vif, 'ip'] + vif_ipv6_base = vif_path + [vif, 'ipv6'] + ifname = f'{interface}.{vif}' + + migrate_rip(config, vif_ip_base, ifname) + migrate_ripng(config, vif_ipv6_base, ifname) + migrate_ospf(config, vif_ip_base, ifname) + migrate_ospfv3(config, vif_ipv6_base, ifname) + + + vif_s_path = ['interfaces', type, interface, 'vif-s'] + if config.exists(vif_s_path): + for vif_s in config.list_nodes(vif_s_path): + vif_s_ip_base = vif_s_path + [vif_s, 'ip'] + vif_s_ipv6_base = vif_s_path + [vif_s, 'ipv6'] + + # vif-c interfaces MUST be migrated before their parent vif-s + # interface as the migrate_*() functions delete the path! + vif_c_path = ['interfaces', type, interface, 'vif-s', vif_s, 'vif-c'] + if config.exists(vif_c_path): + for vif_c in config.list_nodes(vif_c_path): + vif_c_ip_base = vif_c_path + [vif_c, 'ip'] + vif_c_ipv6_base = vif_c_path + [vif_c, 'ipv6'] + ifname = f'{interface}.{vif_s}.{vif_c}' + + migrate_rip(config, vif_c_ip_base, ifname) + migrate_ripng(config, vif_c_ipv6_base, ifname) + migrate_ospf(config, vif_c_ip_base, ifname) + migrate_ospfv3(config, vif_c_ipv6_base, ifname) + + + ifname = f'{interface}.{vif_s}' + migrate_rip(config, vif_s_ip_base, ifname) + migrate_ripng(config, vif_s_ipv6_base, ifname) + migrate_ospf(config, vif_s_ip_base, ifname) + migrate_ospfv3(config, vif_s_ipv6_base, ifname) try: with open(file_name, 'w') as f: f.write(config.to_string()) except OSError as e: print("Failed to save the modified config: {}".format(e)) - sys.exit(1) + exit(1) diff --git a/src/migration-scripts/interfaces/22-to-23 b/src/migration-scripts/interfaces/22-to-23 index 93ce9215f..d1ec2ad3e 100755 --- a/src/migration-scripts/interfaces/22-to-23 +++ b/src/migration-scripts/interfaces/22-to-23 @@ -14,356 +14,47 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -# Migrate Wireguard to store keys in CLI -# Migrate EAPoL to PKI configuration +# A VTI interface also requires an IPSec configuration - VyOS 1.2 supported +# having a VTI interface in the CLI but no IPSec configuration - drop VTI +# configuration if this is the case for VyOS 1.4 -import os import sys from vyos.configtree import ConfigTree -from vyos.pki import load_certificate -from vyos.pki import load_crl -from vyos.pki import load_dh_parameters -from vyos.pki import load_private_key -from vyos.pki import encode_certificate -from vyos.pki import encode_dh_parameters -from vyos.pki import encode_private_key -from vyos.util import run -def wrapped_pem_to_config_value(pem): - out = [] - for line in pem.strip().split("\n"): - if not line or line.startswith("-----") or line[0] == '#': - continue - out.append(line) - return "".join(out) +if __name__ == '__main__': + if (len(sys.argv) < 1): + print("Must specify file name!") + sys.exit(1) -def read_file_for_pki(config_auth_path): - full_path = os.path.join(AUTH_DIR, config_auth_path) - output = None + file_name = sys.argv[1] - if os.path.isfile(full_path): - if not os.access(full_path, os.R_OK): - run(f'sudo chmod 644 {full_path}') + with open(file_name, 'r') as f: + config_file = f.read() - with open(full_path, 'r') as f: - output = f.read() + config = ConfigTree(config_file) + base = ['interfaces', 'vti'] + if not config.exists(base): + # Nothing to do + sys.exit(0) - return output - -if (len(sys.argv) < 1): - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - -AUTH_DIR = '/config/auth' -pki_base = ['pki'] - -# OpenVPN -base = ['interfaces', 'openvpn'] - -if config.exists(base): - for interface in config.list_nodes(base): - x509_base = base + [interface, 'tls'] - pki_name = f'openvpn_{interface}' - - if config.exists(base + [interface, 'shared-secret-key-file']): - if not config.exists(pki_base + ['openvpn', 'shared-secret']): - config.set(pki_base + ['openvpn', 'shared-secret']) - config.set_tag(pki_base + ['openvpn', 'shared-secret']) - - key_file = config.return_value(base + [interface, 'shared-secret-key-file']) - key = read_file_for_pki(key_file) - key_pki_name = f'{pki_name}_shared' - - if key: - config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key)) - config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1') - config.set(base + [interface, 'shared-secret-key'], value=key_pki_name) - else: - print(f'Failed to migrate shared-secret-key on openvpn interface {interface}') - - config.delete(base + [interface, 'shared-secret-key-file']) - - if not config.exists(base + [interface, 'tls']): - continue - - if config.exists(base + [interface, 'tls', 'auth-file']): - if not config.exists(pki_base + ['openvpn', 'shared-secret']): - config.set(pki_base + ['openvpn', 'shared-secret']) - config.set_tag(pki_base + ['openvpn', 'shared-secret']) - - key_file = config.return_value(base + [interface, 'tls', 'auth-file']) - key = read_file_for_pki(key_file) - key_pki_name = f'{pki_name}_auth' - - if key: - config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key)) - config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1') - config.set(base + [interface, 'tls', 'auth-key'], value=key_pki_name) - else: - print(f'Failed to migrate auth-key on openvpn interface {interface}') - - config.delete(base + [interface, 'tls', 'auth-file']) - - if config.exists(base + [interface, 'tls', 'crypt-file']): - if not config.exists(pki_base + ['openvpn', 'shared-secret']): - config.set(pki_base + ['openvpn', 'shared-secret']) - config.set_tag(pki_base + ['openvpn', 'shared-secret']) - - key_file = config.return_value(base + [interface, 'tls', 'crypt-file']) - key = read_file_for_pki(key_file) - key_pki_name = f'{pki_name}_crypt' - - if key: - config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key)) - config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1') - config.set(base + [interface, 'tls', 'crypt-key'], value=key_pki_name) - else: - print(f'Failed to migrate crypt-key on openvpn interface {interface}') - - config.delete(base + [interface, 'tls', 'crypt-file']) - - if config.exists(x509_base + ['ca-cert-file']): - if not config.exists(pki_base + ['ca']): - config.set(pki_base + ['ca']) - config.set_tag(pki_base + ['ca']) - - cert_file = config.return_value(x509_base + ['ca-cert-file']) - cert_path = os.path.join(AUTH_DIR, cert_file) - cert = None - - if os.path.isfile(cert_path): - if not os.access(cert_path, os.R_OK): - run(f'sudo chmod 644 {cert_path}') - - with open(cert_path, 'r') as f: - cert_data = f.read() - cert = load_certificate(cert_data, wrap_tags=False) - - if cert: - cert_pem = encode_certificate(cert) - config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) - config.set(x509_base + ['ca-certificate'], value=pki_name) - else: - print(f'Failed to migrate CA certificate on openvpn interface {interface}') - - config.delete(x509_base + ['ca-cert-file']) - - if config.exists(x509_base + ['crl-file']): - if not config.exists(pki_base + ['ca']): - config.set(pki_base + ['ca']) - config.set_tag(pki_base + ['ca']) - - crl_file = config.return_value(x509_base + ['crl-file']) - crl_path = os.path.join(AUTH_DIR, crl_file) - crl = None - - if os.path.isfile(crl_path): - if not os.access(crl_path, os.R_OK): - run(f'sudo chmod 644 {crl_path}') - - with open(crl_path, 'r') as f: - crl_data = f.read() - crl = load_crl(crl_data, wrap_tags=False) - - if crl: - crl_pem = encode_certificate(crl) - config.set(pki_base + ['ca', pki_name, 'crl'], value=wrapped_pem_to_config_value(crl_pem)) - else: - print(f'Failed to migrate CRL on openvpn interface {interface}') - - config.delete(x509_base + ['crl-file']) - - if config.exists(x509_base + ['cert-file']): - if not config.exists(pki_base + ['certificate']): - config.set(pki_base + ['certificate']) - config.set_tag(pki_base + ['certificate']) - - cert_file = config.return_value(x509_base + ['cert-file']) - cert_path = os.path.join(AUTH_DIR, cert_file) - cert = None - - if os.path.isfile(cert_path): - if not os.access(cert_path, os.R_OK): - run(f'sudo chmod 644 {cert_path}') - - with open(cert_path, 'r') as f: - cert_data = f.read() - cert = load_certificate(cert_data, wrap_tags=False) - - if cert: - cert_pem = encode_certificate(cert) - config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) - config.set(x509_base + ['certificate'], value=pki_name) - else: - print(f'Failed to migrate certificate on openvpn interface {interface}') - - config.delete(x509_base + ['cert-file']) - - if config.exists(x509_base + ['key-file']): - key_file = config.return_value(x509_base + ['key-file']) - key_path = os.path.join(AUTH_DIR, key_file) - key = None - - if os.path.isfile(key_path): - if not os.access(key_path, os.R_OK): - run(f'sudo chmod 644 {key_path}') - - with open(key_path, 'r') as f: - key_data = f.read() - key = load_private_key(key_data, passphrase=None, wrap_tags=False) - - if key: - key_pem = encode_private_key(key, passphrase=None) - config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) - else: - print(f'Failed to migrate private key on openvpn interface {interface}') - - config.delete(x509_base + ['key-file']) - - if config.exists(x509_base + ['dh-file']): - if not config.exists(pki_base + ['dh']): - config.set(pki_base + ['dh']) - config.set_tag(pki_base + ['dh']) - - dh_file = config.return_value(x509_base + ['dh-file']) - dh_path = os.path.join(AUTH_DIR, dh_file) - dh = None - - if os.path.isfile(dh_path): - if not os.access(dh_path, os.R_OK): - run(f'sudo chmod 644 {dh_path}') - - with open(dh_path, 'r') as f: - dh_data = f.read() - dh = load_dh_parameters(dh_data, wrap_tags=False) - - if dh: - dh_pem = encode_dh_parameters(dh) - config.set(pki_base + ['dh', pki_name, 'parameters'], value=wrapped_pem_to_config_value(dh_pem)) - config.set(x509_base + ['dh-params'], value=pki_name) - else: - print(f'Failed to migrate DH parameters on openvpn interface {interface}') - - config.delete(x509_base + ['dh-file']) - -# Wireguard -base = ['interfaces', 'wireguard'] - -if config.exists(base): - for interface in config.list_nodes(base): - private_key_path = base + [interface, 'private-key'] - - key_file = 'default' - if config.exists(private_key_path): - key_file = config.return_value(private_key_path) - - full_key_path = f'/config/auth/wireguard/{key_file}/private.key' - - if not os.path.exists(full_key_path): - print(f'Could not find wireguard private key for migration on interface "{interface}"') - continue - - with open(full_key_path, 'r') as f: - key_data = f.read().strip() - config.set(private_key_path, value=key_data) - - for peer in config.list_nodes(base + [interface, 'peer']): - config.rename(base + [interface, 'peer', peer, 'pubkey'], 'public-key') - -# Ethernet EAPoL -base = ['interfaces', 'ethernet'] - -if config.exists(base): + ipsec_base = ['vpn', 'ipsec', 'site-to-site', 'peer'] for interface in config.list_nodes(base): - if not config.exists(base + [interface, 'eapol']): - continue - - x509_base = base + [interface, 'eapol'] - pki_name = f'eapol_{interface}' - - if config.exists(x509_base + ['ca-cert-file']): - if not config.exists(pki_base + ['ca']): - config.set(pki_base + ['ca']) - config.set_tag(pki_base + ['ca']) - - cert_file = config.return_value(x509_base + ['ca-cert-file']) - cert_path = os.path.join(AUTH_DIR, cert_file) - cert = None - - if os.path.isfile(cert_path): - if not os.access(cert_path, os.R_OK): - run(f'sudo chmod 644 {cert_path}') - - with open(cert_path, 'r') as f: - cert_data = f.read() - cert = load_certificate(cert_data, wrap_tags=False) - - if cert: - cert_pem = encode_certificate(cert) - config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) - config.set(x509_base + ['ca-certificate'], value=pki_name) - else: - print(f'Failed to migrate CA certificate on eapol config for interface {interface}') - - config.delete(x509_base + ['ca-cert-file']) - - if config.exists(x509_base + ['cert-file']): - if not config.exists(pki_base + ['certificate']): - config.set(pki_base + ['certificate']) - config.set_tag(pki_base + ['certificate']) - - cert_file = config.return_value(x509_base + ['cert-file']) - cert_path = os.path.join(AUTH_DIR, cert_file) - cert = None - - if os.path.isfile(cert_path): - if not os.access(cert_path, os.R_OK): - run(f'sudo chmod 644 {cert_path}') - - with open(cert_path, 'r') as f: - cert_data = f.read() - cert = load_certificate(cert_data, wrap_tags=False) - - if cert: - cert_pem = encode_certificate(cert) - config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) - config.set(x509_base + ['certificate'], value=pki_name) - else: - print(f'Failed to migrate certificate on eapol config for interface {interface}') - - config.delete(x509_base + ['cert-file']) - - if config.exists(x509_base + ['key-file']): - key_file = config.return_value(x509_base + ['key-file']) - key_path = os.path.join(AUTH_DIR, key_file) - key = None - - if os.path.isfile(key_path): - if not os.access(key_path, os.R_OK): - run(f'sudo chmod 644 {key_path}') - - with open(key_path, 'r') as f: - key_data = f.read() - key = load_private_key(key_data, passphrase=None, wrap_tags=False) - - if key: - key_pem = encode_private_key(key, passphrase=None) - config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) - else: - print(f'Failed to migrate private key on eapol config for interface {interface}') - - config.delete(x509_base + ['key-file']) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) + found = False + if config.exists(ipsec_base): + for peer in config.list_nodes(ipsec_base): + if config.exists(ipsec_base + [peer, 'vti', 'bind']): + tmp = config.return_value(ipsec_base + [peer, 'vti', 'bind']) + if tmp == interface: + # Interface was found and we no longer need to search + # for it in our IPSec peers + found = True + break + if not found: + config.delete(base + [interface]) + + try: + with open(file_name, 'w') as f: + f.write(config.to_string()) + except OSError as e: + print("Failed to save the modified config: {}".format(e)) + sys.exit(1) diff --git a/src/migration-scripts/interfaces/23-to-24 b/src/migration-scripts/interfaces/23-to-24 new file mode 100755 index 000000000..93ce9215f --- /dev/null +++ b/src/migration-scripts/interfaces/23-to-24 @@ -0,0 +1,369 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# Migrate Wireguard to store keys in CLI +# Migrate EAPoL to PKI configuration + +import os +import sys +from vyos.configtree import ConfigTree +from vyos.pki import load_certificate +from vyos.pki import load_crl +from vyos.pki import load_dh_parameters +from vyos.pki import load_private_key +from vyos.pki import encode_certificate +from vyos.pki import encode_dh_parameters +from vyos.pki import encode_private_key +from vyos.util import run + +def wrapped_pem_to_config_value(pem): + out = [] + for line in pem.strip().split("\n"): + if not line or line.startswith("-----") or line[0] == '#': + continue + out.append(line) + return "".join(out) + +def read_file_for_pki(config_auth_path): + full_path = os.path.join(AUTH_DIR, config_auth_path) + output = None + + if os.path.isfile(full_path): + if not os.access(full_path, os.R_OK): + run(f'sudo chmod 644 {full_path}') + + with open(full_path, 'r') as f: + output = f.read() + + return output + +if (len(sys.argv) < 1): + print("Must specify file name!") + sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +config = ConfigTree(config_file) + +AUTH_DIR = '/config/auth' +pki_base = ['pki'] + +# OpenVPN +base = ['interfaces', 'openvpn'] + +if config.exists(base): + for interface in config.list_nodes(base): + x509_base = base + [interface, 'tls'] + pki_name = f'openvpn_{interface}' + + if config.exists(base + [interface, 'shared-secret-key-file']): + if not config.exists(pki_base + ['openvpn', 'shared-secret']): + config.set(pki_base + ['openvpn', 'shared-secret']) + config.set_tag(pki_base + ['openvpn', 'shared-secret']) + + key_file = config.return_value(base + [interface, 'shared-secret-key-file']) + key = read_file_for_pki(key_file) + key_pki_name = f'{pki_name}_shared' + + if key: + config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key)) + config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1') + config.set(base + [interface, 'shared-secret-key'], value=key_pki_name) + else: + print(f'Failed to migrate shared-secret-key on openvpn interface {interface}') + + config.delete(base + [interface, 'shared-secret-key-file']) + + if not config.exists(base + [interface, 'tls']): + continue + + if config.exists(base + [interface, 'tls', 'auth-file']): + if not config.exists(pki_base + ['openvpn', 'shared-secret']): + config.set(pki_base + ['openvpn', 'shared-secret']) + config.set_tag(pki_base + ['openvpn', 'shared-secret']) + + key_file = config.return_value(base + [interface, 'tls', 'auth-file']) + key = read_file_for_pki(key_file) + key_pki_name = f'{pki_name}_auth' + + if key: + config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key)) + config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1') + config.set(base + [interface, 'tls', 'auth-key'], value=key_pki_name) + else: + print(f'Failed to migrate auth-key on openvpn interface {interface}') + + config.delete(base + [interface, 'tls', 'auth-file']) + + if config.exists(base + [interface, 'tls', 'crypt-file']): + if not config.exists(pki_base + ['openvpn', 'shared-secret']): + config.set(pki_base + ['openvpn', 'shared-secret']) + config.set_tag(pki_base + ['openvpn', 'shared-secret']) + + key_file = config.return_value(base + [interface, 'tls', 'crypt-file']) + key = read_file_for_pki(key_file) + key_pki_name = f'{pki_name}_crypt' + + if key: + config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key)) + config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1') + config.set(base + [interface, 'tls', 'crypt-key'], value=key_pki_name) + else: + print(f'Failed to migrate crypt-key on openvpn interface {interface}') + + config.delete(base + [interface, 'tls', 'crypt-file']) + + if config.exists(x509_base + ['ca-cert-file']): + if not config.exists(pki_base + ['ca']): + config.set(pki_base + ['ca']) + config.set_tag(pki_base + ['ca']) + + cert_file = config.return_value(x509_base + ['ca-cert-file']) + cert_path = os.path.join(AUTH_DIR, cert_file) + cert = None + + if os.path.isfile(cert_path): + if not os.access(cert_path, os.R_OK): + run(f'sudo chmod 644 {cert_path}') + + with open(cert_path, 'r') as f: + cert_data = f.read() + cert = load_certificate(cert_data, wrap_tags=False) + + if cert: + cert_pem = encode_certificate(cert) + config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + config.set(x509_base + ['ca-certificate'], value=pki_name) + else: + print(f'Failed to migrate CA certificate on openvpn interface {interface}') + + config.delete(x509_base + ['ca-cert-file']) + + if config.exists(x509_base + ['crl-file']): + if not config.exists(pki_base + ['ca']): + config.set(pki_base + ['ca']) + config.set_tag(pki_base + ['ca']) + + crl_file = config.return_value(x509_base + ['crl-file']) + crl_path = os.path.join(AUTH_DIR, crl_file) + crl = None + + if os.path.isfile(crl_path): + if not os.access(crl_path, os.R_OK): + run(f'sudo chmod 644 {crl_path}') + + with open(crl_path, 'r') as f: + crl_data = f.read() + crl = load_crl(crl_data, wrap_tags=False) + + if crl: + crl_pem = encode_certificate(crl) + config.set(pki_base + ['ca', pki_name, 'crl'], value=wrapped_pem_to_config_value(crl_pem)) + else: + print(f'Failed to migrate CRL on openvpn interface {interface}') + + config.delete(x509_base + ['crl-file']) + + if config.exists(x509_base + ['cert-file']): + if not config.exists(pki_base + ['certificate']): + config.set(pki_base + ['certificate']) + config.set_tag(pki_base + ['certificate']) + + cert_file = config.return_value(x509_base + ['cert-file']) + cert_path = os.path.join(AUTH_DIR, cert_file) + cert = None + + if os.path.isfile(cert_path): + if not os.access(cert_path, os.R_OK): + run(f'sudo chmod 644 {cert_path}') + + with open(cert_path, 'r') as f: + cert_data = f.read() + cert = load_certificate(cert_data, wrap_tags=False) + + if cert: + cert_pem = encode_certificate(cert) + config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + config.set(x509_base + ['certificate'], value=pki_name) + else: + print(f'Failed to migrate certificate on openvpn interface {interface}') + + config.delete(x509_base + ['cert-file']) + + if config.exists(x509_base + ['key-file']): + key_file = config.return_value(x509_base + ['key-file']) + key_path = os.path.join(AUTH_DIR, key_file) + key = None + + if os.path.isfile(key_path): + if not os.access(key_path, os.R_OK): + run(f'sudo chmod 644 {key_path}') + + with open(key_path, 'r') as f: + key_data = f.read() + key = load_private_key(key_data, passphrase=None, wrap_tags=False) + + if key: + key_pem = encode_private_key(key, passphrase=None) + config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) + else: + print(f'Failed to migrate private key on openvpn interface {interface}') + + config.delete(x509_base + ['key-file']) + + if config.exists(x509_base + ['dh-file']): + if not config.exists(pki_base + ['dh']): + config.set(pki_base + ['dh']) + config.set_tag(pki_base + ['dh']) + + dh_file = config.return_value(x509_base + ['dh-file']) + dh_path = os.path.join(AUTH_DIR, dh_file) + dh = None + + if os.path.isfile(dh_path): + if not os.access(dh_path, os.R_OK): + run(f'sudo chmod 644 {dh_path}') + + with open(dh_path, 'r') as f: + dh_data = f.read() + dh = load_dh_parameters(dh_data, wrap_tags=False) + + if dh: + dh_pem = encode_dh_parameters(dh) + config.set(pki_base + ['dh', pki_name, 'parameters'], value=wrapped_pem_to_config_value(dh_pem)) + config.set(x509_base + ['dh-params'], value=pki_name) + else: + print(f'Failed to migrate DH parameters on openvpn interface {interface}') + + config.delete(x509_base + ['dh-file']) + +# Wireguard +base = ['interfaces', 'wireguard'] + +if config.exists(base): + for interface in config.list_nodes(base): + private_key_path = base + [interface, 'private-key'] + + key_file = 'default' + if config.exists(private_key_path): + key_file = config.return_value(private_key_path) + + full_key_path = f'/config/auth/wireguard/{key_file}/private.key' + + if not os.path.exists(full_key_path): + print(f'Could not find wireguard private key for migration on interface "{interface}"') + continue + + with open(full_key_path, 'r') as f: + key_data = f.read().strip() + config.set(private_key_path, value=key_data) + + for peer in config.list_nodes(base + [interface, 'peer']): + config.rename(base + [interface, 'peer', peer, 'pubkey'], 'public-key') + +# Ethernet EAPoL +base = ['interfaces', 'ethernet'] + +if config.exists(base): + for interface in config.list_nodes(base): + if not config.exists(base + [interface, 'eapol']): + continue + + x509_base = base + [interface, 'eapol'] + pki_name = f'eapol_{interface}' + + if config.exists(x509_base + ['ca-cert-file']): + if not config.exists(pki_base + ['ca']): + config.set(pki_base + ['ca']) + config.set_tag(pki_base + ['ca']) + + cert_file = config.return_value(x509_base + ['ca-cert-file']) + cert_path = os.path.join(AUTH_DIR, cert_file) + cert = None + + if os.path.isfile(cert_path): + if not os.access(cert_path, os.R_OK): + run(f'sudo chmod 644 {cert_path}') + + with open(cert_path, 'r') as f: + cert_data = f.read() + cert = load_certificate(cert_data, wrap_tags=False) + + if cert: + cert_pem = encode_certificate(cert) + config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + config.set(x509_base + ['ca-certificate'], value=pki_name) + else: + print(f'Failed to migrate CA certificate on eapol config for interface {interface}') + + config.delete(x509_base + ['ca-cert-file']) + + if config.exists(x509_base + ['cert-file']): + if not config.exists(pki_base + ['certificate']): + config.set(pki_base + ['certificate']) + config.set_tag(pki_base + ['certificate']) + + cert_file = config.return_value(x509_base + ['cert-file']) + cert_path = os.path.join(AUTH_DIR, cert_file) + cert = None + + if os.path.isfile(cert_path): + if not os.access(cert_path, os.R_OK): + run(f'sudo chmod 644 {cert_path}') + + with open(cert_path, 'r') as f: + cert_data = f.read() + cert = load_certificate(cert_data, wrap_tags=False) + + if cert: + cert_pem = encode_certificate(cert) + config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + config.set(x509_base + ['certificate'], value=pki_name) + else: + print(f'Failed to migrate certificate on eapol config for interface {interface}') + + config.delete(x509_base + ['cert-file']) + + if config.exists(x509_base + ['key-file']): + key_file = config.return_value(x509_base + ['key-file']) + key_path = os.path.join(AUTH_DIR, key_file) + key = None + + if os.path.isfile(key_path): + if not os.access(key_path, os.R_OK): + run(f'sudo chmod 644 {key_path}') + + with open(key_path, 'r') as f: + key_data = f.read() + key = load_private_key(key_data, passphrase=None, wrap_tags=False) + + if key: + key_pem = encode_private_key(key, passphrase=None) + config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) + else: + print(f'Failed to migrate private key on eapol config for interface {interface}') + + config.delete(x509_base + ['key-file']) + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print("Failed to save the modified config: {}".format(e)) + sys.exit(1) diff --git a/src/migration-scripts/system/20-to-21 b/src/migration-scripts/system/20-to-21 index ad41be646..1728995de 100755 --- a/src/migration-scripts/system/20-to-21 +++ b/src/migration-scripts/system/20-to-21 @@ -14,9 +14,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +# T3795: merge "system name-servers-dhcp" into "system name-server" + import os -from sys import exit, argv +from sys import argv from vyos.configtree import ConfigTree if (len(argv) < 1): @@ -27,27 +29,16 @@ file_name = argv[1] with open(file_name, 'r') as f: config_file = f.read() -base = ['system', 'sysctl'] +base = ['system', 'name-servers-dhcp'] config = ConfigTree(config_file) - if not config.exists(base): # Nothing to do exit(0) -for all_custom in ['all', 'custom']: - if config.exists(base + [all_custom]): - for key in config.list_nodes(base + [all_custom]): - tmp = config.return_value(base + [all_custom, key, 'value']) - config.set(base + ['parameter', key, 'value'], value=tmp) - config.set_tag(base + ['parameter']) - config.delete(base + [all_custom]) - -for ipv4_param in ['net.ipv4.igmp_max_memberships', 'net.ipv4.ipfrag_time']: - if config.exists(base + [ipv4_param]): - tmp = config.return_value(base + [ipv4_param]) - config.set(base + ['parameter', ipv4_param, 'value'], value=tmp) - config.set_tag(base + ['parameter']) - config.delete(base + [ipv4_param]) +for interface in config.return_values(base): + config.set(['system', 'name-server'], value=interface, replace=False) + +config.delete(base) try: with open(file_name, 'w') as f: diff --git a/src/migration-scripts/system/21-to-22 b/src/migration-scripts/system/21-to-22 new file mode 100755 index 000000000..ad41be646 --- /dev/null +++ b/src/migration-scripts/system/21-to-22 @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit, argv +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 = ['system', 'sysctl'] +config = ConfigTree(config_file) + +if not config.exists(base): + # Nothing to do + exit(0) + +for all_custom in ['all', 'custom']: + if config.exists(base + [all_custom]): + for key in config.list_nodes(base + [all_custom]): + tmp = config.return_value(base + [all_custom, key, 'value']) + config.set(base + ['parameter', key, 'value'], value=tmp) + config.set_tag(base + ['parameter']) + config.delete(base + [all_custom]) + +for ipv4_param in ['net.ipv4.igmp_max_memberships', 'net.ipv4.ipfrag_time']: + if config.exists(base + [ipv4_param]): + tmp = config.return_value(base + [ipv4_param]) + config.set(base + ['parameter', ipv4_param, 'value'], value=tmp) + config.set_tag(base + ['parameter']) + config.delete(base + [ipv4_param]) + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print("Failed to save the modified config: {}".format(e)) + exit(1) diff --git a/src/migration-scripts/vrrp/2-to-3 b/src/migration-scripts/vrrp/2-to-3 new file mode 100755 index 000000000..1151ae18c --- /dev/null +++ b/src/migration-scripts/vrrp/2-to-3 @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# T3847: vrrp config cleanup + +from sys import argv +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 = ['high-availability', 'vrrp'] +config = ConfigTree(config_file) + +if not config.exists(base): + # Nothing to do + exit(0) + +if config.exists(base + ['group']): + for group in config.list_nodes(base + ['group']): + group_base = base + ['group', group] + + # Deprecated option + tmp = group_base + ['transition-script', 'mode-force'] + if config.exists(tmp): + config.delete(tmp) + + # Rename virtual-address -> address + tmp = group_base + ['virtual-address'] + if config.exists(tmp): + config.rename(tmp, 'address') + + # Rename virtual-address-excluded -> excluded-address + tmp = group_base + ['virtual-address-excluded'] + if config.exists(tmp): + config.rename(tmp, 'excluded-address') + +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/containers_op.py b/src/op_mode/containers_op.py index 1e3fc3a8f..bc317029c 100755 --- a/src/op_mode/containers_op.py +++ b/src/op_mode/containers_op.py @@ -15,10 +15,10 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import argparse -from vyos.configquery import query_context, ConfigQueryError -from vyos.util import cmd -config, op = query_context() +from getpass import getuser +from vyos.configquery import ConfigTreeQuery +from vyos.util import cmd parser = argparse.ArgumentParser() parser.add_argument("-a", "--all", action="store_true", help="Show all containers") @@ -26,34 +26,53 @@ parser.add_argument("-i", "--image", action="store_true", help="Show container i parser.add_argument("-n", "--networks", action="store_true", help="Show container images") parser.add_argument("-p", "--pull", action="store", help="Pull image for container") parser.add_argument("-d", "--remove", action="store", help="Delete container image") +parser.add_argument("-u", "--update", action="store", help="Update given container image") -if not config.exists(['container']): +config = ConfigTreeQuery() +base = ['container'] +if not config.exists(base): print('Containers not configured') exit(0) +if getuser() != 'root': + raise OSError('This functions needs to be run as root to return correct results!') + if __name__ == '__main__': args = parser.parse_args() if args.all: print(cmd('podman ps --all')) - exit(0) - if args.image: + + elif args.image: print(cmd('podman image ls')) - exit(0) - if args.networks: + + elif args.networks: print(cmd('podman network ls')) - exit(0) - if args.pull: + + elif args.pull: image = args.pull try: - print(cmd(f'sudo podman image pull {image}')) + print(cmd(f'podman image pull {image}')) except: print(f'Can\'t find or download image "{image}"') - exit(0) - if args.remove: + + elif args.remove: image = args.remove try: - print(cmd(f'sudo podman image rm {image}')) + print(cmd(f'podman image rm {image}')) except: print(f'Can\'t delete image "{image}"') - exit(0) + + elif args.update: + tmp = config.get_config_dict(base + ['name', args.update], + key_mangling=('-', '_'), get_first_key=True) + try: + image = tmp['image'] + print(cmd(f'podman image pull {image}')) + except: + print(f'Can\'t find or download image "{image}"') + else: + parser.print_help() + exit(1) + + exit(0) diff --git a/src/op_mode/dns_forwarding_statistics.py b/src/op_mode/dns_forwarding_statistics.py index 1fb61d263..d79b6c024 100755 --- a/src/op_mode/dns_forwarding_statistics.py +++ b/src/op_mode/dns_forwarding_statistics.py @@ -11,7 +11,7 @@ PDNS_CMD='/usr/bin/rec_control --socket-dir=/run/powerdns' OUT_TMPL_SRC = """ DNS forwarding statistics: -Cache entries: {{ cache_entries -}} +Cache entries: {{ cache_entries }} Cache size: {{ cache_size }} kbytes """ diff --git a/src/op_mode/ikev2_profile_generator.py b/src/op_mode/ikev2_profile_generator.py index d45525431..990b06c12 100755 --- a/src/op_mode/ikev2_profile_generator.py +++ b/src/op_mode/ikev2_profile_generator.py @@ -21,7 +21,7 @@ from sys import exit from socket import getfqdn from cryptography.x509.oid import NameOID -from vyos.config import Config +from vyos.configquery import ConfigTreeQuery from vyos.pki import load_certificate from vyos.template import render_to_string from vyos.util import ask_input @@ -117,7 +117,7 @@ args = parser.parse_args() ipsec_base = ['vpn', 'ipsec'] config_base = ipsec_base + ['remote-access', 'connection'] pki_base = ['pki'] -conf = Config() +conf = ConfigTreeQuery() if not conf.exists(config_base): exit('IPSec remote-access is not configured!') @@ -153,7 +153,7 @@ cert = load_certificate(pki['certificate'][cert_name]['certificate']) data['ca_cn'] = ca_cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value data['cert_cn'] = cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value -data['ca_cert'] = conf.return_value(pki_base + ['ca', ca_name, 'certificate']) +data['ca_cert'] = conf.value(pki_base + ['ca', ca_name, 'certificate']) esp_proposals = conf.get_config_dict(ipsec_base + ['esp-group', data['esp_group'], 'proposal'], key_mangling=('-', '_'), get_first_key=True) diff --git a/src/op_mode/ping.py b/src/op_mode/ping.py index 2144ab53c..60bbc0c78 100755 --- a/src/op_mode/ping.py +++ b/src/op_mode/ping.py @@ -62,8 +62,8 @@ options = { }, 'interface': { 'ping': '{command} -I {value}', - 'type': '<interface> <X.X.X.X> <h:h:h:h:h:h:h:h>', - 'help': 'Interface to use as source for ping' + 'type': '<interface>', + 'help': 'Source interface' }, 'interval': { 'ping': '{command} -i {value}', @@ -115,6 +115,10 @@ options = { 'type': '<bytes>', 'help': 'Number of bytes to send' }, + 'source-address': { + 'ping': '{command} -I {value}', + 'type': '<x.x.x.x> <h:h:h:h:h:h:h:h>', + }, 'ttl': { 'ping': '{command} -t {value}', 'type': '<ttl>', @@ -234,4 +238,4 @@ if __name__ == '__main__': # print(f'{command} {host}') os.system(f'{command} {host}') -
\ No newline at end of file + diff --git a/src/op_mode/pki.py b/src/op_mode/pki.py index 297270cf1..2283cd820 100755 --- a/src/op_mode/pki.py +++ b/src/op_mode/pki.py @@ -17,7 +17,6 @@ import argparse import ipaddress import os -import re import sys import tabulate @@ -25,6 +24,7 @@ from cryptography import x509 from cryptography.x509.oid import ExtendedKeyUsageOID from vyos.config import Config +from vyos.configquery import ConfigTreeQuery from vyos.configdict import dict_merge from vyos.pki import encode_certificate, encode_public_key, encode_private_key, encode_dh_parameters from vyos.pki import create_certificate, create_certificate_request, create_certificate_revocation_list @@ -37,25 +37,24 @@ from vyos.util import ask_input, ask_yes_no from vyos.util import cmd CERT_REQ_END = '-----END CERTIFICATE REQUEST-----' - auth_dir = '/config/auth' # Helper Functions - +conf = ConfigTreeQuery() def get_default_values(): # Fetch default x509 values - conf = Config() base = ['pki', 'x509', 'default'] x509_defaults = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) + get_first_key=True, + no_tag_node_value_mangle=True) default_values = defaults(base) - return dict_merge(default_values, x509_defaults) + x509_defaults = dict_merge(default_values, x509_defaults) + + return x509_defaults def get_config_ca_certificate(name=None): # Fetch ca certificates from config - conf = Config() base = ['pki', 'ca'] - if not conf.exists(base): return False @@ -65,13 +64,12 @@ def get_config_ca_certificate(name=None): return False return conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) + get_first_key=True, + no_tag_node_value_mangle=True) def get_config_certificate(name=None): # Get certificates from config - conf = Config() base = ['pki', 'certificate'] - if not conf.exists(base): return False @@ -81,7 +79,8 @@ def get_config_certificate(name=None): return False return conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) + get_first_key=True, + no_tag_node_value_mangle=True) def get_certificate_ca(cert, ca_certs): # Find CA certificate for given certificate @@ -100,7 +99,6 @@ def get_certificate_ca(cert, ca_certs): def get_config_revoked_certificates(): # Fetch revoked certificates from config - conf = Config() ca_base = ['pki', 'ca'] cert_base = ['pki', 'certificate'] @@ -108,12 +106,14 @@ def get_config_revoked_certificates(): if conf.exists(ca_base): ca_certificates = conf.get_config_dict(ca_base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) + get_first_key=True, + no_tag_node_value_mangle=True) certs.extend(ca_certificates.values()) if conf.exists(cert_base): certificates = conf.get_config_dict(cert_base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) + get_first_key=True, + no_tag_node_value_mangle=True) certs.extend(certificates.values()) return [cert_dict for cert_dict in certs if 'revoke' in cert_dict] @@ -144,39 +144,41 @@ def get_revoked_by_serial_numbers(serial_numbers=[]): def install_certificate(name, cert='', private_key=None, key_type=None, key_passphrase=None, is_ca=False): # Show conf commands for installing certificate prefix = 'ca' if is_ca else 'certificate' - print("Configure mode commands to install:") + print('Configure mode commands to install:') + base = f"set pki {prefix} {name}" if cert: cert_pem = "".join(encode_certificate(cert).strip().split("\n")[1:-1]) - print("set pki %s %s certificate '%s'" % (prefix, name, cert_pem)) + print(f"{base} certificate '{cert_pem}'") if private_key: key_pem = "".join(encode_private_key(private_key, passphrase=key_passphrase).strip().split("\n")[1:-1]) - print("set pki %s %s private key '%s'" % (prefix, name, key_pem)) + print(f"{base} private key '{key_pem}'") if key_passphrase: - print("set pki %s %s private password-protected" % (prefix, name)) + print(f"{base} private password-protected") def install_crl(ca_name, crl): # Show conf commands for installing crl print("Configure mode commands to install CRL:") crl_pem = "".join(encode_certificate(crl).strip().split("\n")[1:-1]) - print("set pki ca %s crl '%s'" % (ca_name, crl_pem)) + print(f"set pki ca {ca_name} crl '{crl_pem}'") def install_dh_parameters(name, params): # Show conf commands for installing dh params print("Configure mode commands to install DH parameters:") dh_pem = "".join(encode_dh_parameters(params).strip().split("\n")[1:-1]) - print("set pki dh %s parameters '%s'" % (name, dh_pem)) + print(f"set pki dh {name} parameters '{dh_pem}'") def install_ssh_key(name, public_key, private_key, passphrase=None): # Show conf commands for installing ssh key key_openssh = encode_public_key(public_key, encoding='OpenSSH', key_format='OpenSSH') username = os.getlogin() type_key_split = key_openssh.split(" ") + + base = f"set system login user {username} authentication public-keys {name}" print("Configure mode commands to install SSH key:") - print("set system login user %s authentication public-keys %s key '%s'" % (username, name, type_key_split[1])) - print("set system login user %s authentication public-keys %s type '%s'" % (username, name, type_key_split[0])) - print("") + print(f"{base} key '{type_key_split[1]}'") + print(f"{base} type '{type_key_split[0]}'", end="\n\n") print(encode_private_key(private_key, encoding='PEM', key_format='OpenSSH', passphrase=passphrase)) def install_keypair(name, key_type, private_key=None, public_key=None, passphrase=None): @@ -189,7 +191,7 @@ def install_keypair(name, key_type, private_key=None, public_key=None, passphras if install_public_key: install_public_pem = "".join(public_key_pem.strip().split("\n")[1:-1]) - print("set pki key-pair %s public key '%s'" % (name, install_public_pem)) + print(f"set pki key-pair {name} public key '{install_public_pem}'") else: print("Public key:") print(public_key_pem) @@ -200,30 +202,53 @@ def install_keypair(name, key_type, private_key=None, public_key=None, passphras if install_private_key: install_private_pem = "".join(private_key_pem.strip().split("\n")[1:-1]) - print("set pki key-pair %s private key '%s'" % (name, install_private_pem)) + print(f"set pki key-pair {name} private key '{install_private_pem}'") if passphrase: - print("set pki key-pair %s private password-protected" % (name,)) + print(f"set pki key-pair {name} private password-protected") else: print("Private key:") print(private_key_pem) -def install_wireguard_key(name, private_key, public_key): +def install_wireguard_key(interface, private_key, public_key): # Show conf commands for installing wireguard key pairs - is_interface = re.match(r'^wg[\d]+$', name) + from vyos.ifconfig import Section + if Section.section(interface) != 'wireguard': + print(f'"{interface}" is not a WireGuard interface name!') + exit(1) + + # Check if we are running in a config session - if yes, we can directly write to the CLI + cli_string = f"interfaces wireguard {interface} private-key '{private_key}'" + if Config().in_session(): + cmd(f"/opt/vyatta/sbin/my_set {cli_string}") + + print('"generate" CLI command executed from config session.\nGenerated private-key was imported to CLI!',end='\n\n') + print(f'Use the following command to verify: show interfaces wireguard {interface}') + else: + print('"generate" CLI command executed from operational level.\n' + 'Generated private-key is not stored to CLI, use configure mode commands to install key:', end='\n\n') + print(f"set {cli_string}", end="\n\n") - print("Configure mode commands to install key:") - if is_interface: - print("set interfaces wireguard %s private-key '%s'" % (name, private_key)) - print("") - print("Public key for use on peer configuration: " + public_key) + print(f"Corresponding public-key to use on peer system is: '{public_key}'") + + +def install_wireguard_psk(interface, peer, psk): + from vyos.ifconfig import Section + if Section.section(interface) != 'wireguard': + print(f'"{interface}" is not a WireGuard interface name!') + exit(1) + + # Check if we are running in a config session - if yes, we can directly write to the CLI + cli_string = f"interfaces wireguard {interface} peer {peer} preshared-key '{psk}'" + if Config().in_session(): + cmd(f"/opt/vyatta/sbin/my_set {cli_string}") + + print('"generate" CLI command executed from config session.\nGenerated preshared-key was imported to CLI!',end='\n\n') + print(f'Use the following command to verify: show interfaces wireguard {interface}') else: - print("set interfaces wireguard [INTERFACE] peer %s public-key '%s'" % (name, public_key)) - print("") - print("Private key for use on peer configuration: " + private_key) + print('"generate" CLI command executed from operational level.\n' + 'Generated preshared-key is not stored to CLI, use configure mode commands to install key:', end='\n\n') + print(f"set {cli_string}", end="\n\n") -def install_wireguard_psk(name, psk): - # Show conf commands for installing wireguard psk - print("set interfaces wireguard [INTERFACE] peer %s preshared-key '%s'" % (name, psk)) def ask_passphrase(): passphrase = None @@ -464,7 +489,7 @@ def generate_certificate_sign(name, ca_name, install=False, file=False): if not cert_req: print("Invalid certificate request") return None - + cert = generate_certificate(cert_req, ca_cert, ca_private_key, is_ca=False) passphrase = ask_passphrase() @@ -630,49 +655,37 @@ def generate_openvpn_key(name, install=False, file=False): key_data = "".join(key_lines[1:-1]) # Remove wrapper tags and line endings key_version = '1' + import re version_search = re.search(r'BEGIN OpenVPN Static key V(\d+)', result) # Future-proofing (hopefully) if version_search: key_version = version_search[1] + base = f"set pki openvpn shared-secret {name}" print("Configure mode commands to install OpenVPN key:") - print("set pki openvpn shared-secret %s key '%s'" % (name, key_data)) - print("set pki openvpn shared-secret %s version '%s'" % (name, key_version)) + print(f"{base} key '{key_data}'") + print(f"{base} version '{key_version}'") if file: write_file(f'{name}.key', result) -def generate_wireguard_key(name, install=False, file=False): +def generate_wireguard_key(interface=None, install=False): private_key = cmd('wg genkey') public_key = cmd('wg pubkey', input=private_key) - if not install: - print("Private key: " + private_key) - print("Public key: " + public_key) - return None - - if install: - install_wireguard_key(name, private_key, public_key) - - if file: - write_file(f'{name}_public.key', public_key) - write_file(f'{name}_private.key', private_key) + if interface and install: + install_wireguard_key(interface, private_key, public_key) + else: + print(f'Private key: {private_key}') + print(f'Public key: {public_key}', end='\n\n') -def generate_wireguard_psk(name, install=False, file=False): +def generate_wireguard_psk(interface=None, peer=None, install=False): psk = cmd('wg genpsk') - - if not install and not file: - print("Pre-shared key:") - print(psk) - return None - - if install: - install_wireguard_psk(name, psk) - - if file: - write_file(f'{name}.key', psk) + if interface and peer and install: + install_wireguard_psk(interface, peer, psk) + else: + print(f'Pre-shared key: {psk}') # Show functions - def show_certificate_authority(name=None): headers = ['Name', 'Subject', 'Issuer CN', 'Issued', 'Expiry', 'Private Key', 'Parent'] data = [] @@ -789,10 +802,13 @@ if __name__ == '__main__': # OpenVPN parser.add_argument('--openvpn', help='OpenVPN TLS key', required=False) - # Wireguard + # WireGuard parser.add_argument('--wireguard', help='Wireguard', action='store_true') - parser.add_argument('--key', help='Wireguard key pair', required=False) - parser.add_argument('--psk', help='Wireguard pre shared key', required=False) + group = parser.add_mutually_exclusive_group() + group.add_argument('--key', help='Wireguard key pair', action='store_true', required=False) + group.add_argument('--psk', help='Wireguard pre shared key', action='store_true', required=False) + parser.add_argument('--interface', help='Install generated keys into running-config for named interface', action='store') + parser.add_argument('--peer', help='Install generated keys into running-config for peer', action='store') # Global parser.add_argument('--file', help='Write generated keys into specified filename', action='store_true') @@ -813,26 +829,47 @@ if __name__ == '__main__': elif args.self_sign: generate_certificate_selfsign(args.certificate, install=args.install, file=args.file) else: - generate_certificate_request(name=args.certificate, install=args.install) + generate_certificate_request(name=args.certificate, install=args.install, file=args.file) + elif args.crl: generate_certificate_revocation_list(args.crl, install=args.install, file=args.file) + elif args.ssh: generate_ssh_keypair(args.ssh, install=args.install, file=args.file) + elif args.dh: generate_dh_parameters(args.dh, install=args.install, file=args.file) + elif args.keypair: generate_keypair(args.keypair, install=args.install, file=args.file) + elif args.openvpn: generate_openvpn_key(args.openvpn, install=args.install, file=args.file) + elif args.wireguard: + # WireGuard supports writing key directly into the CLI, but this + # requires the vyos_libexec_dir environment variable to be set + os.environ["vyos_libexec_dir"] = "/usr/libexec/vyos" + if args.key: - generate_wireguard_key(args.key, install=args.install, file=args.file) - elif args.psk: - generate_wireguard_psk(args.psk, install=args.install, file=args.file) + generate_wireguard_key(args.interface, install=args.install) + if args.psk: + generate_wireguard_psk(args.interface, peer=args.peer, install=args.install) + elif args.action == 'show': if args.ca: - show_certificate_authority(None if args.ca == 'all' else args.ca) + ca_name = None if args.ca == 'all' else args.ca + if ca_name: + if not conf.exists(['pki', 'ca', ca_name]): + print(f'CA "{ca_name}" does not exist!') + exit(1) + show_certificate_authority(ca_name) elif args.certificate: + cert_name = None if args.certificate == 'all' else args.certificate + if cert_name: + if not conf.exists(['pki', 'certificate', cert_name]): + print(f'Certificate "{cert_name}" does not exist!') + exit(1) show_certificate(None if args.certificate == 'all' else args.certificate) elif args.crl: show_crl(None if args.crl == 'all' else args.crl) diff --git a/src/op_mode/powerctrl.py b/src/op_mode/powerctrl.py index f8b5a3dda..679b03c0b 100755 --- a/src/op_mode/powerctrl.py +++ b/src/op_mode/powerctrl.py @@ -92,37 +92,40 @@ def cancel_shutdown(): try: run('/sbin/shutdown -c --no-wall') except OSError as e: - exit("Could not cancel a reboot or poweroff: %s" % e) + exit(f'Could not cancel a reboot or poweroff: {e}') - message = 'Scheduled {} has been cancelled {}'.format(output['MODE'], timenow) + mode = output['MODE'] + message = f'Scheduled {mode} has been cancelled {timenow}' run(f'wall {message} > /dev/null 2>&1') else: print("Reboot or poweroff is not scheduled") def execute_shutdown(time, reboot=True, ask=True): + action = "reboot" if reboot else "poweroff" if not ask: - action = "reboot" if reboot else "poweroff" - if not ask_yes_no("Are you sure you want to %s this system?" % action): + if not ask_yes_no(f"Are you sure you want to {action} this system?"): exit(0) - - action = "-r" if reboot else "-P" + action_cmd = "-r" if reboot else "-P" if len(time) == 0: # T870 legacy reboot job support chk_vyatta_based_reboots() ### - out = cmd(f'/sbin/shutdown {action} now', stderr=STDOUT) + out = cmd(f'/sbin/shutdown {action_cmd} now', stderr=STDOUT) print(out.split(",", 1)[0]) return elif len(time) == 1: # Assume the argument is just time ts = parse_time(time[0]) if ts: - cmd(f'/sbin/shutdown {action} {time[0]}', stderr=STDOUT) + cmd(f'/sbin/shutdown {action_cmd} {time[0]}', stderr=STDOUT) + # Inform all other logged in users about the reboot/shutdown + wall_msg = f'System {action} is scheduled {time[0]}' + cmd(f'/usr/bin/wall "{wall_msg}"') else: - exit("Invalid time \"{0}\". The valid format is HH:MM".format(time[0])) + exit(f'Invalid time "{time[0]}". The valid format is HH:MM') elif len(time) == 2: # Assume it's date and time ts = parse_time(time[0]) @@ -131,14 +134,18 @@ def execute_shutdown(time, reboot=True, ask=True): t = datetime.combine(ds, ts) td = t - datetime.now() t2 = 1 + int(td.total_seconds())//60 # Get total minutes - cmd('/sbin/shutdown {action} {t2}', stderr=STDOUT) + + cmd(f'/sbin/shutdown {action_cmd} {t2}', stderr=STDOUT) + # Inform all other logged in users about the reboot/shutdown + wall_msg = f'System {action} is scheduled {time[1]} {time[0]}' + cmd(f'/usr/bin/wall "{wall_msg}"') else: if not ts: - exit("Invalid time \"{0}\". The valid format is HH:MM".format(time[0])) + exit(f'Invalid time "{time[0]}". The valid format is HH:MM') else: - exit("Invalid time \"{0}\". A valid format is YYYY-MM-DD [HH:MM]".format(time[1])) + exit(f'Invalid date "{time[1]}". A valid format is YYYY-MM-DD [HH:MM]') else: - exit("Could not decode date and time. Valids formats are HH:MM or YYYY-MM-DD HH:MM") + exit('Could not decode date and time. Valids formats are HH:MM or YYYY-MM-DD HH:MM') check_shutdown() diff --git a/src/op_mode/restart_frr.py b/src/op_mode/restart_frr.py index d1b66b33f..109c8dd7b 100755 --- a/src/op_mode/restart_frr.py +++ b/src/op_mode/restart_frr.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019 VyOS maintainers and contributors +# Copyright (C) 2019-2021 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -13,16 +13,19 @@ # # 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 argparse import logging -from logging.handlers import SysLogHandler -from pathlib import Path import psutil +from logging.handlers import SysLogHandler +from shutil import rmtree + from vyos.util import call +from vyos.util import ask_yes_no +from vyos.util import process_named_running +from vyos.util import makedir # some default values watchfrr = '/usr/lib/frr/watchfrr.sh' @@ -40,40 +43,45 @@ logger.setLevel(logging.INFO) def _check_safety(): try: # print warning - answer = input("WARNING: This is a potentially unsafe function! You may lose the connection to the router or active configuration after running this command. Use it at your own risk! Continue? [y/N]: ") - if not answer.lower() == "y": - logger.error("User aborted command") + if not ask_yes_no('WARNING: This is a potentially unsafe function!\n' \ + 'You may lose the connection to the router or active configuration after\n' \ + 'running this command. Use it at your own risk!\n\n' + 'Continue?'): return False # check if another restart process already running if len([process for process in psutil.process_iter(attrs=['pid', 'name', 'cmdline']) if 'python' in process.info['name'] and 'restart_frr.py' in process.info['cmdline'][1]]) > 1: - logger.error("Another restart_frr.py already running") - answer = input("Another restart_frr.py process is already running. It is unsafe to continue. Do you want to process anyway? [y/N]: ") - if not answer.lower() == "y": + message = 'Another restart_frr.py process is already running!' + logger.error(message) + if not ask_yes_no(f'\n{message} It is unsafe to continue.\n\n' \ + 'Do you want to process anyway?'): return False # check if watchfrr.sh is running - for process in psutil.process_iter(attrs=['pid', 'name', 'cmdline']): - if 'bash' in process.info['name'] and watchfrr in process.info['cmdline']: - logger.error("Another {} already running".format(watchfrr)) - answer = input("Another {} process is already running. It is unsafe to continue. Do you want to process anyway? [y/N]: ".format(watchfrr)) - if not answer.lower() == "y": - return False + tmp = os.path.basename(watchfrr) + if process_named_running(tmp): + message = f'Another {tmp} process is already running.' + logger.error(message) + if not ask_yes_no(f'{message} It is unsafe to continue.\n\n' \ + 'Do you want to process anyway?'): + return False # check if vtysh is running - for process in psutil.process_iter(attrs=['pid', 'name', 'cmdline']): - if 'vtysh' in process.info['name']: - logger.error("The vtysh is running by another task") - answer = input("The vtysh is running by another task. It is unsafe to continue. Do you want to process anyway? [y/N]: ") - if not answer.lower() == "y": - return False + if process_named_running('vtysh'): + message = 'vtysh process is executed by another task.' + logger.error(message) + if not ask_yes_no(f'{message} It is unsafe to continue.\n\n' \ + 'Do you want to process anyway?'): + return False # check if temporary directory exists - if Path(frrconfig_tmp).exists(): - logger.error("The temporary directory \"{}\" already exists".format(frrconfig_tmp)) - answer = input("The temporary directory \"{}\" already exists. It is unsafe to continue. Do you want to process anyway? [y/N]: ".format(frrconfig_tmp)) - if not answer.lower() == "y": + if os.path.exists(frrconfig_tmp): + message = f'Temporary directory "{frrconfig_tmp}" already exists!' + logger.error(message) + if not ask_yes_no(f'{message} It is unsafe to continue.\n\n' \ + 'Do you want to process anyway?'): return False + except: logger.error("Something goes wrong in _check_safety()") return False @@ -84,94 +92,68 @@ def _check_safety(): # write active config to file def _write_config(): # create temporary directory - Path(frrconfig_tmp).mkdir(parents=False, exist_ok=True) + makedir(frrconfig_tmp) # save frr.conf to it - command = "{} -n -w --config_dir {} 2> /dev/null".format(vtysh, frrconfig_tmp) + command = f'{vtysh} -n -w --config_dir {frrconfig_tmp} 2> /dev/null' return_code = call(command) - if not return_code == 0: - logger.error("Failed to save active config: \"{}\" returned exit code: {}".format(command, return_code)) + if return_code != 0: + logger.error(f'Failed to save active config: "{command}" returned exit code: {return_code}') return False - logger.info("Active config saved to {}".format(frrconfig_tmp)) + logger.info(f'Active config saved to {frrconfig_tmp}') return True # clear and remove temporary directory def _cleanup(): - tmpdir = Path(frrconfig_tmp) - try: - if tmpdir.exists(): - for file in tmpdir.iterdir(): - file.unlink() - tmpdir.rmdir() - except: - logger.error("Failed to remove temporary directory {}".format(frrconfig_tmp)) - print("Failed to remove temporary directory {}".format(frrconfig_tmp)) - -# check if daemon is running -def _daemon_check(daemon): - command = "{} print_status {}".format(watchfrr, daemon) - return_code = call(command) - if not return_code == 0: - logger.error("Daemon \"{}\" is not running".format(daemon)) - return False - - # return True if all checks were passed - return True + if os.path.isdir(frrconfig_tmp): + rmtree(frrconfig_tmp) # restart daemon def _daemon_restart(daemon): - command = "{} restart {}".format(watchfrr, daemon) + command = f'{watchfrr} restart {daemon}' return_code = call(command) if not return_code == 0: - logger.error("Failed to restart daemon \"{}\"".format(daemon)) + logger.error(f'Failed to restart daemon "{daemon}"!') return False # return True if restarted successfully - logger.info("Daemon \"{}\" restarted".format(daemon)) + logger.info(f'Daemon "{daemon}" restarted!') return True # reload old config def _reload_config(daemon): if daemon != '': - command = "{} -n -b --config_dir {} -d {} 2> /dev/null".format(vtysh, frrconfig_tmp, daemon) + command = f'{vtysh} -n -b --config_dir {frrconfig_tmp} -d {daemon} 2> /dev/null' else: - command = "{} -n -b --config_dir {} 2> /dev/null".format(vtysh, frrconfig_tmp) + command = f'{vtysh} -n -b --config_dir {frrconfig_tmp} 2> /dev/null' return_code = call(command) if not return_code == 0: - logger.error("Failed to reinstall configuration") + logger.error('Failed to re-install configuration!') return False # return True if restarted successfully - logger.info("Configuration reinstalled successfully") - return True - -# check all daemons if they are running -def _check_args_daemon(daemons): - for daemon in daemons: - if not _daemon_check(daemon): - return False + logger.info('Configuration re-installed successfully!') return True # define program arguments cmd_args_parser = argparse.ArgumentParser(description='restart frr daemons') cmd_args_parser.add_argument('--action', choices=['restart'], required=True, help='action to frr daemons') -cmd_args_parser.add_argument('--daemon', choices=['bfdd', 'bgpd', 'ospfd', 'ospf6d', 'ripd', 'ripngd', 'staticd', 'zebra'], required=False, nargs='*', help='select single or multiple daemons') +cmd_args_parser.add_argument('--daemon', choices=['bfdd', 'bgpd', 'ospfd', 'ospf6d', 'isisd', 'ripd', 'ripngd', 'staticd', 'zebra'], required=False, nargs='*', help='select single or multiple daemons') # parse arguments cmd_args = cmd_args_parser.parse_args() - # main logic # restart daemon if cmd_args.action == 'restart': # check if it is safe to restart FRR if not _check_safety(): print("\nOne of the safety checks was failed or user aborted command. Exiting.") - sys.exit(1) + exit(1) if not _write_config(): print("Failed to save active config") _cleanup() - sys.exit(1) + exit(1) # a little trick to make further commands more clear if not cmd_args.daemon: @@ -179,19 +161,20 @@ if cmd_args.action == 'restart': # check all daemons if they are running if cmd_args.daemon != ['']: - if not _check_args_daemon(cmd_args.daemon): - print("Warning: some of listed daemons are not running") + for daemon in cmd_args.daemon: + if not process_named_running(daemon): + print('WARNING: some of listed daemons are not running!') # run command to restart daemon for daemon in cmd_args.daemon: if not _daemon_restart(daemon): - print("Failed to restart daemon: {}".format(daemon)) + print('Failed to restart daemon: {daemon}') _cleanup() - sys.exit(1) + exit(1) # reinstall old configuration _reload_config(daemon) # cleanup after all actions _cleanup() -sys.exit(0) +exit(0) diff --git a/src/op_mode/show_dhcp.py b/src/op_mode/show_dhcp.py index 4df275e04..cd6e8ed43 100755 --- a/src/op_mode/show_dhcp.py +++ b/src/op_mode/show_dhcp.py @@ -177,7 +177,7 @@ if __name__ == '__main__': group = parser.add_mutually_exclusive_group() group.add_argument("-l", "--leases", action="store_true", help="Show DHCP leases") group.add_argument("-s", "--statistics", action="store_true", help="Show DHCP statistics") - group.add_argument("--allowed", type=str, choices=["pool", "sort", "state"], help="Show allowed values for argument") + group.add_argument("--allowed", type=str, choices=["sort", "state"], help="Show allowed values for argument") parser.add_argument("-p", "--pool", type=str, help="Show lease for specific pool") parser.add_argument("-S", "--sort", type=str, default='ip', help="Sort by") @@ -188,11 +188,7 @@ if __name__ == '__main__': conf = Config() - if args.allowed == 'pool': - if conf.exists_effective('service dhcp-server'): - print(' '.join(conf.list_effective_nodes("service dhcp-server shared-network-name"))) - exit(0) - elif args.allowed == 'sort': + if args.allowed == 'sort': print(' '.join(lease_display_fields.keys())) exit(0) elif args.allowed == 'state': diff --git a/src/op_mode/show_interfaces.py b/src/op_mode/show_interfaces.py index 20d5d9e17..3d50eb938 100755 --- a/src/op_mode/show_interfaces.py +++ b/src/op_mode/show_interfaces.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -# Copyright 2017, 2019 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2017-2021 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 @@ -19,9 +19,7 @@ import os import re import sys import glob -import datetime import argparse -import netifaces from vyos.ifconfig import Section from vyos.ifconfig import Interface @@ -63,27 +61,27 @@ def filtered_interfaces(ifnames, iftypes, vif, vrrp): ifnames: a list of interfaces names to consider, empty do not filter return an instance of the interface class """ - allnames = Section.interfaces() + if isinstance(iftypes, list): + for iftype in iftypes: + yield from filtered_interfaces(ifnames, iftype, vif, vrrp) - vrrp_interfaces = VRRP.active_interfaces() if vrrp else [] - - for ifname in allnames: + for ifname in Section.interfaces(iftypes): + # Bail out early if interface name not part of our search list if ifnames and ifname not in ifnames: continue - # return the class which can handle this interface name - klass = Section.klass(ifname) - # connect to the interface - interface = klass(ifname, create=False, debug=False) - - if iftypes and interface.definition['section'] not in iftypes: - continue + # As we are only "reading" from the interface - we must use the + # generic base class which exposes all the data via a common API + interface = Interface(ifname, create=False, debug=False) + # VLAN interfaces have a '.' in their name by convention if vif and not '.' in ifname: continue - if vrrp and ifname not in vrrp_interfaces: - continue + if vrrp: + vrrp_interfaces = VRRP.active_interfaces() + if ifname not in vrrp_interfaces: + continue yield interface @@ -120,10 +118,6 @@ def split_text(text, used=0): yield line[1:] -def get_vrrp_intf(): - return [intf for intf in Section.interfaces() if intf.is_vrrp()] - - def get_counter_val(clear, now): """ attempt to correct a counter if it wrapped, copied from perl diff --git a/src/op_mode/show_ipsec_sa.py b/src/op_mode/show_ipsec_sa.py index e491267fd..c964caaeb 100755 --- a/src/op_mode/show_ipsec_sa.py +++ b/src/op_mode/show_ipsec_sa.py @@ -23,6 +23,12 @@ import hurry.filesize import vyos.util +def convert(text): + return int(text) if text.isdigit() else text.lower() + +def alphanum_key(key): + return [convert(c) for c in re.split('([0-9]+)', str(key))] + def format_output(conns, sas): sa_data = [] @@ -111,7 +117,7 @@ if __name__ == '__main__': headers = ["Connection", "State", "Uptime", "Bytes In/Out", "Packets In/Out", "Remote address", "Remote ID", "Proposal"] sa_data = format_output(conns, sas) - sa_data = sorted(sa_data, key=lambda peer: peer[0]) + sa_data = sorted(sa_data, key=alphanum_key) output = tabulate.tabulate(sa_data, headers) print(output) except PermissionError: diff --git a/src/op_mode/show_nat_rules.py b/src/op_mode/show_nat_rules.py index 0f40ecabe..d68def26a 100755 --- a/src/op_mode/show_nat_rules.py +++ b/src/op_mode/show_nat_rules.py @@ -67,46 +67,54 @@ if args.source or args.destination: continue interface = dict_search('match.right', data['expr'][0]) srcdest = '' - for i in [1, 2]: - srcdest_json = dict_search('match.right', data['expr'][i]) - if not srcdest_json: - continue - - if isinstance(srcdest_json,str): - srcdest += srcdest_json + ' ' - elif 'prefix' in srcdest_json: - addr_tmp = dict_search('match.right.prefix.addr', data['expr'][i]) - len_tmp = dict_search('match.right.prefix.len', data['expr'][i]) - if addr_tmp and len_tmp: - srcdest = addr_tmp + '/' + str(len_tmp) + ' ' - elif 'set' in srcdest_json: - if isinstance(srcdest_json['set'][0],str): - srcdest += 'port ' + str(srcdest_json['set'][0]) + ' ' - else: - port_range = srcdest_json['set'][0]['range'] - srcdest += 'port ' + str(port_range[0]) + '-' + str(port_range[1]) + ' ' - + srcdests = [] tran_addr = '' - tran_addr_json = dict_search('snat.addr' if args.source else 'dnat.addr', data['expr'][3]) - if tran_addr_json: - if isinstance(tran_addr_json,str): - tran_addr = tran_addr_json - elif 'prefix' in tran_addr_json: - addr_tmp = dict_search('snat.addr.prefix.addr' if args.source else 'dnat.addr.prefix.addr', data['expr'][3]) - len_tmp = dict_search('snat.addr.prefix.len' if args.source else 'dnat.addr.prefix.len', data['expr'][3]) - if addr_tmp and len_tmp: - tran_addr = addr_tmp + '/' + str(len_tmp) - else: - if 'masquerade' in data['expr'][3]: - tran_addr = 'masquerade' - elif 'log' in data['expr'][3]: - continue - - tran_port = dict_search('snat.port' if args.source else 'dnat.port', data['expr'][3]) - if tran_port: - tran_addr += ' port ' + str(tran_port) + for i in range(1,len(data['expr']) ): + srcdest_json = dict_search('match.right', data['expr'][i]) + if srcdest_json: + if isinstance(srcdest_json,str): + if srcdest != '': + srcdests.append(srcdest) + srcdest = '' + srcdest = srcdest_json + ' ' + elif 'prefix' in srcdest_json: + addr_tmp = dict_search('match.right.prefix.addr', data['expr'][i]) + len_tmp = dict_search('match.right.prefix.len', data['expr'][i]) + if addr_tmp and len_tmp: + srcdest = addr_tmp + '/' + str(len_tmp) + ' ' + elif 'set' in srcdest_json: + if isinstance(srcdest_json['set'][0],int): + srcdest += 'port ' + str(srcdest_json['set'][0]) + ' ' + else: + port_range = srcdest_json['set'][0]['range'] + srcdest += 'port ' + str(port_range[0]) + '-' + str(port_range[1]) + ' ' + + tran_addr_json = dict_search('snat' if args.source else 'dnat', data['expr'][i]) + if tran_addr_json: + if isinstance(tran_addr_json['addr'],str): + tran_addr += tran_addr_json['addr'] + ' ' + elif 'prefix' in tran_addr_json['addr']: + addr_tmp = dict_search('snat.addr.prefix.addr' if args.source else 'dnat.addr.prefix.addr', data['expr'][3]) + len_tmp = dict_search('snat.addr.prefix.len' if args.source else 'dnat.addr.prefix.len', data['expr'][3]) + if addr_tmp and len_tmp: + tran_addr += addr_tmp + '/' + str(len_tmp) + ' ' + + if isinstance(tran_addr_json['port'],int): + tran_addr += 'port ' + tran_addr_json['port'] + + else: + if 'masquerade' in data['expr'][i]: + tran_addr = 'masquerade' + elif 'log' in data['expr'][i]: + continue - print(format_nat_rule.format(rule, srcdest, tran_addr, interface)) + if srcdest != '': + srcdests.append(srcdest) + srcdest = '' + print(format_nat_rule.format(rule, srcdests[0], tran_addr, interface)) + + for i in range(1, len(srcdests)): + print(format_nat_rule.format(' ', srcdests[i], ' ', ' ')) exit(0) else: diff --git a/src/op_mode/show_system_integrity.py b/src/op_mode/show_system_integrity.py deleted file mode 100755 index c34d41e80..000000000 --- a/src/op_mode/show_system_integrity.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2020 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -# - -import sys -import os -import re -import json -from datetime import datetime, timedelta - -version_file = r'/usr/share/vyos/version.json' - - -def _get_sys_build_version(): - if not os.path.exists(version_file): - return None - buf = open(version_file, 'r').read() - j = json.loads(buf) - if not 'built_on' in j: - return None - return datetime.strptime(j['built_on'], '%a %d %b %Y %H:%M %Z') - - -def _check_pkgs(build_stamp): - pkg_diffs = { - 'buildtime': str(build_stamp), - 'pkg': {} - } - - pkg_info = os.listdir('/var/lib/dpkg/info/') - for file in pkg_info: - if re.search('\.list$', file): - fts = os.stat('/var/lib/dpkg/info/' + file).st_mtime - dt_str = (datetime.utcfromtimestamp( - fts).strftime('%Y-%m-%d %H:%M:%S')) - fdt = datetime.strptime(dt_str, '%Y-%m-%d %H:%M:%S') - if fdt > build_stamp: - pkg_diffs['pkg'].update( - {str(re.sub('\.list', '', file)): str(fdt)}) - - if len(pkg_diffs['pkg']) != 0: - return pkg_diffs - else: - return None - - -if __name__ == '__main__': - built_date = _get_sys_build_version() - if not built_date: - sys.exit(1) - pkgs = _check_pkgs(built_date) - if pkgs: - print ( - "The following packages don\'t fit the image creation time\nbuild time:\t" + pkgs['buildtime']) - for k, v in pkgs['pkg'].items(): - print ("installed: " + v + '\t' + k) diff --git a/src/op_mode/show_version.py b/src/op_mode/show_version.py index 5bbc2e1f1..7962e1e7b 100755 --- a/src/op_mode/show_version.py +++ b/src/op_mode/show_version.py @@ -32,12 +32,12 @@ parser.add_argument("-j", "--json", action="store_true", help="Produce JSON outp version_output_tmpl = """ Version: VyOS {{version}} -Release Train: {{release_train}} +Release train: {{release_train}} Built by: {{built_by}} Built on: {{built_on}} Build UUID: {{build_uuid}} -Build Commit ID: {{build_git}} +Build commit ID: {{build_git}} Architecture: {{system_arch}} Boot via: {{boot_via}} diff --git a/src/op_mode/show_wwan.py b/src/op_mode/show_wwan.py index 249dda2a5..529b5bd0f 100755 --- a/src/op_mode/show_wwan.py +++ b/src/op_mode/show_wwan.py @@ -34,13 +34,17 @@ required = parser.add_argument_group('Required arguments') required.add_argument("--interface", help="WWAN interface name, e.g. wwan0", required=True) def qmi_cmd(device, command, silent=False): - tmp = cmd(f'qmicli --device={device} --device-open-proxy {command}') - tmp = tmp.replace(f'[{cdc}] ', '') - if not silent: - # skip first line as this only holds the info headline - for line in tmp.splitlines()[1:]: - print(line.lstrip()) - return tmp + try: + tmp = cmd(f'qmicli --device={device} --device-open-proxy {command}') + tmp = tmp.replace(f'[{cdc}] ', '') + if not silent: + # skip first line as this only holds the info headline + for line in tmp.splitlines()[1:]: + print(line.lstrip()) + return tmp + except: + print('Command not supported by Modem') + exit(1) if __name__ == '__main__': args = parser.parse_args() diff --git a/src/op_mode/wireguard_client.py b/src/op_mode/wireguard_client.py index 7661254da..76c1ff7d1 100755 --- a/src/op_mode/wireguard_client.py +++ b/src/op_mode/wireguard_client.py @@ -39,10 +39,11 @@ To enable this configuration on a VyOS router you can use the following commands set interfaces wireguard {{ interface }} peer {{ name }} allowed-ips '{{ addr }}' {% endfor %} set interfaces wireguard {{ interface }} peer {{ name }} public-key '{{ pubkey }}' + +=== RoadWarrior (client) configuration === """ client_config = """ -=== RoadWarrior (client) configuration === [Interface] PrivateKey = {{ privkey }} diff --git a/src/system/keepalived-fifo.py b/src/system/keepalived-fifo.py index 3b4330e9b..1fba0d75b 100755 --- a/src/system/keepalived-fifo.py +++ b/src/system/keepalived-fifo.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-2021 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -13,7 +13,6 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -# import os import time @@ -22,11 +21,13 @@ import argparse import threading import re import json -from pathlib import Path -from queue import Queue import logging + +from queue import Queue from logging.handlers import SysLogHandler +from vyos.ifconfig.vrrp import VRRP +from vyos.configquery import ConfigTreeQuery from vyos.util import cmd # configure logging @@ -44,12 +45,13 @@ mdns_update_command = 'sudo /usr/libexec/vyos/conf_mode/service_mdns-repeater.py class KeepalivedFifo: # init - read command arguments def __init__(self): - logger.info("Starting FIFO pipe for Keepalived") + logger.info('Starting FIFO pipe for Keepalived') # define program arguments cmd_args_parser = argparse.ArgumentParser(description='Create FIFO pipe for keepalived and process notify events', add_help=False) cmd_args_parser.add_argument('PIPE', help='path to the FIFO pipe') # parse arguments cmd_args = cmd_args_parser.parse_args() + self._config_load() self.pipe_path = cmd_args.PIPE @@ -61,33 +63,34 @@ class KeepalivedFifo: # load configuration def _config_load(self): try: - # read the dictionary file with configuration - with open('/run/keepalived_config.dict', 'r') as dict_file: - vrrp_config_dict = json.load(dict_file) + base = ['high-availability', 'vrrp'] + conf = ConfigTreeQuery() + if not conf.exists(base): + raise ValueError() + + # Read VRRP configuration directly from CLI + vrrp_config_dict = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True) self.vrrp_config = {'vrrp_groups': {}, 'sync_groups': {}} - # save VRRP instances to the new dictionary - for vrrp_group in vrrp_config_dict['vrrp_groups']: - self.vrrp_config['vrrp_groups'][vrrp_group['name']] = { - 'STOP': vrrp_group.get('stop_script'), - 'FAULT': vrrp_group.get('fault_script'), - 'BACKUP': vrrp_group.get('backup_script'), - 'MASTER': vrrp_group.get('master_script') - } - # save VRRP sync groups to the new dictionary - for sync_group in vrrp_config_dict['sync_groups']: - self.vrrp_config['sync_groups'][sync_group['name']] = { - 'STOP': sync_group.get('stop_script'), - 'FAULT': sync_group.get('fault_script'), - 'BACKUP': sync_group.get('backup_script'), - 'MASTER': sync_group.get('master_script') - } - logger.debug("Loaded configuration: {}".format(self.vrrp_config)) + for key in ['group', 'sync_group']: + if key not in vrrp_config_dict: + continue + for group, group_config in vrrp_config_dict[key].items(): + if 'transition_script' not in group_config: + continue + self.vrrp_config['vrrp_groups'][group] = { + 'STOP': group_config['transition_script'].get('stop'), + 'FAULT': group_config['transition_script'].get('fault'), + 'BACKUP': group_config['transition_script'].get('backup'), + 'MASTER': group_config['transition_script'].get('master'), + } + logger.info(f'Loaded configuration: {self.vrrp_config}') except Exception as err: - logger.error("Unable to load configuration: {}".format(err)) + logger.error(f'Unable to load configuration: {err}') # run command def _run_command(self, command): - logger.debug("Running the command: {}".format(command)) + logger.debug(f'Running the command: {command}') try: cmd(command) except OSError as err: @@ -95,14 +98,14 @@ class KeepalivedFifo: # create FIFO pipe def pipe_create(self): - if Path(self.pipe_path).exists(): - logger.info("PIPE already exist: {}".format(self.pipe_path)) + if os.path.exists(self.pipe_path): + logger.info(f'PIPE already exist: {self.pipe_path}') else: os.mkfifo(self.pipe_path) # process message from pipe def pipe_process(self): - logger.debug("Message processing start") + logger.debug('Message processing start') regex_notify = re.compile(r'^(?P<type>\w+) "(?P<name>[\w-]+)" (?P<state>\w+) (?P<priority>\d+)$', re.MULTILINE) while self.stopme.is_set() is False: # wait for a new message event from pipe_wait @@ -113,14 +116,14 @@ class KeepalivedFifo: # get all messages from queue and try to process them while self.message_queue.empty() is not True: message = self.message_queue.get() - logger.debug("Received message: {}".format(message)) + logger.debug(f'Received message: {message}') notify_message = regex_notify.search(message) # try to process a message if it looks valid if notify_message: n_type = notify_message.group('type') n_name = notify_message.group('name') n_state = notify_message.group('state') - logger.info("{} {} changed state to {}".format(n_type, n_name, n_state)) + logger.info(f'{n_type} {n_name} changed state to {n_state}') # check and run commands for VRRP instances if n_type == 'INSTANCE': if os.path.exists(mdns_running_file): @@ -135,7 +138,7 @@ class KeepalivedFifo: if n_type == 'GROUP': if os.path.exists(mdns_running_file): cmd(mdns_update_command) - + if n_name in self.vrrp_config['sync_groups'] and n_state in self.vrrp_config['sync_groups'][n_name]: n_script = self.vrrp_config['sync_groups'][n_name].get(n_state) if n_script: @@ -143,16 +146,16 @@ class KeepalivedFifo: # mark task in queue as done self.message_queue.task_done() except Exception as err: - logger.error("Error processing message: {}".format(err)) - logger.debug("Terminating messages processing thread") + logger.error(f'Error processing message: {err}') + logger.debug('Terminating messages processing thread') # wait for messages def pipe_wait(self): - logger.debug("Message reading start") + logger.debug('Message reading start') self.pipe_read = os.open(self.pipe_path, os.O_RDONLY | os.O_NONBLOCK) while self.stopme.is_set() is False: # sleep a bit to not produce 100% CPU load - time.sleep(0.1) + time.sleep(0.250) try: # try to read a message from PIPE message = os.read(self.pipe_read, 500) @@ -165,21 +168,19 @@ class KeepalivedFifo: except Exception as err: # ignore the "Resource temporarily unavailable" error if err.errno != 11: - logger.error("Error receiving message: {}".format(err)) + logger.error(f'Error receiving message: {err}') - logger.debug("Closing FIFO pipe") + logger.debug('Closing FIFO pipe') os.close(self.pipe_read) - # handle SIGTERM signal to allow finish all messages processing def sigterm_handle(signum, frame): - logger.info("Ending processing: Received SIGTERM signal") + logger.info('Ending processing: Received SIGTERM signal') fifo.stopme.set() thread_wait_message.join() fifo.message_event.set() thread_process_message.join() - signal.signal(signal.SIGTERM, sigterm_handle) # init our class diff --git a/src/systemd/opennhrp.service b/src/systemd/opennhrp.service index 70235f89d..c9a44de29 100644 --- a/src/systemd/opennhrp.service +++ b/src/systemd/opennhrp.service @@ -6,8 +6,8 @@ StartLimitIntervalSec=0 [Service] Type=forking -ExecStart=/usr/sbin/opennhrp -d -v -a /run/opennhrp.socket -c /run/opennhrp/opennhrp.conf -s /etc/opennhrp/opennhrp-script.py -p /run/opennhrp.pid +ExecStart=/usr/sbin/opennhrp -d -v -a /run/opennhrp.socket -c /run/opennhrp/opennhrp.conf -s /etc/opennhrp/opennhrp-script.py -p /run/opennhrp/opennhrp.pid ExecReload=/usr/bin/kill -HUP $MAINPID -PIDFile=/run/opennhrp.pid +PIDFile=/run/opennhrp/opennhrp.pid Restart=on-failure RestartSec=20 diff --git a/src/tests/test_dict_search.py b/src/tests/test_dict_search.py index 991722f0f..1028437b2 100644 --- a/src/tests/test_dict_search.py +++ b/src/tests/test_dict_search.py @@ -16,13 +16,25 @@ from unittest import TestCase from vyos.util import dict_search +from vyos.util import dict_search_recursive data = { 'string': 'fooo', 'nested': {'string': 'bar', 'empty': '', 'list': ['foo', 'bar']}, 'non': {}, 'list': ['bar', 'baz'], - 'dict': {'key_1': {}, 'key_2': 'vyos'} + 'dict': {'key_1': {}, 'key_2': 'vyos'}, + 'interfaces': {'dummy': {'dum0': {'address': ['192.0.2.17/29']}}, + 'ethernet': {'eth0': {'address': ['2001:db8::1/64', '192.0.2.1/29'], + 'description': 'Test123', + 'duplex': 'auto', + 'hw_id': '00:00:00:00:00:01', + 'speed': 'auto'}, + 'eth1': {'address': ['192.0.2.9/29'], + 'description': 'Test456', + 'duplex': 'auto', + 'hw_id': '00:00:00:00:00:02', + 'speed': 'auto'}}} } class TestDictSearch(TestCase): @@ -63,3 +75,10 @@ class TestDictSearch(TestCase): # TestDictSearch: Return list items when querying nested list self.assertEqual(dict_search('nested.list', None), None) self.assertEqual(dict_search(None, data), None) + + def test_dict_search_recursive(self): + # Test nested search in dictionary + tmp = list(dict_search_recursive(data, 'hw_id')) + self.assertEqual(len(tmp), 2) + tmp = list(dict_search_recursive(data, 'address')) + self.assertEqual(len(tmp), 3) diff --git a/src/validators/base64 b/src/validators/base64 new file mode 100755 index 000000000..e2b1e730d --- /dev/null +++ b/src/validators/base64 @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import base64 +from sys import argv + +if __name__ == '__main__': + if len(argv) != 2: + exit(1) + try: + base64.b64decode(argv[1]) + except: + exit(1) + exit(0) diff --git a/src/validators/bgp-large-community-list b/src/validators/bgp-large-community-list new file mode 100755 index 000000000..c07268e81 --- /dev/null +++ b/src/validators/bgp-large-community-list @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import re +import sys + +from vyos.template import is_ipv4 + +pattern = '(.*):(.*):(.*)' + +if __name__ == '__main__': + if len(sys.argv) != 2: + sys.exit(1) + + value = sys.argv[1].split(':') + if not len(value) == 3: + sys.exit(1) + + if not (re.match(pattern, sys.argv[1]) and + (is_ipv4(value[0]) or value[0].isdigit()) and value[1].isdigit()): + sys.exit(1) + + sys.exit(0) diff --git a/src/validators/bgp-route-target b/src/validators/bgp-route-target new file mode 100755 index 000000000..e7e4d403f --- /dev/null +++ b/src/validators/bgp-route-target @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from argparse import ArgumentParser +from vyos.template import is_ipv4 + +parser = ArgumentParser() +group = parser.add_mutually_exclusive_group() +group.add_argument('--single', action='store', help='Validate and allow only one route-target') +group.add_argument('--multi', action='store', help='Validate multiple, whitespace separated route-targets') +args = parser.parse_args() + +def is_valid_rt(rt): + # every route target needs to have a colon and must consists of two parts + value = rt.split(':') + if len(value) != 2: + return False + # A route target must either be only numbers, or the first part must be an + # IPv4 address + if (is_ipv4(value[0]) or value[0].isdigit()) and value[1].isdigit(): + return True + return False + +if __name__ == '__main__': + if args.single: + if not is_valid_rt(args.single): + exit(1) + + elif args.multi: + for rt in args.multi.split(' '): + if not is_valid_rt(rt): + exit(1) + + else: + parser.print_help() + exit(1) + + exit(0) diff --git a/src/validators/script b/src/validators/script index 2665ec1f6..1d8a27e5c 100755 --- a/src/validators/script +++ b/src/validators/script @@ -1,8 +1,6 @@ #!/usr/bin/env python3 # -# numeric value validator -# -# Copyright (C) 2018 VyOS maintainers and contributors +# Copyright (C) 2018-2021 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -23,7 +21,6 @@ import shlex import vyos.util - if __name__ == '__main__': if len(sys.argv) < 2: sys.exit('Please specify script file to check') @@ -35,11 +32,11 @@ if __name__ == '__main__': sys.exit(f'File {script} does not exist') if not (os.path.isfile(script) and os.access(script, os.X_OK)): - sys.exit('File {script} is not an executable file') + sys.exit(f'File {script} is not an executable file') # File outside the config dir is just a warning if not vyos.util.file_is_persistent(script): sys.exit( - 'Warning: file {path} is outside the / config directory\n' + f'Warning: file {path} is outside the / config directory\n' 'It will not be automatically migrated to a new image on system update' ) |