diff options
42 files changed, 1294 insertions, 149 deletions
diff --git a/data/templates/conserver/dropbear@.service.tmpl b/data/templates/conserver/dropbear@.service.tmpl new file mode 100644 index 000000000..4bb73f751 --- /dev/null +++ b/data/templates/conserver/dropbear@.service.tmpl @@ -0,0 +1,4 @@ +[Service] +ExecStart= +ExecStart=/usr/sbin/dropbear -w -j -k -r /etc/dropbear/dropbear_rsa_host_key -c "/usr/bin/console {{ device }}" -P /run/conserver/dropbear.%I.pid -p %I +PIDFile=/run/conserver/dropbear.%I.pid diff --git a/data/templates/dhcp-server/dhcpd.conf.tmpl b/data/templates/dhcp-server/dhcpd.conf.tmpl index ff2e31998..f0bfa468c 100644 --- a/data/templates/dhcp-server/dhcpd.conf.tmpl +++ b/data/templates/dhcp-server/dhcpd.conf.tmpl @@ -8,16 +8,12 @@ on release { set ClientName = pick-first-value(host-decl-name, option fqdn.hostname, option host-name); set ClientIp = binary-to-ascii(10, 8, ".",leased-address); - set ClientMac = binary-to-ascii(16, 8, ":",substring(hardware, 1, 6)); - set ClientDomain = pick-first-value(config-option domain-name, "..YYZ!"); - execute("/usr/libexec/vyos/system/on-dhcp-event.sh", "release", ClientName, ClientIp, ClientMac, ClientDomain); + execute("/usr/libexec/vyos/system/on-dhcp-event.sh", "release", "", ClientIp, "", ""); } on expiry { set ClientName = pick-first-value(host-decl-name, option fqdn.hostname, option host-name); set ClientIp = binary-to-ascii(10, 8, ".",leased-address); - set ClientMac = binary-to-ascii(16, 8, ":",substring(hardware, 1, 6)); - set ClientDomain = pick-first-value(config-option domain-name, "..YYZ!"); - execute("/usr/libexec/vyos/system/on-dhcp-event.sh", "release", ClientName, ClientIp, ClientMac, ClientDomain); + execute("/usr/libexec/vyos/system/on-dhcp-event.sh", "release", "", ClientIp, "", ""); } {% endif %} @@ -201,11 +197,15 @@ shared-network {{ network | replace('_','-') }} { on commit { set shared-networkname = "{{ network | replace('_','-') }}"; {% if hostfile_update is defined %} - set ClientName = pick-first-value(host-decl-name, option fqdn.hostname, option host-name); set ClientIp = binary-to-ascii(10, 8, ".", leased-address); set ClientMac = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6)); - set ClientDomain = pick-first-value(config-option domain-name, "..YYZ!"); - execute("/usr/libexec/vyos/system/on-dhcp-event.sh", "commit", ClientName, ClientIp, ClientMac, ClientDomain); + set ClientName = pick-first-value(host-decl-name, option fqdn.hostname, option host-name, "empty_hostname"); + if not (ClientName = "empty_hostname") { + set ClientDomain = pick-first-value(config-option domain-name, "..YYZ!"); + execute("/usr/libexec/vyos/system/on-dhcp-event.sh", "commit", ClientName, ClientIp, ClientMac, ClientDomain); + } else { + log(concat("Hostname is not defined for client with IP: ", ClientIP, " MAC: ", ClientMac)); + } {% endif %} } } diff --git a/data/templates/frr/bgp.frr.tmpl b/data/templates/frr/bgp.frr.tmpl index 4ac025eee..43e405222 100644 --- a/data/templates/frr/bgp.frr.tmpl +++ b/data/templates/frr/bgp.frr.tmpl @@ -58,6 +58,14 @@ {% if config.ttl_security is defined and config.ttl_security.hops is defined and config.ttl_security.hops is not none %} neighbor {{ neighbor }} ttl-security hops {{ config.ttl_security.hops }} {% endif %} +{% if config.timers is defined %} +{% if config.timers.connect is defined and config.timers.connect is not none %} + neighbor {{ neighbor }} timers connect {{ config.timers.connect }} +{% endif %} +{% if config.timers.holdtime is defined and config.timers.keepalive is defined and config.timers.holdtime is not none and config.timers.keepalive is not none %} + neighbor {{ neighbor }} timers {{ config.timers.keepalive }} {{ config.timers.holdtime }} +{% endif %} +{% endif %} {% if config.update_source is defined and config.update_source is not none %} neighbor {{ neighbor }} update-source {{ config.update_source }} {% endif %} diff --git a/data/templates/frr/static_routes_macro.j2 b/data/templates/frr/static_routes_macro.j2 index b24232ef3..f10b58047 100644 --- a/data/templates/frr/static_routes_macro.j2 +++ b/data/templates/frr/static_routes_macro.j2 @@ -2,6 +2,12 @@ {% if prefix_config.blackhole is defined %} {{ ip_ipv6 }} route {{ prefix }} blackhole {{ prefix_config.blackhole.distance if prefix_config.blackhole.distance is defined }} {{ 'tag ' + prefix_config.blackhole.tag if prefix_config.blackhole.tag is defined }} {{ 'table ' + table if table is defined and table is not none }} {% endif %} +{% if prefix_config.dhcp_interface is defined and prefix_config.dhcp_interface is not none %} +{% set next_hop = prefix_config.dhcp_interface | get_dhcp_router %} +{% if next_hop is defined and next_hop is not none %} +{{ ip_ipv6 }} route {{ prefix }} {{ next_hop }} +{% endif %} +{% endif %} {% if prefix_config.interface is defined and prefix_config.interface is not none %} {% for interface, interface_config in prefix_config.interface.items() if interface_config.disable is not defined %} {{ ip_ipv6 }} route {{ prefix }} {{ interface }} {{ interface_config.distance if interface_config.distance is defined }} {{ 'nexthop-vrf ' + interface_config.vrf if interface_config.vrf is defined }} {{ 'table ' + table if table is defined and table is not none }} diff --git a/data/templates/https/nginx.default.tmpl b/data/templates/https/nginx.default.tmpl index 855ebff4f..81f8b3b8c 100644 --- a/data/templates/https/nginx.default.tmpl +++ b/data/templates/https/nginx.default.tmpl @@ -50,6 +50,7 @@ server { {% endif %} } + error_page 497 =301 https://$host:{{ server.port }}$request_uri; error_page 501 502 503 =200 @50*_json; {% if api_set %} diff --git a/debian/changelog b/debian/changelog index 2b65b22c6..c9d925253 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -vyos-1x (1.3dev0) unstable; urgency=medium +vyos-1x (1.4dev0) unstable; urgency=medium * Dummy changelog entry for vyos-1x repository This is a internal VyOS package and the VyOS package process does not use @@ -7,4 +7,4 @@ vyos-1x (1.3dev0) unstable; urgency=medium The correct verion number of this package is auto-generated by GIT on build-time - -- VyOS maintainers and contributors <maintainers@vyos.io> Wed, 26 Aug 2020 19:07:24 +0000 + -- VyOS maintainers and contributors <maintainers@vyos.io> Mon, 11 Jan 2021 19:02:53 +0100 diff --git a/interface-definitions/include/interface-parameters-flowlabel.xml.i b/interface-definitions/include/interface-parameters-flowlabel.xml.i new file mode 100644 index 000000000..ae65c27c9 --- /dev/null +++ b/interface-definitions/include/interface-parameters-flowlabel.xml.i @@ -0,0 +1,16 @@ +<!-- included start from interface-parameters-flowlabel.xml.i --> +<leafNode name="flowlabel"> + <properties> + <help>Specifies the flow label to use in outgoing packets</help> + <valueHelp> + <format>0x0-0x0FFFFF</format> + <description>Tunnel key, 'inherit' or hex value</description> + </valueHelp> + <constraint> + <regex>^((0x){0,1}(0?[0-9A-Fa-f]{1,5})|inherit)$</regex> + </constraint> + <constraintErrorMessage>Must be 'inherit' or a number</constraintErrorMessage> + </properties> + <defaultValue>inherit</defaultValue> +</leafNode> +<!-- included end --> diff --git a/interface-definitions/include/interface-parameters-key.xml.i b/interface-definitions/include/interface-parameters-key.xml.i new file mode 100644 index 000000000..e918ff0e8 --- /dev/null +++ b/interface-definitions/include/interface-parameters-key.xml.i @@ -0,0 +1,15 @@ +<!-- included start from interface-parameters-key.xml.i --> +<leafNode name="key"> + <properties> + <help>Tunnel key</help> + <valueHelp> + <format>u32</format> + <description>Tunnel key</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + <constraintErrorMessage>key must be between 0-4294967295</constraintErrorMessage> + </properties> +</leafNode> +<!-- included end --> diff --git a/interface-definitions/include/interface-parameters-tos.xml.i b/interface-definitions/include/interface-parameters-tos.xml.i new file mode 100644 index 000000000..ebb537bed --- /dev/null +++ b/interface-definitions/include/interface-parameters-tos.xml.i @@ -0,0 +1,16 @@ +<!-- included start from tunnel-parameters-tos.xml.i --> +<leafNode name="tos"> + <properties> + <help>Specifies TOS value to use in outgoing packets</help> + <valueHelp> + <format>0-99</format> + <description>Type of Service (TOS)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-99"/> + </constraint> + <constraintErrorMessage>TOS must be between 0 and 99</constraintErrorMessage> + </properties> + <defaultValue>inherit</defaultValue> +</leafNode> +<!-- included end --> diff --git a/interface-definitions/include/interface-parameters-ttl.xml.i b/interface-definitions/include/interface-parameters-ttl.xml.i new file mode 100644 index 000000000..a6a6f163f --- /dev/null +++ b/interface-definitions/include/interface-parameters-ttl.xml.i @@ -0,0 +1,20 @@ +<!-- included start from interface-parameters-ttl.xml.i --> +<leafNode name="ttl"> + <properties> + <help>Specifies TTL value to use in outgoing packets (default: 0)</help> + <valueHelp> + <format>0</format> + <description>Copy value from original IP header</description> + </valueHelp> + <valueHelp> + <format>1-255</format> + <description>Time to Live</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-255"/> + </constraint> + <constraintErrorMessage>TTL must be between 0 and 255</constraintErrorMessage> + </properties> + <defaultValue>0</defaultValue> +</leafNode> +<!-- included end --> diff --git a/interface-definitions/include/source-interface.xml.i b/interface-definitions/include/source-interface.xml.i index e6f0b69a1..797206430 100644 --- a/interface-definitions/include/source-interface.xml.i +++ b/interface-definitions/include/source-interface.xml.i @@ -1,14 +1,17 @@ <!-- included start from source-interface.xml.i --> <leafNode name="source-interface"> <properties> - <help>Physical interface used for connection</help> + <help>Interface used to establish connection</help> <valueHelp> <format>interface</format> - <description>Physical interface used for connection</description> + <description>Interface name</description> </valueHelp> <completionHelp> <script>${vyos_completion_dir}/list_interfaces.py</script> </completionHelp> + <constraint> + <validator name="interface-name"/> + </constraint> </properties> </leafNode> <!-- included end --> diff --git a/interface-definitions/include/static-route.xml.i b/interface-definitions/include/static-route.xml.i index 386582e09..21fcbcd3f 100644 --- a/interface-definitions/include/static-route.xml.i +++ b/interface-definitions/include/static-route.xml.i @@ -31,6 +31,21 @@ </leafNode> </children> </node> + <leafNode name="dhcp-interface"> + <properties> + <help>DHCP interface supplying next-hop IP address</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces.py</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>DHCP interface name</description> + </valueHelp> + <constraint> + <validator name="interface-name"/> + </constraint> + </properties> + </leafNode> <tagNode name="interface"> <properties> <help>Next-hop IPv4 router interface</help> diff --git a/interface-definitions/include/tunnel-parameters-ip.xml.i b/interface-definitions/include/tunnel-parameters-ip.xml.i deleted file mode 100644 index 0a667d199..000000000 --- a/interface-definitions/include/tunnel-parameters-ip.xml.i +++ /dev/null @@ -1,46 +0,0 @@ -<!-- included start from tunnel-parameters-ip.xml.i --> -<leafNode name="ttl"> - <properties> - <help>Time to live (default: 0)</help> - <valueHelp> - <format>0</format> - <description>Copy value from original IP header</description> - </valueHelp> - <valueHelp> - <format>1-255</format> - <description>Time to Live</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 0-255"/> - </constraint> - <constraintErrorMessage>TTL must be between 0 and 255</constraintErrorMessage> - </properties> - <defaultValue>0</defaultValue> -</leafNode> -<leafNode name="tos"> - <properties> - <help>Type of Service (TOS)</help> - <valueHelp> - <format>0-99</format> - <description>Type of Service (TOS)</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 0-99"/> - </constraint> - <constraintErrorMessage>TOS must be between 0 and 99</constraintErrorMessage> - </properties> - <defaultValue>inherit</defaultValue> -</leafNode> -<leafNode name="key"> - <properties> - <help>Tunnel key</help> - <valueHelp> - <format>u32</format> - <description>Tunnel key</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 0-4294967295"/> - </constraint> - <constraintErrorMessage>key must be between 0-4294967295</constraintErrorMessage> - </properties> -</leafNode> diff --git a/interface-definitions/interfaces-erspan.xml.in b/interface-definitions/interfaces-erspan.xml.in index 8a4bfea2b..e36a64d3a 100644 --- a/interface-definitions/interfaces-erspan.xml.in +++ b/interface-definitions/interfaces-erspan.xml.in @@ -51,7 +51,9 @@ <help>IPv4 specific tunnel parameters</help> </properties> <children> - #include <include/tunnel-parameters-ip.xml.i> + #include <include/interface-parameters-key.xml.i> + #include <include/interface-parameters-tos.xml.i> + #include <include/interface-parameters-ttl.xml.i> </children> </node> <leafNode name="version"> diff --git a/interface-definitions/interfaces-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in index 45573a826..7a97980a2 100644 --- a/interface-definitions/interfaces-tunnel.xml.in +++ b/interface-definitions/interfaces-tunnel.xml.in @@ -151,7 +151,9 @@ <valueless/> </properties> </leafNode> - #include <include/tunnel-parameters-ip.xml.i> + #include <include/interface-parameters-key.xml.i> + #include <include/interface-parameters-tos.xml.i> + #include <include/interface-parameters-ttl.xml.i> </children> </node> <node name="ipv6"> @@ -173,20 +175,7 @@ </properties> <defaultValue>4</defaultValue> </leafNode> - <leafNode name="flowlabel"> - <properties> - <help>Flowlabel</help> - <valueHelp> - <format>0x0-0x0FFFFF</format> - <description>Tunnel key, 'inherit' or hex value</description> - </valueHelp> - <constraint> - <regex>(0x){0,1}(0?[0-9A-Fa-f]{1,5})</regex> - </constraint> - <constraintErrorMessage>Must be 'inherit' or a number</constraintErrorMessage> - </properties> - <defaultValue>inherit</defaultValue> - </leafNode> + #include <include/interface-parameters-flowlabel.xml.i> <leafNode name="hoplimit"> <properties> <help>Hoplimit</help> diff --git a/interface-definitions/protocols-bgp.xml.in b/interface-definitions/protocols-bgp.xml.in index 3f3b2661d..46b913830 100644 --- a/interface-definitions/protocols-bgp.xml.in +++ b/interface-definitions/protocols-bgp.xml.in @@ -75,6 +75,14 @@ #include <include/bgp-afi-redistribute-metric-route-map.xml.i> </children> </node> + <node name="isis"> + <properties> + <help>Redistribute IS-IS routes into BGP</help> + </properties> + <children> + #include <include/bgp-afi-redistribute-metric-route-map.xml.i> + </children> + </node> <node name="kernel"> <properties> <help>Redistribute kernel routes into BGP</help> diff --git a/interface-definitions/protocols-ospf.xml.in b/interface-definitions/protocols-ospf.xml.in index 4c480c71d..b9c9fcc04 100644 --- a/interface-definitions/protocols-ospf.xml.in +++ b/interface-definitions/protocols-ospf.xml.in @@ -39,6 +39,10 @@ <description>Filter connected routes</description> </valueHelp> <valueHelp> + <format>isis</format> + <description>Filter IS-IS routes</description> + </valueHelp> + <valueHelp> <format>kernel</format> <description>Filter Kernel routes</description> </valueHelp> @@ -51,7 +55,7 @@ <description>Filter static routes</description> </valueHelp> <constraint> - <regex>^(bgp|connected|kernel|rip|static)$</regex> + <regex>^(bgp|connected|isis|kernel|rip|static)$</regex> </constraint> <constraintErrorMessage>Must be bgp, connected, kernel, rip, or static</constraintErrorMessage> <multi/> @@ -699,6 +703,16 @@ #include <include/ospf-route-map.xml.i> </children> </node> + <node name="isis"> + <properties> + <help>Redistribute IS-IS routes</help> + </properties> + <children> + #include <include/ospf-metric.xml.i> + #include <include/ospf-metric-type.xml.i> + #include <include/ospf-route-map.xml.i> + </children> + </node> <node name="kernel"> <properties> <help>Redistribute kernel routes</help> diff --git a/interface-definitions/protocols-rip.xml.in b/interface-definitions/protocols-rip.xml.in index 263350dc8..1ae3bd8f7 100644 --- a/interface-definitions/protocols-rip.xml.in +++ b/interface-definitions/protocols-rip.xml.in @@ -176,6 +176,14 @@ #include <include/rip-redistribute.xml.i> </children> </node> + <node name="isis"> + <properties> + <help>Redistribute IS-IS routes</help> + </properties> + <children> + #include <include/rip-redistribute.xml.i> + </children> + </node> <node name="kernel"> <properties> <help>Redistribute kernel routes</help> diff --git a/interface-definitions/vrrp.xml.in b/interface-definitions/vrrp.xml.in index caa9f4a33..3c4c9b83c 100644 --- a/interface-definitions/vrrp.xml.in +++ b/interface-definitions/vrrp.xml.in @@ -212,6 +212,15 @@ </constraint> </properties> </leafNode> + <leafNode name="mode-force"> + <properties> + <valueless/> + <help>Disable VRRP state checking (deprecated, will be removed in VyOS 1.4)</help> + <constraint> + <validator name="script"/> + </constraint> + </properties> + </leafNode> </children> </node> <leafNode name="virtual-address"> diff --git a/op-mode-definitions/ipoe-server.xml.in b/op-mode-definitions/ipoe-server.xml.in index 18178f0b0..89cefa08d 100644 --- a/op-mode-definitions/ipoe-server.xml.in +++ b/op-mode-definitions/ipoe-server.xml.in @@ -4,17 +4,17 @@ <children> <node name="ipoe-server"> <properties> - <help>Clear ipoe-server sessions or process</help> + <help>Clear IPoE server sessions or process</help> </properties> <children> <node name="session"> <properties> - <help>Clear ipoe-server session</help> + <help>Clear IPoE server session</help> </properties> <children> <tagNode name="username"> <properties> - <help>Clear ipoe-server session by username</help> + <help>Clear IPoE server session by username</help> <completionHelp> <script>${vyos_completion_dir}/list_ipoe.py --selector="username"</script> </completionHelp> @@ -23,7 +23,7 @@ </tagNode> <tagNode name="sid"> <properties> - <help>Clear ipoe-server session by Session ID</help> + <help>Clear IPoE server session by Session ID</help> <completionHelp> <script>${vyos_completion_dir}/list_ipoe.py --selector="sid"</script> </completionHelp> @@ -32,7 +32,7 @@ </tagNode> <tagNode name="interface"> <properties> - <help>Clear ipoe-server session by interface</help> + <help>Clear IPoE server session by interface</help> <completionHelp> <script>${vyos_completion_dir}/list_ipoe.py --selector="ifname"</script> </completionHelp> @@ -49,7 +49,7 @@ <children> <node name="ipoe-server"> <properties> - <help>show ipoe-server status</help> + <help>Show IPoE server status</help> </properties> <children> <leafNode name="sessions"> diff --git a/op-mode-definitions/openconnect.xml.in b/op-mode-definitions/openconnect.xml.in index 9b82b114e..36f23239e 100644 --- a/op-mode-definitions/openconnect.xml.in +++ b/op-mode-definitions/openconnect.xml.in @@ -4,12 +4,12 @@ <children> <node name="openconnect-server"> <properties> - <help>show openconnect-server information</help> + <help>Show OpenConnect server information</help> </properties> <children> <leafNode name="sessions"> <properties> - <help>Show active openconnect server sessions</help> + <help>Show active OpenConnect server sessions</help> </properties> <command>${vyos_op_scripts_dir}/openconnect-control.py --action="show_sessions"</command> </leafNode> diff --git a/op-mode-definitions/pppoe-server.xml.in b/op-mode-definitions/pppoe-server.xml.in index 6d89b3e77..6efdc5a48 100644 --- a/op-mode-definitions/pppoe-server.xml.in +++ b/op-mode-definitions/pppoe-server.xml.in @@ -4,7 +4,7 @@ <children> <node name="pppoe-server"> <properties> - <help>Show pppoe-server status</help> + <help>Show PPPoE server status</help> </properties> <children> <leafNode name="sessions"> @@ -21,7 +21,7 @@ </leafNode> <leafNode name="interfaces"> <properties> - <help>Show interfaces where pppoe-server listens on</help> + <help>Show interfaces where PPPoE server listens on</help> </properties> <command>${vyos_op_scripts_dir}/ppp-server-ctrl.py --proto="pppoe" --action="pppoe interface show"</command> </leafNode> @@ -51,13 +51,13 @@ <children> <leafNode name="all"> <properties> - <help>Terminate all pppoe-server users</help> + <help>Terminate all PPPoE server users</help> </properties> <command>${vyos_op_scripts_dir}/ppp-server-ctrl.py --proto="pppoe" --action="terminate all"</command> </leafNode> <tagNode name="interface"> <properties> - <help>Terminate a ppp interface</help> + <help>Terminate a PPP interface</help> </properties> <command>${vyos_op_scripts_dir}/ppp-server-ctrl.py --proto="pppoe" --action="terminate if $4"</command> </tagNode> @@ -85,7 +85,7 @@ <children> <leafNode name="enable"> <properties> - <help>Deny new connections and stop to serve pppoe after disconnect last session</help> + <help>Deny new connections and stop serving PPPoE after disconnecting the last session</help> </properties> <command>${vyos_op_scripts_dir}/ppp-server-ctrl.py --proto="pppoe" --action="shutdown soft"</command> </leafNode> diff --git a/op-mode-definitions/wireguard.xml.in b/op-mode-definitions/wireguard.xml.in index 69ba8043d..4aee4b1ac 100644 --- a/op-mode-definitions/wireguard.xml.in +++ b/op-mode-definitions/wireguard.xml.in @@ -1,28 +1,28 @@ <?xml version="1.0"?> -<!-- wireguard key management --> +<!-- Wireguard key management --> <interfaceDefinition> <node name="generate"> <children> <node name="wireguard"> <properties> - <help>wireguard key generation utility</help> + <help>Generate Wireguard keys</help> </properties> <children> <leafNode name="default-keypair"> <properties> - <help>generates the wireguard default-keypair</help> + <help>Generate the default Wireguard keypair</help> </properties> <command>sudo ${vyos_op_scripts_dir}/wireguard.py --genkey</command> </leafNode> <leafNode name="preshared-key"> <properties> - <help>generate a wireguard preshared key</help> + <help>Generate a Wireguard preshared key</help> </properties> <command>${vyos_op_scripts_dir}/wireguard.py --genpsk</command> </leafNode> <tagNode name="named-keypairs"> <properties> - <help>Generates named wireguard keypairs</help> + <help>Generate specified Wireguard keypairs</help> </properties> <command>sudo ${vyos_op_scripts_dir}/wireguard.py --genkey --location "$4"</command> </tagNode> @@ -34,17 +34,17 @@ <children> <node name="wireguard"> <properties> - <help>Show wireguard properties</help> + <help>Show Wireguard properties</help> </properties> <children> <node name="keypairs"> <properties> - <help>Shows named wireguard keys</help> + <help>Show Wireguard keys</help> </properties> <children> <tagNode name="pubkey"> <properties> - <help>Show wireguard private named key</help> + <help>Show specified Wireguard public key</help> <completionHelp> <script>${vyos_op_scripts_dir}/wireguard.py --listkdir</script> </completionHelp> @@ -53,7 +53,7 @@ </tagNode> <tagNode name="privkey"> <properties> - <help>Show wireguard public named key</help> + <help>Show specified Wireguard private key</help> <completionHelp> <script>${vyos_op_scripts_dir}/wireguard.py --listkdir</script> </completionHelp> @@ -68,7 +68,7 @@ <children> <tagNode name="wireguard"> <properties> - <help>show wireguard interface information</help> + <help>Show Wireguard interface information</help> <completionHelp> <script>${vyos_completion_dir}/list_interfaces.py --type wireguard</script> </completionHelp> @@ -77,19 +77,19 @@ <children> <leafNode name="allowed-ips"> <properties> - <help>show all allowed-ips for the specified interface</help> + <help>Show all IP addresses allowed for the specified interface</help> </properties> <command>sudo wg show "$4" allowed-ips</command> </leafNode> <leafNode name="endpoints"> <properties> - <help>show all endpoints for the specified interface</help> + <help>Show all endpoints for the specified interface</help> </properties> <command>sudo wg show "$4" endpoints</command> </leafNode> <leafNode name="peers"> <properties> - <help>show all peer IDs for the specified interface</help> + <help>Show all peer IDs for the specified interface</help> </properties> <command>sudo wg show "$4" peers</command> </leafNode> @@ -98,13 +98,13 @@ </tagNode> <node name="wireguard"> <properties> - <help>Show wireguard interface information</help> + <help>Show Wireguard interface information</help> </properties> <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=wireguard --action=show-brief</command> <children> <leafNode name="detail"> <properties> - <help>Show detailed wireguard interface information</help> + <help>Show detailed Wireguard interface information</help> </properties> <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=wireguard --action=show</command> </leafNode> @@ -118,12 +118,12 @@ <children> <node name="wireguard"> <properties> - <help>Delete wireguard properties</help> + <help>Delete Wireguard properties</help> </properties> <children> <tagNode name="keypair"> <properties> - <help>Delete a wireguard keypair</help> + <help>Delete a Wireguard keypair</help> <completionHelp> <script>${vyos_op_scripts_dir}/wireguard.py --listkdir</script> </completionHelp> diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py index e8a339d2f..136feae8d 100644 --- a/python/vyos/ethtool.py +++ b/python/vyos/ethtool.py @@ -13,7 +13,7 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see <http://www.gnu.org/licenses/>. -from vyos.util import cmd +from vyos.util import popen class Ethtool: """ @@ -33,12 +33,13 @@ class Ethtool: # 'tx-esp-segmentation': {'fixed': True, 'on': False}, # } features = { } + ring_buffers = { } def __init__(self, ifname): # Now populate features dictionaty - tmp = cmd(f'ethtool -k {ifname}') + out, err = popen(f'ethtool -k {ifname}') # skip the first line, it only says: "Features for eth0": - for line in tmp.splitlines()[1:]: + for line in out.splitlines()[1:]: if ":" in line: key, value = [s.strip() for s in line.strip().split(":", 1)] fixed = "fixed" in value @@ -49,6 +50,16 @@ class Ethtool: "fixed": fixed } + out, err = popen(f'ethtool -g {ifname}') + # We are only interested in line 2-5 which contains the device maximum + # ringbuffers + for line in out.splitlines()[2:6]: + if ':' in line: + key, value = [s.strip() for s in line.strip().split(":", 1)] + key = key.lower().replace(' ', '_') + self.ring_buffers[key] = int(value) + + def is_fixed_lro(self): # in case of a missing configuration, rather return "fixed". In Ethtool # terminology "fixed" means the setting can not be changed by the user. @@ -78,3 +89,13 @@ class Ethtool: # in case of a missing configuration, rather return "fixed". In Ethtool # terminology "fixed" means the setting can not be changed by the user. return self.features.get('udp-fragmentation-offload', True).get('fixed', True) + + def get_rx_buffer(self): + # Configuration of RX ring-buffers is not supported on every device, + # thus when it's impossible return None + return self.ring_buffers.get('rx', None) + + def get_tx_buffer(self): + # Configuration of TX ring-buffers is not supported on every device, + # thus when it's impossible return None + return self.ring_buffers.get('tx', None) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index d9507d816..4bdabd432 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -923,12 +923,12 @@ class Interface(Control): else: add_vlan.append(vlan) allowed_vlan_ids.append(vlan) - + # Remove redundant VLANs from the system for vlan in list_diff(cur_vlan_ids, add_vlan): cmd = f'bridge vlan del dev {ifname} vid {vlan} master' self._cmd(cmd) - + for vlan in allowed_vlan_ids: cmd = f'bridge vlan add dev {ifname} vid {vlan} master' self._cmd(cmd) @@ -1074,6 +1074,10 @@ class Interface(Control): interface setup code and provide a single point of entry when workin on any interface. """ + if self.debug: + import pprint + pprint.pprint(config) + # Cache the configuration - it will be reused inside e.g. DHCP handler # XXX: maybe pass the option via __init__ in the future and rename this # method to apply()? diff --git a/python/vyos/template.py b/python/vyos/template.py index 527384d0b..22883b103 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -288,7 +288,6 @@ def compare_netmask(netmask1, netmask2): except: return False - @register_filter('isc_static_route') def isc_static_route(subnet, router): # https://ercpe.de/blog/pushing-static-routes-with-isc-dhcp-server @@ -316,3 +315,22 @@ def is_file(filename): if os.path.exists(filename): return os.path.isfile(filename) return False + +@register_filter('get_dhcp_router') +def get_dhcp_router(interface): + """ Static routes can point to a router received by a DHCP reply. This + helper is used to get the current default router from the DHCP reply. + + Returns False of no router is found, returns the IP address as string if + a router is found. + """ + interface = interface.replace('.', '_') + lease_file = f'/var/lib/dhcp/dhclient_{interface}.leases' + if not os.path.exists(lease_file): + return None + + from vyos.util import read_file + for line in read_file(lease_file).splitlines(): + if 'option routers' in line: + (_, _, address) = line.split() + return address.rstrip(';') diff --git a/smoketest/configs/bgp-small-internet-exchange b/smoketest/configs/bgp-small-internet-exchange index de6213b50..d51f87c4a 100644 --- a/smoketest/configs/bgp-small-internet-exchange +++ b/smoketest/configs/bgp-small-internet-exchange @@ -11,6 +11,100 @@ interfaces { } } policy { + as-path-list bogon-asns { + rule 10 { + action permit + description "RFC 7607" + regex _0_ + } + rule 20 { + action permit + description "RFC 4893" + regex _23456_ + } + rule 30 { + action permit + description "RFC 5398/6996/7300" + regex _6449[6-9]_|_65[0-4][0-9][0-9]_|_655[0-4][0-9]_|_6555[0-1]_ + } + rule 40 { + action permit + description "IANA reserved" + regex _6555[2-9]_|_655[6-9][0-9]_|_65[6-9][0-9][0-9]_|_6[6-9][0-9][0-9][0-]_|_[7-9][0-9][0-9][0-9][0-9]_|_1[0-2][0-9][0-9][0-9][0-9]_|_130[0-9][0-9][0-9]_|_1310[0-6][0-9]_|_13107[01]_ + } + } + prefix-list bogon-v4 { + rule 10 { + action permit + le 32 + prefix 0.0.0.0/8 + } + rule 20 { + action permit + le 32 + prefix 10.0.0.0/8 + } + rule 30 { + action permit + le 32 + prefix 100.64.0.0/10 + } + rule 40 { + action permit + le 32 + prefix 127.0.0.0/8 + } + rule 50 { + action permit + le 32 + prefix 169.254.0.0/16 + } + rule 60 { + action permit + le 32 + prefix 172.16.0.0/12 + } + rule 70 { + action permit + le 32 + prefix 192.0.2.0/24 + } + rule 80 { + action permit + le 32 + prefix 192.88.99.0/24 + } + rule 90 { + action permit + le 32 + prefix 192.168.0.0/16 + } + rule 100 { + action permit + le 32 + prefix 198.18.0.0/15 + } + rule 110 { + action permit + le 32 + prefix 198.51.100.0/24 + } + rule 120 { + action permit + le 32 + prefix 203.0.113.0/24 + } + rule 130 { + action permit + le 32 + prefix 224.0.0.0/4 + } + rule 140 { + action permit + le 32 + prefix 240.0.0.0/4 + } + } prefix-list IX-out-v4 { rule 10 { action permit @@ -21,6 +115,88 @@ policy { prefix 10.0.128.0/23 } } + prefix-list prefix-filter-v4 { + rule 10 { + action permit + ge 25 + prefix 0.0.0.0/0 + } + } + prefix-list6 bogon-v6 { + rule 10 { + action permit + description "RFC 4291 IPv4-compatible, loopback, et al" + le 128 + prefix ::/8 + } + rule 20 { + action permit + description "RFC 6666 Discard-Only" + le 128 + prefix 0100::/64 + } + rule 30 { + action permit + description "RFC 5180 BMWG" + le 128 + prefix 2001:2::/48 + } + rule 40 { + action permit + description "RFC 4843 ORCHID" + le 128 + prefix 2001:10::/28 + } + rule 50 { + action permit + description "RFC 3849 documentation" + le 128 + prefix 2001:db8::/32 + } + rule 60 { + action permit + description "RFC 7526 6to4 anycast relay" + le 128 + prefix 2002::/16 + } + rule 70 { + action permit + description "RFC 3701 old 6bone" + le 128 + prefix 3ffe::/16 + } + rule 80 { + action permit + description "RFC 4193 unique local unicast" + le 128 + prefix fc00::/7 + } + rule 90 { + action permit + description "RFC 4291 link local unicast" + le 128 + prefix fe80::/10 + } + rule 100 { + action permit + description "RFC 3879 old site local unicast" + le 128 + prefix fec0::/10 + } + rule 110 { + action permit + description "RFC 4291 multicast" + le 128 + prefix ff00::/8 + } + } + prefix-list6 prefix-filter-v6 { + rule 10 { + action permit + ge 49 + prefix ::/0 + } + } prefix-list6 IX-out-v6 { rule 10 { action permit @@ -31,6 +207,88 @@ policy { prefix 2001:db8:200::/40 } } + route-map eBGP-IN-v4 { + rule 10 { + action deny + match { + as-path bogon-asns + } + } + rule 20 { + action deny + match { + ip { + address { + prefix-list bogon-v4 + } + } + } + } + rule 30 { + action deny + match { + ip { + address { + prefix-list prefix-filter-v4 + } + } + } + } + rule 40 { + action permit + set { + local-preference 100 + metric 0 + } + } + } + route-map eBGP-IN-v6 { + rule 10 { + action deny + match { + as-path bogon-asns + } + } + rule 20 { + action deny + match { + ipv6 { + address { + prefix-list bogon-v6 + } + } + } + } + rule 30 { + action deny + match { + ipv6 { + address { + prefix-list prefix-filter-v6 + } + } + } + } + rule 40 { + action permit + set { + local-preference 100 + metric 0 + } + } + } + route-map IX-in-v4 { + rule 5 { + action permit + call eBGP-IN-v4 + on-match { + next + } + } + rule 10 { + action permit + } + } route-map IX-out-v4 { rule 10 { action permit @@ -43,6 +301,18 @@ policy { } } } + route-map IX-in-v6 { + rule 5 { + action permit + call eBGP-IN-v6 + on-match { + next + } + } + rule 10 { + action permit + } + } route-map IX-out-v6 { rule 10 { action permit diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py new file mode 100755 index 000000000..8efbab7e5 --- /dev/null +++ b/smoketest/scripts/cli/test_policy.py @@ -0,0 +1,681 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import unittest + +from vyos.util import cmd +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError + +base_path = ['policy'] + +def getFRRconfig(section): + return cmd(f'vtysh -c "show run" | sed -n "/^{section}/,/^!/p"') + +class TestPolicy(unittest.TestCase): + def setUp(self): + self.session = ConfigSession(os.getpid()) + + def tearDown(self): + self.session.delete(base_path) + self.session.commit() + del self.session + + def test_access_list(self): + acls = { + '50' : { + 'rule' : { + '5' : { + 'action' : 'permit', + 'source' : { 'any' : '' }, + }, + '10' : { + 'action' : 'deny', + 'source' : { 'host' : '1.2.3.4' }, + }, + }, + }, + '150' : { + 'rule' : { + '10' : { + 'action' : 'permit', + 'source' : { 'any' : '' }, + 'destination' : { 'host' : '2.2.2.2' }, + }, + '10' : { + 'action' : 'deny', + 'source' : { 'any' : '' }, + 'destination' : { 'any' : '' }, + }, + }, + }, + '2000' : { + 'rule' : { + '10' : { + 'action' : 'permit', + 'destination' : { 'any' : '' }, + 'source' : { 'network' : '10.0.0.0', 'inverse-mask' : '0.255.255.255' }, + }, + '20' : { + 'action' : 'permit', + 'destination' : { 'any' : '' }, + 'source' : { 'network' : '172.16.0.0', 'inverse-mask' : '0.15.255.255' }, + }, + '30' : { + 'action' : 'permit', + 'destination' : { 'any' : '' }, + 'source' : { 'network' : '192.168.0.0', 'inverse-mask' : '0.0.255.255' }, + }, + '50' : { + 'action' : 'permit', + 'destination' : { 'network' : '172.16.0.0', 'inverse-mask' : '0.15.255.255' }, + 'source' : { 'network' : '10.0.0.0', 'inverse-mask' : '0.255.255.255' }, + }, + '60' : { + 'action' : 'deny', + 'destination' : { 'network' : '192.168.0.0', 'inverse-mask' : '0.0.255.255' }, + 'source' : { 'network' : '172.16.0.0', 'inverse-mask' : '0.15.255.255' }, + }, + '70' : { + 'action' : 'deny', + 'destination' : { 'any' : '' }, + 'source' : { 'any' : '' }, + }, + }, + }, + } + + for acl, acl_config in acls.items(): + path = base_path + ['access-list', acl] + self.session.set(path + ['description', f'VyOS-ACL-{acl}']) + if 'rule' not in acl_config: + continue + + for rule, rule_config in acl_config['rule'].items(): + self.session.set(path + ['rule', rule, 'action', rule_config['action']]) + for direction in ['source', 'destination']: + if direction in rule_config: + if 'any' in rule_config[direction]: + self.session.set(path + ['rule', rule, direction, 'any']) + if 'host' in rule_config[direction]: + self.session.set(path + ['rule', rule, direction, 'host', rule_config[direction]['host']]) + if 'network' in rule_config[direction]: + self.session.set(path + ['rule', rule, direction, 'network', rule_config[direction]['network']]) + self.session.set(path + ['rule', rule, direction, 'inverse-mask', rule_config[direction]['inverse-mask']]) + + self.session.commit() + + config = getFRRconfig('access-list') + for acl, acl_config in acls.items(): + seq = '5' + for rule, rule_config in acl_config['rule'].items(): + tmp = f'access-list {acl} seq {seq}' + if rule_config['action'] == 'permit': + tmp += ' permit' + else: + tmp += ' deny' + + if {'source', 'destination'} <= set(rule_config): + tmp += ' ip' + + for direction in ['source', 'destination']: + if direction in rule_config: + if 'any' in rule_config[direction]: + tmp += ' any' + if 'host' in rule_config[direction]: + tmp += ' ' + rule_config[direction]['host'] + if 'network' in rule_config[direction]: + tmp += ' ' + rule_config[direction]['network'] + ' ' + rule_config[direction]['inverse-mask'] + + self.assertIn(tmp, config) + seq = int(seq) + 5 + + def test_access_list6(self): + acls = { + '50' : { + 'rule' : { + '5' : { + 'action' : 'permit', + 'source' : { 'any' : '' }, + }, + '10' : { + 'action' : 'deny', + 'source' : { 'network' : '2001:db8:10::/48', 'exact-match' : '' }, + }, + '10' : { + 'action' : 'deny', + 'source' : { 'network' : '2001:db8:20::/48' }, + }, + }, + }, + '100' : { + 'rule' : { + '5' : { + 'action' : 'deny', + 'source' : { 'network' : '2001:db8:10::/64', 'exact-match' : '' }, + }, + '10' : { + 'action' : 'deny', + 'source' : { 'network' : '2001:db8:20::/64', }, + }, + '15' : { + 'action' : 'deny', + 'source' : { 'network' : '2001:db8:30::/64', 'exact-match' : '' }, + }, + '20' : { + 'action' : 'deny', + 'source' : { 'network' : '2001:db8:40::/64', 'exact-match' : '' }, + }, + '100' : { + 'action' : 'deny', + 'source' : { 'any' : '' }, + }, + }, + }, + } + + for acl, acl_config in acls.items(): + path = base_path + ['access-list6', acl] + self.session.set(path + ['description', f'VyOS-ACL-{acl}']) + if 'rule' not in acl_config: + continue + + for rule, rule_config in acl_config['rule'].items(): + self.session.set(path + ['rule', rule, 'action', rule_config['action']]) + for direction in ['source', 'destination']: + if direction in rule_config: + if 'any' in rule_config[direction]: + self.session.set(path + ['rule', rule, direction, 'any']) + if 'network' in rule_config[direction]: + self.session.set(path + ['rule', rule, direction, 'network', rule_config[direction]['network']]) + if 'exact-match' in rule_config[direction]: + self.session.set(path + ['rule', rule, direction, 'exact-match']) + + self.session.commit() + + config = getFRRconfig('ipv6 access-list') + for acl, acl_config in acls.items(): + seq = '5' + for rule, rule_config in acl_config['rule'].items(): + tmp = f'ipv6 access-list {acl} seq {seq}' + if rule_config['action'] == 'permit': + tmp += ' permit' + else: + tmp += ' deny' + + if {'source', 'destination'} <= set(rule_config): + tmp += ' ip' + + for direction in ['source', 'destination']: + if direction in rule_config: + if 'any' in rule_config[direction]: + tmp += ' any' + if 'network' in rule_config[direction]: + tmp += ' ' + rule_config[direction]['network'] + if 'exact-match' in rule_config[direction]: + tmp += ' exact-match' + + self.assertIn(tmp, config) + seq = int(seq) + 5 + + + def test_as_path_list(self): + test_data = { + 'VyOS' : { + 'rule' : { + '10' : { + 'action' : 'permit', + 'regex' : '^44501 64502$', + }, + '20' : { + 'action' : 'permit', + 'regex' : '44501|44502|44503', + }, + '30' : { + 'action' : 'permit', + 'regex' : '^44501_([0-9]+_)+', + }, + }, + }, + 'Customers' : { + 'rule' : { + '10' : { + 'action' : 'permit', + 'regex' : '_10_', + }, + '20' : { + 'action' : 'permit', + 'regex' : '_20_', + }, + '30' : { + 'action' : 'permit', + 'regex' : '_30_', + }, + '30' : { + 'action' : 'deny', + 'regex' : '_40_', + }, + }, + }, + 'bogons' : { + 'rule' : { + '10' : { + 'action' : 'permit', + 'regex' : '_0_', + }, + '20' : { + 'action' : 'permit', + 'regex' : '_23456_', + }, + '30' : { + 'action' : 'permit', + 'regex' : '_6449[6-9]_|_65[0-4][0-9][0-9]_|_655[0-4][0-9]_|_6555[0-1]_', + }, + '30' : { + 'action' : 'permit', + 'regex' : '_6555[2-9]_|_655[6-9][0-9]_|_65[6-9][0-9][0-9]_|_6[6-9][0-9][0-9][0-]_|_[7-9][0-9][0-9][0-9][0-9]_|_1[0-2][0-9][0-9][0-9][0-9]_|_130[0-9][0-9][0-9]_|_1310[0-6][0-9]_|_13107[01]_', + }, + }, + }, + } + + for as_path, as_path_config in test_data.items(): + path = base_path + ['as-path-list', as_path] + self.session.set(path + ['description', f'VyOS-ASPATH-{as_path}']) + if 'rule' not in as_path_config: + continue + + for rule, rule_config in as_path_config['rule'].items(): + if 'action' in rule_config: + self.session.set(path + ['rule', rule, 'action', rule_config['action']]) + if 'regex' in rule_config: + self.session.set(path + ['rule', rule, 'regex', rule_config['regex']]) + + self.session.commit() + + config = getFRRconfig('bgp as-path access-list') + for as_path, as_path_config in test_data.items(): + if 'rule' not in as_path_config: + continue + + for rule, rule_config in as_path_config['rule'].items(): + tmp = f'bgp as-path access-list {as_path}' + if rule_config['action'] == 'permit': + tmp += ' permit' + else: + tmp += ' deny' + + tmp += ' ' + rule_config['regex'] + + self.assertIn(tmp, config) + + def test_community_list(self): + test_data = { + '100' : { + 'rule' : { + '4' : { + 'action' : 'permit', + 'regex' : '.*', + }, + }, + }, + '200' : { + 'rule' : { + '1' : { + 'action' : 'deny', + 'regex' : '^1:201$', + }, + '2' : { + 'action' : 'deny', + 'regex' : '1:101$', + }, + '3' : { + 'action' : 'deny', + 'regex' : '^1:100$', + }, + }, + }, + } + + for comm_list, comm_list_config in test_data.items(): + path = base_path + ['community-list', comm_list] + self.session.set(path + ['description', f'VyOS-COMM-{comm_list}']) + if 'rule' not in comm_list_config: + continue + + for rule, rule_config in comm_list_config['rule'].items(): + if 'action' in rule_config: + self.session.set(path + ['rule', rule, 'action', rule_config['action']]) + if 'regex' in rule_config: + self.session.set(path + ['rule', rule, 'regex', rule_config['regex']]) + + self.session.commit() + + config = getFRRconfig('bgp community-list') + for comm_list, comm_list_config in test_data.items(): + if 'rule' not in comm_list_config: + continue + + seq = '5' + for rule, rule_config in comm_list_config['rule'].items(): + tmp = f'bgp community-list {comm_list} seq {seq}' + if rule_config['action'] == 'permit': + tmp += ' permit' + else: + tmp += ' deny' + + tmp += ' ' + rule_config['regex'] + + self.assertIn(tmp, config) + seq = int(seq) + 5 + + def test_extended_community_list(self): + test_data = { + 'foo' : { + 'rule' : { + '4' : { + 'action' : 'permit', + 'regex' : '.*', + }, + }, + }, + '200' : { + 'rule' : { + '1' : { + 'action' : 'deny', + 'regex' : '^1:201$', + }, + '2' : { + 'action' : 'deny', + 'regex' : '1:101$', + }, + '3' : { + 'action' : 'deny', + 'regex' : '^1:100$', + }, + }, + }, + } + + for comm_list, comm_list_config in test_data.items(): + path = base_path + ['extcommunity-list', comm_list] + self.session.set(path + ['description', f'VyOS-EXTCOMM-{comm_list}']) + if 'rule' not in comm_list_config: + continue + + for rule, rule_config in comm_list_config['rule'].items(): + if 'action' in rule_config: + self.session.set(path + ['rule', rule, 'action', rule_config['action']]) + if 'regex' in rule_config: + self.session.set(path + ['rule', rule, 'regex', rule_config['regex']]) + + self.session.commit() + + config = getFRRconfig('bgp extcommunity-list') + for comm_list, comm_list_config in test_data.items(): + if 'rule' not in comm_list_config: + continue + + seq = '5' + for rule, rule_config in comm_list_config['rule'].items(): + # if the community is not a number but a name, the expanded + # keyword is used + expanded = '' + if not comm_list.isnumeric(): + expanded = ' expanded' + tmp = f'bgp extcommunity-list{expanded} {comm_list} seq {seq}' + + if rule_config['action'] == 'permit': + tmp += ' permit' + else: + tmp += ' deny' + + tmp += ' ' + rule_config['regex'] + + self.assertIn(tmp, config) + seq = int(seq) + 5 + + + def test_large_community_list(self): + test_data = { + 'foo' : { + 'rule' : { + '10' : { + 'action' : 'permit', + 'regex' : '667:123:100', + }, + }, + }, + 'bar' : { + 'rule' : { + '10' : { + 'action' : 'permit', + 'regex' : '65000:120:10', + }, + '20' : { + 'action' : 'permit', + 'regex' : '65000:120:20', + }, + '30' : { + 'action' : 'permit', + 'regex' : '65000:120:30', + }, + }, + }, + } + + for comm_list, comm_list_config in test_data.items(): + path = base_path + ['large-community-list', comm_list] + self.session.set(path + ['description', f'VyOS-LARGECOMM-{comm_list}']) + if 'rule' not in comm_list_config: + continue + + for rule, rule_config in comm_list_config['rule'].items(): + if 'action' in rule_config: + self.session.set(path + ['rule', rule, 'action', rule_config['action']]) + if 'regex' in rule_config: + self.session.set(path + ['rule', rule, 'regex', rule_config['regex']]) + + self.session.commit() + + config = getFRRconfig('bgp large-community-list') + for comm_list, comm_list_config in test_data.items(): + if 'rule' not in comm_list_config: + continue + + seq = '5' + for rule, rule_config in comm_list_config['rule'].items(): + tmp = f'bgp large-community-list expanded {comm_list} seq {seq}' + + if rule_config['action'] == 'permit': + tmp += ' permit' + else: + tmp += ' deny' + + tmp += ' ' + rule_config['regex'] + + self.assertIn(tmp, config) + seq = int(seq) + 5 + + + def test_prefix_list(self): + test_data = { + 'foo' : { + 'rule' : { + '10' : { + 'action' : 'permit', + 'prefix' : '10.0.0.0/8', + 'ge' : '16', + 'le' : '24', + }, + '20' : { + 'action' : 'deny', + 'prefix' : '172.16.0.0/12', + 'ge' : '16', + }, + '30' : { + 'action' : 'permit', + 'prefix' : '192.168.0.0/16', + }, + }, + }, + 'bar' : { + 'rule' : { + '10' : { + 'action' : 'permit', + 'prefix' : '10.0.10.0/24', + 'ge' : '25', + 'le' : '26', + }, + '20' : { + 'action' : 'deny', + 'prefix' : '10.0.20.0/24', + 'le' : '25', + }, + '25' : { + 'action' : 'permit', + 'prefix' : '10.0.25.0/24', + }, + }, + }, + } + + for prefix_list, prefix_list_config in test_data.items(): + path = base_path + ['prefix-list', prefix_list] + self.session.set(path + ['description', f'VyOS-PFX-LIST-{prefix_list}']) + if 'rule' not in prefix_list_config: + continue + + for rule, rule_config in prefix_list_config['rule'].items(): + if 'action' in rule_config: + self.session.set(path + ['rule', rule, 'action', rule_config['action']]) + if 'prefix' in rule_config: + self.session.set(path + ['rule', rule, 'prefix', rule_config['prefix']]) + if 'ge' in rule_config: + self.session.set(path + ['rule', rule, 'ge', rule_config['ge']]) + if 'le' in rule_config: + self.session.set(path + ['rule', rule, 'le', rule_config['le']]) + + self.session.commit() + + config = getFRRconfig('ip prefix-list') + for prefix_list, prefix_list_config in test_data.items(): + if 'rule' not in prefix_list_config: + continue + + for rule, rule_config in prefix_list_config['rule'].items(): + tmp = f'ip prefix-list {prefix_list} seq {rule}' + + if rule_config['action'] == 'permit': + tmp += ' permit' + else: + tmp += ' deny' + + tmp += ' ' + rule_config['prefix'] + + if 'ge' in rule_config: + tmp += ' ge ' + rule_config['ge'] + if 'le' in rule_config: + tmp += ' le ' + rule_config['le'] + + self.assertIn(tmp, config) + + + def test_prefix_list6(self): + test_data = { + 'foo' : { + 'rule' : { + '10' : { + 'action' : 'permit', + 'prefix' : '2001:db8::/32', + 'ge' : '40', + 'le' : '48', + }, + '20' : { + 'action' : 'deny', + 'prefix' : '2001:db8::/32', + 'ge' : '48', + }, + '30' : { + 'action' : 'permit', + 'prefix' : '2001:db8:1000::/64', + }, + }, + }, + 'bar' : { + 'rule' : { + '10' : { + 'action' : 'permit', + 'prefix' : '2001:db8:100::/40', + 'ge' : '48', + }, + '20' : { + 'action' : 'permit', + 'prefix' : '2001:db8:200::/40', + 'ge' : '48', + }, + '25' : { + 'action' : 'deny', + 'prefix' : '2001:db8:300::/40', + 'le' : '64', + }, + }, + }, + } + + for prefix_list, prefix_list_config in test_data.items(): + path = base_path + ['prefix-list6', prefix_list] + self.session.set(path + ['description', f'VyOS-PFX-LIST-{prefix_list}']) + if 'rule' not in prefix_list_config: + continue + + for rule, rule_config in prefix_list_config['rule'].items(): + if 'action' in rule_config: + self.session.set(path + ['rule', rule, 'action', rule_config['action']]) + if 'prefix' in rule_config: + self.session.set(path + ['rule', rule, 'prefix', rule_config['prefix']]) + if 'ge' in rule_config: + self.session.set(path + ['rule', rule, 'ge', rule_config['ge']]) + if 'le' in rule_config: + self.session.set(path + ['rule', rule, 'le', rule_config['le']]) + + self.session.commit() + + config = getFRRconfig('ipv6 prefix-list') + for prefix_list, prefix_list_config in test_data.items(): + if 'rule' not in prefix_list_config: + continue + + for rule, rule_config in prefix_list_config['rule'].items(): + tmp = f'ipv6 prefix-list {prefix_list} seq {rule}' + + if rule_config['action'] == 'permit': + tmp += ' permit' + else: + tmp += ' deny' + + tmp += ' ' + rule_config['prefix'] + + if 'ge' in rule_config: + tmp += ' ge ' + rule_config['ge'] + if 'le' in rule_config: + tmp += ' le ' + rule_config['le'] + + self.assertIn(tmp, config) + +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 f8c1f6a57..607dfc116 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -390,7 +390,7 @@ class TestProtocolsBGP(unittest.TestCase): } # We want to redistribute ... - redistributes = ['connected', 'kernel', 'ospf', 'rip', 'static'] + redistributes = ['connected', 'isis', 'kernel', 'ospf', 'rip', 'static'] for redistribute in redistributes: self.session.set(base_path + ['address-family', 'ipv4-unicast', 'redistribute', redistribute]) diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py index ce30f6a7d..0ca8bb3bd 100755 --- a/smoketest/scripts/cli/test_protocols_ospf.py +++ b/smoketest/scripts/cli/test_protocols_ospf.py @@ -88,7 +88,7 @@ class TestProtocolsOSPF(unittest.TestCase): def test_ospf_03_access_list(self): acl = '100' seq = '10' - protocols = ['bgp', 'connected', 'kernel', 'rip', 'static'] + protocols = ['bgp', 'connected', 'isis', 'kernel', 'rip', 'static'] self.session.set(['policy', 'access-list', acl, 'rule', seq, 'action', 'permit']) self.session.set(['policy', 'access-list', acl, 'rule', seq, 'source', 'any']) @@ -215,7 +215,7 @@ class TestProtocolsOSPF(unittest.TestCase): def test_ospf_08_redistribute(self): metric = '15' metric_type = '1' - redistribute = ['bgp', 'connected', 'kernel', 'rip', 'static'] + redistribute = ['bgp', 'connected', 'isis', 'kernel', 'rip', 'static'] for protocol in redistribute: self.session.set(base_path + ['redistribute', protocol, 'metric', metric]) diff --git a/smoketest/scripts/cli/test_protocols_rip.py b/smoketest/scripts/cli/test_protocols_rip.py index 2c5c9030a..f42ea0c0a 100755 --- a/smoketest/scripts/cli/test_protocols_rip.py +++ b/smoketest/scripts/cli/test_protocols_rip.py @@ -71,7 +71,7 @@ class TestProtocolsRIP(unittest.TestCase): interfaces = Section.interfaces('ethernet') neighbors = ['1.2.3.4', '1.2.3.5', '1.2.3.6'] networks = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'] - redistribute = ['bgp', 'connected', 'kernel', 'ospf', 'static'] + redistribute = ['bgp', 'connected', 'isis', 'kernel', 'ospf', 'static'] timer_garbage = '888' timer_timeout = '1000' timer_update = '90' diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index bf4650773..378f400b8 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -30,6 +30,7 @@ from vyos.configverify import verify_mtu from vyos.configverify import verify_mtu_ipv6 from vyos.configverify import verify_vlan_config from vyos.configverify import verify_vrf +from vyos.ethtool import Ethtool from vyos.ifconfig import EthernetIf from vyos.template import render from vyos.util import call @@ -82,11 +83,31 @@ def verify(ethernet): driver = EthernetIf(ifname).get_driver_name() # T3342 - Xen driver requires special treatment - if driver == "vif": + if driver == 'vif': if int(ethernet['mtu']) > 1500 and dict_search('offload.sg', ethernet) == None: raise ConfigError('Xen netback drivers requires scatter-gatter offloading '\ 'for MTU size larger then 1500 bytes') + ethtool = Ethtool(ifname) + if 'ring_buffer' in ethernet: + max_rx = ethtool.get_rx_buffer() + if not max_rx: + raise ConfigError('Driver does not support RX ring-buffer configuration!') + + max_tx = ethtool.get_tx_buffer() + if not max_tx: + raise ConfigError('Driver does not support TX ring-buffer configuration!') + + rx = dict_search('ring_buffer.rx', ethernet) + if rx and int(rx) > int(max_rx): + raise ConfigError(f'Driver only supports a maximum RX ring-buffer '\ + f'size of "{max_rx}" bytes!') + + tx = dict_search('ring_buffer.tx', ethernet) + if tx and int(tx) > int(max_tx): + raise ConfigError(f'Driver only supports a maximum TX ring-buffer '\ + f'size of "{max_tx}" bytes!') + # XDP requires multiple TX queues if 'xdp' in ethernet: queues = glob(f'/sys/class/net/{ifname}/queues/tx-*') diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index 034bd6dd1..87da214a8 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -140,7 +140,6 @@ def apply(tunnel): 'parameters.ip.ttl' : 'ttl', 'parameters.ip.tos' : 'tos', 'parameters.ip.key' : 'key', - 'parameters.ipv6.encaplimit' : 'encaplimit' } # Add additional IPv6 options if tunnel is IPv6 aware diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index baf5c4159..1ce2d3e7c 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -23,6 +23,7 @@ from vyos.configdict import dict_merge from vyos.template import render_to_string from vyos.util import call from vyos.util import dict_search +from vyos.validate import is_addr_assigned from vyos import ConfigError from vyos import frr from vyos import airbag @@ -99,6 +100,13 @@ def verify(bgp): raise ConfigError(f'Specified peer-group "{peer_group}" for '\ f'neighbor "{neighbor}" does not exist!') + # ttl-security and ebgp-multihop can't be used in the same configration + if 'ebgp_multihop' in peer_config and 'ttl_security' in peer_config: + raise ConfigError('You can\'t set both ebgp-multihop and ttl-security hops') + + # Check spaces in the password + if 'password' in peer_config and ' ' in peer_config['password']: + raise ConfigError('You can\'t use spaces in the password') # Some checks can/must only be done on a neighbor and not a peer-group if neighbor == 'neighbor': @@ -107,6 +115,10 @@ def verify(bgp): if not verify_remote_as(peer_config, asn_config): raise ConfigError(f'Neighbor "{peer}" remote-as must be set!') + # Check if neighbor address is assigned as system interface address + if is_addr_assigned(peer): + raise ConfigError(f'Can\'t configure local address as neighbor "{peer}"') + for afi in ['ipv4_unicast', 'ipv6_unicast', 'l2vpn_evpn']: # Bail out early if address family is not configured if 'address_family' not in peer_config or afi not in peer_config['address_family']: diff --git a/src/conf_mode/service_console-server.py b/src/conf_mode/service_console-server.py index 0e5fc75b0..6e94a19ae 100755 --- a/src/conf_mode/service_console-server.py +++ b/src/conf_mode/service_console-server.py @@ -25,7 +25,8 @@ from vyos.util import call from vyos.xml import defaults from vyos import ConfigError -config_file = r'/run/conserver/conserver.cf' +config_file = '/run/conserver/conserver.cf' +dropbear_systemd_file = '/etc/systemd/system/dropbear@{port}.service.d/override.conf' def get_config(config=None): if config: @@ -75,9 +76,22 @@ def generate(proxy): return None render(config_file, 'conserver/conserver.conf.tmpl', proxy) + if 'device' in proxy: + for device in proxy['device']: + if 'ssh' not in proxy['device'][device]: + continue + + tmp = { + 'device' : device, + 'port' : proxy['device'][device]['ssh']['port'], + } + render(dropbear_systemd_file.format(**tmp), + 'conserver/dropbear@.service.tmpl', tmp) + return None def apply(proxy): + call('systemctl daemon-reload') call('systemctl stop dropbear@*.service conserver-server.service') if not proxy: @@ -89,9 +103,10 @@ def apply(proxy): if 'device' in proxy: for device in proxy['device']: - if 'ssh' in proxy['device'][device]: - port = proxy['device'][device]['ssh']['port'] - call(f'systemctl restart dropbear@{device}.service') + if 'ssh' not in proxy['device'][device]: + continue + port = proxy['device'][device]['ssh']['port'] + call(f'systemctl restart dropbear@{port}.service') return None diff --git a/src/conf_mode/vrrp.py b/src/conf_mode/vrrp.py index 4510dd3e7..680a80859 100755 --- a/src/conf_mode/vrrp.py +++ b/src/conf_mode/vrrp.py @@ -75,6 +75,7 @@ def get_config(config=None): group["backup_script"] = config.return_value("transition-script backup") group["fault_script"] = config.return_value("transition-script fault") group["stop_script"] = config.return_value("transition-script stop") + group["script_mode_force"] = config.exists("transition-script mode-force") if config.exists("no-preempt"): group["preempt"] = False @@ -183,6 +184,11 @@ def verify(data): if isinstance(pa, IPv4Address): raise ConfigError("VRRP group {0} uses IPv6 but its peer-address is IPv4".format(group["name"])) + # Warn the user about the deprecated mode-force option + if group['script_mode_force']: + print("""Warning: "transition-script mode-force" VRRP option is deprecated and will be removed in VyOS 1.4.""") + print("""It's no longer necessary, so you can safely remove it from your config now.""") + # Disallow same VRID on multiple interfaces _groups = sorted(vrrp_groups, key=(lambda x: x["interface"])) count = len(_groups) - 1 diff --git a/src/migration-scripts/nat/4-to-5 b/src/migration-scripts/nat/4-to-5 index dda191719..b791996e2 100755 --- a/src/migration-scripts/nat/4-to-5 +++ b/src/migration-scripts/nat/4-to-5 @@ -36,9 +36,15 @@ if not config.exists(['nat']): exit(0) else: for direction in ['source', 'destination']: + # If a node doesn't exist, we obviously have nothing to do. if not config.exists(['nat', direction]): continue + # However, we also need to handle the case when a 'source' or 'destination' sub-node does exist, + # but there are no rules under it. + if not config.list_nodes(['nat', direction]): + continue + for rule in config.list_nodes(['nat', direction, 'rule']): base = ['nat', direction, 'rule', rule] diff --git a/src/services/vyos-configd b/src/services/vyos-configd index 3bd516463..1e60e53df 100755 --- a/src/services/vyos-configd +++ b/src/services/vyos-configd @@ -34,6 +34,8 @@ from vyos import ConfigError CFG_GROUP = 'vyattacfg' +script_stdout_log = '/tmp/vyos-configd-script-stdout' + debug = True logger = logging.getLogger(__name__) @@ -60,7 +62,8 @@ configd_env_unset_file = os.path.join(directories['data'], 'vyos-configd-env-uns # sourced on entering config session configd_env_file = '/etc/default/vyos-configd-env' -session_tty = None +session_out = None +session_mode = None def key_name_from_file_name(f): return os.path.splitext(f)[0] @@ -109,7 +112,7 @@ include_set = {key_name_from_file_name(f) for f in filenames if f in include} def run_script(script, config) -> int: config.set_level([]) try: - with open(session_tty, 'w') as f, redirect_stdout(f): + with open(session_out, session_mode) as f, redirect_stdout(f): with redirect_stderr(f): c = script.get_config(config) script.verify(c) @@ -117,7 +120,7 @@ def run_script(script, config) -> int: script.apply(c) except ConfigError as e: logger.critical(e) - with open(session_tty, 'w') as f, redirect_stdout(f): + with open(session_out, session_mode) as f, redirect_stdout(f): print(f"{e}\n") return R_ERROR_COMMIT except Exception as e: @@ -127,7 +130,8 @@ def run_script(script, config) -> int: return R_SUCCESS def initialization(socket): - global session_tty + global session_out + global session_mode # Reset config strings: active_string = '' session_string = '' @@ -155,9 +159,15 @@ def initialization(socket): logger.debug(f"config session pid is {pid_string}") try: - session_tty = os.readlink(f"/proc/{pid_string}/fd/1") + session_out = os.readlink(f"/proc/{pid_string}/fd/1") + session_mode = 'w' except FileNotFoundError: - session_tty = None + session_out = None + + # if not a 'live' session, for example on boot, write to file + if not session_out or '/dev/pts' not in session_out: + session_out = script_stdout_log + session_mode = 'a' try: configsource = ConfigSourceString(running_config_text=active_string, diff --git a/src/system/on-dhcp-event.sh b/src/system/on-dhcp-event.sh index a062dc810..49e53d7e1 100755 --- a/src/system/on-dhcp-event.sh +++ b/src/system/on-dhcp-event.sh @@ -21,21 +21,20 @@ client_mac=$4 domain=$5 hostsd_client="/usr/bin/vyos-hostsd-client" -if [ -z "$client_name" ]; then - logger -s -t on-dhcp-event "Client name was empty, using MAC \"$client_mac\" instead" - client_name=$(echo "client-"$client_mac | tr : -) -fi - -if [ "$domain" == "..YYZ!" ]; then - client_fqdn_name=$client_name - client_search_expr=$client_name -else - client_fqdn_name=$client_name.$domain - client_search_expr="$client_name\\.$domain" -fi - case "$action" in commit) # add mapping for new lease + if [ -z "$client_name" ]; then + logger -s -t on-dhcp-event "Client name was empty, using MAC \"$client_mac\" instead" + client_name=$(echo "client-"$client_mac | tr : -) + fi + + if [ "$domain" == "..YYZ!" ]; then + client_fqdn_name=$client_name + client_search_expr=$client_name + else + client_fqdn_name=$client_name.$domain + client_search_expr="$client_name\\.$domain" + fi $hostsd_client --add-hosts "$client_fqdn_name,$client_ip" --tag "dhcp-server-$client_ip" --apply exit 0 ;; diff --git a/src/systemd/dropbear@.service b/src/systemd/dropbear@.service index a3fde5708..acf926af9 100644 --- a/src/systemd/dropbear@.service +++ b/src/systemd/dropbear@.service @@ -8,9 +8,8 @@ StartLimitIntervalSec=0 [Service] Type=forking -ExecStartPre=/usr/bin/bash -c '/usr/bin/systemctl set-environment PORT=$(cli-shell-api returnActiveValue service console-server device "%I" ssh port)' -ExecStart=-/usr/sbin/dropbear -w -j -k -r /etc/dropbear/dropbear_rsa_host_key -c "/usr/bin/console %I" -P /run/conserver/dropbear.%I.pid -p ${PORT} -PIDFile=/run/conserver/dropbear.%I.pid +ExecStart=/usr/sbin/dropbear -w -j -k -r /etc/dropbear/dropbear_rsa_host_key -P /run/dropbear/dropbear.%I.pid -p %I +PIDFile=/run/dropbear/dropbear.%I.pid KillMode=process Restart=always RestartSec=10 diff --git a/src/tests/test_util.py b/src/tests/test_util.py index f7405cbde..22bc085c5 100644 --- a/src/tests/test_util.py +++ b/src/tests/test_util.py @@ -17,11 +17,7 @@ from unittest import TestCase from vyos.util import mangle_dict_keys - class TestVyOSUtil(TestCase): - def setUp(self): - pass - def test_key_mangline(self): data = {"foo-bar": {"baz-quux": None}} expected_data = {"foo_bar": {"baz_quux": None}} diff --git a/src/validators/interface-name b/src/validators/interface-name index 8e337b401..72e9fd54a 100755 --- a/src/validators/interface-name +++ b/src/validators/interface-name @@ -17,7 +17,7 @@ import re import sys -pattern = '^(bond|br|dum|en|ersp|eth|gnv|lan|l2tp|l2tpeth|macsec|peth|ppp|pppoe|pptp|sstp|tun|vti|vtun|vxlan|wg|wlan|wlm)[0-9]+|lo$' +pattern = '^(bond|br|dum|en|ersp|eth|gnv|lan|l2tp|l2tpeth|macsec|peth|ppp|pppoe|pptp|sstp|tun|vti|vtun|vxlan|wg|wlan|wlm)[0-9]+(.\d+)?|lo$' if __name__ == '__main__': if len(sys.argv) != 2: |