summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/frr/isisd.frr.j242
-rw-r--r--data/templates/https/vyos-http-api.service.j21
-rw-r--r--data/templates/pppoe/peer.j22
-rw-r--r--debian/control369
-rw-r--r--interface-definitions/include/isis/level-1-2-leaf.xml.i13
-rw-r--r--interface-definitions/include/isis/lfa-local.xml.i128
-rw-r--r--interface-definitions/include/isis/lfa-protocol.xml.i11
-rw-r--r--interface-definitions/include/isis/lfa-remote.xml.i28
-rw-r--r--interface-definitions/include/isis/protocol-common-config.xml.i8
-rw-r--r--interface-definitions/interfaces-pppoe.xml.in14
-rw-r--r--op-mode-definitions/include/isis-common.xml.i27
-rw-r--r--op-mode-definitions/raid.xml.in6
-rw-r--r--python/vyos/component_version.py25
-rw-r--r--python/vyos/config_mgmt.py29
-rw-r--r--python/vyos/defaults.py1
-rwxr-xr-xsmoketest/scripts/cli/test_component_version.py50
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_pppoe.py9
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_isis.py62
-rwxr-xr-xsrc/conf_mode/http-api.py6
-rwxr-xr-xsrc/conf_mode/interfaces-pppoe.py5
-rwxr-xr-xsrc/conf_mode/protocols_isis.py37
-rw-r--r--src/etc/sysctl.d/30-vyos-router.conf3
-rwxr-xr-xsrc/helpers/config_dependency.py79
-rwxr-xr-xsrc/helpers/vyos-save-config.py19
-rwxr-xr-xsrc/init/vyos-router26
-rwxr-xr-xsrc/services/vyos-http-api-server173
26 files changed, 895 insertions, 278 deletions
diff --git a/data/templates/frr/isisd.frr.j2 b/data/templates/frr/isisd.frr.j2
index dbb8c7305..1e1cc3c27 100644
--- a/data/templates/frr/isisd.frr.j2
+++ b/data/templates/frr/isisd.frr.j2
@@ -165,6 +165,48 @@ advertise-passive-only
{% endfor %}
{% endfor %}
{% endif %}
+{% if fast_reroute.lfa is vyos_defined %}
+{% if fast_reroute.lfa.local is vyos_defined %}
+{% if fast_reroute.lfa.local.load_sharing.disable.level_1 is vyos_defined %}
+ fast-reroute load-sharing disable level-1
+{% elif fast_reroute.lfa.local.load_sharing.disable.level_2 is vyos_defined %}
+ fast-reroute load-sharing disable level-2
+{% elif fast_reroute.lfa.local.load_sharing.disable is vyos_defined %}
+ fast-reroute load-sharing disable
+{% endif %}
+{% if fast_reroute.lfa.local.priority_limit is vyos_defined %}
+{% for priority, priority_limit_options in fast_reroute.lfa.local.priority_limit.items() %}
+{% for level in priority_limit_options %}
+ fast-reroute priority-limit {{ priority }} {{ level | replace('_', '-') }}
+{% endfor %}
+{% endfor %}
+{% endif %}
+{% if fast_reroute.lfa.local.tiebreaker is vyos_defined %}
+{% for tiebreaker, tiebreaker_options in fast_reroute.lfa.local.tiebreaker.items() %}
+{% for index, index_options in tiebreaker_options.items() %}
+{% for index_value, index_value_options in index_options.items() %}
+{% for level in index_value_options %}
+ fast-reroute lfa tiebreaker {{ tiebreaker | replace('_', '-') }} index {{ index_value }} {{ level | replace('_', '-') }}
+{% endfor %}
+{% endfor %}
+{% endfor %}
+{% endfor %}
+{% endif %}
+{% endif %}
+{% if fast_reroute.lfa.remote.prefix_list is vyos_defined %}
+{% for prefix_list, prefix_list_options in fast_reroute.lfa.remote.prefix_list.items() %}
+{% if prefix_list_options.level_1 is vyos_defined %}
+fast-reroute remote-lfa prefix-list {{ prefix_list }} level-1
+{% endif %}
+{% if prefix_list_options.level_2 is vyos_defined %}
+fast-reroute remote-lfa prefix-list {{ prefix_list }} level-2
+{% endif %}
+{% if prefix_list is vyos_defined and prefix_list_options.level_1 is not vyos_defined and prefix_list_options.level_2 is not vyos_defined %}
+fast-reroute remote-lfa prefix-list {{ prefix_list }}
+{% endif %}
+{% endfor %}
+{% endif %}
+{% endif %}
{% if redistribute.ipv4 is vyos_defined %}
{% for protocol, protocol_options in redistribute.ipv4.items() %}
{% for level, level_config in protocol_options.items() %}
diff --git a/data/templates/https/vyos-http-api.service.j2 b/data/templates/https/vyos-http-api.service.j2
index fb424e06c..f620b3248 100644
--- a/data/templates/https/vyos-http-api.service.j2
+++ b/data/templates/https/vyos-http-api.service.j2
@@ -6,6 +6,7 @@ Requires=vyos-router.service
[Service]
ExecStart={{ vrf_command }}/usr/libexec/vyos/services/vyos-http-api-server
+ExecReload=kill -HUP $MAINPID
Type=idle
SyslogIdentifier=vyos-http-api
diff --git a/data/templates/pppoe/peer.j2 b/data/templates/pppoe/peer.j2
index f30cefe63..2a99fcb2a 100644
--- a/data/templates/pppoe/peer.j2
+++ b/data/templates/pppoe/peer.j2
@@ -50,7 +50,7 @@ ifname {{ ifname }}
ipparam {{ ifname }}
debug
mtu {{ mtu }}
-mru {{ mtu }}
+mru {{ mru }}
{% if authentication is vyos_defined %}
{{ 'user "' + authentication.username + '"' if authentication.username is vyos_defined }}
diff --git a/debian/control b/debian/control
index 735733956..32de13f1b 100644
--- a/debian/control
+++ b/debian/control
@@ -11,15 +11,17 @@ Build-Depends:
libvyosconfig0 (>= 0.0.7),
libzmq3-dev,
python3 (>= 3.10),
- python3-coverage,
+# For generating command definitions
python3-lxml,
+ python3-xmltodict,
+# For running tests
+ python3-coverage,
python3-netifaces,
python3-nose,
python3-jinja2,
python3-psutil,
python3-setuptools,
python3-sphinx,
- python3-xmltodict,
quilt,
whois
Standards-Version: 3.9.6
@@ -31,107 +33,20 @@ Pre-Depends:
libpam-tacplus [amd64],
libpam-radius-auth [amd64]
Depends:
+## Fundamentals
${python3:Depends} (>= 3.10),
- aardvark-dns,
- accel-ppp,
- auditd,
- avahi-daemon,
- aws-gwlbtun,
- beep,
- bmon,
- bsdmainutils,
- charon-systemd,
- conntrack,
- conntrackd,
- conserver-client,
- conserver-server,
- console-data,
- cron,
- curl,
- dbus,
- ddclient (>= 3.9.1),
- dropbear,
- easy-rsa,
- etherwake,
- ethtool,
- fdisk,
- fastnetmon [amd64],
- file,
- frr (>= 7.5),
- frr-pythontools,
- frr-rpki-rtrlib,
- frr-snmp,
- fuse-overlayfs,
- libpam-google-authenticator,
- grc,
- haproxy,
- hostapd,
- hsflowd,
- hvinfo,
- igmpproxy,
- ipaddrcheck,
- iperf,
- iperf3,
- iproute2 (>= 6.0.0),
- iptables,
- iputils-arping,
- isc-dhcp-client,
- isc-dhcp-relay,
- isc-dhcp-server,
- iw,
- keepalived (>=2.0.5),
- lcdproc,
- lcdproc-extra-drivers,
- libatomic1,
- libauparse0,
- libcharon-extra-plugins (>=5.9),
- libcharon-extauth-plugins (>=5.9),
- libndp-tools,
- libnetfilter-conntrack3,
- libnfnetlink0,
- libqmi-utils,
- libstrongswan-extra-plugins (>=5.9),
- libstrongswan-standard-plugins (>=5.9),
- libvppinfra [amd64],
libvyosconfig0,
- linux-cpupower,
- lldpd,
- lm-sensors,
- lsscsi,
- minisign,
- modemmanager,
- mtr-tiny,
- ndisc6,
- ndppd,
- netavark,
- netplug,
- nfct,
- nftables (>= 0.9.3),
- nginx-light,
- chrony,
- nvme-cli,
- ocserv,
- opennhrp,
- openssh-server,
- openssl,
- openvpn,
- openvpn-auth-ldap,
- openvpn-auth-radius,
- openvpn-otp,
- owamp-client,
- owamp-server,
- pciutils,
- pdns-recursor,
- pmacct (>= 1.6.0),
- podman,
- pppoe,
- procps,
+ vyatta-bash,
+ vyatta-cfg,
+ vyos-http-api-tools,
+ vyos-utils,
+## End of Fundamentals
+## Python libraries used in multiple modules and scripts
python3,
python3-certbot-nginx,
python3-cryptography,
python3-hurry.filesize,
python3-inotify,
- python3-isc-dhcp-leases,
python3-jinja2,
python3-jmespath,
python3-netaddr,
@@ -144,57 +59,257 @@ Depends:
python3-pyudev,
python3-six,
python3-tabulate,
- python3-vici (>= 5.7.2),
python3-voluptuous,
- python3-vpp-api [amd64],
python3-xmltodict,
python3-zmq,
+## End of Python libraries
+## Basic System services and utilities
+ sudo,
+ systemd,
+ bsdmainutils,
+ openssl,
+ curl,
+ dbus,
+ file,
+ iproute2 (>= 6.0.0),
+ linux-cpupower,
+# ipaddrcheck is widely used in IP value validators
+ ipaddrcheck,
+ ethtool,
+ fdisk,
+ lm-sensors,
+ procps,
+ netplug,
+ sed,
+ ssl-cert,
+ tuned,
+ beep,
+ wide-dhcpv6-client,
+# Generic colorizer
+ grc,
+## End of System services and utilities
+## For the installer
+# Image signature verification tool
+ minisign,
+# Live filesystem tools
+ squashfs-tools,
+ fuse-overlayfs,
+## End installer
+ auditd,
+ iputils-arping,
+ isc-dhcp-client,
+# For "vpn pptp", "vpn l2tp", "vpn sstp", "service ipoe-server"
+ accel-ppp,
+# End "vpn pptp", "vpn l2tp", "vpn sstp", "service ipoe-server"
+ avahi-daemon,
+ conntrack,
+ conntrackd,
+## Conf mode features
+# For "interfaces wireless"
+ hostapd,
+ hsflowd,
+ iw,
+ wireless-regdb,
+ wpasupplicant (>= 0.6.7),
+# End "interfaces wireless"
+# For "interfaces wwan"
+ modemmanager,
+ usb-modeswitch,
+ libqmi-utils,
+# End "interfaces wwan"
+# For "interfaces openvpn"
+ openvpn,
+ openvpn-auth-ldap,
+ openvpn-auth-radius,
+ openvpn-otp,
+ libpam-google-authenticator,
+# End "interfaces openvpn"
+# For "interfaces wireguard"
+ wireguard-tools,
qrencode,
+# End "interfaces wireguard"
+# For "interfaces pppoe"
+ pppoe,
+# End "interfaces pppoe"
+# For "interfaces sstpc"
+ sstp-client,
+# End "interfaces sstpc"
+# For "protocols *"
+ frr (>= 7.5),
+ frr-pythontools,
+ frr-rpki-rtrlib,
+ frr-snmp,
+# End "protocols *"
+# For "protocols nhrp" (part of DMVPN)
+ opennhrp,
+# End "protocols nhrp"
+# For "protocols igmp-proxy"
+ igmpproxy,
+# End "protocols igmp-proxy"
+# For "service console-server"
+ conserver-client,
+ conserver-server,
+ console-data,
+ dropbear,
+# End "service console-server"
+# For "set service aws glb"
+ aws-gwlbtun,
+# For "service dns dynamic"
+ ddclient (>= 3.9.1),
+# End "service dns dynamic"
+# # For "service ids"
+ fastnetmon [amd64],
+# End "service ids"
+# For "service router-advert"
radvd,
+# End "service route-advert"
+# For "high-availability reverse-proxy"
+ haproxy,
+# End "high-availability reverse-proxy"
+# For "service dhcp-relay"
+ isc-dhcp-relay,
+# For "service dhcp-server"
+ isc-dhcp-server,
+ python3-isc-dhcp-leases,
+# End "service dhcp-server"
+# For "service lldp"
+ lldpd,
+# End "service lldp"
+# For "service https"
+ nginx-light,
+# End "service https"
+# For "service ssh"
+ openssh-server,
+ sshguard,
+# End "service ssh"
+# For "service salt-minion"
salt-minion,
- sed,
- smartmontools,
+# End "service salt-minion"
+# For "service snmp"
snmp,
snmpd,
- squashfs-tools,
+# End "service snmp"
+# For "service upnp"
+ miniupnpd-nftables,
+# End "service upnp"
+# For "service webproxy"
squid,
squidclient,
squidguard,
- sshguard,
- ssl-cert,
- sstp-client,
- strongswan (>= 5.9),
- strongswan-swanctl (>= 5.9),
- stunnel4,
- sudo,
- systemd,
+# End "service webproxy"
+# For "service monitoring telegraf"
telegraf (>= 1.20),
- tcpdump,
- tcptraceroute,
- telnet,
+# End "service monitoring telegraf"
+# For "service monitoring zabbix-agent"
+ zabbix-agent2,
+# End "service monitoring zabbix-agent"
+# For "service tftp-server"
tftpd-hpa,
- traceroute,
- tuned,
+# End "service tftp-server"
+# For "service dns forwarding"
+ pdns-recursor,
+# End "service dns forwarding"
+# For "service sla owamp"
+ owamp-client,
+ owamp-server,
+# End "service sla owamp"
+# For "service sla twamp"
twamp-client,
twamp-server,
+# End "service sla twamp"
+# For "service broadcast-relay"
udp-broadcast-relay,
- uidmap,
- usb-modeswitch,
+# End "service broadcast-relay"
+# For "high-availability vrrp"
+ keepalived (>=2.0.5),
+# End "high-availability-vrrp"
+# For "system task-scheduler"
+ cron,
+# End "system task-scheduler"
+# For "system lcd"
+ lcdproc,
+ lcdproc-extra-drivers,
+# End "system lcd"
+# For firewall
+ libndp-tools,
+ libnetfilter-conntrack3,
+ libnfnetlink0,
+ nfct,
+ nftables (>= 0.9.3),
+# For "vpn ipsec"
+ strongswan (>= 5.9),
+ strongswan-swanctl (>= 5.9),
+ charon-systemd,
+ libcharon-extra-plugins (>=5.9),
+ libcharon-extauth-plugins (>=5.9),
+ libstrongswan-extra-plugins (>=5.9),
+ libstrongswan-standard-plugins (>=5.9),
+ python3-vici (>= 5.7.2),
+# End "vpn ipsec"
+# For nat66
+ ndppd,
+# End nat66
+# For "system ntp"
+ chrony,
+# End "system ntp"
+# For "vpn openconnect"
+ ocserv,
+# End "vpn openconnect"
+# For "set system flow-accounting"
+ pmacct (>= 1.6.0),
+# End "set system flow-accounting"
+# For container
+ podman,
+ netavark,
+ aardvark-dns,
+# iptables is only used for containers now, not the the firewall CLI
+ iptables,
+# End container
+## End Configuration mode
+## Operational mode
+# Used for hypervisor model in "run show version"
+ hvinfo,
+# For "run traceroute"
+ traceroute,
+# For "run monitor traffic"
+ tcpdump,
+# End "run monitor traffic"
+# For "run show hardware storage smart"
+ smartmontools,
+# For "run show hardware scsi"
+ lsscsi,
+# For "run show hardware pci"
+ pciutils,
+# For "show hardware usb"
usbutils,
+# For "run show hardware storage nvme"
+ nvme-cli,
+# For "run monitor bandwidth-test"
+ iperf,
+ iperf3,
+# End "run monitor bandwidth-test"
+# For "run wake-on-lan"
+ etherwake,
+# For "run force ipv6-nd"
+ ndisc6,
+# For "run monitor bandwidth"
+ bmon,
+# End Operational mode
+## VPP
vpp [amd64],
vpp-plugin-core [amd64],
vpp-plugin-dpdk [amd64],
- vyatta-bash,
- vyatta-cfg,
- vyos-http-api-tools,
- vyos-utils,
- wide-dhcpv6-client,
- wireguard-tools,
- wireless-regdb,
- wpasupplicant (>= 0.6.7),
- zabbix-agent2,
- ndppd,
- miniupnpd-nftables
+ python3-vpp-api [amd64],
+ libvppinfra [amd64],
+## End VPP
+## Optional utilities
+ easy-rsa,
+ tcptraceroute,
+ mtr-tiny,
+ telnet,
+ stunnel4,
+ uidmap
+## End optional utilities
Description: VyOS configuration scripts and data
VyOS configuration scripts, interface definitions, and everything
diff --git a/interface-definitions/include/isis/level-1-2-leaf.xml.i b/interface-definitions/include/isis/level-1-2-leaf.xml.i
new file mode 100644
index 000000000..3703da1ed
--- /dev/null
+++ b/interface-definitions/include/isis/level-1-2-leaf.xml.i
@@ -0,0 +1,13 @@
+<!-- include start from isis/level-1-2-leaf.xml.i -->
+<leafNode name="level-1">
+ <properties>
+ <help>Match on IS-IS level-1 routes</help>
+ <valueless/>
+ </properties>
+</leafNode>
+<leafNode name="level-2">
+ <properties>
+ <help>Match on IS-IS level-2 routes</help>
+ <valueless/>
+ </properties>
+</leafNode> \ No newline at end of file
diff --git a/interface-definitions/include/isis/lfa-local.xml.i b/interface-definitions/include/isis/lfa-local.xml.i
new file mode 100644
index 000000000..c5bf6a3eb
--- /dev/null
+++ b/interface-definitions/include/isis/lfa-local.xml.i
@@ -0,0 +1,128 @@
+<!-- include start from isis/lfa-local.xml.i -->
+<node name="local">
+ <properties>
+ <help>Local loop free alternate options</help>
+ </properties>
+ <children>
+ <node name="load-sharing">
+ <properties>
+ <help>Load share prefixes across multiple backups</help>
+ </properties>
+ <children>
+ <node name="disable">
+ <properties>
+ <help>Disable load sharing</help>
+ </properties>
+ <children>
+ #include <include/isis/level-1-2-leaf.xml.i>
+ </children>
+ </node>
+ </children>
+ </node>
+ <node name="priority-limit">
+ <properties>
+ <help>Limit backup computation up to the prefix priority</help>
+ </properties>
+ <children>
+ <node name="medium">
+ <properties>
+ <help>Compute for critical, high, and medium priority prefixes</help>
+ </properties>
+ <children>
+ #include <include/isis/level-1-2-leaf.xml.i>
+ </children>
+ </node>
+ <node name="high">
+ <properties>
+ <help>Compute for critical, and high priority prefixes</help>
+ </properties>
+ <children>
+ #include <include/isis/level-1-2-leaf.xml.i>
+ </children>
+ </node>
+ <node name="critical">
+ <properties>
+ <help>Compute for critical priority prefixes only</help>
+ </properties>
+ <children>
+ #include <include/isis/level-1-2-leaf.xml.i>
+ </children>
+ </node>
+ </children>
+ </node>
+ <node name="tiebreaker">
+ <properties>
+ <help>Configure tiebreaker for multiple backups</help>
+ </properties>
+ <children>
+ <node name="downstream">
+ <properties>
+ <help>Prefer backup path via downstream node</help>
+ </properties>
+ <children>
+ <tagNode name="index">
+ <properties>
+ <help>Set preference order among tiebreakers</help>
+ <valueHelp>
+ <format>u32:1-255</format>
+ <description>The index integer value</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-255"/>
+ </constraint>
+ </properties>
+ <children>
+ #include <include/isis/level-1-2-leaf.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ <node name="lowest-backup-metric">
+ <properties>
+ <help>Prefer backup path with lowest total metric</help>
+ </properties>
+ <children>
+ <tagNode name="index">
+ <properties>
+ <help>Set preference order among tiebreakers</help>
+ <valueHelp>
+ <format>u32:1-255</format>
+ <description>The index integer value</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-255"/>
+ </constraint>
+ </properties>
+ <children>
+ #include <include/isis/level-1-2-leaf.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ <node name="node-protecting">
+ <properties>
+ <help>Prefer node protecting backup path</help>
+ </properties>
+ <children>
+ <tagNode name="index">
+ <properties>
+ <help>Set preference order among tiebreakers</help>
+ <valueHelp>
+ <format>u32:1-255</format>
+ <description>The index integer value</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-255"/>
+ </constraint>
+ </properties>
+ <children>
+ #include <include/isis/level-1-2-leaf.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+</node>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/isis/lfa-protocol.xml.i b/interface-definitions/include/isis/lfa-protocol.xml.i
new file mode 100644
index 000000000..cfb1a6dc1
--- /dev/null
+++ b/interface-definitions/include/isis/lfa-protocol.xml.i
@@ -0,0 +1,11 @@
+<!-- include start from isis/lfa-protocol.xml.i -->
+<node name="lfa">
+ <properties>
+ <help>Loop free alternate functionality</help>
+ </properties>
+ <children>
+ #include <include/isis/lfa-remote.xml.i>
+ #include <include/isis/lfa-local.xml.i>
+ </children>
+</node>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/isis/lfa-remote.xml.i b/interface-definitions/include/isis/lfa-remote.xml.i
new file mode 100644
index 000000000..8434e35bf
--- /dev/null
+++ b/interface-definitions/include/isis/lfa-remote.xml.i
@@ -0,0 +1,28 @@
+<!-- include start from isis/lfa-remote.xml.i -->
+<node name="remote">
+ <properties>
+ <help>Remote loop free alternate options</help>
+ </properties>
+ <children>
+ <tagNode name="prefix-list">
+ <properties>
+ <help>Filter PQ node router ID based on prefix list</help>
+ <completionHelp>
+ <path>policy prefix-list</path>
+ </completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Name of IPv4/IPv6 prefix-list</description>
+ </valueHelp>
+ <constraint>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
+ </constraint>
+ <constraintErrorMessage>Name of prefix-list can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage>
+ </properties>
+ <children>
+ #include <include/isis/level-1-2-leaf.xml.i>
+ </children>
+ </tagNode>
+ </children>
+</node>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/isis/protocol-common-config.xml.i b/interface-definitions/include/isis/protocol-common-config.xml.i
index 648f2b319..404f03cb5 100644
--- a/interface-definitions/include/isis/protocol-common-config.xml.i
+++ b/interface-definitions/include/isis/protocol-common-config.xml.i
@@ -165,6 +165,14 @@
</properties>
</leafNode>
#include <include/isis/ldp-sync-protocol.xml.i>
+<node name="fast-reroute">
+ <properties>
+ <help>IS-IS fast reroute configuration</help>
+ </properties>
+ <children>
+ #include <include/isis/lfa-protocol.xml.i>
+ </children>
+</node>
<leafNode name="net">
<properties>
<help>A Network Entity Title for this process (ISO only)</help>
diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in
index b78f92c85..30fcb8573 100644
--- a/interface-definitions/interfaces-pppoe.xml.in
+++ b/interface-definitions/interfaces-pppoe.xml.in
@@ -109,6 +109,20 @@
<leafNode name="mtu">
<defaultValue>1492</defaultValue>
</leafNode>
+ <leafNode name="mru">
+ <properties>
+ <help>Maximum Receive Unit (MRU)</help>
+ <valueHelp>
+ <format>u32:128-16384</format>
+ <description>Maximum Receive Unit in byte</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 128-16384"/>
+ </constraint>
+ <constraintErrorMessage>MRU must be between 128 and 16384</constraintErrorMessage>
+ </properties>
+ <defaultValue>1492</defaultValue>
+ </leafNode>
#include <include/interface/no-peer-dns.xml.i>
<leafNode name="remote-address">
<properties>
diff --git a/op-mode-definitions/include/isis-common.xml.i b/op-mode-definitions/include/isis-common.xml.i
index e94d868e8..493a56633 100644
--- a/op-mode-definitions/include/isis-common.xml.i
+++ b/op-mode-definitions/include/isis-common.xml.i
@@ -17,6 +17,33 @@
</properties>
<command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
</tagNode>
+<node name="fast-reroute">
+ <properties>
+ <help>Show IS-IS fast reroute/loop free alternate (lfa) information</help>
+ </properties>
+ <children>
+ <node name="summary">
+ <properties>
+ <help>Show summary of fast reroute/loop free alternate (lfa) information</help>
+ </properties>
+ <children>
+ <leafNode name="level-1">
+ <properties>
+ <help>Show level-1 specific fast reroute/loop free alternate (lfa) information</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </leafNode>
+ <leafNode name="level-2">
+ <properties>
+ <help>Show level-2 specific fast reroute/loop free alternate (lfa) information</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </leafNode>
+ </children>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </node>
+ </children>
+</node>
<leafNode name="hostname">
<properties>
<help>Show IS-IS dynamic hostname mapping</help>
diff --git a/op-mode-definitions/raid.xml.in b/op-mode-definitions/raid.xml.in
index 5d0c9ef3d..85fbf4566 100644
--- a/op-mode-definitions/raid.xml.in
+++ b/op-mode-definitions/raid.xml.in
@@ -37,7 +37,7 @@
<children>
<tagNode name="raid">
<properties>
- <help>Add a RAID set element</help>
+ <help>Delete a RAID set element</help>
<completionHelp>
<script>${vyos_completion_dir}/list_raidset.sh</script>
</completionHelp>
@@ -50,7 +50,7 @@
<children>
<tagNode name="member">
<properties>
- <help>Add a member to a RAID set</help>
+ <help>Delete a member from a RAID set</help>
</properties>
<command>sudo ${vyos_op_scripts_dir}/raid.py delete --raid-set-name $3 --by-id --member $6</command>
</tagNode>
@@ -58,7 +58,7 @@
</node>
<tagNode name="member">
<properties>
- <help>Add a member to a RAID set</help>
+ <help>Delete a member from a RAID set</help>
</properties>
<command>sudo ${vyos_op_scripts_dir}/raid.py delete --raid-set-name $3 --member $5</command>
</tagNode>
diff --git a/python/vyos/component_version.py b/python/vyos/component_version.py
index 84e0ae51a..9662ebfcf 100644
--- a/python/vyos/component_version.py
+++ b/python/vyos/component_version.py
@@ -90,31 +90,6 @@ def from_system():
"""
return component_version()
-def legacy_from_system():
- """
- Get system component version dict from legacy location.
- This is for a transitional sanity check; the directory will eventually
- be removed.
- """
- system_versions = {}
- legacy_dir = directories['current']
-
- # To be removed:
- if not os.path.isdir(legacy_dir):
- return system_versions
-
- try:
- version_info = os.listdir(legacy_dir)
- except OSError as err:
- sys.exit(repr(err))
-
- for info in version_info:
- if re.match(r'[\w,-]+@\d+', info):
- pair = info.split('@')
- system_versions[pair[0]] = int(pair[1])
-
- return system_versions
-
def format_string(ver: dict) -> str:
"""
Version dict to string.
diff --git a/python/vyos/config_mgmt.py b/python/vyos/config_mgmt.py
index dbf17ade4..654a8d698 100644
--- a/python/vyos/config_mgmt.py
+++ b/python/vyos/config_mgmt.py
@@ -25,7 +25,7 @@ from datetime import datetime
from textwrap import dedent
from pathlib import Path
from tabulate import tabulate
-from shutil import copy
+from shutil import copy, chown
from vyos.config import Config
from vyos.configtree import ConfigTree, ConfigTreeError, show_diff
@@ -37,6 +37,7 @@ from vyos.utils.process import is_systemd_service_active
from vyos.utils.process import rc_cmd
SAVE_CONFIG = '/usr/libexec/vyos/vyos-save-config.py'
+config_json = '/run/vyatta/config/config.json'
# created by vyatta-cfg-postinst
commit_post_hook_dir = '/etc/commit/post-hooks.d'
@@ -64,8 +65,11 @@ formatter = logging.Formatter('%(funcName)s: %(levelname)s:%(message)s')
ch.setFormatter(formatter)
logger.addHandler(ch)
-def save_config(target):
- cmd = f'{SAVE_CONFIG} {target}'
+def save_config(target, json_out=None):
+ if json_out is None:
+ cmd = f'{SAVE_CONFIG} {target}'
+ else:
+ cmd = f'{SAVE_CONFIG} {target} --write-json-file {json_out}'
rc, out = rc_cmd(cmd)
if rc != 0:
logger.critical(f'save config failed: {out}')
@@ -326,6 +330,12 @@ Proceed ?'''
"""
mask = os.umask(0o002)
os.makedirs(archive_dir, exist_ok=True)
+ json_dir = os.path.dirname(config_json)
+ try:
+ os.makedirs(json_dir, exist_ok=True)
+ chown(json_dir, group='vyattacfg')
+ except OSError as e:
+ logger.warning(f'cannot create {json_dir}: {e}')
self._add_logrotate_conf()
@@ -481,10 +491,21 @@ Proceed ?'''
ext = os.getpid()
cmp_saved = f'/tmp/config.boot.{ext}'
if save_to_tmp:
- save_config(cmp_saved)
+ save_config(cmp_saved, json_out=config_json)
else:
copy(config_file, cmp_saved)
+ # on boot, we need to manually create the config.json file; after
+ # boot, it is written by save_config, above
+ if not os.path.exists(config_json):
+ ct = self._get_saved_config_tree()
+ try:
+ with open(config_json, 'w') as f:
+ f.write(ct.to_json())
+ chown(config_json, group='vyattacfg')
+ except OSError as e:
+ logger.warning(f'cannot create {config_json}: {e}')
+
try:
if cmp(cmp_saved, archive_config_file, shallow=False):
os.unlink(cmp_saved)
diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py
index a5314790d..a229533bd 100644
--- a/python/vyos/defaults.py
+++ b/python/vyos/defaults.py
@@ -24,7 +24,6 @@ directories = {
'op_mode' : f'{base_dir}/op_mode',
'services' : f'{base_dir}/services',
'config' : '/opt/vyatta/etc/config',
- 'current' : '/opt/vyatta/etc/config-migrate/current',
'migrate' : '/opt/vyatta/etc/config-migrate/migrate',
'log' : '/var/log/vyatta',
'templates' : '/usr/share/vyos/templates/',
diff --git a/smoketest/scripts/cli/test_component_version.py b/smoketest/scripts/cli/test_component_version.py
deleted file mode 100755
index 7b1b12c53..000000000
--- a/smoketest/scripts/cli/test_component_version.py
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2022 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import unittest
-
-import vyos.component_version as component_version
-
-# After T3474, component versions should be updated in the files in
-# vyos-1x/interface-definitions/include/version/
-# This test verifies that the legacy version in curver_DATA does not exceed
-# that in the xml cache.
-class TestComponentVersion(unittest.TestCase):
- def setUp(self):
- self.legacy_d = component_version.legacy_from_system()
- self.xml_d = component_version.from_system()
- self.set_legacy_d = set(self.legacy_d)
- self.set_xml_d = set(self.xml_d)
-
- def test_component_version(self):
- bool_issubset = (self.set_legacy_d.issubset(self.set_xml_d))
- if not bool_issubset:
- missing = self.set_legacy_d.difference(self.set_xml_d)
- print(f'\n\ncomponents in legacy but not in XML: {missing}')
- print('new components must be listed in xml-component-version.xml.in')
- self.assertTrue(bool_issubset)
-
- bad_component_version = False
- for k, v in self.legacy_d.items():
- bool_inequality = (v <= self.xml_d[k])
- if not bool_inequality:
- print(f'\n\n{k} has not been updated in XML component versions:')
- print(f'legacy version {v}; XML version {self.xml_d[k]}')
- bad_component_version = True
- self.assertFalse(bad_component_version)
-
-if __name__ == '__main__':
- unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_interfaces_pppoe.py b/smoketest/scripts/cli/test_interfaces_pppoe.py
index 0ce5e2fe0..7b702759f 100755
--- a/smoketest/scripts/cli/test_interfaces_pppoe.py
+++ b/smoketest/scripts/cli/test_interfaces_pppoe.py
@@ -59,10 +59,12 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase):
user = f'VyOS-user-{interface}'
passwd = f'VyOS-passwd-{interface}'
mtu = '1400'
+ mru = '1300'
self.cli_set(base_path + [interface, 'authentication', 'username', user])
self.cli_set(base_path + [interface, 'authentication', 'password', passwd])
self.cli_set(base_path + [interface, 'mtu', mtu])
+ self.cli_set(base_path + [interface, 'mru', '9000'])
self.cli_set(base_path + [interface, 'no-peer-dns'])
# check validate() - a source-interface is required
@@ -70,6 +72,11 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
self.cli_set(base_path + [interface, 'source-interface', self._source_interface])
+ # check validate() - MRU needs to be less or equal then MTU
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_set(base_path + [interface, 'mru', mru])
+
# commit changes
self.cli_commit()
@@ -80,6 +87,8 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase):
tmp = get_config_value(interface, 'mtu')[1]
self.assertEqual(tmp, mtu)
+ tmp = get_config_value(interface, 'mru')[1]
+ self.assertEqual(tmp, mru)
tmp = get_config_value(interface, 'user')[1].replace('"', '')
self.assertEqual(tmp, user)
tmp = get_config_value(interface, 'password')[1].replace('"', '')
diff --git a/smoketest/scripts/cli/test_protocols_isis.py b/smoketest/scripts/cli/test_protocols_isis.py
index 747fb5e80..8b423dbea 100755
--- a/smoketest/scripts/cli/test_protocols_isis.py
+++ b/smoketest/scripts/cli/test_protocols_isis.py
@@ -324,5 +324,65 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' ipv6 router isis {domain}', tmp)
self.assertIn(f' no isis mpls ldp-sync', tmp)
+ def test_isis_09_lfa(self):
+ prefix_list = 'lfa-prefix-list-test-1'
+ prefix_list_address = '192.168.255.255/32'
+ interface = 'lo'
+
+ self.cli_set(base_path + ['net', net])
+ self.cli_set(base_path + ['interface', interface])
+ self.cli_set(['policy', 'prefix-list', prefix_list, 'rule', '1', 'action', 'permit'])
+ self.cli_set(['policy', 'prefix-list', prefix_list, 'rule', '1', 'prefix', prefix_list_address])
+
+ # Commit main ISIS changes
+ self.cli_commit()
+
+ # Add remote portion of LFA with prefix list with validation
+ for level in ['level-1', 'level-2']:
+ self.cli_set(base_path + ['fast-reroute', 'lfa', 'remote', 'prefix-list', prefix_list, level])
+ self.cli_commit()
+ tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd')
+ self.assertIn(f' net {net}', tmp)
+ self.assertIn(f' fast-reroute remote-lfa prefix-list {prefix_list} {level}', tmp)
+ self.cli_delete(base_path + ['fast-reroute'])
+ self.cli_commit()
+
+ # Add local portion of LFA load-sharing portion with validation
+ for level in ['level-1', 'level-2']:
+ self.cli_set(base_path + ['fast-reroute', 'lfa', 'local', 'load-sharing', 'disable', level])
+ self.cli_commit()
+ tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd')
+ self.assertIn(f' net {net}', tmp)
+ self.assertIn(f' fast-reroute load-sharing disable {level}', tmp)
+ self.cli_delete(base_path + ['fast-reroute'])
+ self.cli_commit()
+
+ # Add local portion of LFA priority-limit portion with validation
+ for priority in ['critical', 'high', 'medium']:
+ for level in ['level-1', 'level-2']:
+ self.cli_set(base_path + ['fast-reroute', 'lfa', 'local', 'priority-limit', priority, level])
+ self.cli_commit()
+ tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd')
+ self.assertIn(f' net {net}', tmp)
+ self.assertIn(f' fast-reroute priority-limit {priority} {level}', tmp)
+ self.cli_delete(base_path + ['fast-reroute'])
+ self.cli_commit()
+
+ # Add local portion of LFA tiebreaker portion with validation
+ index = '100'
+ for tiebreaker in ['downstream','lowest-backup-metric','node-protecting']:
+ for level in ['level-1', 'level-2']:
+ self.cli_set(base_path + ['fast-reroute', 'lfa', 'local', 'tiebreaker', tiebreaker, 'index', index, level])
+ self.cli_commit()
+ tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd')
+ self.assertIn(f' net {net}', tmp)
+ self.assertIn(f' fast-reroute lfa tiebreaker {tiebreaker} index {index} {level}', tmp)
+ self.cli_delete(base_path + ['fast-reroute'])
+ self.cli_commit()
+
+ # Clean up and remove prefix list
+ self.cli_delete(['policy', 'prefix-list', prefix_list])
+ self.cli_commit()
+
if __name__ == '__main__':
- unittest.main(verbosity=2)
+ unittest.main(verbosity=2) \ No newline at end of file
diff --git a/src/conf_mode/http-api.py b/src/conf_mode/http-api.py
index 793a90d88..d8fe3b736 100755
--- a/src/conf_mode/http-api.py
+++ b/src/conf_mode/http-api.py
@@ -27,6 +27,7 @@ from vyos.config import Config
from vyos.configdep import set_dependents, call_dependents
from vyos.template import render
from vyos.utils.process import call
+from vyos.utils.process import is_systemd_service_running
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -130,7 +131,10 @@ def apply(http_api):
service_name = 'vyos-http-api.service'
if http_api is not None:
- call(f'systemctl restart {service_name}')
+ if is_systemd_service_running(f'{service_name}'):
+ call(f'systemctl reload {service_name}')
+ else:
+ call(f'systemctl restart {service_name}')
else:
call(f'systemctl stop {service_name}')
diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py
index fca91253c..0a03a172c 100755
--- a/src/conf_mode/interfaces-pppoe.py
+++ b/src/conf_mode/interfaces-pppoe.py
@@ -77,6 +77,11 @@ def verify(pppoe):
if {'connect_on_demand', 'vrf'} <= set(pppoe):
raise ConfigError('On-demand dialing and VRF can not be used at the same time')
+ # both MTU and MRU have default values, thus we do not need to check
+ # if the key exists
+ if int(pppoe['mru']) > int(pppoe['mtu']):
+ raise ConfigError('PPPoE MRU needs to be lower then MTU!')
+
return None
def generate(pppoe):
diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py
index e00c58ee4..ce67ccff7 100755
--- a/src/conf_mode/protocols_isis.py
+++ b/src/conf_mode/protocols_isis.py
@@ -48,7 +48,8 @@ def get_config(config=None):
# eqivalent of the C foo ? 'a' : 'b' statement
base = vrf and ['vrf', 'name', vrf, 'protocols', 'isis'] or base_path
isis = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True)
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
# Assign the name of our VRF context. This MUST be done before the return
# statement below, else on deletion we will delete the default instance
@@ -219,6 +220,38 @@ def verify(isis):
if ("explicit_null" in prefix_config['index']) and ("no_php_flag" in prefix_config['index']):
raise ConfigError(f'Segment routing prefix {prefix} cannot have both explicit-null '\
f'and no-php-flag configured at the same time.')
+
+ # Check for LFA tiebreaker index duplication
+ if dict_search('fast_reroute.lfa.local.tiebreaker', isis):
+ comparison_dictionary = {}
+ for item, item_options in isis['fast_reroute']['lfa']['local']['tiebreaker'].items():
+ for index, index_options in item_options.items():
+ for index_value, index_value_options in index_options.items():
+ if index_value not in comparison_dictionary.keys():
+ comparison_dictionary[index_value] = [item]
+ else:
+ comparison_dictionary[index_value].append(item)
+ for index, index_length in comparison_dictionary.items():
+ if int(len(index_length)) > 1:
+ raise ConfigError(f'LFA index {index} cannot have more than one tiebreaker configured.')
+
+ # Check for LFA priority-limit configured multiple times per level
+ if dict_search('fast_reroute.lfa.local.priority_limit', isis):
+ comparison_dictionary = {}
+ for priority, priority_options in isis['fast_reroute']['lfa']['local']['priority_limit'].items():
+ for level, level_options in priority_options.items():
+ if level not in comparison_dictionary.keys():
+ comparison_dictionary[level] = [priority]
+ else:
+ comparison_dictionary[level].append(priority)
+ for level, level_length in comparison_dictionary.items():
+ if int(len(level_length)) > 1:
+ raise ConfigError(f'LFA priority-limit on {level.replace("_", "-")} cannot have more than one priority configured.')
+
+ # Check for LFA remote prefix list configured with more than one list
+ if dict_search('fast_reroute.lfa.remote.prefix_list', isis):
+ if int(len(isis['fast_reroute']['lfa']['remote']['prefix_list'].items())) > 1:
+ raise ConfigError(f'LFA remote prefix-list has more than one configured. Cannot have more than one configured.')
return None
@@ -265,4 +298,4 @@ if __name__ == '__main__':
apply(c)
except ConfigError as e:
print(e)
- exit(1)
+ exit(1) \ No newline at end of file
diff --git a/src/etc/sysctl.d/30-vyos-router.conf b/src/etc/sysctl.d/30-vyos-router.conf
index fcdc1b21d..1c9b8999f 100644
--- a/src/etc/sysctl.d/30-vyos-router.conf
+++ b/src/etc/sysctl.d/30-vyos-router.conf
@@ -21,7 +21,6 @@ net.ipv4.conf.all.arp_filter=0
# https://vyos.dev/T300
net.ipv4.conf.all.arp_ignore=0
-
net.ipv4.conf.all.arp_announce=2
# Enable packet forwarding for IPv4
@@ -103,6 +102,6 @@ net.ipv4.igmp_max_memberships = 512
net.core.rps_sock_flow_entries = 32768
# Congestion control
-net.core.default_qdisc=fq
+net.core.default_qdisc=fq_codel
net.ipv4.tcp_congestion_control=bbr
diff --git a/src/helpers/config_dependency.py b/src/helpers/config_dependency.py
index 50c72956e..817bcc65a 100755
--- a/src/helpers/config_dependency.py
+++ b/src/helpers/config_dependency.py
@@ -18,22 +18,75 @@
import os
import sys
+import json
from argparse import ArgumentParser
from argparse import ArgumentTypeError
-
-try:
- from vyos.configdep import check_dependency_graph
- from vyos.defaults import directories
-except ImportError:
- # allow running during addon package build
- _here = os.path.dirname(__file__)
- sys.path.append(os.path.join(_here, '../../python/vyos'))
- from configdep import check_dependency_graph
- from defaults import directories
+from graphlib import TopologicalSorter, CycleError
# addon packages will need to specify the dependency directory
-dependency_dir = os.path.join(directories['data'],
- 'config-mode-dependencies')
+data_dir = '/usr/share/vyos/'
+dependency_dir = os.path.join(data_dir, 'config-mode-dependencies')
+
+def dict_merge(source, destination):
+ from copy import deepcopy
+ tmp = deepcopy(destination)
+
+ for key, value in source.items():
+ if key not in tmp:
+ tmp[key] = value
+ elif isinstance(source[key], dict):
+ tmp[key] = dict_merge(source[key], tmp[key])
+
+ return tmp
+
+def read_dependency_dict(dependency_dir: str = dependency_dir) -> dict:
+ res = {}
+ for dep_file in os.listdir(dependency_dir):
+ if not dep_file.endswith('.json'):
+ continue
+ path = os.path.join(dependency_dir, dep_file)
+ with open(path) as f:
+ d = json.load(f)
+ if dep_file == 'vyos-1x.json':
+ res = dict_merge(res, d)
+ else:
+ res = dict_merge(d, res)
+
+ return res
+
+def graph_from_dependency_dict(d: dict) -> dict:
+ g = {}
+ for k in list(d):
+ g[k] = set()
+ # add the dependencies for every sub-case; should there be cases
+ # that are mutally exclusive in the future, the graphs will be
+ # distinguished
+ for el in list(d[k]):
+ g[k] |= set(d[k][el])
+
+ return g
+
+def is_acyclic(d: dict) -> bool:
+ g = graph_from_dependency_dict(d)
+ ts = TopologicalSorter(g)
+ try:
+ # get node iterator
+ order = ts.static_order()
+ # try iteration
+ _ = [*order]
+ except CycleError:
+ return False
+
+ return True
+
+def check_dependency_graph(dependency_dir: str = dependency_dir,
+ supplement: str = None) -> bool:
+ d = read_dependency_dict(dependency_dir=dependency_dir)
+ if supplement is not None:
+ with open(supplement) as f:
+ d = dict_merge(json.load(f), d)
+
+ return is_acyclic(d)
def path_exists(s):
if not os.path.exists(s):
@@ -50,8 +103,10 @@ def main():
args = vars(parser.parse_args())
if not check_dependency_graph(**args):
+ print("dependency error: cycle exists")
sys.exit(1)
+ print("dependency graph acyclic")
sys.exit(0)
if __name__ == '__main__':
diff --git a/src/helpers/vyos-save-config.py b/src/helpers/vyos-save-config.py
index 8af4a7916..518bd9864 100755
--- a/src/helpers/vyos-save-config.py
+++ b/src/helpers/vyos-save-config.py
@@ -19,6 +19,7 @@ import os
import re
import sys
from tempfile import NamedTemporaryFile
+from argparse import ArgumentParser
from vyos.config import Config
from vyos.remote import urlc
@@ -28,8 +29,15 @@ from vyos.defaults import directories
DEFAULT_CONFIG_PATH = os.path.join(directories['config'], 'config.boot')
remote_save = None
-if len(sys.argv) > 1:
- save_file = sys.argv[1]
+parser = ArgumentParser(description='Save configuration')
+parser.add_argument('file', type=str, nargs='?', help='Save configuration to file')
+parser.add_argument('--write-json-file', type=str, help='Save JSON of configuration to file')
+args = parser.parse_args()
+file = args.file
+json_file = args.write_json_file
+
+if file is not None:
+ save_file = file
else:
save_file = DEFAULT_CONFIG_PATH
@@ -51,6 +59,13 @@ with open(write_file, 'w') as f:
f.write("\n")
f.write(system_footer())
+if json_file is not None and ct is not None:
+ try:
+ with open(json_file, 'w') as f:
+ f.write(ct.to_json())
+ except OSError as e:
+ print(f'failed to write JSON file: {e}')
+
if remote_save is not None:
try:
remote_save.upload(write_file)
diff --git a/src/init/vyos-router b/src/init/vyos-router
index cf97d4c6e..35095afe4 100755
--- a/src/init/vyos-router
+++ b/src/init/vyos-router
@@ -234,10 +234,31 @@ cleanup_post_commit_hooks () {
# system defaults.
security_reset ()
{
+
+ # restore NSS cofniguration back to sane system defaults
+ # will be overwritten later when configuration is loaded
+ cat <<EOF >/etc/nsswitch.conf
+passwd: files
+group: files
+shadow: files
+gshadow: files
+
+# Per T2678, commenting out myhostname
+hosts: files dns #myhostname
+networks: files
+
+protocols: db files
+services: db files
+ethers: db files
+rpc: db files
+
+netgroup: nis
+EOF
+
# restore PAM back to virgin state (no radius/tacacs services)
- pam-auth-update --package --remove radius
+ pam-auth-update --disable radius-mandatory radius-optional
rm -f /etc/pam_radius_auth.conf
- pam-auth-update --package --remove tacplus
+ pam-auth-update --disable tacplus-mandatory tacplus-optional
rm -f /etc/tacplus_nss.conf /etc/tacplus_servers
# Certain configuration files are re-generated by the configuration
@@ -349,7 +370,6 @@ start ()
# As VyOS does not execute commands that are not present in the CLI we call
# the script by hand to have a single source for the login banner and MOTD
${vyos_conf_scripts_dir}/system_console.py || log_failure_msg "could not reset serial console"
- ${vyos_conf_scripts_dir}/system-login.py || log_failure_msg "could not reset system login"
${vyos_conf_scripts_dir}/system-login-banner.py || log_failure_msg "could not reset motd and issue files"
${vyos_conf_scripts_dir}/system-option.py || log_failure_msg "could not reset system option files"
${vyos_conf_scripts_dir}/system-ip.py || log_failure_msg "could not reset system IPv4 options"
diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server
index 66e80ced5..3a9efb73e 100755
--- a/src/services/vyos-http-api-server
+++ b/src/services/vyos-http-api-server
@@ -22,12 +22,14 @@ import grp
import copy
import json
import logging
+import signal
import traceback
import threading
+from time import sleep
from typing import List, Union, Callable, Dict
-import uvicorn
from fastapi import FastAPI, Depends, Request, Response, HTTPException
+from fastapi import BackgroundTasks
from fastapi.responses import HTMLResponse
from fastapi.exceptions import RequestValidationError
from fastapi.routing import APIRoute
@@ -36,10 +38,14 @@ from starlette.middleware.cors import CORSMiddleware
from starlette.datastructures import FormData
from starlette.formparsers import FormParser, MultiPartParser
from multipart.multipart import parse_options_header
+from uvicorn import Config as UvicornConfig
+from uvicorn import Server as UvicornServer
from ariadne.asgi import GraphQL
-import vyos.config
+from vyos.config import Config
+from vyos.configtree import ConfigTree
+from vyos.configdiff import get_config_diff
from vyos.configsession import ConfigSession, ConfigSessionError
import api.graphql.state
@@ -410,12 +416,24 @@ app.router.route_class = MultipartRoute
async def validation_exception_handler(request, exc):
return error(400, str(exc.errors()[0]))
+self_ref_msg = "Requested HTTP API server configuration change; commit will be called in the background"
+
+def call_commit(s: ConfigSession):
+ try:
+ s.commit()
+ except ConfigSessionError as e:
+ s.discard()
+ if app.state.vyos_debug:
+ logger.warning(f"ConfigSessionError:\n {traceback.format_exc()}")
+ else:
+ logger.warning(f"ConfigSessionError: {e}")
+
def _configure_op(data: Union[ConfigureModel, ConfigureListModel,
ConfigSectionModel, ConfigSectionListModel],
- request: Request):
+ request: Request, background_tasks: BackgroundTasks):
session = app.state.vyos_session
env = session.get_session_env()
- config = vyos.config.Config(session_env=env)
+ config = Config(session_env=env)
endpoint = request.url.path
@@ -470,7 +488,15 @@ def _configure_op(data: Union[ConfigureModel, ConfigureListModel,
else:
raise ConfigSessionError(f"'{op}' is not a valid operation")
# end for
- session.commit()
+ config = Config(session_env=env)
+ d = get_config_diff(config)
+
+ if d.is_node_changed(['service', 'https']):
+ background_tasks.add_task(call_commit, session)
+ msg = self_ref_msg
+ else:
+ session.commit()
+
logger.info(f"Configuration modified via HTTP API using key '{app.state.vyos_id}'")
except ConfigSessionError as e:
session.discard()
@@ -495,21 +521,21 @@ def _configure_op(data: Union[ConfigureModel, ConfigureListModel,
@app.post('/configure')
def configure_op(data: Union[ConfigureModel,
- ConfigureListModel],
- request: Request):
- return _configure_op(data, request)
+ ConfigureListModel],
+ request: Request, background_tasks: BackgroundTasks):
+ return _configure_op(data, request, background_tasks)
@app.post('/configure-section')
def configure_section_op(data: Union[ConfigSectionModel,
- ConfigSectionListModel],
- request: Request):
- return _configure_op(data, request)
+ ConfigSectionListModel],
+ request: Request, background_tasks: BackgroundTasks):
+ return _configure_op(data, request, background_tasks)
@app.post("/retrieve")
async def retrieve_op(data: RetrieveModel):
session = app.state.vyos_session
env = session.get_session_env()
- config = vyos.config.Config(session_env=env)
+ config = Config(session_env=env)
op = data.op
path = " ".join(data.path)
@@ -528,10 +554,10 @@ async def retrieve_op(data: RetrieveModel):
res = session.show_config(path=data.path)
if config_format == 'json':
- config_tree = vyos.configtree.ConfigTree(res)
+ config_tree = ConfigTree(res)
res = json.loads(config_tree.to_json())
elif config_format == 'json_ast':
- config_tree = vyos.configtree.ConfigTree(res)
+ config_tree = ConfigTree(res)
res = json.loads(config_tree.to_json_ast())
elif config_format == 'raw':
pass
@@ -548,10 +574,11 @@ async def retrieve_op(data: RetrieveModel):
return success(res)
@app.post('/config-file')
-def config_file_op(data: ConfigFileModel):
+def config_file_op(data: ConfigFileModel, background_tasks: BackgroundTasks):
session = app.state.vyos_session
-
+ env = session.get_session_env()
op = data.op
+ msg = None
try:
if op == 'save':
@@ -559,14 +586,23 @@ def config_file_op(data: ConfigFileModel):
path = data.file
else:
path = '/config/config.boot'
- res = session.save_config(path)
+ msg = session.save_config(path)
elif op == 'load':
if data.file:
path = data.file
else:
return error(400, "Missing required field \"file\"")
- res = session.migrate_and_load_config(path)
- res = session.commit()
+
+ session.migrate_and_load_config(path)
+
+ config = Config(session_env=env)
+ d = get_config_diff(config)
+
+ if d.is_node_changed(['service', 'https']):
+ background_tasks.add_task(call_commit, session)
+ msg = self_ref_msg
+ else:
+ session.commit()
else:
return error(400, f"'{op}' is not a valid operation")
except ConfigSessionError as e:
@@ -575,7 +611,7 @@ def config_file_op(data: ConfigFileModel):
logger.critical(traceback.format_exc())
return error(500, "An internal error occured. Check the logs for details.")
- return success(res)
+ return success(msg)
@app.post('/image')
def image_op(data: ImageModel):
@@ -607,7 +643,7 @@ def image_op(data: ImageModel):
return success(res)
@app.post('/container-image')
-def image_op(data: ContainerImageModel):
+def container_image_op(data: ContainerImageModel):
session = app.state.vyos_session
op = data.op
@@ -702,7 +738,7 @@ def reset_op(data: ResetModel):
# GraphQL integration
###
-def graphql_init(fast_api_app):
+def graphql_init(app: FastAPI = app):
from api.graphql.libs.token_auth import get_user_context
api.graphql.state.init()
api.graphql.state.settings['app'] = app
@@ -728,26 +764,45 @@ def graphql_init(fast_api_app):
debug=True,
introspection=in_spec))
###
+# Modify uvicorn to allow reloading server within the configsession
+###
-if __name__ == '__main__':
- # systemd's user and group options don't work, do it by hand here,
- # else no one else will be able to commit
- cfg_group = grp.getgrnam(CFG_GROUP)
- os.setgid(cfg_group.gr_gid)
+server = None
+shutdown = False
- # Need to set file permissions to 775 too so that every vyattacfg group member
- # has write access to the running config
- os.umask(0o002)
+class ApiServerConfig(UvicornConfig):
+ pass
+
+class ApiServer(UvicornServer):
+ def install_signal_handlers(self):
+ pass
+
+def reload_handler(signum, frame):
+ global server
+ logger.debug('Reload signal received...')
+ if server is not None:
+ server.handle_exit(signum, frame)
+ server = None
+ logger.info('Server stopping for reload...')
+ else:
+ logger.warning('Reload called for non-running server...')
+def shutdown_handler(signum, frame):
+ global shutdown
+ logger.debug('Shutdown signal received...')
+ server.handle_exit(signum, frame)
+ logger.info('Server shutdown...')
+ shutdown = True
+
+def initialization(session: ConfigSession, app: FastAPI = app):
+ global server
try:
server_config = load_server_config()
- except Exception as err:
- logger.critical(f"Failed to load the HTTP API server config: {err}")
+ except Exception as e:
+ logger.critical(f'Failed to load the HTTP API server config: {e}')
sys.exit(1)
- config_session = ConfigSession(os.getpid())
-
- app.state.vyos_session = config_session
+ app.state.vyos_session = session
app.state.vyos_keys = server_config['api_keys']
app.state.vyos_debug = server_config['debug']
@@ -770,14 +825,44 @@ if __name__ == '__main__':
if app.state.vyos_graphql:
graphql_init(app)
+ if not server_config['socket']:
+ config = ApiServerConfig(app,
+ host=server_config["listen_address"],
+ port=int(server_config["port"]),
+ proxy_headers=True)
+ else:
+ config = ApiServerConfig(app,
+ uds="/run/api.sock",
+ proxy_headers=True)
+ server = ApiServer(config)
+
+def run_server():
try:
- if not server_config['socket']:
- uvicorn.run(app, host=server_config["listen_address"],
- port=int(server_config["port"]),
- proxy_headers=True)
- else:
- uvicorn.run(app, uds="/run/api.sock",
- proxy_headers=True)
- except OSError as err:
- logger.critical(f"OSError {err}")
+ server.run()
+ except OSError as e:
+ logger.critical(e)
sys.exit(1)
+
+if __name__ == '__main__':
+ # systemd's user and group options don't work, do it by hand here,
+ # else no one else will be able to commit
+ cfg_group = grp.getgrnam(CFG_GROUP)
+ os.setgid(cfg_group.gr_gid)
+
+ # Need to set file permissions to 775 too so that every vyattacfg group member
+ # has write access to the running config
+ os.umask(0o002)
+
+ signal.signal(signal.SIGHUP, reload_handler)
+ signal.signal(signal.SIGTERM, shutdown_handler)
+
+ config_session = ConfigSession(os.getpid())
+
+ while True:
+ logger.debug('Enter main loop...')
+ if shutdown:
+ break
+ if server is None:
+ initialization(config_session)
+ server.run()
+ sleep(1)