==================
Please consider setting this package "on hold" by typing
- echo "quagga hold" | dpkg --set-selections
+ echo "frr hold" | dpkg --set-selections
and verifying this using
dpkg --get-selections | grep 'hold$'
Setting a package "on hold" means that it will not automatically be upgraded.
Instead apt-get only displays a warning saying that a new version would be
-available forcing you to explicitly type "apt-get install quagga" to upgrade it.
+available forcing you to explicitly type "apt-get install frr" to upgrade it.
-* What is quagga?
+* What is frr?
=================
-http://www.quagga.net/
-> Quagga is a routing software suite, providing implementations of OSPFv2,
+http://www.freerangerouting.net/
+> Frr is a routing software suite, providing implementations of OSPFv2,
> OSPFv3, RIP v1 and v2, RIPv3 and BGPv4 for Unix platforms, particularly
-> FreeBSD and Linux and also NetBSD, to mention a few. Quagga is a fork of GNU
+> FreeBSD and Linux and also NetBSD, to mention a few. Frr is a fork of GNU
> Zebra which was developed by Kunihiro Ishiguro. Development of GNU Zebra
> slowed dramatically to the point where eventually GNU Zebra was forked into
-> Quagga.
+> Frr.
-> The Quagga tree is an attempt to provide a zebra tree with at least the
+> The Frr tree is an attempt to provide a zebra tree with at least the
> bug-fixes, which have accumulated, applied, while tracking any significant
> changes made to the zebra.org tree. Ultimately, this tree hopes to revitalise
> development of this code base.
-I packaged zebra-pj which was then renamed to quagga to get people used to it
+I packaged zebra-pj which was then renamed to frr to get people used to it
and offer Debian users the choice which versions they like to use. I hope this
-brings quagga some feedback and helps it evolving to a good successor of the
+brings frr some feedback and helps it evolving to a good successor of the
orphaned zebra.
-- Christian Hammers <ch@debian.org>, Jul/Aug 2003
* Why has SNMP support been disabled?
=====================================
-Quagga used to link against the NetSNMP libraries to provide SNMP
+Frr used to link against the NetSNMP libraries to provide SNMP
support. Those libraries sadly link against the OpenSSL libraries
to provide crypto support for SNMPv3 among others.
OpenSSL now is not compatible with the GNU GENERAL PUBLIC LICENSE (GPL)
-licence that Quagga is distributed under. For more explanation read:
+licence that Frr is distributed under. For more explanation read:
http://www.gnome.org/~markmc/openssl-and-the-gpl.html
http://www.gnu.org/licenses/gpl-faq.html#GPLIncompatibleLibs
Updating the licence to explecitly allow linking against OpenSSL
would requite the affirmation of all people that ever contributed
-a significant part to Zebra or Quagga and thus are the collective
+a significant part to Zebra or Frr and thus are the collective
"copyright holder". That's too much work. Using a shrinked down
version of NetSNMP without OpenSSL or convincing the NetSNMP people
to change to GnuTLS are maybe good solutions but not reachable
*BUT*
It is allowed by the used licence mix that you fetch the sources and
-build Quagga yourself with SNMP with
- <remove the "grep ^smux" block at the end of debian/quagga.preinst>
+build Frr yourself with SNMP with
+ <remove the "grep ^smux" block at the end of debian/frr.preinst>
# export WANT_SNMP=1
- # apt-get -b source quagga
+ # apt-get -b source frr
Just distributing it in binary form, linked against OpenSSL, is forbidden.
* Daemon selection:
===================
-The Debian package uses /etc/quagga/daemons to tell the
+The Debian package uses /etc/frr/daemons to tell the
initscript which daemons to start. It's in the format
<daemon>=<yes|no|priority>
with no spaces (it's simply source-d into the initscript).
system's routing table if not set up properly.
Priorities were suggested by Dancer <dancer@zeor.simegen.com>.
-They're used to start the Quagga daemons in more than one step
+They're used to start the Frr daemons in more than one step
(for example start one or two at network initialization and the
-rest later). The number of Quagga daemons being small, priorities
+rest later). The number of Frr daemons being small, priorities
must be between 1 and 9, inclusive (or the initscript has to be
-changed). /etc/init.d/quagga then can be started as
+changed). /etc/init.d/frr then can be started as
-/etc/init.d/quagga <start|stop|restart|<priority>>
+/etc/init.d/frr <start|stop|restart|<priority>>
where priority 0 is the same as 'stop', priority 10 or 'start'
means 'start all'
=====================================================================
If this message occurs the receive buffer should be increased by adding the
-following to /etc/sysctl.conf and "--nl-bufsize" to /etc/quagga/debian.conf.
+following to /etc/sysctl.conf and "--nl-bufsize" to /etc/frr/debian.conf.
> net.core.rmem_default = 262144
> net.core.rmem_max = 262144
-See message #4525 from 2005-05-09 in the quagga-users mailing list.
+See message #4525 from 2005-05-09 in the frr-users mailing list.
* vtysh immediately exists:
===========================
-Check /etc/pam.d/quagga, it probably denies access to your user. The passwords
-configured in /etc/quagga/Quagga.conf are only for telnet access.
+Check /etc/pam.d/frr, it probably denies access to your user. The passwords
+configured in /etc/frr/Frr.conf are only for telnet access.
for i in debian/patches/*.diff; do echo -e "#\n# $i\n#"; patch --fuzz=3 --dry-run -p1 < $i; done
#
-# Filename transition from zebra to quagga
+# Filename transition from zebra to frr
#
Files that keep their names
/usr/bin/vtysh
Files that got an -pj suffix
- /etc/default/zebra -> /etc/quagga/debian.conf
- /etc/init.d/zebra -> /etc/init.d/quagga
- /etc/zebra/ -> /etc/quagga/
- /usr/share/doc/zebra/ -> /usr/share/doc/quagga/
- /var/log/zebra/ -> /var/log/quagga/
- /var/run/ -> /var/run/quagga/
+ /etc/default/zebra -> /etc/frr/debian.conf
+ /etc/init.d/zebra -> /etc/init.d/frr
+ /etc/zebra/ -> /etc/frr/
+ /usr/share/doc/zebra/ -> /usr/share/doc/frr/
+ /var/log/zebra/ -> /var/log/frr/
+ /var/run/ -> /var/run/frr/
Files that were moved
- /usr/sbin/* -> /usr/lib/quagga/
+ /usr/sbin/* -> /usr/lib/frr/
+frr (2.0) Released; urgency=medium
+
+ * Switchover to FRR
+
quagga (0.99.24+cl3u5) RELEASED; urgency=medium
* Closes: CM-12846 - Resolve Memory leaks in 'show ip bgp neighbor json'
-Source: quagga
+Source: frr
Section: net
Priority: optional
Maintainer: Christian Hammers <ch@debian.org>
Uploaders: Florian Weimer <fw@debian.org>
Build-Depends: debhelper (>= 7.0.50~), libncurses5-dev, libreadline-dev, texlive-latex-base, texlive-generic-recommended, libpam0g-dev | libpam-dev, libcap-dev, texinfo (>= 4.7), imagemagick, ghostscript, groff, po-debconf, autotools-dev, hardening-wrapper, libpcre3-dev, gawk, chrpath, libsnmp-dev, git, dh-autoreconf, libjson0, libjson0-dev, dh-systemd, libsystemd-dev, python-ipaddr
Standards-Version: 3.9.6
-Homepage: http://www.quagga.net/
+Homepage: http://www.frr.net/
XS-Testsuite: autopkgtest
-Package: quagga
+Package: frr
Architecture: any
Depends: ${shlibs:Depends}, logrotate (>= 3.2-11), iproute, ${misc:Depends}
Pre-Depends: adduser
Replaces: zebra, zebra-pj
Suggests: snmpd
Description: BGP/OSPF/RIP routing daemon
- GNU Quagga is free software which manages TCP/IP based routing protocols.
+ Frr is free software which manages TCP/IP based routing protocols.
It supports BGP4, BGP4+, OSPFv2, OSPFv3, IS-IS, RIPv1, RIPv2, and RIPng as
well as the IPv6 versions of these.
.
- As the precessor Zebra has been considered orphaned, the Quagga project
- has been formed by members of the zebra mailing list and the former
- zebra-pj project to continue developing.
- .
- Quagga uses threading if the kernel supports it, but can also run on
+ Frr uses threading if the kernel supports it, but can also run on
kernels that do not support threading. Each protocol has its own daemon.
.
It is more than a routed replacement, it can be used as a Route Server and
a Route Reflector.
-Package: quagga-dbg
+Package: frr-dbg
Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}, quagga (= ${binary:Version})
+Depends: ${shlibs:Depends}, ${misc:Depends}, frr (= ${binary:Version})
Priority: extra
Section: debug
Description: BGP/OSPF/RIP routing daemon (debug symbols)
This package provides debugging symbols for all binary packages built from
- quagga source package. It's highly recommended to have this package installed
- before reporting any Quagga crashes to either Quagga developers or Debian
+ frr source package. It's highly recommended to have this package installed
+ before reporting any Frr crashes to either Frr developers or Debian
package maintainers.
-Package: quagga-doc
+Package: frr-doc
Section: net
Architecture: all
Depends: ${misc:Depends}
-Suggests: quagga
-Description: documentation files for quagga
- This package includes info files for quagga, a free software which manages
+Suggests: frr
+Description: documentation files for frr
+ This package includes info files for frr, a free software which manages
TCP/IP based routing protocols. It supports BGP4, BGP4+, OSPFv2, OSPFv3,
IS-IS, RIPv1, RIPv2, and RIPng as well as the IPv6 versions of these.
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
-Upstream-Name: Quagga
-Upstream-Contact: maintainers@quagga.net, security@quagga.net
-Source: http://www.quagga.net/
+Upstream-Name: Frr
+Upstream-Contact: maintainers@freerangerouting.net, security@freerangerouting.net
+Source: http://www.freerangerouting.net/
Files: *
Copyright: 1996-2003 by the original Zebra authors:
--- /dev/null
+AUTHORS
+NEWS
+README
+REPORTING-BUGS
+doc/BGP-TypeCode
+doc/draft-zebra-00.txt
+doc/mpls/
+bgpd/BGP4-MIB.txt
--- /dev/null
+doc/frr.info*
--- /dev/null
+usr/share/info
--- /dev/null
+frr-doc: wrong-section-according-to-package-name frr-doc => doc
--- /dev/null
+# Create the /run/frr directory at boot or from systemd-tmpfiles on install
+d /run/frr 0755 frr frr
--- /dev/null
+#!/bin/bash -e
+
+. /usr/share/debconf/confmodule
+
--- /dev/null
+etc/logrotate.d/
+etc/frr/
+usr/share/doc/frr/
+usr/share/doc/frr/examples/
+usr/share/lintian/overrides/
+usr/share/snmp/mibs/
+var/log/frr/
--- /dev/null
+tools
+debian/README.Debian
--- /dev/null
+etc/frr/
+usr/bin/vtysh
+usr/include/frr/
+usr/lib/
+tools/frr-reload.py usr/lib/frr/
+tools/frr usr/lib/frr
+usr/share/doc/frr/
+usr/share/man/man1/vtysh.1
+usr/share/man/man1/frr.1
+usr/share/man/man8
+usr/share/man/man8/bgpd.8
+usr/share/man/man8/ospf6d.8
+usr/share/man/man8/ospfd.8
+usr/share/man/man8/ripd.8
+usr/share/man/man8/ripngd.8
+usr/share/man/man8/zebra.8
+usr/share/man/man8/isisd.8
+usr/share/man/man8/watchfrr.8
+usr/share/snmp/mibs/
+cumulus/etc/* etc/
+tools/*.service lib/systemd/system
+debian/frr.conf usr/lib/tmpfiles.d
--- /dev/null
+frr: non-dev-pkg-with-shlib-symlink usr/lib/libospfapiclient.so.0.0.0 usr/lib/libospfapiclient.so
+frr: non-dev-pkg-with-shlib-symlink usr/lib/libospf.so.0.0.0 usr/lib/libospf.so
+frr: non-dev-pkg-with-shlib-symlink usr/lib/libzebra.so.0.0.0 usr/lib/libzebra.so
+frr: package-name-doesnt-match-sonames libospf0 libospfapiclient0 libzebra0
--- /dev/null
+/var/log/frr/*.log {
+ size 500k
+ sharedscripts
+ missingok
+ compress
+ rotate 14
+ create 640 frr frrvty
+
+ postrotate
+ for i in zebra bgpd ripd ospfd ripngd ospf6d isisd pimd; do
+ if [ -e /var/run/frr/$i.pid ] ; then
+ kill -USR1 `cat /var/run/frr/$i.pid`
+ fi
+
+ done
+ endscript
+}
--- /dev/null
+doc/bgpd.8
+doc/ospf6d.8
+doc/ospfd.8
+doc/ripd.8
+doc/ripngd.8
+doc/vtysh.1
+doc/zebra.8
+doc/isisd.8
+doc/watchfrr.8
--- /dev/null
+# Any user may call vtysh but only those belonging to the group frrvty can
+# actually connect to the socket and use the program.
+auth sufficient pam_permit.so
--- /dev/null
+#!/bin/bash -e
+
+######################
+PASSWDFILE=/etc/passwd
+GROUPFILE=/etc/group
+
+frruid=`egrep "^frr:" $PASSWDFILE | awk -F ":" '{ print $3 }'`
+frrgid=`egrep "^frr:" $GROUPFILE | awk -F ":" '{ print $3 }'`
+frrvtygid=`egrep "^frrvty:" $GROUPFILE | awk -F ":" '{ print $3 }'`
+
+[ -n ${frruid} ] || (echo "No uid for frr in ${PASSWDFILE}" && /bin/false)
+[ -n ${frrgid} ] || (echo "No gid for frr in ${GROUPFILE}" && /bin/false)
+[ -n ${frrVTYgid} ] || (echo "No gid for frrvty in ${GROUPFILE}" && /bin/false)
+
+chown -R ${frruid}:${frrgid} /etc/frr
+touch /etc/frr/vtysh.conf
+chgrp ${frrvtygid} /etc/frr/vtysh*
+chmod 440 /etc/sudoers.d/frr_sudoers
+chmod 644 /etc/frr/*
+
+ENVIRONMENTFILE=/etc/environment
+if ! grep --quiet VTYSH_PAGER=/bin/cat ${ENVIRONMENTFILE}; then
+ echo "VTYSH_PAGER=/bin/cat" >> ${ENVIRONMENTFILE}
+fi
+##################################################
+
+if [ -n "$DEBIAN_SCRIPT_DEBUG" ]; then set -v -x; DEBIAN_SCRIPT_TRACE=1; fi
+${DEBIAN_SCRIPT_TRACE:+ echo "#42#DEBUG# RUNNING $0 $*"}
+
+# This is most likely due to the answer "no" to the "really stop the server"
+# question in the prerm script.
+if [ "$1" = "abort-upgrade" ]; then
+ exit 0
+fi
+
+. /usr/share/debconf/confmodule
+
+db_stop
+
+#DEBHELPER#
+
--- /dev/null
+#!/bin/bash -e
+
+if [ -n "$DEBIAN_SCRIPT_DEBUG" ]; then set -v -x; DEBIAN_SCRIPT_TRACE=1; fi
+${DEBIAN_SCRIPT_TRACE:+ echo "#42#DEBUG# RUNNING $0 $*"}
+# set -u not because of debhelper
+
+if [ "$1" = "purge" ]; then
+ rm -rf /etc/frr /var/run/frr /var/log/frr
+ userdel frr >/dev/null 2>&1 || true
+fi
+
+#DEBHELPER#
--- /dev/null
+#!/bin/bash
+
+if [ -n "$DEBIAN_SCRIPT_DEBUG" ]; then set -v -x; DEBIAN_SCRIPT_TRACE=1; fi
+${DEBIAN_SCRIPT_TRACE:+ echo "#42#DEBUG# RUNNING $0 $*"}
+set -e
+set -u
+
+# creating frrvty group if it isn't already there
+if ! getent group frrvty >/dev/null; then
+ addgroup --system frrvty >/dev/null
+fi
+
+# creating frr group if it isn't already there
+if ! getent group frr >/dev/null; then
+ addgroup --system frr >/dev/null
+fi
+
+# creating frr user if he isn't already there
+if ! getent passwd frr >/dev/null; then
+ adduser \
+ --system \
+ --ingroup frr \
+ --home /var/run/frr/ \
+ --gecos "Frr routing suite" \
+ --shell /bin/false \
+ frr >/dev/null
+fi
+
+# We may be installing over an older version of
+# frr and as such we need to intelligently
+# check to see if the frr user is in the frrvty
+# group.
+if ! /usr/bin/id frr | grep &>/dev/null 'frrvty'; then
+ usermod -a -G frrvty frr >/dev/null
+fi
+
+# Do not change permissions when upgrading as it would violate policy.
+if [ "$1" = "install" ]; then
+ # Logfiles are group readable in case users were put into the frr group.
+ d=/var/log/frr/
+ mkdir -p $d
+ chown -R frr:frr $d
+ chmod u=rwx,go=rx $d
+ find $d -type f -print0 | xargs -0 --no-run-if-empty chmod u=rw,g=r,o=
+
+ # Strict permissions for the sockets.
+ d=/var/run/frr/
+ mkdir -p $d
+ chown -R frr:frr $d
+ chmod u=rwx,go=rx $d
+ find $d -type f -print0 | xargs -0 --no-run-if-empty chmod u=rw,go=
+
+ # Config files. Vtysh does not have access to the individual daemons config file
+ d=/etc/frr/
+ mkdir -p $d
+ chown frr:frrvty $d
+ chmod ug=rwx,o=rx $d
+ find $d -type f -print0 | xargs -0 --no-run-if-empty chown frr:frr
+ find $d -type f -print0 | xargs -0 --no-run-if-empty chmod u=rw,g=r,o=
+
+ # Exceptions for vtysh.
+ f=$d/vtysh.conf
+ if [ -f $f ]; then
+ chown frr:frrvty $f
+ chmod u=rw,g=r,o= $f
+ fi
+
+ # Exceptions for vtysh.
+ f=$d/Frr.conf
+ if [ -f $d/Zebra.conf ]; then
+ mv $d/Zebra.conf $f
+ fi
+ if [ -f $f ]; then
+ chown frr:frrvty $f
+ chmod u=rw,g=r,o= $f
+ fi
+fi
+
+#DEBHELPER#
--- /dev/null
+#!/bin/bash -e
+
+. /usr/share/debconf/confmodule
+
+if [ -n "$DEBIAN_SCRIPT_DEBUG" ]; then set -v -x; DEBIAN_SCRIPT_TRACE=1; fi
+${DEBIAN_SCRIPT_TRACE:+ echo "#42#DEBUG# RUNNING $0 $*"}
+
+# prerm remove
+# old-prerm upgrade new-version
+# new-prerm failed-upgrade old-version
+# conflictor's-prerm remove in-favour package new-version
+# deconfigured's-prerm deconfigure in-favour package-being-installed version removing conflicting-package
+case $1 in
+ remove|upgrade)
+ ;;
+
+ failed-upgrade)
+ # If frr/really_stop was negated then this script exits with return
+ # code 1 and is called again with "failed-upgrade". Well, exit again.
+ exit 1
+ ;;
+
+esac
+
+#DEBHELPER#
+++ /dev/null
-AUTHORS
-NEWS
-README
-REPORTING-BUGS
-doc/BGP-TypeCode
-doc/draft-zebra-00.txt
-doc/mpls/
-bgpd/BGP4-MIB.txt
+++ /dev/null
-doc/quagga.info*
+++ /dev/null
-usr/share/info
+++ /dev/null
-quagga-doc: wrong-section-according-to-package-name quagga-doc => doc
+++ /dev/null
-# Create the /run/quagga directory at boot or from systemd-tmpfiles on install
-d /run/quagga 0755 quagga quagga
+++ /dev/null
-#!/bin/bash -e
-
-. /usr/share/debconf/confmodule
-
+++ /dev/null
-etc/logrotate.d/
-etc/quagga/
-usr/share/doc/quagga/
-usr/share/doc/quagga/examples/
-usr/share/lintian/overrides/
-usr/share/snmp/mibs/
-var/log/quagga/
+++ /dev/null
-tools
-debian/README.Debian
+++ /dev/null
-etc/quagga/
-usr/bin/vtysh
-usr/include/quagga/
-usr/lib/
-tools/quagga-reload.py usr/lib/quagga/
-tools/quagga usr/lib/quagga
-usr/share/doc/quagga/
-usr/share/man/man1/vtysh.1
-usr/share/man/man1/quagga.1
-usr/share/man/man8
-usr/share/man/man8/bgpd.8
-usr/share/man/man8/ospf6d.8
-usr/share/man/man8/ospfd.8
-usr/share/man/man8/ripd.8
-usr/share/man/man8/ripngd.8
-usr/share/man/man8/zebra.8
-usr/share/man/man8/isisd.8
-usr/share/man/man8/watchfrr.8
-usr/share/snmp/mibs/
-cumulus/etc/* etc/
-tools/*.service lib/systemd/system
-debian/quagga.conf usr/lib/tmpfiles.d
+++ /dev/null
-quagga: non-dev-pkg-with-shlib-symlink usr/lib/libospfapiclient.so.0.0.0 usr/lib/libospfapiclient.so
-quagga: non-dev-pkg-with-shlib-symlink usr/lib/libospf.so.0.0.0 usr/lib/libospf.so
-quagga: non-dev-pkg-with-shlib-symlink usr/lib/libzebra.so.0.0.0 usr/lib/libzebra.so
-quagga: package-name-doesnt-match-sonames libospf0 libospfapiclient0 libzebra0
+++ /dev/null
-/var/log/quagga/*.log {
- size 500k
- sharedscripts
- missingok
- compress
- rotate 14
- create 640 quagga quaggavty
-
- postrotate
- for i in zebra bgpd ripd ospfd ripngd ospf6d isisd pimd; do
- if [ -e /var/run/quagga/$i.pid ] ; then
- kill -USR1 `cat /var/run/quagga/$i.pid`
- fi
-
- done
- endscript
-}
+++ /dev/null
-doc/bgpd.8
-doc/ospf6d.8
-doc/ospfd.8
-doc/ripd.8
-doc/ripngd.8
-doc/vtysh.1
-doc/zebra.8
-doc/isisd.8
-doc/watchfrr.8
+++ /dev/null
-# Any user may call vtysh but only those belonging to the group quaggavty can
-# actually connect to the socket and use the program.
-auth sufficient pam_permit.so
+++ /dev/null
-#!/bin/bash -e
-
-######################
-PASSWDFILE=/etc/passwd
-GROUPFILE=/etc/group
-
-quaggauid=`egrep "^quagga:" $PASSWDFILE | awk -F ":" '{ print $3 }'`
-quaggagid=`egrep "^quagga:" $GROUPFILE | awk -F ":" '{ print $3 }'`
-quaggavtygid=`egrep "^quaggavty:" $GROUPFILE | awk -F ":" '{ print $3 }'`
-
-[ -n ${quaggauid} ] || (echo "No uid for quagga in ${PASSWDFILE}" && /bin/false)
-[ -n ${quaggagid} ] || (echo "No gid for quagga in ${GROUPFILE}" && /bin/false)
-[ -n ${quaggaVTYgid} ] || (echo "No gid for quaggavty in ${GROUPFILE}" && /bin/false)
-
-chown -R ${quaggauid}:${quaggagid} /etc/quagga
-touch /etc/quagga/vtysh.conf
-chgrp ${quaggavtygid} /etc/quagga/vtysh*
-chmod 440 /etc/sudoers.d/quagga_sudoers
-chmod 644 /etc/quagga/*
-
-ENVIRONMENTFILE=/etc/environment
-if ! grep --quiet VTYSH_PAGER=/bin/cat ${ENVIRONMENTFILE}; then
- echo "VTYSH_PAGER=/bin/cat" >> ${ENVIRONMENTFILE}
-fi
-##################################################
-
-if [ -n "$DEBIAN_SCRIPT_DEBUG" ]; then set -v -x; DEBIAN_SCRIPT_TRACE=1; fi
-${DEBIAN_SCRIPT_TRACE:+ echo "#42#DEBUG# RUNNING $0 $*"}
-
-# This is most likely due to the answer "no" to the "really stop the server"
-# question in the prerm script.
-if [ "$1" = "abort-upgrade" ]; then
- exit 0
-fi
-
-. /usr/share/debconf/confmodule
-
-db_stop
-
-#DEBHELPER#
-
+++ /dev/null
-#!/bin/bash -e
-
-if [ -n "$DEBIAN_SCRIPT_DEBUG" ]; then set -v -x; DEBIAN_SCRIPT_TRACE=1; fi
-${DEBIAN_SCRIPT_TRACE:+ echo "#42#DEBUG# RUNNING $0 $*"}
-# set -u not because of debhelper
-
-if [ "$1" = "purge" ]; then
- rm -rf /etc/quagga /var/run/quagga /var/log/quagga
- userdel quagga >/dev/null 2>&1 || true
-fi
-
-#DEBHELPER#
+++ /dev/null
-#!/bin/bash
-
-if [ -n "$DEBIAN_SCRIPT_DEBUG" ]; then set -v -x; DEBIAN_SCRIPT_TRACE=1; fi
-${DEBIAN_SCRIPT_TRACE:+ echo "#42#DEBUG# RUNNING $0 $*"}
-set -e
-set -u
-
-# creating quaggavty group if it isn't already there
-if ! getent group quaggavty >/dev/null; then
- addgroup --system quaggavty >/dev/null
-fi
-
-# creating quagga group if it isn't already there
-if ! getent group quagga >/dev/null; then
- addgroup --system quagga >/dev/null
-fi
-
-# creating quagga user if he isn't already there
-if ! getent passwd quagga >/dev/null; then
- adduser \
- --system \
- --ingroup quagga \
- --home /var/run/quagga/ \
- --gecos "Quagga routing suite" \
- --shell /bin/false \
- quagga >/dev/null
-fi
-
-# We may be installing over an older version of
-# quagga and as such we need to intelligently
-# check to see if the quagga user is in the quaggavty
-# group.
-if ! /usr/bin/id quagga | grep &>/dev/null 'quaggavty'; then
- usermod -a -G quaggavty quagga >/dev/null
-fi
-
-# Do not change permissions when upgrading as it would violate policy.
-if [ "$1" = "install" ]; then
- # Logfiles are group readable in case users were put into the quagga group.
- d=/var/log/quagga/
- mkdir -p $d
- chown -R quagga:quagga $d
- chmod u=rwx,go=rx $d
- find $d -type f -print0 | xargs -0 --no-run-if-empty chmod u=rw,g=r,o=
-
- # Strict permissions for the sockets.
- d=/var/run/quagga/
- mkdir -p $d
- chown -R quagga:quagga $d
- chmod u=rwx,go=rx $d
- find $d -type f -print0 | xargs -0 --no-run-if-empty chmod u=rw,go=
-
- # Config files. Vtysh does not have access to the individual daemons config file
- d=/etc/quagga/
- mkdir -p $d
- chown quagga:quaggavty $d
- chmod ug=rwx,o=rx $d
- find $d -type f -print0 | xargs -0 --no-run-if-empty chown quagga:quagga
- find $d -type f -print0 | xargs -0 --no-run-if-empty chmod u=rw,g=r,o=
-
- # Exceptions for vtysh.
- f=$d/vtysh.conf
- if [ -f $f ]; then
- chown quagga:quaggavty $f
- chmod u=rw,g=r,o= $f
- fi
-
- # Exceptions for vtysh.
- f=$d/Quagga.conf
- if [ -f $d/Zebra.conf ]; then
- mv $d/Zebra.conf $f
- fi
- if [ -f $f ]; then
- chown quagga:quaggavty $f
- chmod u=rw,g=r,o= $f
- fi
-fi
-
-#DEBHELPER#
+++ /dev/null
-#!/bin/bash -e
-
-. /usr/share/debconf/confmodule
-
-if [ -n "$DEBIAN_SCRIPT_DEBUG" ]; then set -v -x; DEBIAN_SCRIPT_TRACE=1; fi
-${DEBIAN_SCRIPT_TRACE:+ echo "#42#DEBUG# RUNNING $0 $*"}
-
-# prerm remove
-# old-prerm upgrade new-version
-# new-prerm failed-upgrade old-version
-# conflictor's-prerm remove in-favour package new-version
-# deconfigured's-prerm deconfigure in-favour package-being-installed version removing conflicting-package
-case $1 in
- remove|upgrade)
- ;;
-
- failed-upgrade)
- # If quagga/really_stop was negated then this script exits with return
- # code 1 and is called again with "failed-upgrade". Well, exit again.
- exit 1
- ;;
-
-esac
-
-#DEBHELPER#
endif
%:
- dh $@ --with=systemd,autoreconf --parallel --dbg-package=quagga-dbg --list-missing
+ dh $@ --with=systemd,autoreconf --parallel --dbg-package=frr-dbg --list-missing
override_dh_auto_configure:
- # Quagga needs /proc to check some BSD vs Linux specific stuff.
+ # Frr needs /proc to check some BSD vs Linux specific stuff.
# Else it fails with an obscure error message pointing out that
# IPCTL_FORWARDING is an undefined symbol which is not very helpful.
@if ! [ -d /proc/1 ]; then \
if ! [ -e config.status ]; then \
dh_auto_configure -- \
- --enable-exampledir=/usr/share/doc/quagga/examples/ \
- --localstatedir=/var/run/quagga \
- --sbindir=/usr/lib/quagga \
- --sysconfdir=/etc/quagga \
+ --enable-exampledir=/usr/share/doc/frr/examples/ \
+ --localstatedir=/var/run/frr \
+ --sbindir=/usr/lib/frr \
+ --sysconfdir=/etc/frr \
$(USE_SNMP) \
--enable-ospfapi=yes \
--enable-vtysh=yes \
--enable-isisd=yes \
--enable-multipath=256 \
- --enable-user=quagga \
- --enable-group=quagga \
- --enable-vty-group=quaggavty \
+ --enable-user=frr \
+ --enable-group=frr \
+ --enable-vty-group=frrvty \
--enable-configfile-mask=0640 \
--enable-logfile-mask=0640 \
--enable-werror \
--enable-systemd=yes \
--enable-poll=yes \
--enable-cumulus=yes \
- --enable-pimd=no \
+ --enable-pimd=yes \
--enable-dependency-tracking \
--enable-bgp-vnc=no; \
fi
# doc/ is a bit crazy
ifeq ($(GENERATE_PDF), 1)
- dh_auto_build -- -C doc quagga.pdf || true # pdfetex fails with exit code 1 but still produces a good looking .pdf
+ dh_auto_build -- -C doc frr.pdf || true # pdfetex fails with exit code 1 but still produces a good looking .pdf
endif
- rm -vf doc/quagga.info
- dh_auto_build -- -C doc quagga.info
- rm -vf doc/quagga.info.html*
+ rm -vf doc/frr.info
+ dh_auto_build -- -C doc frr.info
+ rm -vf doc/frr.info.html*
override_dh_auto_test:
rm -f debian/tmp/usr/share/info/dir*
# install config files
- mkdir -p debian/tmp/etc/quagga/
- perl -pi -e 's#^!log file #!log file /var/log/quagga/#' debian/tmp/usr/share/doc/quagga/examples/*sample*
+ mkdir -p debian/tmp/etc/frr/
+ perl -pi -e 's#^!log file #!log file /var/log/frr/#' debian/tmp/usr/share/doc/frr/examples/*sample*
- # installing the Quagga specific SNMP MIB
+ # installing the Frr specific SNMP MIB
install -D -m 644 ./zebra/GNOME-PRODUCT-ZEBRA-MIB debian/tmp/usr/share/snmp/mibs/GNOME-PRODUCT-ZEBRA-MIB
# cleaning .la files
sed -i "/dependency_libs/ s/'.*'/''/" debian/tmp/usr/lib/*.la
override_dh_systemd_start:
- dh_systemd_start quagga.service
+ dh_systemd_start frr.service
override_dh_systemd_enable:
- dh_systemd_enable quagga.service
+ dh_systemd_enable frr.service
Tests: daemons
-Depends: quagga
+Depends: frr
Restrictions: needs-root
#!/bin/bash
#---------------
-# Testing quagga
+# Testing frr
#---------------
set -e
# modify config file to enable all daemons and copy config files
-CONFIG_FILE=/etc/quagga/daemons
+CONFIG_FILE=/etc/frr/daemons
DAEMONS=("zebra" "bgpd" "ospfd" "ospf6d" "ripd" "ripngd" "isisd" "pimd")
for daemon in "${DAEMONS[@]}"
do
sed -i -e "s/${daemon}=no/${daemon}=yes/g" $CONFIG_FILE
- cp /usr/share/doc/quagga/examples/${daemon}.conf.sample /etc/quagga/${daemon}.conf
+ cp /usr/share/doc/frr/examples/${daemon}.conf.sample /etc/frr/${daemon}.conf
done
-# reload quagga
-/etc/init.d/quagga restart > /dev/null 2>&1
+# reload frr
+/etc/init.d/frr restart > /dev/null 2>&1
# check daemons
for daemon in "${DAEMONS[@]}"
# Site Directory Pattern Version Script
version=3
opts=uversionmangle=s/(\d)[_\.\-\+]?((RC|rc|pre|dev|beta|alpha|b|a)[\-\.]?\d*)$/$1~$2/ \
- http://download.savannah.gnu.org/releases/quagga/quagga-(\d.*)\.(?:tgz|tar\.(?:gz|bz2|xz))
+ http://download.savannah.gnu.org/releases/frr/quagga-(\d.*)\.(?:tgz|tar\.(?:gz|bz2|xz))
# Bart Martens <bartm@debian.org> Fri, 25 Jan 2013 06:38:53 +0000
--- /dev/null
+check process watchfrr with pidfile /var/run/frr/watchfrr.pid
+ start program = "/etc/init.d/frr start watchfrr" with timeout 120 seconds
+ stop program = "/etc/init.d/frr stop watchfrr"
+ if 3 restarts within 10 cycles then timeout
+++ /dev/null
-check process watchfrr with pidfile /var/run/quagga/watchfrr.pid
- start program = "/etc/init.d/quagga start watchfrr" with timeout 120 seconds
- stop program = "/etc/init.d/quagga stop watchfrr"
- if 3 restarts within 10 cycles then timeout
# install /etc sources
%if "%{initsystem}" == "systemd"
mkdir -p %{buildroot}%{_unitdir}
-install %{quagga_tools}/quagga.service \
- %{buildroot}%{_unitdir}/quagga.service
+install %{quagga_tools}/frr.service \
+ %{buildroot}%{_unitdir}/frr.service
%else
mkdir -p %{buildroot}/etc/rc.d/init.d
for daemon in %{all_daemons} ; do
%if "%{initsystem}" == "systemd"
for daemon in %all_daemons ; do
- %systemd_post quagga.service
+ %systemd_post frr.service
done
%else
for daemon in %all_daemons ; do
%{_sbindir}/bgpd
%{_sbindir}/ssd
%{_sbindir}/quagga
-%{_sbindir}/quagga-reload.py
-%{_sbindir}/quagga-reload.pyc
-%{_sbindir}/quagga-reload.pyo
+%{_sbindir}/frr-reload.py
+%{_sbindir}/frr-reload.pyc
+%{_sbindir}/frr-reload.pyo
%if %{with_watchfrr}
%{_sbindir}/watchfrr
%endif
%{_bindir}/*
%config /etc/quagga/[!v]*
%if "%{initsystem}" == "systemd"
- %config %{_unitdir}/quagga.service
+ %config %{_unitdir}/frr.service
%else
%config /etc/rc.d/init.d/zebra
%if %{with_watchfrr}
-sbin_SCRIPTS = quagga-reload.py quagga
+sbin_SCRIPTS = frr-reload.py frr
-EXTRA_DIST = quagga.service quagga-reload.py quagga
+EXTRA_DIST = frr.service frr-reload.py frr
--- /dev/null
+#!/bin/bash
+#
+### BEGIN INIT INFO
+# Provides: frr
+# Required-Start: $local_fs $network $remote_fs $syslog
+# Required-Stop: $local_fs $network $remote_fs $syslog
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: start and stop the Frr routing suite
+# Description: Frr is a routing suite for IP routing protocols like
+# BGP, OSPF, RIP and others. This script contols the main
+# daemon "frr" as well as the individual protocol daemons.
+### END INIT INFO
+#
+
+PATH=/bin:/usr/bin:/sbin:/usr/sbin
+D_PATH=/usr/lib/frr
+C_PATH=/etc/frr
+V_PATH=/var/run/frr
+
+# Local Daemon selection may be done by using /etc/frr/daemons.
+# See /usr/share/doc/frr/README.Debian.gz for further information.
+# Keep zebra first and do not list watchfrr!
+DAEMONS="zebra bgpd ripd ripngd ospfd ospf6d isisd babeld pimd"
+MAX_INSTANCES=5
+RELOAD_SCRIPT=/usr/lib/frr/frr-reload.py
+
+. /lib/lsb/init-functions
+
+if [ -f /usr/lib/frr/ssd ]; then
+ SSD=/usr/lib/frr/ssd
+else
+ SSD=`which start-stop-daemon`
+fi
+
+# Print the name of the pidfile.
+pidfile()
+{
+ echo "$V_PATH/$1.pid"
+}
+
+# Print the name of the vtysh.
+vtyfile()
+{
+ echo "$V_PATH/$1.vty"
+}
+
+# Check if daemon is started by using the pidfile.
+started()
+{
+ [ ! -e `pidfile $1` ] && return 3
+ if [ -n "$2" ] && [ "$2" == "log" ]; then
+ status_of_proc -p `pidfile $1` $1 $1 && return 0 || return $?
+ else
+ kill -0 `cat \`pidfile $1\`` 2> /dev/null || return 1
+ return 0
+ fi
+}
+
+# Loads the config via vtysh -b if configured to do so.
+vtysh_b ()
+{
+ # Rember, that all variables have been incremented by 1 in convert_daemon_prios()
+ if [ "$vtysh_enable" = 2 -a -f $C_PATH/Frr.conf ]; then
+ /usr/bin/vtysh -b -n
+ fi
+}
+
+# Check if the daemon is activated and if its executable and config files
+# are in place.
+# params: daemon name
+# returns: 0=ok, 1=error
+check_daemon()
+{
+ # If the integrated config file is used the others are not checked.
+ if [ -r "$C_PATH/Frr.conf" ]; then
+ return 0
+ fi
+
+ # vtysh_enable has no config file nor binary so skip check.
+ # (Not sure why vtysh_enable is in this list but does not hurt)
+ if [ $1 != "watchfrr" -a $1 != "vtysh_enable" ]; then
+ # check for daemon binary
+ if [ ! -x "$D_PATH/$1" ]; then return 1; fi
+
+ # check for config file
+ if [ -n "$2" ]; then
+ if [ ! -r "$C_PATH/$1-$2.conf" ]; then
+ touch "$C_PATH/$1-$2.conf"
+ chown frr:frr "$C_PATH/$1-$2.conf"
+ fi
+ elif [ ! -r "$C_PATH/$1.conf" ]; then
+ touch "$C_PATH/$1.conf"
+ chown frr:frr "$C_PATH/$1.conf"
+ fi
+ fi
+ return 0
+}
+
+# Starts the server if it's not alrady running according to the pid file.
+# The Frr daemons creates the pidfile when starting.
+start()
+{
+ ulimit -n $MAX_FDS
+ if [ "$1" = "watchfrr" ]; then
+
+ # We may need to restart watchfrr if new daemons are added and/or
+ # removed
+ if started "$1" ; then
+ stop watchfrr
+ else
+ # Echo only once. watchfrr is printed in the stop above
+ echo -n " $1"
+ fi
+
+ if [ -e /var/run/frr/watchfrr.started ] ; then
+ rm /var/run/frr/watchfrr.started
+ fi
+ ${SSD} \
+ --start \
+ --pidfile=`pidfile $1` \
+ --exec "$D_PATH/$1" \
+ -- \
+ "${watchfrr_options[@]}"
+ for i in `seq 1 10`;
+ do
+ if [ -e /var/run/frr/watchfrr.started ] ; then
+ break
+ else
+ sleep 1
+ fi
+ done
+ elif [ -n "$2" ]; then
+ echo -n " $1-$2"
+ if ! check_daemon $1 $2 ; then
+ echo -n " (binary does not exist)"
+ return;
+ fi
+
+ ${SSD} \
+ --start \
+ --pidfile=`pidfile $1-$2` \
+ --exec "$D_PATH/$1" \
+ -- \
+ `eval echo "$""$1""_options"` -n "$2"
+ else
+ echo -n " $1"
+ if ! check_daemon $1; then
+ echo -n " (binary does not exist)"
+ return;
+ fi
+
+ ${SSD} \
+ --start \
+ --pidfile=`pidfile $1` \
+ --exec "$D_PATH/$1" \
+ -- \
+ `eval echo "$""$1""_options"`
+ fi
+}
+
+# Stop the daemon given in the parameter, printing its name to the terminal.
+stop()
+{
+ local inst
+
+ if [ -n "$2" ]; then
+ inst="$1-$2"
+ else
+ inst="$1"
+ fi
+
+ if ! started "$inst" ; then
+ echo -n " ($inst)"
+ return 0
+ else
+ PIDFILE=`pidfile $inst`
+ PID=`cat $PIDFILE 2>/dev/null`
+ ${SSD} --stop --quiet --retry=TERM/30/KILL/5 --oknodo --pidfile "$PIDFILE" --exec "$D_PATH/$1"
+ #
+ # Now we have to wait until $DAEMON has _really_ stopped.
+ #
+ if test -n "$PID" && kill -0 $PID 2>/dev/null; then
+ echo -n " (waiting) ."
+ cnt=0
+ while kill -0 $PID 2>/dev/null; do
+ cnt=`expr $cnt + 1`
+ if [ $cnt -gt 60 ]; then
+ # Waited 120 secs now, fail.
+ echo -n "Failed.. "
+ break
+ fi
+ sleep 2
+ echo -n "."
+ done
+ fi
+ echo -n " $inst"
+ rm -f `pidfile $inst`
+ rm -f `vtyfile $inst`
+ fi
+}
+
+# Converts values from /etc/frr/daemons to all-numeric values.
+convert_daemon_prios()
+{
+ for name in $DAEMONS zebra vtysh_enable watchfrr_enable; do
+ # First, assign the value set by the user to $value
+ eval value=\${${name}:0:3}
+
+ # Daemon not activated or entry missing?
+ if [ "$value" = "no" -o "$value" = "" ]; then value=0; fi
+
+ # These strings parsed for backwards compatibility.
+ if [ "$value" = "yes" -o "$value" = "true" ]; then
+ value=1;
+ fi
+
+ # Zebra is threatened special. It must be between 0=off and the first
+ # user assigned value "1" so we increase all other enabled daemons' values.
+ if [ "$name" != "zebra" -a "$value" -gt 0 ]; then value=`expr "$value" + 1`; fi
+
+ # If e.g. name is zebra then we set "zebra=yes".
+ eval $name=$value
+ done
+}
+
+# Starts watchfrr for all wanted daemons.
+start_watchfrr()
+{
+ local daemon_name
+ local daemon_prio
+ local found_one
+ local daemon_inst
+
+ # Start the monitor daemon only if desired.
+ if [ 0 -eq "$watchfrr_enable" ]; then
+ return
+ fi
+
+ # Check variable type
+ if ! declare -p watchfrr_options | grep -q '^declare \-a'; then
+ echo
+ echo "ERROR: The variable watchfrr_options from /etc/frr/debian.cnf must be a BASH array!"
+ echo "ERROR: Please convert config file and restart!"
+ exit 1
+ fi
+
+ # Which daemons have been started?
+ found_one=0
+ for daemon_name in $DAEMONS; do
+ eval daemon_prio=\$$daemon_name
+ if [ "$daemon_prio" -gt 0 ]; then
+ eval "daemon_inst=\${${daemon_name}_instances//,/ }"
+ if [ -n "$daemon_inst" ]; then
+ for inst in ${daemon_inst}; do
+ eval "inst_disable=\${${daemon_name}_${inst}}"
+ if [ -z ${inst_disable} ] || [ ${inst_disable} != 0 ]; then
+ if check_daemon $daemon_name $inst; then
+ watchfrr_options+=("${daemon_name}-${inst}")
+ fi
+ fi
+ done
+ else
+ if check_daemon $daemon_name; then
+ watchfrr_options+=($daemon_name)
+ fi
+ fi
+ found_one=1
+ fi
+ done
+
+ # Start if at least one daemon is activated.
+ if [ $found_one -eq 1 ]; then
+ echo -n "Starting Frr monitor daemon:"
+ start watchfrr
+ echo "."
+ fi
+}
+
+# Stopps watchfrr.
+stop_watchfrr()
+{
+ echo -n "Stopping Frr monitor daemon:"
+ stop watchfrr
+ echo "."
+}
+
+# Stops all daemons that have a lower level of priority than the given.
+# (technically if daemon_prio >= wanted_prio)
+stop_prio()
+{
+ local wanted_prio
+ local daemon_prio
+ local daemon_list
+ local daemon_inst
+ local inst
+
+ if [ -n "$2" ] && [[ "$2" =~ (.*)-(.*) ]]; then
+ daemon=${BASH_REMATCH[1]}
+ inst=${BASH_REMATCH[2]}
+ else
+ daemon="$2"
+ fi
+
+ wanted_prio=$1
+ daemon_list=${daemon:-$DAEMONS}
+
+ echo -n "Stopping Frr daemons (prio:$wanted_prio):"
+
+ for prio_i in `seq 10 -1 $wanted_prio`; do
+ for daemon_name in $daemon_list; do
+ eval daemon_prio=\${${daemon_name}:0:3}
+ daemon_inst=""
+ if [ $daemon_prio -eq $prio_i ]; then
+ eval "daemon_inst=\${${daemon_name}_instances//,/ }"
+ if [ -n "$daemon_inst" ]; then
+ for i in ${daemon_inst}; do
+ if [ -n "$inst" ] && [ "$i" == "$inst" ]; then
+ stop "$daemon_name" "$inst"
+ elif [ x"$inst" == x ]; then
+ stop "$daemon_name" "$i"
+ fi
+ done
+ else
+ stop "$daemon_name"
+ fi
+ fi
+ done
+ done
+
+ echo "."
+ if [ -z "$inst" ]; then
+ # Now stop other daemons that're prowling, coz the daemons file changed
+ echo -n "Stopping other frr daemons"
+ if [ -n "$daemon" ]; then
+ eval "file_list_suffix="$V_PATH"/"$daemon*""
+ else
+ eval "file_list_suffix="$V_PATH/*""
+ fi
+ for pidfile in $file_list_suffix.pid; do
+ PID=`cat $pidfile 2>/dev/null`
+ ${SSD} --stop --quiet --oknodo --pidfile "$pidfile"
+ echo -n "."
+ rm -rf "$pidfile"
+ done
+ echo "."
+
+ echo -n "Removing remaining .vty files"
+ for vtyfile in $file_list_suffix.vty; do
+ rm -rf "$vtyfile"
+ done
+ echo "."
+ fi
+}
+
+# Starts all daemons that have a higher level of priority than the given.
+# (technically if daemon_prio <= wanted_prio)
+start_prio()
+{
+ local wanted_prio
+ local daemon_prio
+ local daemon_list
+ local daemon_name
+ local daemon_inst
+ local inst
+
+ if [ -n "$2" ] && [[ "$2" =~ (.*)-(.*) ]]; then
+ daemon=${BASH_REMATCH[1]}
+ inst=${BASH_REMATCH[2]}
+ else
+ daemon="$2"
+ fi
+
+ wanted_prio=$1
+ daemon_list=${daemon:-$DAEMONS}
+
+ echo -n "Starting Frr daemons (prio:$wanted_prio):"
+
+ for prio_i in `seq 1 $wanted_prio`; do
+ for daemon_name in $daemon_list; do
+ eval daemon_prio=\$${daemon_name}
+ daemon_inst=""
+ if [ $daemon_prio -eq $prio_i ]; then
+ eval "daemon_inst=\${${daemon_name}_instances//,/ }"
+ if [ -n "$daemon_inst" ]; then
+ if [ `echo "$daemon_inst" | wc -w` -gt ${MAX_INSTANCES} ]; then
+ echo "Max instances supported is ${MAX_INSTANCES}. Aborting"
+ exit 1
+ fi
+ # Check if we're starting again by switching from single instance
+ # to MI version
+ if started "$daemon_name"; then
+ PIDFILE=`pidfile $daemon_name`
+ ${SSD} \
+ --stop --quiet --oknodo \
+ --pidfile "$PIDFILE" \
+ --exec "$D_PATH/$daemon_name"
+
+ rm -f `pidfile $1`
+ rm -f `vtyfile $1`
+ fi
+
+ for i in ${daemon_inst}; do
+ if [ -n "$inst" ] && [ "$i" == "$inst" ]; then
+ start "$daemon_name" "$inst"
+ elif [ x"$inst" == x ]; then
+ start "$daemon_name" "$i"
+ fi
+ done
+ else
+ # Check if we're starting again by switching from
+ # single instance to MI version
+ eval "file_list_suffix="$V_PATH"/"$daemon_name-*""
+ for pidfile in $file_list_suffix.pid; do
+ ${SSD} --stop --quiet --oknodo --pidfile "$pidfile"
+ echo -n "."
+ rm -rf "$pidfile"
+ done
+ for vtyfile in $file_list_suffix.vty; do
+ rm -rf "$vtyfile"
+ done
+
+ start "$daemon_name"
+ fi
+ fi
+ done
+ done
+ echo "."
+}
+
+check_status()
+{
+ local daemon_name
+ local daemon_prio
+ local daemon_inst
+ local failed_status=0
+
+ if [ -n "$1" ] && [[ "$1" =~ (.*)-(.*) ]]; then
+ daemon=${BASH_REMATCH[1]}
+ inst=${BASH_REMATCH[2]}
+ else
+ daemon="$1"
+ fi
+
+ daemon_list=${daemon:-$DAEMONS}
+
+ # Which daemons have been started?
+ for daemon_name in $daemon_list; do
+ eval daemon_prio=\$$daemon_name
+ if [ "$daemon_prio" -gt 0 ]; then
+ eval "daemon_inst=\${${daemon_name}_instances//,/ }"
+ if [ -n "$daemon_inst" ]; then
+ for i in ${daemon_inst}; do
+ if [ -n "$inst" -a "$inst" = "$i" ]; then
+ started "$1" "log" || failed_status=$?
+ elif [ -z "$inst" ]; then
+ started "$daemon_name-$i" "log" || failed_status=$?
+ fi
+ done
+ else
+ started "$daemon_name" "log" || failed_status=$?
+ fi
+ fi
+ done
+
+ # All daemons that need to have been started are up and running
+ return $failed_status
+}
+
+#########################################################
+# Main program #
+#########################################################
+
+# Config broken but script must exit silently.
+[ ! -r "$C_PATH/daemons" ] && exit 0
+
+# Load configuration
+. "$C_PATH/daemons"
+. "$C_PATH/debian.conf"
+
+# Read configuration variable file if it is present
+[ -r /etc/default/frr ] && . /etc/default/frr
+
+MAX_INSTANCES=${MAX_INSTANCES:=5}
+
+# Set priority of un-startable daemons to 'no' and substitute 'yes' to '0'
+convert_daemon_prios
+
+if [ ! -d $V_PATH ]; then
+ echo "Creating $V_PATH"
+ mkdir -p $V_PATH
+ chown frr:frr $V_PATH
+ chmod 755 /$V_PATH
+fi
+
+if [ -n "$3" ] && [ "$3" != "all" ]; then
+ dmn="$2"-"$3"
+elif [ -n "$2" ] && [ "$2" != "all" ]; then
+ dmn="$2"
+fi
+
+case "$1" in
+ start)
+ # Try to load this necessary (at least for 2.6) module.
+ if [ -d /lib/modules/`uname -r` ] ; then
+ echo "Loading capability module if not yet done."
+ set +e; LC_ALL=C modprobe -a capability 2>&1 | egrep -v "(not found|Can't locate)"; set -e
+ fi
+
+ # Start all daemons
+ cd $C_PATH/
+ if [ "$2" != "watchfrr" ]; then
+ start_prio 10 $dmn
+ fi
+ start_watchfrr
+ vtysh_b
+ ;;
+
+ 1|2|3|4|5|6|7|8|9|10)
+ # Stop/start daemons for the appropriate priority level
+ stop_prio $1
+ start_prio $1
+ vtysh_b
+ ;;
+
+ stop|0)
+ # Stop all daemons at level '0' or 'stop'
+ stop_watchfrr
+ if [ "$dmn" != "watchfrr" ]; then
+ [ -n "${dmn}" ] && eval "${dmn/-/_}=0"
+ stop_prio 0 $dmn
+ fi
+
+ if [ -z "$dmn" -o "$dmn" = "zebra" ]; then
+ echo "Removing all routes made by zebra."
+ ip route flush proto zebra
+ else
+ [ -n "$dmn" ] && eval "${dmn/-/_}=0"
+ start_watchfrr
+ fi
+ ;;
+
+ reload)
+ # Just apply the commands that have changed, no restart necessary
+ [ ! -x "$RELOAD_SCRIPT" ] && echo "frr-reload script not available" && exit 0
+ NEW_CONFIG_FILE="${2:-$C_PATH/Frr.conf}"
+ [ ! -r $NEW_CONFIG_FILE ] && echo "Unable to read new configuration file $NEW_CONFIG_FILE" && exit 1
+ echo "Applying only incremental changes to running configuration from Frr.conf"
+ "$RELOAD_SCRIPT" --reload /etc/frr/Frr.conf
+ exit $?
+ ;;
+
+ status)
+ check_status $dmn
+ exit $?
+ ;;
+
+ restart|force-reload)
+ $0 stop $dmn
+ sleep 1
+ $0 start $dmn
+ ;;
+
+ *)
+ echo "Usage: /etc/init.d/frr {start|stop|status|reload|restart|force-reload|<priority>} [daemon]"
+ echo " E.g. '/etc/init.d/frr 5' would start all daemons with a prio 1-5."
+ echo " reload applies only modifications from the running config to all daemons."
+ echo " reload neither restarts starts any daemon nor starts any new ones."
+ echo " Read /usr/share/doc/frr/README.Debian for details."
+ exit 1
+ ;;
+esac
+
+echo "Exiting from the script"
+exit 0
--- /dev/null
+#!/usr/bin/python
+# Frr Reloader
+# Copyright (C) 2014 Cumulus Networks, Inc.
+#
+# This file is part of Frr.
+#
+# Frr is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2, or (at your option) any
+# later version.
+#
+# Frr 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 Frr; see the file COPYING. If not, write to the Free
+# Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+# 02111-1307, USA.
+#
+"""
+This program
+- reads a frr configuration text file
+- reads frr's current running configuration via "vtysh -c 'show running'"
+- compares the two configs and determines what commands to execute to
+ synchronize frr's running configuration with the configuation in the
+ text file
+"""
+
+import argparse
+import copy
+import logging
+import os
+import random
+import re
+import string
+import subprocess
+import sys
+from collections import OrderedDict
+from ipaddr import IPv6Address
+from pprint import pformat
+
+
+log = logging.getLogger(__name__)
+
+
+class VtyshMarkException(Exception):
+ pass
+
+
+class Context(object):
+
+ """
+ A Context object represents a section of frr configuration such as:
+!
+interface swp3
+ description swp3 -> r8's swp1
+ ipv6 nd suppress-ra
+ link-detect
+!
+
+or a single line context object such as this:
+
+ip forwarding
+
+ """
+
+ def __init__(self, keys, lines):
+ self.keys = keys
+ self.lines = lines
+
+ # Keep a dictionary of the lines, this is to make it easy to tell if a
+ # line exists in this Context
+ self.dlines = OrderedDict()
+
+ for ligne in lines:
+ self.dlines[ligne] = True
+
+ def add_lines(self, lines):
+ """
+ Add lines to specified context
+ """
+
+ self.lines.extend(lines)
+
+ for ligne in lines:
+ self.dlines[ligne] = True
+
+
+class Config(object):
+
+ """
+ A frr configuration is stored in a Config object. A Config object
+ contains a dictionary of Context objects where the Context keys
+ ('router ospf' for example) are our dictionary key.
+ """
+
+ def __init__(self):
+ self.lines = []
+ self.contexts = OrderedDict()
+
+ def load_from_file(self, filename):
+ """
+ Read configuration from specified file and slurp it into internal memory
+ The internal representation has been marked appropriately by passing it
+ through vtysh with the -m parameter
+ """
+ log.info('Loading Config object from file %s', filename)
+
+ try:
+ file_output = subprocess.check_output(['/usr/bin/vtysh', '-m', '-f', filename])
+ except subprocess.CalledProcessError as e:
+ raise VtyshMarkException(str(e))
+
+ for line in file_output.split('\n'):
+ line = line.strip()
+ if ":" in line:
+ qv6_line = get_normalized_ipv6_line(line)
+ self.lines.append(qv6_line)
+ else:
+ self.lines.append(line)
+
+ self.load_contexts()
+
+ def load_from_show_running(self):
+ """
+ Read running configuration and slurp it into internal memory
+ The internal representation has been marked appropriately by passing it
+ through vtysh with the -m parameter
+ """
+ log.info('Loading Config object from vtysh show running')
+
+ try:
+ config_text = subprocess.check_output(
+ "/usr/bin/vtysh -c 'show run' | /usr/bin/tail -n +4 | /usr/bin/vtysh -m -f -",
+ shell=True)
+ except subprocess.CalledProcessError as e:
+ raise VtyshMarkException(str(e))
+
+ for line in config_text.split('\n'):
+ line = line.strip()
+
+ if (line == 'Building configuration...' or
+ line == 'Current configuration:' or
+ not line):
+ continue
+
+ self.lines.append(line)
+
+ self.load_contexts()
+
+ def get_lines(self):
+ """
+ Return the lines read in from the configuration
+ """
+
+ return '\n'.join(self.lines)
+
+ def get_contexts(self):
+ """
+ Return the parsed context as strings for display, log etc.
+ """
+
+ for (_, ctx) in sorted(self.contexts.iteritems()):
+ print str(ctx) + '\n'
+
+ def save_contexts(self, key, lines):
+ """
+ Save the provided key and lines as a context
+ """
+
+ if not key:
+ return
+
+ if lines:
+ if tuple(key) not in self.contexts:
+ ctx = Context(tuple(key), lines)
+ self.contexts[tuple(key)] = ctx
+ else:
+ ctx = self.contexts[tuple(key)]
+ ctx.add_lines(lines)
+
+ else:
+ if tuple(key) not in self.contexts:
+ ctx = Context(tuple(key), [])
+ self.contexts[tuple(key)] = ctx
+
+ def load_contexts(self):
+ """
+ Parse the configuration and create contexts for each appropriate block
+ """
+
+ current_context_lines = []
+ ctx_keys = []
+
+ '''
+ The end of a context is flagged via the 'end' keyword:
+
+!
+interface swp52
+ ipv6 nd suppress-ra
+ link-detect
+!
+end
+router bgp 10
+ bgp router-id 10.0.0.1
+ bgp log-neighbor-changes
+ no bgp default ipv4-unicast
+ neighbor EBGP peer-group
+ neighbor EBGP advertisement-interval 1
+ neighbor EBGP timers connect 10
+ neighbor 2001:40:1:4::6 remote-as 40
+ neighbor 2001:40:1:8::a remote-as 40
+!
+end
+ address-family ipv6
+ neighbor IBGPv6 activate
+ neighbor 2001:10::2 peer-group IBGPv6
+ neighbor 2001:10::3 peer-group IBGPv6
+ exit-address-family
+!
+end
+router ospf
+ ospf router-id 10.0.0.1
+ log-adjacency-changes detail
+ timers throttle spf 0 50 5000
+!
+end
+ '''
+
+ # The code assumes that its working on the output from the "vtysh -m"
+ # command. That provides the appropriate markers to signify end of
+ # a context. This routine uses that to build the contexts for the
+ # config.
+ #
+ # There are single line contexts such as "log file /media/node/zebra.log"
+ # and multi-line contexts such as "router ospf" and subcontexts
+ # within a context such as "address-family" within "router bgp"
+ # In each of these cases, the first line of the context becomes the
+ # key of the context. So "router bgp 10" is the key for the non-address
+ # family part of bgp, "router bgp 10, address-family ipv6 unicast" is
+ # the key for the subcontext and so on.
+ ctx_keys = []
+ main_ctx_key = []
+ new_ctx = True
+
+ # the keywords that we know are single line contexts. bgp in this case
+ # is not the main router bgp block, but enabling multi-instance
+ oneline_ctx_keywords = ("access-list ",
+ "bgp ",
+ "debug ",
+ "dump ",
+ "enable ",
+ "hostname ",
+ "ip ",
+ "ipv6 ",
+ "log ",
+ "password ",
+ "ptm-enable",
+ "router-id ",
+ "service ",
+ "table ",
+ "username ",
+ "zebra ")
+
+ for line in self.lines:
+
+ if not line:
+ continue
+
+ if line.startswith('!') or line.startswith('#'):
+ continue
+
+ # one line contexts
+ if new_ctx is True and any(line.startswith(keyword) for keyword in oneline_ctx_keywords):
+ self.save_contexts(ctx_keys, current_context_lines)
+
+ # Start a new context
+ main_ctx_key = []
+ ctx_keys = [line, ]
+ current_context_lines = []
+
+ log.debug('LINE %-50s: entering new context, %-50s', line, ctx_keys)
+ self.save_contexts(ctx_keys, current_context_lines)
+ new_ctx = True
+
+ elif line == "end":
+ self.save_contexts(ctx_keys, current_context_lines)
+ log.debug('LINE %-50s: exiting old context, %-50s', line, ctx_keys)
+
+ # Start a new context
+ new_ctx = True
+ main_ctx_key = []
+ ctx_keys = []
+ current_context_lines = []
+
+ elif line == "exit-address-family" or line == "exit":
+ # if this exit is for address-family ipv4 unicast, ignore the pop
+ if main_ctx_key:
+ self.save_contexts(ctx_keys, current_context_lines)
+
+ # Start a new context
+ ctx_keys = copy.deepcopy(main_ctx_key)
+ current_context_lines = []
+ log.debug('LINE %-50s: popping from subcontext to ctx%-50s', line, ctx_keys)
+
+ elif new_ctx is True:
+ if not main_ctx_key:
+ ctx_keys = [line, ]
+ else:
+ ctx_keys = copy.deepcopy(main_ctx_key)
+ main_ctx_key = []
+
+ current_context_lines = []
+ new_ctx = False
+ log.debug('LINE %-50s: entering new context, %-50s', line, ctx_keys)
+
+ elif "address-family " in line:
+ main_ctx_key = []
+
+ # Save old context first
+ self.save_contexts(ctx_keys, current_context_lines)
+ current_context_lines = []
+ main_ctx_key = copy.deepcopy(ctx_keys)
+ log.debug('LINE %-50s: entering sub-context, append to ctx_keys', line)
+
+ if line == "address-family ipv6":
+ ctx_keys.append("address-family ipv6 unicast")
+ elif line == "address-family ipv4":
+ ctx_keys.append("address-family ipv4 unicast")
+ else:
+ ctx_keys.append(line)
+
+ else:
+ # Continuing in an existing context, add non-commented lines to it
+ current_context_lines.append(line)
+ log.debug('LINE %-50s: append to current_context_lines, %-50s', line, ctx_keys)
+
+ # Save the context of the last one
+ self.save_contexts(ctx_keys, current_context_lines)
+
+
+def line_to_vtysh_conft(ctx_keys, line, delete):
+ """
+ Return the vtysh command for the specified context line
+ """
+
+ cmd = []
+ cmd.append('vtysh')
+ cmd.append('-c')
+ cmd.append('conf t')
+
+ if line:
+ for ctx_key in ctx_keys:
+ cmd.append('-c')
+ cmd.append(ctx_key)
+
+ line = line.lstrip()
+
+ if delete:
+ cmd.append('-c')
+
+ if line.startswith('no '):
+ cmd.append('%s' % line[3:])
+ else:
+ cmd.append('no %s' % line)
+
+ else:
+ cmd.append('-c')
+ cmd.append(line)
+
+ # If line is None then we are typically deleting an entire
+ # context ('no router ospf' for example)
+ else:
+
+ if delete:
+
+ # Only put the 'no' on the last sub-context
+ for ctx_key in ctx_keys:
+ cmd.append('-c')
+
+ if ctx_key == ctx_keys[-1]:
+ cmd.append('no %s' % ctx_key)
+ else:
+ cmd.append('%s' % ctx_key)
+ else:
+ for ctx_key in ctx_keys:
+ cmd.append('-c')
+ cmd.append(ctx_key)
+
+ return cmd
+
+
+def line_for_vtysh_file(ctx_keys, line, delete):
+ """
+ Return the command as it would appear in Frr.conf
+ """
+ cmd = []
+
+ if line:
+ for (i, ctx_key) in enumerate(ctx_keys):
+ cmd.append(' ' * i + ctx_key)
+
+ line = line.lstrip()
+ indent = len(ctx_keys) * ' '
+
+ if delete:
+ if line.startswith('no '):
+ cmd.append('%s%s' % (indent, line[3:]))
+ else:
+ cmd.append('%sno %s' % (indent, line))
+
+ else:
+ cmd.append(indent + line)
+
+ # If line is None then we are typically deleting an entire
+ # context ('no router ospf' for example)
+ else:
+ if delete:
+
+ # Only put the 'no' on the last sub-context
+ for ctx_key in ctx_keys:
+
+ if ctx_key == ctx_keys[-1]:
+ cmd.append('no %s' % ctx_key)
+ else:
+ cmd.append('%s' % ctx_key)
+ else:
+ for ctx_key in ctx_keys:
+ cmd.append(ctx_key)
+
+ return '\n' + '\n'.join(cmd)
+
+
+def get_normalized_ipv6_line(line):
+ """
+ Return a normalized IPv6 line as produced by frr,
+ with all letters in lower case and trailing and leading
+ zeros removed
+ """
+ norm_line = ""
+ words = line.split(' ')
+ for word in words:
+ if ":" in word:
+ try:
+ norm_word = str(IPv6Address(word)).lower()
+ except:
+ norm_word = word
+ else:
+ norm_word = word
+ norm_line = norm_line + " " + norm_word
+
+ return norm_line.strip()
+
+
+def line_exist(lines, target_ctx_keys, target_line):
+ for (ctx_keys, line) in lines:
+ if ctx_keys == target_ctx_keys and line == target_line:
+ return True
+ return False
+
+
+def ignore_delete_re_add_lines(lines_to_add, lines_to_del):
+
+ # Quite possibly the most confusing (while accurate) variable names in history
+ lines_to_add_to_del = []
+ lines_to_del_to_del = []
+
+ for (ctx_keys, line) in lines_to_del:
+ deleted = False
+
+ if ctx_keys[0].startswith('router bgp') and line and line.startswith('neighbor '):
+ """
+ BGP changed how it displays swpX peers that are part of peer-group. Older
+ versions of frr would display these on separate lines:
+ neighbor swp1 interface
+ neighbor swp1 peer-group FOO
+
+ but today we display via a single line
+ neighbor swp1 interface peer-group FOO
+
+ This change confuses frr-reload.py so check to see if we are deleting
+ neighbor swp1 interface peer-group FOO
+
+ and adding
+ neighbor swp1 interface
+ neighbor swp1 peer-group FOO
+
+ If so then chop the del line and the corresponding add lines
+ """
+
+ re_swpx_int_peergroup = re.search('neighbor (\S+) interface peer-group (\S+)', line)
+ re_swpx_int_v6only_peergroup = re.search('neighbor (\S+) interface v6only peer-group (\S+)', line)
+
+ if re_swpx_int_peergroup or re_swpx_int_v6only_peergroup:
+ swpx_interface = None
+ swpx_peergroup = None
+
+ if re_swpx_int_peergroup:
+ swpx = re_swpx_int_peergroup.group(1)
+ peergroup = re_swpx_int_peergroup.group(2)
+ swpx_interface = "neighbor %s interface" % swpx
+ elif re_swpx_int_v6only_peergroup:
+ swpx = re_swpx_int_v6only_peergroup.group(1)
+ peergroup = re_swpx_int_v6only_peergroup.group(2)
+ swpx_interface = "neighbor %s interface v6only" % swpx
+
+ swpx_peergroup = "neighbor %s peer-group %s" % (swpx, peergroup)
+ found_add_swpx_interface = line_exist(lines_to_add, ctx_keys, swpx_interface)
+ found_add_swpx_peergroup = line_exist(lines_to_add, ctx_keys, swpx_peergroup)
+ tmp_ctx_keys = tuple(list(ctx_keys))
+
+ if not found_add_swpx_peergroup:
+ tmp_ctx_keys = list(ctx_keys)
+ tmp_ctx_keys.append('address-family ipv4 unicast')
+ tmp_ctx_keys = tuple(tmp_ctx_keys)
+ found_add_swpx_peergroup = line_exist(lines_to_add, tmp_ctx_keys, swpx_peergroup)
+
+ if not found_add_swpx_peergroup:
+ tmp_ctx_keys = list(ctx_keys)
+ tmp_ctx_keys.append('address-family ipv6 unicast')
+ tmp_ctx_keys = tuple(tmp_ctx_keys)
+ found_add_swpx_peergroup = line_exist(lines_to_add, tmp_ctx_keys, swpx_peergroup)
+
+ if found_add_swpx_interface and found_add_swpx_peergroup:
+ deleted = True
+ lines_to_del_to_del.append((ctx_keys, line))
+ lines_to_add_to_del.append((ctx_keys, swpx_interface))
+ lines_to_add_to_del.append((tmp_ctx_keys, swpx_peergroup))
+
+ """
+ In 3.0.1 we changed how we display neighbor interface command. Older
+ versions of frr would display the following:
+ neighbor swp1 interface
+ neighbor swp1 remote-as external
+ neighbor swp1 capability extended-nexthop
+
+ but today we display via a single line
+ neighbor swp1 interface remote-as external
+
+ and capability extended-nexthop is no longer needed because we
+ automatically enable it when the neighbor is of type interface.
+
+ This change confuses frr-reload.py so check to see if we are deleting
+ neighbor swp1 interface remote-as (external|internal|ASNUM)
+
+ and adding
+ neighbor swp1 interface
+ neighbor swp1 remote-as (external|internal|ASNUM)
+ neighbor swp1 capability extended-nexthop
+
+ If so then chop the del line and the corresponding add lines
+ """
+ re_swpx_int_remoteas = re.search('neighbor (\S+) interface remote-as (\S+)', line)
+ re_swpx_int_v6only_remoteas = re.search('neighbor (\S+) interface v6only remote-as (\S+)', line)
+
+ if re_swpx_int_remoteas or re_swpx_int_v6only_remoteas:
+ swpx_interface = None
+ swpx_remoteas = None
+
+ if re_swpx_int_remoteas:
+ swpx = re_swpx_int_remoteas.group(1)
+ remoteas = re_swpx_int_remoteas.group(2)
+ swpx_interface = "neighbor %s interface" % swpx
+ elif re_swpx_int_v6only_remoteas:
+ swpx = re_swpx_int_v6only_remoteas.group(1)
+ remoteas = re_swpx_int_v6only_remoteas.group(2)
+ swpx_interface = "neighbor %s interface v6only" % swpx
+
+ swpx_remoteas = "neighbor %s remote-as %s" % (swpx, remoteas)
+ found_add_swpx_interface = line_exist(lines_to_add, ctx_keys, swpx_interface)
+ found_add_swpx_remoteas = line_exist(lines_to_add, ctx_keys, swpx_remoteas)
+ tmp_ctx_keys = tuple(list(ctx_keys))
+
+ if found_add_swpx_interface and found_add_swpx_remoteas:
+ deleted = True
+ lines_to_del_to_del.append((ctx_keys, line))
+ lines_to_add_to_del.append((ctx_keys, swpx_interface))
+ lines_to_add_to_del.append((tmp_ctx_keys, swpx_remoteas))
+
+ if not deleted:
+ found_add_line = line_exist(lines_to_add, ctx_keys, line)
+
+ if found_add_line:
+ lines_to_del_to_del.append((ctx_keys, line))
+ lines_to_add_to_del.append((ctx_keys, line))
+ else:
+ '''
+ We have commands that used to be displayed in the global part
+ of 'router bgp' that are now displayed under 'address-family ipv4 unicast'
+
+ # old way
+ router bgp 64900
+ neighbor ISL advertisement-interval 0
+
+ vs.
+
+ # new way
+ router bgp 64900
+ address-family ipv4 unicast
+ neighbor ISL advertisement-interval 0
+
+ Look to see if we are deleting it in one format just to add it back in the other
+ '''
+ if ctx_keys[0].startswith('router bgp') and len(ctx_keys) > 1 and ctx_keys[1] == 'address-family ipv4 unicast':
+ tmp_ctx_keys = list(ctx_keys)[:-1]
+ tmp_ctx_keys = tuple(tmp_ctx_keys)
+
+ found_add_line = line_exist(lines_to_add, tmp_ctx_keys, line)
+
+ if found_add_line:
+ lines_to_del_to_del.append((ctx_keys, line))
+ lines_to_add_to_del.append((tmp_ctx_keys, line))
+
+ for (ctx_keys, line) in lines_to_del_to_del:
+ lines_to_del.remove((ctx_keys, line))
+
+ for (ctx_keys, line) in lines_to_add_to_del:
+ lines_to_add.remove((ctx_keys, line))
+
+ return (lines_to_add, lines_to_del)
+
+
+def compare_context_objects(newconf, running):
+ """
+ Create a context diff for the two specified contexts
+ """
+
+ # Compare the two Config objects to find the lines that we need to add/del
+ lines_to_add = []
+ lines_to_del = []
+ delete_bgpd = False
+
+ # Find contexts that are in newconf but not in running
+ # Find contexts that are in running but not in newconf
+ for (running_ctx_keys, running_ctx) in running.contexts.iteritems():
+
+ if running_ctx_keys not in newconf.contexts:
+
+ # We check that the len is 1 here so that we only look at ('router bgp 10')
+ # and not ('router bgp 10', 'address-family ipv4 unicast'). The
+ # latter could cause a false delete_bgpd positive if ipv4 unicast is in
+ # running but not in newconf.
+ if "router bgp" in running_ctx_keys[0] and len(running_ctx_keys) == 1:
+ delete_bgpd = True
+ lines_to_del.append((running_ctx_keys, None))
+
+ # If this is an address-family under 'router bgp' and we are already deleting the
+ # entire 'router bgp' context then ignore this sub-context
+ elif "router bgp" in running_ctx_keys[0] and len(running_ctx_keys) > 1 and delete_bgpd:
+ continue
+
+ # Non-global context
+ elif running_ctx_keys and not any("address-family" in key for key in running_ctx_keys):
+ lines_to_del.append((running_ctx_keys, None))
+
+ # Global context
+ else:
+ for line in running_ctx.lines:
+ lines_to_del.append((running_ctx_keys, line))
+
+ # Find the lines within each context to add
+ # Find the lines within each context to del
+ for (newconf_ctx_keys, newconf_ctx) in newconf.contexts.iteritems():
+
+ if newconf_ctx_keys in running.contexts:
+ running_ctx = running.contexts[newconf_ctx_keys]
+
+ for line in newconf_ctx.lines:
+ if line not in running_ctx.dlines:
+ lines_to_add.append((newconf_ctx_keys, line))
+
+ for line in running_ctx.lines:
+ if line not in newconf_ctx.dlines:
+ lines_to_del.append((newconf_ctx_keys, line))
+
+ for (newconf_ctx_keys, newconf_ctx) in newconf.contexts.iteritems():
+
+ if newconf_ctx_keys not in running.contexts:
+ lines_to_add.append((newconf_ctx_keys, None))
+
+ for line in newconf_ctx.lines:
+ lines_to_add.append((newconf_ctx_keys, line))
+
+ (lines_to_add, lines_to_del) = ignore_delete_re_add_lines(lines_to_add, lines_to_del)
+
+ return (lines_to_add, lines_to_del)
+
+if __name__ == '__main__':
+ # Command line options
+ parser = argparse.ArgumentParser(description='Dynamically apply diff in frr configs')
+ parser.add_argument('--input', help='Read running config from file instead of "show running"')
+ group = parser.add_mutually_exclusive_group(required=True)
+ group.add_argument('--reload', action='store_true', help='Apply the deltas', default=False)
+ group.add_argument('--test', action='store_true', help='Show the deltas', default=False)
+ parser.add_argument('--debug', action='store_true', help='Enable debugs', default=False)
+ parser.add_argument('--stdout', action='store_true', help='Log to STDOUT', default=False)
+ parser.add_argument('filename', help='Location of new frr config file')
+ args = parser.parse_args()
+
+ # Logging
+ # For --test log to stdout
+ # For --reload log to /var/log/frr/frr-reload.log
+ if args.test or args.stdout:
+ logging.basicConfig(level=logging.INFO,
+ format='%(asctime)s %(levelname)5s: %(message)s')
+
+ # Color the errors and warnings in red
+ logging.addLevelName(logging.ERROR, "\033[91m %s\033[0m" % logging.getLevelName(logging.ERROR))
+ logging.addLevelName(logging.WARNING, "\033[91m%s\033[0m" % logging.getLevelName(logging.WARNING))
+
+ elif args.reload:
+ if not os.path.isdir('/var/log/frr/'):
+ os.makedirs('/var/log/frr/')
+
+ logging.basicConfig(filename='/var/log/frr/frr-reload.log',
+ level=logging.INFO,
+ format='%(asctime)s %(levelname)5s: %(message)s')
+
+ # argparse should prevent this from happening but just to be safe...
+ else:
+ raise Exception('Must specify --reload or --test')
+ log = logging.getLogger(__name__)
+
+ # Verify the new config file is valid
+ if not os.path.isfile(args.filename):
+ print "Filename %s does not exist" % args.filename
+ sys.exit(1)
+
+ if not os.path.getsize(args.filename):
+ print "Filename %s is an empty file" % args.filename
+ sys.exit(1)
+
+ # Verify that 'service integrated-vtysh-config' is configured
+ vtysh_filename = '/etc/frr/vtysh.conf'
+ service_integrated_vtysh_config = True
+
+ if os.path.isfile(vtysh_filename):
+ with open(vtysh_filename, 'r') as fh:
+ for line in fh.readlines():
+ line = line.strip()
+
+ if line == 'no service integrated-vtysh-config':
+ service_integrated_vtysh_config = False
+ break
+
+ if not service_integrated_vtysh_config:
+ print "'service integrated-vtysh-config' is not configured, this is required for 'service frr reload'"
+ sys.exit(1)
+
+ if args.debug:
+ log.setLevel(logging.DEBUG)
+
+ log.info('Called via "%s"', str(args))
+
+ # Create a Config object from the config generated by newconf
+ newconf = Config()
+ newconf.load_from_file(args.filename)
+
+ if args.test:
+
+ # Create a Config object from the running config
+ running = Config()
+
+ if args.input:
+ running.load_from_file(args.input)
+ else:
+ running.load_from_show_running()
+
+ (lines_to_add, lines_to_del) = compare_context_objects(newconf, running)
+ lines_to_configure = []
+
+ if lines_to_del:
+ print "\nLines To Delete"
+ print "==============="
+
+ for (ctx_keys, line) in lines_to_del:
+
+ if line == '!':
+ continue
+
+ cmd = line_for_vtysh_file(ctx_keys, line, True)
+ lines_to_configure.append(cmd)
+ print cmd
+
+ if lines_to_add:
+ print "\nLines To Add"
+ print "============"
+
+ for (ctx_keys, line) in lines_to_add:
+
+ if line == '!':
+ continue
+
+ cmd = line_for_vtysh_file(ctx_keys, line, False)
+ lines_to_configure.append(cmd)
+ print cmd
+
+ elif args.reload:
+
+ log.debug('New Frr Config\n%s', newconf.get_lines())
+
+ # This looks a little odd but we have to do this twice...here is why
+ # If the user had this running bgp config:
+ #
+ # router bgp 10
+ # neighbor 1.1.1.1 remote-as 50
+ # neighbor 1.1.1.1 route-map FOO out
+ #
+ # and this config in the newconf config file
+ #
+ # router bgp 10
+ # neighbor 1.1.1.1 remote-as 999
+ # neighbor 1.1.1.1 route-map FOO out
+ #
+ #
+ # Then the script will do
+ # - no neighbor 1.1.1.1 remote-as 50
+ # - neighbor 1.1.1.1 remote-as 999
+ #
+ # The problem is the "no neighbor 1.1.1.1 remote-as 50" will also remove
+ # the "neighbor 1.1.1.1 route-map FOO out" line...so we compare the
+ # configs again to put this line back.
+
+ for x in range(2):
+ running = Config()
+ running.load_from_show_running()
+ log.debug('Running Frr Config (Pass #%d)\n%s', x, running.get_lines())
+
+ (lines_to_add, lines_to_del) = compare_context_objects(newconf, running)
+
+ if lines_to_del:
+ for (ctx_keys, line) in lines_to_del:
+
+ if line == '!':
+ continue
+
+ # 'no' commands are tricky, we can't just put them in a file and
+ # vtysh -f that file. See the next comment for an explanation
+ # of their quirks
+ cmd = line_to_vtysh_conft(ctx_keys, line, True)
+ original_cmd = cmd
+
+ # Some commands in frr are picky about taking a "no" of the entire line.
+ # OSPF is bad about this, you can't "no" the entire line, you have to "no"
+ # only the beginning. If we hit one of these command an exception will be
+ # thrown. Catch it and remove the last '-c', 'FOO' from cmd and try again.
+ #
+ # Example:
+ # frr(config-if)# ip ospf authentication message-digest 1.1.1.1
+ # frr(config-if)# no ip ospf authentication message-digest 1.1.1.1
+ # % Unknown command.
+ # frr(config-if)# no ip ospf authentication message-digest
+ # % Unknown command.
+ # frr(config-if)# no ip ospf authentication
+ # frr(config-if)#
+
+ while True:
+ try:
+ _ = subprocess.check_output(cmd)
+
+ except subprocess.CalledProcessError:
+
+ # - Pull the last entry from cmd (this would be
+ # 'no ip ospf authentication message-digest 1.1.1.1' in
+ # our example above
+ # - Split that last entry by whitespace and drop the last word
+ log.warning('Failed to execute %s', ' '.join(cmd))
+ last_arg = cmd[-1].split(' ')
+
+ if len(last_arg) <= 2:
+ log.error('"%s" we failed to remove this command', original_cmd)
+ break
+
+ new_last_arg = last_arg[0:-1]
+ cmd[-1] = ' '.join(new_last_arg)
+ else:
+ log.info('Executed "%s"', ' '.join(cmd))
+ break
+
+ if lines_to_add:
+ lines_to_configure = []
+
+ for (ctx_keys, line) in lines_to_add:
+
+ if line == '!':
+ continue
+
+ cmd = line_for_vtysh_file(ctx_keys, line, False)
+ lines_to_configure.append(cmd)
+
+ if lines_to_configure:
+ random_string = ''.join(random.SystemRandom().choice(
+ string.ascii_uppercase +
+ string.digits) for _ in range(6))
+
+ filename = "/var/run/frr/reload-%s.txt" % random_string
+ log.info("%s content\n%s" % (filename, pformat(lines_to_configure)))
+
+ with open(filename, 'w') as fh:
+ for line in lines_to_configure:
+ fh.write(line + '\n')
+ subprocess.call(['/usr/bin/vtysh', '-f', filename])
+ os.unlink(filename)
+
+ # Make these changes persistent
+ subprocess.call(['/usr/bin/vtysh', '-c', 'write'])
--- /dev/null
+[Unit]
+Description=Cumulus Linux FRR
+After=syslog.target networking.service
+OnFailure=heartbeat-failed@%n.service
+
+[Service]
+Nice=-5
+EnvironmentFile=/etc/default/frr
+Type=forking
+NotifyAccess=all
+StartLimitInterval=3m
+StartLimitBurst=3
+TimeoutSec=1m
+WatchdogSec=60s
+RestartSec=5
+Restart=on-abnormal
+LimitNOFILE=1024
+ExecStart=/usr/lib/frr/frr start
+ExecStop=/usr/lib/frr/frr stop
+ExecReload=/usr/lib/frr/frr-reload.py --reload /etc/frr/Frr.conf
+[Install]
+WantedBy=network-online.target
+++ /dev/null
-#!/bin/bash
-#
-### BEGIN INIT INFO
-# Provides: quagga
-# Required-Start: $local_fs $network $remote_fs $syslog
-# Required-Stop: $local_fs $network $remote_fs $syslog
-# Default-Start: 2 3 4 5
-# Default-Stop: 0 1 6
-# Short-Description: start and stop the Quagga routing suite
-# Description: Quagga is a routing suite for IP routing protocols like
-# BGP, OSPF, RIP and others. This script contols the main
-# daemon "quagga" as well as the individual protocol daemons.
-### END INIT INFO
-#
-
-PATH=/bin:/usr/bin:/sbin:/usr/sbin
-D_PATH=/usr/lib/quagga
-C_PATH=/etc/quagga
-V_PATH=/var/run/quagga
-
-# Local Daemon selection may be done by using /etc/quagga/daemons.
-# See /usr/share/doc/quagga/README.Debian.gz for further information.
-# Keep zebra first and do not list watchfrr!
-DAEMONS="zebra bgpd ripd ripngd ospfd ospf6d isisd babeld pimd"
-MAX_INSTANCES=5
-RELOAD_SCRIPT=/usr/lib/quagga/quagga-reload.py
-
-. /lib/lsb/init-functions
-
-if [ -f /usr/lib/quagga/ssd ]; then
- SSD=/usr/lib/quagga/ssd
-else
- SSD=`which start-stop-daemon`
-fi
-
-# Print the name of the pidfile.
-pidfile()
-{
- echo "$V_PATH/$1.pid"
-}
-
-# Print the name of the vtysh.
-vtyfile()
-{
- echo "$V_PATH/$1.vty"
-}
-
-# Check if daemon is started by using the pidfile.
-started()
-{
- [ ! -e `pidfile $1` ] && return 3
- if [ -n "$2" ] && [ "$2" == "log" ]; then
- status_of_proc -p `pidfile $1` $1 $1 && return 0 || return $?
- else
- kill -0 `cat \`pidfile $1\`` 2> /dev/null || return 1
- return 0
- fi
-}
-
-# Loads the config via vtysh -b if configured to do so.
-vtysh_b ()
-{
- # Rember, that all variables have been incremented by 1 in convert_daemon_prios()
- if [ "$vtysh_enable" = 2 -a -f $C_PATH/Quagga.conf ]; then
- /usr/bin/vtysh -b -n
- fi
-}
-
-# Check if the daemon is activated and if its executable and config files
-# are in place.
-# params: daemon name
-# returns: 0=ok, 1=error
-check_daemon()
-{
- # If the integrated config file is used the others are not checked.
- if [ -r "$C_PATH/Quagga.conf" ]; then
- return 0
- fi
-
- # vtysh_enable has no config file nor binary so skip check.
- # (Not sure why vtysh_enable is in this list but does not hurt)
- if [ $1 != "watchfrr" -a $1 != "vtysh_enable" ]; then
- # check for daemon binary
- if [ ! -x "$D_PATH/$1" ]; then return 1; fi
-
- # check for config file
- if [ -n "$2" ]; then
- if [ ! -r "$C_PATH/$1-$2.conf" ]; then
- touch "$C_PATH/$1-$2.conf"
- chown quagga:quagga "$C_PATH/$1-$2.conf"
- fi
- elif [ ! -r "$C_PATH/$1.conf" ]; then
- touch "$C_PATH/$1.conf"
- chown quagga:quagga "$C_PATH/$1.conf"
- fi
- fi
- return 0
-}
-
-# Starts the server if it's not alrady running according to the pid file.
-# The Quagga daemons creates the pidfile when starting.
-start()
-{
- ulimit -n $MAX_FDS
- if [ "$1" = "watchfrr" ]; then
-
- # We may need to restart watchfrr if new daemons are added and/or
- # removed
- if started "$1" ; then
- stop watchfrr
- else
- # Echo only once. watchfrr is printed in the stop above
- echo -n " $1"
- fi
-
- if [ -e /var/run/quagga/watchfrr.started ] ; then
- rm /var/run/quagga/watchfrr.started
- fi
- ${SSD} \
- --start \
- --pidfile=`pidfile $1` \
- --exec "$D_PATH/$1" \
- -- \
- "${watchfrr_options[@]}"
- for i in `seq 1 10`;
- do
- if [ -e /var/run/quagga/watchfrr.started ] ; then
- break
- else
- sleep 1
- fi
- done
- elif [ -n "$2" ]; then
- echo -n " $1-$2"
- if ! check_daemon $1 $2 ; then
- echo -n " (binary does not exist)"
- return;
- fi
-
- ${SSD} \
- --start \
- --pidfile=`pidfile $1-$2` \
- --exec "$D_PATH/$1" \
- -- \
- `eval echo "$""$1""_options"` -n "$2"
- else
- echo -n " $1"
- if ! check_daemon $1; then
- echo -n " (binary does not exist)"
- return;
- fi
-
- ${SSD} \
- --start \
- --pidfile=`pidfile $1` \
- --exec "$D_PATH/$1" \
- -- \
- `eval echo "$""$1""_options"`
- fi
-}
-
-# Stop the daemon given in the parameter, printing its name to the terminal.
-stop()
-{
- local inst
-
- if [ -n "$2" ]; then
- inst="$1-$2"
- else
- inst="$1"
- fi
-
- if ! started "$inst" ; then
- echo -n " ($inst)"
- return 0
- else
- PIDFILE=`pidfile $inst`
- PID=`cat $PIDFILE 2>/dev/null`
- ${SSD} --stop --quiet --retry=TERM/30/KILL/5 --oknodo --pidfile "$PIDFILE" --exec "$D_PATH/$1"
- #
- # Now we have to wait until $DAEMON has _really_ stopped.
- #
- if test -n "$PID" && kill -0 $PID 2>/dev/null; then
- echo -n " (waiting) ."
- cnt=0
- while kill -0 $PID 2>/dev/null; do
- cnt=`expr $cnt + 1`
- if [ $cnt -gt 60 ]; then
- # Waited 120 secs now, fail.
- echo -n "Failed.. "
- break
- fi
- sleep 2
- echo -n "."
- done
- fi
- echo -n " $inst"
- rm -f `pidfile $inst`
- rm -f `vtyfile $inst`
- fi
-}
-
-# Converts values from /etc/quagga/daemons to all-numeric values.
-convert_daemon_prios()
-{
- for name in $DAEMONS zebra vtysh_enable watchfrr_enable; do
- # First, assign the value set by the user to $value
- eval value=\${${name}:0:3}
-
- # Daemon not activated or entry missing?
- if [ "$value" = "no" -o "$value" = "" ]; then value=0; fi
-
- # These strings parsed for backwards compatibility.
- if [ "$value" = "yes" -o "$value" = "true" ]; then
- value=1;
- fi
-
- # Zebra is threatened special. It must be between 0=off and the first
- # user assigned value "1" so we increase all other enabled daemons' values.
- if [ "$name" != "zebra" -a "$value" -gt 0 ]; then value=`expr "$value" + 1`; fi
-
- # If e.g. name is zebra then we set "zebra=yes".
- eval $name=$value
- done
-}
-
-# Starts watchfrr for all wanted daemons.
-start_watchfrr()
-{
- local daemon_name
- local daemon_prio
- local found_one
- local daemon_inst
-
- # Start the monitor daemon only if desired.
- if [ 0 -eq "$watchfrr_enable" ]; then
- return
- fi
-
- # Check variable type
- if ! declare -p watchfrr_options | grep -q '^declare \-a'; then
- echo
- echo "ERROR: The variable watchfrr_options from /etc/quagga/debian.cnf must be a BASH array!"
- echo "ERROR: Please convert config file and restart!"
- exit 1
- fi
-
- # Which daemons have been started?
- found_one=0
- for daemon_name in $DAEMONS; do
- eval daemon_prio=\$$daemon_name
- if [ "$daemon_prio" -gt 0 ]; then
- eval "daemon_inst=\${${daemon_name}_instances//,/ }"
- if [ -n "$daemon_inst" ]; then
- for inst in ${daemon_inst}; do
- eval "inst_disable=\${${daemon_name}_${inst}}"
- if [ -z ${inst_disable} ] || [ ${inst_disable} != 0 ]; then
- if check_daemon $daemon_name $inst; then
- watchfrr_options+=("${daemon_name}-${inst}")
- fi
- fi
- done
- else
- if check_daemon $daemon_name; then
- watchfrr_options+=($daemon_name)
- fi
- fi
- found_one=1
- fi
- done
-
- # Start if at least one daemon is activated.
- if [ $found_one -eq 1 ]; then
- echo -n "Starting Quagga monitor daemon:"
- start watchfrr
- echo "."
- fi
-}
-
-# Stopps watchfrr.
-stop_watchfrr()
-{
- echo -n "Stopping Quagga monitor daemon:"
- stop watchfrr
- echo "."
-}
-
-# Stops all daemons that have a lower level of priority than the given.
-# (technically if daemon_prio >= wanted_prio)
-stop_prio()
-{
- local wanted_prio
- local daemon_prio
- local daemon_list
- local daemon_inst
- local inst
-
- if [ -n "$2" ] && [[ "$2" =~ (.*)-(.*) ]]; then
- daemon=${BASH_REMATCH[1]}
- inst=${BASH_REMATCH[2]}
- else
- daemon="$2"
- fi
-
- wanted_prio=$1
- daemon_list=${daemon:-$DAEMONS}
-
- echo -n "Stopping Quagga daemons (prio:$wanted_prio):"
-
- for prio_i in `seq 10 -1 $wanted_prio`; do
- for daemon_name in $daemon_list; do
- eval daemon_prio=\${${daemon_name}:0:3}
- daemon_inst=""
- if [ $daemon_prio -eq $prio_i ]; then
- eval "daemon_inst=\${${daemon_name}_instances//,/ }"
- if [ -n "$daemon_inst" ]; then
- for i in ${daemon_inst}; do
- if [ -n "$inst" ] && [ "$i" == "$inst" ]; then
- stop "$daemon_name" "$inst"
- elif [ x"$inst" == x ]; then
- stop "$daemon_name" "$i"
- fi
- done
- else
- stop "$daemon_name"
- fi
- fi
- done
- done
-
- echo "."
- if [ -z "$inst" ]; then
- # Now stop other daemons that're prowling, coz the daemons file changed
- echo -n "Stopping other quagga daemons"
- if [ -n "$daemon" ]; then
- eval "file_list_suffix="$V_PATH"/"$daemon*""
- else
- eval "file_list_suffix="$V_PATH/*""
- fi
- for pidfile in $file_list_suffix.pid; do
- PID=`cat $pidfile 2>/dev/null`
- ${SSD} --stop --quiet --oknodo --pidfile "$pidfile"
- echo -n "."
- rm -rf "$pidfile"
- done
- echo "."
-
- echo -n "Removing remaining .vty files"
- for vtyfile in $file_list_suffix.vty; do
- rm -rf "$vtyfile"
- done
- echo "."
- fi
-}
-
-# Starts all daemons that have a higher level of priority than the given.
-# (technically if daemon_prio <= wanted_prio)
-start_prio()
-{
- local wanted_prio
- local daemon_prio
- local daemon_list
- local daemon_name
- local daemon_inst
- local inst
-
- if [ -n "$2" ] && [[ "$2" =~ (.*)-(.*) ]]; then
- daemon=${BASH_REMATCH[1]}
- inst=${BASH_REMATCH[2]}
- else
- daemon="$2"
- fi
-
- wanted_prio=$1
- daemon_list=${daemon:-$DAEMONS}
-
- echo -n "Starting Quagga daemons (prio:$wanted_prio):"
-
- for prio_i in `seq 1 $wanted_prio`; do
- for daemon_name in $daemon_list; do
- eval daemon_prio=\$${daemon_name}
- daemon_inst=""
- if [ $daemon_prio -eq $prio_i ]; then
- eval "daemon_inst=\${${daemon_name}_instances//,/ }"
- if [ -n "$daemon_inst" ]; then
- if [ `echo "$daemon_inst" | wc -w` -gt ${MAX_INSTANCES} ]; then
- echo "Max instances supported is ${MAX_INSTANCES}. Aborting"
- exit 1
- fi
- # Check if we're starting again by switching from single instance
- # to MI version
- if started "$daemon_name"; then
- PIDFILE=`pidfile $daemon_name`
- ${SSD} \
- --stop --quiet --oknodo \
- --pidfile "$PIDFILE" \
- --exec "$D_PATH/$daemon_name"
-
- rm -f `pidfile $1`
- rm -f `vtyfile $1`
- fi
-
- for i in ${daemon_inst}; do
- if [ -n "$inst" ] && [ "$i" == "$inst" ]; then
- start "$daemon_name" "$inst"
- elif [ x"$inst" == x ]; then
- start "$daemon_name" "$i"
- fi
- done
- else
- # Check if we're starting again by switching from
- # single instance to MI version
- eval "file_list_suffix="$V_PATH"/"$daemon_name-*""
- for pidfile in $file_list_suffix.pid; do
- ${SSD} --stop --quiet --oknodo --pidfile "$pidfile"
- echo -n "."
- rm -rf "$pidfile"
- done
- for vtyfile in $file_list_suffix.vty; do
- rm -rf "$vtyfile"
- done
-
- start "$daemon_name"
- fi
- fi
- done
- done
- echo "."
-}
-
-check_status()
-{
- local daemon_name
- local daemon_prio
- local daemon_inst
- local failed_status=0
-
- if [ -n "$1" ] && [[ "$1" =~ (.*)-(.*) ]]; then
- daemon=${BASH_REMATCH[1]}
- inst=${BASH_REMATCH[2]}
- else
- daemon="$1"
- fi
-
- daemon_list=${daemon:-$DAEMONS}
-
- # Which daemons have been started?
- for daemon_name in $daemon_list; do
- eval daemon_prio=\$$daemon_name
- if [ "$daemon_prio" -gt 0 ]; then
- eval "daemon_inst=\${${daemon_name}_instances//,/ }"
- if [ -n "$daemon_inst" ]; then
- for i in ${daemon_inst}; do
- if [ -n "$inst" -a "$inst" = "$i" ]; then
- started "$1" "log" || failed_status=$?
- elif [ -z "$inst" ]; then
- started "$daemon_name-$i" "log" || failed_status=$?
- fi
- done
- else
- started "$daemon_name" "log" || failed_status=$?
- fi
- fi
- done
-
- # All daemons that need to have been started are up and running
- return $failed_status
-}
-
-#########################################################
-# Main program #
-#########################################################
-
-# Config broken but script must exit silently.
-[ ! -r "$C_PATH/daemons" ] && exit 0
-
-# Load configuration
-. "$C_PATH/daemons"
-. "$C_PATH/debian.conf"
-
-# Read configuration variable file if it is present
-[ -r /etc/default/quagga ] && . /etc/default/quagga
-
-MAX_INSTANCES=${MAX_INSTANCES:=5}
-
-# Set priority of un-startable daemons to 'no' and substitute 'yes' to '0'
-convert_daemon_prios
-
-if [ ! -d $V_PATH ]; then
- echo "Creating $V_PATH"
- mkdir -p $V_PATH
- chown quagga:quagga $V_PATH
- chmod 755 /$V_PATH
-fi
-
-if [ -n "$3" ] && [ "$3" != "all" ]; then
- dmn="$2"-"$3"
-elif [ -n "$2" ] && [ "$2" != "all" ]; then
- dmn="$2"
-fi
-
-case "$1" in
- start)
- # Try to load this necessary (at least for 2.6) module.
- if [ -d /lib/modules/`uname -r` ] ; then
- echo "Loading capability module if not yet done."
- set +e; LC_ALL=C modprobe -a capability 2>&1 | egrep -v "(not found|Can't locate)"; set -e
- fi
-
- # Start all daemons
- cd $C_PATH/
- if [ "$2" != "watchfrr" ]; then
- start_prio 10 $dmn
- fi
- start_watchfrr
- vtysh_b
- ;;
-
- 1|2|3|4|5|6|7|8|9|10)
- # Stop/start daemons for the appropriate priority level
- stop_prio $1
- start_prio $1
- vtysh_b
- ;;
-
- stop|0)
- # Stop all daemons at level '0' or 'stop'
- stop_watchfrr
- if [ "$dmn" != "watchfrr" ]; then
- [ -n "${dmn}" ] && eval "${dmn/-/_}=0"
- stop_prio 0 $dmn
- fi
-
- if [ -z "$dmn" -o "$dmn" = "zebra" ]; then
- echo "Removing all routes made by zebra."
- ip route flush proto zebra
- else
- [ -n "$dmn" ] && eval "${dmn/-/_}=0"
- start_watchfrr
- fi
- ;;
-
- reload)
- # Just apply the commands that have changed, no restart necessary
- [ ! -x "$RELOAD_SCRIPT" ] && echo "quagga-reload script not available" && exit 0
- NEW_CONFIG_FILE="${2:-$C_PATH/Quagga.conf}"
- [ ! -r $NEW_CONFIG_FILE ] && echo "Unable to read new configuration file $NEW_CONFIG_FILE" && exit 1
- echo "Applying only incremental changes to running configuration from Quagga.conf"
- "$RELOAD_SCRIPT" --reload /etc/quagga/Quagga.conf
- exit $?
- ;;
-
- status)
- check_status $dmn
- exit $?
- ;;
-
- restart|force-reload)
- $0 stop $dmn
- sleep 1
- $0 start $dmn
- ;;
-
- *)
- echo "Usage: /etc/init.d/quagga {start|stop|status|reload|restart|force-reload|<priority>} [daemon]"
- echo " E.g. '/etc/init.d/quagga 5' would start all daemons with a prio 1-5."
- echo " reload applies only modifications from the running config to all daemons."
- echo " reload neither restarts starts any daemon nor starts any new ones."
- echo " Read /usr/share/doc/quagga/README.Debian for details."
- exit 1
- ;;
-esac
-
-echo "Exiting from the script"
-exit 0
+++ /dev/null
-#!/usr/bin/python
-# Quagga Reloader
-# Copyright (C) 2014 Cumulus Networks, Inc.
-#
-# This file is part of Quagga.
-#
-# Quagga is free software; you can redistribute it and/or modify it
-# under the terms of the GNU General Public License as published by the
-# Free Software Foundation; either version 2, or (at your option) any
-# later version.
-#
-# Quagga 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 Quagga; see the file COPYING. If not, write to the Free
-# Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-# 02111-1307, USA.
-#
-"""
-This program
-- reads a quagga configuration text file
-- reads quagga's current running configuration via "vtysh -c 'show running'"
-- compares the two configs and determines what commands to execute to
- synchronize quagga's running configuration with the configuation in the
- text file
-"""
-
-import argparse
-import copy
-import logging
-import os
-import random
-import re
-import string
-import subprocess
-import sys
-from collections import OrderedDict
-from ipaddr import IPv6Address
-from pprint import pformat
-
-
-log = logging.getLogger(__name__)
-
-
-class VtyshMarkException(Exception):
- pass
-
-
-class Context(object):
-
- """
- A Context object represents a section of quagga configuration such as:
-!
-interface swp3
- description swp3 -> r8's swp1
- ipv6 nd suppress-ra
- link-detect
-!
-
-or a single line context object such as this:
-
-ip forwarding
-
- """
-
- def __init__(self, keys, lines):
- self.keys = keys
- self.lines = lines
-
- # Keep a dictionary of the lines, this is to make it easy to tell if a
- # line exists in this Context
- self.dlines = OrderedDict()
-
- for ligne in lines:
- self.dlines[ligne] = True
-
- def add_lines(self, lines):
- """
- Add lines to specified context
- """
-
- self.lines.extend(lines)
-
- for ligne in lines:
- self.dlines[ligne] = True
-
-
-class Config(object):
-
- """
- A quagga configuration is stored in a Config object. A Config object
- contains a dictionary of Context objects where the Context keys
- ('router ospf' for example) are our dictionary key.
- """
-
- def __init__(self):
- self.lines = []
- self.contexts = OrderedDict()
-
- def load_from_file(self, filename):
- """
- Read configuration from specified file and slurp it into internal memory
- The internal representation has been marked appropriately by passing it
- through vtysh with the -m parameter
- """
- log.info('Loading Config object from file %s', filename)
-
- try:
- file_output = subprocess.check_output(['/usr/bin/vtysh', '-m', '-f', filename])
- except subprocess.CalledProcessError as e:
- raise VtyshMarkException(str(e))
-
- for line in file_output.split('\n'):
- line = line.strip()
- if ":" in line:
- qv6_line = get_normalized_ipv6_line(line)
- self.lines.append(qv6_line)
- else:
- self.lines.append(line)
-
- self.load_contexts()
-
- def load_from_show_running(self):
- """
- Read running configuration and slurp it into internal memory
- The internal representation has been marked appropriately by passing it
- through vtysh with the -m parameter
- """
- log.info('Loading Config object from vtysh show running')
-
- try:
- config_text = subprocess.check_output(
- "/usr/bin/vtysh -c 'show run' | /usr/bin/tail -n +4 | /usr/bin/vtysh -m -f -",
- shell=True)
- except subprocess.CalledProcessError as e:
- raise VtyshMarkException(str(e))
-
- for line in config_text.split('\n'):
- line = line.strip()
-
- if (line == 'Building configuration...' or
- line == 'Current configuration:' or
- not line):
- continue
-
- self.lines.append(line)
-
- self.load_contexts()
-
- def get_lines(self):
- """
- Return the lines read in from the configuration
- """
-
- return '\n'.join(self.lines)
-
- def get_contexts(self):
- """
- Return the parsed context as strings for display, log etc.
- """
-
- for (_, ctx) in sorted(self.contexts.iteritems()):
- print str(ctx) + '\n'
-
- def save_contexts(self, key, lines):
- """
- Save the provided key and lines as a context
- """
-
- if not key:
- return
-
- if lines:
- if tuple(key) not in self.contexts:
- ctx = Context(tuple(key), lines)
- self.contexts[tuple(key)] = ctx
- else:
- ctx = self.contexts[tuple(key)]
- ctx.add_lines(lines)
-
- else:
- if tuple(key) not in self.contexts:
- ctx = Context(tuple(key), [])
- self.contexts[tuple(key)] = ctx
-
- def load_contexts(self):
- """
- Parse the configuration and create contexts for each appropriate block
- """
-
- current_context_lines = []
- ctx_keys = []
-
- '''
- The end of a context is flagged via the 'end' keyword:
-
-!
-interface swp52
- ipv6 nd suppress-ra
- link-detect
-!
-end
-router bgp 10
- bgp router-id 10.0.0.1
- bgp log-neighbor-changes
- no bgp default ipv4-unicast
- neighbor EBGP peer-group
- neighbor EBGP advertisement-interval 1
- neighbor EBGP timers connect 10
- neighbor 2001:40:1:4::6 remote-as 40
- neighbor 2001:40:1:8::a remote-as 40
-!
-end
- address-family ipv6
- neighbor IBGPv6 activate
- neighbor 2001:10::2 peer-group IBGPv6
- neighbor 2001:10::3 peer-group IBGPv6
- exit-address-family
-!
-end
-router ospf
- ospf router-id 10.0.0.1
- log-adjacency-changes detail
- timers throttle spf 0 50 5000
-!
-end
- '''
-
- # The code assumes that its working on the output from the "vtysh -m"
- # command. That provides the appropriate markers to signify end of
- # a context. This routine uses that to build the contexts for the
- # config.
- #
- # There are single line contexts such as "log file /media/node/zebra.log"
- # and multi-line contexts such as "router ospf" and subcontexts
- # within a context such as "address-family" within "router bgp"
- # In each of these cases, the first line of the context becomes the
- # key of the context. So "router bgp 10" is the key for the non-address
- # family part of bgp, "router bgp 10, address-family ipv6 unicast" is
- # the key for the subcontext and so on.
- ctx_keys = []
- main_ctx_key = []
- new_ctx = True
-
- # the keywords that we know are single line contexts. bgp in this case
- # is not the main router bgp block, but enabling multi-instance
- oneline_ctx_keywords = ("access-list ",
- "bgp ",
- "debug ",
- "dump ",
- "enable ",
- "hostname ",
- "ip ",
- "ipv6 ",
- "log ",
- "password ",
- "ptm-enable",
- "router-id ",
- "service ",
- "table ",
- "username ",
- "zebra ")
-
- for line in self.lines:
-
- if not line:
- continue
-
- if line.startswith('!') or line.startswith('#'):
- continue
-
- # one line contexts
- if new_ctx is True and any(line.startswith(keyword) for keyword in oneline_ctx_keywords):
- self.save_contexts(ctx_keys, current_context_lines)
-
- # Start a new context
- main_ctx_key = []
- ctx_keys = [line, ]
- current_context_lines = []
-
- log.debug('LINE %-50s: entering new context, %-50s', line, ctx_keys)
- self.save_contexts(ctx_keys, current_context_lines)
- new_ctx = True
-
- elif line == "end":
- self.save_contexts(ctx_keys, current_context_lines)
- log.debug('LINE %-50s: exiting old context, %-50s', line, ctx_keys)
-
- # Start a new context
- new_ctx = True
- main_ctx_key = []
- ctx_keys = []
- current_context_lines = []
-
- elif line == "exit-address-family" or line == "exit":
- # if this exit is for address-family ipv4 unicast, ignore the pop
- if main_ctx_key:
- self.save_contexts(ctx_keys, current_context_lines)
-
- # Start a new context
- ctx_keys = copy.deepcopy(main_ctx_key)
- current_context_lines = []
- log.debug('LINE %-50s: popping from subcontext to ctx%-50s', line, ctx_keys)
-
- elif new_ctx is True:
- if not main_ctx_key:
- ctx_keys = [line, ]
- else:
- ctx_keys = copy.deepcopy(main_ctx_key)
- main_ctx_key = []
-
- current_context_lines = []
- new_ctx = False
- log.debug('LINE %-50s: entering new context, %-50s', line, ctx_keys)
-
- elif "address-family " in line:
- main_ctx_key = []
-
- # Save old context first
- self.save_contexts(ctx_keys, current_context_lines)
- current_context_lines = []
- main_ctx_key = copy.deepcopy(ctx_keys)
- log.debug('LINE %-50s: entering sub-context, append to ctx_keys', line)
-
- if line == "address-family ipv6":
- ctx_keys.append("address-family ipv6 unicast")
- elif line == "address-family ipv4":
- ctx_keys.append("address-family ipv4 unicast")
- else:
- ctx_keys.append(line)
-
- else:
- # Continuing in an existing context, add non-commented lines to it
- current_context_lines.append(line)
- log.debug('LINE %-50s: append to current_context_lines, %-50s', line, ctx_keys)
-
- # Save the context of the last one
- self.save_contexts(ctx_keys, current_context_lines)
-
-
-def line_to_vtysh_conft(ctx_keys, line, delete):
- """
- Return the vtysh command for the specified context line
- """
-
- cmd = []
- cmd.append('vtysh')
- cmd.append('-c')
- cmd.append('conf t')
-
- if line:
- for ctx_key in ctx_keys:
- cmd.append('-c')
- cmd.append(ctx_key)
-
- line = line.lstrip()
-
- if delete:
- cmd.append('-c')
-
- if line.startswith('no '):
- cmd.append('%s' % line[3:])
- else:
- cmd.append('no %s' % line)
-
- else:
- cmd.append('-c')
- cmd.append(line)
-
- # If line is None then we are typically deleting an entire
- # context ('no router ospf' for example)
- else:
-
- if delete:
-
- # Only put the 'no' on the last sub-context
- for ctx_key in ctx_keys:
- cmd.append('-c')
-
- if ctx_key == ctx_keys[-1]:
- cmd.append('no %s' % ctx_key)
- else:
- cmd.append('%s' % ctx_key)
- else:
- for ctx_key in ctx_keys:
- cmd.append('-c')
- cmd.append(ctx_key)
-
- return cmd
-
-
-def line_for_vtysh_file(ctx_keys, line, delete):
- """
- Return the command as it would appear in Quagga.conf
- """
- cmd = []
-
- if line:
- for (i, ctx_key) in enumerate(ctx_keys):
- cmd.append(' ' * i + ctx_key)
-
- line = line.lstrip()
- indent = len(ctx_keys) * ' '
-
- if delete:
- if line.startswith('no '):
- cmd.append('%s%s' % (indent, line[3:]))
- else:
- cmd.append('%sno %s' % (indent, line))
-
- else:
- cmd.append(indent + line)
-
- # If line is None then we are typically deleting an entire
- # context ('no router ospf' for example)
- else:
- if delete:
-
- # Only put the 'no' on the last sub-context
- for ctx_key in ctx_keys:
-
- if ctx_key == ctx_keys[-1]:
- cmd.append('no %s' % ctx_key)
- else:
- cmd.append('%s' % ctx_key)
- else:
- for ctx_key in ctx_keys:
- cmd.append(ctx_key)
-
- return '\n' + '\n'.join(cmd)
-
-
-def get_normalized_ipv6_line(line):
- """
- Return a normalized IPv6 line as produced by quagga,
- with all letters in lower case and trailing and leading
- zeros removed
- """
- norm_line = ""
- words = line.split(' ')
- for word in words:
- if ":" in word:
- try:
- norm_word = str(IPv6Address(word)).lower()
- except:
- norm_word = word
- else:
- norm_word = word
- norm_line = norm_line + " " + norm_word
-
- return norm_line.strip()
-
-
-def line_exist(lines, target_ctx_keys, target_line):
- for (ctx_keys, line) in lines:
- if ctx_keys == target_ctx_keys and line == target_line:
- return True
- return False
-
-
-def ignore_delete_re_add_lines(lines_to_add, lines_to_del):
-
- # Quite possibly the most confusing (while accurate) variable names in history
- lines_to_add_to_del = []
- lines_to_del_to_del = []
-
- for (ctx_keys, line) in lines_to_del:
- deleted = False
-
- if ctx_keys[0].startswith('router bgp') and line and line.startswith('neighbor '):
- """
- BGP changed how it displays swpX peers that are part of peer-group. Older
- versions of quagga would display these on separate lines:
- neighbor swp1 interface
- neighbor swp1 peer-group FOO
-
- but today we display via a single line
- neighbor swp1 interface peer-group FOO
-
- This change confuses quagga-reload.py so check to see if we are deleting
- neighbor swp1 interface peer-group FOO
-
- and adding
- neighbor swp1 interface
- neighbor swp1 peer-group FOO
-
- If so then chop the del line and the corresponding add lines
- """
-
- re_swpx_int_peergroup = re.search('neighbor (\S+) interface peer-group (\S+)', line)
- re_swpx_int_v6only_peergroup = re.search('neighbor (\S+) interface v6only peer-group (\S+)', line)
-
- if re_swpx_int_peergroup or re_swpx_int_v6only_peergroup:
- swpx_interface = None
- swpx_peergroup = None
-
- if re_swpx_int_peergroup:
- swpx = re_swpx_int_peergroup.group(1)
- peergroup = re_swpx_int_peergroup.group(2)
- swpx_interface = "neighbor %s interface" % swpx
- elif re_swpx_int_v6only_peergroup:
- swpx = re_swpx_int_v6only_peergroup.group(1)
- peergroup = re_swpx_int_v6only_peergroup.group(2)
- swpx_interface = "neighbor %s interface v6only" % swpx
-
- swpx_peergroup = "neighbor %s peer-group %s" % (swpx, peergroup)
- found_add_swpx_interface = line_exist(lines_to_add, ctx_keys, swpx_interface)
- found_add_swpx_peergroup = line_exist(lines_to_add, ctx_keys, swpx_peergroup)
- tmp_ctx_keys = tuple(list(ctx_keys))
-
- if not found_add_swpx_peergroup:
- tmp_ctx_keys = list(ctx_keys)
- tmp_ctx_keys.append('address-family ipv4 unicast')
- tmp_ctx_keys = tuple(tmp_ctx_keys)
- found_add_swpx_peergroup = line_exist(lines_to_add, tmp_ctx_keys, swpx_peergroup)
-
- if not found_add_swpx_peergroup:
- tmp_ctx_keys = list(ctx_keys)
- tmp_ctx_keys.append('address-family ipv6 unicast')
- tmp_ctx_keys = tuple(tmp_ctx_keys)
- found_add_swpx_peergroup = line_exist(lines_to_add, tmp_ctx_keys, swpx_peergroup)
-
- if found_add_swpx_interface and found_add_swpx_peergroup:
- deleted = True
- lines_to_del_to_del.append((ctx_keys, line))
- lines_to_add_to_del.append((ctx_keys, swpx_interface))
- lines_to_add_to_del.append((tmp_ctx_keys, swpx_peergroup))
-
- """
- In 3.0.1 we changed how we display neighbor interface command. Older
- versions of quagga would display the following:
- neighbor swp1 interface
- neighbor swp1 remote-as external
- neighbor swp1 capability extended-nexthop
-
- but today we display via a single line
- neighbor swp1 interface remote-as external
-
- and capability extended-nexthop is no longer needed because we
- automatically enable it when the neighbor is of type interface.
-
- This change confuses quagga-reload.py so check to see if we are deleting
- neighbor swp1 interface remote-as (external|internal|ASNUM)
-
- and adding
- neighbor swp1 interface
- neighbor swp1 remote-as (external|internal|ASNUM)
- neighbor swp1 capability extended-nexthop
-
- If so then chop the del line and the corresponding add lines
- """
- re_swpx_int_remoteas = re.search('neighbor (\S+) interface remote-as (\S+)', line)
- re_swpx_int_v6only_remoteas = re.search('neighbor (\S+) interface v6only remote-as (\S+)', line)
-
- if re_swpx_int_remoteas or re_swpx_int_v6only_remoteas:
- swpx_interface = None
- swpx_remoteas = None
-
- if re_swpx_int_remoteas:
- swpx = re_swpx_int_remoteas.group(1)
- remoteas = re_swpx_int_remoteas.group(2)
- swpx_interface = "neighbor %s interface" % swpx
- elif re_swpx_int_v6only_remoteas:
- swpx = re_swpx_int_v6only_remoteas.group(1)
- remoteas = re_swpx_int_v6only_remoteas.group(2)
- swpx_interface = "neighbor %s interface v6only" % swpx
-
- swpx_remoteas = "neighbor %s remote-as %s" % (swpx, remoteas)
- found_add_swpx_interface = line_exist(lines_to_add, ctx_keys, swpx_interface)
- found_add_swpx_remoteas = line_exist(lines_to_add, ctx_keys, swpx_remoteas)
- tmp_ctx_keys = tuple(list(ctx_keys))
-
- if found_add_swpx_interface and found_add_swpx_remoteas:
- deleted = True
- lines_to_del_to_del.append((ctx_keys, line))
- lines_to_add_to_del.append((ctx_keys, swpx_interface))
- lines_to_add_to_del.append((tmp_ctx_keys, swpx_remoteas))
-
- if not deleted:
- found_add_line = line_exist(lines_to_add, ctx_keys, line)
-
- if found_add_line:
- lines_to_del_to_del.append((ctx_keys, line))
- lines_to_add_to_del.append((ctx_keys, line))
- else:
- '''
- We have commands that used to be displayed in the global part
- of 'router bgp' that are now displayed under 'address-family ipv4 unicast'
-
- # old way
- router bgp 64900
- neighbor ISL advertisement-interval 0
-
- vs.
-
- # new way
- router bgp 64900
- address-family ipv4 unicast
- neighbor ISL advertisement-interval 0
-
- Look to see if we are deleting it in one format just to add it back in the other
- '''
- if ctx_keys[0].startswith('router bgp') and len(ctx_keys) > 1 and ctx_keys[1] == 'address-family ipv4 unicast':
- tmp_ctx_keys = list(ctx_keys)[:-1]
- tmp_ctx_keys = tuple(tmp_ctx_keys)
-
- found_add_line = line_exist(lines_to_add, tmp_ctx_keys, line)
-
- if found_add_line:
- lines_to_del_to_del.append((ctx_keys, line))
- lines_to_add_to_del.append((tmp_ctx_keys, line))
-
- for (ctx_keys, line) in lines_to_del_to_del:
- lines_to_del.remove((ctx_keys, line))
-
- for (ctx_keys, line) in lines_to_add_to_del:
- lines_to_add.remove((ctx_keys, line))
-
- return (lines_to_add, lines_to_del)
-
-
-def compare_context_objects(newconf, running):
- """
- Create a context diff for the two specified contexts
- """
-
- # Compare the two Config objects to find the lines that we need to add/del
- lines_to_add = []
- lines_to_del = []
- delete_bgpd = False
-
- # Find contexts that are in newconf but not in running
- # Find contexts that are in running but not in newconf
- for (running_ctx_keys, running_ctx) in running.contexts.iteritems():
-
- if running_ctx_keys not in newconf.contexts:
-
- # We check that the len is 1 here so that we only look at ('router bgp 10')
- # and not ('router bgp 10', 'address-family ipv4 unicast'). The
- # latter could cause a false delete_bgpd positive if ipv4 unicast is in
- # running but not in newconf.
- if "router bgp" in running_ctx_keys[0] and len(running_ctx_keys) == 1:
- delete_bgpd = True
- lines_to_del.append((running_ctx_keys, None))
-
- # If this is an address-family under 'router bgp' and we are already deleting the
- # entire 'router bgp' context then ignore this sub-context
- elif "router bgp" in running_ctx_keys[0] and len(running_ctx_keys) > 1 and delete_bgpd:
- continue
-
- # Non-global context
- elif running_ctx_keys and not any("address-family" in key for key in running_ctx_keys):
- lines_to_del.append((running_ctx_keys, None))
-
- # Global context
- else:
- for line in running_ctx.lines:
- lines_to_del.append((running_ctx_keys, line))
-
- # Find the lines within each context to add
- # Find the lines within each context to del
- for (newconf_ctx_keys, newconf_ctx) in newconf.contexts.iteritems():
-
- if newconf_ctx_keys in running.contexts:
- running_ctx = running.contexts[newconf_ctx_keys]
-
- for line in newconf_ctx.lines:
- if line not in running_ctx.dlines:
- lines_to_add.append((newconf_ctx_keys, line))
-
- for line in running_ctx.lines:
- if line not in newconf_ctx.dlines:
- lines_to_del.append((newconf_ctx_keys, line))
-
- for (newconf_ctx_keys, newconf_ctx) in newconf.contexts.iteritems():
-
- if newconf_ctx_keys not in running.contexts:
- lines_to_add.append((newconf_ctx_keys, None))
-
- for line in newconf_ctx.lines:
- lines_to_add.append((newconf_ctx_keys, line))
-
- (lines_to_add, lines_to_del) = ignore_delete_re_add_lines(lines_to_add, lines_to_del)
-
- return (lines_to_add, lines_to_del)
-
-if __name__ == '__main__':
- # Command line options
- parser = argparse.ArgumentParser(description='Dynamically apply diff in quagga configs')
- parser.add_argument('--input', help='Read running config from file instead of "show running"')
- group = parser.add_mutually_exclusive_group(required=True)
- group.add_argument('--reload', action='store_true', help='Apply the deltas', default=False)
- group.add_argument('--test', action='store_true', help='Show the deltas', default=False)
- parser.add_argument('--debug', action='store_true', help='Enable debugs', default=False)
- parser.add_argument('--stdout', action='store_true', help='Log to STDOUT', default=False)
- parser.add_argument('filename', help='Location of new quagga config file')
- args = parser.parse_args()
-
- # Logging
- # For --test log to stdout
- # For --reload log to /var/log/quagga/quagga-reload.log
- if args.test or args.stdout:
- logging.basicConfig(level=logging.INFO,
- format='%(asctime)s %(levelname)5s: %(message)s')
-
- # Color the errors and warnings in red
- logging.addLevelName(logging.ERROR, "\033[91m %s\033[0m" % logging.getLevelName(logging.ERROR))
- logging.addLevelName(logging.WARNING, "\033[91m%s\033[0m" % logging.getLevelName(logging.WARNING))
-
- elif args.reload:
- if not os.path.isdir('/var/log/quagga/'):
- os.makedirs('/var/log/quagga/')
-
- logging.basicConfig(filename='/var/log/quagga/quagga-reload.log',
- level=logging.INFO,
- format='%(asctime)s %(levelname)5s: %(message)s')
-
- # argparse should prevent this from happening but just to be safe...
- else:
- raise Exception('Must specify --reload or --test')
- log = logging.getLogger(__name__)
-
- # Verify the new config file is valid
- if not os.path.isfile(args.filename):
- print "Filename %s does not exist" % args.filename
- sys.exit(1)
-
- if not os.path.getsize(args.filename):
- print "Filename %s is an empty file" % args.filename
- sys.exit(1)
-
- # Verify that 'service integrated-vtysh-config' is configured
- vtysh_filename = '/etc/quagga/vtysh.conf'
- service_integrated_vtysh_config = True
-
- if os.path.isfile(vtysh_filename):
- with open(vtysh_filename, 'r') as fh:
- for line in fh.readlines():
- line = line.strip()
-
- if line == 'no service integrated-vtysh-config':
- service_integrated_vtysh_config = False
- break
-
- if not service_integrated_vtysh_config:
- print "'service integrated-vtysh-config' is not configured, this is required for 'service quagga reload'"
- sys.exit(1)
-
- if args.debug:
- log.setLevel(logging.DEBUG)
-
- log.info('Called via "%s"', str(args))
-
- # Create a Config object from the config generated by newconf
- newconf = Config()
- newconf.load_from_file(args.filename)
-
- if args.test:
-
- # Create a Config object from the running config
- running = Config()
-
- if args.input:
- running.load_from_file(args.input)
- else:
- running.load_from_show_running()
-
- (lines_to_add, lines_to_del) = compare_context_objects(newconf, running)
- lines_to_configure = []
-
- if lines_to_del:
- print "\nLines To Delete"
- print "==============="
-
- for (ctx_keys, line) in lines_to_del:
-
- if line == '!':
- continue
-
- cmd = line_for_vtysh_file(ctx_keys, line, True)
- lines_to_configure.append(cmd)
- print cmd
-
- if lines_to_add:
- print "\nLines To Add"
- print "============"
-
- for (ctx_keys, line) in lines_to_add:
-
- if line == '!':
- continue
-
- cmd = line_for_vtysh_file(ctx_keys, line, False)
- lines_to_configure.append(cmd)
- print cmd
-
- elif args.reload:
-
- log.debug('New Quagga Config\n%s', newconf.get_lines())
-
- # This looks a little odd but we have to do this twice...here is why
- # If the user had this running bgp config:
- #
- # router bgp 10
- # neighbor 1.1.1.1 remote-as 50
- # neighbor 1.1.1.1 route-map FOO out
- #
- # and this config in the newconf config file
- #
- # router bgp 10
- # neighbor 1.1.1.1 remote-as 999
- # neighbor 1.1.1.1 route-map FOO out
- #
- #
- # Then the script will do
- # - no neighbor 1.1.1.1 remote-as 50
- # - neighbor 1.1.1.1 remote-as 999
- #
- # The problem is the "no neighbor 1.1.1.1 remote-as 50" will also remove
- # the "neighbor 1.1.1.1 route-map FOO out" line...so we compare the
- # configs again to put this line back.
-
- for x in range(2):
- running = Config()
- running.load_from_show_running()
- log.debug('Running Quagga Config (Pass #%d)\n%s', x, running.get_lines())
-
- (lines_to_add, lines_to_del) = compare_context_objects(newconf, running)
-
- if lines_to_del:
- for (ctx_keys, line) in lines_to_del:
-
- if line == '!':
- continue
-
- # 'no' commands are tricky, we can't just put them in a file and
- # vtysh -f that file. See the next comment for an explanation
- # of their quirks
- cmd = line_to_vtysh_conft(ctx_keys, line, True)
- original_cmd = cmd
-
- # Some commands in quagga are picky about taking a "no" of the entire line.
- # OSPF is bad about this, you can't "no" the entire line, you have to "no"
- # only the beginning. If we hit one of these command an exception will be
- # thrown. Catch it and remove the last '-c', 'FOO' from cmd and try again.
- #
- # Example:
- # quagga(config-if)# ip ospf authentication message-digest 1.1.1.1
- # quagga(config-if)# no ip ospf authentication message-digest 1.1.1.1
- # % Unknown command.
- # quagga(config-if)# no ip ospf authentication message-digest
- # % Unknown command.
- # quagga(config-if)# no ip ospf authentication
- # quagga(config-if)#
-
- while True:
- try:
- _ = subprocess.check_output(cmd)
-
- except subprocess.CalledProcessError:
-
- # - Pull the last entry from cmd (this would be
- # 'no ip ospf authentication message-digest 1.1.1.1' in
- # our example above
- # - Split that last entry by whitespace and drop the last word
- log.warning('Failed to execute %s', ' '.join(cmd))
- last_arg = cmd[-1].split(' ')
-
- if len(last_arg) <= 2:
- log.error('"%s" we failed to remove this command', original_cmd)
- break
-
- new_last_arg = last_arg[0:-1]
- cmd[-1] = ' '.join(new_last_arg)
- else:
- log.info('Executed "%s"', ' '.join(cmd))
- break
-
- if lines_to_add:
- lines_to_configure = []
-
- for (ctx_keys, line) in lines_to_add:
-
- if line == '!':
- continue
-
- cmd = line_for_vtysh_file(ctx_keys, line, False)
- lines_to_configure.append(cmd)
-
- if lines_to_configure:
- random_string = ''.join(random.SystemRandom().choice(
- string.ascii_uppercase +
- string.digits) for _ in range(6))
-
- filename = "/var/run/quagga/reload-%s.txt" % random_string
- log.info("%s content\n%s" % (filename, pformat(lines_to_configure)))
-
- with open(filename, 'w') as fh:
- for line in lines_to_configure:
- fh.write(line + '\n')
- subprocess.call(['/usr/bin/vtysh', '-f', filename])
- os.unlink(filename)
-
- # Make these changes persistent
- subprocess.call(['/usr/bin/vtysh', '-c', 'write'])
+++ /dev/null
-[Unit]
-Description=Cumulus Linux Quagga
-After=syslog.target networking.service
-OnFailure=heartbeat-failed@%n.service
-
-[Service]
-Nice=-5
-EnvironmentFile=/etc/default/quagga
-Type=forking
-NotifyAccess=all
-StartLimitInterval=3m
-StartLimitBurst=3
-TimeoutSec=1m
-WatchdogSec=60s
-RestartSec=5
-Restart=on-abnormal
-LimitNOFILE=1024
-ExecStart=/usr/lib/quagga/quagga start
-ExecStop=/usr/lib/quagga/quagga stop
-ExecReload=/usr/lib/quagga/quagga-reload.py --reload /etc/quagga/Quagga.conf
-[Install]
-WantedBy=network-online.target