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'          )  | 
