diff options
130 files changed, 2948 insertions, 630 deletions
diff --git a/data/templates/accel-ppp/pppoe.config.tmpl b/data/templates/accel-ppp/pppoe.config.tmpl index 238e7ee15..0a8e0079b 100644 --- a/data/templates/accel-ppp/pppoe.config.tmpl +++ b/data/templates/accel-ppp/pppoe.config.tmpl @@ -108,19 +108,17 @@ ac-name={{ access_concentrator }} {% if iface_config.vlan_id is not defined and iface_config.vlan_range is not defined %} interface={{ iface }} {% endif %} -{% if iface_config.vlan_id is defined and iface_config.vlan_range is not defined %} -{% for vlan in iface_config.vlan_id %} -interface={{ iface }}.{{ vlan }} -vlan-mon={{ iface }},{{ vlan }} +{% if iface_config.vlan_range is defined %} +{% for regex in iface_config.regex %} +interface=re:^{{ iface | replace('.', '\\.') }}\.({{ regex }})$ {% endfor %} -{% endif %} -{% if iface_config.vlan_range is defined and iface_config.vlan_id is not defined %} vlan-mon={{ iface }},{{ iface_config.vlan_range | join(',') }} -interface=re:{{ iface | replace('.', '\\.') }}\.\d+ {% endif %} -{% if iface_config.vlan_id is defined and iface_config.vlan_range is defined %} -vlan-mon={{ iface }},{{ iface_config.vlan_id | join(',') }},{{ iface_config.vlan_range | join(',') }} -interface=re:{{ iface | replace('.', '\\.') }}\.\d+ +{% if iface_config.vlan_id is defined %} +{% for vlan in iface_config.vlan_id %} +vlan-mon={{ iface }},{{ vlan }} +interface=re:^{{ iface | replace('.', '\\.') }}\.{{ vlan }}$ +{% endfor %} {% endif %} {% endfor %} {% endif %} diff --git a/data/templates/dns-forwarding/recursor.conf.tmpl b/data/templates/dns-forwarding/recursor.conf.tmpl index d44f756e8..02efe903b 100644 --- a/data/templates/dns-forwarding/recursor.conf.tmpl +++ b/data/templates/dns-forwarding/recursor.conf.tmpl @@ -31,5 +31,8 @@ dnssec={{ dnssec }} # serve rfc1918 records serve-rfc1918={{ 'no' if no_serve_rfc1918 is defined else 'yes' }} +# zones +auth-zones={% for z in authoritative_zones %}{{ z.name }}={{ z.file }}{{- "," if not loop.last -}}{% endfor %} + forward-zones-file=recursor.forward-zones.conf diff --git a/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl b/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl index 784d5c360..7f29c387e 100644 --- a/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl +++ b/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl @@ -22,3 +22,9 @@ addNTA("{{ zone }}", "static") {% endfor %} {% endif %} +{% if authoritative_zones is defined %} +-- from 'service dns forwarding authoritative-domain' +{% for zone in authoritative_zones %} +addNTA("{{ zone }}", "static") +{% endfor %} +{% endif %} diff --git a/data/templates/dns-forwarding/recursor.zone.conf.tmpl b/data/templates/dns-forwarding/recursor.zone.conf.tmpl new file mode 100644 index 000000000..758871bef --- /dev/null +++ b/data/templates/dns-forwarding/recursor.zone.conf.tmpl @@ -0,0 +1,7 @@ +; +; Autogenerated by dns_forwarding.py +; +; +{% for r in records %} +{{ r.name }} {{ r.ttl }} {{ r.type }} {{ r.value }} +{% endfor %} diff --git a/data/templates/frr/bfdd.frr.tmpl b/data/templates/frr/bfdd.frr.tmpl index c14939677..439f79d67 100644 --- a/data/templates/frr/bfdd.frr.tmpl +++ b/data/templates/frr/bfdd.frr.tmpl @@ -6,13 +6,16 @@ bfd detect-multiplier {{ profile_config.interval.multiplier }} receive-interval {{ profile_config.interval.receive }} transmit-interval {{ profile_config.interval.transmit }} -{% if profile_config.interval['echo-interval'] is defined and profile_config.interval['echo-interval'] is not none %} - echo transmit-interval {{ profile_config.interval['echo-interval'] }} - echo receive-interval {{ profile_config.interval['echo-interval'] }} +{% if profile_config.interval.echo_interval is defined and profile_config.interval.echo_interval is not none %} + echo transmit-interval {{ profile_config.interval.echo_interval }} + echo receive-interval {{ profile_config.interval.echo_interval }} {% endif %} -{% if profile_config['echo-mode'] is defined %} +{% if profile_config.echo_mode is defined %} echo-mode {% endif %} +{% if profile_config.passive is defined %} + passive-mode +{% endif %} {% if profile_config.shutdown is defined %} shutdown {% else %} @@ -24,16 +27,23 @@ bfd {% endif %} {% if peer is defined and peer is not none %} {% for peer_name, peer_config in peer.items() %} - peer {{ peer_name }}{{ ' multihop' if peer_config.multihop is defined }}{{ ' local-address ' + peer_config.source.address if peer_config.source is defined and peer_config.source.address is defined }}{{ ' interface ' + peer_config.source.interface if peer_config.source is defined and peer_config.source.interface is defined }} + peer {{ peer_name }}{{ ' multihop' if peer_config.multihop is defined }}{{ ' local-address ' + peer_config.source.address if peer_config.source is defined and peer_config.source.address is defined }}{{ ' interface ' + peer_config.source.interface if peer_config.source is defined and peer_config.source.interface is defined }} {{ ' vrf ' + peer_config.vrf if peer_config.vrf is defined and peer_config.vrf is not none }} detect-multiplier {{ peer_config.interval.multiplier }} receive-interval {{ peer_config.interval.receive }} transmit-interval {{ peer_config.interval.transmit }} -{% if peer_config.interval['echo-interval'] is defined and peer_config.interval['echo-interval'] is not none %} - echo-interval {{ peer_config.interval['echo-interval'] }} +{% if peer_config.interval.echo_interval is defined and peer_config.interval.echo_interval is not none %} + echo transmit-interval {{ peer_config.interval.echo_interval }} + echo receive-interval {{ peer_config.interval.echo_interval }} {% endif %} -{% if peer_config['echo-mode'] is defined %} +{% if peer_config.echo_mode is defined %} echo-mode {% endif %} +{% if peer_config.passive is defined %} + passive-mode +{% endif %} +{% if peer_config.profile is defined and peer_config.profile is not none %} + profile {{ peer_config.profile }} +{% endif %} {% if peer_config.shutdown is defined %} shutdown {% else %} diff --git a/data/templates/frr/bgpd.frr.tmpl b/data/templates/frr/bgpd.frr.tmpl index fbdbafd6e..45e0544b7 100644 --- a/data/templates/frr/bgpd.frr.tmpl +++ b/data/templates/frr/bgpd.frr.tmpl @@ -17,6 +17,12 @@ {% endif %} {% if config.bfd is defined %} neighbor {{ neighbor }} bfd +{% if config.bfd.check_control_plane_failure is defined %} + neighbor {{ neighbor }} bfd check-control-plane-failure +{% endif %} +{% if config.bfd.profile is defined and config.bfd.profile is not none %} + neighbor {{ neighbor }} bfd profile {{ config.bfd.profile }} +{% endif %} {% endif %} {% if config.capability is defined and config.capability is not none %} {% if config.capability.dynamic is defined %} @@ -140,6 +146,17 @@ {% if afi_config.as_override is defined %} neighbor {{ neighbor }} as-override {% endif %} +{% if afi_config.conditionally_advertise is defined and afi_config.conditionally_advertise is not none %} +{% if afi_config.conditionally_advertise.advertise_map is defined and afi_config.conditionally_advertise.advertise_map is not none %} +{% set exist_non_exist_map = 'exist-map' %} +{% if afi_config.conditionally_advertise.exist_map is defined and afi_config.conditionally_advertise.exist_map is not none %} +{% set exist_non_exist_map = 'exist-map ' ~ afi_config.conditionally_advertise.exist_map %} +{% elif afi_config.conditionally_advertise.non_exist_map is defined and afi_config.conditionally_advertise.non_exist_map is not none %} +{% set exist_non_exist_map = 'non-exist-map ' ~ afi_config.conditionally_advertise.non_exist_map %} +{% endif %} + neighbor {{ neighbor }} advertise-map {{ afi_config.conditionally_advertise.advertise_map }} {{ exist_non_exist_map }} +{% endif %} +{% endif %} {% if afi_config.remove_private_as is defined %} neighbor {{ neighbor }} remove-private-AS {% endif %} @@ -469,6 +486,11 @@ router bgp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is defined and vrf is not none {% if parameters.cluster_id is defined and parameters.cluster_id is not none %} bgp cluster-id {{ parameters.cluster_id }} {% endif %} +{% if parameters.conditional_advertisement is defined and parameters.conditional_advertisement is not none %} +{% if parameters.conditional_advertisement.timer is defined and parameters.conditional_advertisement.timer is not none %} + bgp conditional-advertisement timer {{ parameters.conditional_advertisement.timer }} +{% endif %} +{% endif %} {% if parameters.confederation is defined and parameters.confederation is not none %} {% if parameters.confederation.identifier is defined and parameters.confederation.identifier is not none %} bgp confederation identifier {{ parameters.confederation.identifier }} @@ -499,6 +521,9 @@ router bgp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is defined and vrf is not none {% endfor %} {% endif %} {% endif %} +{% if parameters.fast_convergence is defined %} + bgp fast-convergence +{% endif %} {% if parameters.graceful_restart is defined %} bgp graceful-restart {{ 'stalepath-time ' ~ parameters.graceful_restart.stalepath_time if parameters.graceful_restart.stalepath_time is defined }} {% endif %} @@ -508,6 +533,9 @@ router bgp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is defined and vrf is not none {% if parameters.log_neighbor_changes is defined %} bgp log-neighbor-changes {% endif %} +{% if parameters.minimum_holdtime is defined and parameters.minimum_holdtime is not none %} + bgp minimum-holdtime {{ parameters.minimum_holdtime }} +{% endif %} {% if parameters.network_import_check is defined %} bgp network import-check {% endif %} @@ -517,11 +545,20 @@ router bgp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is defined and vrf is not none {% if parameters.no_fast_external_failover is defined %} no bgp fast-external-failover {% endif %} +{% if parameters.reject_as_sets is defined %} + bgp reject-as-sets +{% endif %} {% if parameters.router_id is defined and parameters.router_id is not none %} bgp router-id {{ parameters.router_id }} {% endif %} +{% if parameters.shutdown is defined %} + bgp shutdown +{% endif %} +{% if parameters.suppress_fib_pending is defined %} + bgp suppress-fib-pending +{% endif %} {% endif %} {% if timers is defined and timers.keepalive is defined and timers.holdtime is defined %} timers bgp {{ timers.keepalive }} {{ timers.holdtime }} {% endif %} -exit
\ No newline at end of file +exit diff --git a/data/templates/frr/isisd.frr.tmpl b/data/templates/frr/isisd.frr.tmpl index fc0799e02..b1e3f825b 100644 --- a/data/templates/frr/isisd.frr.tmpl +++ b/data/templates/frr/isisd.frr.tmpl @@ -6,6 +6,9 @@ interface {{ iface }} {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} ipv6 router isis VyOS {% if iface_config.bfd is defined %} isis bfd +{% if iface_config.bfd.profile is defined and iface_config.bfd.profile is not none %} + isis bfd profile {{ iface_config.bfd.profile }} +{% endif %} {% endif %} {% if iface_config.network is defined and iface_config.network.point_to_point is defined %} isis network point-to-point diff --git a/data/templates/frr/ldpd.frr.tmpl b/data/templates/frr/ldpd.frr.tmpl index 0a5411552..537ea4025 100644 --- a/data/templates/frr/ldpd.frr.tmpl +++ b/data/templates/frr/ldpd.frr.tmpl @@ -2,69 +2,69 @@ {% if ldp is defined %} mpls ldp {% if ldp.router_id is defined %} -router-id {{ ldp.router_id }} + router-id {{ ldp.router_id }} {% endif %} {% if ldp.parameters is defined %} {% if ldp.parameters.cisco_interop_tlv is defined %} -dual-stack cisco-interop + dual-stack cisco-interop {% endif %} {% if ldp.parameters.transport_prefer_ipv4 is defined%} -dual-stack transport-connection prefer ipv4 + dual-stack transport-connection prefer ipv4 {% endif %} {% if ldp.parameters.ordered_control is defined%} -ordered-control + ordered-control {% endif %} {% endif %} {% if ldp.neighbor is defined %} {% for neighbors in ldp.neighbor %} {% if ldp.neighbor[neighbors].password is defined %} -neighbor {{neighbors}} password {{ldp.neighbor[neighbors].password}} + neighbor {{ neighbors }} password {{ ldp.neighbor[neighbors].password }} {% endif %} {% if ldp.neighbor[neighbors].ttl_security is defined %} {% if 'disable' in ldp.neighbor[neighbors].ttl_security %} -neighbor {{neighbors}} ttl-security disable + neighbor {{ neighbors }} ttl-security disable {% else %} -neighbor {{neighbors}} ttl-security hops {{ldp.neighbor[neighbors].ttl_security}} + neighbor {{ neighbors }} ttl-security hops {{ ldp.neighbor[neighbors].ttl_security }} {% endif %} {% endif %} {% if ldp.neighbor[neighbors].session_holdtime is defined %} -neighbor {{neighbors}} session holdtime {{ldp.neighbor[neighbors].session_holdtime}} + neighbor {{ neighbors }} session holdtime {{ ldp.neighbor[neighbors].session_holdtime }} {% endif %} {% endfor %} {% endif %} -! + ! {% if ldp.discovery is defined %} {% if ldp.discovery.transport_ipv4_address is defined %} -address-family ipv4 + address-family ipv4 {% if ldp.allocation is defined %} {% if ldp.allocation.ipv4 is defined %} {% if ldp.allocation.ipv4.access_list is defined %} -label local allocate for {{ ldp.allocation.ipv4.access_list }} + label local allocate for {{ ldp.allocation.ipv4.access_list }} {% endif %} {% endif %} {% else %} -label local allocate host-routes + label local allocate host-routes {% endif %} {% if ldp.discovery.transport_ipv4_address is defined %} -discovery transport-address {{ ldp.discovery.transport_ipv4_address }} + discovery transport-address {{ ldp.discovery.transport_ipv4_address }} {% endif %} {% if ldp.discovery.hello_ipv4_holdtime is defined %} -discovery hello holdtime {{ ldp.discovery.hello_ipv4_holdtime }} + discovery hello holdtime {{ ldp.discovery.hello_ipv4_holdtime }} {% endif %} {% if ldp.discovery.hello_ipv4_interval is defined %} -discovery hello interval {{ ldp.discovery.hello_ipv4_interval }} + discovery hello interval {{ ldp.discovery.hello_ipv4_interval }} {% endif %} {% if ldp.discovery.session_ipv4_holdtime is defined %} -session holdtime {{ ldp.discovery.session_ipv4_holdtime }} + session holdtime {{ ldp.discovery.session_ipv4_holdtime }} {% endif %} {% if ldp.import is defined %} {% if ldp.import.ipv4 is defined %} {% if ldp.import.ipv4.import_filter is defined %} {% if ldp.import.ipv4.import_filter.filter_access_list is defined %} {% if ldp.import.ipv4.import_filter.neighbor_access_list is defined %} -label remote accept for {{ ldp.import.ipv4.import_filter.filter_access_list }} from {{ ldp.import.ipv4.import_filter.neighbor_access_list }} + label remote accept for {{ ldp.import.ipv4.import_filter.filter_access_list }} from {{ ldp.import.ipv4.import_filter.neighbor_access_list }} {% else %} -label remote accept for {{ ldp.import.ipv4.import_filter.filter_access_list }} + label remote accept for {{ ldp.import.ipv4.import_filter.filter_access_list }} {% endif %} {% endif %} {% endif %} @@ -73,14 +73,14 @@ label remote accept for {{ ldp.import.ipv4.import_filter.filter_access_list }} {% if ldp.export is defined %} {% if ldp.export.ipv4 is defined %} {% if ldp.export.ipv4.explicit_null is defined %} -label local advertise explicit-null + label local advertise explicit-null {% endif %} {% if ldp.export.ipv4.export_filter is defined %} {% if ldp.export.ipv4.export_filter.filter_access_list is defined %} {% if ldp.export.ipv4.export_filter.neighbor_access_list is defined %} -label local advertise for {{ ldp.export.ipv4.export_filter.filter_access_list }} to {{ ldp.export.ipv4.export_filter.neighbor_access_list }} + label local advertise for {{ ldp.export.ipv4.export_filter.filter_access_list }} to {{ ldp.export.ipv4.export_filter.neighbor_access_list }} {% else %} -label local advertise for {{ ldp.export.ipv4.export_filter.filter_access_list }} + label local advertise for {{ ldp.export.ipv4.export_filter.filter_access_list }} {% endif %} {% endif %} {% endif %} @@ -88,59 +88,59 @@ label local advertise for {{ ldp.export.ipv4.export_filter.filter_access_list }} {% endif %} {% if ldp.targeted_neighbor is defined %} {% if ldp.targeted_neighbor.ipv4.enable is defined %} -discovery targeted-hello accept + discovery targeted-hello accept {% endif %} {% if ldp.targeted_neighbor.ipv4.hello_holdtime is defined %} -discovery targeted-hello holdtime {{ ldp.targeted_neighbor.ipv4.hello_holdtime }} + discovery targeted-hello holdtime {{ ldp.targeted_neighbor.ipv4.hello_holdtime }} {% endif %} {% if ldp.targeted_neighbor.ipv4.hello_interval is defined %} -discovery targeted-hello interval {{ ldp.targeted_neighbor.ipv4.hello_interval }} + discovery targeted-hello interval {{ ldp.targeted_neighbor.ipv4.hello_interval }} {% endif %} {% for addresses in ldp.targeted_neighbor.ipv4.address %} -neighbor {{addresses}} targeted + neighbor {{addresses}} targeted {% endfor %} {% endif %} {% for interfaces in ldp.interface %} -interface {{interfaces}} + interface {{interfaces}} {% endfor %} -exit-address-family + exit-address-family {% else %} -no address-family ipv4 + no address-family ipv4 {% endif %} {% endif %} -! + ! {% if ldp.discovery is defined %} {% if ldp.discovery.transport_ipv6_address is defined %} -address-family ipv6 + address-family ipv6 {% if ldp.allocation is defined %} {% if ldp.allocation.ipv6 is defined %} {% if ldp.allocation.ipv6.access_list6 is defined %} -label local allocate for {{ ldp.allocation.ipv6.access_list6 }} + label local allocate for {{ ldp.allocation.ipv6.access_list6 }} {% endif %} {% endif %} {% else %} -label local allocate host-routes + label local allocate host-routes {% endif %} {% if ldp.discovery.transport_ipv6_address is defined %} -discovery transport-address {{ ldp.discovery.transport_ipv6_address }} + discovery transport-address {{ ldp.discovery.transport_ipv6_address }} {% endif %} {% if ldp.discovery.hello_ipv6_holdtime is defined %} -discovery hello holdtime {{ ldp.discovery.hello_ipv6_holdtime }} + discovery hello holdtime {{ ldp.discovery.hello_ipv6_holdtime }} {% endif %} {% if ldp.discovery.hello_ipv6_interval is defined %} -discovery hello interval {{ ldp.discovery.hello_ipv6_interval }} + discovery hello interval {{ ldp.discovery.hello_ipv6_interval }} {% endif %} {% if ldp.discovery.session_ipv6_holdtime is defined %} -session holdtime {{ ldp.discovery.session_ipv6_holdtime }} + session holdtime {{ ldp.discovery.session_ipv6_holdtime }} {% endif %} {% if ldp.import is defined %} {% if ldp.import.ipv6 is defined %} {% if ldp.import.ipv6.import_filter is defined %} {% if ldp.import.ipv6.import_filter.filter_access_list6 is defined %} {% if ldp.import.ipv6.import_filter.neighbor_access_list6 is defined %} -label remote accept for {{ ldp.import.ipv6.import_filter.filter_access_list6 }} from {{ ldp.import.ipv6.import_filter.neighbor_access_list6 }} + label remote accept for {{ ldp.import.ipv6.import_filter.filter_access_list6 }} from {{ ldp.import.ipv6.import_filter.neighbor_access_list6 }} {% else %} -label remote accept for {{ ldp.import.ipv6.import_filter.filter_access_list6 }} + label remote accept for {{ ldp.import.ipv6.import_filter.filter_access_list6 }} {% endif %} {% endif %} {% endif %} @@ -149,14 +149,14 @@ label remote accept for {{ ldp.import.ipv6.import_filter.filter_access_list6 }} {% if ldp.export is defined %} {% if ldp.export.ipv6 is defined %} {% if ldp.export.ipv6.explicit_null is defined %} -label local advertise explicit-null + label local advertise explicit-null {% endif %} {% if ldp.export.ipv6.export_filter is defined %} {% if ldp.export.ipv6.export_filter.filter_access_list6 is defined %} {% if ldp.export.ipv6.export_filter.neighbor_access_list6 is defined %} -label local advertise for {{ ldp.export.ipv6.export_filter.filter_access_list6 }} to {{ ldp.export.ipv6.export_filter.neighbor_access_list6 }} + label local advertise for {{ ldp.export.ipv6.export_filter.filter_access_list6 }} to {{ ldp.export.ipv6.export_filter.neighbor_access_list6 }} {% else %} -label local advertise for {{ ldp.export.ipv6.export_filter.filter_access_list6 }} + label local advertise for {{ ldp.export.ipv6.export_filter.filter_access_list6 }} {% endif %} {% endif %} {% endif %} @@ -164,24 +164,27 @@ label local advertise for {{ ldp.export.ipv6.export_filter.filter_access_list6 } {% endif %} {% if ldp.targeted_neighbor is defined %} {% if ldp.targeted_neighbor.ipv6.enable is defined %} -discovery targeted-hello accept + discovery targeted-hello accept {% endif %} {% if ldp.targeted_neighbor.ipv6.hello_holdtime is defined %} -discovery targeted-hello holdtime {{ ldp.targeted_neighbor.ipv6.hello_holdtime }} + discovery targeted-hello holdtime {{ ldp.targeted_neighbor.ipv6.hello_holdtime }} {% endif %} {% if ldp.targeted_neighbor.ipv6.hello_interval is defined %} -discovery targeted-hello interval {{ ldp.targeted_neighbor.ipv6.hello_interval }} + discovery targeted-hello interval {{ ldp.targeted_neighbor.ipv6.hello_interval }} {% endif %} {% for addresses in ldp.targeted_neighbor.ipv6.address %} -neighbor {{addresses}} targeted + neighbor {{addresses}} targeted {% endfor %} {% endif %} {% for interfaces in ldp.interface %} -interface {{interfaces}} + interface {{interfaces}} {% endfor %} -exit-address-family + exit-address-family {% else %} -no address-family ipv6 + no address-family ipv6 {% endif %} + ! {% endif %} +exit {% endif %} +! diff --git a/data/templates/frr/ospf6d.frr.tmpl b/data/templates/frr/ospf6d.frr.tmpl index 10a6d9b4b..c366326bf 100644 --- a/data/templates/frr/ospf6d.frr.tmpl +++ b/data/templates/frr/ospf6d.frr.tmpl @@ -25,6 +25,9 @@ interface {{ iface }} {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} {% endif %} {% if iface_config.bfd is defined %} ipv6 ospf6 bfd +{% if iface_config.bfd.profile is defined and iface_config.bfd.profile is not none %} + ipv6 ospf6 bfd profile {{ iface_config.bfd.profile }} +{% endif %} {% endif %} {% if iface_config.mtu_ignore is defined %} ipv6 ospf6 mtu-ignore diff --git a/data/templates/frr/ospfd.frr.tmpl b/data/templates/frr/ospfd.frr.tmpl index a7b770f07..af66baf53 100644 --- a/data/templates/frr/ospfd.frr.tmpl +++ b/data/templates/frr/ospfd.frr.tmpl @@ -42,6 +42,9 @@ interface {{ iface }} {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} {% endif %} {% if iface_config.bfd is defined %} ip ospf bfd +{% if iface_config.bfd.profile is defined and iface_config.bfd.profile is not none %} + ip ospf bfd profile {{ iface_config.bfd.profile }} +{% endif %} {% endif %} {% if iface_config.mtu_ignore is defined %} ip ospf mtu-ignore diff --git a/data/templates/https/nginx.default.tmpl b/data/templates/https/nginx.default.tmpl index 9d73baeee..ac9203e83 100644 --- a/data/templates/https/nginx.default.tmpl +++ b/data/templates/https/nginx.default.tmpl @@ -44,7 +44,11 @@ server { # proxy settings for HTTP API, if enabled; 503, if not location ~ /(retrieve|configure|config-file|image|generate|show|docs|openapi.json|redoc|graphql) { {% if server.api %} +{% if server.api.socket %} + proxy_pass http://unix:/run/api.sock; +{% else %} proxy_pass http://localhost:{{ server.api.port }}; +{% endif %} proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 600; diff --git a/data/templates/https/override.conf.tmpl b/data/templates/https/override.conf.tmpl new file mode 100644 index 000000000..824b1ba3b --- /dev/null +++ b/data/templates/https/override.conf.tmpl @@ -0,0 +1,15 @@ +{% set vrf_command = 'ip vrf exec ' + vrf + ' ' if vrf is defined else '' %} +[Unit] +StartLimitIntervalSec=0 +After=vyos-router.service + +[Service] +ExecStartPre= +ExecStartPre={{vrf_command}}/usr/sbin/nginx -t -q -g 'daemon on; master_process on;' +ExecStart= +ExecStart={{vrf_command}}/usr/sbin/nginx -g 'daemon on; master_process on;' +ExecReload= +ExecReload={{vrf_command}}/usr/sbin/nginx -g 'daemon on; master_process on;' -s reload +Restart=always +RestartPreventExitStatus= +RestartSec=10 diff --git a/data/templates/netflow/uacctd.conf.tmpl b/data/templates/netflow/uacctd.conf.tmpl index 1c183bb20..11fc76769 100644 --- a/data/templates/netflow/uacctd.conf.tmpl +++ b/data/templates/netflow/uacctd.conf.tmpl @@ -68,5 +68,8 @@ sfprobe_agentip[sf_{{ server['address'] }}]: {{ templatecfg['sflow']['agent-addr {% if templatecfg['sflow']['sampling-rate'] != none %} sampling_rate[sf_{{ server['address'] }}]: {{ templatecfg['sflow']['sampling-rate'] }} {% endif %} +{% if templatecfg['sflow']['source-address'] != none %} +sfprobe_source_ip[sf_{{ server['address'] }}]: {{ templatecfg['sflow']['source-address'] }} +{% endif %} {% endfor %} {% endif %} diff --git a/data/templates/vrrp/keepalived.conf.tmpl b/data/templates/vrrp/keepalived.conf.tmpl index b4824a994..b93aa4bc9 100644 --- a/data/templates/vrrp/keepalived.conf.tmpl +++ b/data/templates/vrrp/keepalived.conf.tmpl @@ -5,9 +5,6 @@ global_defs { dynamic_interfaces script_user root - # 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 } diff --git a/interface-definitions/dns-forwarding.xml.in b/interface-definitions/dns-forwarding.xml.in index 5b0c87597..4faf604ad 100644 --- a/interface-definitions/dns-forwarding.xml.in +++ b/interface-definitions/dns-forwarding.xml.in @@ -105,6 +105,456 @@ </leafNode> </children> </tagNode> + <tagNode name="authoritative-domain"> + <properties> + <help>Domain to host authoritative records for</help> + <valueHelp> + <format>text</format> + <description>An absolute DNS name</description> + </valueHelp> + <constraint> + <regex>^[-_a-zA-Z0-9.]{1,63}$</regex> + </constraint> + </properties> + <children> + <node name="records"> + <properties> + <help>DNS zone records</help> + </properties> + <children> + <tagNode name="a"> + <properties> + <help>"A" record</help> + <valueHelp> + <format>text</format> + <description>A DNS name relative to the root record</description> + </valueHelp> + <valueHelp> + <format>@</format> + <description>Root record</description> + </valueHelp> + <constraint> + <regex>^([-_a-zA-Z0-9.]{1,63}|@)(?<!\.)$</regex> + </constraint> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>IPv4 address [REQUIRED]</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address</description> + </valueHelp> + <multi/> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + #include <include/dns/time-to-live.xml.i> + #include <include/generic-disable-node.xml.i> + </children> + </tagNode> + <tagNode name="aaaa"> + <properties> + <help>"AAAA" record</help> + <valueHelp> + <format>text</format> + <description>A DNS name relative to the root record</description> + </valueHelp> + <valueHelp> + <format>@</format> + <description>Root record</description> + </valueHelp> + <constraint> + <regex>^([-_a-zA-Z0-9.]{1,63}|@)(?<!\.)$</regex> + </constraint> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>IPv6 address [REQUIRED]</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address</description> + </valueHelp> + <multi/> + <constraint> + <validator name="ipv6-address"/> + </constraint> + </properties> + </leafNode> + #include <include/dns/time-to-live.xml.i> + #include <include/generic-disable-node.xml.i> + </children> + </tagNode> + <tagNode name="cname"> + <properties> + <help>"CNAME" record</help> + <valueHelp> + <format>text</format> + <description>A DNS name relative to the root record</description> + </valueHelp> + <valueHelp> + <format>@</format> + <description>Root record</description> + </valueHelp> + <constraint> + <regex>^([-_a-zA-Z0-9.]{1,63}|@)(?<!\.)$</regex> + </constraint> + </properties> + <children> + <leafNode name="target"> + <properties> + <help>Target DNS name [REQUIRED]</help> + <valueHelp> + <format>name.example.com</format> + <description>An absolute DNS name</description> + </valueHelp> + <constraint> + <regex>^[-_a-zA-Z0-9.]{1,63}(?<!\.)$</regex> + </constraint> + </properties> + </leafNode> + #include <include/dns/time-to-live.xml.i> + #include <include/generic-disable-node.xml.i> + </children> + </tagNode> + <tagNode name="mx"> + <properties> + <help>"MX" record</help> + <valueHelp> + <format>text</format> + <description>A DNS name relative to the root record</description> + </valueHelp> + <valueHelp> + <format>@</format> + <description>Root record</description> + </valueHelp> + <constraint> + <regex>^([-_a-zA-Z0-9.]{1,63}|@)(?<!\.)$</regex> + </constraint> + </properties> + <children> + <tagNode name="server"> + <properties> + <help>Mail server [REQUIRED]</help> + <valueHelp> + <format>name.example.com</format> + <description>An absolute DNS name</description> + </valueHelp> + <constraint> + <regex>^[-_a-zA-Z0-9.]{1,63}(?<!\.)$</regex> + </constraint> + </properties> + <children> + <leafNode name="priority"> + <properties> + <help>Server priority</help> + <valueHelp> + <format>u32:1-999</format> + <description>Server priority (lower numbers are higher priority)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999"/> + </constraint> + </properties> + <defaultValue>10</defaultValue> + </leafNode> + </children> + </tagNode> + #include <include/dns/time-to-live.xml.i> + #include <include/generic-disable-node.xml.i> + </children> + </tagNode> + <tagNode name="ptr"> + <properties> + <help>"PTR" record</help> + <valueHelp> + <format>text</format> + <description>A DNS name relative to the root record</description> + </valueHelp> + <valueHelp> + <format>@</format> + <description>Root record</description> + </valueHelp> + <constraint> + <regex>^([-_a-zA-Z0-9.]{1,63}|@)(?<!\.)$</regex> + </constraint> + </properties> + <children> + <leafNode name="target"> + <properties> + <help>Target DNS name [REQUIRED]</help> + <valueHelp> + <format>name.example.com</format> + <description>An absolute DNS name</description> + </valueHelp> + <constraint> + <regex>^[-_a-zA-Z0-9.]{1,63}(?<!\.)$</regex> + </constraint> + </properties> + </leafNode> + #include <include/dns/time-to-live.xml.i> + #include <include/generic-disable-node.xml.i> + </children> + </tagNode> + <tagNode name="txt"> + <properties> + <help>"TXT" record</help> + <valueHelp> + <format>text</format> + <description>A DNS name relative to the root record</description> + </valueHelp> + <valueHelp> + <format>@</format> + <description>Root record</description> + </valueHelp> + <constraint> + <regex>^([-_a-zA-Z0-9.]{1,63}|@)(?<!\.)$</regex> + </constraint> + </properties> + <children> + <leafNode name="value"> + <properties> + <help>Record contents [REQUIRED]</help> + <valueHelp> + <format>text</format> + <description>Record contents</description> + </valueHelp> + <multi/> + </properties> + </leafNode> + #include <include/dns/time-to-live.xml.i> + #include <include/generic-disable-node.xml.i> + </children> + </tagNode> + <tagNode name="spf"> + <properties> + <help>"SPF" record (type=SPF)</help> + <valueHelp> + <format>text</format> + <description>A DNS name relative to the root record</description> + </valueHelp> + <valueHelp> + <format>@</format> + <description>Root record</description> + </valueHelp> + <constraint> + <regex>^([-_a-zA-Z0-9.]{1,63}|@)(?<!\.)$</regex> + </constraint> + </properties> + <children> + <leafNode name="value"> + <properties> + <help>Record contents [REQUIRED]</help> + <valueHelp> + <format>text</format> + <description>Record contents</description> + </valueHelp> + </properties> + </leafNode> + #include <include/dns/time-to-live.xml.i> + #include <include/generic-disable-node.xml.i> + </children> + </tagNode> + <tagNode name="srv"> + <properties> + <help>"SRV" record</help> + <valueHelp> + <format>text</format> + <description>A DNS name relative to the root record</description> + </valueHelp> + <valueHelp> + <format>@</format> + <description>Root record</description> + </valueHelp> + <constraint> + <regex>^([-_a-zA-Z0-9.]{1,63}|@)(?<!\.)$</regex> + </constraint> + </properties> + <children> + <tagNode name="entry"> + <properties> + <help>Service entry [REQUIRED]</help> + <valueHelp> + <format>u32:0-65535</format> + <description>Entry number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + </properties> + <children> + <leafNode name="hostname"> + <properties> + <help>Server hostname [REQUIRED]</help> + <valueHelp> + <format>name.example.com</format> + <description>An absolute DNS name</description> + </valueHelp> + <constraint> + <regex>^[-_a-zA-Z0-9.]{1,63}(?<!\.)$</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="port"> + <properties> + <help>Port number [REQUIRED]</help> + <valueHelp> + <format>u32:0-65535</format> + <description>TCP/UDP port number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65536"/> + </constraint> + </properties> + </leafNode> + <leafNode name="priority"> + <properties> + <help>Entry priority</help> + <valueHelp> + <format>u32:0-65535</format> + <description>Entry priority (lower numbers are higher priority)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + </properties> + <defaultValue>10</defaultValue> + </leafNode> + <leafNode name="weight"> + <properties> + <help>Entry weight</help> + <valueHelp> + <format>u32:0-65535</format> + <description>Entry weight</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + </properties> + <defaultValue>0</defaultValue> + </leafNode> + </children> + </tagNode> + #include <include/dns/time-to-live.xml.i> + #include <include/generic-disable-node.xml.i> + </children> + </tagNode> + <tagNode name="naptr"> + <properties> + <help>"NAPTR" record</help> + <valueHelp> + <format>text</format> + <description>A DNS name relative to the root record</description> + </valueHelp> + <valueHelp> + <format>@</format> + <description>Root record</description> + </valueHelp> + <constraint> + <regex>^([-_a-zA-Z0-9.]{1,63}|@)(?<!\.)$</regex> + </constraint> + </properties> + <children> + <tagNode name="rule"> + <properties> + <help>NAPTR rule [REQUIRED]</help> + <valueHelp> + <format>u32:0-65535</format> + <description>Rule number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + </properties> + <children> + <leafNode name="order"> + <properties> + <help>Rule order</help> + <valueHelp> + <format>u32:0-65535</format> + <description>Rule order (lower order is evaluated first)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="preference"> + <properties> + <help>Rule preference</help> + <valueHelp> + <format>u32:0-65535</format> + <description>Rule preference</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + </properties> + <defaultValue>0</defaultValue> + </leafNode> + <leafNode name="lookup-srv"> + <properties> + <help>"S" flag</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="lookup-a"> + <properties> + <help>"A" flag</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="resolve-uri"> + <properties> + <help>"U" flag</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="protocol-specific"> + <properties> + <help>"P" flag</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="service"> + <properties> + <help>Service type</help> + <constraint> + <regex>^[a-zA-Z][a-zA-Z0-9]{0,31}(\+[a-zA-Z][a-zA-Z0-9]{0,31})?$</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="regexp"> + <properties> + <help>Regular expression</help> + </properties> + </leafNode> + <leafNode name="replacement"> + <properties> + <help>Replacement DNS name</help> + <valueHelp> + <format>name.example.com</format> + <description>An absolute DNS name</description> + </valueHelp> + <constraint> + <regex>^[-_a-zA-Z0-9.]{1,63}(?<!\.)$</regex> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + #include <include/dns/time-to-live.xml.i> + #include <include/generic-disable-node.xml.i> + </children> + </tagNode> + </children> + </node> + #include <include/generic-disable-node.xml.i> + </children> + </tagNode> <leafNode name="ignore-hosts-file"> <properties> <help>Do not use local /etc/hosts file in name resolution</help> @@ -114,7 +564,7 @@ <leafNode name="no-serve-rfc1918"> <properties> <help>Makes the server authoritatively not aware of RFC1918 addresses</help> - <valueless/> + <valueless/> </properties> </leafNode> <leafNode name="allow-from"> diff --git a/interface-definitions/flow-accounting-conf.xml.in b/interface-definitions/flow-accounting-conf.xml.in index 113c1d849..b98794792 100644 --- a/interface-definitions/flow-accounting-conf.xml.in +++ b/interface-definitions/flow-accounting-conf.xml.in @@ -420,6 +420,7 @@ </leafNode> </children> </tagNode> + #include <include/source-address-ipv4-ipv6.xml.i> </children> </node> </children> diff --git a/interface-definitions/https.xml.in b/interface-definitions/https.xml.in index f60df7c34..6fea2f1f6 100644 --- a/interface-definitions/https.xml.in +++ b/interface-definitions/https.xml.in @@ -101,6 +101,25 @@ <hidden/> </properties> </leafNode> + <leafNode name="socket"> + <properties> + <help>Run server on Unix domain socket</help> + <valueless/> + </properties> + </leafNode> + <node name="cors"> + <properties> + <help>Set CORS options</help> + </properties> + <children> + <leafNode name="allow-origin"> + <properties> + <help>Allow resource request from origin</help> + <multi/> + </properties> + </leafNode> + </children> + </node> </children> </node> <node name="api-restrict"> @@ -143,6 +162,7 @@ </node> </children> </node> + #include <include/interface/vrf.xml.i> </children> </node> </children> diff --git a/interface-definitions/include/bfd.xml.i b/interface-definitions/include/bfd.xml.i deleted file mode 100644 index 2bc3664e1..000000000 --- a/interface-definitions/include/bfd.xml.i +++ /dev/null @@ -1,8 +0,0 @@ -<!-- include start from bfd.xml.i --> -<leafNode name="bfd"> - <properties> - <help>Enable Bidirectional Forwarding Detection (BFD)</help> - <valueless/> - </properties> -</leafNode> -<!-- include end --> diff --git a/interface-definitions/include/bfd/bfd.xml.i b/interface-definitions/include/bfd/bfd.xml.i new file mode 100644 index 000000000..022956d98 --- /dev/null +++ b/interface-definitions/include/bfd/bfd.xml.i @@ -0,0 +1,10 @@ +<!-- include start from bfd/bfd.xml.i --> +<node name="bfd"> + <properties> + <help>Enable Bidirectional Forwarding Detection (BFD)</help> + </properties> + <children> + #include <include/bfd/profile.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bfd-common.xml.i b/interface-definitions/include/bfd/common.xml.i index 1d6ab5d55..e52221441 100644 --- a/interface-definitions/include/bfd-common.xml.i +++ b/interface-definitions/include/bfd/common.xml.i @@ -1,4 +1,4 @@ -<!-- include start from bfd-common.xml.i --> +<!-- include start from bfd/common.xml.i --> <leafNode name="echo-mode"> <properties> <help>Enables the echo transmission mode</help> @@ -15,7 +15,7 @@ <help>Minimum interval of receiving control packets</help> <valueHelp> <format>u32:10-60000</format> - <description>Interval in milliseconds</description> + <description>Interval in milliseconds (default: 300)</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 10-60000"/> @@ -28,7 +28,7 @@ <help>Minimum interval of transmitting control packets</help> <valueHelp> <format>u32:10-60000</format> - <description>Interval in milliseconds</description> + <description>Interval in milliseconds (default: 300)</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 10-60000"/> @@ -41,7 +41,7 @@ <help>Multiplier to determine packet loss</help> <valueHelp> <format>u32:2-255</format> - <description>Remote transmission interval will be multiplied by this value</description> + <description>Remote transmission interval will be multiplied by this value (default: 3)</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 2-255"/> @@ -63,6 +63,12 @@ </leafNode> </children> </node> +<leafNode name="passive"> + <properties> + <help>Do not attempt to start sessions</help> + <valueless/> + </properties> +</leafNode> <leafNode name="shutdown"> <properties> <help>Disable this peer</help> diff --git a/interface-definitions/include/bfd/profile.xml.i b/interface-definitions/include/bfd/profile.xml.i new file mode 100644 index 000000000..5ff057286 --- /dev/null +++ b/interface-definitions/include/bfd/profile.xml.i @@ -0,0 +1,14 @@ +<!-- include start from bfd/profile.xml.i --> +<leafNode name="profile"> + <properties> + <help>Use settings from BFD profile</help> + <completionHelp> + <path>protocols bfd profile</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>BFD profile name</description> + </valueHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/afi-l2vpn-common.xml.i b/interface-definitions/include/bgp/afi-l2vpn-common.xml.i index 8deb189ab..d586635c8 100644 --- a/interface-definitions/include/bgp/afi-l2vpn-common.xml.i +++ b/interface-definitions/include/bgp/afi-l2vpn-common.xml.i @@ -25,7 +25,7 @@ <description>Route target (A.B.C.D:MN|EF:OPQR|GHJK:MN)</description> </valueHelp> <constraint> - <validator name="bgp-route-target" argument="--single"/> + <validator name="bgp-rd-rt" argument="--route-target"/> </constraint> </properties> </leafNode> @@ -37,7 +37,7 @@ <description>Route target (A.B.C.D:MN|EF:OPQR|GHJK:MN)</description> </valueHelp> <constraint> - <validator name="bgp-route-target" argument="--single"/> + <validator name="bgp-rd-rt" argument="--route-target"/> </constraint> </properties> </leafNode> @@ -49,7 +49,7 @@ <description>Route target (A.B.C.D:MN|EF:OPQR|GHJK:MN)</description> </valueHelp> <constraint> - <validator name="bgp-route-target" argument="--single"/> + <validator name="bgp-rd-rt" argument="--route-target"/> </constraint> </properties> </leafNode> diff --git a/interface-definitions/include/bgp/afi-route-target-vpn.xml.i b/interface-definitions/include/bgp/afi-route-target-vpn.xml.i index 0cd0fdd76..5784f9eac 100644 --- a/interface-definitions/include/bgp/afi-route-target-vpn.xml.i +++ b/interface-definitions/include/bgp/afi-route-target-vpn.xml.i @@ -17,7 +17,7 @@ <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"/> + <validator name="bgp-rd-rt" argument="--route-target-multi"/> </constraint> </properties> </leafNode> @@ -29,7 +29,7 @@ <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"/> + <validator name="bgp-rd-rt" argument="--route-target-multi"/> </constraint> </properties> </leafNode> @@ -41,7 +41,7 @@ <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"/> + <validator name="bgp-rd-rt" argument="--route-target-multi"/> </constraint> </properties> </leafNode> diff --git a/interface-definitions/include/bgp/afi-common.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i index 62beff40c..f3fc4444c 100644 --- a/interface-definitions/include/bgp/afi-common.xml.i +++ b/interface-definitions/include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i @@ -1,4 +1,4 @@ -<!-- include start from bgp/afi-common.xml.i --> +<!-- include start from bgp/neighbor-afi-ipv4-ipv6-common.xml.i --> <leafNode name="addpath-tx-all"> <properties> <help>Use addpath to advertise all paths to a neighbor</help> @@ -11,6 +11,61 @@ <valueless/> </properties> </leafNode> +<node name="conditionally-advertise"> + <properties> + <help>Use route-map to conditionally advertise routes</help> + </properties> + <children> + <leafNode name="advertise-map"> + <properties> + <help>Route-map to conditionally advertise routes</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="exist-map"> + <properties> + <help>Advertise routes only if prefixes in exist-map are installed in BGP table</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="non-exist-map"> + <properties> + <help>Advertise routes only if prefixes in non-exist-map are not installed in BGP table</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> + </children> +</node> #include <include/bgp/afi-allowas-in.xml.i> <leafNode name="as-override"> <properties> diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv4-labeled-unicast.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv4-labeled-unicast.xml.i index 45a440fd8..0eae29f5e 100644 --- a/interface-definitions/include/bgp/neighbor-afi-ipv4-labeled-unicast.xml.i +++ b/interface-definitions/include/bgp/neighbor-afi-ipv4-labeled-unicast.xml.i @@ -13,7 +13,7 @@ </children> </node> #include <include/bgp/afi-ipv4-prefix-list.xml.i> - #include <include/bgp/afi-common.xml.i> + #include <include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i> #include <include/bgp/afi-default-originate.xml.i> </children> </node> diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv4-multicast.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv4-multicast.xml.i index 6526169ca..4bb6df7c3 100644 --- a/interface-definitions/include/bgp/neighbor-afi-ipv4-multicast.xml.i +++ b/interface-definitions/include/bgp/neighbor-afi-ipv4-multicast.xml.i @@ -13,7 +13,7 @@ </children> </node> #include <include/bgp/afi-ipv4-prefix-list.xml.i> - #include <include/bgp/afi-common.xml.i> + #include <include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i> #include <include/bgp/afi-default-originate.xml.i> </children> </node> diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv4-unicast.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv4-unicast.xml.i index b7b7ca5b5..0094ce874 100644 --- a/interface-definitions/include/bgp/neighbor-afi-ipv4-unicast.xml.i +++ b/interface-definitions/include/bgp/neighbor-afi-ipv4-unicast.xml.i @@ -13,7 +13,7 @@ </children> </node> #include <include/bgp/afi-ipv4-prefix-list.xml.i> - #include <include/bgp/afi-common.xml.i> + #include <include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i> #include <include/bgp/afi-default-originate.xml.i> </children> </node> diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv4-vpn.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv4-vpn.xml.i index 838327bc9..220f22fe3 100644 --- a/interface-definitions/include/bgp/neighbor-afi-ipv4-vpn.xml.i +++ b/interface-definitions/include/bgp/neighbor-afi-ipv4-vpn.xml.i @@ -5,7 +5,7 @@ </properties> <children> #include <include/bgp/afi-ipv4-prefix-list.xml.i> - #include <include/bgp/afi-common.xml.i> + #include <include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i> </children> </node> <!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv6-labeled-unicast.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv6-labeled-unicast.xml.i index f680b7357..995183571 100644 --- a/interface-definitions/include/bgp/neighbor-afi-ipv6-labeled-unicast.xml.i +++ b/interface-definitions/include/bgp/neighbor-afi-ipv6-labeled-unicast.xml.i @@ -14,7 +14,7 @@ </node> #include <include/bgp/afi-ipv6-nexthop-local.xml.i> #include <include/bgp/afi-ipv6-prefix-list.xml.i> - #include <include/bgp/afi-common.xml.i> + #include <include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i> #include <include/bgp/afi-default-originate.xml.i> </children> </node> diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv6-multicast.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv6-multicast.xml.i index 1f8db8361..bb713c313 100644 --- a/interface-definitions/include/bgp/neighbor-afi-ipv6-multicast.xml.i +++ b/interface-definitions/include/bgp/neighbor-afi-ipv6-multicast.xml.i @@ -6,7 +6,7 @@ <children> #include <include/bgp/afi-ipv6-nexthop-local.xml.i> #include <include/bgp/afi-ipv6-prefix-list.xml.i> - #include <include/bgp/afi-common.xml.i> + #include <include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i> #include <include/bgp/afi-default-originate.xml.i> </children> </node> diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv6-unicast.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv6-unicast.xml.i index f6b812c28..26a5e7090 100644 --- a/interface-definitions/include/bgp/neighbor-afi-ipv6-unicast.xml.i +++ b/interface-definitions/include/bgp/neighbor-afi-ipv6-unicast.xml.i @@ -14,7 +14,7 @@ </node> #include <include/bgp/afi-ipv6-nexthop-local.xml.i> #include <include/bgp/afi-ipv6-prefix-list.xml.i> - #include <include/bgp/afi-common.xml.i> + #include <include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i> #include <include/bgp/afi-default-originate.xml.i> </children> </node> diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv6-vpn.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv6-vpn.xml.i index c0df71cf3..5c6811986 100644 --- a/interface-definitions/include/bgp/neighbor-afi-ipv6-vpn.xml.i +++ b/interface-definitions/include/bgp/neighbor-afi-ipv6-vpn.xml.i @@ -6,7 +6,7 @@ <children> #include <include/bgp/afi-ipv6-nexthop-local.xml.i> #include <include/bgp/afi-ipv6-prefix-list.xml.i> - #include <include/bgp/afi-common.xml.i> + #include <include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i> </children> </node> <!-- include end --> diff --git a/interface-definitions/include/bgp/neighbor-bfd.xml.i b/interface-definitions/include/bgp/neighbor-bfd.xml.i index d486bdd8a..fac2a1166 100644 --- a/interface-definitions/include/bgp/neighbor-bfd.xml.i +++ b/interface-definitions/include/bgp/neighbor-bfd.xml.i @@ -4,6 +4,7 @@ <help>Enable Bidirectional Forwarding Detection (BFD) support</help> </properties> <children> + #include <include/bfd/profile.xml.i> <leafNode name="check-control-plane-failure"> <properties> <help>Allow to write CBIT independence in BFD outgoing packets and read both C-BIT value of BFD and lookup BGP peer status</help> diff --git a/interface-definitions/include/bgp/neighbor-shutdown.xml.i b/interface-definitions/include/bgp/neighbor-shutdown.xml.i index 6d15899a6..acc7bc5a9 100644 --- a/interface-definitions/include/bgp/neighbor-shutdown.xml.i +++ b/interface-definitions/include/bgp/neighbor-shutdown.xml.i @@ -1,7 +1,7 @@ <!-- include start from bgp/neighbor-shutdown.xml.i --> <leafNode name="shutdown"> <properties> - <help>Administratively shut down this neighbor</help> + <help>Administratively shutdown this neighbor</help> <valueless/> </properties> </leafNode> diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i index 2dfae517e..8214d0779 100644 --- a/interface-definitions/include/bgp/protocol-common-config.xml.i +++ b/interface-definitions/include/bgp/protocol-common-config.xml.i @@ -1181,6 +1181,26 @@ </leafNode> </children> </node> + <node name="conditional-advertisement"> + <properties> + <help>Conditional advertisement settings</help> + </properties> + <children> + <leafNode name="timer"> + <properties> + <help>Set period to rescan BGP table to check if condition is met</help> + <valueHelp> + <format>u32:5-240</format> + <description>Period to rerun the conditional advertisement scanner process (default: 60)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 5-240"/> + </constraint> + </properties> + <defaultValue>60</defaultValue> + </leafNode> + </children> + </node> <node name="dampening"> <properties> <help>Enable route-flap dampening</help> @@ -1343,6 +1363,12 @@ <valueless/> </properties> </leafNode> + <leafNode name="fast-convergence"> + <properties> + <help>Teardown sessions immediately whenever peer becomes unreachable</help> + <valueless/> + </properties> + </leafNode> <node name="graceful-restart"> <properties> <help>Graceful restart capability parameters</help> @@ -1374,6 +1400,18 @@ <valueless/> </properties> </leafNode> + <leafNode name="minimum-holdtime"> + <properties> + <help>BGP minimum holdtime</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Minimum holdtime in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> <leafNode name="network-import-check"> <properties> <help>Enable IGP route check for network statements</help> @@ -1392,6 +1430,24 @@ <valueless/> </properties> </leafNode> + <leafNode name="reject-as-sets"> + <properties> + <help>Reject routes with AS_SET or AS_CONFED_SET flag</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="shutdown"> + <properties> + <help>Administrative shutdown of the BGP instance</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="suppress-fib-pending"> + <properties> + <help>Advertise only routes that are programmed in kernel to peers</help> + <valueless/> + </properties> + </leafNode> #include <include/router-id.xml.i> </children> </node> @@ -1441,4 +1497,4 @@ #include <include/bgp/timers-keepalive.xml.i> </children> </node> -<!-- include end -->
\ No newline at end of file +<!-- include end --> diff --git a/interface-definitions/include/bgp/route-distinguisher.xml.i b/interface-definitions/include/bgp/route-distinguisher.xml.i index 6d0aa3ef1..8bc5b452e 100644 --- a/interface-definitions/include/bgp/route-distinguisher.xml.i +++ b/interface-definitions/include/bgp/route-distinguisher.xml.i @@ -7,7 +7,7 @@ <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> + <validator name="bgp-rd-rt" argument="--route-distinguisher"/> </constraint> </properties> </leafNode> diff --git a/interface-definitions/include/dhcp/ntp-server.xml.i b/interface-definitions/include/dhcp/ntp-server.xml.i index 32d8207e5..4d7235aa1 100644 --- a/interface-definitions/include/dhcp/ntp-server.xml.i +++ b/interface-definitions/include/dhcp/ntp-server.xml.i @@ -1,15 +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> +<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/dns/time-to-live.xml.i b/interface-definitions/include/dns/time-to-live.xml.i new file mode 100644 index 000000000..5c1a1472d --- /dev/null +++ b/interface-definitions/include/dns/time-to-live.xml.i @@ -0,0 +1,15 @@ +<!-- include start from dns/time-to-live.xml.i --> +<leafNode name="ttl"> + <properties> + <help>Time-to-live (TTL)</help> + <valueHelp> + <format>u32:0-2147483647</format> + <description>TTL in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-2147483647"/> + </constraint> + </properties> + <defaultValue>300</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/netns.xml.i b/interface-definitions/include/interface/netns.xml.i new file mode 100644 index 000000000..39f9118fa --- /dev/null +++ b/interface-definitions/include/interface/netns.xml.i @@ -0,0 +1,14 @@ +<!-- include start from interface/netns.xml.i --> +<leafNode name="netns"> + <properties> + <help>Network namespace name</help> + <valueHelp> + <format>text</format> + <description>Network namespace name</description> + </valueHelp> + <completionHelp> + <path>netns name</path> + </completionHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/vrf.xml.i b/interface-definitions/include/interface/vrf.xml.i index 5ad978a27..8605f56e8 100644 --- a/interface-definitions/include/interface/vrf.xml.i +++ b/interface-definitions/include/interface/vrf.xml.i @@ -3,7 +3,7 @@ <properties> <help>VRF instance name</help> <valueHelp> - <format>text</format> + <format>txt</format> <description>VRF instance name</description> </valueHelp> <completionHelp> diff --git a/interface-definitions/include/isis/protocol-common-config.xml.i b/interface-definitions/include/isis/protocol-common-config.xml.i index 84e2f7bb2..8ffa14a19 100644 --- a/interface-definitions/include/isis/protocol-common-config.xml.i +++ b/interface-definitions/include/isis/protocol-common-config.xml.i @@ -648,7 +648,7 @@ </completionHelp> </properties> <children> - #include <include/bfd.xml.i> + #include <include/bfd/bfd.xml.i> <leafNode name="circuit-type"> <properties> <help>Configure circuit type for interface</help> diff --git a/interface-definitions/include/ospf/interface-common.xml.i b/interface-definitions/include/ospf/interface-common.xml.i index 4b0aef380..738651594 100644 --- a/interface-definitions/include/ospf/interface-common.xml.i +++ b/interface-definitions/include/ospf/interface-common.xml.i @@ -1,5 +1,5 @@ <!-- include start from ospf/interface-common.xml.i --> -#include <include/bfd.xml.i> +#include <include/bfd/bfd.xml.i> <leafNode name="cost"> <properties> <help>Interface cost</help> diff --git a/interface-definitions/interfaces-dummy.xml.in b/interface-definitions/interfaces-dummy.xml.in index 2bc88c1a7..4d4c44160 100644 --- a/interface-definitions/interfaces-dummy.xml.in +++ b/interface-definitions/interfaces-dummy.xml.in @@ -27,6 +27,7 @@ #include <include/interface/source-validation.xml.i> </children> </node> + #include <include/interface/netns.xml.i> #include <include/interface/vrf.xml.i> </children> </tagNode> diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in index 0a8a88596..caeb58116 100644 --- a/interface-definitions/interfaces-vxlan.xml.in +++ b/interface-definitions/interfaces-vxlan.xml.in @@ -19,6 +19,12 @@ #include <include/interface/address-ipv4-ipv6.xml.i> #include <include/interface/description.xml.i> #include <include/interface/disable.xml.i> + <leafNode name="external"> + <properties> + <help>Use external control plane</help> + <valueless/> + </properties> + </leafNode> <leafNode name="group"> <properties> <help>Multicast group address for VXLAN interface</help> diff --git a/interface-definitions/netns.xml.in b/interface-definitions/netns.xml.in new file mode 100644 index 000000000..80de805fb --- /dev/null +++ b/interface-definitions/netns.xml.in @@ -0,0 +1,23 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="netns" owner="${vyos_conf_scripts_dir}/netns.py"> + <properties> + <help>Network namespace</help> + <priority>299</priority> + </properties> + <children> + <tagNode name="name"> + <properties> + <help>Network namespace name</help> + <constraint> + <regex>^[a-zA-Z0-9-_]{1,100}</regex> + </constraint> + <constraintErrorMessage>Netns name must be alphanumeric and can contain hyphens and underscores.</constraintErrorMessage> + </properties> + <children> + #include <include/interface/description.xml.i> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/protocols-bfd.xml.in b/interface-definitions/protocols-bfd.xml.in index 7b22b8125..a9957d884 100644 --- a/interface-definitions/protocols-bfd.xml.in +++ b/interface-definitions/protocols-bfd.xml.in @@ -26,18 +26,7 @@ </constraint> </properties> <children> - <leafNode name="profile"> - <properties> - <help>Use settings from BFD profile</help> - <completionHelp> - <path>protocols bfd profile</path> - </completionHelp> - <valueHelp> - <format>txt</format> - <description>BFD profile name</description> - </valueHelp> - </properties> - </leafNode> + #include <include/bfd/profile.xml.i> <node name="source"> <properties> <help>Bind listener to specified interface/address, mandatory for IPv6</help> @@ -66,13 +55,14 @@ </leafNode> </children> </node> - #include <include/bfd-common.xml.i> + #include <include/bfd/common.xml.i> <leafNode name="multihop"> <properties> <help>Allow this BFD peer to not be directly connected</help> <valueless/> </properties> </leafNode> + #include <include/interface/vrf.xml.i> </children> </tagNode> <tagNode name="profile"> @@ -87,7 +77,7 @@ </constraint> </properties> <children> - #include <include/bfd-common.xml.i> + #include <include/bfd/common.xml.i> </children> </tagNode> </children> diff --git a/interface-definitions/service_pppoe-server.xml.in b/interface-definitions/service_pppoe-server.xml.in index 188aed6c4..97952d882 100644 --- a/interface-definitions/service_pppoe-server.xml.in +++ b/interface-definitions/service_pppoe-server.xml.in @@ -70,19 +70,27 @@ <children> <leafNode name="vlan-id"> <properties> - <help>VLAN monitor for the automatic creation of vlans (user per vlan)</help> + <help>VLAN monitor for the automatic creation of single vlan</help> + <valueHelp> + <format>u32:1-4094</format> + <description>VLAN monitor for the automatic creation of single vlan</description> + </valueHelp> <constraint> - <validator name="numeric" argument="--range 1-4096"/> + <validator name="numeric" argument="--range 1-4094"/> </constraint> - <constraintErrorMessage>VLAN ID needs to be between 1 and 4096</constraintErrorMessage> + <constraintErrorMessage>VLAN ID needs to be between 1 and 4094</constraintErrorMessage> <multi/> </properties> </leafNode> <leafNode name="vlan-range"> <properties> - <help>VLAN monitor for the automatic creation of vlans (user per vlan)</help> + <help>VLAN monitor for the automatic creation of vlans range</help> + <valueHelp> + <format>start-end</format> + <description>VLAN monitor range for the automatic creation of vlans (e.g. 1-4094)</description> + </valueHelp> <constraint> - <regex>(409[0-6]|40[0-8][0-9]|[1-3][0-9]{3}|[1-9][0-9]{0,2})-(409[0-6]|40[0-8][0-9]|[1-3][0-9]{3}|[1-9][0-9]{0,2})</regex> + <validator name="range" argument="--min=1 --max=4094"/> </constraint> <multi/> </properties> diff --git a/interface-definitions/service_webproxy.xml.in b/interface-definitions/service_webproxy.xml.in index d61a95690..03f504ac7 100644 --- a/interface-definitions/service_webproxy.xml.in +++ b/interface-definitions/service_webproxy.xml.in @@ -16,7 +16,7 @@ <description>Domain to use for urls that do not contain a '.'</description> </valueHelp> <constraint> - <regex>^[\.][a-z0-9-][$]?</regex> + <regex>[.][A-Za-z0-9][-.A-Za-z0-9]*</regex> </constraint> <constraintErrorMessage>Must start append-domain with a '.'</constraintErrorMessage> </properties> diff --git a/op-mode-definitions/disks.xml.in b/op-mode-definitions/disks.xml.in index 2102a2e8e..117ac5065 100644 --- a/op-mode-definitions/disks.xml.in +++ b/op-mode-definitions/disks.xml.in @@ -20,7 +20,7 @@ <script>${vyos_completion_dir}/list_disks.py --exclude ${COMP_WORDS[2]}</script> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/format_disk.py --target $3 --proto $5</command> + <command>sudo ${vyos_op_scripts_dir}/format_disk.py --target $3 --proto $5</command> </tagNode> </children> </tagNode> diff --git a/op-mode-definitions/include/bgp/afi-common.xml.i b/op-mode-definitions/include/bgp/afi-common.xml.i index 4d5f56656..acf20d950 100644 --- a/op-mode-definitions/include/bgp/afi-common.xml.i +++ b/op-mode-definitions/include/bgp/afi-common.xml.i @@ -61,5 +61,4 @@ </leafNode> </children> </node> -#include <include/vtysh-generic-wide.xml.i> <!-- included end --> 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 a51595b7f..084f5da83 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 @@ -230,4 +230,5 @@ </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> </tagNode> +#include <include/vtysh-generic-wide.xml.i> <!-- included end --> diff --git a/op-mode-definitions/include/show-route-bgp.xml.i b/op-mode-definitions/include/show-route-bgp.xml.i new file mode 100644 index 000000000..5c26bf43f --- /dev/null +++ b/op-mode-definitions/include/show-route-bgp.xml.i @@ -0,0 +1,8 @@ +<!-- included start from show-route-bgp.xml.i --> +<leafNode name="bgp"> + <properties> + <help>Border Gateway Protocol (BGP)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/show-route-connected.xml.i b/op-mode-definitions/include/show-route-connected.xml.i new file mode 100644 index 000000000..37364de64 --- /dev/null +++ b/op-mode-definitions/include/show-route-connected.xml.i @@ -0,0 +1,8 @@ +<!-- included start from show-route-connected.xml.i --> +<leafNode name="connected"> + <properties> + <help>Connected routes (directly attached subnet or host)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/show-route-isis.xml.i b/op-mode-definitions/include/show-route-isis.xml.i new file mode 100644 index 000000000..9ff2ccdc5 --- /dev/null +++ b/op-mode-definitions/include/show-route-isis.xml.i @@ -0,0 +1,8 @@ +<!-- included start from show-route-isis.xml.i --> +<leafNode name="isis"> + <properties> + <help>Intermediate System to Intermediate System (IS-IS)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/show-route-kernel.xml.i b/op-mode-definitions/include/show-route-kernel.xml.i new file mode 100644 index 000000000..8c5ac414e --- /dev/null +++ b/op-mode-definitions/include/show-route-kernel.xml.i @@ -0,0 +1,8 @@ +<!-- included start from show-route-kernel.xml.i --> +<leafNode name="kernel"> + <properties> + <help>Kernel routes (not installed via the zebra RIB)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/show-route-ospf.xml.i b/op-mode-definitions/include/show-route-ospf.xml.i new file mode 100644 index 000000000..1122aaba5 --- /dev/null +++ b/op-mode-definitions/include/show-route-ospf.xml.i @@ -0,0 +1,8 @@ +<!-- included start from show-route-ospf.xml.i --> +<leafNode name="ospf"> + <properties> + <help>Open Shortest Path First (OSPFv2)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/show-route-ospfv3.xml.i b/op-mode-definitions/include/show-route-ospfv3.xml.i new file mode 100644 index 000000000..c7a11b7ba --- /dev/null +++ b/op-mode-definitions/include/show-route-ospfv3.xml.i @@ -0,0 +1,8 @@ +<!-- included start from show-route-ospfv3.xml.i --> +<leafNode name="ospfv3"> + <properties> + <help>Open Shortest Path First (IPv6) (OSPFv3)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/show-route-rip.xml.i b/op-mode-definitions/include/show-route-rip.xml.i new file mode 100644 index 000000000..3c2fede28 --- /dev/null +++ b/op-mode-definitions/include/show-route-rip.xml.i @@ -0,0 +1,8 @@ +<!-- included start from show-route-rip.xml.i --> +<leafNode name="rip"> + <properties> + <help>Routing Information Protocol (RIP)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/show-route-ripng.xml.i b/op-mode-definitions/include/show-route-ripng.xml.i new file mode 100644 index 000000000..6e59cb054 --- /dev/null +++ b/op-mode-definitions/include/show-route-ripng.xml.i @@ -0,0 +1,8 @@ +<!-- included start from show-route-ripng.xml.i --> +<leafNode name="ripng"> + <properties> + <help>Routing Information Protocol next-generation (IPv6) (RIPng)</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/show-route-static.xml.i b/op-mode-definitions/include/show-route-static.xml.i new file mode 100644 index 000000000..c2e396763 --- /dev/null +++ b/op-mode-definitions/include/show-route-static.xml.i @@ -0,0 +1,8 @@ +<!-- included start from show-route-static.xml.i --> +<leafNode name="static"> + <properties> + <help>Statically configured routes</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/show-route-summary.xml.i b/op-mode-definitions/include/show-route-summary.xml.i new file mode 100644 index 000000000..471124562 --- /dev/null +++ b/op-mode-definitions/include/show-route-summary.xml.i @@ -0,0 +1,8 @@ +<!-- included start from show-route-summary.xml.i --> +<leafNode name="summary"> + <properties> + <help>Summary of all routes</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/show-route-supernets-only.xml.i b/op-mode-definitions/include/show-route-supernets-only.xml.i new file mode 100644 index 000000000..4d1e7c51f --- /dev/null +++ b/op-mode-definitions/include/show-route-supernets-only.xml.i @@ -0,0 +1,8 @@ +<!-- included start from show-route-supernets-only.xml.i --> +<leafNode name="supernets-only"> + <properties> + <help>Show supernet entries only</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/include/show-route-table.xml.i b/op-mode-definitions/include/show-route-table.xml.i new file mode 100644 index 000000000..c3cf82a86 --- /dev/null +++ b/op-mode-definitions/include/show-route-table.xml.i @@ -0,0 +1,17 @@ +<!-- included start from show-route-table.xml.i --> +<node name="table"> + <properties> + <help>Table to display</help> + </properties> +</node> +<tagNode name="table"> + <properties> + <help>The table number to display</help> + <completionHelp> + <list>all</list> + <path>protocols static table</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</tagNode> +<!-- included end --> diff --git a/op-mode-definitions/include/show-route-tag.xml.i b/op-mode-definitions/include/show-route-tag.xml.i new file mode 100644 index 000000000..8bfa0ae4e --- /dev/null +++ b/op-mode-definitions/include/show-route-tag.xml.i @@ -0,0 +1,16 @@ +<!-- included start from show-route-tag.xml.i --> +<node name="tag"> + <properties> + <help>Show only routes with tag</help> + </properties> +</node> +<tagNode name="tag"> + <properties> + <help>Tag value</help> + <completionHelp> + <list><1-4294967295></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</tagNode> +<!-- included end --> diff --git a/op-mode-definitions/show-bfd.xml.in b/op-mode-definitions/show-bfd.xml.in new file mode 100644 index 000000000..39e42e6ec --- /dev/null +++ b/op-mode-definitions/show-bfd.xml.in @@ -0,0 +1,56 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="bfd"> + <properties> + <help>Show Bidirectional Forwarding Detection (BFD)</help> + </properties> + <children> + <node name="peer"> + <properties> + <help>Show all Bidirectional Forwarding Detection (BFD) peer status</help> + </properties> + </node> + <tagNode name="peer"> + <properties> + <help>Show Bidirectional Forwarding Detection (BFD) peer status</help> + <completionHelp> + <script>vtysh -c "show bfd peers" | awk '/[:blank:]*peer/ { printf "%s\n", $2 }'</script> + </completionHelp> + </properties> + <command>vtysh -c "show bfd peers" | sed -n "/peer $4 /,/^$/p"</command> + <children> + <leafNode name="counters"> + <properties> + <help>Show Bidirectional Forwarding Detection (BFD) peer counters</help> + </properties> + <command>vtysh -c "show bfd peers counters" | sed -n "/peer $4 /,/^$/p"</command> + </leafNode> + </children> + </tagNode> + <node name="peers"> + <properties> + <help>Show Bidirectional Forwarding Detection peers</help> + </properties> + <command>vtysh -c "show bfd peers"</command> + <children> + <leafNode name="counters"> + <properties> + <help>Show Bidirectional Forwarding Detection (BFD) peer counters</help> + </properties> + <command>vtysh -c "show bfd peers counters"</command> + </leafNode> + <leafNode name="brief"> + <properties> + <help>Show Bidirectional Forwarding Detection (BFD) peers brief</help> + </properties> + <command>vtysh -c "show bfd peers brief"</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-interfaces-geneve.xml.in b/op-mode-definitions/show-interfaces-geneve.xml.in new file mode 100644 index 000000000..a47933315 --- /dev/null +++ b/op-mode-definitions/show-interfaces-geneve.xml.in @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="interfaces"> + <children> + <tagNode name="geneve"> + <properties> + <help>Show specified GENEVE interface information</help> + <completionHelp> + <path>interfaces geneve</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command> + <children> + <leafNode name="brief"> + <properties> + <help>Show summary of the specified GENEVE interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command> + </leafNode> + </children> + </tagNode> + <node name="geneve"> + <properties> + <help>Show GENEVE interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=geneve --action=show-brief</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed GENEVE interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=geneve --action=show</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-ip-route.xml.in b/op-mode-definitions/show-ip-route.xml.in index fdbb6859d..1e906672d 100644 --- a/op-mode-definitions/show-ip-route.xml.in +++ b/op-mode-definitions/show-ip-route.xml.in @@ -13,12 +13,7 @@ </properties> <command>vtysh -c "show ip route"</command> <children> - <leafNode name="bgp"> - <properties> - <help>Show IP BGP routes</help> - </properties> - <command>vtysh -c "show ip route bgp"</command> - </leafNode> + #include <include/show-route-bgp.xml.i> <node name="cache"> <properties> <help>Show kernel route cache</help> @@ -34,12 +29,7 @@ </properties> <command>ip -s route list cache $5</command> </tagNode> - <leafNode name="connected"> - <properties> - <help>Show IP connected routes</help> - </properties> - <command>vtysh -c "show ip route connected"</command> - </leafNode> + #include <include/show-route-connected.xml.i> <node name="forward"> <properties> <help>Show kernel route table</help> @@ -55,76 +45,15 @@ </properties> <command>ip -s route list $5</command> </tagNode> - <leafNode name="isis"> - <properties> - <help>Show IP IS-IS routes</help> - </properties> - <command>vtysh -c "show ip route isis"</command> - </leafNode> - <leafNode name="kernel"> - <properties> - <help>Show IP kernel routes</help> - </properties> - <command>vtysh -c "show ip route kernel"</command> - </leafNode> - <leafNode name="ospf"> - <properties> - <help>Show IP OSPF routes</help> - </properties> - <command>vtysh -c "show ip route ospf"</command> - </leafNode> - <leafNode name="rip"> - <properties> - <help>Show IP RIP routes</help> - </properties> - <command>vtysh -c "show ip route rip"</command> - </leafNode> - <leafNode name="static"> - <properties> - <help>Show IP static routes</help> - </properties> - <command>vtysh -c "show ip route static"</command> - </leafNode> - <leafNode name="summary"> - <properties> - <help>Show IP routes summary</help> - </properties> - <command>vtysh -c "show ip route summary"</command> - </leafNode> - <leafNode name="supernets-only"> - <properties> - <help>Show IP supernet routes</help> - </properties> - <command>vtysh -c "show ip route supernets-only"</command> - </leafNode> - <node name="table"> - <properties> - <help>Show IP routes in policy table</help> - </properties> - </node> - <tagNode name="table"> - <properties> - <help>Show IP routes in policy table</help> - <completionHelp> - <list><1-200></list> - </completionHelp> - </properties> - <command>vtysh -c "show ip route table $5"</command> - </tagNode> - <node name="tag"> - <properties> - <help>Show only routes with tag</help> - </properties> - </node> - <tagNode name="tag"> - <properties> - <help>Tag value</help> - <completionHelp> - <list><1-4294967295></list> - </completionHelp> - </properties> - <command>vtysh -c "show ip route tag $5"</command> - </tagNode> + #include <include/show-route-isis.xml.i> + #include <include/show-route-kernel.xml.i> + #include <include/show-route-ospf.xml.i> + #include <include/show-route-rip.xml.i> + #include <include/show-route-static.xml.i> + #include <include/show-route-summary.xml.i> + #include <include/show-route-supernets-only.xml.i> + #include <include/show-route-table.xml.i> + #include <include/show-route-tag.xml.i> <tagNode name="vrf"> <properties> <help>Show IP routes in VRF</help> @@ -133,7 +62,19 @@ <path>vrf name</path> </completionHelp> </properties> - <command>vtysh -c "show ip route vrf $5"</command> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/show-route-bgp.xml.i> + #include <include/show-route-connected.xml.i> + #include <include/show-route-isis.xml.i> + #include <include/show-route-kernel.xml.i> + #include <include/show-route-ospf.xml.i> + #include <include/show-route-rip.xml.i> + #include <include/show-route-static.xml.i> + #include <include/show-route-summary.xml.i> + #include <include/show-route-supernets-only.xml.i> + #include <include/show-route-tag.xml.i> + </children> </tagNode> </children> </node> @@ -144,7 +85,7 @@ <list><x.x.x.x> <x.x.x.x/x></list> </completionHelp> </properties> - <command>vtysh -c "show ip route $4"</command> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> <children> <leafNode name="longer-prefixes"> <properties> diff --git a/op-mode-definitions/show-ipv6-route.xml.in b/op-mode-definitions/show-ipv6-route.xml.in index 8624574ac..2c5024991 100644 --- a/op-mode-definitions/show-ipv6-route.xml.in +++ b/op-mode-definitions/show-ipv6-route.xml.in @@ -13,12 +13,7 @@ </properties> <command>vtysh -c "show ipv6 route"</command> <children> - <node name="bgp"> - <properties> - <help>Show IPv6 BGP routes</help> - </properties> - <command>vtysh -c "show ipv6 route bgp"</command> - </node> + #include <include/show-route-bgp.xml.i> <node name="cache"> <properties> <help>Show kernel IPv6 route cache</help> @@ -34,12 +29,7 @@ </properties> <command>ip -s -f inet6 route list cache $5</command> </tagNode> - <node name="connected"> - <properties> - <help>Show IPv6 connected routes</help> - </properties> - <command>vtysh -c "show ipv6 route connected"</command> - </node> + #include <include/show-route-connected.xml.i> <node name="forward"> <properties> <help>Show kernel IPv6 route table</help> @@ -55,71 +45,36 @@ </properties> <command>ip -s -f inet6 route list $5</command> </tagNode> - <node name="isis"> - <properties> - <help>Show IPv6 IS-IS routes</help> - </properties> - <command>vtysh -c "show ipv6 route isis"</command> - </node> - <node name="kernel"> - <properties> - <help>Show IPv6 Kernel routes</help> - </properties> - <command>vtysh -c "show ipv6 route kernel"</command> - </node> - <node name="ospfv3"> - <properties> - <help>Show IPv6 OSPF routes</help> - </properties> - <command>vtysh -c "show ipv6 route ospf6"</command> - </node> - <node name="ripng"> - <properties> - <help>Show IPv6 RIPNG routes</help> - </properties> - <command>vtysh -c "show ipv6 route ripng"</command> - </node> - <node name="static"> - <properties> - <help>Show IPv6 static routes</help> - </properties> - <command>vtysh -c "show ipv6 route static"</command> - </node> - <node name="summary"> - <properties> - <help>Show IPv6 routes summary</help> - </properties> - <command>vtysh -c "show ipv6 route summary"</command> - </node> - <node name="table"> - <properties> - <help>Show IPv6 routes in policy tables</help> - </properties> - <command>vtysh -c "show ipv6 route table all"</command> - </node> - <tagNode name="table"> - <properties> - <help>Show IPv6 routes in specific policy table</help> - <completionHelp> - <path>protocols static table</path> - </completionHelp> - </properties> - <command>vtysh -c "show ipv6 route table $5"</command> - </tagNode> - <node name="vrf"> - <properties> - <help>Show IPv6 routes in VRFs</help> - </properties> - <command>vtysh -c "show ipv6 route vrf all"</command> - </node> + #include <include/show-route-isis.xml.i> + #include <include/show-route-kernel.xml.i> + #include <include/show-route-ospfv3.xml.i> + #include <include/show-route-ripng.xml.i> + #include <include/show-route-static.xml.i> + #include <include/show-route-summary.xml.i> + #include <include/show-route-table.xml.i> + #include <include/show-route-tag.xml.i> <tagNode name="vrf"> <properties> - <help>Show IPv6 routes in specific VRF</help> + <help>Show IPv6 routes in VRF</help> <completionHelp> + <list>all</list> <path>vrf name</path> </completionHelp> </properties> - <command>vtysh -c "show ipv6 route vrf $5"</command> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + <children> + #include <include/show-route-bgp.xml.i> + #include <include/show-route-connected.xml.i> + #include <include/show-route-isis.xml.i> + #include <include/show-route-kernel.xml.i> + #include <include/show-route-ospfv3.xml.i> + #include <include/show-route-ripng.xml.i> + #include <include/show-route-static.xml.i> + #include <include/show-route-summary.xml.i> + #include <include/show-route-supernets-only.xml.i> + #include <include/show-route-table.xml.i> + #include <include/show-route-tag.xml.i> + </children> </tagNode> </children> </node> diff --git a/op-mode-definitions/show-netns.xml.in b/op-mode-definitions/show-netns.xml.in new file mode 100644 index 000000000..8d5072d4e --- /dev/null +++ b/op-mode-definitions/show-netns.xml.in @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="netns"> + <properties> + <help>Show network namespace information</help> + </properties> + <command>ip netns ls</command> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-protocols.xml.in b/op-mode-definitions/show-protocols.xml.in index d595e2c3c..698001b76 100644 --- a/op-mode-definitions/show-protocols.xml.in +++ b/op-mode-definitions/show-protocols.xml.in @@ -7,50 +7,6 @@ <help>Show protocol specific information</help> </properties> <children> - <node name="bfd"> - <properties> - <help>Show Bidirectional Forwarding Detection (BFD)</help> - </properties> - <children> - <node name="peer"> - <properties> - <help>Show all Bidirectional Forwarding Detection (BFD) peer status</help> - </properties> - <command>vtysh -c "show bfd peers"</command> - <children> - <leafNode name="counters"> - <properties> - <help>Show Bidirectional Forwarding Detection (BFD) peer counters</help> - </properties> - <command>vtysh -c "show bfd peers counters"</command> - </leafNode> - </children> - </node> - <tagNode name="peer"> - <properties> - <help>Show Bidirectional Forwarding Detection (BFD) peer status</help> - <completionHelp> - <script>vtysh -c "show bfd peers" | awk '/[:blank:]*peer/ { printf "%s\n", $2 }'</script> - </completionHelp> - </properties> - <command>vtysh -c "show bfd peers" | awk -v BFD_PEER=$5 'BEGIN { regex = sprintf("(peer %s.*)vrf", BFD_PEER) } { if (match($0, regex, bfd_peer_value)) peer=bfd_peer_value[1] } END { if (peer) system("vtysh -c \"show bfd " peer "\"") }'</command> - <children> - <leafNode name="counters"> - <properties> - <help>Show Bidirectional Forwarding Detection (BFD) peer counters</help> - </properties> - <command>vtysh -c "show bfd peers" | awk -v BFD_PEER=$5 'BEGIN { regex = sprintf("(peer %s.*)vrf", BFD_PEER) } { if (match($0, regex, bfd_peer_value)) peer=bfd_peer_value[1] } END { if (peer) system("vtysh -c \"show bfd " peer " counters\"") }'</command> - </leafNode> - </children> - </tagNode> - <leafNode name="peers"> - <properties> - <help>Show Bidirectional Forwarding Detection (BFD) peers brief</help> - </properties> - <command>vtysh -c "show bfd peers brief"</command> - </leafNode> - </children> - </node> <node name="static"> <properties> <help>Show static protocol parameters</help> diff --git a/python/vyos/base.py b/python/vyos/base.py index 4e23714e5..c78045548 100644 --- a/python/vyos/base.py +++ b/python/vyos/base.py @@ -1,4 +1,4 @@ -# Copyright 2018 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2018-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 @@ -13,6 +13,11 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see <http://www.gnu.org/licenses/>. +from textwrap import fill class ConfigError(Exception): - pass + def __init__(self, message): + # Reformat the message and trim it to 72 characters in length + message = fill(message, width=72) + # Call the base class constructor with the parameters it needs + super().__init__(message) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 425a2e416..d974a7565 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -459,7 +459,10 @@ def get_interface_dict(config, base, ifname=''): # Only add defaults if interface is not about to be deleted - this is # to keep a cleaner config dict. if 'deleted' not in dict: - dict['vif'][vif] = dict_merge(default_vif_values, vif_config) + address = leaf_node_changed(config, ['vif', vif, 'address']) + if address: dict['vif'][vif].update({'address_old' : address}) + + dict['vif'][vif] = dict_merge(default_vif_values, dict['vif'][vif]) # XXX: T2665: blend in proper DHCPv6-PD default values dict['vif'][vif] = T2665_set_dhcpv6pd_defaults(dict['vif'][vif]) @@ -480,7 +483,11 @@ def get_interface_dict(config, base, ifname=''): # Only add defaults if interface is not about to be deleted - this is # to keep a cleaner config dict. if 'deleted' not in dict: - dict['vif_s'][vif_s] = dict_merge(default_vif_s_values, vif_s_config) + address = leaf_node_changed(config, ['vif-s', vif_s, 'address']) + if address: dict['vif_s'][vif_s].update({'address_old' : address}) + + dict['vif_s'][vif_s] = dict_merge(default_vif_s_values, + dict['vif_s'][vif_s]) # XXX: T2665: blend in proper DHCPv6-PD default values dict['vif_s'][vif_s] = T2665_set_dhcpv6pd_defaults(dict['vif_s'][vif_s]) @@ -499,8 +506,12 @@ def get_interface_dict(config, base, ifname=''): # Only add defaults if interface is not about to be deleted - this is # to keep a cleaner config dict. if 'deleted' not in dict: + address = leaf_node_changed(config, ['vif-s', vif_s, 'vif-c', vif_c, 'address']) + if address: dict['vif_s'][vif_s]['vif_c'][vif_c].update( + {'address_old' : address}) + dict['vif_s'][vif_s]['vif_c'][vif_c] = dict_merge( - default_vif_c_values, vif_c_config) + default_vif_c_values, dict['vif_s'][vif_s]['vif_c'][vif_c]) # XXX: T2665: blend in proper DHCPv6-PD default values dict['vif_s'][vif_s]['vif_c'][vif_c] = T2665_set_dhcpv6pd_defaults( dict['vif_s'][vif_s]['vif_c'][vif_c]) diff --git a/python/vyos/configquery.py b/python/vyos/configquery.py index 1cdcbcf39..5b097b312 100644 --- a/python/vyos/configquery.py +++ b/python/vyos/configquery.py @@ -18,16 +18,15 @@ A small library that allows querying existence or value(s) of config settings from op mode, and execution of arbitrary op mode commands. ''' -import re -import json -from copy import deepcopy +import os from subprocess import STDOUT -import vyos.util -import vyos.xml +from vyos.util import popen, boot_configuration_complete from vyos.config import Config -from vyos.configtree import ConfigTree -from vyos.configsource import ConfigSourceSession +from vyos.configsource import ConfigSourceSession, ConfigSourceString +from vyos.defaults import directories + +config_file = os.path.join(directories['config'], 'config.boot') class ConfigQueryError(Exception): pass @@ -58,21 +57,21 @@ class CliShellApiConfigQuery(GenericConfigQuery): def exists(self, path: list): cmd = ' '.join(path) - (_, err) = vyos.util.popen(f'cli-shell-api existsActive {cmd}') + (_, err) = popen(f'cli-shell-api existsActive {cmd}') if err: return False return True def value(self, path: list): cmd = ' '.join(path) - (out, err) = vyos.util.popen(f'cli-shell-api returnActiveValue {cmd}') + (out, err) = popen(f'cli-shell-api returnActiveValue {cmd}') if err: raise ConfigQueryError('No value for given path') return out def values(self, path: list): cmd = ' '.join(path) - (out, err) = vyos.util.popen(f'cli-shell-api returnActiveValues {cmd}') + (out, err) = popen(f'cli-shell-api returnActiveValues {cmd}') if err: raise ConfigQueryError('No values for given path') return out @@ -81,25 +80,36 @@ class ConfigTreeQuery(GenericConfigQuery): def __init__(self): super().__init__() - config_source = ConfigSourceSession() - self.configtree = Config(config_source=config_source) + if boot_configuration_complete(): + config_source = ConfigSourceSession() + self.config = Config(config_source=config_source) + else: + try: + with open(config_file) as f: + config_string = f.read() + except OSError as err: + raise ConfigQueryError('No config file available') from err + + config_source = ConfigSourceString(running_config_text=config_string, + session_config_text=config_string) + self.config = Config(config_source=config_source) def exists(self, path: list): - return self.configtree.exists(path) + return self.config.exists(path) def value(self, path: list): - return self.configtree.return_value(path) + return self.config.return_value(path) def values(self, path: list): - return self.configtree.return_values(path) + return self.config.return_values(path) def list_nodes(self, path: list): - return self.configtree.list_nodes(path) + return self.config.list_nodes(path) def get_config_dict(self, path=[], effective=False, key_mangling=None, get_first_key=False, no_multi_convert=False, no_tag_node_value_mangle=False): - return self.configtree.get_config_dict(path, effective=effective, + return self.config.get_config_dict(path, effective=effective, key_mangling=key_mangling, get_first_key=get_first_key, no_multi_convert=no_multi_convert, no_tag_node_value_mangle=no_tag_node_value_mangle) @@ -110,7 +120,7 @@ class VbashOpRun(GenericOpRun): def run(self, path: list, **kwargs): cmd = ' '.join(path) - (out, err) = vyos.util.popen(f'. /opt/vyatta/share/vyatta-op/functions/interpreter/vyatta-op-run; _vyatta_op_run {cmd}', stderr=STDOUT, **kwargs) + (out, err) = popen(f'/opt/vyatta/bin/vyatta-op-cmd-wrapper {cmd}', stderr=STDOUT, **kwargs) if err: raise ConfigQueryError(out) return out diff --git a/python/vyos/configsource.py b/python/vyos/configsource.py index b0981d25e..a0f6a46b5 100644 --- a/python/vyos/configsource.py +++ b/python/vyos/configsource.py @@ -19,6 +19,7 @@ import re import subprocess from vyos.configtree import ConfigTree +from vyos.util import boot_configuration_complete class VyOSError(Exception): """ @@ -117,7 +118,7 @@ class ConfigSourceSession(ConfigSource): # Running config can be obtained either from op or conf mode, it always succeeds # once the config system is initialized during boot; # before initialization, set to empty string - if os.path.isfile('/tmp/vyos-config-status'): + if boot_configuration_complete(): try: running_config_text = self._run([self._cli_shell_api, '--show-active-only', '--show-show-defaults', '--show-ignore-edit', 'showConfig']) except VyOSError: diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py index 00b14a985..c77b695bd 100644 --- a/python/vyos/defaults.py +++ b/python/vyos/defaults.py @@ -29,6 +29,8 @@ directories = { "vyos_udev_dir": "/run/udev/vyos" } +config_status = '/tmp/vyos-config-status' + cfg_group = 'vyattacfg' cfg_vintage = 'vyos' @@ -44,8 +46,9 @@ https_data = { api_data = { 'listen_address' : '127.0.0.1', 'port' : '8080', - 'strict' : 'false', - 'debug' : 'false', + 'socket' : False, + 'strict' : False, + 'debug' : False, 'api_keys' : [ {"id": "testapp", "key": "qwerty"} ] } diff --git a/python/vyos/hostsd_client.py b/python/vyos/hostsd_client.py index 303b6ea47..f31ef51cf 100644 --- a/python/vyos/hostsd_client.py +++ b/python/vyos/hostsd_client.py @@ -79,6 +79,18 @@ class Client(object): msg = {'type': 'forward_zones', 'op': 'get'} return self._communicate(msg) + def add_authoritative_zones(self, data): + msg = {'type': 'authoritative_zones', 'op': 'add', 'data': data} + self._communicate(msg) + + def delete_authoritative_zones(self, data): + msg = {'type': 'authoritative_zones', 'op': 'delete', 'data': data} + self._communicate(msg) + + def get_authoritative_zones(self): + msg = {'type': 'authoritative_zones', 'op': 'get'} + return self._communicate(msg) + def add_search_domains(self, data): msg = {'type': 'search_domains', 'op': 'add', 'data': data} self._communicate(msg) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 58d130ef6..bcb692697 100755 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -37,6 +37,7 @@ 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 get_interface_namespace from vyos.util import is_systemd_service_active from vyos.template import is_ipv4 from vyos.template import is_ipv6 @@ -135,6 +136,9 @@ class Interface(Control): 'validate': assert_mtu, 'shellcmd': 'ip link set dev {ifname} mtu {value}', }, + 'netns': { + 'shellcmd': 'ip link set dev {ifname} netns {value}', + }, 'vrf': { 'convert': lambda v: f'master {v}' if v else 'nomaster', 'shellcmd': 'ip link set dev {ifname} {value}', @@ -512,6 +516,35 @@ class Interface(Control): if prev_state == 'up': self.set_admin_state('up') + def del_netns(self, netns): + """ + Remove interface from given NETNS. + """ + + # If NETNS does not exist then there is nothing to delete + if not os.path.exists(f'/run/netns/{netns}'): + return None + + # As a PoC we only allow 'dummy' interfaces + if 'dum' not in self.ifname: + return None + + # Check if interface realy exists in namespace + if get_interface_namespace(self.ifname) != None: + self._cmd(f'ip netns exec {get_interface_namespace(self.ifname)} ip link del dev {self.ifname}') + return + + def set_netns(self, netns): + """ + Add interface from given NETNS. + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('dum0').set_netns('foo') + """ + + self.set_interface('netns', netns) + def set_vrf(self, vrf): """ Add/Remove interface from given VRF instance. @@ -1353,6 +1386,16 @@ class Interface(Control): if mac: self.set_mac(mac) + # If interface is connected to NETNS we don't have to check all other + # settings like MTU/IPv6/sysctl values, etc. + # Since the interface is pushed onto a separate logical stack + # Configure NETNS + if dict_search('netns', config) != None: + self.set_netns(config.get('netns', '')) + return + else: + self.del_netns(config.get('netns', '')) + # Update interface description self.set_alias(config.get('description', '')) diff --git a/python/vyos/ifconfig/section.py b/python/vyos/ifconfig/section.py index 0e4447b9e..91f667b65 100644 --- a/python/vyos/ifconfig/section.py +++ b/python/vyos/ifconfig/section.py @@ -52,12 +52,12 @@ class Section: name: name of the interface vlan: if vlan is True, do not stop at the vlan number """ - name = name.rstrip('0123456789') - name = name.rstrip('.') - if vlan: - name = name.rstrip('0123456789.') if vrrp: - name = name.rstrip('0123456789v') + name = re.sub(r'\d(\d|v|\.)*$', '', name) + elif vlan: + name = re.sub(r'\d(\d|\.)*$', '', name) + else: + name = re.sub(r'\d+$', '', name) return name @classmethod diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py index d73fb47b8..9615f396d 100644 --- a/python/vyos/ifconfig/vxlan.py +++ b/python/vyos/ifconfig/vxlan.py @@ -54,18 +54,20 @@ class VXLANIf(Interface): # arguments used by iproute2. For more information please refer to: # - https://man7.org/linux/man-pages/man8/ip-link.8.html mapping = { - 'source_address' : 'local', - 'source_interface' : 'dev', - 'remote' : 'remote', 'group' : 'group', + 'external' : 'external', 'parameters.ip.dont_fragment': 'df set', 'parameters.ip.tos' : 'tos', 'parameters.ip.ttl' : 'ttl', 'parameters.ipv6.flowlabel' : 'flowlabel', 'parameters.nolearning' : 'nolearning', + 'remote' : 'remote', + 'source_address' : 'local', + 'source_interface' : 'dev', + 'vni' : 'id', } - cmd = 'ip link add {ifname} type {type} id {vni} dstport {port}' + cmd = 'ip link add {ifname} type {type} dstport {port}' for vyos_key, iproute2_key in mapping.items(): # dict_search will return an empty dict "{}" for valueless nodes like # "parameters.nolearning" - thus we need to test the nodes existence diff --git a/python/vyos/range_regex.py b/python/vyos/range_regex.py new file mode 100644 index 000000000..a8190d140 --- /dev/null +++ b/python/vyos/range_regex.py @@ -0,0 +1,142 @@ +'''Copyright (c) 2013, Dmitry Voronin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +''' +import math + +# coding=utf8 + +# Split range to ranges that has its unique pattern. +# Example for 12-345: +# +# 12- 19: 1[2-9] +# 20- 99: [2-9]\d +# 100-299: [1-2]\d{2} +# 300-339: 3[0-3]\d +# 340-345: 34[0-5] + +def range_to_regex(inpt_range): + if isinstance(inpt_range, str): + range_list = inpt_range.split('-') + # Check input arguments + if len(range_list) == 2: + # The first element in range must be higher then the second + if int(range_list[0]) < int(range_list[1]): + return regex_for_range(int(range_list[0]), int(range_list[1])) + + return None + +def bounded_regex_for_range(min_, max_): + return r'\b({})\b'.format(regex_for_range(min_, max_)) + +def regex_for_range(min_, max_): + """ + > regex_for_range(12, 345) + '1[2-9]|[2-9]\d|[1-2]\d{2}|3[0-3]\d|34[0-5]' + """ + positive_subpatterns = [] + negative_subpatterns = [] + + if min_ < 0: + min__ = 1 + if max_ < 0: + min__ = abs(max_) + max__ = abs(min_) + + negative_subpatterns = split_to_patterns(min__, max__) + min_ = 0 + + if max_ >= 0: + positive_subpatterns = split_to_patterns(min_, max_) + + negative_only_subpatterns = ['-' + val for val in negative_subpatterns if val not in positive_subpatterns] + positive_only_subpatterns = [val for val in positive_subpatterns if val not in negative_subpatterns] + intersected_subpatterns = ['-?' + val for val in negative_subpatterns if val in positive_subpatterns] + + subpatterns = negative_only_subpatterns + intersected_subpatterns + positive_only_subpatterns + return '|'.join(subpatterns) + + +def split_to_patterns(min_, max_): + subpatterns = [] + + start = min_ + for stop in split_to_ranges(min_, max_): + subpatterns.append(range_to_pattern(start, stop)) + start = stop + 1 + + return subpatterns + + +def split_to_ranges(min_, max_): + stops = {max_} + + nines_count = 1 + stop = fill_by_nines(min_, nines_count) + while min_ <= stop < max_: + stops.add(stop) + + nines_count += 1 + stop = fill_by_nines(min_, nines_count) + + zeros_count = 1 + stop = fill_by_zeros(max_ + 1, zeros_count) - 1 + while min_ < stop <= max_: + stops.add(stop) + + zeros_count += 1 + stop = fill_by_zeros(max_ + 1, zeros_count) - 1 + + stops = list(stops) + stops.sort() + + return stops + + +def fill_by_nines(integer, nines_count): + return int(str(integer)[:-nines_count] + '9' * nines_count) + + +def fill_by_zeros(integer, zeros_count): + return integer - integer % 10 ** zeros_count + + +def range_to_pattern(start, stop): + pattern = '' + any_digit_count = 0 + + for start_digit, stop_digit in zip(str(start), str(stop)): + if start_digit == stop_digit: + pattern += start_digit + elif start_digit != '0' or stop_digit != '9': + pattern += '[{}-{}]'.format(start_digit, stop_digit) + else: + any_digit_count += 1 + + if any_digit_count: + pattern += r'\d' + + if any_digit_count > 1: + pattern += '{{{}}}'.format(any_digit_count) + + return pattern
\ No newline at end of file diff --git a/python/vyos/util.py b/python/vyos/util.py index d8e83ab8d..954c6670d 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -794,6 +794,24 @@ def get_interface_address(interface): tmp = loads(cmd(f'ip -d -j addr show {interface}'))[0] return tmp +def get_interface_namespace(iface): + """ + Returns wich netns the interface belongs to + """ + from json import loads + # Check if netns exist + tmp = loads(cmd(f'ip --json netns ls')) + if len(tmp) == 0: + return None + + for ns in tmp: + namespace = f'{ns["name"]}' + # Search interface in each netns + data = loads(cmd(f'ip netns exec {namespace} ip -j link show')) + for compare in data: + if iface == compare["ifname"]: + return namespace + def get_all_vrfs(): """ Return a dictionary of all system wide known VRF instances """ from json import loads @@ -961,3 +979,12 @@ def is_wwan_connected(interface): # return True/False if interface is in connected state return dict_search('modem.generic.state', tmp) == 'connected' + +def boot_configuration_complete() -> bool: + """ Check if the boot config loader has completed + """ + from vyos.defaults import config_status + + if os.path.isfile(config_status): + return True + return False diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py index 340ec4edd..bc0a6c128 100644 --- a/smoketest/scripts/cli/base_interfaces_test.py +++ b/smoketest/scripts/cli/base_interfaces_test.py @@ -171,10 +171,10 @@ class BasicInterfaceTest: def test_add_multiple_ip_addresses(self): # Add address for intf in self._interfaces: + for option in self._options.get(intf, []): + self.cli_set(self._base_path + [intf] + option.split()) for addr in self._test_addr: self.cli_set(self._base_path + [intf, 'address', addr]) - for option in self._options.get(intf, []): - self.cli_set(self._base_path + [intf] + option.split()) self.cli_commit() @@ -297,6 +297,23 @@ class BasicInterfaceTest: self.assertEqual(Interface(vif).get_admin_state(), 'up') + # T4064: Delete interface addresses, keep VLAN interface + for interface in self._interfaces: + base = self._base_path + [interface] + for vlan in self._vlan_range: + base = self._base_path + [interface, 'vif', vlan] + self.cli_delete(base + ['address']) + + self.cli_commit() + + # Verify no IP address is assigned + for interface in self._interfaces: + for vlan in self._vlan_range: + vif = f'{intf}.{vlan}' + for address in self._test_addr: + self.assertFalse(is_intf_addr_assigned(vif, address)) + + def test_vif_8021q_mtu_limits(self): # XXX: This testcase is not allowed to run as first testcase, reason # is the Wireless test will first load the wifi kernel hwsim module @@ -493,6 +510,24 @@ class BasicInterfaceTest: tmp = get_interface_config(vif) self.assertEqual(tmp['mtu'], int(self._mtu)) + + # T4064: Delete interface addresses, keep VLAN interface + for interface in self._interfaces: + base = self._base_path + [interface] + for vif_s in self._qinq_range: + for vif_c in self._vlan_range: + self.cli_delete(self._base_path + [interface, 'vif-s', vif_s, 'vif-c', vif_c, 'address']) + + self.cli_commit() + # Verify no IP address is assigned + for interface in self._interfaces: + base = self._base_path + [interface] + for vif_s in self._qinq_range: + for vif_c in self._vlan_range: + vif = f'{interface}.{vif_s}.{vif_c}' + for address in self._test_addr: + self.assertFalse(is_intf_addr_assigned(vif, address)) + # T3972: remove vif-c interfaces from vif-s for interface in self._interfaces: base = self._base_path + [interface] diff --git a/smoketest/scripts/cli/test_configd_init.py b/smoketest/scripts/cli/test_configd_init.py new file mode 100755 index 000000000..5dec89963 --- /dev/null +++ b/smoketest/scripts/cli/test_configd_init.py @@ -0,0 +1,38 @@ +#!/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 time import sleep + +from vyos.util import cmd, is_systemd_service_running + +class TestConfigdInit(unittest.TestCase): + def setUp(self): + self.running_state = is_systemd_service_running('vyos-configd.service') + + def test_configd_init(self): + if not self.running_state: + cmd('sudo systemctl start vyos-configd.service') + # allow time for init to succeed/fail + sleep(2) + self.assertTrue(is_systemd_service_running('vyos-configd.service')) + + def tearDown(self): + if not self.running_state: + cmd('sudo systemctl stop vyos-configd.service') + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_ha_vrrp.py b/smoketest/scripts/cli/test_ha_vrrp.py index 2524bf2b1..23a9f7796 100755 --- a/smoketest/scripts/cli/test_ha_vrrp.py +++ b/smoketest/scripts/cli/test_ha_vrrp.py @@ -44,7 +44,7 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase): for group in groups: vlan_id = group.lstrip('VLAN') - self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id]) + self.cli_delete(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id]) self.cli_delete(base_path) self.cli_commit() @@ -108,7 +108,7 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase): # Authentication self.cli_set(group_base + ['authentication', 'type', 'plaintext-password']) - self.cli_set(group_base + ['authentication', 'password', f'vyos-{group}']) + self.cli_set(group_base + ['authentication', 'password', f'{group}']) # commit changes self.cli_commit() @@ -129,7 +129,7 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' {vip}', config) # Authentication - self.assertIn(f'auth_pass "vyos-{group}"', config) + self.assertIn(f'auth_pass "{group}"', config) self.assertIn(f'auth_type PASS', config) def test_03_sync_group(self): diff --git a/smoketest/scripts/cli/test_interfaces_netns.py b/smoketest/scripts/cli/test_interfaces_netns.py new file mode 100755 index 000000000..9975a6b09 --- /dev/null +++ b/smoketest/scripts/cli/test_interfaces_netns.py @@ -0,0 +1,83 @@ +#!/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 os +import json +import unittest + +from netifaces import interfaces +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Interface +from vyos.ifconfig import Section +from vyos.util import cmd + +base_path = ['netns'] +namespaces = ['mgmt', 'front', 'back', 'ams-ix'] + +class NETNSTest(VyOSUnitTestSHIM.TestCase): + + def setUp(self): + self._interfaces = ['dum10', 'dum12', 'dum50'] + + def test_create_netns(self): + for netns in namespaces: + base = base_path + ['name', netns] + self.cli_set(base) + + # commit changes + self.cli_commit() + + netns_list = cmd('ip netns ls') + + # Verify NETNS configuration + for netns in namespaces: + self.assertTrue(netns in netns_list) + + + def test_netns_assign_interface(self): + netns = 'foo' + self.cli_set(['netns', 'name', netns]) + + # Set + for iface in self._interfaces: + self.cli_set(['interfaces', 'dummy', iface, 'netns', netns]) + + # commit changes + self.cli_commit() + + netns_iface_list = cmd(f'sudo ip netns exec {netns} ip link show') + + for iface in self._interfaces: + self.assertTrue(iface in netns_iface_list) + + # Delete + for iface in self._interfaces: + self.cli_delete(['interfaces', 'dummy', iface, 'netns', netns]) + + # commit changes + self.cli_commit() + + netns_iface_list = cmd(f'sudo ip netns exec {netns} ip link show') + + for iface in self._interfaces: + self.assertNotIn(iface, netns_iface_list) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_vxlan.py b/smoketest/scripts/cli/test_interfaces_vxlan.py index f63c850d8..9278adadd 100755 --- a/smoketest/scripts/cli/test_interfaces_vxlan.py +++ b/smoketest/scripts/cli/test_interfaces_vxlan.py @@ -16,6 +16,7 @@ import unittest +from vyos.configsession import ConfigSessionError from vyos.ifconfig import Interface from vyos.util import get_interface_config @@ -78,6 +79,9 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase): label = options['linkinfo']['info_data']['label'] self.assertIn(f'parameters ipv6 flowlabel {label}', self._options[interface]) + if any('external' in s for s in self._options[interface]): + self.assertTrue(options['linkinfo']['info_data']['external']) + self.assertEqual('vxlan', options['linkinfo']['info_kind']) self.assertEqual('set', options['linkinfo']['info_data']['df']) self.assertEqual(f'0x{tos}', options['linkinfo']['info_data']['tos']) @@ -85,5 +89,36 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase): self.assertEqual(Interface(interface).get_admin_state(), 'up') ttl += 10 + def test_vxlan_external(self): + interface = 'vxlan0' + source_address = '192.0.2.1' + self.cli_set(self._base_path + [interface, 'external']) + self.cli_set(self._base_path + [interface, 'source-address', source_address]) + + # Both 'VNI' and 'external' can not be specified at the same time. + self.cli_set(self._base_path + [interface, 'vni', '111']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(self._base_path + [interface, 'vni']) + + # Now add some more interfaces - this must fail and a CLI error needs + # to be generated as Linux can only handle one VXLAN tunnel when using + # external mode. + for intf in self._interfaces: + for option in self._options.get(intf, []): + self.cli_set(self._base_path + [intf] + option.split()) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + # Remove those test interfaces again + for intf in self._interfaces: + self.cli_delete(self._base_path + [intf]) + + self.cli_commit() + + options = get_interface_config(interface) + self.assertTrue(options['linkinfo']['info_data']['external']) + self.assertEqual('vxlan', options['linkinfo']['info_kind']) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_bfd.py b/smoketest/scripts/cli/test_protocols_bfd.py index 46a019dfc..fdc254a05 100755 --- a/smoketest/scripts/cli/test_protocols_bfd.py +++ b/smoketest/scripts/cli/test_protocols_bfd.py @@ -24,30 +24,35 @@ PROCESS_NAME = 'bfdd' base_path = ['protocols', 'bfd'] dum_if = 'dum1001' +vrf_name = 'red' peers = { '192.0.2.10' : { 'intv_rx' : '500', 'intv_tx' : '600', 'multihop' : '', 'source_addr': '192.0.2.254', - }, + 'profile' : 'foo-bar-baz', + }, '192.0.2.20' : { 'echo_mode' : '', 'intv_echo' : '100', 'intv_mult' : '100', 'intv_rx' : '222', 'intv_tx' : '333', + 'passive' : '', 'shutdown' : '', + 'profile' : 'foo', 'source_intf': dum_if, - }, - '2001:db8::a' : { + }, + '2001:db8::1000:1' : { 'source_addr': '2001:db8::1', - 'source_intf': dum_if, - }, - '2001:db8::b' : { + 'vrf' : vrf_name, + }, + '2001:db8::2000:1' : { 'source_addr': '2001:db8::1', 'multihop' : '', - }, + 'profile' : 'baz_foo', + }, } profiles = { @@ -59,9 +64,15 @@ profiles = { 'intv_tx' : '333', 'shutdown' : '', }, - 'bar' : { + 'foo-bar-baz' : { + 'intv_mult' : '4', + 'intv_rx' : '400', + 'intv_tx' : '400', + }, + 'baz_foo' : { 'intv_mult' : '102', 'intv_rx' : '444', + 'passive' : '', }, } @@ -73,6 +84,8 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): self.assertTrue(process_named_running(PROCESS_NAME)) def test_bfd_peer(self): + self.cli_set(['vrf', 'name', vrf_name, 'table', '1000']) + for peer, peer_config in peers.items(): if 'echo_mode' in peer_config: self.cli_set(base_path + ['peer', peer, 'echo-mode']) @@ -86,18 +99,22 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['peer', peer, 'interval', 'transmit', peer_config["intv_tx"]]) if 'multihop' in peer_config: self.cli_set(base_path + ['peer', peer, 'multihop']) + if 'passive' in peer_config: + self.cli_set(base_path + ['peer', peer, 'passive']) if 'shutdown' in peer_config: self.cli_set(base_path + ['peer', peer, 'shutdown']) if 'source_addr' in peer_config: self.cli_set(base_path + ['peer', peer, 'source', 'address', peer_config["source_addr"]]) if 'source_intf' in peer_config: self.cli_set(base_path + ['peer', peer, 'source', 'interface', peer_config["source_intf"]]) + if 'vrf' in peer_config: + self.cli_set(base_path + ['peer', peer, 'vrf', peer_config["vrf"]]) # commit changes self.cli_commit() # Verify FRR bgpd configuration - frrconfig = self.getFRRconfig('bfd') + frrconfig = self.getFRRconfig('bfd', daemon=PROCESS_NAME) for peer, peer_config in peers.items(): tmp = f'peer {peer}' if 'multihop' in peer_config: @@ -106,9 +123,11 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): tmp += f' local-address {peer_config["source_addr"]}' if 'source_intf' in peer_config: tmp += f' interface {peer_config["source_intf"]}' + if 'vrf' in peer_config: + tmp += f' vrf {peer_config["vrf"]}' self.assertIn(tmp, frrconfig) - peerconfig = self.getFRRconfig(f' peer {peer}', end='') + peerconfig = self.getFRRconfig(f' peer {peer}', end='', daemon=PROCESS_NAME) if 'echo_mode' in peer_config: self.assertIn(f'echo-mode', peerconfig) @@ -121,14 +140,16 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): self.assertIn(f'receive-interval {peer_config["intv_rx"]}', peerconfig) if 'intv_tx' in peer_config: self.assertIn(f'transmit-interval {peer_config["intv_tx"]}', peerconfig) + if 'passive' in peer_config: + self.assertIn(f'passive-mode', peerconfig) if 'shutdown' in peer_config: self.assertIn(f'shutdown', peerconfig) else: self.assertNotIn(f'shutdown', peerconfig) - def test_bfd_profile(self): - peer = '192.0.2.10' + self.cli_delete(['vrf', 'name', vrf_name]) + def test_bfd_profile(self): for profile, profile_config in profiles.items(): if 'echo_mode' in profile_config: self.cli_set(base_path + ['profile', profile, 'echo-mode']) @@ -140,10 +161,25 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['profile', profile, 'interval', 'receive', profile_config["intv_rx"]]) if 'intv_tx' in profile_config: self.cli_set(base_path + ['profile', profile, 'interval', 'transmit', profile_config["intv_tx"]]) + if 'passive' in profile_config: + self.cli_set(base_path + ['profile', profile, 'passive']) if 'shutdown' in profile_config: self.cli_set(base_path + ['profile', profile, 'shutdown']) - self.cli_set(base_path + ['peer', peer, 'profile', list(profiles)[0]]) + for peer, peer_config in peers.items(): + if 'profile' in peer_config: + self.cli_set(base_path + ['peer', peer, 'profile', peer_config["profile"] + 'wrong']) + if 'source_addr' in peer_config: + self.cli_set(base_path + ['peer', peer, 'source', 'address', peer_config["source_addr"]]) + if 'source_intf' in peer_config: + self.cli_set(base_path + ['peer', peer, 'source', 'interface', peer_config["source_intf"]]) + + # BFD profile does not exist! + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for peer, peer_config in peers.items(): + if 'profile' in peer_config: + self.cli_set(base_path + ['peer', peer, 'profile', peer_config["profile"]]) # commit changes self.cli_commit() @@ -152,20 +188,27 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): for profile, profile_config in profiles.items(): config = self.getFRRconfig(f' profile {profile}', endsection='^ !') if 'echo_mode' in profile_config: - self.assertIn(f'echo-mode', config) + self.assertIn(f' echo-mode', config) if 'intv_echo' in profile_config: - self.assertIn(f'echo receive-interval {profile_config["intv_echo"]}', config) - self.assertIn(f'echo transmit-interval {profile_config["intv_echo"]}', config) + self.assertIn(f' echo receive-interval {profile_config["intv_echo"]}', config) + self.assertIn(f' echo transmit-interval {profile_config["intv_echo"]}', config) if 'intv_mult' in profile_config: - self.assertIn(f'detect-multiplier {profile_config["intv_mult"]}', config) + self.assertIn(f' detect-multiplier {profile_config["intv_mult"]}', config) if 'intv_rx' in profile_config: - self.assertIn(f'receive-interval {profile_config["intv_rx"]}', config) + self.assertIn(f' receive-interval {profile_config["intv_rx"]}', config) if 'intv_tx' in profile_config: - self.assertIn(f'transmit-interval {profile_config["intv_tx"]}', config) + self.assertIn(f' transmit-interval {profile_config["intv_tx"]}', config) + if 'passive' in profile_config: + self.assertIn(f' passive-mode', config) if 'shutdown' in profile_config: - self.assertIn(f'shutdown', config) + self.assertIn(f' shutdown', config) else: self.assertNotIn(f'shutdown', config) + for peer, peer_config in peers.items(): + peerconfig = self.getFRRconfig(f' peer {peer}', end='', daemon=PROCESS_NAME) + if 'profile' in peer_config: + self.assertIn(f' profile {peer_config["profile"]}', peerconfig) + 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 16284ed01..d7230baf4 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -32,9 +32,11 @@ prefix_list_in = 'pfx-foo-in' prefix_list_out = 'pfx-foo-out' prefix_list_in6 = 'pfx-foo-in6' prefix_list_out6 = 'pfx-foo-out6' +bfd_profile = 'foo-bar-baz' neighbor_config = { '192.0.2.1' : { + 'bfd' : '', 'cap_dynamic' : '', 'cap_ext_next' : '', 'remote_as' : '100', @@ -51,23 +53,30 @@ neighbor_config = { 'addpath_all' : '', }, '192.0.2.2' : { + 'bfd_profile' : bfd_profile, 'remote_as' : '200', 'shutdown' : '', 'no_cap_nego' : '', 'port' : '667', 'cap_strict' : '', + 'advertise_map': route_map_in, + 'non_exist_map': route_map_out, 'pfx_list_in' : prefix_list_in, 'pfx_list_out' : prefix_list_out, 'no_send_comm_std' : '', }, '192.0.2.3' : { + 'advertise_map': route_map_in, 'description' : 'foo bar baz', 'remote_as' : '200', 'passive' : '', 'multi_hop' : '5', 'update_src' : 'lo', + 'peer_group' : 'foo', }, '2001:db8::1' : { + 'advertise_map': route_map_in, + 'exist_map' : route_map_out, 'cap_dynamic' : '', 'cap_ext_next' : '', 'remote_as' : '123', @@ -83,6 +92,7 @@ neighbor_config = { 'route_map_out': route_map_out, 'no_send_comm_std' : '', 'addpath_per_as' : '', + 'peer_group' : 'foo-bar', }, '2001:db8::2' : { 'remote_as' : '456', @@ -93,11 +103,15 @@ neighbor_config = { 'pfx_list_in' : prefix_list_in6, 'pfx_list_out' : prefix_list_out6, 'no_send_comm_ext' : '', + 'peer_group' : 'foo-bar_baz', }, } peer_group_config = { 'foo' : { + 'advertise_map': route_map_in, + 'exist_map' : route_map_out, + 'bfd' : '', 'remote_as' : '100', 'passive' : '', 'password' : 'VyOS-Secure123', @@ -105,7 +119,8 @@ peer_group_config = { 'cap_over' : '', 'ttl_security': '5', }, - 'bar' : { + 'foo-bar' : { + 'advertise_map': route_map_in, 'description' : 'foo peer bar group', 'remote_as' : '200', 'shutdown' : '', @@ -115,7 +130,10 @@ peer_group_config = { 'pfx_list_out' : prefix_list_out, 'no_send_comm_ext' : '', }, - 'baz' : { + 'foo-bar_baz' : { + 'advertise_map': route_map_in, + 'non_exist_map': route_map_out, + 'bfd_profile' : bfd_profile, 'cap_dynamic' : '', 'cap_ext_next' : '', 'remote_as' : '200', @@ -128,23 +146,34 @@ peer_group_config = { } class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): - def setUp(self): - self.cli_set(['policy', 'route-map', route_map_in, 'rule', '10', 'action', 'permit']) - self.cli_set(['policy', 'route-map', route_map_out, 'rule', '10', 'action', 'permit']) - self.cli_set(['policy', 'prefix-list', prefix_list_in, 'rule', '10', 'action', 'permit']) - self.cli_set(['policy', 'prefix-list', prefix_list_in, 'rule', '10', 'prefix', '192.0.2.0/25']) - self.cli_set(['policy', 'prefix-list', prefix_list_out, 'rule', '10', 'action', 'permit']) - self.cli_set(['policy', 'prefix-list', prefix_list_out, 'rule', '10', 'prefix', '192.0.2.128/25']) - - self.cli_set(['policy', 'prefix-list6', prefix_list_in6, 'rule', '10', 'action', 'permit']) - self.cli_set(['policy', 'prefix-list6', prefix_list_in6, 'rule', '10', 'prefix', '2001:db8:1000::/64']) - self.cli_set(['policy', 'prefix-list6', prefix_list_out6, 'rule', '10', 'action', 'deny']) - self.cli_set(['policy', 'prefix-list6', prefix_list_out6, 'rule', '10', 'prefix', '2001:db8:2000::/64']) + @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 :) + cls.cli_delete(cls, base_path) + + cls.cli_set(cls, ['policy', 'route-map', route_map_in, 'rule', '10', 'action', 'permit']) + cls.cli_set(cls, ['policy', 'route-map', route_map_out, 'rule', '10', 'action', 'permit']) + cls.cli_set(cls, ['policy', 'prefix-list', prefix_list_in, 'rule', '10', 'action', 'permit']) + cls.cli_set(cls, ['policy', 'prefix-list', prefix_list_in, 'rule', '10', 'prefix', '192.0.2.0/25']) + cls.cli_set(cls, ['policy', 'prefix-list', prefix_list_out, 'rule', '10', 'action', 'permit']) + cls.cli_set(cls, ['policy', 'prefix-list', prefix_list_out, 'rule', '10', 'prefix', '192.0.2.128/25']) + + cls.cli_set(cls, ['policy', 'prefix-list6', prefix_list_in6, 'rule', '10', 'action', 'permit']) + cls.cli_set(cls, ['policy', 'prefix-list6', prefix_list_in6, 'rule', '10', 'prefix', '2001:db8:1000::/64']) + cls.cli_set(cls, ['policy', 'prefix-list6', prefix_list_out6, 'rule', '10', 'action', 'deny']) + cls.cli_set(cls, ['policy', 'prefix-list6', prefix_list_out6, 'rule', '10', 'prefix', '2001:db8:2000::/64']) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['policy']) + def setUp(self): self.cli_set(base_path + ['local-as', ASN]) def tearDown(self): - self.cli_delete(['policy']) self.cli_delete(['vrf']) self.cli_delete(base_path) self.cli_commit() @@ -154,6 +183,11 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): def verify_frr_config(self, peer, peer_config, frrconfig): # recurring patterns to verify for both a simple neighbor and a peer-group + if 'bfd' in peer_config: + self.assertIn(f' neighbor {peer} bfd', frrconfig) + if 'bfd_profile' in peer_config: + self.assertIn(f' neighbor {peer} bfd profile {peer_config["bfd_profile"]}', frrconfig) + self.assertIn(f' neighbor {peer} bfd check-control-plane-failure', frrconfig) if 'cap_dynamic' in peer_config: self.assertIn(f' neighbor {peer} capability dynamic', frrconfig) if 'cap_ext_next' in peer_config: @@ -198,7 +232,13 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' neighbor {peer} addpath-tx-all-paths', frrconfig) if 'addpath_per_as' in peer_config: self.assertIn(f' neighbor {peer} addpath-tx-bestpath-per-AS', frrconfig) - + if 'advertise_map' in peer_config: + base = f' neighbor {peer} advertise-map {peer_config["advertise_map"]}' + if 'exist_map' in peer_config: + base = f'{base} exist-map {peer_config["exist_map"]}' + if 'non_exist_map' in peer_config: + base = f'{base} non-exist-map {peer_config["non_exist_map"]}' + self.assertIn(base, frrconfig) def test_bgp_01_simple(self): router_id = '127.0.0.1' @@ -208,6 +248,8 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): max_path_v4ibgp = '4' max_path_v6 = '8' max_path_v6ibgp = '16' + cond_adv_timer = '30' + min_hold_time = '2' self.cli_set(base_path + ['parameters', 'router-id', router_id]) self.cli_set(base_path + ['parameters', 'log-neighbor-changes']) @@ -229,6 +271,13 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['parameters', 'bestpath', 'bandwidth', 'default-weight-for-missing']) self.cli_set(base_path + ['parameters', 'bestpath', 'compare-routerid']) + self.cli_set(base_path + ['parameters', 'conditional-advertisement', 'timer', cond_adv_timer]) + self.cli_set(base_path + ['parameters', 'fast-convergence']) + self.cli_set(base_path + ['parameters', 'minimum-holdtime', min_hold_time]) + self.cli_set(base_path + ['parameters', 'reject-as-sets']) + self.cli_set(base_path + ['parameters', 'shutdown']) + self.cli_set(base_path + ['parameters', 'suppress-fib-pending']) + # AFI maximum path support self.cli_set(base_path + ['address-family', 'ipv4-unicast', 'maximum-paths', 'ebgp', max_path_v4]) self.cli_set(base_path + ['address-family', 'ipv4-unicast', 'maximum-paths', 'ibgp', max_path_v4ibgp]) @@ -244,11 +293,17 @@ 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' bgp conditional-advertisement timer {cond_adv_timer}', frrconfig) + self.assertIn(f' bgp fast-convergence', 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) self.assertIn(f' bgp bestpath bandwidth default-weight-for-missing', frrconfig) self.assertIn(f' bgp bestpath compare-routerid', frrconfig) + self.assertIn(f' bgp minimum-holdtime {min_hold_time}', frrconfig) + self.assertIn(f' bgp reject-as-sets', frrconfig) + self.assertIn(f' bgp shutdown', frrconfig) + self.assertIn(f' bgp suppress-fib-pending', frrconfig) self.assertNotIn(f'bgp ebgp-requires-policy', frrconfig) afiv4_config = self.getFRRconfig(' address-family ipv4 unicast') @@ -270,6 +325,11 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): if 'adv_interv' in peer_config: self.cli_set(base_path + ['neighbor', peer, 'advertisement-interval', peer_config["adv_interv"]]) + if 'bfd' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'bfd']) + if 'bfd_profile' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'bfd', 'profile', peer_config["bfd_profile"]]) + self.cli_set(base_path + ['neighbor', peer, 'bfd', 'check-control-plane-failure']) if 'cap_dynamic' in peer_config: self.cli_set(base_path + ['neighbor', peer, 'capability', 'dynamic']) if 'cap_ext_next' in peer_config: @@ -319,6 +379,20 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): if 'addpath_per_as' in peer_config: self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'addpath-tx-per-as']) + # Conditional advertisement + if 'advertise_map' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'conditionally-advertise', 'advertise-map', peer_config["advertise_map"]]) + # Either exist-map or non-exist-map needs to be specified + if 'exist_map' not in peer_config and 'non_exist_map' not in peer_config: + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'conditionally-advertise', 'exist-map', route_map_in]) + + if 'exist_map' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'conditionally-advertise', 'exist-map', peer_config["exist_map"]]) + if 'non_exist_map' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'conditionally-advertise', 'non-exist-map', peer_config["non_exist_map"]]) + # commit changes self.cli_commit() @@ -339,6 +413,11 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): def test_bgp_03_peer_groups(self): # Test out individual peer-group configuration items for peer_group, config in peer_group_config.items(): + if 'bfd' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'bfd']) + if 'bfd_profile' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'bfd', 'profile', config["bfd_profile"]]) + self.cli_set(base_path + ['peer-group', peer_group, 'bfd', 'check-control-plane-failure']) if 'cap_dynamic' in config: self.cli_set(base_path + ['peer-group', peer_group, 'capability', 'dynamic']) if 'cap_ext_next' in config: @@ -382,6 +461,24 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): if 'addpath_per_as' in config: self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'addpath-tx-per-as']) + # Conditional advertisement + if 'advertise_map' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'conditionally-advertise', 'advertise-map', config["advertise_map"]]) + # Either exist-map or non-exist-map needs to be specified + if 'exist_map' not in config and 'non_exist_map' not in config: + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'conditionally-advertise', 'exist-map', route_map_in]) + + if 'exist_map' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'conditionally-advertise', 'exist-map', config["exist_map"]]) + if 'non_exist_map' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'conditionally-advertise', 'non-exist-map', config["non_exist_map"]]) + + for peer, peer_config in neighbor_config.items(): + if 'peer_group' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'peer-group', peer_config['peer_group']]) + # commit changes self.cli_commit() @@ -393,6 +490,10 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' neighbor {peer_group} peer-group', frrconfig) self.verify_frr_config(peer, peer_config, frrconfig) + for peer, peer_config in neighbor_config.items(): + if 'peer_group' in peer_config: + self.assertIn(f' neighbor {peer} peer-group {peer_config["peer_group"]}', frrconfig) + def test_bgp_04_afi_ipv4(self): networks = { @@ -753,4 +854,4 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' exit-address-family', afi_config) if __name__ == '__main__': - unittest.main(verbosity=2)
\ No newline at end of file + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_isis.py b/smoketest/scripts/cli/test_protocols_isis.py index e42040025..7f51c7178 100755 --- a/smoketest/scripts/cli/test_protocols_isis.py +++ b/smoketest/scripts/cli/test_protocols_isis.py @@ -198,17 +198,19 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): self.assertIn(f' area-password clear {password}', tmp) - def test_isis_06_spf_delay(self): + def test_isis_06_spf_delay_bfd(self): network = 'point-to-point' holddown = '10' init_delay = '50' long_delay = '200' short_delay = '100' time_to_learn = '75' + bfd_profile = 'isis-bfd' self.cli_set(base_path + ['net', net]) for interface in self._interfaces: self.cli_set(base_path + ['interface', interface, 'network', network]) + self.cli_set(base_path + ['interface', interface, 'bfd', 'profile', bfd_profile]) self.cli_set(base_path + ['spf-delay-ietf', 'holddown', holddown]) # verify() - All types of spf-delay must be configured @@ -244,6 +246,8 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): self.assertIn(f' ip router isis {domain}', tmp) self.assertIn(f' ipv6 router isis {domain}', tmp) self.assertIn(f' isis network {network}', tmp) + self.assertIn(f' isis bfd', tmp) + self.assertIn(f' isis bfd profile {bfd_profile}', tmp) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_mpls.py b/smoketest/scripts/cli/test_protocols_mpls.py new file mode 100755 index 000000000..13d38d01b --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_mpls.py @@ -0,0 +1,117 @@ +#!/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 ConfigSessionError +from vyos.ifconfig import Section +from vyos.util import process_named_running + +PROCESS_NAME = 'ldpd' +base_path = ['protocols', 'mpls', 'ldp'] + +peers = { + '192.0.2.10' : { + 'intv_rx' : '500', + 'intv_tx' : '600', + 'multihop' : '', + 'source_addr': '192.0.2.254', + }, + '192.0.2.20' : { + 'echo_mode' : '', + 'intv_echo' : '100', + 'intv_mult' : '100', + 'intv_rx' : '222', + 'intv_tx' : '333', + 'passive' : '', + 'shutdown' : '', + }, + '2001:db8::a' : { + 'source_addr': '2001:db8::1', + }, + '2001:db8::b' : { + 'source_addr': '2001:db8::1', + 'multihop' : '', + }, +} + +profiles = { + 'foo' : { + 'echo_mode' : '', + 'intv_echo' : '100', + 'intv_mult' : '101', + 'intv_rx' : '222', + 'intv_tx' : '333', + 'shutdown' : '', + }, + 'bar' : { + 'intv_mult' : '102', + 'intv_rx' : '444', + 'passive' : '', + }, +} + +class TestProtocolsMPLS(VyOSUnitTestSHIM.TestCase): + @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 :) + cls.cli_delete(cls, base_path) + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_mpls_basic(self): + self.debug = True + router_id = '1.2.3.4' + transport_ipv4_addr = '5.6.7.8' + interfaces = Section.interfaces('ethernet') + + self.cli_set(base_path + ['router-id', router_id]) + + # At least one LDP interface must be configured + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for interface in interfaces: + self.cli_set(base_path + ['interface', interface]) + + # LDP transport address missing + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['discovery', 'transport-ipv4-address', transport_ipv4_addr]) + + # Commit changes + self.cli_commit() + + # Validate configuration + frrconfig = self.getFRRconfig('mpls ldp', daemon=PROCESS_NAME) + self.assertIn(f'mpls ldp', frrconfig) + self.assertIn(f' router-id {router_id}', frrconfig) + + # Validate AFI IPv4 + afiv4_config = self.getFRRconfig(' address-family ipv4', daemon=PROCESS_NAME) + self.assertIn(f' discovery transport-address {transport_ipv4_addr}', afiv4_config) + for interface in interfaces: + self.assertIn(f' interface {interface}', afiv4_config) + +if __name__ == '__main__': + unittest.main(verbosity=2, failfast=True) diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py index 04853c5fe..5783c5efb 100755 --- a/smoketest/scripts/cli/test_protocols_ospf.py +++ b/smoketest/scripts/cli/test_protocols_ospf.py @@ -251,13 +251,14 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): cost = '150' network = 'point-to-point' priority = '200' + bfd_profile = 'vyos-test' self.cli_set(base_path + ['passive-interface', 'default']) for interface in interfaces: base_interface = base_path + ['interface', interface] self.cli_set(base_interface + ['authentication', 'plaintext-password', password]) self.cli_set(base_interface + ['bandwidth', bandwidth]) - self.cli_set(base_interface + ['bfd']) + self.cli_set(base_interface + ['bfd', 'profile', bfd_profile]) self.cli_set(base_interface + ['cost', cost]) self.cli_set(base_interface + ['mtu-ignore']) self.cli_set(base_interface + ['network', network]) @@ -272,6 +273,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.assertIn(f'interface {interface}', config) self.assertIn(f' ip ospf authentication-key {password}', config) self.assertIn(f' ip ospf bfd', config) + self.assertIn(f' ip ospf bfd profile {bfd_profile}', config) self.assertIn(f' ip ospf cost {cost}', config) self.assertIn(f' ip ospf mtu-ignore', config) self.assertIn(f' ip ospf network {network}', config) diff --git a/smoketest/scripts/cli/test_protocols_ospfv3.py b/smoketest/scripts/cli/test_protocols_ospfv3.py index f0557f640..40dd254a8 100755 --- a/smoketest/scripts/cli/test_protocols_ospfv3.py +++ b/smoketest/scripts/cli/test_protocols_ospfv3.py @@ -110,6 +110,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.assertIn(f' redistribute {protocol} route-map {route_map}', frrconfig) def test_ospfv3_04_interfaces(self): + bfd_profile = 'vyos-ipv6' self.cli_set(base_path + ['parameters', 'router-id', router_id]) self.cli_set(base_path + ['area', default_area]) @@ -119,7 +120,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): interfaces = Section.interfaces('ethernet') for interface in interfaces: if_base = base_path + ['interface', interface] - self.cli_set(if_base + ['bfd']) + self.cli_set(if_base + ['bfd', 'profile', bfd_profile]) self.cli_set(if_base + ['cost', cost]) self.cli_set(if_base + ['instance-id', '0']) self.cli_set(if_base + ['mtu-ignore']) @@ -142,6 +143,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): if_config = self.getFRRconfig(f'interface {interface}') self.assertIn(f'interface {interface}', if_config) self.assertIn(f' ipv6 ospf6 bfd', if_config) + self.assertIn(f' ipv6 ospf6 bfd profile {bfd_profile}', if_config) self.assertIn(f' ipv6 ospf6 cost {cost}', if_config) self.assertIn(f' ipv6 ospf6 mtu-ignore', if_config) self.assertIn(f' ipv6 ospf6 network point-to-point', if_config) diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 06366362a..23a16df63 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -17,6 +17,7 @@ import os from sys import exit +from glob import glob from vyos.config import Config from vyos.configdict import dict_merge @@ -50,10 +51,12 @@ def get_config(config=None): if not conf.exists(base): return None - dns = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + dns = 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. + # options which we need to update into the dictionary retrieved. default_values = defaults(base) + # T2665 due to how defaults under tag nodes work, we must clear these out before we merge + del default_values['authoritative_domain'] dns = dict_merge(default_values, dns) # some additions to the default dictionary @@ -66,6 +69,183 @@ def get_config(config=None): if conf.exists(base_nameservers_dhcp): dns.update({'system_name_server_dhcp': conf.return_values(base_nameservers_dhcp)}) + if 'authoritative_domain' in dns: + dns['authoritative_zones'] = [] + dns['authoritative_zone_errors'] = [] + for node in dns['authoritative_domain']: + zonedata = dns['authoritative_domain'][node] + if ('disable' in zonedata) or (not 'records' in zonedata): + continue + zone = { + 'name': node, + 'file': "{}/zone.{}.conf".format(pdns_rec_run_dir, node), + 'records': [], + } + + recorddata = zonedata['records'] + + for rtype in [ 'a', 'aaaa', 'cname', 'mx', 'ptr', 'txt', 'spf', 'srv', 'naptr' ]: + if rtype not in recorddata: + continue + for subnode in recorddata[rtype]: + if 'disable' in recorddata[rtype][subnode]: + continue + + rdata = recorddata[rtype][subnode] + + if rtype in [ 'a', 'aaaa' ]: + rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665 + rdata = dict_merge(rdefaults, rdata) + + if not 'address' in rdata: + dns['authoritative_zone_errors'].append('{}.{}: at least one address is required'.format(subnode, node)) + continue + + for address in rdata['address']: + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': address + }) + elif rtype in ['cname', 'ptr']: + rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665 + rdata = dict_merge(rdefaults, rdata) + + if not 'target' in rdata: + dns['authoritative_zone_errors'].append('{}.{}: target is required'.format(subnode, node)) + continue + + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': '{}.'.format(rdata['target']) + }) + elif rtype == 'mx': + rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665 + del rdefaults['server'] + rdata = dict_merge(rdefaults, rdata) + + if not 'server' in rdata: + dns['authoritative_zone_errors'].append('{}.{}: at least one server is required'.format(subnode, node)) + continue + + for servername in rdata['server']: + serverdata = rdata['server'][servername] + serverdefaults = defaults(base + ['authoritative-domain', 'records', rtype, 'server']) # T2665 + serverdata = dict_merge(serverdefaults, serverdata) + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': '{} {}.'.format(serverdata['priority'], servername) + }) + elif rtype == 'txt': + rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665 + rdata = dict_merge(rdefaults, rdata) + + if not 'value' in rdata: + dns['authoritative_zone_errors'].append('{}.{}: at least one value is required'.format(subnode, node)) + continue + + for value in rdata['value']: + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': "\"{}\"".format(value.replace("\"", "\\\"")) + }) + elif rtype == 'spf': + rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665 + rdata = dict_merge(rdefaults, rdata) + + if not 'value' in rdata: + dns['authoritative_zone_errors'].append('{}.{}: value is required'.format(subnode, node)) + continue + + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': '"{}"'.format(rdata['value'].replace("\"", "\\\"")) + }) + elif rtype == 'srv': + rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665 + del rdefaults['entry'] + rdata = dict_merge(rdefaults, rdata) + + if not 'entry' in rdata: + dns['authoritative_zone_errors'].append('{}.{}: at least one entry is required'.format(subnode, node)) + continue + + for entryno in rdata['entry']: + entrydata = rdata['entry'][entryno] + entrydefaults = defaults(base + ['authoritative-domain', 'records', rtype, 'entry']) # T2665 + entrydata = dict_merge(entrydefaults, entrydata) + + if not 'hostname' in entrydata: + dns['authoritative_zone_errors'].append('{}.{}: hostname is required for entry {}'.format(subnode, node, entryno)) + continue + + if not 'port' in entrydata: + dns['authoritative_zone_errors'].append('{}.{}: port is required for entry {}'.format(subnode, node, entryno)) + continue + + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': '{} {} {} {}.'.format(entrydata['priority'], entrydata['weight'], entrydata['port'], entrydata['hostname']) + }) + elif rtype == 'naptr': + rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665 + del rdefaults['rule'] + rdata = dict_merge(rdefaults, rdata) + + + if not 'rule' in rdata: + dns['authoritative_zone_errors'].append('{}.{}: at least one rule is required'.format(subnode, node)) + continue + + for ruleno in rdata['rule']: + ruledata = rdata['rule'][ruleno] + ruledefaults = defaults(base + ['authoritative-domain', 'records', rtype, 'rule']) # T2665 + ruledata = dict_merge(ruledefaults, ruledata) + flags = "" + if 'lookup-srv' in ruledata: + flags += "S" + if 'lookup-a' in ruledata: + flags += "A" + if 'resolve-uri' in ruledata: + flags += "U" + if 'protocol-specific' in ruledata: + flags += "P" + + if 'order' in ruledata: + order = ruledata['order'] + else: + order = ruleno + + if 'regexp' in ruledata: + regexp= ruledata['regexp'].replace("\"", "\\\"") + else: + regexp = '' + + if ruledata['replacement']: + replacement = '{}.'.format(ruledata['replacement']) + else: + replacement = '' + + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': '{} {} "{}" "{}" "{}" {}'.format(order, ruledata['preference'], flags, ruledata['service'], regexp, replacement) + }) + + dns['authoritative_zones'].append(zone) + return dns def verify(dns): @@ -86,6 +266,11 @@ def verify(dns): if 'server' not in dns['domain'][domain]: raise ConfigError(f'No server configured for domain {domain}!') + if ('authoritative_zone_errors' in dns) and dns['authoritative_zone_errors']: + for error in dns['authoritative_zone_errors']: + print(error) + raise ConfigError('Invalid authoritative records have been defined') + if 'system' in dns: if not ('system_name_server' in dns or 'system_name_server_dhcp' in dns): print("Warning: No 'system name-server' or 'system " \ @@ -104,6 +289,15 @@ def generate(dns): render(pdns_rec_lua_conf_file, 'dns-forwarding/recursor.conf.lua.tmpl', dns, user=pdns_rec_user, group=pdns_rec_group) + for zone_filename in glob(f'{pdns_rec_run_dir}/zone.*.conf'): + os.unlink(zone_filename) + + if 'authoritative_zones' in dns: + for zone in dns['authoritative_zones']: + render(zone['file'], 'dns-forwarding/recursor.zone.conf.tmpl', + zone, user=pdns_rec_user, group=pdns_rec_group) + + # if vyos-hostsd didn't create its files yet, create them (empty) for file in [pdns_rec_hostsd_lua_conf_file, pdns_rec_hostsd_zones_file]: with open(file, 'a'): @@ -119,6 +313,9 @@ def apply(dns): if os.path.isfile(pdns_rec_config_file): os.unlink(pdns_rec_config_file) + + for zone_filename in glob(f'{pdns_rec_run_dir}/zone.*.conf'): + os.unlink(zone_filename) else: ### first apply vyos-hostsd config hc = hostsd_client() @@ -153,6 +350,12 @@ def apply(dns): if 'domain' in dns: hc.add_forward_zones(dns['domain']) + # hostsd generates NTAs for the authoritative zones + # the list and keys() are required as get returns a dict, not list + hc.delete_authoritative_zones(list(hc.get_authoritative_zones())) + if 'authoritative_zones' in dns: + hc.add_authoritative_zones(list(map(lambda zone: zone['name'], dns['authoritative_zones']))) + # call hostsd to generate forward-zones and its lua-config-file hc.apply() diff --git a/src/conf_mode/flow_accounting_conf.py b/src/conf_mode/flow_accounting_conf.py index 0a4559ade..daad00067 100755 --- a/src/conf_mode/flow_accounting_conf.py +++ b/src/conf_mode/flow_accounting_conf.py @@ -169,7 +169,8 @@ def get_config(): 'configured': vc.exists('system flow-accounting sflow'), 'agent-address': vc.return_value('system flow-accounting sflow agent-address'), 'sampling-rate': vc.return_value('system flow-accounting sflow sampling-rate'), - 'servers': None + 'servers': None, + 'source-address': vc.return_value('system flow-accounting sflow source-address') }, 'netflow': { 'configured': vc.exists('system flow-accounting netflow'), diff --git a/src/conf_mode/http-api.py b/src/conf_mode/http-api.py index 4bfcbeb47..ea0743cd5 100755 --- a/src/conf_mode/http-api.py +++ b/src/conf_mode/http-api.py @@ -31,7 +31,7 @@ from vyos.util import call from vyos import airbag airbag.enable() -config_file = '/etc/vyos/http-api.conf' +api_conf_file = '/etc/vyos/http-api.conf' vyos_conf_scripts_dir=vyos.defaults.directories['conf_mode'] @@ -55,15 +55,24 @@ def get_config(config=None): conf.set_level('service https api') if conf.exists('strict'): - http_api['strict'] = 'true' + http_api['strict'] = True if conf.exists('debug'): - http_api['debug'] = 'true' + http_api['debug'] = True + + if conf.exists('socket'): + http_api['socket'] = True if conf.exists('port'): port = conf.return_value('port') http_api['port'] = port + if conf.exists('cors'): + http_api['cors'] = {} + if conf.exists('cors allow-origin'): + origins = conf.return_values('cors allow-origin') + http_api['cors']['origins'] = origins[:] + if conf.exists('keys'): for name in conf.list_nodes('keys id'): if conf.exists('keys id {0} key'.format(name)): @@ -88,7 +97,7 @@ def generate(http_api): if not os.path.exists('/etc/vyos'): os.mkdir('/etc/vyos') - with open(config_file, 'w') as f: + with open(api_conf_file, 'w') as f: json.dump(http_api, f, indent=2) return None diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py index 92dc4a410..053ee5d4a 100755 --- a/src/conf_mode/https.py +++ b/src/conf_mode/https.py @@ -23,6 +23,7 @@ import vyos.defaults import vyos.certbot_util from vyos.config import Config +from vyos.configverify import verify_vrf from vyos import ConfigError from vyos.pki import wrap_certificate from vyos.pki import wrap_private_key @@ -34,6 +35,7 @@ from vyos import airbag airbag.enable() config_file = '/etc/nginx/sites-available/default' +systemd_override = r'/etc/systemd/system/nginx.service.d/override.conf' cert_dir = '/etc/ssl/certs' key_dir = '/etc/ssl/private' certbot_dir = vyos.defaults.directories['certbot'] @@ -103,6 +105,8 @@ def verify(https): if not domains_found: raise ConfigError("At least one 'virtual-host <id> server-name' " "matching the 'certbot domain-name' is required.") + + verify_vrf(https) return None def generate(https): @@ -143,7 +147,6 @@ def generate(https): server_cert = str(wrap_certificate(pki_cert['certificate'])) if 'ca-certificate' in cert_dict: ca_cert = cert_dict['ca-certificate'] - print(ca_cert) server_cert += '\n' + str(wrap_certificate(https['pki']['ca'][ca_cert]['certificate'])) write_file(cert_path, server_cert) @@ -188,6 +191,8 @@ def generate(https): vhosts = https.get('api-restrict', {}).get('virtual-host', []) if vhosts: api_data['vhost'] = vhosts[:] + if 'socket' in list(api_settings): + api_data['socket'] = True if api_data: vhost_list = api_data.get('vhost', []) @@ -209,10 +214,12 @@ def generate(https): } render(config_file, 'https/nginx.default.tmpl', data) - + render(systemd_override, 'https/override.conf.tmpl', https) return None def apply(https): + # Reload systemd manager configuration + call('systemctl daemon-reload') if https is not None: call('systemctl restart nginx.service') else: diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 804f2d14f..6cd931049 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -44,6 +44,20 @@ def get_config(config=None): base = ['interfaces', 'vxlan'] vxlan = get_interface_dict(conf, base) + # We need to verify that no other VXLAN tunnel is configured when external + # mode is in use - Linux Kernel limitation + conf.set_level(base) + vxlan['other_tunnels'] = conf.get_config_dict([], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + # This if-clause is just to be sure - it will always evaluate to true + ifname = vxlan['ifname'] + if ifname in vxlan['other_tunnels']: + del vxlan['other_tunnels'][ifname] + if len(vxlan['other_tunnels']) == 0: + del vxlan['other_tunnels'] + return vxlan def verify(vxlan): @@ -63,8 +77,17 @@ def verify(vxlan): if not any(tmp in ['group', 'remote', 'source_address'] for tmp in vxlan): raise ConfigError('Group, remote or source-address must be configured') - if 'vni' not in vxlan: - raise ConfigError('Must configure VNI for VXLAN') + if 'vni' not in vxlan and 'external' not in vxlan: + raise ConfigError( + 'Must either configure VXLAN "vni" or use "external" CLI option!') + + if {'external', 'vni'} <= set(vxlan): + raise ConfigError('Can not specify both "external" and "VNI"!') + + if {'external', 'other_tunnels'} <= set(vxlan): + other_tunnels = ', '.join(vxlan['other_tunnels']) + raise ConfigError(f'Only one VXLAN tunnel is supported when "external" '\ + f'CLI option is used. Additional tunnels: {other_tunnels}') if 'source_interface' in vxlan: # VXLAN adds at least an overhead of 50 byte - we need to check the diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py index f013e5411..a4b033374 100755 --- a/src/conf_mode/interfaces-wwan.py +++ b/src/conf_mode/interfaces-wwan.py @@ -17,6 +17,7 @@ import os from sys import exit +from time import sleep from vyos.config import Config from vyos.configdict import get_interface_dict @@ -28,10 +29,15 @@ from vyos.util import cmd from vyos.util import call from vyos.util import dict_search from vyos.util import DEVNULL +from vyos.util import is_systemd_service_active +from vyos.util import write_file from vyos import ConfigError from vyos import airbag airbag.enable() +service_name = 'ModemManager.service' +cron_script = '/etc/cron.d/wwan' + def get_config(config=None): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the @@ -44,6 +50,20 @@ def get_config(config=None): base = ['interfaces', 'wwan'] wwan = get_interface_dict(conf, base) + # We need to know the amount of other WWAN interfaces as ModemManager needs + # to be started or stopped. + conf.set_level(base) + wwan['other_interfaces'] = conf.get_config_dict([], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + # This if-clause is just to be sure - it will always evaluate to true + ifname = wwan['ifname'] + if ifname in wwan['other_interfaces']: + del wwan['other_interfaces'][ifname] + if len(wwan['other_interfaces']) == 0: + del wwan['other_interfaces'] + return wwan def verify(wwan): @@ -61,9 +81,26 @@ def verify(wwan): return None def generate(wwan): + if 'deleted' in wwan: + return None + + if not os.path.exists(cron_script): + write_file(cron_script, '*/5 * * * * root /usr/libexec/vyos/vyos-check-wwan.py') return None def apply(wwan): + if not is_systemd_service_active(service_name): + cmd(f'systemctl start {service_name}') + + counter = 100 + # Wait until a modem is detected and then we can continue + while counter > 0: + counter -= 1 + tmp = cmd('mmcli -L') + if tmp != 'No modems were found': + break + sleep(0.250) + # we only need the modem number. wwan0 -> 0, wwan1 -> 1 modem = wwan['ifname'].lstrip('wwan') base_cmd = f'mmcli --modem {modem}' @@ -73,6 +110,15 @@ def apply(wwan): w = WWANIf(wwan['ifname']) if 'deleted' in wwan or 'disable' in wwan: w.remove() + + # There are no other WWAN interfaces - stop the daemon + if 'other_interfaces' not in wwan: + cmd(f'systemctl stop {service_name}') + # Clean CRON helper script which is used for to re-connect when + # RF signal is lost + if os.path.exists(cron_script): + os.unlink(cron_script) + return None ip_type = 'ipv4' @@ -93,6 +139,9 @@ def apply(wwan): call(command, stdout=DEVNULL) w.update(wwan) + if 'other_interfaces' not in wwan and 'deleted' in wwan: + cmd(f'systemctl start {service_name}') + return None if __name__ == '__main__': diff --git a/src/conf_mode/netns.py b/src/conf_mode/netns.py new file mode 100755 index 000000000..0924eb616 --- /dev/null +++ b/src/conf_mode/netns.py @@ -0,0 +1,118 @@ +#!/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 +from tempfile import NamedTemporaryFile + +from vyos.config import Config +from vyos.configdict import node_changed +from vyos.ifconfig import Interface +from vyos.util import call +from vyos.util import dict_search +from vyos.util import get_interface_config +from vyos import ConfigError +from vyos import airbag +airbag.enable() + + +def netns_interfaces(c, match): + """ + get NETNS bound interfaces + """ + matched = [] + old_level = c.get_level() + c.set_level(['interfaces']) + section = c.get_config_dict([], get_first_key=True) + for type in section: + interfaces = section[type] + for name in interfaces: + interface = interfaces[name] + if 'netns' in interface: + v = interface.get('netns', '') + if v == match: + matched.append(name) + + c.set_level(old_level) + return matched + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['netns'] + netns = conf.get_config_dict(base, get_first_key=True, + no_tag_node_value_mangle=True) + + # determine which NETNS has been removed + for name in node_changed(conf, base + ['name']): + if 'netns_remove' not in netns: + netns.update({'netns_remove' : {}}) + + netns['netns_remove'][name] = {} + # get NETNS bound interfaces + interfaces = netns_interfaces(conf, name) + if interfaces: netns['netns_remove'][name]['interface'] = interfaces + + return netns + +def verify(netns): + # ensure NETNS is not assigned to any interface + if 'netns_remove' in netns: + for name, config in netns['netns_remove'].items(): + if 'interface' in config: + raise ConfigError(f'Can not remove NETNS "{name}", it still has '\ + f'member interfaces!') + + if 'name' in netns: + for name, config in netns['name'].items(): + print(name) + + return None + + +def generate(netns): + if not netns: + return None + + return None + + +def apply(netns): + + for tmp in (dict_search('netns_remove', netns) or []): + if os.path.isfile(f'/run/netns/{tmp}'): + call(f'ip netns del {tmp}') + + if 'name' in netns: + for name, config in netns['name'].items(): + if not os.path.isfile(f'/run/netns/{name}'): + call(f'ip netns add {name}') + + 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/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index 94825ba10..4ebc0989c 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -18,6 +18,7 @@ import os from vyos.config import Config from vyos.configdict import dict_merge +from vyos.configverify import verify_vrf from vyos.template import is_ipv6 from vyos.template import render_to_string from vyos.validate import is_ipv6_link_local @@ -33,7 +34,9 @@ def get_config(config=None): else: conf = Config() base = ['protocols', 'bfd'] - bfd = conf.get_config_dict(base, get_first_key=True) + bfd = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) # Bail out early if configuration tree does not exist if not conf.exists(base): return bfd @@ -76,11 +79,19 @@ def verify(bfd): # multihop and echo-mode cannot be used together if 'echo_mode' in peer_config: - raise ConfigError('Multihop and echo-mode cannot be used together') + raise ConfigError('BFD multihop and echo-mode cannot be used together') # multihop doesn't accept interface names if 'source' in peer_config and 'interface' in peer_config['source']: - raise ConfigError('Multihop and source interface cannot be used together') + raise ConfigError('BFD multihop and source interface cannot be used together') + + if 'profile' in peer_config: + profile_name = peer_config['profile'] + if 'profile' not in bfd or profile_name not in bfd['profile']: + raise ConfigError(f'BFD profile "{profile_name}" does not exist!') + + if 'vrf' in peer_config: + verify_vrf(peer_config) return None diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index b88f0c4ef..d8704727c 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -183,6 +183,28 @@ def verify(bgp): raise ConfigError(f'Neighbor "{peer}" cannot have both ipv6-unicast and ipv6-labeled-unicast configured at the same time!') afi_config = peer_config['address_family'][afi] + + if 'conditionally_advertise' in afi_config: + if 'advertise_map' not in afi_config['conditionally_advertise']: + raise ConfigError('Must speficy advertise-map when conditionally-advertise is in use!') + # Verify advertise-map (which is a route-map) exists + verify_route_map(afi_config['conditionally_advertise']['advertise_map'], bgp) + + if ('exist_map' not in afi_config['conditionally_advertise'] and + 'non_exist_map' not in afi_config['conditionally_advertise']): + raise ConfigError('Must either speficy exist-map or non-exist-map when ' \ + 'conditionally-advertise is in use!') + + if {'exist_map', 'non_exist_map'} <= set(afi_config['conditionally_advertise']): + raise ConfigError('Can not specify both exist-map and non-exist-map for ' \ + 'conditionally-advertise!') + + if 'exist_map' in afi_config['conditionally_advertise']: + verify_route_map(afi_config['conditionally_advertise']['exist_map'], bgp) + + if 'non_exist_map' in afi_config['conditionally_advertise']: + verify_route_map(afi_config['conditionally_advertise']['non_exist_map'], bgp) + # Validate if configured Prefix list exists if 'prefix_list' in afi_config: for tmp in ['import', 'export']: @@ -255,14 +277,6 @@ def verify(bgp): 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_mpls.py b/src/conf_mode/protocols_mpls.py index 3b27608da..0b0c7d07b 100755 --- a/src/conf_mode/protocols_mpls.py +++ b/src/conf_mode/protocols_mpls.py @@ -66,36 +66,24 @@ def verify(mpls): def generate(mpls): # If there's no MPLS config generated, create dictionary key with no value. - if not mpls: - mpls['new_frr_config'] = '' + if not mpls or 'deleted' in mpls: return None - mpls['new_frr_config'] = render_to_string('frr/ldpd.frr.tmpl', mpls) + mpls['frr_ldpd_config'] = render_to_string('frr/ldpd.frr.tmpl', mpls) return None def apply(mpls): - # Define dictionary that will load FRR config - frr_cfg = {} + ldpd_damon = 'ldpd' + # Save original configuration prior to starting any commit actions - frr_cfg['original_config'] = frr.get_configuration(daemon='ldpd') - frr_cfg['modified_config'] = frr.replace_section(frr_cfg['original_config'], mpls['new_frr_config'], from_re='mpls.*') - - # If FRR config is blank, rerun the blank commit three times due to frr-reload - # behavior/bug not properly clearing out on one commit. - if mpls['new_frr_config'] == '': - for x in range(3): - frr.reload_configuration(frr_cfg['modified_config'], daemon='ldpd') - elif not 'ldp' in mpls: - for x in range(3): - frr.reload_configuration(frr_cfg['modified_config'], daemon='ldpd') - else: - # FRR mark configuration will test for syntax errors and throws an - # exception if any syntax errors is detected - frr.mark_configuration(frr_cfg['modified_config']) + frr_cfg = frr.FRRConfig() + + frr_cfg.load_configuration(ldpd_damon) + frr_cfg.modify_section(f'^mpls ldp', stop_pattern='^exit', remove_stop_mark=True) - # Commit resulting configuration to FRR, this will throw CommitError - # on failure - frr.reload_configuration(frr_cfg['modified_config'], daemon='ldpd') + if 'frr_ldpd_config' in mpls: + frr_cfg.add_before(frr.default_add_before, mpls['frr_ldpd_config']) + frr_cfg.commit_configuration(ldpd_damon) # Set number of entries in the platform label tables labels = '0' @@ -122,7 +110,7 @@ def apply(mpls): system_interfaces = [] # Populate system interfaces list with local MPLS capable interfaces for interface in glob('/proc/sys/net/mpls/conf/*'): - system_interfaces.append(os.path.basename(interface)) + system_interfaces.append(os.path.basename(interface)) # This is where the comparison is done on if an interface needs to be enabled/disabled. for system_interface in system_interfaces: interface_state = read_file(f'/proc/sys/net/mpls/conf/{system_interface}/input') @@ -138,7 +126,7 @@ def apply(mpls): system_interfaces = [] # If MPLS interfaces are not configured, set MPLS processing disabled for interface in glob('/proc/sys/net/mpls/conf/*'): - system_interfaces.append(os.path.basename(interface)) + system_interfaces.append(os.path.basename(interface)) for system_interface in system_interfaces: system_interface = system_interface.replace('.', '/') call(f'sysctl -wq net.mpls.conf.{system_interface}.input=0') diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py index 9fbd531da..1f31d132d 100755 --- a/src/conf_mode/service_pppoe-server.py +++ b/src/conf_mode/service_pppoe-server.py @@ -24,8 +24,11 @@ from vyos.configverify import verify_accel_ppp_base_service from vyos.template import render from vyos.util import call from vyos.util import dict_search +from vyos.util import get_interface_config from vyos import ConfigError from vyos import airbag +from vyos.range_regex import range_to_regex + airbag.enable() pppoe_conf = r'/run/accel-pppd/pppoe.conf' @@ -56,6 +59,11 @@ def verify(pppoe): if 'interface' not in pppoe: raise ConfigError('At least one listen interface must be defined!') + # Check is interface exists in the system + for iface in pppoe['interface']: + if not get_interface_config(iface): + raise ConfigError(f'Interface {iface} does not exist!') + # local ippool and gateway settings config checks if not (dict_search('client_ip_pool.subnet', pppoe) or (dict_search('client_ip_pool.start', pppoe) and @@ -73,6 +81,13 @@ def generate(pppoe): if not pppoe: return None + # Generate special regex for dynamic interfaces + for iface in pppoe['interface']: + if 'vlan_range' in pppoe['interface'][iface]: + pppoe['interface'][iface]['regex'] = [] + for vlan_range in pppoe['interface'][iface]['vlan_range']: + pppoe['interface'][iface]['regex'].append(range_to_regex(vlan_range)) + render(pppoe_conf, 'accel-ppp/pppoe.config.tmpl', pppoe) if dict_search('authentication.mode', pppoe) == 'local': diff --git a/src/conf_mode/system-login-banner.py b/src/conf_mode/system-login-banner.py index 2220d7b66..9642b2aae 100755 --- a/src/conf_mode/system-login-banner.py +++ b/src/conf_mode/system-login-banner.py @@ -15,13 +15,16 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from sys import exit + from vyos.config import Config +from vyos.util import write_file from vyos import ConfigError - from vyos import airbag airbag.enable() motd=""" +Welcome to VyOS + Check out project news at https://blog.vyos.io and feel free to report bugs at https://phabricator.vyos.net @@ -38,7 +41,7 @@ POSTLOGIN_FILE = r'/etc/motd' default_config_data = { 'issue': 'Welcome to VyOS - \\n \\l\n\n', - 'issue_net': 'Welcome to VyOS\n', + 'issue_net': '', 'motd': motd } @@ -92,14 +95,9 @@ def generate(banner): pass def apply(banner): - with open(PRELOGIN_FILE, 'w') as f: - f.write(banner['issue']) - - with open(PRELOGIN_NET_FILE, 'w') as f: - f.write(banner['issue_net']) - - with open(POSTLOGIN_FILE, 'w') as f: - f.write(banner['motd']) + write_file(PRELOGIN_FILE, banner['issue']) + write_file(PRELOGIN_NET_FILE, banner['issue_net']) + write_file(POSTLOGIN_FILE, banner['motd']) return None diff --git a/src/helpers/vyos-boot-config-loader.py b/src/helpers/vyos-boot-config-loader.py index c5bf22f10..b9cc87bfa 100755 --- a/src/helpers/vyos-boot-config-loader.py +++ b/src/helpers/vyos-boot-config-loader.py @@ -23,12 +23,12 @@ import grp import traceback from datetime import datetime -from vyos.defaults import directories +from vyos.defaults import directories, config_status from vyos.configsession import ConfigSession, ConfigSessionError from vyos.configtree import ConfigTree from vyos.util import cmd -STATUS_FILE = '/tmp/vyos-config-status' +STATUS_FILE = config_status TRACE_FILE = '/tmp/boot-config-trace' CFG_GROUP = 'vyattacfg' diff --git a/src/helpers/vyos-check-wwan.py b/src/helpers/vyos-check-wwan.py index c6e6c54b7..2ff9a574f 100755 --- a/src/helpers/vyos-check-wwan.py +++ b/src/helpers/vyos-check-wwan.py @@ -18,7 +18,6 @@ from vyos.configquery import VbashOpRun from vyos.configquery import ConfigTreeQuery from vyos.util import is_wwan_connected -from vyos.util import call conf = ConfigTreeQuery() dict = conf.get_config_dict(['interfaces', 'wwan'], key_mangling=('-', '_'), @@ -30,8 +29,7 @@ for interface, interface_config in dict.items(): # do not restart this interface as it's disabled by the user continue - #op = VbashOpRun() - #op.run(['connect', 'interface', interface]) - call(f'VYOS_TAGNODE_VALUE={interface} /usr/libexec/vyos/conf_mode/interfaces-wwan.py') + op = VbashOpRun() + op.run(['connect', 'interface', interface]) exit(0) diff --git a/src/helpers/vyos_net_name b/src/helpers/vyos_net_name index e21d8c9ff..afeef8f2d 100755 --- a/src/helpers/vyos_net_name +++ b/src/helpers/vyos_net_name @@ -25,14 +25,13 @@ from sys import argv from vyos.configtree import ConfigTree from vyos.defaults import directories -from vyos.util import cmd +from vyos.util import cmd, boot_configuration_complete 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() @@ -43,13 +42,6 @@ except FileExistsError: 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 """ diff --git a/src/op_mode/format_disk.py b/src/op_mode/format_disk.py index df4486bce..b3ba44e87 100755 --- a/src/op_mode/format_disk.py +++ b/src/op_mode/format_disk.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 @@ -17,11 +17,10 @@ import argparse import os import re -import sys + from datetime import datetime -from time import sleep -from vyos.util import is_admin, ask_yes_no +from vyos.util import ask_yes_no from vyos.util import call from vyos.util import cmd from vyos.util import DEVNULL @@ -38,16 +37,17 @@ def list_disks(): def is_busy(disk: str): """Check if given disk device is busy by re-reading it's partition table""" - return call(f'sudo blockdev --rereadpt /dev/{disk}', stderr=DEVNULL) != 0 + return call(f'blockdev --rereadpt /dev/{disk}', stderr=DEVNULL) != 0 def backup_partitions(disk: str): """Save sfdisk partitions output to a backup file""" - device_path = '/dev/' + disk - backup_ts = datetime.now().strftime('%Y-%m-%d-%H:%M') - backup_file = '/var/tmp/backup_{}.{}'.format(disk, backup_ts) - cmd(f'sudo /sbin/sfdisk -d {device_path} > {backup_file}') + device_path = f'/dev/{disk}' + backup_ts = datetime.now().strftime('%Y%m%d-%H%M') + backup_file = f'/var/tmp/backup_{disk}.{backup_ts}' + call(f'sfdisk -d {device_path} > {backup_file}') + print(f'Partition table backup saved to {backup_file}') def list_partitions(disk: str): @@ -65,11 +65,11 @@ def list_partitions(disk: str): def delete_partition(disk: str, partition_idx: int): - cmd(f'sudo /sbin/parted /dev/{disk} rm {partition_idx}') + cmd(f'parted /dev/{disk} rm {partition_idx}') def format_disk_like(target: str, proto: str): - cmd(f'sudo /sbin/sfdisk -d /dev/{proto} | sudo /sbin/sfdisk --force /dev/{target}') + cmd(f'sfdisk -d /dev/{proto} | sfdisk --force /dev/{target}') if __name__ == '__main__': @@ -79,10 +79,6 @@ if __name__ == '__main__': group.add_argument('-p', '--proto', type=str, required=True, help='Prototype device to use as reference') args = parser.parse_args() - if not is_admin(): - print('Must be admin or root to format disk') - sys.exit(1) - target_disk = args.target eligible_target_disks = list_disks() @@ -90,54 +86,48 @@ if __name__ == '__main__': eligible_proto_disks = eligible_target_disks.copy() eligible_proto_disks.remove(target_disk) - fmt = { - 'target_disk': target_disk, - 'proto_disk': proto_disk, - } - if proto_disk == target_disk: print('The two disk drives must be different.') - sys.exit(1) + exit(1) - if not os.path.exists('/dev/' + proto_disk): - print('Device /dev/{proto_disk} does not exist'.format_map(fmt)) - sys.exit(1) + if not os.path.exists(f'/dev/{proto_disk}'): + print(f'Device /dev/{proto_disk} does not exist') + exit(1) if not os.path.exists('/dev/' + target_disk): - print('Device /dev/{target_disk} does not exist'.format_map(fmt)) - sys.exit(1) + print(f'Device /dev/{target_disk} does not exist') + exit(1) if target_disk not in eligible_target_disks: - print('Device {target_disk} can not be formatted'.format_map(fmt)) - sys.exit(1) + print(f'Device {target_disk} can not be formatted') + exit(1) if proto_disk not in eligible_proto_disks: - print('Device {proto_disk} can not be used as a prototype for {target_disk}'.format_map(fmt)) - sys.exit(1) + print(f'Device {proto_disk} can not be used as a prototype for {target_disk}') + exit(1) if is_busy(target_disk): - print("Disk device {target_disk} is busy. Can't format it now".format_map(fmt)) - sys.exit(1) + print(f'Disk device {target_disk} is busy, unable to format') + exit(1) - print('This will re-format disk {target_disk} so that it has the same disk\n' - 'partion sizes and offsets as {proto_disk}. This will not copy\n' - 'data from {proto_disk} to {target_disk}. But this will erase all\n' - 'data on {target_disk}.\n'.format_map(fmt)) + print(f'\nThis will re-format disk {target_disk} so that it has the same disk' + f'\npartion sizes and offsets as {proto_disk}. This will not copy' + f'\ndata from {proto_disk} to {target_disk}. But this will erase all' + f'\ndata on {target_disk}.\n') - if not ask_yes_no("Do you wish to proceed?"): - print('OK. Disk drive {target_disk} will not be re-formated'.format_map(fmt)) - sys.exit(0) + if not ask_yes_no('Do you wish to proceed?'): + print(f'Disk drive {target_disk} will not be re-formated') + exit(0) - print('OK. Re-formating disk drive {target_disk}...'.format_map(fmt)) + print(f'Re-formating disk drive {target_disk}...') print('Making backup copy of partitions...') backup_partitions(target_disk) - sleep(1) print('Deleting old partitions...') for p in list_partitions(target_disk): delete_partition(disk=target_disk, partition_idx=p) - print('Creating new partitions on {target_disk} based on {proto_disk}...'.format_map(fmt)) + print(f'Creating new partitions on {target_disk} based on {proto_disk}...') format_disk_like(target=target_disk, proto=proto_disk) - print('Done.') + print('Done!') diff --git a/src/op_mode/ppp-server-ctrl.py b/src/op_mode/ppp-server-ctrl.py index 670cdf879..e93963fdd 100755 --- a/src/op_mode/ppp-server-ctrl.py +++ b/src/op_mode/ppp-server-ctrl.py @@ -60,7 +60,7 @@ def main(): output, err = popen(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][args.proto]) + args.action + ses_pattern, stderr=DEVNULL, decode='utf-8') if not err: try: - print(output) + print(f' {output}') except: sys.exit(0) else: diff --git a/src/services/api/graphql/README.graphql b/src/services/api/graphql/README.graphql index a3c30b005..1133d79ed 100644 --- a/src/services/api/graphql/README.graphql +++ b/src/services/api/graphql/README.graphql @@ -1,7 +1,12 @@ +The following examples are in the form as entered in the GraphQL +'playground', which is found at: + +https://{{ host_address }}/graphql + Example using GraphQL mutations to configure a DHCP server: -This assumes that the http-api is running: +All examples assume that the http-api is running: 'set service https api' @@ -58,8 +63,8 @@ N.B. fileName can be empty (fileName: "") or data can be empty (data: {}) to save to /config/config.boot; to save to an alternative path, specify fileName. -Similarly, using the same 'endpoint' (meaning the form of the request and -resolver; the actual enpoint for all GraphQL requests is +Similarly, using an analogous 'endpoint' (meaning the form of the request +and resolver; the actual enpoint for all GraphQL requests is https://hostname/graphql), one can load an arbitrary config file from a path. @@ -75,7 +80,7 @@ mutation { Op-mode 'show' commands may be requested by path, e.g.: -mutation { +query { Show (data: {path: ["interfaces", "ethernet", "detail"]}) { success errors @@ -88,16 +93,58 @@ mutation { N.B. to see the output the 'data' field 'result' must be present in the request. -The GraphQL playground will be found at: +Mutations to manipulate firewall address groups: -https://{{ host_address }}/graphql +mutation { + CreateFirewallAddressGroup (data: {name: "ADDR-GRP", address: "10.0.0.1"}) { + success + errors + } +} + +mutation { + UpdateFirewallAddressGroupMembers (data: {name: "ADDR-GRP", + address: ["10.0.0.1-10.0.0.8", "192.168.0.1"]}) { + success + errors + } +} + +mutation { + RemoveFirewallAddressGroupMembers (data: {name: "ADDR-GRP", + address: "192.168.0.1"}) { + success + errors + } +} + +N.B. The schema for the above specify that 'address' be of the form 'list of +strings' (SDL type [String!]! for UpdateFirewallAddressGroupMembers, where +the ! indicates that the input is required; SDL type [String] in +CreateFirewallAddressGroup, since a group may be created without any +addresses). However, notice that a single string may be passed without being +a member of a list, in which case the specification allows for 'input +coercion': + +http://spec.graphql.org/October2021/#sec-Scalars.Input-Coercion + +Similarly, IPv6 versions of the above: -An equivalent curl command to the first example above would be: +CreateFirewallAddressIpv6Group +UpdateFirewallAddressIpv6GroupMembers +RemoveFirewallAddressIpv6GroupMembers + + +Instead of using the GraphQL playground, an equivalent curl command to the +first example above would be: curl -k 'https://192.168.100.168/graphql' -H 'Content-Type: application/json' --data-binary '{"query": "mutation {createInterfaceEthernet (data: {interface: \"eth1\", address: \"192.168.0.1/24\", description: \"BOB\"}) {success errors data {address}}}"}' Note that the 'mutation' term is prefaced by 'query' in the curl command. +Curl equivalents may be read from within the GraphQL playground at the 'copy +curl' button. + What's here: services diff --git a/src/services/api/graphql/bindings.py b/src/services/api/graphql/bindings.py index 1fbe13d0c..84d719fda 100644 --- a/src/services/api/graphql/bindings.py +++ b/src/services/api/graphql/bindings.py @@ -1,4 +1,20 @@ +# Copyright 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 +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + import vyos.defaults +from . graphql.queries import query from . graphql.mutations import mutation from . graphql.directives import directives_dict from ariadne import make_executable_schema, load_schema_from_path, snake_case_fallback_resolvers @@ -8,6 +24,6 @@ def generate_schema(): type_defs = load_schema_from_path(api_schema_dir) - schema = make_executable_schema(type_defs, mutation, snake_case_fallback_resolvers, directives=directives_dict) + schema = make_executable_schema(type_defs, query, mutation, snake_case_fallback_resolvers, directives=directives_dict) return schema diff --git a/src/services/api/graphql/graphql/directives.py b/src/services/api/graphql/graphql/directives.py index 10bc522db..0a9298f55 100644 --- a/src/services/api/graphql/graphql/directives.py +++ b/src/services/api/graphql/graphql/directives.py @@ -1,4 +1,20 @@ +# Copyright 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 +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + from ariadne import SchemaDirectiveVisitor, ObjectType +from . queries import * from . mutations import * def non(arg): diff --git a/src/services/api/graphql/graphql/mutations.py b/src/services/api/graphql/graphql/mutations.py index 8e5aab56d..0c3eb702a 100644 --- a/src/services/api/graphql/graphql/mutations.py +++ b/src/services/api/graphql/graphql/mutations.py @@ -1,3 +1,17 @@ +# Copyright 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 +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. from importlib import import_module from typing import Any, Dict @@ -10,7 +24,7 @@ from api.graphql.recipes.session import Session mutation = ObjectType("Mutation") -def make_resolver(mutation_name, class_name, session_func): +def make_mutation_resolver(mutation_name, class_name, session_func): """Dynamically generate a resolver for the mutation named in the schema by 'mutation_name'. @@ -66,34 +80,20 @@ def make_resolver(mutation_name, class_name, session_func): return func_impl -def make_configure_resolver(mutation_name): - class_name = mutation_name - return make_resolver(mutation_name, class_name, 'configure') +def make_prefix_resolver(mutation_name, prefix=[]): + for pre in prefix: + Pre = pre.capitalize() + if Pre in mutation_name: + class_name = mutation_name.replace(Pre, '', 1) + return make_mutation_resolver(mutation_name, class_name, pre) + raise Exception -def make_show_config_resolver(mutation_name): +def make_configure_resolver(mutation_name): class_name = mutation_name - return make_resolver(mutation_name, class_name, 'show_config') + return make_mutation_resolver(mutation_name, class_name, 'configure') def make_config_file_resolver(mutation_name): - if 'Save' in mutation_name: - class_name = mutation_name.replace('Save', '', 1) - return make_resolver(mutation_name, class_name, 'save') - elif 'Load' in mutation_name: - class_name = mutation_name.replace('Load', '', 1) - return make_resolver(mutation_name, class_name, 'load') - else: - raise Exception - -def make_show_resolver(mutation_name): - class_name = mutation_name - return make_resolver(mutation_name, class_name, 'show') + return make_prefix_resolver(mutation_name, prefix=['save', 'load']) def make_image_resolver(mutation_name): - if 'Add' in mutation_name: - class_name = mutation_name.replace('Add', '', 1) - return make_resolver(mutation_name, class_name, 'add') - elif 'Delete' in mutation_name: - class_name = mutation_name.replace('Delete', '', 1) - return make_resolver(mutation_name, class_name, 'delete') - else: - raise Exception + return make_prefix_resolver(mutation_name, prefix=['add', 'delete']) diff --git a/src/services/api/graphql/graphql/queries.py b/src/services/api/graphql/graphql/queries.py new file mode 100644 index 000000000..e1868091e --- /dev/null +++ b/src/services/api/graphql/graphql/queries.py @@ -0,0 +1,89 @@ +# Copyright 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 +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +from importlib import import_module +from typing import Any, Dict +from ariadne import ObjectType, convert_kwargs_to_snake_case, convert_camel_case_to_snake +from graphql import GraphQLResolveInfo +from makefun import with_signature + +from .. import state +from api.graphql.recipes.session import Session + +query = ObjectType("Query") + +def make_query_resolver(query_name, class_name, session_func): + """Dynamically generate a resolver for the query named in the + schema by 'query_name'. + + Dynamic generation is provided using the package 'makefun' (via the + decorator 'with_signature'), which provides signature-preserving + function wrappers; it provides several improvements over, say, + functools.wraps. + + :raise Exception: + raising ConfigErrors, or internal errors + """ + + func_base_name = convert_camel_case_to_snake(class_name) + resolver_name = f'resolve_{func_base_name}' + func_sig = '(obj: Any, info: GraphQLResolveInfo, data: Dict)' + + @query.field(query_name) + @convert_kwargs_to_snake_case + @with_signature(func_sig, func_name=resolver_name) + async def func_impl(*args, **kwargs): + try: + if 'data' not in kwargs: + return { + "success": False, + "errors": ['missing data'] + } + + data = kwargs['data'] + session = state.settings['app'].state.vyos_session + + # one may override the session functions with a local subclass + try: + mod = import_module(f'api.graphql.recipes.{func_base_name}') + klass = getattr(mod, class_name) + except ImportError: + # otherwise, dynamically generate subclass to invoke subclass + # name based templates + klass = type(class_name, (Session,), {}) + k = klass(session, data) + method = getattr(k, session_func) + result = method() + data['result'] = result + + return { + "success": True, + "data": data + } + except Exception as error: + return { + "success": False, + "errors": [str(error)] + } + + return func_impl + +def make_show_config_resolver(query_name): + class_name = query_name + return make_query_resolver(query_name, class_name, 'show_config') + +def make_show_resolver(query_name): + class_name = query_name + return make_query_resolver(query_name, class_name, 'show') diff --git a/src/services/api/graphql/graphql/schema/firewall_group.graphql b/src/services/api/graphql/graphql/schema/firewall_group.graphql index efe7de632..d89904b9e 100644 --- a/src/services/api/graphql/graphql/schema/firewall_group.graphql +++ b/src/services/api/graphql/graphql/schema/firewall_group.graphql @@ -45,3 +45,51 @@ type RemoveFirewallAddressGroupMembersResult { success: Boolean! errors: [String] } + +input CreateFirewallAddressIpv6GroupInput { + name: String! + address: [String] +} + +type CreateFirewallAddressIpv6Group { + name: String! + address: [String] +} + +type CreateFirewallAddressIpv6GroupResult { + data: CreateFirewallAddressIpv6Group + success: Boolean! + errors: [String] +} + +input UpdateFirewallAddressIpv6GroupMembersInput { + name: String! + address: [String!]! +} + +type UpdateFirewallAddressIpv6GroupMembers { + name: String! + address: [String!]! +} + +type UpdateFirewallAddressIpv6GroupMembersResult { + data: UpdateFirewallAddressIpv6GroupMembers + success: Boolean! + errors: [String] +} + +input RemoveFirewallAddressIpv6GroupMembersInput { + name: String! + address: [String!]! +} + +type RemoveFirewallAddressIpv6GroupMembers { + name: String! + address: [String!]! +} + +type RemoveFirewallAddressIpv6GroupMembersResult { + data: RemoveFirewallAddressIpv6GroupMembers + success: Boolean! + errors: [String] +} diff --git a/src/services/api/graphql/graphql/schema/schema.graphql b/src/services/api/graphql/graphql/schema/schema.graphql index c6899bee6..952e46f34 100644 --- a/src/services/api/graphql/graphql/schema/schema.graphql +++ b/src/services/api/graphql/graphql/schema/schema.graphql @@ -3,26 +3,28 @@ schema { mutation: Mutation } -type Query { - _dummy: String -} - directive @configure on FIELD_DEFINITION directive @configfile on FIELD_DEFINITION directive @show on FIELD_DEFINITION directive @showconfig on FIELD_DEFINITION directive @image on FIELD_DEFINITION +type Query { + Show(data: ShowInput) : ShowResult @show + ShowConfig(data: ShowConfigInput) : ShowConfigResult @showconfig +} + type Mutation { CreateDhcpServer(data: DhcpServerConfigInput) : CreateDhcpServerResult @configure CreateInterfaceEthernet(data: InterfaceEthernetConfigInput) : CreateInterfaceEthernetResult @configure CreateFirewallAddressGroup(data: CreateFirewallAddressGroupInput) : CreateFirewallAddressGroupResult @configure UpdateFirewallAddressGroupMembers(data: UpdateFirewallAddressGroupMembersInput) : UpdateFirewallAddressGroupMembersResult @configure RemoveFirewallAddressGroupMembers(data: RemoveFirewallAddressGroupMembersInput) : RemoveFirewallAddressGroupMembersResult @configure + CreateFirewallAddressIpv6Group(data: CreateFirewallAddressIpv6GroupInput) : CreateFirewallAddressIpv6GroupResult @configure + UpdateFirewallAddressIpv6GroupMembers(data: UpdateFirewallAddressIpv6GroupMembersInput) : UpdateFirewallAddressIpv6GroupMembersResult @configure + RemoveFirewallAddressIpv6GroupMembers(data: RemoveFirewallAddressIpv6GroupMembersInput) : RemoveFirewallAddressIpv6GroupMembersResult @configure SaveConfigFile(data: SaveConfigFileInput) : SaveConfigFileResult @configfile LoadConfigFile(data: LoadConfigFileInput) : LoadConfigFileResult @configfile - Show(data: ShowInput) : ShowResult @show - ShowConfig(data: ShowConfigInput) : ShowConfigResult @showconfig AddSystemImage(data: AddSystemImageInput) : AddSystemImageResult @image DeleteSystemImage(data: DeleteSystemImageInput) : DeleteSystemImageResult @image } diff --git a/src/services/api/graphql/recipes/remove_firewall_address_group_members.py b/src/services/api/graphql/recipes/remove_firewall_address_group_members.py index cde30c27a..b91932e14 100644 --- a/src/services/api/graphql/recipes/remove_firewall_address_group_members.py +++ b/src/services/api/graphql/recipes/remove_firewall_address_group_members.py @@ -1,3 +1,17 @@ +# Copyright 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 +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. from . session import Session diff --git a/src/services/api/graphql/recipes/session.py b/src/services/api/graphql/recipes/session.py index 5ece78ee6..1f844ff70 100644 --- a/src/services/api/graphql/recipes/session.py +++ b/src/services/api/graphql/recipes/session.py @@ -1,3 +1,18 @@ +# Copyright 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 +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + import json from ariadne import convert_camel_case_to_snake diff --git a/src/services/api/graphql/recipes/templates/create_firewall_address_ipv_6_group.tmpl b/src/services/api/graphql/recipes/templates/create_firewall_address_ipv_6_group.tmpl new file mode 100644 index 000000000..e9b660722 --- /dev/null +++ b/src/services/api/graphql/recipes/templates/create_firewall_address_ipv_6_group.tmpl @@ -0,0 +1,4 @@ +set firewall group ipv6-address-group {{ name }} +{% for add in address %} +set firewall group ipv6-address-group {{ name }} address {{ add }} +{% endfor %} diff --git a/src/services/api/graphql/recipes/templates/remove_firewall_address_ipv_6_group_members.tmpl b/src/services/api/graphql/recipes/templates/remove_firewall_address_ipv_6_group_members.tmpl new file mode 100644 index 000000000..0efa0b226 --- /dev/null +++ b/src/services/api/graphql/recipes/templates/remove_firewall_address_ipv_6_group_members.tmpl @@ -0,0 +1,3 @@ +{% for add in address %} +delete firewall group ipv6-address-group {{ name }} address {{ add }} +{% endfor %} diff --git a/src/services/api/graphql/recipes/templates/update_firewall_address_ipv_6_group_members.tmpl b/src/services/api/graphql/recipes/templates/update_firewall_address_ipv_6_group_members.tmpl new file mode 100644 index 000000000..f98a5517c --- /dev/null +++ b/src/services/api/graphql/recipes/templates/update_firewall_address_ipv_6_group_members.tmpl @@ -0,0 +1,3 @@ +{% for add in address %} +set firewall group ipv6-address-group {{ name }} address {{ add }} +{% endfor %} diff --git a/src/services/vyos-configd b/src/services/vyos-configd index 670b6e66a..48c9135e2 100755 --- a/src/services/vyos-configd +++ b/src/services/vyos-configd @@ -28,6 +28,7 @@ import zmq from contextlib import contextmanager from vyos.defaults import directories +from vyos.util import boot_configuration_complete from vyos.configsource import ConfigSourceString, ConfigSourceError from vyos.config import Config from vyos import ConfigError @@ -186,7 +187,7 @@ def initialization(socket): session_out = None # if not a 'live' session, for example on boot, write to file - if not session_out or not os.path.isfile('/tmp/vyos-config-status'): + if not session_out or not boot_configuration_complete(): session_out = script_stdout_log session_mode = 'a' diff --git a/src/services/vyos-hostsd b/src/services/vyos-hostsd index f4b1d0fc2..df9f18d2d 100755 --- a/src/services/vyos-hostsd +++ b/src/services/vyos-hostsd @@ -139,6 +139,27 @@ # } # # +### authoritative_zones +## Additional zones hosted authoritatively by pdns-recursor. +## We add NTAs for these zones but do not do much else here. +# +# { 'type': 'authoritative_zones', +# 'op': 'add', +# 'data': ['<str zone>', ...] +# } +# +# { 'type': 'authoritative_zones', +# 'op': 'delete', +# 'data': ['<str zone>', ...] +# } +# +# { 'type': 'authoritative_zones', +# 'op': 'get', +# } +# response: +# { 'data': ['<str zone>', ...] } +# +# ### search_domains # # { 'type': 'search_domains', @@ -255,6 +276,7 @@ STATE = { "name_server_tags_recursor": [], "name_server_tags_system": [], "forward_zones": {}, + "authoritative_zones": [], "hosts": {}, "host_name": "vyos", "domain_name": "", @@ -267,7 +289,8 @@ base_schema = Schema({ Required('op'): Any('add', 'delete', 'set', 'get', 'apply'), 'type': Any('name_servers', 'name_server_tags_recursor', 'name_server_tags_system', - 'forward_zones', 'search_domains', 'hosts', 'host_name'), + 'forward_zones', 'authoritative_zones', 'search_domains', + 'hosts', 'host_name'), 'data': Any(list, dict), 'tag': str, 'tag_regex': str @@ -347,6 +370,11 @@ msg_schema_map = { 'delete': data_list_schema, 'get': op_type_schema }, + 'authoritative_zones': { + 'add': data_list_schema, + 'delete': data_list_schema, + 'get': op_type_schema + }, 'search_domains': { 'add': data_dict_list_schema, 'delete': data_list_schema, @@ -522,7 +550,7 @@ def handle_message(msg): data = get_option(msg, 'data') if _type in ['name_servers', 'forward_zones', 'search_domains', 'hosts']: delete_items_from_dict(STATE[_type], data) - elif _type in ['name_server_tags_recursor', 'name_server_tags_system']: + elif _type in ['name_server_tags_recursor', 'name_server_tags_system', 'authoritative_zones']: delete_items_from_list(STATE[_type], data) else: raise ValueError(f'Operation "{op}" unknown data type "{_type}"') @@ -534,7 +562,7 @@ def handle_message(msg): elif _type in ['forward_zones', 'hosts']: add_items_to_dict(STATE[_type], data) # maybe we need to rec_control clear-nta each domain that was removed here? - elif _type in ['name_server_tags_recursor', 'name_server_tags_system']: + elif _type in ['name_server_tags_recursor', 'name_server_tags_system', 'authoritative_zones']: add_items_to_list(STATE[_type], data) else: raise ValueError(f'Operation "{op}" unknown data type "{_type}"') @@ -550,7 +578,7 @@ def handle_message(msg): if _type in ['name_servers', 'search_domains', 'hosts']: tag_regex = get_option(msg, 'tag_regex') result = get_items_from_dict_regex(STATE[_type], tag_regex) - elif _type in ['name_server_tags_recursor', 'name_server_tags_system', 'forward_zones']: + elif _type in ['name_server_tags_recursor', 'name_server_tags_system', 'forward_zones', 'authoritative_zones']: result = STATE[_type] else: raise ValueError(f'Operation "{op}" unknown data type "{_type}"') diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index aa7ac6708..06871f1d6 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -32,6 +32,7 @@ from fastapi.responses import HTMLResponse from fastapi.exceptions import RequestValidationError from fastapi.routing import APIRoute from pydantic import BaseModel, StrictStr, validator +from starlette.middleware.cors import CORSMiddleware from starlette.datastructures import FormData from starlette.formparsers import FormParser, MultiPartParser from multipart.multipart import parse_options_header @@ -610,13 +611,19 @@ def show_op(data: ShowModel): # GraphQL integration ### -from api.graphql.bindings import generate_schema +def graphql_init(fast_api_app): + from api.graphql.bindings import generate_schema -api.graphql.state.init() + api.graphql.state.init() + api.graphql.state.settings['app'] = app -schema = generate_schema() + schema = generate_schema() -app.add_route('/graphql', GraphQL(schema, debug=True)) + if app.state.vyos_origins: + origins = app.state.vyos_origins + app.add_route('/graphql', CORSMiddleware(GraphQL(schema, debug=True), allow_origins=origins, allow_methods=("GET", "POST", "OPTIONS"))) + else: + app.add_route('/graphql', GraphQL(schema, debug=True)) ### @@ -640,15 +647,20 @@ if __name__ == '__main__': app.state.vyos_session = config_session app.state.vyos_keys = server_config['api_keys'] - app.state.vyos_debug = bool(server_config['debug'] == 'true') - app.state.vyos_strict = bool(server_config['strict'] == 'true') + app.state.vyos_debug = server_config['debug'] + app.state.vyos_strict = server_config['strict'] + app.state.vyos_origins = server_config.get('cors', {}).get('origins', []) - api.graphql.state.settings['app'] = app + graphql_init(app) try: - uvicorn.run(app, host=server_config["listen_address"], - port=int(server_config["port"]), - proxy_headers=True) + if not server_config['socket']: + uvicorn.run(app, host=server_config["listen_address"], + port=int(server_config["port"]), + proxy_headers=True) + else: + uvicorn.run(app, uds="/run/api.sock", + proxy_headers=True) except OSError as err: logger.critical(f"OSError {err}") sys.exit(1) diff --git a/src/system/keepalived-fifo.py b/src/system/keepalived-fifo.py index 1fba0d75b..b1fe7e43f 100755 --- a/src/system/keepalived-fifo.py +++ b/src/system/keepalived-fifo.py @@ -29,6 +29,7 @@ from logging.handlers import SysLogHandler from vyos.ifconfig.vrrp import VRRP from vyos.configquery import ConfigTreeQuery from vyos.util import cmd +from vyos.util import dict_search # configure logging logger = logging.getLogger(__name__) @@ -69,22 +70,10 @@ class KeepalivedFifo: 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': {}} - 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}') + self.vrrp_config_dict = conf.get_config_dict(base, + key_mangling=('-', '_'), get_first_key=True) + + logger.debug(f'Loaded configuration: {self.vrrp_config_dict}') except Exception as err: logger.error(f'Unable to load configuration: {err}') @@ -129,20 +118,17 @@ class KeepalivedFifo: if os.path.exists(mdns_running_file): cmd(mdns_update_command) - if n_name in self.vrrp_config['vrrp_groups'] and n_state in self.vrrp_config['vrrp_groups'][n_name]: - n_script = self.vrrp_config['vrrp_groups'][n_name].get(n_state) - if n_script: - self._run_command(n_script) + tmp = dict_search(f'group.{n_name}.transition_script.{n_state.lower()}', self.vrrp_config_dict) + if tmp != None: + self._run_command(tmp) # check and run commands for VRRP sync groups - # currently, this is not available in VyOS CLI - if n_type == 'GROUP': + elif 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: - self._run_command(n_script) + tmp = dict_search(f'sync_group.{n_name}.transition_script.{n_state.lower()}', self.vrrp_config_dict) + if tmp != None: + self._run_command(tmp) # mark task in queue as done self.message_queue.task_done() except Exception as err: diff --git a/src/validators/bgp-route-target b/src/validators/bgp-rd-rt index e7e4d403f..b2b69c9be 100755 --- a/src/validators/bgp-route-target +++ b/src/validators/bgp-rd-rt @@ -19,29 +19,37 @@ 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') +group.add_argument('--route-distinguisher', action='store', help='Validate BGP route distinguisher') +group.add_argument('--route-target', action='store', help='Validate one BGP route-target') +group.add_argument('--route-target-multi', action='store', help='Validate multiple, whitespace separated BGP 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 +def is_valid(rt): + """ Verify BGP RD/RT - both can be verified using the same logic """ + # every RD/RT (route distinguisher/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 + + # An RD/RT 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): + if args.route_distinguisher: + if not is_valid(args.route_distinguisher): + exit(1) + + elif args.route_target: + if not is_valid(args.route_target): exit(1) - elif args.multi: - for rt in args.multi.split(' '): - if not is_valid_rt(rt): + elif args.route_target_multi: + for rt in args.route_target_multi.split(' '): + if not is_valid(rt): exit(1) else: diff --git a/src/validators/ipv4-multicast b/src/validators/ipv4-multicast index e5cbc9532..5465c728d 100755 --- a/src/validators/ipv4-multicast +++ b/src/validators/ipv4-multicast @@ -1,3 +1,3 @@ #!/bin/sh -ipaddrcheck --is-ipv4-multicast $1 +ipaddrcheck --is-ipv4-multicast $1 && ipaddrcheck --is-ipv4-single $1 diff --git a/src/validators/ipv6-multicast b/src/validators/ipv6-multicast index 66cd90c9c..5afc437e5 100755 --- a/src/validators/ipv6-multicast +++ b/src/validators/ipv6-multicast @@ -1,3 +1,3 @@ #!/bin/sh -ipaddrcheck --is-ipv6-multicast $1 +ipaddrcheck --is-ipv6-multicast $1 && ipaddrcheck --is-ipv6-single $1 diff --git a/src/validators/range b/src/validators/range new file mode 100755 index 000000000..d4c25f3c4 --- /dev/null +++ b/src/validators/range @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import re +import sys +import argparse + +class MalformedRange(Exception): + pass + +def validate_range(value, min=None, max=None): + try: + lower, upper = re.match(r'^(\d+)-(\d+)$', value).groups() + + lower, upper = int(lower), int(upper) + + if int(lower) > int(upper): + raise MalformedRange("the lower bound exceeds the upper bound".format(value)) + + if min is not None: + if lower < min: + raise MalformedRange("the lower bound must not be less than {}".format(min)) + + if max is not None: + if upper > max: + raise MalformedRange("the upper bound must not be greater than {}".format(max)) + + except (AttributeError, ValueError): + raise MalformedRange("range syntax error") + +parser = argparse.ArgumentParser(description='Range validator.') +parser.add_argument('--min', type=int, action='store') +parser.add_argument('--max', type=int, action='store') +parser.add_argument('value', action='store') + +if __name__ == '__main__': + args = parser.parse_args() + + try: + validate_range(args.value, min=args.min, max=args.max) + except MalformedRange as e: + print("Incorrect range '{}': {}".format(args.value, e)) + sys.exit(1) diff --git a/src/validators/script b/src/validators/script index 1d8a27e5c..4ffdeb2a0 100755 --- a/src/validators/script +++ b/src/validators/script @@ -36,7 +36,7 @@ if __name__ == '__main__': # File outside the config dir is just a warning if not vyos.util.file_is_persistent(script): - sys.exit( - f'Warning: file {path} is outside the / config directory\n' + sys.exit(0)( + f'Warning: file {script} is outside the "/config" directory\n' 'It will not be automatically migrated to a new image on system update' ) |