diff options
43 files changed, 863 insertions, 267 deletions
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/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/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/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..d26cd5e7a 100644 --- a/interface-definitions/https.xml.in +++ b/interface-definitions/https.xml.in @@ -143,6 +143,7 @@ </node> </children> </node> + #include <include/interface/vrf.xml.i> </children> </node> </children> diff --git a/interface-definitions/include/bfd-common.xml.i b/interface-definitions/include/bfd-common.xml.i index 1d6ab5d55..8379784f7 100644 --- a/interface-definitions/include/bfd-common.xml.i +++ b/interface-definitions/include/bfd-common.xml.i @@ -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/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/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/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/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/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..d5a968001 100644 --- a/interface-definitions/protocols-bfd.xml.in +++ b/interface-definitions/protocols-bfd.xml.in @@ -73,6 +73,7 @@ <valueless/> </properties> </leafNode> + #include <include/interface/vrf.xml.i> </children> </tagNode> <tagNode name="profile"> 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/bfd-common.xml.i b/op-mode-definitions/include/bfd-common.xml.i new file mode 100644 index 000000000..eebfbdad4 --- /dev/null +++ b/op-mode-definitions/include/bfd-common.xml.i @@ -0,0 +1,54 @@ +<!-- included start from bfd-common.xml.i --> +<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> + <node name="peers"> + <properties> + <help>Show Bidirectional Forwarding Detection peers</help> + </properties> + <command>vtysh -c "show bfd peers"</command> + <children> + <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> +<!-- 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..7339c92a2 --- /dev/null +++ b/op-mode-definitions/show-bfd.xml.in @@ -0,0 +1,8 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + #include <include/bfd-common.xml.i> + </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-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..48bfa10dc 100644 --- a/op-mode-definitions/show-protocols.xml.in +++ b/op-mode-definitions/show-protocols.xml.in @@ -7,50 +7,7 @@ <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> + #include <include/bfd-common.xml.i> <node name="static"> <properties> <help>Show static protocol parameters</help> 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/util.py b/python/vyos/util.py index d8e83ab8d..157b26bf7 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 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_protocols_bfd.py b/smoketest/scripts/cli/test_protocols_bfd.py index 46a019dfc..d234750b4 100755 --- a/smoketest/scripts/cli/test_protocols_bfd.py +++ b/smoketest/scripts/cli/test_protocols_bfd.py @@ -24,6 +24,7 @@ PROCESS_NAME = 'bfdd' base_path = ['protocols', 'bfd'] dum_if = 'dum1001' +vrf_name = 'red' peers = { '192.0.2.10' : { 'intv_rx' : '500', @@ -37,12 +38,14 @@ peers = { 'intv_mult' : '100', 'intv_rx' : '222', 'intv_tx' : '333', + 'passive' : '', 'shutdown' : '', + 'profile' : 'foo', 'source_intf': dum_if, }, '2001:db8::a' : { 'source_addr': '2001:db8::1', - 'source_intf': dum_if, + 'vrf' : vrf_name, }, '2001:db8::b' : { 'source_addr': '2001:db8::1', @@ -62,6 +65,7 @@ profiles = { 'bar' : { 'intv_mult' : '102', 'intv_rx' : '444', + 'passive' : '', }, } @@ -73,6 +77,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,12 +92,16 @@ 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() @@ -106,6 +116,8 @@ 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='') @@ -121,11 +133,15 @@ 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) + self.cli_delete(['vrf', 'name', vrf_name]) + def test_bfd_profile(self): peer = '192.0.2.10' @@ -140,10 +156,21 @@ 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']) + + # 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 +179,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='') + 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_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/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/https.py b/src/conf_mode/https.py index 92dc4a410..cd5073aa2 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) @@ -209,10 +212,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/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..8593da170 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,8 @@ 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) # Bail out early if configuration tree does not exist if not conf.exists(base): return bfd @@ -76,11 +78,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..03fb17ba7 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -255,14 +255,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/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/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/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/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' ) |