diff options
54 files changed, 3788 insertions, 434 deletions
@@ -33,6 +33,8 @@ op_xml_obj = $(op_xml_src:.xml.in=.xml) interface_definitions: $(config_xml_obj) mkdir -p $(TMPL_DIR) + $(CURDIR)/scripts/override-default $(BUILD_DIR)/interface-definitions + find $(BUILD_DIR)/interface-definitions -type f -name "*.xml" | xargs -I {} $(CURDIR)/scripts/build-command-templates {} $(CURDIR)/schema/interface_definition.rng $(TMPL_DIR) || exit 1 # XXX: delete top level node.def's that now live in other packages @@ -45,6 +47,7 @@ interface_definitions: $(config_xml_obj) rm -f $(TMPL_DIR)/vpn/node.def rm -f $(TMPL_DIR)/vpn/ipsec/node.def rm -rf $(TMPL_DIR)/vpn/nipsec + rm -rf $(TMPL_DIR)/protocols/nospf # XXX: required until OSPF and RIP is migrated from vyatta-cfg-quagga to vyos-1x mkdir $(TMPL_DIR)/interfaces/loopback/node.tag/ipv6 diff --git a/data/templates/firewall/nftables-nat.tmpl b/data/templates/firewall/nftables-nat.tmpl index 770a24a95..5480447f2 100644 --- a/data/templates/firewall/nftables-nat.tmpl +++ b/data/templates/firewall/nftables-nat.tmpl @@ -21,18 +21,34 @@ {% set comment = 'DST-NAT-' + rule %} {% set base_log = '[NAT-DST-' + rule %} {% set interface = ' iifname "' + config.inbound_interface + '"' if config.inbound_interface is defined and config.inbound_interface != 'any' else '' %} -{% set trns_addr = 'dnat to ' + config.translation.address if config.translation is defined and config.translation.address is defined and config.translation.address is not none %} +{% if config.translation is defined and config.translation.address is defined and config.translation.address is not none %} +{# support 1:1 network translation #} +{% if config.translation.address | is_ip_network %} +{% set trns_addr = 'dnat ip prefix to ip daddr map { ' + config.source.address + ' : ' + config.translation.address + ' }' %} +{# we can now clear out the src_addr part as it's already covered in aboves map #} +{% set src_addr = '' %} +{% else %} +{% set trns_addr = 'dnat to ' + config.translation.address %} +{% endif %} +{% endif %} {% elif chain == 'POSTROUTING' %} {% set comment = 'SRC-NAT-' + rule %} {% set base_log = '[NAT-SRC-' + rule %} {% set interface = ' oifname "' + config.outbound_interface + '"' if config.outbound_interface is defined and config.outbound_interface != 'any' else '' %} -{% if config.translation is defined and config.translation.address is defined and config.translation.address == 'masquerade' %} -{% set trns_addr = config.translation.address %} -{% if config.translation.port is defined and config.translation.port is not none %} -{% set trns_addr = trns_addr + ' to ' %} +{% if config.translation is defined and config.translation.address is defined and config.translation.address is not none %} +{% if config.translation.address == 'masquerade' %} +{% set trns_addr = config.translation.address %} +{% if config.translation.port is defined and config.translation.port is not none %} +{% set trns_addr = trns_addr + ' to ' %} +{% endif %} +{# support 1:1 network translation #} +{% elif config.translation.address | is_ip_network %} +{% set trns_addr = 'snat ip prefix to ip saddr map { ' + config.source.address + ' : ' + config.translation.address + ' }' %} +{# we can now clear out the src_addr part as it's already covered in aboves map #} +{% set src_addr = '' %} +{% else %} +{% set trns_addr = 'snat to ' + config.translation.address %} {% endif %} -{% else %} -{% set trns_addr = 'snat to ' + config.translation.address if config.translation is defined and config.translation.address is defined and config.translation.address is not none %} {% endif %} {% endif %} {% set trns_port = ':' + config.translation.port if config.translation is defined and config.translation.port is defined and config.translation.port is not none %} @@ -132,7 +148,6 @@ add rule ip raw NAT_CONNTRACK counter accept {{ nat_rule(rule, config, 'PREROUTING') }} {% endfor %} {% endif %} - # # Source NAT rules build up here # diff --git a/data/templates/frr/bgp.frr.tmpl b/data/templates/frr/bgp.frr.tmpl index b3aa8fe02..68731abab 100644 --- a/data/templates/frr/bgp.frr.tmpl +++ b/data/templates/frr/bgp.frr.tmpl @@ -198,8 +198,8 @@ router bgp {{ asn }} {% endif %} ! {% if neighbor is defined and neighbor is not none %} -{% for n, config in neighbor.items() %} -{{ bgp_neighbor(n, config) }} +{% for peer, config in neighbor.items() %} +{{ bgp_neighbor(peer, config) }} {% endfor %} {% endif %} ! diff --git a/data/templates/frr/ospf.frr.tmpl b/data/templates/frr/ospf.frr.tmpl new file mode 100644 index 000000000..465034f15 --- /dev/null +++ b/data/templates/frr/ospf.frr.tmpl @@ -0,0 +1,2 @@ +! +! diff --git a/data/templates/ntp/ntp.conf.tmpl b/data/templates/ntp/ntpd.conf.tmpl index 2b56b53c3..2b56b53c3 100644 --- a/data/templates/ntp/ntp.conf.tmpl +++ b/data/templates/ntp/ntpd.conf.tmpl diff --git a/data/templates/ntp/override.conf.tmpl b/data/templates/ntp/override.conf.tmpl index e0b947686..28eb61b21 100644 --- a/data/templates/ntp/override.conf.tmpl +++ b/data/templates/ntp/override.conf.tmpl @@ -1,11 +1,14 @@ {% set vrf_command = 'ip vrf exec ' + vrf + ' ' if vrf is defined else '' %} [Unit] StartLimitIntervalSec=0 +ConditionPathExists={{config_file}} After=vyos-router.service [Service] ExecStart= -ExecStart={{vrf_command}}/usr/lib/ntp/ntp-systemd-wrapper +ExecStart={{vrf_command}}/usr/sbin/ntpd -g -p {{config_file | replace('.conf', '.pid') }} -c {{config_file}} -u ntp:ntp +PIDFile= +PIDFile={{config_file | replace('.conf', '.pid') }} Restart=always RestartSec=10 diff --git a/data/templates/openvpn/server.conf.tmpl b/data/templates/openvpn/server.conf.tmpl index b3b0c936a..79288e40f 100644 --- a/data/templates/openvpn/server.conf.tmpl +++ b/data/templates/openvpn/server.conf.tmpl @@ -281,6 +281,10 @@ compat-names # Custom options added by user (not validated) # {% for option in openvpn_option %} -{{ option }} +{% for argument in option.split('--') %} +{% if argument is defined and argument != '' %} +--{{ argument }} +{% endif %} +{% endfor %} {% endfor %} {% endif %} diff --git a/data/templates/ssh/override.conf.tmpl b/data/templates/ssh/override.conf.tmpl index 0abde6248..5f8f35e89 100644 --- a/data/templates/ssh/override.conf.tmpl +++ b/data/templates/ssh/override.conf.tmpl @@ -8,5 +8,6 @@ ConditionPathExists={{config_file}} ExecStart= ExecStart={{vrf_command}}/usr/sbin/sshd -f {{config_file}} -D $SSHD_OPTS Restart=always +RestartPreventExitStatus= RestartSec=10 RuntimeDirectoryPreserve=yes diff --git a/data/templates/vrf/vrf.conf.tmpl b/data/templates/vrf/vrf.conf.tmpl index 6d01d2b89..29c0ba08d 100644 --- a/data/templates/vrf/vrf.conf.tmpl +++ b/data/templates/vrf/vrf.conf.tmpl @@ -1,8 +1,9 @@ ### Autogenerated by vrf.py ### # # Routing table ID to name mapping reference - # id vrf name comment -{% for vrf in vrf_add %} -{{ "%-10s" | format(vrf.table) }} {{ "%-16s" | format(vrf.name) }} # {{ vrf.description }} -{% endfor %} +{% if name is defined and name is not none %} +{% for vrf, vrf_config in name.items() %} +{{ "%-10s" | format(vrf_config.table) }} {{ "%-16s" | format(vrf) }} {{ '# ' + vrf_config.description if vrf_config.description is defined and vrf_config.description is not none }} +{% endfor %} +{% endif %} diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst index 92948de12..5fadddc86 100644 --- a/debian/vyos-1x.postinst +++ b/debian/vyos-1x.postinst @@ -20,6 +20,34 @@ if ! grep -q '^minion' /etc/passwd; then adduser --quiet minion users fi +# OpenVPN should get its own user +if ! grep -q '^openvpn' /etc/passwd; then + adduser --quiet --firstuid 100 --system --group --shell /usr/sbin/nologin openvpn +fi + +# Add RADIUS operator user for RADIUS authenticated users to map to +if ! grep -q '^radius_user' /etc/passwd; then + adduser --quiet --firstuid 1001 --disabled-login --ingroup users --gecos "radius user" --shell /bin/vbash radius_user + adduser --quiet radius_user frrvty + adduser --quiet radius_user vyattaop + adduser --quiet radius_user operator + adduser --quiet radius_user adm + adduser --quiet radius_user dip + adduser --quiet radius_user users +fi + +# Add RADIUS admin user for RADIUS authenticated users to map to +if ! grep -q '^radius_priv_user' /etc/passwd; then + adduser --quiet --firstuid 1001 --disabled-login --ingroup vyattacfg --gecos "radius privileged user" --shell /bin/vbash radius_priv_user + adduser --quiet radius_priv_user frrvty + adduser --quiet radius_priv_user vyattacfg + adduser --quiet radius_priv_user sudo + adduser --quiet radius_priv_user adm + adduser --quiet radius_priv_user dip + adduser --quiet radius_priv_user disk + adduser --quiet radius_priv_user users +fi + # add hostsd group for vyos-hostsd if ! grep -q '^hostsd' /etc/group; then addgroup --quiet --system hostsd diff --git a/interface-definitions/dhcp-server.xml.in b/interface-definitions/dhcp-server.xml.in index 912e4eaf7..aba2bea8a 100644 --- a/interface-definitions/dhcp-server.xml.in +++ b/interface-definitions/dhcp-server.xml.in @@ -335,11 +335,14 @@ </leafNode> <leafNode name="mac-address"> <properties> - <help>MAC address of static mapping [REQUIRED]</help> + <help>Media Access Control (MAC) address</help> <valueHelp> <format>h:h:h:h:h:h</format> - <description>MAC address used in static mapping [REQUIRED]</description> + <description>Hardware (MAC) address</description> </valueHelp> + <constraint> + <validator name="mac-address"/> + </constraint> </properties> </leafNode> <leafNode name="static-mapping-parameters"> diff --git a/interface-definitions/include/ospf-metric.xml.i b/interface-definitions/include/ospf-metric.xml.i new file mode 100644 index 000000000..771fda02d --- /dev/null +++ b/interface-definitions/include/ospf-metric.xml.i @@ -0,0 +1,33 @@ +<!-- included start from ospf-metric.xml.i --> +<leafNode name="metric"> + <properties> + <help>OSPF default metric</help> + <valueHelp> + <format>u32:0-16777214</format> + <description>Default metric</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-16777214"/> + </constraint> + </properties> +</leafNode> +<leafNode name="metric-type"> + <properties> + <help>OSPF metric type for default routes</help> + <valueHelp> + <format>u32:1-2</format> + <description>Metric type for default routes (default 2)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2"/> + </constraint> + </properties> +</leafNode> +<leafNode name="route-map"> + <properties> + <help>Route map reference</help> + <completionHelp> + <path>policy route-map</path> + </completionHelp> + </properties> +</leafNode> diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in index c32c0ca32..63c543f33 100644 --- a/interface-definitions/interfaces-bridge.xml.in +++ b/interface-definitions/interfaces-bridge.xml.in @@ -86,6 +86,12 @@ #include <include/interface-ipv6-options.xml.i> #include <include/interface-mac.xml.i> #include <include/interface-mirror.xml.i> + <leafNode name="enable-vlan"> + <properties> + <help>Enable VLAN aware bridge</help> + <valueless/> + </properties> + </leafNode> <leafNode name="max-age"> <properties> <help>Interval at which neighbor bridges are removed</help> @@ -138,7 +144,7 @@ <description>VLAN id range allowed on this interface (use '-' as delimiter)</description> </valueHelp> <constraint> - <regex>^([0-9]{1,4}-[0-9]{1,4})|([0-9]{1,4})$</regex> + <validator name="allowed-vlan"/> </constraint> <constraintErrorMessage>not a valid VLAN ID value or range</constraintErrorMessage> <multi/> @@ -196,7 +202,6 @@ <valueless/> </properties> </leafNode> - #include <include/vif-s.xml.i> #include <include/vif.xml.i> </children> </tagNode> diff --git a/interface-definitions/protocols-bgp.xml.in b/interface-definitions/protocols-bgp.xml.in index 96fd74db7..12437128c 100644 --- a/interface-definitions/protocols-bgp.xml.in +++ b/interface-definitions/protocols-bgp.xml.in @@ -545,7 +545,7 @@ <help>Half-life time for dampening [REQUIRED]</help> <valueHelp> <format>u32:1-45</format> - <description>Half-life penalty in seconds</description> + <description>Half-life penalty in minutes</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-45"/> @@ -557,7 +557,7 @@ <help>Maximum duration to suppress a stable route [REQUIRED]</help> <valueHelp> <format>u32:1-255</format> - <description>Maximum suppress duration in seconds</description> + <description>Maximum suppress duration in minutes</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-255"/> @@ -566,10 +566,10 @@ </leafNode> <leafNode name="re-use"> <properties> - <help>Time to start reusing a route [REQUIRED]</help> + <help>Threshold to start reusing a route [REQUIRED]</help> <valueHelp> <format>u32:1-20000</format> - <description>Re-use time in seconds</description> + <description>Re-use penalty points</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-20000"/> @@ -581,7 +581,7 @@ <help>When to start suppressing a route [REQUIRED]</help> <valueHelp> <format>u32:1-20000</format> - <description>Start-suppress-time</description> + <description>Start-suppress penalty points</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-20000"/> diff --git a/interface-definitions/protocols-ospf.xml.in b/interface-definitions/protocols-ospf.xml.in new file mode 100644 index 000000000..4d5b84be0 --- /dev/null +++ b/interface-definitions/protocols-ospf.xml.in @@ -0,0 +1,840 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Protocol OSPF configuration --> +<interfaceDefinition> + <node name="protocols"> + <children> + <node name="nospf" owner="${vyos_conf_scripts_dir}/protocols_ospf.py"> + <properties> + <help>Open Shortest Path First protocol (OSPF) parameters</help> + <priority>620</priority> + </properties> + <children> + <tagNode name="access-list"> + <properties> + <help>Access list to filter networks in routing updates</help> + <valueHelp> + <format>u32</format> + <description>Access-list number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + <children> + <leafNode name="export"> + <properties> + <help>Filter for outgoing routing update [REQUIRED]</help> + <completionHelp> + <list>bgp connected kernel rip static</list> + </completionHelp> + <valueHelp> + <format>bgp</format> + <description>Filter BGP routes</description> + </valueHelp> + <valueHelp> + <format>connected</format> + <description>Filter connected routes</description> + </valueHelp> + <valueHelp> + <format>kernel</format> + <description>Filter Kernel routes</description> + </valueHelp> + <valueHelp> + <format>rip</format> + <description>Filter RIP routes</description> + </valueHelp> + <valueHelp> + <format>static</format> + <description>Filter static routes</description> + </valueHelp> + <constraint> + <regex>^(bgp|connected|kernel|rip|static)$</regex> + </constraint> + <constraintErrorMessage>Must be bgp, connected, kernel, rip, or static</constraintErrorMessage> + <multi/> + </properties> + </leafNode> + </children> + </tagNode> + <tagNode name="area"> + <properties> + <help>OSPF Area</help> + <valueHelp> + <format>u32</format> + <description>OSPF area in decimal notation</description> + </valueHelp> + <valueHelp> + <format>ipv4</format> + <description>OSPF area in dotted decimal notation</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + <validator name="ip-address"/> + </constraint> + </properties> + <children> + <node name="area-type"> + <properties> + <help>Area type</help> + </properties> + <children> + <leafNode name="normal"> + <properties> + <help>Normal OSPF area</help> + <valueless/> + </properties> + </leafNode> + <node name="nssa"> + <properties> + <help>Nssa OSPF area</help> + </properties> + <children> + <leafNode name="default-cost"> + <properties> + <help>Summary-default cost of nssa area</help> + <valueHelp> + <format>u32:0-16777215</format> + <description>Summary default cost</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-16777215"/> + </constraint> + </properties> + </leafNode> + <leafNode name="no-summary"> + <properties> + <help>Do not inject inter-area routes into stub</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="translate"> + <properties> + <help>Nssa-abr</help> + <completionHelp> + <list>always candidate never</list> + </completionHelp> + <valueHelp> + <format>always</format> + <description>NSSA-ABR to always translate</description> + </valueHelp> + <valueHelp> + <format>candidate</format> + <description>NSSA-ABR for translate election (default)</description> + </valueHelp> + <valueHelp> + <format>never</format> + <description>NSSA-ABR to never translate</description> + </valueHelp> + <constraint> + <regex>^(always|candidate|never)$</regex> + </constraint> + </properties> + </leafNode> + </children> + </node> + <node name="stub"> + <properties> + <help>Stub OSPF area</help> + </properties> + <children> + <leafNode name="default-cost"> + <properties> + <help>Summary-default cost of nssa area</help> + <valueHelp> + <format>u32:0-16777215</format> + <description>Summary default cost</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-16777215"/> + </constraint> + </properties> + </leafNode> + <leafNode name="no-summary"> + <properties> + <help>Do not inject inter-area routes into stub</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <leafNode name="authentication"> + <properties> + <help>OSPF area authentication type</help> + <completionHelp> + <list>plaintext-password md5</list> + </completionHelp> + <valueHelp> + <format>plaintext-password</format> + <description>Use plain-text authentication</description> + </valueHelp> + <valueHelp> + <format>md5</format> + <description>Use md5 authentication</description> + </valueHelp> + <constraint> + <regex>^(plaintext-password|md5)$</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="network"> + <properties> + <help>OSPF network [REQUIRED]</help> + <valueHelp> + <format>ipv4net</format> + <description>OSPF network [REQUIRED]</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + <tagNode name="range"> + <properties> + <help>Summarize routes matching prefix (border routers only)</help> + </properties> + <children> + <leafNode name="cost"> + <properties> + <help>Metric for this range</help> + <valueHelp> + <format>u32:0-16777215</format> + <description>Metric for this range</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-16777215"/> + </constraint> + </properties> + </leafNode> + <leafNode name="not-advertise"> + <properties> + <help>Do not advertise this range</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="substitute"> + <properties> + <help>Announce area range as another prefix</help> + <valueHelp> + <format>ipv4net</format> + <description>Announce area range as another prefix</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + <leafNode name="shortcut"> + <properties> + <help>Area shortcut mode</help> + <completionHelp> + <list>default disable enable</list> + </completionHelp> + <valueHelp> + <format>default</format> + <description>Set default</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable shortcutting mode</description> + </valueHelp> + <valueHelp> + <format>enable</format> + <description>Enable shortcutting mode</description> + </valueHelp> + <constraint> + <regex>^(default|disable|enable)$</regex> + </constraint> + </properties> + </leafNode> + <tagNode name="virtual-link"> + <properties> + <help>Virtual link</help> + </properties> + <children> + <node name="authentication"> + <properties> + <help>Authentication</help> + </properties> + <children> + <node name="md5"> + <properties> + <help>MD5 key id</help> + </properties> + <children> + <tagNode name="key-id"> + <properties> + <help>MD5 key id</help> + <valueHelp> + <format>u32:1-255</format> + <description>MD5 key id</description> + </valueHelp> + </properties> + <children> + <leafNode name="md5-key"> + <properties> + <help>MD5 authentication type</help> + <valueHelp> + <format>txt</format> + <description>MD5 Key (16 characters or less)</description> + </valueHelp> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + <leafNode name="plaintext-password"> + <properties> + <help>Plain text password</help> + <valueHelp> + <format>txt</format> + <description>Plain text password (8 characters or less)</description> + </valueHelp> + </properties> + </leafNode> + </children> + </node> + <leafNode name="dead-interval"> + <properties> + <help>Interval after which a neighbor is declared dead</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Neighbor dead interval (seconds)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="hello-interval"> + <properties> + <help>Interval between hello packets</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Hello interval (seconds)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="retransmit-interval"> + <properties> + <help>Interval between retransmitting lost link state advertisements</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Retransmit interval (seconds)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="transmit-delay"> + <properties> + <help>Link state transmit delay</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Link state transmit delay (seconds)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + <node name="auto-cost"> + <properties> + <help>Calculate OSPF interface cost according to bandwidth</help> + </properties> + <children> + <leafNode name="reference-bandwidth"> + <properties> + <help>Reference bandwidth method to assign OSPF cost</help> + <valueHelp> + <format>u32:1-4294967</format> + <description>Reference bandwidth cost in Mbits/sec (default 100)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4294967"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <node name="default-information"> + <properties> + <help>Control distribution of default information</help> + </properties> + <children> + <node name="originate"> + <properties> + <help>Distribute a default route</help> + </properties> + <children> + <leafNode name="always"> + <properties> + <help>Always advertise default route</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="metric"> + <properties> + <help>OSPF default metric</help> + <valueHelp> + <format>u32:0-16777214</format> + <description>Default metric</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-16777214"/> + </constraint> + </properties> + </leafNode> + <leafNode name="metric-type"> + <properties> + <help>OSPF metric type for default routes</help> + <valueHelp> + <format>u32:1-2</format> + <description>Metric type for default routes (default 2)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2"/> + </constraint> + </properties> + </leafNode> + <leafNode name="route-map"> + <properties> + <help>Route map reference</help> + <completionHelp> + <path>policy route-map</path> + </completionHelp> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <leafNode name="default-metric"> + <properties> + <help>Metric of redistributed routes</help> + <valueHelp> + <format>u32:0-16777214</format> + <description>Metric of redistributed routes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-16777214"/> + </constraint> + </properties> + </leafNode> + <node name="distance"> + <properties> + <help>Administrative distance</help> + </properties> + <children> + <leafNode name="global"> + <properties> + <help>OSPF administrative distance</help> + <valueHelp> + <format>u32:1-255</format> + <description>Administrative distance</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + <node name="ospf"> + <properties> + <help>OSPF administrative distance</help> + </properties> + <children> + <leafNode name="external"> + <properties> + <help>Distance for external routes</help> + <valueHelp> + <format>u32:1-255</format> + <description>Distance for external routes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + <leafNode name="inter-area"> + <properties> + <help>Distance for inter-area routes</help> + <valueHelp> + <format>u32:1-255</format> + <description>Distance for inter-area routes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + <leafNode name="intra-area"> + <properties> + <help>Distance for intra-area routes</help> + <valueHelp> + <format>u32:1-255</format> + <description>Distance for intra-area routes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <node name="log-adjacency-changes"> + <properties> + <help>Log changes in adjacency state</help> + </properties> + <children> + <leafNode name="detail"> + <properties> + <help>Log all state changes</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <node name="max-metric"> + <properties> + <help>OSPF maximum and infinite-distance metric</help> + </properties> + <children> + <node name="router-lsa"> + <properties> + <help>Advertise own Router-LSA with infinite distance (stub router)</help> + </properties> + <children> + <leafNode name="administrative"> + <properties> + <help>Administratively apply, for an indefinite period</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="on-shutdown"> + <properties> + <help>Advertise stub-router prior to full shutdown of OSPF</help> + <valueHelp> + <format>u32:5-86400</format> + <description>Time (seconds) to advertise self as stub-router</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 5-86400"/> + </constraint> + </properties> + </leafNode> + <leafNode name="on-startup"> + <properties> + <help>Automatically advertise stub Router-LSA on startup of OSPF</help> + <valueHelp> + <format>u32:5-86400</format> + <description>Time (seconds) to advertise self as stub-router</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 5-86400"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <node name="mpls-te"> + <properties> + <help>MultiProtocol Label Switching-Traffic Engineering (MPLS-TE) parameters</help> + </properties> + <children> + <leafNode name="enable"> + <properties> + <help>Enable MPLS-TE functionality</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="router-address"> + <properties> + <help>Stable IP address of the advertising router</help> + <valueHelp> + <format>ipv4</format> + <description>Stable IP address of the advertising router</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <tagNode name="neighbor"> + <properties> + <help>Neighbor IP address</help> + <valueHelp> + <format>ipv4</format> + <description>Neighbor IP address</description> + </valueHelp> + </properties> + <children> + <leafNode name="poll-interval"> + <properties> + <help>Dead neighbor polling interval</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Seconds between dead neighbor polling interval (default 60)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="priority"> + <properties> + <help>Neighbor priority in seconds</help> + <valueHelp> + <format>u32:0-255</format> + <description>Neighbor priority (default 0)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-255"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + <node name="parameters"> + <properties> + <help>OSPF specific parameters</help> + </properties> + <children> + <leafNode name="abr-type"> + <properties> + <help>OSPF ABR type</help> + <completionHelp> + <list>cisco ibm shortcut standard</list> + </completionHelp> + <valueHelp> + <format>cisco</format> + <description>Cisco ABR type (default)</description> + </valueHelp> + <valueHelp> + <format>ibm</format> + <description>Ibm ABR type</description> + </valueHelp> + <valueHelp> + <format>shortcut</format> + <description>Shortcut ABR type</description> + </valueHelp> + <valueHelp> + <format>standard</format> + <description>Standard ABR type</description> + </valueHelp> + <constraint> + <regex>^(cisco|ibm|shortcut|standard)$</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="opaque-lsa"> + <properties> + <help>Enable the Opaque-LSA capability (rfc2370)</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="rfc1583-compatibility"> + <properties> + <help>Enable rfc1583 criteria for handling AS external routes</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="router-id"> + <properties> + <help>Override the default router identifier</help> + <valueHelp> + <format>ipv4</format> + <description>Override the default router identifier</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <leafNode name="passive-interface"> + <properties> + <help>Suppress routing updates on an interface</help> + <valueHelp> + <format><interface></format> + <description>Interface to be passive (i.e. suppress routing updates)</description> + </valueHelp> + <valueHelp> + <format>default</format> + <description>Default to suppress routing updates on all interfaces</description> + </valueHelp> + <completionHelp> + <list>default</list> + <script>${vyos_completion_dir}/list_interfaces.py</script> + </completionHelp> + <multi/> + </properties> + </leafNode> + <leafNode name="passive-interface-exclude"> + <properties> + <help>Interface to exclude when using 'passive-interface default'</help> + <valueHelp> + <format><interface></format> + <description>Interface to be passive (i.e. suppress routing updates)</description> + </valueHelp> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces.py</script> + </completionHelp> + <multi/> + </properties> + </leafNode> + <node name="redistribute"> + <properties> + <help>Redistribute information from another routing protocol</help> + </properties> + <children> + <node name="bgp"> + <properties> + <help>Redistribute BGP routes</help> + </properties> + <children> + #include <include/ospf-metric.xml.i> + </children> + </node> + <node name="connected"> + <properties> + <help>Redistribute connected routes</help> + </properties> + <children> + #include <include/ospf-metric.xml.i> + </children> + </node> + <node name="kernel"> + <properties> + <help>Redistribute kernel routes</help> + </properties> + <children> + #include <include/ospf-metric.xml.i> + </children> + </node> + <node name="rip"> + <properties> + <help>Redistribute rip routes</help> + </properties> + <children> + #include <include/ospf-metric.xml.i> + </children> + </node> + <node name="static"> + <properties> + <help>Redistribute static routes</help> + </properties> + <children> + #include <include/ospf-metric.xml.i> + </children> + </node> + </children> + </node> + <node name="refresh"> + <properties> + <help>Adjust refresh parameters</help> + </properties> + <children> + <leafNode name="timers"> + <properties> + <help>Refresh timer</help> + <valueHelp> + <format>u32:10-1800</format> + <description>Timer value in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 10-1800"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <leafNode name="route-map"> + <properties> + <help>Filter routes installed in local route map</help> + <completionHelp> + <path>policy route-map</path> + </completionHelp> + </properties> + </leafNode> + <node name="timers"> + <properties> + <help>Adjust routing timers</help> + </properties> + <children> + <node name="throttle"> + <properties> + <help>Throttling adaptive timers</help> + </properties> + <children> + <node name="spf"> + <properties> + <help>OSPF SPF timers</help> + </properties> + <children> + <leafNode name="delay"> + <properties> + <help>Delay (msec) from first change received till SPF calculation</help> + <valueHelp> + <format>u32:0-600000</format> + <description>Delay in msec (default 200)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-600000"/> + </constraint> + </properties> + </leafNode> + <leafNode name="initial-holdtime"> + <properties> + <help>Initial hold time(msec) between consecutive SPF calculations</help> + <valueHelp> + <format>u32:0-600000</format> + <description>Initial hold time in msec (default 1000)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-600000"/> + </constraint> + </properties> + </leafNode> + <leafNode name="max-holdtime"> + <properties> + <help>Maximum hold time (msec)</help> + <valueHelp> + <format>u32:0-600000</format> + <description>Max hold time in msec (default 10000)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-600000"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/vrf.xml.in b/interface-definitions/vrf.xml.in index 06923971d..81c89d94b 100644 --- a/interface-definitions/vrf.xml.in +++ b/interface-definitions/vrf.xml.in @@ -40,6 +40,7 @@ </properties> </leafNode> #include <include/interface-description.xml.i> + #include <include/interface-disable.xml.i> </children> </tagNode> </children> diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index e5e758a8b..5acb1fdfe 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -126,14 +126,14 @@ def leaf_node_changed(conf, path): return None -def node_changed(conf, path): +def node_changed(conf, path, key_mangling=None): """ Check if a leaf node was altered. If it has been altered - values has been changed, or it was added/removed, we will return the old value. If nothing has been changed, None is returned """ from vyos.configdiff import get_config_diff, Diff - D = get_config_diff(conf, key_mangling=('-', '_')) + D = get_config_diff(conf, key_mangling) D.set_level(conf.get_level()) # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448 keys = D.get_child_nodes_diff(path, expand_nodes=Diff.DELETE)['delete'].keys() @@ -272,9 +272,9 @@ def has_vlan_subinterface_configured(conf, intf): old_level = conf.get_level() conf.set_level([]) - intfpath = 'interfaces ' + Section.get_config_path(intf) - if ( conf.exists(f'{intfpath} vif') or - conf.exists(f'{intfpath} vif-s')): + intfpath = ['interfaces', Section.section(intf), intf] + if ( conf.exists(intfpath + ['vif']) or + conf.exists(intfpath + ['vif-s'])): ret = True conf.set_level(old_level) diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index 76520f2ba..1bd617a05 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -22,6 +22,7 @@ from vyos.validate import assert_positive from vyos.util import cmd from vyos.util import dict_search from vyos.configdict import get_vlan_ids +from vyos.configdict import list_diff @Interface.register class BridgeIf(Interface): @@ -274,20 +275,36 @@ class BridgeIf(Interface): for member in (tmp or []): if member in interfaces(): self.del_port(member) - vlan_filter = 0 - vlan_del = set() - vlan_add = set() + # enable/disable Vlan Filter + vlan_filter = '1' if 'enable_vlan' in config else '0' + self.set_vlan_filter(vlan_filter) + + if int(vlan_filter): + add_vlan = [] + cur_vlan_ids = get_vlan_ids(ifname) + + tmp = dict_search('vif', config) + if tmp: + for vif, vif_config in tmp.items(): + add_vlan.append(vif) + + # 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} self' + self._cmd(cmd) + + for vlan in add_vlan: + cmd = f'bridge vlan add dev {ifname} vid {vlan} self' + self._cmd(cmd) + + # VLAN of bridge parent interface is always 1 + # VLAN 1 is the default VLAN for all unlabeled packets + cmd = f'bridge vlan add dev {ifname} vid 1 pvid untagged self' + self._cmd(cmd) tmp = dict_search('member.interface', config) if tmp: - if self.get_vlan_filter(): - bridge_vlan_ids = get_vlan_ids(ifname) - # Delete VLAN ID for the bridge - if 1 in bridge_vlan_ids: - bridge_vlan_ids.remove(1) - for vlan in bridge_vlan_ids: - vlan_del.add(str(vlan)) for interface, interface_config in tmp.items(): # if interface does yet not exist bail out early and @@ -315,63 +332,40 @@ class BridgeIf(Interface): value = interface_config.get('priority') lower.set_path_priority(value) - tmp = dict_search('native_vlan_removed', interface_config) - - for vlan_id in (tmp or []): - cmd = f'bridge vlan del dev {interface} vid {vlan_id}' - self._cmd(cmd) - cmd = f'bridge vlan add dev {interface} vid 1 pvid untagged master' - self._cmd(cmd) - vlan_del.add(vlan_id) - vlan_add.add(1) - - tmp = dict_search('allowed_vlan_removed', interface_config) - - - for vlan_id in (tmp or []): - cmd = f'bridge vlan del dev {interface} vid {vlan_id}' - self._cmd(cmd) - vlan_del.add(vlan_id) - - if 'native_vlan' in interface_config: - vlan_filter = 1 - cmd = f'bridge vlan del dev {interface} vid 1' - self._cmd(cmd) - vlan_id = interface_config['native_vlan'] - if int(vlan_id) != 1: - if 1 in vlan_add: - vlan_add.remove(1) - vlan_del.add(1) - cmd = f'bridge vlan add dev {interface} vid {vlan_id} pvid untagged master' - self._cmd(cmd) - vlan_add.add(vlan_id) - if vlan_id in vlan_del: - vlan_del.remove(vlan_id) - - if 'allowed_vlan' in interface_config: - vlan_filter = 1 - if 'native_vlan' not in interface_config: - cmd = f'bridge vlan del dev {interface} vid 1' + if int(vlan_filter): + add_vlan = [] + native_vlan_id = None + allowed_vlan_ids= [] + cur_vlan_ids = get_vlan_ids(interface) + + if 'native_vlan' in interface_config: + vlan_id = interface_config['native_vlan'] + add_vlan.append(vlan_id) + native_vlan_id = vlan_id + + if 'allowed_vlan' in interface_config: + for vlan in interface_config['allowed_vlan']: + vlan_range = vlan.split('-') + if len(vlan_range) == 2: + for vlan_add in range(int(vlan_range[0]),int(vlan_range[1]) + 1): + add_vlan.append(str(vlan_add)) + allowed_vlan_ids.append(str(vlan_add)) + 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 {interface} vid {vlan} master' self._cmd(cmd) - vlan_del.add(1) - for vlan in interface_config['allowed_vlan']: + + for vlan in allowed_vlan_ids: cmd = f'bridge vlan add dev {interface} vid {vlan} master' self._cmd(cmd) - vlan_add.add(vlan) - if vlan in vlan_del: - vlan_del.remove(vlan) - - for vlan in vlan_del: - cmd = f'bridge vlan del dev {ifname} vid {vlan} self' - self._cmd(cmd) - - for vlan in vlan_add: - cmd = f'bridge vlan add dev {ifname} vid {vlan} self' - self._cmd(cmd) - - # enable/disable Vlan Filter - self.set_vlan_filter(vlan_filter) - + # Setting native VLAN to system + if native_vlan_id: + cmd = f'bridge vlan add dev {interface} vid {native_vlan_id} pvid untagged master' + self._cmd(cmd) # Enable/Disable of an interface must always be done at the end of the # derived class to make use of the ref-counting set_admin_state() diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 1561d340e..3b92ce463 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -900,50 +900,43 @@ class Interface(Control): if 'priority' in bridge_config: self.set_path_cost(bridge_config['priority']) - vlan_filter = 0 - vlan_add = set() - - del_ifname_vlan_ids = get_vlan_ids(ifname) bridge_vlan_filter = Section.klass(bridge)(bridge, create=True).get_vlan_filter() - if bridge_vlan_filter: - if 1 in del_ifname_vlan_ids: - del_ifname_vlan_ids.remove(1) - vlan_filter = 1 - - for vlan in del_ifname_vlan_ids: - cmd = f'bridge vlan del dev {ifname} vid {vlan}' - self._cmd(cmd) - - if 'native_vlan' in bridge_config: - vlan_filter = 1 - cmd = f'bridge vlan del dev {self.ifname} vid 1' - self._cmd(cmd) - vlan_id = bridge_config['native_vlan'] - cmd = f'bridge vlan add dev {self.ifname} vid {vlan_id} pvid untagged master' - self._cmd(cmd) - vlan_add.add(vlan_id) - - if 'allowed_vlan' in bridge_config: - vlan_filter = 1 - if 'native_vlan' not in bridge_config: - cmd = f'bridge vlan del dev {self.ifname} vid 1' + if int(bridge_vlan_filter): + cur_vlan_ids = get_vlan_ids(ifname) + add_vlan = [] + native_vlan_id = None + allowed_vlan_ids= [] + + if 'native_vlan' in bridge_config: + vlan_id = bridge_config['native_vlan'] + add_vlan.append(vlan_id) + native_vlan_id = vlan_id + + if 'allowed_vlan' in bridge_config: + for vlan in bridge_config['allowed_vlan']: + vlan_range = vlan.split('-') + if len(vlan_range) == 2: + for vlan_add in range(int(vlan_range[0]),int(vlan_range[1]) + 1): + add_vlan.append(str(vlan_add)) + allowed_vlan_ids.append(str(vlan_add)) + 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 bridge_config['allowed_vlan']: - cmd = f'bridge vlan add dev {self.ifname} vid {vlan} master' + + for vlan in allowed_vlan_ids: + cmd = f'bridge vlan add dev {ifname} vid {vlan} master' self._cmd(cmd) - vlan_add.add(vlan) - - if vlan_filter: - # Setting VLAN ID for the bridge - for vlan in vlan_add: - cmd = f'bridge vlan add dev {bridge} vid {vlan} self' + # Setting native VLAN to system + if native_vlan_id: + cmd = f'bridge vlan add dev {ifname} vid {native_vlan_id} pvid untagged master' self._cmd(cmd) - # enable/disable Vlan Filter - # When the VLAN aware option is not detected, the setting of `bridge` should not be overwritten - Section.klass(bridge)(bridge, create=True).set_vlan_filter(vlan_filter) - def set_dhcp(self, enable): """ Enable/Disable DHCP client on a given interface. diff --git a/python/vyos/ifconfig/tunnel.py b/python/vyos/ifconfig/tunnel.py index 7e3f9565a..4320bf8bc 100644 --- a/python/vyos/ifconfig/tunnel.py +++ b/python/vyos/ifconfig/tunnel.py @@ -55,6 +55,7 @@ class _Tunnel(Interface): 'ttl' : '', 'tos' : '', 'key' : '', + 'raw' : '', } options = Interface.options + list(default.keys()) diff --git a/python/vyos/template.py b/python/vyos/template.py index bf087c223..527384d0b 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -149,7 +149,9 @@ def netmask_from_ipv4(address): Example: - 172.18.201.10 -> 255.255.255.128 """ - from netifaces import interfaces, ifaddresses, AF_INET + from netifaces import interfaces + from netifaces import ifaddresses + from netifaces import AF_INET for interface in interfaces(): tmp = ifaddresses(interface) if AF_INET in tmp: @@ -160,6 +162,30 @@ def netmask_from_ipv4(address): raise ValueError +@register_filter('is_ip_network') +def is_ip_network(addr): + """ Take IP(v4/v6) address and validate if the passed argument is a network + or a host address. + + Example: + - 192.0.2.0 -> False + - 192.0.2.10/24 -> False + - 192.0.2.0/24 -> True + - 2001:db8:: -> False + - 2001:db8::100 -> False + - 2001:db8::/48 -> True + - 2001:db8:1000::/64 -> True + """ + try: + from ipaddress import ip_network + # input variables must contain a / to indicate its CIDR notation + if len(addr.split('/')) != 2: + raise ValueError() + ip_network(addr) + return True + except: + return False + @register_filter('network_from_ipv4') def network_from_ipv4(address): """ Take IP address and search all attached interface IP addresses for the @@ -248,6 +274,21 @@ def dec_ip(address, decrement): from ipaddress import ip_interface return str(ip_interface(address).ip - int(decrement)) +@register_filter('compare_netmask') +def compare_netmask(netmask1, netmask2): + """ + Compare two IP netmask if they have the exact same size. + + compare_netmask('10.0.0.0/8', '20.0.0.0/8') -> True + compare_netmask('10.0.0.0/8', '20.0.0.0/16') -> False + """ + from ipaddress import ip_network + try: + return ip_network(netmask1).netmask == ip_network(netmask2).netmask + 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 diff --git a/scripts/override-default b/scripts/override-default new file mode 100755 index 000000000..d91b89426 --- /dev/null +++ b/scripts/override-default @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +# +# override-default: preprocessor for XML interface definitions to interpret +# redundant entries (relative to path) with tag 'defaultValue' as an override +# directive. Must be called before build-command-templates, as the schema +# disallows redundancy. +# +# 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/>. +# +# + +# Use lxml xpath capability to find multiple elements with tag defaultValue +# relative to path; replace and remove to override the value. + +import sys +import glob +import logging +from lxml import etree + +debug = False + +logger = logging.getLogger(__name__) +logs_handler = logging.StreamHandler() +logger.addHandler(logs_handler) + +if debug: + logger.setLevel(logging.DEBUG) +else: + logger.setLevel(logging.INFO) + +def override_element(l: list): + """ + Allow multiple override elements; use the final one (in document order). + """ + if len(l) < 2: + logger.debug("passing list of single element to override_element") + return + + # assemble list of leafNodes of overriding defaultValues, for later removal + parents = [] + for el in l[1:]: + parents.append(el.getparent()) + + # replace element with final override + l[0].getparent().replace(l[0], l[-1]) + + # remove all but overridden element + for el in parents: + el.getparent().remove(el) + +def collect_and_override(dir_name): + """ + Collect elements with defaultValue tag into dictionary indexed by tuple + of (name, str(ancestor path)); the second component must be immutable for + tuple to act as key, hence str(). + """ + for fname in glob.glob(f'{dir_name}/*.xml'): + tree = etree.parse(fname) + root = tree.getroot() + defv = {} + + xpath_str = f'//defaultValue' + xp = tree.xpath(xpath_str) + + for element in xp: + ap = element.xpath('ancestor::*[@name]') + defv.setdefault((ap[-1].get("name"), str(ap[:-1])), []).append(element) + + for k, v in defv.items(): + if len(v) > 1: + logger.debug(f'overridding default in {k[0]}') + override_element(v) + + revised_str = etree.tostring(root, encoding='unicode', pretty_print=True) + + with open(f'{fname}', 'w') as f: + f.write(revised_str) + +def main(): + if len(sys.argv) < 2: + logger.critical('Must specify XML directory!') + sys.exit(1) + + dir_name = sys.argv[1] + + collect_and_override(dir_name) + +if __name__ == '__main__': + main() diff --git a/smoketest/configs/bgp-ix-router b/smoketest/configs/bgp-ixp index de6213b50..de6213b50 100644 --- a/smoketest/configs/bgp-ix-router +++ b/smoketest/configs/bgp-ixp diff --git a/smoketest/configs/bgp-rpki b/smoketest/configs/bgp-rpki new file mode 100644 index 000000000..e11ec9e72 --- /dev/null +++ b/smoketest/configs/bgp-rpki @@ -0,0 +1,116 @@ +interfaces { + ethernet eth0 { + address 192.0.2.100/25 + address 2001:db8::ffff/64 + } + ethernet eth1 { + } + loopback lo { + } +} +policy { + route-map ebgp-transit-rpki { + rule 10 { + action deny + match { + rpki invalid + } + } + rule 20 { + action permit + match { + rpki notfound + } + set { + local-preference 20 + } + } + rule 30 { + action permit + match { + rpki valid + } + set { + local-preference 100 + } + } + } +} +protocols { + bgp 64500 { + neighbor 1.2.3.4 { + address-family { + ipv4-unicast { + nexthop-self { + } + route-map { + import ebgp-transit-rpki + } + } + } + remote-as 10 + } + } + rpki { + cache routinator { + address 192.0.2.10 + port 3323 + } + } + static { + route 0.0.0.0/0 { + next-hop 192.0.2.1 { + } + } + route6 ::/0 { + next-hop 2001:db8::1 { + } + } + } +} +service { + ssh { + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/ + plaintext-password "" + } + } + } + ntp { + server 0.pool.ntp.org { + } + server 1.pool.ntp.org { + } + server 2.pool.ntp.org { + } + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } +} + + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@1:conntrack-sync@1:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@13:ipoe-server@1:ipsec@5:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@6:salt@1:snmp@2:ssh@2:sstp@3:system@19:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webgui@1:webproxy@2:zone-policy@1" +// Release version: 1.3-rolling-202010241631 diff --git a/smoketest/configs/dmz-guest-lan-nat-pppoe-router b/smoketest/configs/dmz-guest-lan-nat-pppoe-router new file mode 100644 index 000000000..075f18b9e --- /dev/null +++ b/smoketest/configs/dmz-guest-lan-nat-pppoe-router @@ -0,0 +1,1666 @@ +firewall { + all-ping enable + broadcast-ping disable + config-trap disable + group { + address-group MEDIA-STREAMING-CLIENTS { + address 172.16.35.241 + address 172.16.35.242 + address 172.16.35.243 + } + address-group DMZ-WEBSERVER { + address 172.16.36.10 + address 172.16.36.40 + address 172.16.36.20 + } + address-group DMZ-RDP-SERVER { + address 172.16.33.40 + } + address-group DOMAIN-CONTROLLER { + address 172.16.100.10 + address 172.16.100.20 + } + address-group AUDIO-STREAM { + address 172.16.35.20 + address 172.16.35.21 + address 172.16.35.22 + address 172.16.35.23 + } + ipv6-network-group LOCAL-ADDRESSES { + network ff02::/64 + network fe80::/10 + } + network-group SSH-IN-ALLOW { + network 192.0.2.0/24 + network 10.0.0.0/8 + network 172.16.0.0/12 + network 192.168.0.0/16 + } + port-group SMART-TV-PORTS { + port 5005-5006 + port 80 + port 443 + port 3722 + } + } + ipv6-name ALLOW-ALL-6 { + default-action accept + } + ipv6-name ALLOW-BASIC-6 { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + state { + invalid enable + } + } + rule 10 { + action accept + protocol icmpv6 + } + } + ipv6-name ALLOW-ESTABLISHED-6 { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + state { + invalid enable + } + } + rule 10 { + action accept + destination { + group { + network-group LOCAL-ADDRESSES + } + } + protocol icmpv6 + source { + address fe80::/10 + } + } + rule 20 { + action accept + icmpv6 { + type echo-request + } + protocol icmpv6 + } + rule 21 { + action accept + icmpv6 { + type destination-unreachable + } + protocol icmpv6 + } + rule 22 { + action accept + icmpv6 { + type packet-too-big + } + protocol icmpv6 + } + rule 23 { + action accept + icmpv6 { + type time-exceeded + } + protocol icmpv6 + } + rule 24 { + action accept + icmpv6 { + type parameter-problem + } + protocol icmpv6 + } + } + ipv6-name WAN-LOCAL-6 { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + state { + invalid enable + } + } + rule 10 { + action accept + destination { + address ff02::/64 + } + protocol icmpv6 + source { + address fe80::/10 + } + } + rule 50 { + action accept + description DHCPv6 + destination { + address fe80::/10 + port 546 + } + protocol udp + source { + address fe80::/10 + port 547 + } + } + } + ipv6-receive-redirects disable + ipv6-src-route disable + ip-src-route disable + log-martians enable + name DMZ-GUEST { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + } + name DMZ-LAN { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 100 { + action accept + description "NTP and LDAP to AD DC" + destination { + group { + address-group DOMAIN-CONTROLLER + } + port 123,389,636 + } + protocol tcp_udp + } + rule 300 { + action accept + destination { + group { + address-group DMZ-RDP-SERVER + } + port 3389 + } + protocol tcp_udp + source { + address 172.16.36.20 + } + } + } + name DMZ-LOCAL { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 50 { + action accept + destination { + address 172.16.254.30 + port 53 + } + protocol tcp_udp + } + rule 123 { + action accept + destination { + port 123 + } + protocol udp + } + } + name DMZ-WAN { + default-action accept + } + name GUEST-DMZ { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 100 { + action accept + destination { + port 80,443 + } + protocol tcp + } + } + name GUEST-IOT { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 100 { + action accept + description "MEDIA-STREAMING-CLIENTS Devices to GUEST" + destination { + group { + address-group MEDIA-STREAMING-CLIENTS + } + } + protocol tcp_udp + } + rule 110 { + action accept + description "AUDIO-STREAM Devices to GUEST" + destination { + group { + address-group AUDIO-STREAM + } + } + protocol tcp_udp + } + rule 200 { + action accept + description "MCAST relay" + destination { + address 224.0.0.251 + port 5353 + } + protocol udp + } + rule 300 { + action accept + description "BCAST relay" + destination { + port 1900 + } + protocol udp + } + } + name GUEST-LAN { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + } + name GUEST-LOCAL { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 10 { + action accept + description DNS + destination { + address 172.31.0.254 + port 53 + } + protocol tcp_udp + } + rule 11 { + action accept + description DHCP + destination { + port 67 + } + protocol udp + } + rule 15 { + action accept + destination { + address 172.31.0.254 + } + protocol icmp + } + rule 200 { + action accept + description "MCAST relay" + destination { + address 224.0.0.251 + port 5353 + } + protocol udp + } + rule 210 { + action accept + description "AUDIO-STREAM Broadcast" + destination { + port 1900 + } + protocol udp + } + } + name GUEST-WAN { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 25 { + action accept + description SMTP + destination { + port 25,587 + } + protocol tcp + } + rule 53 { + action accept + destination { + port 53 + } + protocol tcp_udp + } + rule 60 { + action accept + source { + address 172.31.0.200 + } + } + rule 80 { + action accept + source { + address 172.31.0.200 + } + } + rule 100 { + action accept + protocol icmp + } + rule 110 { + action accept + description POP3 + destination { + port 110,995 + } + protocol tcp + } + rule 123 { + action accept + description "NTP Client" + destination { + port 123 + } + protocol udp + } + rule 143 { + action accept + description IMAP + destination { + port 143,993 + } + protocol tcp + } + rule 200 { + action accept + destination { + port 80,443 + } + protocol tcp + } + rule 500 { + action accept + description "L2TP IPSec" + destination { + port 500,4500 + } + protocol udp + } + rule 600 { + action accept + destination { + port 5222-5224 + } + protocol tcp + } + rule 601 { + action accept + destination { + port 3478-3497,4500,16384-16387,16393-16402 + } + protocol udp + } + rule 1000 { + action accept + source { + address 172.31.0.184 + } + } + } + name IOT-GUEST { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 100 { + action accept + description "MEDIA-STREAMING-CLIENTS Devices to IOT" + protocol tcp_udp + source { + group { + address-group MEDIA-STREAMING-CLIENTS + } + } + } + rule 110 { + action accept + description "AUDIO-STREAM Devices to IOT" + protocol tcp_udp + source { + group { + address-group AUDIO-STREAM + } + } + } + rule 200 { + action accept + description "MCAST relay" + destination { + address 224.0.0.251 + port 5353 + } + protocol udp + } + rule 300 { + action accept + description "BCAST relay" + destination { + port 1900 + } + protocol udp + } + } + name IOT-LAN { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 100 { + action accept + description "AppleTV to LAN" + destination { + group { + port-group SMART-TV-PORTS + } + } + protocol tcp_udp + source { + group { + address-group MEDIA-STREAMING-CLIENTS + } + } + } + rule 110 { + action accept + description "AUDIO-STREAM Devices to LAN" + protocol tcp_udp + source { + group { + address-group AUDIO-STREAM + } + } + } + } + name IOT-LOCAL { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 10 { + action accept + description DNS + destination { + address 172.16.254.30 + port 53 + } + protocol tcp_udp + } + rule 11 { + action accept + description DHCP + destination { + port 67 + } + protocol udp + } + rule 15 { + action accept + destination { + address 172.16.35.254 + } + protocol icmp + } + rule 200 { + action accept + description "MCAST relay" + destination { + address 224.0.0.251 + port 5353 + } + protocol udp + } + rule 201 { + action accept + description "MCAST relay" + destination { + address 172.16.35.254 + port 5353 + } + protocol udp + } + rule 210 { + action accept + description "AUDIO-STREAM Broadcast" + destination { + port 1900,1902,6969 + } + protocol udp + } + } + name IOT-WAN { + default-action accept + } + name LAN-DMZ { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 22 { + action accept + description "SSH into DMZ" + destination { + port 22 + } + protocol tcp + } + rule 100 { + action accept + destination { + group { + address-group DMZ-WEBSERVER + } + port 22,80,443 + } + protocol tcp + } + } + name LAN-GUEST { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + } + name LAN-IOT { + default-action accept + } + name LAN-LOCAL { + default-action accept + } + name LAN-WAN { + default-action accept + } + name LOCAL-DMZ { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + } + name LOCAL-GUEST { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 5 { + action accept + protocol icmp + } + rule 200 { + action accept + description "MCAST relay" + destination { + address 224.0.0.251 + port 5353 + } + protocol udp + } + rule 300 { + action accept + description "BCAST relay" + destination { + port 1900 + } + protocol udp + } + } + name LOCAL-IOT { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 5 { + action accept + protocol icmp + } + rule 200 { + action accept + description "MCAST relay" + destination { + address 224.0.0.251 + port 5353 + } + protocol udp + } + rule 300 { + action accept + description "BCAST relay" + destination { + port 1900,6969 + } + protocol udp + } + } + name LOCAL-LAN { + default-action accept + } + name LOCAL-WAN { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 10 { + action accept + protocol icmp + } + rule 50 { + action accept + description DNS + destination { + port 53 + } + protocol tcp_udp + } + rule 80 { + action accept + destination { + port 80,443 + } + protocol tcp + } + rule 123 { + action accept + description NTP + destination { + port 123 + } + protocol udp + } + } + name WAN-DMZ { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 100 { + action accept + destination { + address 172.16.36.10 + port 80,443 + } + protocol tcp + } + } + name WAN-GUEST { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 1000 { + action accept + destination { + address 172.31.0.184 + } + } + rule 8000 { + action accept + destination { + address 172.31.0.200 + port 10000 + } + protocol udp + } + } + name WAN-IOT { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + } + name WAN-LAN { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 1000 { + action accept + destination { + address 172.16.33.40 + port 3389 + } + protocol tcp + source { + group { + network-group SSH-IN-ALLOW + } + } + } + } + name WAN-LOCAL { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 22 { + action accept + destination { + port 22 + } + protocol tcp + source { + group { + network-group SSH-IN-ALLOW + } + } + } + } + options { + interface pppoe0 { + adjust-mss 1452 + adjust-mss6 1432 + } + } + receive-redirects disable + send-redirects enable + source-validation disable + syn-cookies enable + twa-hazards-protection disable +} +interfaces { + dummy dum0 { + address 172.16.254.30/32 + } + ethernet eth0 { + duplex auto + speed auto + vif 5 { + address 172.16.37.254/24 + } + vif 10 { + address 172.16.33.254/24 + } + vif 20 { + address 172.31.0.254/24 + } + vif 35 { + address 172.16.35.254/24 + } + vif 50 { + address 172.16.36.254/24 + } + vif 100 { + address 172.16.100.254/24 + } + vif 201 { + address 172.18.201.254/24 + } + vif 202 { + address 172.18.202.254/24 + } + vif 203 { + address 172.18.203.254/24 + } + vif 204 { + address 172.18.204.254/24 + } + } + ethernet eth1 { + vif 7 { + description FTTH-PPPoE + } + } + loopback lo { + address 172.16.254.30/32 + } + pppoe pppoe0 { + authentication { + password vyos + user vyos + } + default-route auto + description "FTTH 100/50MBit" + dhcpv6-options { + pd 0 { + interface eth0.10 { + address 1 + sla-id 10 + } + interface eth0.20 { + address 1 + sla-id 20 + } + length 56 + } + } + ipv6 { + address { + autoconf + } + } + mtu 1492 + no-peer-dns + source-interface eth1.7 + } +} +nat { + destination { + rule 100 { + description HTTP(S) + destination { + port 80,443 + } + inbound-interface pppoe0 + log + protocol tcp + translation { + address 172.16.36.10 + } + } + rule 1000 { + destination { + port 3389 + } + disable + inbound-interface pppoe0 + protocol tcp + translation { + address 172.16.33.40 + } + } + rule 8000 { + destination { + port 10000 + } + inbound-interface pppoe0 + log + protocol udp + translation { + address 172.31.0.200 + } + } + } + source { + rule 100 { + log + outbound-interface pppoe0 + source { + address 172.16.32.0/19 + } + translation { + address masquerade + } + } + rule 200 { + outbound-interface pppoe0 + source { + address 172.16.100.0/24 + } + translation { + address masquerade + } + } + rule 300 { + outbound-interface pppoe0 + source { + address 172.31.0.0/24 + } + translation { + address masquerade + } + } + rule 400 { + outbound-interface pppoe0 + source { + address 172.18.200.0/21 + } + translation { + address masquerade + } + } + } +} +protocols { + static { + interface-route6 2000::/3 { + next-hop-interface pppoe0 { + } + } + route 10.0.0.0/8 { + blackhole { + distance 254 + } + } + route 169.254.0.0/16 { + blackhole { + distance 254 + } + } + route 172.16.0.0/12 { + blackhole { + distance 254 + } + } + route 192.168.0.0/16 { + blackhole { + distance 254 + } + } + } +} +service { + dhcp-server { + shared-network-name BACKBONE { + authoritative + subnet 172.16.37.0/24 { + default-router 172.16.37.254 + dns-server 172.16.254.30 + domain-name vyos.net + domain-search vyos.net + lease 86400 + ntp-server 172.16.254.30 + range 0 { + start 172.16.37.120 + stop 172.16.37.149 + } + static-mapping AP1.wue3 { + ip-address 172.16.37.231 + mac-address 18:e8:29:6c:c3:a5 + } + } + } + shared-network-name GUEST { + authoritative + subnet 172.31.0.0/24 { + default-router 172.31.0.254 + dns-server 172.31.0.254 + domain-name vyos.net + domain-search vyos.net + lease 86400 + range 0 { + start 172.31.0.100 + stop 172.31.0.199 + } + static-mapping host01 { + ip-address 172.31.0.200 + mac-address 00:50:00:00:00:01 + } + static-mapping host02 { + ip-address 172.31.0.184 + mac-address 00:50:00:00:00:02 + } + } + } + shared-network-name IOT { + authoritative + subnet 172.16.35.0/24 { + default-router 172.16.35.254 + dns-server 172.16.254.30 + domain-name vyos.net + domain-search vyos.net + lease 86400 + ntp-server 172.16.254.30 + range 0 { + start 172.16.35.101 + stop 172.16.35.149 + } + } + } + shared-network-name LAN { + authoritative + subnet 172.16.33.0/24 { + default-router 172.16.33.254 + dns-server 172.16.254.30 + domain-name vyos.net + domain-search vyos.net + lease 86400 + ntp-server 172.16.254.30 + range 0 { + start 172.16.33.100 + stop 172.16.33.189 + } + } + } + } + dns { + forwarding { + allow-from 172.16.0.0/12 + cache-size 0 + domain 16.172.in-addr.arpa { + addnta + recursion-desired + server 172.16.100.10 + server 172.16.100.20 + server 172.16.110.30 + } + domain 18.172.in-addr.arpa { + addnta + recursion-desired + server 172.16.100.10 + server 172.16.100.20 + server 172.16.110.30 + } + domain vyos.net { + addnta + recursion-desired + server 172.16.100.20 + server 172.16.100.10 + server 172.16.110.30 + } + ignore-hosts-file + listen-address 172.16.254.30 + listen-address 172.31.0.254 + negative-ttl 60 + } + } + lldp { + legacy-protocols { + cdp + } + snmp { + enable + } + } + mdns { + repeater { + interface eth0.35 + interface eth0.10 + } + } + router-advert { + interface eth0.10 { + prefix ::/64 { + preferred-lifetime 2700 + valid-lifetime 5400 + } + } + interface eth0.20 { + prefix ::/64 { + preferred-lifetime 2700 + valid-lifetime 5400 + } + } + } + snmp { + community fooBar { + authorization ro + network 172.16.100.0/24 + } + contact "VyOS maintainers and contributors <maintainers@vyos.io>" + listen-address 172.16.254.30 { + port 161 + } + location "The Internet" + } + ssh { + disable-host-validation + port 22 + } +} +system { + config-management { + commit-archive { + location tftp://172.16.20.200/CONFIGS/vyos + } + commit-revisions 200 + } + conntrack { + expect-table-size 2048 + hash-size 32768 + modules { + sip { + disable + } + } + table-size 262144 + timeout { + icmp 30 + other 600 + udp { + other 300 + stream 300 + } + } + } + console { + device ttyS0 { + speed 115200 + } + } + domain-name vyos.net + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/ + plaintext-password "" + } + } + } + name-server 172.16.254.30 + ntp { + allow-clients { + address 172.16.0.0/12 + } + server 0.pool.ntp.org { + } + server 1.pool.ntp.org { + } + server 2.pool.ntp.org { + } + } + option { + ctrl-alt-delete ignore + reboot-on-panic + startup-beep + } + syslog { + global { + facility all { + level debug + } + facility protocols { + level debug + } + } + host 172.16.100.1 { + facility all { + level warning + } + } + } + time-zone Europe/Berlin +} +traffic-policy { + shaper QoS { + bandwidth 50mbit + default { + bandwidth 100% + burst 15k + queue-limit 1000 + queue-type fq-codel + } + } +} +zone-policy { + zone DMZ { + default-action drop + from GUEST { + firewall { + name GUEST-DMZ + } + } + from LAN { + firewall { + name LAN-DMZ + } + } + from LOCAL { + firewall { + name LOCAL-DMZ + } + } + from WAN { + firewall { + name WAN-DMZ + } + } + interface eth0.50 + } + zone GUEST { + default-action drop + from DMZ { + firewall { + name DMZ-GUEST + } + } + from IOT { + firewall { + name IOT-GUEST + } + } + from LAN { + firewall { + name LAN-GUEST + } + } + from LOCAL { + firewall { + ipv6-name ALLOW-ALL-6 + name LOCAL-GUEST + } + } + from WAN { + firewall { + ipv6-name ALLOW-ESTABLISHED-6 + name WAN-GUEST + } + } + interface eth0.20 + } + zone IOT { + default-action drop + from GUEST { + firewall { + name GUEST-IOT + } + } + from LAN { + firewall { + name LAN-IOT + } + } + from LOCAL { + firewall { + name LOCAL-IOT + } + } + from WAN { + firewall { + name WAN-IOT + } + } + interface eth0.35 + } + zone LAN { + default-action drop + from DMZ { + firewall { + name DMZ-LAN + } + } + from GUEST { + firewall { + name GUEST-LAN + } + } + from IOT { + firewall { + name IOT-LAN + } + } + from LOCAL { + firewall { + ipv6-name ALLOW-ALL-6 + name LOCAL-LAN + } + } + from WAN { + firewall { + ipv6-name ALLOW-ESTABLISHED-6 + name WAN-LAN + } + } + interface eth0.5 + interface eth0.10 + interface eth0.100 + interface eth0.201 + interface eth0.202 + interface eth0.203 + interface eth0.204 + } + zone LOCAL { + default-action drop + from DMZ { + firewall { + name DMZ-LOCAL + } + } + from GUEST { + firewall { + ipv6-name ALLOW-ESTABLISHED-6 + name GUEST-LOCAL + } + } + from IOT { + firewall { + name IOT-LOCAL + } + } + from LAN { + firewall { + ipv6-name ALLOW-ALL-6 + name LAN-LOCAL + } + } + from WAN { + firewall { + ipv6-name WAN-LOCAL-6 + name WAN-LOCAL + } + } + local-zone + } + zone WAN { + default-action drop + from DMZ { + firewall { + name DMZ-WAN + } + } + from GUEST { + firewall { + ipv6-name ALLOW-ALL-6 + name GUEST-WAN + } + } + from IOT { + firewall { + name IOT-WAN + } + } + from LAN { + firewall { + ipv6-name ALLOW-ALL-6 + name LAN-WAN + } + } + from LOCAL { + firewall { + ipv6-name ALLOW-ALL-6 + name LOCAL-WAN + } + } + interface pppoe0 + } +} + + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@1:conntrack-sync@1:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@18:ipoe-server@1:ipsec@5:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@6:salt@1:snmp@2:ssh@2:sstp@3:system@20:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.3-beta-202101091250 + diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py index 8a09dd96f..6a68bcc26 100644 --- a/smoketest/scripts/cli/base_interfaces_test.py +++ b/smoketest/scripts/cli/base_interfaces_test.py @@ -18,9 +18,10 @@ import json from binascii import hexlify -from netifaces import ifaddresses from netifaces import AF_INET from netifaces import AF_INET6 +from netifaces import ifaddresses +from netifaces import interfaces from vyos.configsession import ConfigSession from vyos.ifconfig import Interface @@ -81,21 +82,22 @@ class BasicInterfaceTest: self.session.set(['interfaces', section, span]) def tearDown(self): - # Ethernet is handled in its derived class - if 'ethernet' not in self._base_path: - self.session.delete(self._base_path) - # Tear down mirror interfaces for SPAN (Switch Port Analyzer) for span in self._mirror_interfaces: section = Section.section(span) self.session.delete(['interfaces', section, span]) + self.session.delete(self._base_path) self.session.commit() del self.session + # Verify that no previously interface remained on the system + for intf in self._interfaces: + self.assertNotIn(intf, interfaces()) + def test_span_mirror(self): if not self._mirror_interfaces: - self.skipTest('not enabled') + self.skipTest('not supported') # Check the two-way mirror rules of ingress and egress for mirror in self._mirror_interfaces: @@ -111,6 +113,19 @@ class BasicInterfaceTest: self.assertTrue(is_mirrored_to(interface, mirror, 'ffff')) self.assertTrue(is_mirrored_to(interface, mirror, '1')) + def test_interface_disable(self): + # Check if description can be added to interface and + # can be read back + for intf in self._interfaces: + self.session.set(self._base_path + [intf, 'disable']) + for option in self._options.get(intf, []): + self.session.set(self._base_path + [intf] + option.split()) + + self.session.commit() + + # Validate interface description + for intf in self._interfaces: + self.assertEqual(Interface(intf).get_admin_state(), 'down') def test_interface_description(self): # Check if description can be added to interface and @@ -150,6 +165,7 @@ class BasicInterfaceTest: for intf in self._interfaces: self.assertTrue(is_intf_addr_assigned(intf, addr)) + self.assertEqual(Interface(intf).get_admin_state(), 'up') def test_add_multiple_ip_addresses(self): # Add address @@ -174,7 +190,7 @@ class BasicInterfaceTest: def test_ipv6_link_local_address(self): # Common function for IPv6 link-local address assignemnts if not self._test_ipv6: - self.skipTest('not enabled') + self.skipTest('not supported') for interface in self._interfaces: base = self._base_path + [interface] @@ -201,7 +217,7 @@ class BasicInterfaceTest: def test_interface_mtu(self): if not self._test_mtu: - self.skipTest('not enabled') + self.skipTest('not supported') for intf in self._interfaces: base = self._base_path + [intf] @@ -221,7 +237,7 @@ class BasicInterfaceTest: # Testcase if MTU can be changed to 1200 on non IPv6 # enabled interfaces if not self._test_mtu: - self.skipTest('not enabled') + self.skipTest('not supported') old_mtu = self._mtu self._mtu = '1200' @@ -244,9 +260,13 @@ class BasicInterfaceTest: self._mtu = old_mtu - def test_8021q_vlan_interfaces(self): + def test_vif_8021q_interfaces(self): + # XXX: This testcase is not allowed to run as first testcase, reason + # is the Wireless test will first load the wifi kernel hwsim module + # which creates a wlan0 and wlan1 interface which will fail the + # tearDown() test in the end that no interface is allowed to survive! if not self._test_vlan: - self.skipTest('not enabled') + self.skipTest('not supported') for interface in self._interfaces: base = self._base_path + [interface] @@ -269,11 +289,16 @@ class BasicInterfaceTest: tmp = read_file(f'/sys/class/net/{vif}/mtu') self.assertEqual(tmp, self._mtu) + self.assertEqual(Interface(vif).get_admin_state(), 'up') - def test_8021ad_qinq_vlan_interfaces(self): + def test_vif_s_8021ad_vlan_interfaces(self): + # XXX: This testcase is not allowed to run as first testcase, reason + # is the Wireless test will first load the wifi kernel hwsim module + # which creates a wlan0 and wlan1 interface which will fail the + # tearDown() test in the end that no interface is allowed to survive! if not self._test_qinq: - self.skipTest('not enabled') + self.skipTest('not supported') for interface in self._interfaces: base = self._base_path + [interface] @@ -304,7 +329,7 @@ class BasicInterfaceTest: def test_interface_ip_options(self): if not self._test_ip: - self.skipTest('not enabled') + self.skipTest('not supported') for interface in self._interfaces: arp_tmo = '300' @@ -355,7 +380,7 @@ class BasicInterfaceTest: def test_interface_ipv6_options(self): if not self._test_ipv6: - self.skipTest('not enabled') + self.skipTest('not supported') for interface in self._interfaces: dad_transmits = '10' @@ -379,7 +404,7 @@ class BasicInterfaceTest: def test_dhcpv6pd_auto_sla_id(self): if not self._test_ipv6_pd: - self.skipTest('not enabled') + self.skipTest('not supported') prefix_len = '56' sla_len = str(64 - int(prefix_len)) @@ -435,7 +460,7 @@ class BasicInterfaceTest: def test_dhcpv6pd_manual_sla_id(self): if not self._test_ipv6_pd: - self.skipTest('not enabled') + self.skipTest('not supported') prefix_len = '56' sla_len = str(64 - int(prefix_len)) diff --git a/smoketest/scripts/cli/test_interfaces_bonding.py b/smoketest/scripts/cli/test_interfaces_bonding.py index f42ec3e9b..560bfb92b 100755 --- a/smoketest/scripts/cli/test_interfaces_bonding.py +++ b/smoketest/scripts/cli/test_interfaces_bonding.py @@ -60,8 +60,8 @@ class BondingInterfaceTest(BasicInterfaceTest.BaseTest): slaves = read_file(f'/sys/class/net/{interface}/bonding/slaves').split() self.assertListEqual(slaves, self._members) - def test_8021q_vlan_interfaces(self): - super().test_8021q_vlan_interfaces() + def test_vif_8021q_interfaces(self): + super().test_vif_8021q_interfaces() for interface in self._interfaces: slaves = read_file(f'/sys/class/net/{interface}/bonding/slaves').split() diff --git a/smoketest/scripts/cli/test_interfaces_bridge.py b/smoketest/scripts/cli/test_interfaces_bridge.py index 464226b6f..8f03290be 100755 --- a/smoketest/scripts/cli/test_interfaces_bridge.py +++ b/smoketest/scripts/cli/test_interfaces_bridge.py @@ -25,6 +25,7 @@ from netifaces import interfaces from vyos.ifconfig import Section from vyos.util import cmd from vyos.util import read_file +from vyos.validate import is_intf_addr_assigned class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): def setUp(self): @@ -32,7 +33,6 @@ class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): self._test_ipv6 = True self._test_ipv6_pd = True self._test_vlan = True - self._test_qinq = True self._base_path = ['interfaces', 'bridge'] self._mirror_interfaces = ['dum21354'] self._members = [] @@ -53,6 +53,12 @@ class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): super().setUp() + def tearDown(self): + for intf in self._interfaces: + self.session.delete(self._base_path + [intf]) + + super().tearDown() + def test_add_remove_bridge_member(self): # Add member interfaces to bridge and set STP cost/priority for interface in self._interfaces: @@ -87,12 +93,22 @@ class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): self.session.commit() + def test_vif_8021q_interfaces(self): + for interface in self._interfaces: + base = self._base_path + [interface] + self.session.set(base + ['enable-vlan']) + super().test_vif_8021q_interfaces() + def test_bridge_vlan_filter(self): + + vif_vlan = 2 # Add member interface to bridge and set VLAN filter for interface in self._interfaces: base = self._base_path + [interface] - self.session.set(base + ['vif', '1', 'address', '192.0.2.1/24']) - self.session.set(base + ['vif', '2', 'address', '192.0.3.1/24']) + self.session.set(base + ['enable-vlan']) + self.session.set(base + ['address', '192.0.2.1/24']) + self.session.set(base + ['vif', str(vif_vlan), 'address', '192.0.3.1/24']) + self.session.set(base + ['vif', str(vif_vlan), 'mtu', self._mtu]) vlan_id = 101 allowed_vlan = 2 @@ -182,10 +198,13 @@ class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): for vif in vifs: # member interface must be assigned to the bridge self.assertTrue(os.path.exists(f'/sys/class/net/{interface}/lower_{member}.{vif}')) - - # remove VLAN interfaces - for vif in vifs: - self.session.delete(['interfaces', 'ethernet', member, 'vif', vif]) + + # delete all members + for interface in self._interfaces: + for member in self._members: + for vif in vifs: + self.session.delete(['interfaces', 'ethernet', member, 'vif', vif]) + self.session.delete(['interfaces', 'bridge', interface, 'member', 'interface', f'{member}.{vif}']) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py index 42c1f15df..9d896f690 100755 --- a/smoketest/scripts/cli/test_interfaces_ethernet.py +++ b/smoketest/scripts/cli/test_interfaces_ethernet.py @@ -78,8 +78,13 @@ class EthernetInterfaceTest(BasicInterfaceTest.BaseTest): self.session.set(self._base_path + [interface, 'speed', 'auto']) self.session.set(self._base_path + [interface, 'hw-id', self._macs[interface]]) - super().tearDown() + # Tear down mirror interfaces for SPAN (Switch Port Analyzer) + for span in self._mirror_interfaces: + section = Section.section(span) + self.session.delete(['interfaces', section, span]) + self.session.commit() + del self.session def test_dhcp_disable_interface(self): # When interface is configured as admin down, it must be admin down @@ -193,12 +198,12 @@ if __name__ == '__main__': # Generate mandatory SSL certificate tmp = f'openssl req -newkey rsa:4096 -new -nodes -x509 -days 3650 '\ f'-keyout {ssl_key} -out {ssl_cert} -subj {subject}' - print(cmd(tmp)) + cmd(tmp) if not os.path.isfile(ca_cert): # Generate "CA" tmp = f'openssl req -new -x509 -key {ssl_key} -out {ca_cert} -subj {subject}' - print(cmd(tmp)) + cmd(tmp) for file in [ca_cert, ssl_cert, ssl_key]: cmd(f'sudo chown radius_priv_user:vyattacfg {file}') diff --git a/smoketest/scripts/cli/test_interfaces_loopback.py b/smoketest/scripts/cli/test_interfaces_loopback.py index 79225a1bd..36000c3ff 100755 --- a/smoketest/scripts/cli/test_interfaces_loopback.py +++ b/smoketest/scripts/cli/test_interfaces_loopback.py @@ -17,6 +17,8 @@ import unittest from base_interfaces_test import BasicInterfaceTest +from netifaces import interfaces + from vyos.validate import is_intf_addr_assigned class LoopbackInterfaceTest(BasicInterfaceTest.BaseTest): @@ -27,6 +29,15 @@ class LoopbackInterfaceTest(BasicInterfaceTest.BaseTest): self._base_path = ['interfaces', 'loopback'] self._interfaces = ['lo'] + def tearDown(self): + self.session.delete(self._base_path) + self.session.commit() + del self.session + + # loopback interface must persist! + for intf in self._interfaces: + self.assertIn(intf, interfaces()) + def test_add_single_ip_address(self): super().test_add_single_ip_address() for addr in self._loopback_addresses: @@ -37,5 +48,8 @@ class LoopbackInterfaceTest(BasicInterfaceTest.BaseTest): for addr in self._loopback_addresses: self.assertTrue(is_intf_addr_assigned('lo', addr)) + def test_interface_disable(self): + self.skipTest('not supported') + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_tunnel.py b/smoketest/scripts/cli/test_interfaces_tunnel.py index f67b813af..8405fc7d0 100755 --- a/smoketest/scripts/cli/test_interfaces_tunnel.py +++ b/smoketest/scripts/cli/test_interfaces_tunnel.py @@ -84,7 +84,6 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest): self.session.delete(['interfaces', 'dummy', source_if]) super().tearDown() - def test_ipv4_encapsulations(self): # When running tests ensure that for certain encapsulation types the # local and remote IP address is actually an IPv4 address @@ -106,7 +105,6 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest): with self.assertRaises(ConfigSessionError): self.session.commit() self.session.set(self._base_path + [interface, 'remote-ip', remote_ip4]) - self.session.set(self._base_path + [interface, 'source-interface', source_if]) # Source interface can not be used with sit and gre-bridge @@ -130,6 +128,7 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest): self.assertEqual(self.local_v4, conf['linkinfo']['info_data']['local']) self.assertEqual(remote_ip4, conf['linkinfo']['info_data']['remote']) + self.assertTrue(conf['linkinfo']['info_data']['pmtudisc']) # cleanup this instance self.session.delete(self._base_path + [interface]) @@ -177,7 +176,7 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest): self.assertEqual(encapsulation, conf['link_type']) self.assertEqual(self.local_v6, conf['linkinfo']['info_data']['local']) - self.assertEqual(remote_ip6, conf['linkinfo']['info_data']['remote']) + self.assertEqual(remote_ip6, conf['linkinfo']['info_data']['remote']) # cleanup this instance self.session.delete(self._base_path + [interface]) @@ -203,5 +202,31 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest): # Check if commit is ok self.session.commit() + def test_tunnel_parameters_gre(self): + interface = f'tun1030' + gre_key = '10' + encapsulation = 'gre' + tos = '20' + + self.session.set(self._base_path + [interface, 'encapsulation', encapsulation]) + self.session.set(self._base_path + [interface, 'local-ip', self.local_v4]) + self.session.set(self._base_path + [interface, 'remote-ip', remote_ip4]) + + self.session.set(self._base_path + [interface, 'parameters', 'ip', 'no-pmtu-discovery']) + self.session.set(self._base_path + [interface, 'parameters', 'ip', 'key', gre_key]) + self.session.set(self._base_path + [interface, 'parameters', 'ip', 'tos', tos]) + + # Check if commit is ok + self.session.commit() + + conf = tunnel_conf(interface) + self.assertEqual(mtu, conf['mtu']) + self.assertEqual(interface, conf['ifname']) + self.assertEqual(encapsulation, conf['link_type']) + self.assertEqual(self.local_v4, conf['linkinfo']['info_data']['local']) + self.assertEqual(remote_ip4, conf['linkinfo']['info_data']['remote']) + self.assertEqual(0, conf['linkinfo']['info_data']['ttl']) + self.assertFalse( conf['linkinfo']['info_data']['pmtudisc']) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_ssh.py b/smoketest/scripts/cli/test_service_ssh.py index eede042de..68081e56f 100755 --- a/smoketest/scripts/cli/test_service_ssh.py +++ b/smoketest/scripts/cli/test_service_ssh.py @@ -27,7 +27,11 @@ from vyos.util import read_file PROCESS_NAME = 'sshd' SSHD_CONF = '/run/sshd/sshd_config' base_path = ['service', 'ssh'] -vrf = 'ssh-test' +vrf = 'mgmt' + +key_rsa = '/etc/ssh/ssh_host_rsa_key' +key_dsa = '/etc/ssh/ssh_host_dsa_key' +key_ed25519 = '/etc/ssh/ssh_host_ed25519_key' def get_config_value(key): tmp = read_file(SSHD_CONF) @@ -47,6 +51,10 @@ class TestServiceSSH(unittest.TestCase): self.session.commit() del self.session + self.assertTrue(os.path.isfile(key_rsa)) + self.assertTrue(os.path.isfile(key_dsa)) + self.assertTrue(os.path.isfile(key_ed25519)) + def test_ssh_default(self): # Check if SSH service runs with default settings - used for checking # behavior of <defaultValue> in XML definition diff --git a/smoketest/scripts/cli/test_system_ip.py b/smoketest/scripts/cli/test_system_ip.py index 8fc18ba88..4ad8d537d 100755 --- a/smoketest/scripts/cli/test_system_ip.py +++ b/smoketest/scripts/cli/test_system_ip.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 Francois Mertz fireboxled@gmail.com +# Copyright (C) 2020 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as diff --git a/smoketest/scripts/cli/test_system_ipv6.py b/smoketest/scripts/cli/test_system_ipv6.py new file mode 100755 index 000000000..df69739eb --- /dev/null +++ b/smoketest/scripts/cli/test_system_ipv6.py @@ -0,0 +1,102 @@ +#!/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.configsession import ConfigSession +from vyos.util import read_file + +base_path = ['system', 'ipv6'] + +file_forwarding = '/proc/sys/net/ipv6/conf/all/forwarding' +file_disable = '/etc/modprobe.d/vyos_disable_ipv6.conf' +file_dad = '/proc/sys/net/ipv6/conf/all/accept_dad' +file_multipath = '/proc/sys/net/ipv6/fib_multipath_hash_policy' + +class TestSystemIPv6(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_system_ipv6_forwarding(self): + # Test if IPv6 forwarding can be disabled globally, default is '1' + # which means forwearding enabled + self.assertEqual(read_file(file_forwarding), '1') + + self.session.set(base_path + ['disable-forwarding']) + self.session.commit() + + self.assertEqual(read_file(file_forwarding), '0') + + def test_system_ipv6_disable(self): + # Do not assign any IPv6 address on interfaces, this requires a reboot + # which can not be tested, but we can read the config file :) + self.session.set(base_path + ['disable']) + self.session.commit() + + # Verify configuration file + self.assertEqual(read_file(file_disable), 'options ipv6 disable_ipv6=1') + + def test_system_ipv6_strict_dad(self): + # This defaults to 1 + self.assertEqual(read_file(file_dad), '1') + + # Do not assign any IPv6 address on interfaces, this requires a reboot + # which can not be tested, but we can read the config file :) + self.session.set(base_path + ['strict-dad']) + self.session.commit() + + # Verify configuration file + self.assertEqual(read_file(file_dad), '2') + + def test_system_ipv6_multipath(self): + # This defaults to 0 + self.assertEqual(read_file(file_multipath), '0') + + # Do not assign any IPv6 address on interfaces, this requires a reboot + # which can not be tested, but we can read the config file :) + self.session.set(base_path + ['multipath', 'layer4-hashing']) + self.session.commit() + + # Verify configuration file + self.assertEqual(read_file(file_multipath), '1') + + def test_system_ipv6_neighbor_table_size(self): + # Maximum number of entries to keep in the ARP cache, the + # default is 8192 + + gc_thresh3 = '/proc/sys/net/ipv6/neigh/default/gc_thresh3' + gc_thresh2 = '/proc/sys/net/ipv6/neigh/default/gc_thresh2' + gc_thresh1 = '/proc/sys/net/ipv6/neigh/default/gc_thresh1' + self.assertEqual(read_file(gc_thresh3), '8192') + self.assertEqual(read_file(gc_thresh2), '4096') + self.assertEqual(read_file(gc_thresh1), '1024') + + for size in [1024, 2048, 4096, 8192, 16384, 32768]: + self.session.set(base_path + ['neighbor', 'table-size', str(size)]) + self.session.commit() + + self.assertEqual(read_file(gc_thresh3), str(size)) + self.assertEqual(read_file(gc_thresh2), str(size // 2)) + self.assertEqual(read_file(gc_thresh1), str(size // 8)) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_ntp.py b/smoketest/scripts/cli/test_system_ntp.py index 986c8dfb2..edb6ad94d 100755 --- a/smoketest/scripts/cli/test_system_ntp.py +++ b/smoketest/scripts/cli/test_system_ntp.py @@ -26,7 +26,7 @@ from vyos.util import read_file from vyos.util import process_named_running PROCESS_NAME = 'ntpd' -NTP_CONF = '/etc/ntp.conf' +NTP_CONF = '/run/ntpd/ntpd.conf' base_path = ['system', 'ntp'] def get_config_value(key): @@ -47,6 +47,8 @@ class TestSystemNTP(unittest.TestCase): self.session.commit() del self.session + self.assertFalse(process_named_running(PROCESS_NAME)) + def test_ntp_options(self): # Test basic NTP support with multiple servers and their options servers = ['192.0.2.1', '192.0.2.2'] @@ -76,7 +78,7 @@ class TestSystemNTP(unittest.TestCase): self.assertTrue(process_named_running(PROCESS_NAME)) def test_ntp_clients(self): - """ Test the allowed-networks statement """ + # Test the allowed-networks statement listen_address = ['127.0.0.1', '::1'] for listen in listen_address: self.session.set(base_path + ['listen-address', listen]) diff --git a/smoketest/scripts/cli/test_vrf.py b/smoketest/scripts/cli/test_vrf.py index 7bcfea861..5270e758a 100755 --- a/smoketest/scripts/cli/test_vrf.py +++ b/smoketest/scripts/cli/test_vrf.py @@ -14,53 +14,111 @@ # 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 unittest +from netifaces import interfaces -from vyos.configsession import ConfigSession, ConfigSessionError +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError from vyos.util import read_file from vyos.validate import is_intf_addr_assigned +from vyos.ifconfig import Interface + +base_path = ['vrf'] +vrfs = ['red', 'green', 'blue', 'foo-bar', 'baz_foo'] class VRFTest(unittest.TestCase): def setUp(self): self.session = ConfigSession(os.getpid()) - self._vrfs = ['red', 'green', 'blue'] def tearDown(self): # delete all VRFs - self.session.delete(['vrf']) + self.session.delete(base_path) self.session.commit() - del self.session + for vrf in vrfs: + self.assertNotIn(vrf, interfaces()) def test_vrf_table_id(self): - table = 1000 - for vrf in self._vrfs: - base = ['vrf', 'name', vrf] - description = "VyOS-VRF-" + vrf + table = '1000' + for vrf in vrfs: + base = base_path + ['name', vrf] + description = f'VyOS-VRF-{vrf}' self.session.set(base + ['description', description]) # check validate() - a table ID is mandatory with self.assertRaises(ConfigSessionError): self.session.commit() - self.session.set(base + ['table', str(table)]) - table += 1 + self.session.set(base + ['table', table]) + if vrf == 'green': + self.session.set(base + ['disable']) + + table = str(int(table) + 1) # commit changes self.session.commit() + # Verify VRF configuration + table = '1000' + iproute2_config = read_file('/etc/iproute2/rt_tables.d/vyos-vrf.conf') + for vrf in vrfs: + description = f'VyOS-VRF-{vrf}' + self.assertTrue(vrf in interfaces()) + vrf_if = Interface(vrf) + # validate proper interface description + self.assertEqual(vrf_if.get_alias(), description) + # validate admin up/down state of VRF + state = 'up' + if vrf == 'green': + state = 'down' + self.assertEqual(vrf_if.get_admin_state(), state) + + # Test the iproute2 lookup file, syntax is as follows: + # + # # id vrf name comment + # 1000 red # VyOS-VRF-red + # 1001 green # VyOS-VRF-green + # ... + regex = f'{table}\s+{vrf}\s+#\s+{description}' + self.assertTrue(re.findall(regex, iproute2_config)) + table = str(int(table) + 1) + def test_vrf_loopback_ips(self): - table = 1000 - for vrf in self._vrfs: - base = ['vrf', 'name', vrf] + table = '2000' + for vrf in vrfs: + base = base_path + ['name', vrf] self.session.set(base + ['table', str(table)]) - table += 1 + table = str(int(table) + 1) # commit changes self.session.commit() - for vrf in self._vrfs: + + # Verify VRF configuration + for vrf in vrfs: + self.assertTrue(vrf in interfaces()) self.assertTrue(is_intf_addr_assigned(vrf, '127.0.0.1')) self.assertTrue(is_intf_addr_assigned(vrf, '::1')) + def test_vrf_table_id_is_unalterable(self): + # Linux Kernel prohibits the change of a VRF table on the fly. + # VRF must be deleted and recreated! + table = '1000' + vrf = vrfs[0] + base = base_path + ['name', vrf] + self.session.set(base + ['table', table]) + + # commit changes + self.session.commit() + + # Check if VRF has been created + self.assertTrue(vrf in interfaces()) + + table = str(int(table) + 1) + self.session.set(base + ['table', table]) + # check validate() - table ID can not be altered! + with self.assertRaises(ConfigSessionError): + self.session.commit() + if __name__ == '__main__': - unittest.main(verbosity=2) + unittest.main(verbosity=2, failfast=True) diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index 7af3e3d7c..fd4ffed9a 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -41,26 +41,6 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -def helper_check_removed_vlan(conf,bridge,key,key_mangling): - key_update = re.sub(key_mangling[0], key_mangling[1], key) - if dict_search('member.interface', bridge): - for interface in bridge['member']['interface']: - tmp = leaf_node_changed(conf, ['member', 'interface',interface,key]) - if tmp: - if 'member' in bridge: - if 'interface' in bridge['member']: - if interface in bridge['member']['interface']: - bridge['member']['interface'][interface].update({f'{key_update}_removed': tmp }) - else: - bridge['member']['interface'].update({interface: {f'{key_update}_removed': tmp }}) - else: - bridge['member'].update({ 'interface': {interface: {f'{key_update}_removed': tmp }}}) - else: - bridge.update({'member': { 'interface': {interface: {f'{key_update}_removed': tmp }}}}) - - return bridge - - def get_config(config=None): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the @@ -74,18 +54,12 @@ def get_config(config=None): bridge = get_interface_dict(conf, base) # determine which members have been removed - tmp = node_changed(conf, ['member', 'interface']) + tmp = node_changed(conf, ['member', 'interface'], key_mangling=('-', '_')) if tmp: if 'member' in bridge: bridge['member'].update({'interface_remove': tmp }) else: bridge.update({'member': {'interface_remove': tmp }}) - - - # determine which members vlan have been removed - - bridge = helper_check_removed_vlan(conf,bridge,'native-vlan',('-', '_')) - bridge = helper_check_removed_vlan(conf,bridge,'allowed-vlan',('-', '_')) if dict_search('member.interface', bridge): # XXX: T2665: we need a copy of the dict keys for iteration, else we will get: @@ -99,7 +73,6 @@ def get_config(config=None): # the default dictionary is not properly paged into the dict (see T2665) # thus we will ammend it ourself default_member_values = defaults(base + ['member', 'interface']) - vlan_aware = False for interface,interface_config in bridge['member']['interface'].items(): bridge['member']['interface'][interface] = dict_merge( default_member_values, bridge['member']['interface'][interface]) @@ -120,19 +93,11 @@ def get_config(config=None): # Bridge members must not have an assigned address tmp = has_address_configured(conf, interface) if tmp: bridge['member']['interface'][interface].update({'has_address' : ''}) - + # VLAN-aware bridge members must not have VLAN interface configuration - if 'native_vlan' in interface_config: - vlan_aware = True - - if 'allowed_vlan' in interface_config: - vlan_aware = True - - - if vlan_aware: - tmp = has_vlan_subinterface_configured(conf,interface) - if tmp: - if tmp: bridge['member']['interface'][interface].update({'has_vlan' : ''}) + tmp = has_vlan_subinterface_configured(conf,interface) + if 'enable_vlan' in bridge and tmp: + bridge['member']['interface'][interface].update({'has_vlan' : ''}) return bridge @@ -142,8 +107,8 @@ def verify(bridge): verify_dhcpv6(bridge) verify_vrf(bridge) - - vlan_aware = False + + ifname = bridge['ifname'] if dict_search('member.interface', bridge): for interface, interface_config in bridge['member']['interface'].items(): @@ -166,31 +131,24 @@ def verify(bridge): if 'has_address' in interface_config: raise ConfigError(error_msg + 'it has an address assigned!') - - if 'has_vlan' in interface_config: - raise ConfigError(error_msg + 'it has an VLAN subinterface assigned!') - - # VLAN-aware bridge members must not have VLAN interface configuration - if 'native_vlan' in interface_config: - vlan_aware = True - - if 'allowed_vlan' in interface_config: - vlan_aware = True - - if vlan_aware and 'wlan' in interface: - raise ConfigError(error_msg + 'VLAN aware cannot be set!') - - if 'allowed_vlan' in interface_config: - for vlan in interface_config['allowed_vlan']: - if re.search('[0-9]{1,4}-[0-9]{1,4}', vlan): - vlan_range = vlan.split('-') - if int(vlan_range[0]) <1 and int(vlan_range[0])>4094: - raise ConfigError('VLAN ID must be between 1 and 4094') - if int(vlan_range[1]) <1 and int(vlan_range[1])>4094: - raise ConfigError('VLAN ID must be between 1 and 4094') - else: - if int(vlan) <1 and int(vlan)>4094: - raise ConfigError('VLAN ID must be between 1 and 4094') + + if 'enable_vlan' in bridge: + if 'has_vlan' in interface_config: + raise ConfigError(error_msg + 'it has an VLAN subinterface assigned!') + + if 'wlan' in interface: + raise ConfigError(error_msg + 'VLAN aware cannot be set!') + else: + for option in ['allowed_vlan', 'native_vlan']: + if option in interface_config: + raise ConfigError('Can not use VLAN options on non VLAN aware bridge') + + if 'enable_vlan' in bridge: + if dict_search('vif.1', bridge): + raise ConfigError(f'VLAN 1 sub interface cannot be set for VLAN aware bridge {ifname}, and VLAN 1 is always the parent interface') + else: + if dict_search('vif', bridge): + raise ConfigError(f'You must first activate "enable-vlan" of {ifname} bridge to use "vif"') return None diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index e4a6a5ec1..ee6f05fcd 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -17,6 +17,7 @@ import os import re +from glob import glob from sys import exit from ipaddress import IPv4Address from ipaddress import IPv4Network @@ -488,14 +489,9 @@ def apply(openvpn): # Do some cleanup when OpenVPN is disabled/deleted if 'deleted' in openvpn or 'disable' in openvpn: - # cleanup old configuration files - cleanup = [] - cleanup.append(cfg_file.format(**openvpn)) - cleanup.append(openvpn['auth_user_pass_file']) - - for file in cleanup: - if os.path.isfile(file): - os.unlink(file) + for cleanup_file in glob(f'/run/openvpn/{interface}.*'): + if os.path.isfile(cleanup_file): + os.unlink(cleanup_file) if interface in interfaces(): VTunIf(interface).remove() diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index 7cfc76aa0..3e6320f02 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -58,7 +58,7 @@ def get_config(config=None): # Determine which Wireguard peer has been removed. # Peers can only be removed with their public key! dict = {} - tmp = node_changed(conf, ['peer']) + tmp = node_changed(conf, ['peer'], key_mangling=('-', '_')) for peer in (tmp or []): pubkey = leaf_node_changed(conf, ['peer', peer, 'pubkey']) if pubkey: diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index 2d98cb11b..dae958774 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-2021 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -26,6 +26,7 @@ from netifaces import interfaces from vyos.config import Config from vyos.configdict import dict_merge from vyos.template import render +from vyos.template import is_ip_network from vyos.util import cmd from vyos.util import check_kmod from vyos.util import dict_search @@ -68,9 +69,9 @@ def verify_rule(config, err_msg): 'ports can only be specified when protocol is '\ 'either tcp, udp or tcp_udp!') - if '/' in (dict_search('translation.address', config) or []): + if is_ip_network(dict_search('translation.address', config)): raise ConfigError(f'{err_msg}\n' \ - 'Cannot use ports with an IPv4net type translation address as it\n' \ + 'Cannot use ports with an IPv4 network as translation address as it\n' \ 'statically maps a whole network of addresses onto another\n' \ 'network of addresses') @@ -147,7 +148,7 @@ def verify(nat): addr = dict_search('translation.address', config) if addr != None: - if addr != 'masquerade': + if addr != 'masquerade' and not is_ip_network(addr): for ip in addr.split('-'): if not is_addr_assigned(ip): print(f'WARNING: IP address {ip} does not exist on the system!') diff --git a/src/conf_mode/ntp.py b/src/conf_mode/ntp.py index b102b3e9e..52070aabc 100755 --- a/src/conf_mode/ntp.py +++ b/src/conf_mode/ntp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2020 VyOS maintainers and contributors +# Copyright (C) 2018-2021 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -24,7 +24,7 @@ from vyos.template import render from vyos import airbag airbag.enable() -config_file = r'/etc/ntp.conf' +config_file = r'/run/ntpd/ntpd.conf' systemd_override = r'/etc/systemd/system/ntp.service.d/override.conf' def get_config(config=None): @@ -33,8 +33,11 @@ def get_config(config=None): else: conf = Config() base = ['system', 'ntp'] + if not conf.exists(base): + return None ntp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + ntp['config_file'] = config_file return ntp def verify(ntp): @@ -42,7 +45,7 @@ def verify(ntp): if not ntp: return None - if len(ntp.get('allow_clients', {})) and not (len(ntp.get('server', {})) > 0): + if 'allow_clients' in ntp and 'server' not in ntp: raise ConfigError('NTP server not configured') verify_vrf(ntp) @@ -53,7 +56,7 @@ def generate(ntp): if not ntp: return None - render(config_file, 'ntp/ntp.conf.tmpl', ntp) + render(config_file, 'ntp/ntpd.conf.tmpl', ntp) render(systemd_override, 'ntp/override.conf.tmpl', ntp) return None diff --git a/src/conf_mode/policy-local-route.py b/src/conf_mode/policy-local-route.py index c4024dce4..013f22665 100755 --- a/src/conf_mode/policy-local-route.py +++ b/src/conf_mode/policy-local-route.py @@ -40,7 +40,7 @@ def get_config(config=None): # delete policy local-route dict = {} - tmp = node_changed(conf, ['policy', 'local-route', 'rule']) + tmp = node_changed(conf, ['policy', 'local-route', 'rule'], key_mangling=('-', '_')) if tmp: for rule in (tmp or []): src = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'source']) diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index f8e34285e..39d367b97 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -14,6 +14,8 @@ # 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 vyos.config import Config @@ -29,6 +31,14 @@ airbag.enable() config_file = r'/tmp/bgp.frr' +DEBUG = os.path.exists('/tmp/bgp.debug') +if DEBUG: + import logging + lg = logging.getLogger("vyos.frr") + lg.setLevel(logging.DEBUG) + ch = logging.StreamHandler() + lg.addHandler(ch) + def get_config(): conf = Config() base = ['protocols', 'bgp'] @@ -78,8 +88,13 @@ def verify(bgp): if neighbor == 'neighbor': # remote-as must be either set explicitly for the neighbor # or for the entire peer-group - if 'remote_as' not in peer_config: - if 'peer_group' not in peer_config or 'remote_as' not in asn_config['peer_group'][peer_config['peer_group']]: + if 'interface' in peer_config: + if 'remote_as' not in peer_config['interface']: + if 'peer_group' not in peer_config['interface'] or 'remote_as' not in asn_config['peer_group'][ peer_config['interface']['peer_group'] ]: + raise ConfigError('Remote AS must be set for neighbor or peer-group!') + + elif 'remote_as' not in peer_config: + if 'peer_group' not in peer_config or 'remote_as' not in asn_config['peer_group'][ peer_config['peer_group'] ]: raise ConfigError('Remote AS must be set for neighbor or peer-group!') for afi in ['ipv4_unicast', 'ipv6_unicast']: @@ -144,6 +159,21 @@ def apply(bgp): frr_cfg.load_configuration(daemon='bgpd') frr_cfg.modify_section(f'router bgp \S+', '') frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', bgp['new_frr_config']) + + # Debugging + if DEBUG: + from pprint import pprint + print('') + print('--------- DEBUGGING ----------') + pprint(dir(frr_cfg)) + print('Existing config:\n') + for line in frr_cfg.original_config: + print(line) + print(f'Replacement config:\n') + print(f'{bgp["new_frr_config"]}') + print(f'Modified config:\n') + print(f'{frr_cfg}') + frr_cfg.commit_configuration(daemon='bgpd') # If FRR config is blank, rerun the blank commit x times due to frr-reload @@ -152,14 +182,6 @@ def apply(bgp): for a in range(5): frr_cfg.commit_configuration(daemon='bgpd') - # Debugging - ''' - print('') - print('--------- DEBUGGING ----------') - print(f'Existing config:\n{frr_cfg["original_config"]}\n\n') - print(f'Replacement config:\n{bgp["new_frr_config"]}\n\n') - print(f'Modified config:\n{frr_cfg["modified_config"]}\n\n') - ''' return None diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py new file mode 100644 index 000000000..73c244571 --- /dev/null +++ b/src/conf_mode/protocols_ospf.py @@ -0,0 +1,103 @@ +#!/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 vyos.config import Config +from vyos.configdict import dict_merge +from vyos.template import render +from vyos.template import render_to_string +from vyos.util import call +from vyos.util import dict_search +from vyos import ConfigError +from vyos import frr +from vyos import airbag +airbag.enable() + +config_file = r'/tmp/ospf.frr' + +DEBUG = os.path.exists('/tmp/ospf.debug') +if DEBUG: + import logging + lg = logging.getLogger("vyos.frr") + lg.setLevel(logging.DEBUG) + ch = logging.StreamHandler() + lg.addHandler(ch) + +def get_config(): + conf = Config() + base = ['protocols', 'ospf'] + ospf = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + return ospf + +def verify(ospf): + if not ospf: + return None + + return None + +def generate(ospf): + if not ospf: + ospf['new_frr_config'] = '' + return None + + # render(config) not needed, its only for debug + render(config_file, 'frr/ospf.frr.tmpl', ospf) + ospf['new_frr_config'] = render_to_string('frr/ospf.frr.tmpl', ospf) + + return None + +def apply(ospf): + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + frr_cfg.load_configuration(daemon='ospfd') + frr_cfg.modify_section(f'router ospf', '') + + # Debugging + if DEBUG: + from pprint import pprint + print('') + print('--------- DEBUGGING ----------') + pprint(dir(frr_cfg)) + print('Existing config:\n') + for line in frr_cfg.original_config: + print(line) + print(f'Replacement config:\n') + print(f'{ospf["new_frr_config"]}') + print(f'Modified config:\n') + print(f'{frr_cfg}') + + frr_cfg.commit_configuration(daemon='ospfd') + + # If FRR config is blank, rerun the blank commit x times due to frr-reload + # behavior/bug not properly clearing out on one commit. + if ospf['new_frr_config'] == '': + for a in range(5): + frr_cfg.commit_configuration(daemon='ospfd') + + 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/ssh.py b/src/conf_mode/ssh.py index 8eeb0a7c1..67724b043 100755 --- a/src/conf_mode/ssh.py +++ b/src/conf_mode/ssh.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2020 VyOS maintainers and contributors +# Copyright (C) 2018-2021 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -17,6 +17,8 @@ import os from sys import exit +from syslog import syslog +from syslog import LOG_INFO from vyos.config import Config from vyos.configdict import dict_merge @@ -31,6 +33,10 @@ airbag.enable() config_file = r'/run/sshd/sshd_config' systemd_override = r'/etc/systemd/system/ssh.service.d/override.conf' +key_rsa = '/etc/ssh/ssh_host_rsa_key' +key_dsa = '/etc/ssh/ssh_host_dsa_key' +key_ed25519 = '/etc/ssh/ssh_host_ed25519_key' + def get_config(config=None): if config: conf = config @@ -66,6 +72,18 @@ def generate(ssh): return None + # This usually happens only once on a fresh system, SSH keys need to be + # freshly generted, one per every system! + if not os.path.isfile(key_rsa): + syslog(LOG_INFO, 'SSH RSA host key not found, generating new key!') + call(f'ssh-keygen -q -N "" -t rsa -f {key_rsa}') + if not os.path.isfile(key_dsa): + syslog(LOG_INFO, 'SSH DSA host key not found, generating new key!') + call(f'ssh-keygen -q -N "" -t dsa -f {key_dsa}') + if not os.path.isfile(key_ed25519): + syslog(LOG_INFO, 'SSH ed25519 host key not found, generating new key!') + call(f'ssh-keygen -q -N "" -t ed25519 -f {key_ed25519}') + render(config_file, 'ssh/sshd_config.tmpl', ssh) render(systemd_override, 'ssh/override.conf.tmpl', ssh) # Reload systemd manager configuration diff --git a/src/conf_mode/system-option.py b/src/conf_mode/system-option.py index 910c14474..454611c55 100755 --- a/src/conf_mode/system-option.py +++ b/src/conf_mode/system-option.py @@ -87,10 +87,10 @@ def apply(options): # Ctrl-Alt-Delete action if os.path.exists(systemd_action_file): os.unlink(systemd_action_file) - if 'ctrl_alt_del' in options: - if options['ctrl_alt_del'] == 'reboot': + if 'ctrl_alt_delete' in options: + if options['ctrl_alt_delete'] == 'reboot': os.symlink('/lib/systemd/system/reboot.target', systemd_action_file) - elif options['ctrl_alt_del'] == 'poweroff': + elif options['ctrl_alt_delete'] == 'poweroff': os.symlink('/lib/systemd/system/poweroff.target', systemd_action_file) # Configure HTTP client @@ -104,11 +104,11 @@ def apply(options): os.unlink(ssh_config) # Reboot system on kernel panic + timeout = '0' + if 'reboot_on_panic' in options: + timeout = '60' with open('/proc/sys/kernel/panic', 'w') as f: - if 'reboot_on_panic' in options: - f.write('60') - else: - f.write('0') + f.write(timeout) # tuned - performance tuning if 'performance' in options: diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index c4ba859b7..6c6e219a5 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-2021 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -17,29 +17,20 @@ import os from sys import exit -from copy import deepcopy from json import loads from vyos.config import Config -from vyos.configdict import list_diff +from vyos.configdict import node_changed from vyos.ifconfig import Interface -from vyos.util import read_file, cmd -from vyos import ConfigError from vyos.template import render - +from vyos.util import cmd +from vyos.util import dict_search +from vyos import ConfigError from vyos import airbag airbag.enable() config_file = r'/etc/iproute2/rt_tables.d/vyos-vrf.conf' -default_config_data = { - 'bind_to_all': '0', - 'deleted': False, - 'vrf_add': [], - 'vrf_existing': [], - 'vrf_remove': [] -} - def _cmd(command): cmd(command, raising=ConfigError, message='Error changing VRF') @@ -81,112 +72,62 @@ def get_config(config=None): conf = config else: conf = Config() - vrf_config = deepcopy(default_config_data) - cfg_base = ['vrf'] - if not conf.exists(cfg_base): - # get all currently effetive VRFs and mark them for deletion - vrf_config['vrf_remove'] = conf.list_effective_nodes(cfg_base + ['name']) - else: - # set configuration level base - conf.set_level(cfg_base) - - # Should services be allowed to bind to all VRFs? - if conf.exists(['bind-to-all']): - vrf_config['bind_to_all'] = '1' - - # Determine vrf interfaces (currently effective) - to determine which - # vrf interface is no longer present and needs to be removed - eff_vrf = conf.list_effective_nodes(['name']) - act_vrf = conf.list_nodes(['name']) - vrf_config['vrf_remove'] = list_diff(eff_vrf, act_vrf) - - # read in individual VRF definition and build up - # configuration - for name in conf.list_nodes(['name']): - vrf_inst = { - 'description' : '', - 'members': [], - 'name' : name, - 'table' : '', - 'table_mod': False - } - conf.set_level(cfg_base + ['name', name]) - - if conf.exists(['table']): - # VRF table can't be changed on demand, thus we need to read in the - # current and the effective routing table number - act_table = conf.return_value(['table']) - eff_table = conf.return_effective_value(['table']) - vrf_inst['table'] = act_table - if eff_table and eff_table != act_table: - vrf_inst['table_mod'] = True - - if conf.exists(['description']): - vrf_inst['description'] = conf.return_value(['description']) - - # append individual VRF configuration to global configuration list - vrf_config['vrf_add'].append(vrf_inst) - - # set configuration level base - conf.set_level(cfg_base) - - # check VRFs which need to be removed as they are not allowed to have - # interfaces attached - tmp = [] - for name in vrf_config['vrf_remove']: - vrf_inst = { - 'interfaces': [], - 'name': name, - 'routes': [] - } - - # find member interfaces of this particulat VRF - vrf_inst['interfaces'] = vrf_interfaces(conf, name) - - # find routing protocols used by this VRF - vrf_inst['routes'] = vrf_routing(conf, name) - - # append individual VRF configuration to temporary configuration list - tmp.append(vrf_inst) - - # replace values in vrf_remove with list of dictionaries - # as we need it in verify() - we can't delete a VRF with members attached - vrf_config['vrf_remove'] = tmp - return vrf_config - -def verify(vrf_config): - # ensure VRF is not assigned to any interface - for vrf in vrf_config['vrf_remove']: - if len(vrf['interfaces']) > 0: - raise ConfigError(f"VRF {vrf['name']} can not be deleted. It has active member interfaces!") + base = ['vrf'] + vrf = conf.get_config_dict(base, get_first_key=True) - if len(vrf['routes']) > 0: - raise ConfigError(f"VRF {vrf['name']} can not be deleted. It has active routing protocols!") + # determine which VRF has been removed + for name in node_changed(conf, base + ['name']): + if 'vrf_remove' not in vrf: + vrf.update({'vrf_remove' : {}}) - table_ids = [] - for vrf in vrf_config['vrf_add']: - # table id is mandatory - if not vrf['table']: - raise ConfigError(f"VRF {vrf['name']} table id is mandatory!") + vrf['vrf_remove'][name] = {} + # get VRF bound interfaces + interfaces = vrf_interfaces(conf, name) + if interfaces: vrf['vrf_remove'][name]['interface'] = interfaces + # get VRF bound routing instances + routes = vrf_routing(conf, name) + if routes: vrf['vrf_remove'][name]['route'] = routes - # routing table id can't be changed - OS restriction - if vrf['table_mod']: - raise ConfigError(f"VRF {vrf['name']} table id modification is not possible!") + return vrf - # VRf routing table ID must be unique on the system - if vrf['table'] in table_ids: - raise ConfigError(f"VRF {vrf['name']} table id {vrf['table']} is not unique!") - - table_ids.append(vrf['table']) +def verify(vrf): + # ensure VRF is not assigned to any interface + if 'vrf_remove' in vrf: + for name, config in vrf['vrf_remove'].items(): + if 'interface' in config: + raise ConfigError(f'Can not remove VRF "{name}", it still has '\ + f'member interfaces!') + if 'route' in config: + raise ConfigError(f'Can not remove VRF "{name}", it still has '\ + f'static routes installed!') + + if 'name' in vrf: + table_ids = [] + for name, config in vrf['name'].items(): + # table id is mandatory + if 'table' not in config: + raise ConfigError(f'VRF "{name}" table id is mandatory!') + + # routing table id can't be changed - OS restriction + if os.path.isdir(f'/sys/class/net/{name}'): + tmp = loads(cmd(f'ip -j -d link show {name}'))[0] + tmp = str(dict_search('linkinfo.info_data.table', tmp)) + if tmp and tmp != config['table']: + raise ConfigError(f'VRF "{name}" table id modification not possible!') + + # VRf routing table ID must be unique on the system + if config['table'] in table_ids: + raise ConfigError(f'VRF "{name}" table id is not unique!') + table_ids.append(config['table']) return None -def generate(vrf_config): - render(config_file, 'vrf/vrf.conf.tmpl', vrf_config) +def generate(vrf): + render(config_file, 'vrf/vrf.conf.tmpl', vrf) return None -def apply(vrf_config): +def apply(vrf): # Documentation # # - https://github.com/torvalds/linux/blob/master/Documentation/networking/vrf.txt @@ -196,40 +137,48 @@ def apply(vrf_config): # - https://netdevconf.info/1.2/slides/oct6/02_ahern_what_is_l3mdev_slides.pdf # set the default VRF global behaviour - bind_all = vrf_config['bind_to_all'] - if read_file('/proc/sys/net/ipv4/tcp_l3mdev_accept') != bind_all: - _cmd(f'sysctl -wq net.ipv4.tcp_l3mdev_accept={bind_all}') - _cmd(f'sysctl -wq net.ipv4.udp_l3mdev_accept={bind_all}') - - for vrf in vrf_config['vrf_remove']: - name = vrf['name'] - if os.path.isdir(f'/sys/class/net/{name}'): - _cmd(f'ip -4 route del vrf {name} unreachable default metric 4278198272') - _cmd(f'ip -6 route del vrf {name} unreachable default metric 4278198272') - _cmd(f'ip link delete dev {name}') - - for vrf in vrf_config['vrf_add']: - name = vrf['name'] - table = vrf['table'] - - if not os.path.isdir(f'/sys/class/net/{name}'): - # For each VRF apart from your default context create a VRF - # interface with a separate routing table - _cmd(f'ip link add {name} type vrf table {table}') - # Start VRf - _cmd(f'ip link set dev {name} up') - # The kernel Documentation/networking/vrf.txt also recommends - # adding unreachable routes to the VRF routing tables so that routes - # afterwards are taken. - _cmd(f'ip -4 route add vrf {name} unreachable default metric 4278198272') - _cmd(f'ip -6 route add vrf {name} unreachable default metric 4278198272') - # We also should add proper loopback IP addresses to the newly - # created VRFs for services bound to the loopback address (SNMP, NTP) - _cmd(f'ip -4 addr add 127.0.0.1/8 dev {name}') - _cmd(f'ip -6 addr add ::1/128 dev {name}') - - # set VRF description for e.g. SNMP monitoring - Interface(name).set_alias(vrf['description']) + bind_all = '0' + if 'bind_to_all' in vrf: + bind_all = '1' + _cmd(f'sysctl -wq net.ipv4.tcp_l3mdev_accept={bind_all}') + _cmd(f'sysctl -wq net.ipv4.udp_l3mdev_accept={bind_all}') + + for tmp in (dict_search('vrf_remove', vrf) or []): + if os.path.isdir(f'/sys/class/net/{tmp}'): + _cmd(f'ip -4 route del vrf {tmp} unreachable default metric 4278198272') + _cmd(f'ip -6 route del vrf {tmp} unreachable default metric 4278198272') + _cmd(f'ip link delete dev {tmp}') + + if 'name' in vrf: + for name, config in vrf['name'].items(): + table = config['table'] + + if not os.path.isdir(f'/sys/class/net/{name}'): + # For each VRF apart from your default context create a VRF + # interface with a separate routing table + _cmd(f'ip link add {name} type vrf table {table}') + # The kernel Documentation/networking/vrf.txt also recommends + # adding unreachable routes to the VRF routing tables so that routes + # afterwards are taken. + _cmd(f'ip -4 route add vrf {name} unreachable default metric 4278198272') + _cmd(f'ip -6 route add vrf {name} unreachable default metric 4278198272') + # We also should add proper loopback IP addresses to the newly + # created VRFs for services bound to the loopback address (SNMP, NTP) + _cmd(f'ip -4 addr add 127.0.0.1/8 dev {name}') + _cmd(f'ip -6 addr add ::1/128 dev {name}') + + # set VRF description for e.g. SNMP monitoring + vrf_if = Interface(name) + vrf_if.set_alias(config.get('description', '')) + # Enable/Disable of an interface must always be done at the end of the + # derived class to make use of the ref-counting set_admin_state() + # function. We will only enable the interface if 'up' was called as + # often as 'down'. This is required by some interface implementations + # as certain parameters can only be changed when the interface is + # in admin-down state. This ensures the link does not flap during + # reconfiguration. + state = 'down' if 'disable' in config else 'up' + vrf_if.set_admin_state(state) # Linux routing uses rules to find tables - routing targets are then # looked up in those tables. If the lookup got a matching route, the @@ -248,13 +197,13 @@ def apply(vrf_config): local_pref = [r.get('priority') for r in list_rules() if r.get('table') == 'local'][0] # change preference when VRFs are enabled and local lookup table is default - if not local_pref and vrf_config['vrf_add']: + if not local_pref and 'name' in vrf: for af in ['-4', '-6']: _cmd(f'ip {af} rule add pref 32765 table local') _cmd(f'ip {af} rule del pref 0') # return to default lookup preference when no VRF is configured - if not vrf_config['vrf_add']: + if 'name' not in vrf: for af in ['-4', '-6']: _cmd(f'ip {af} rule add pref 0 table local') _cmd(f'ip {af} rule del pref 32765') diff --git a/src/etc/sysctl.d/30-vyos-router.conf b/src/etc/sysctl.d/30-vyos-router.conf new file mode 100644 index 000000000..8265e12dc --- /dev/null +++ b/src/etc/sysctl.d/30-vyos-router.conf @@ -0,0 +1,98 @@ +# +# VyOS specific sysctl settings, see sysctl.conf (5) for information. +# + +# Panic on OOPS +kernel.panic_on_oops=1 + +# Timeout before rebooting on panic +kernel.panic=60 + +# Send all core files to /var/core/core.program.pid.time +kernel.core_pattern=/var/core/core-%e-%p-%t + +# ARP configuration +# arp_filter - allow multiple network interfaces on same subnet +# arp_announce - avoid local addresses no on target's subnet +# arp_ignore - reply only if target IP is local_address on the interface + +# arp_filter defaults to 1 so set all to 0 so vrrp interfaces can override it. +net.ipv4.conf.all.arp_filter=0 + +# https://phabricator.vyos.net/T300 +net.ipv4.conf.all.arp_ignore=0 + +net.ipv4.conf.all.arp_announce=2 + +# Enable packet forwarding for IPv4 +net.ipv4.ip_forward=1 + +# if a primary address is removed from an interface promote the +# secondary address if available +net.ipv4.conf.all.promote_secondaries=1 + +# Ignore ICMP broadcasts sent to broadcast/multicast +net.ipv4.icmp_echo_ignore_broadcasts=1 + +# Ignore bogus ICMP errors +net.ipv4.icmp_ignore_bogus_error_responses=1 + +# Send ICMP responses with primary address of exiting interface +net.ipv4.icmp_errors_use_inbound_ifaddr=1 + +# Log packets with impossible addresses to kernel log +net.ipv4.conf.all.log_martians=1 + +# Do not ignore all ICMP ECHO requests by default +net.ipv4.icmp_echo_ignore_all=0 + +# Disable source validation by default +net.ipv4.conf.all.rp_filter=0 +net.ipv4.conf.default.rp_filter=0 + +# Enable tcp syn-cookies by default +net.ipv4.tcp_syncookies=1 + +# Disable accept_redirects by default for any interface +net.ipv4.conf.all.accept_redirects=0 +net.ipv4.conf.default.accept_redirects=0 +net.ipv6.conf.all.accept_redirects=0 +net.ipv6.conf.default.accept_redirects=0 + +# Disable accept_source_route by default +net.ipv4.conf.all.accept_source_route=0 +net.ipv4.conf.default.accept_source_route=0 +net.ipv6.conf.all.accept_source_route=0 +net.ipv6.conf.default.accept_source_route=0 + +# Enable send_redirects by default +net.ipv4.conf.all.send_redirects=1 +net.ipv4.conf.default.send_redirects=1 + +# Increase size of buffer for netlink +net.core.rmem_max=2097152 + +# Enable packet forwarding for IPv6 +net.ipv6.conf.all.forwarding=1 + +# Increase route table limit +net.ipv6.route.max_size = 262144 + +# Do not forget IPv6 addresses when a link goes down +net.ipv6.conf.default.keep_addr_on_down=1 +net.ipv6.conf.all.keep_addr_on_down=1 + +# Default value of 20 seems to interfere with larger OSPF and VRRP setups +net.ipv4.igmp_max_memberships = 512 + +# Enable conntrack helper by default +net.netfilter.nf_conntrack_helper=1 + +# Increase default garbage collection thresholds +net.ipv4.neigh.default.gc_thresh1 = 1024 +net.ipv4.neigh.default.gc_thresh2 = 4096 +net.ipv4.neigh.default.gc_thresh3 = 8192 +# +net.ipv6.neigh.default.gc_thresh1 = 1024 +net.ipv6.neigh.default.gc_thresh2 = 4096 +net.ipv6.neigh.default.gc_thresh3 = 8192 diff --git a/src/etc/udev/rules.d/42-qemu-usb.rules b/src/etc/udev/rules.d/42-qemu-usb.rules new file mode 100644 index 000000000..a79543df7 --- /dev/null +++ b/src/etc/udev/rules.d/42-qemu-usb.rules @@ -0,0 +1,14 @@ +# +# Enable autosuspend for qemu emulated usb hid devices. +# +# Note that there are buggy qemu versions which advertise remote +# wakeup support but don't actually implement it correctly. This +# is the reason why we need a match for the serial number here. +# The serial number "42" is used to tag the implementations where +# remote wakeup is working. +# +# Gerd Hoffmann <kraxel@xxxxxxxxxx> + +ACTION=="add", SUBSYSTEM=="usb", ATTR{product}=="QEMU USB Mouse", ATTR{serial}=="42", TEST=="power/control", ATTR{power/control}="auto" +ACTION=="add", SUBSYSTEM=="usb", ATTR{product}=="QEMU USB Tablet", ATTR{serial}=="42", TEST=="power/control", ATTR{power/control}="auto" +ACTION=="add", SUBSYSTEM=="usb", ATTR{product}=="QEMU USB Keyboard", ATTR{serial}=="42", TEST=="power/control", ATTR{power/control}="auto" diff --git a/src/etc/udev/rules.d/63-hyperv-vf-net.rules b/src/etc/udev/rules.d/63-hyperv-vf-net.rules new file mode 100644 index 000000000..b4dcb5a39 --- /dev/null +++ b/src/etc/udev/rules.d/63-hyperv-vf-net.rules @@ -0,0 +1,5 @@ +ATTR{[dmi/id]sys_vendor}!="Microsoft Corporation", GOTO="end_hyperv_nic" + +ACTION=="add", SUBSYSTEM=="net", DRIVERS=="hv_pci", NAME="vf_%k" + +LABEL="end_hyperv_nic" diff --git a/src/etc/udev/rules.d/64-vyos-vmware-net.rules b/src/etc/udev/rules.d/64-vyos-vmware-net.rules new file mode 100644 index 000000000..66a4a069b --- /dev/null +++ b/src/etc/udev/rules.d/64-vyos-vmware-net.rules @@ -0,0 +1,14 @@ +ATTR{[dmi/id]sys_vendor}!="VMware, Inc.", GOTO="end_vmware_nic" + +ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet0", ENV{VYOS_IFNAME}="eth0" +ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet1", ENV{VYOS_IFNAME}="eth1" +ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet2", ENV{VYOS_IFNAME}="eth2" +ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet3", ENV{VYOS_IFNAME}="eth3" +ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet4", ENV{VYOS_IFNAME}="eth4" +ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet5", ENV{VYOS_IFNAME}="eth5" +ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet6", ENV{VYOS_IFNAME}="eth6" +ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet7", ENV{VYOS_IFNAME}="eth7" +ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet8", ENV{VYOS_IFNAME}="eth8" +ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet9", ENV{VYOS_IFNAME}="eth9" + +LABEL="end_vmware_nic" diff --git a/src/etc/udev/rules.d/65-vyatta-net.rules b/src/etc/udev/rules.d/65-vyatta-net.rules new file mode 100644 index 000000000..2b48c1213 --- /dev/null +++ b/src/etc/udev/rules.d/65-vyatta-net.rules @@ -0,0 +1,26 @@ +# These rules use vyatta_net_name to persistently name network interfaces +# per "hwid" association in the Vyatta configuration file. + +ACTION!="add", GOTO="vyatta_net_end" +SUBSYSTEM!="net", GOTO="vyatta_net_end" + +# ignore the interface if a name has already been set +NAME=="?*", GOTO="vyatta_net_end" + +# Do name change for ethernet and wireless devices only +KERNEL!="eth*|wlan*", GOTO="vyatta_net_end" + +# ignore "secondary" monitor interfaces of mac80211 drivers +KERNEL=="wlan*", ATTRS{type}=="803", GOTO="vyatta_net_end" + +# If using VyOS predefined names +ENV{VYOS_IFNAME}!="eth*", GOTO="end_vyos_predef_names" + +DRIVERS=="?*", PROGRAM="vyatta_net_name %k $attr{address} $env{VYOS_IFNAME}", NAME="%c", GOTO="vyatta_net_end" + +LABEL="end_vyos_predef_names" + +# ignore interfaces without a driver link like bridges and VLANs +DRIVERS=="?*", PROGRAM="vyatta_net_name %k $attr{address}", NAME="%c" + +LABEL="vyatta_net_end" diff --git a/src/tests/test_template.py b/src/tests/test_template.py index 544755692..7800d007f 100644 --- a/src/tests/test_template.py +++ b/src/tests/test_template.py @@ -93,3 +93,22 @@ class TestVyOSTemplate(TestCase): self.assertEqual(vyos.template.dec_ip('2001:db8::b/64', '10'), '2001:db8::1') self.assertEqual(vyos.template.dec_ip('2001:db8::f', '5'), '2001:db8::a') + def test_is_network(self): + self.assertFalse(vyos.template.is_ip_network('192.0.2.0')) + self.assertFalse(vyos.template.is_ip_network('192.0.2.1/24')) + self.assertTrue(vyos.template.is_ip_network('192.0.2.0/24')) + + self.assertFalse(vyos.template.is_ip_network('2001:db8::')) + self.assertFalse(vyos.template.is_ip_network('2001:db8::ffff')) + self.assertTrue(vyos.template.is_ip_network('2001:db8::/48')) + self.assertTrue(vyos.template.is_ip_network('2001:db8:1000::/64')) + + def test_is_network(self): + self.assertTrue(vyos.template.compare_netmask('10.0.0.0/8', '20.0.0.0/8')) + self.assertTrue(vyos.template.compare_netmask('10.0.0.0/16', '20.0.0.0/16')) + self.assertFalse(vyos.template.compare_netmask('10.0.0.0/8', '20.0.0.0/16')) + self.assertFalse(vyos.template.compare_netmask('10.0.0.1', '20.0.0.0/16')) + + self.assertTrue(vyos.template.compare_netmask('2001:db8:1000::/48', '2001:db8:2000::/48')) + self.assertTrue(vyos.template.compare_netmask('2001:db8:1000::/64', '2001:db8:2000::/64')) + self.assertFalse(vyos.template.compare_netmask('2001:db8:1000::/48', '2001:db8:2000::/64')) diff --git a/src/validators/allowed-vlan b/src/validators/allowed-vlan new file mode 100755 index 000000000..11389390b --- /dev/null +++ b/src/validators/allowed-vlan @@ -0,0 +1,19 @@ +#! /usr/bin/python3 + +import sys +import re + +if __name__ == '__main__': + if len(sys.argv)>1: + allowed_vlan = sys.argv[1] + if re.search('[0-9]{1,4}-[0-9]{1,4}', allowed_vlan): + for tmp in allowed_vlan.split('-'): + if int(tmp) not in range(1, 4095): + sys.exit(1) + else: + if int(allowed_vlan) not in range(1, 4095): + sys.exit(1) + else: + sys.exit(2) + + sys.exit(0) |