From d486dd0df02969a9706027f8cba650e3e5ac2045 Mon Sep 17 00:00:00 2001 From: Julien Fortin Date: Thu, 13 Dec 2018 11:43:32 -0800 Subject: [PATCH] ifupdown2 2.0.0 release This is a major update coming all at once from master-next branch master-next branch was started with --orphan option which is basically a new branch without history. The major changes are: - repackaging - cleanup the directory tree - rewritte setup.py to allow install from deb file or pypi (pip install) - add a Makefile to make things (like building a deb) easier - review all debian files Signed-off-by: Julien Fortin --- KNOWN_ISSUES | 6 - Makefile | 96 + README | 162 + README.md | 75 - README.rst | 73 - TODO | 36 - TODO.addons | 13 - addons/batman_adv.py | 344 -- addons/bond.py | 485 --- addons/bridge.py | 2109 ----------- addons/link.py | 88 - addons/loopback.py | 63 - addons/ppp.py | 140 - addons/tunnel.py | 192 - addons/vxlan.py | 308 -- build.sh | 8 - debian/changelog | 159 +- debian/clean | 4 - debian/control | 19 +- debian/copyright | 10 +- debian/ifupdown2.bash-completion | 2 +- debian/ifupdown2.dirs | 7 +- debian/ifupdown2.install | 6 +- debian/ifupdown2.links | 3 +- debian/ifupdown2.manpages | 1 + debian/ifupdown2.networking.service | 5 +- debian/ifupdown2.postinst | 108 +- debian/ifupdown2.postrm | 92 +- debian/ifupdown2.preinst | 39 - debian/networking | 23 - debian/python-ifupdown2.postinst | 114 - debian/python-ifupdown2.postrm | 71 - debian/python-ifupdown2.preinst | 56 - debian/rules | 4 +- debian/source/format | 2 +- debian/watch | 1 + .../batman_adv/configure_batman_adv.sh | 49 - docs/examples/batman_adv/interfaces_batman | 12 - docs/examples/generate_interfaces.py | 29 +- .../vlan_aware_bridges/interfaces.with_bonds | 4 +- .../vlan_aware_bridges/interfaces.with_clag | 4 +- docs/source/addonsapiref.rst | 9 - docs/source/addonshelperapiref.rst | 39 +- docs/source/userguide.rst | 36 +- {config => etc/default}/networking | 0 {init.d => etc/init.d}/networking | 7 +- {config => etc/network/ifupdown2}/addons.conf | 10 +- .../network/ifupdown2}/ifupdown2.conf | 2 +- ifupdown/__init__.py | 5 - ifupdown/exceptions.py | 26 - ifupdown/ifupdownconfig.py | 14 - ifupdown/netlink.py | 155 - ifupdown/rtnetlink.py | 860 ----- ifupdown/rtnetlink_api.py | 237 -- ifupdown2/__init__.py | 46 + ifupdown2/__main__.py | 205 ++ {addons => ifupdown2/addons}/__init__.py | 0 {addons => ifupdown2/addons}/address.py | 490 ++- .../addons}/addressvirtual.py | 125 +- ifupdown2/addons/bond.py | 773 ++++ ifupdown2/addons/bridge.py | 3146 +++++++++++++++++ {addons => ifupdown2/addons}/bridgevlan.py | 59 +- {addons => ifupdown2/addons}/dhcp.py | 41 +- {addons => ifupdown2/addons}/ethtool.py | 275 +- ifupdown2/addons/link.py | 142 + {addons => ifupdown2/addons}/mstpctl.py | 210 +- {addons => ifupdown2/addons}/usercmds.py | 16 +- {addons => ifupdown2/addons}/vlan.py | 115 +- {addons => ifupdown2/addons}/vrf.py | 225 +- {addons => ifupdown2/addons}/vrrpd.py | 47 +- ifupdown2/addons/vxlan.py | 523 +++ {completion => ifupdown2/completion}/ifup | 0 .../ifupdown}/__init__.py | 0 ifupdown2/ifupdown/argv.py | 231 ++ ifupdown2/ifupdown/config.py | 57 + ifupdown2/ifupdown/exceptions.py | 58 + {ifupdown => ifupdown2/ifupdown}/graph.py | 11 +- {ifupdown => ifupdown2/ifupdown}/iface.py | 72 +- {ifupdown => ifupdown2/ifupdown}/iff.py | 2 +- .../ifupdown}/ifupdownbase.py | 20 +- .../ifupdown/ifupdownconfig.py | 7 +- .../ifupdown}/ifupdownflags.py | 8 +- .../ifupdown}/ifupdownmain.py | 539 ++- ifupdown2/ifupdown/log.py | 183 + ifupdown2/ifupdown/main.py | 277 ++ ifupdown2/ifupdown/netlink.py | 627 ++++ .../ifupdown}/networkinterfaces.py | 42 +- .../ifupdown}/policymanager.py | 60 +- {ifupdown => ifupdown2/ifupdown}/scheduler.py | 53 +- .../ifupdown}/statemanager.py | 24 +- {ifupdown => ifupdown2/ifupdown}/template.py | 10 +- {ifupdown => ifupdown2/ifupdown}/utils.py | 129 +- ifupdown2/ifupdownaddons/LinkUtils.py | 2542 +++++++++++++ ifupdown2/ifupdownaddons/__init__.py | 0 .../ifupdownaddons}/cache.py | 9 +- .../ifupdownaddons}/dhclient.py | 11 +- .../ifupdownaddons}/modulebase.py | 152 +- .../ifupdownaddons}/mstpctlutil.py | 61 +- .../ifupdownaddons}/systemutils.py | 22 +- .../ifupdownaddons}/utilsbase.py | 31 +- {scripts => ifupdown2/man}/genmanpages.sh | 0 .../ifup.8.rst => ifupdown2/man/ifdown.8.rst | 0 {man.rst => ifupdown2/man}/ifquery.8.rst | 0 {man.rst => ifupdown2/man}/ifreload.8.rst | 8 +- ifupdown2/man/ifup.8.rst | 183 + .../man}/ifupdown-addons-interfaces.5.rst | 65 +- {man.rst => ifupdown2/man}/interfaces.5.rst | 27 +- {nlmanager => ifupdown2/nlmanager}/README | 0 .../nlmanager}/__init__.py | 0 .../nlmanager}/nllistener.py | 129 +- .../nlmanager}/nlmanager.py | 187 +- .../nlmanager}/nlpacket.py | 1538 ++++++-- {sbin => ifupdown2/sbin}/ifaddon | 4 + ifupdown2/sbin/ifupdown2d | 288 ++ {sbin => ifupdown2/sbin}/start-networking | 5 +- ifupdownaddons/bondutil.py | 426 --- ifupdownaddons/bridgeutils.py | 544 --- ifupdownaddons/ifenslaveutil.py | 421 --- ifupdownaddons/iproute2.py | 1028 ------ man/interfaces.5 | 207 -- sbin/ifupdown | 496 --- sbin/ifupdown2 | 549 --- setup.py | 106 +- stdeb.cfg | 2 - tests/ifstatetest | 56 - tests/test_ifupdown2.py | 22 + 126 files changed, 13587 insertions(+), 10802 deletions(-) delete mode 100644 KNOWN_ISSUES create mode 100644 Makefile create mode 100644 README delete mode 100644 README.md delete mode 100644 README.rst delete mode 100644 TODO delete mode 100644 TODO.addons delete mode 100644 addons/batman_adv.py delete mode 100644 addons/bond.py delete mode 100644 addons/bridge.py delete mode 100644 addons/link.py delete mode 100644 addons/loopback.py delete mode 100644 addons/ppp.py delete mode 100644 addons/tunnel.py delete mode 100644 addons/vxlan.py delete mode 100755 build.sh delete mode 100644 debian/clean delete mode 100644 debian/ifupdown2.preinst delete mode 100644 debian/networking delete mode 100644 debian/python-ifupdown2.postinst delete mode 100644 debian/python-ifupdown2.postrm delete mode 100755 debian/python-ifupdown2.preinst create mode 100644 debian/watch delete mode 100755 docs/examples/batman_adv/configure_batman_adv.sh delete mode 100644 docs/examples/batman_adv/interfaces_batman rename {config => etc/default}/networking (100%) rename {init.d => etc/init.d}/networking (96%) rename {config => etc/network/ifupdown2}/addons.conf (81%) rename {config => etc/network/ifupdown2}/ifupdown2.conf (99%) delete mode 100644 ifupdown/__init__.py delete mode 100644 ifupdown/exceptions.py delete mode 100644 ifupdown/ifupdownconfig.py delete mode 100644 ifupdown/netlink.py delete mode 100644 ifupdown/rtnetlink.py delete mode 100644 ifupdown/rtnetlink_api.py create mode 100644 ifupdown2/__init__.py create mode 100755 ifupdown2/__main__.py rename {addons => ifupdown2/addons}/__init__.py (100%) rename {addons => ifupdown2/addons}/address.py (62%) rename {addons => ifupdown2/addons}/addressvirtual.py (86%) create mode 100644 ifupdown2/addons/bond.py create mode 100644 ifupdown2/addons/bridge.py rename {addons => ifupdown2/addons}/bridgevlan.py (75%) rename {addons => ifupdown2/addons}/dhcp.py (91%) rename {addons => ifupdown2/addons}/ethtool.py (64%) create mode 100644 ifupdown2/addons/link.py rename {addons => ifupdown2/addons}/mstpctl.py (88%) rename {addons => ifupdown2/addons}/usercmds.py (92%) rename {addons => ifupdown2/addons}/vlan.py (73%) rename {addons => ifupdown2/addons}/vrf.py (85%) rename {addons => ifupdown2/addons}/vrrpd.py (87%) create mode 100644 ifupdown2/addons/vxlan.py rename {completion => ifupdown2/completion}/ifup (100%) rename {ifupdownaddons => ifupdown2/ifupdown}/__init__.py (100%) create mode 100644 ifupdown2/ifupdown/argv.py create mode 100644 ifupdown2/ifupdown/config.py create mode 100644 ifupdown2/ifupdown/exceptions.py rename {ifupdown => ifupdown2/ifupdown}/graph.py (92%) rename {ifupdown => ifupdown2/ifupdown}/iface.py (95%) rename {ifupdown => ifupdown2/ifupdown}/iff.py (94%) rename {ifupdown => ifupdown2/ifupdown}/ifupdownbase.py (76%) rename {packages/ifupdown2 => ifupdown2}/ifupdown/ifupdownconfig.py (58%) rename {ifupdown => ifupdown2/ifupdown}/ifupdownflags.py (81%) rename {ifupdown => ifupdown2/ifupdown}/ifupdownmain.py (84%) create mode 100644 ifupdown2/ifupdown/log.py create mode 100644 ifupdown2/ifupdown/main.py create mode 100644 ifupdown2/ifupdown/netlink.py rename {ifupdown => ifupdown2/ifupdown}/networkinterfaces.py (97%) rename {ifupdown => ifupdown2/ifupdown}/policymanager.py (82%) rename {ifupdown => ifupdown2/ifupdown}/scheduler.py (96%) rename {ifupdown => ifupdown2/ifupdown}/statemanager.py (93%) rename {ifupdown => ifupdown2/ifupdown}/template.py (92%) rename {ifupdown => ifupdown2/ifupdown}/utils.py (73%) create mode 100644 ifupdown2/ifupdownaddons/LinkUtils.py create mode 100644 ifupdown2/ifupdownaddons/__init__.py rename {ifupdownaddons => ifupdown2/ifupdownaddons}/cache.py (95%) rename {ifupdownaddons => ifupdown2/ifupdownaddons}/dhclient.py (93%) rename {ifupdownaddons => ifupdown2/ifupdownaddons}/modulebase.py (72%) rename {ifupdownaddons => ifupdown2/ifupdownaddons}/mstpctlutil.py (83%) rename {ifupdownaddons => ifupdown2/ifupdownaddons}/systemutils.py (65%) rename {ifupdownaddons => ifupdown2/ifupdownaddons}/utilsbase.py (69%) rename {scripts => ifupdown2/man}/genmanpages.sh (100%) rename man.rst/ifup.8.rst => ifupdown2/man/ifdown.8.rst (100%) rename {man.rst => ifupdown2/man}/ifquery.8.rst (100%) rename {man.rst => ifupdown2/man}/ifreload.8.rst (91%) create mode 100644 ifupdown2/man/ifup.8.rst rename {man.rst => ifupdown2/man}/ifupdown-addons-interfaces.5.rst (96%) rename {man.rst => ifupdown2/man}/interfaces.5.rst (84%) rename {nlmanager => ifupdown2/nlmanager}/README (100%) rename {nlmanager => ifupdown2/nlmanager}/__init__.py (100%) rename {nlmanager => ifupdown2/nlmanager}/nllistener.py (82%) rename {nlmanager => ifupdown2/nlmanager}/nlmanager.py (83%) rename {nlmanager => ifupdown2/nlmanager}/nlpacket.py (62%) rename {sbin => ifupdown2/sbin}/ifaddon (98%) create mode 100755 ifupdown2/sbin/ifupdown2d rename {sbin => ifupdown2/sbin}/start-networking (97%) delete mode 100644 ifupdownaddons/bondutil.py delete mode 100644 ifupdownaddons/bridgeutils.py delete mode 100644 ifupdownaddons/ifenslaveutil.py delete mode 100644 ifupdownaddons/iproute2.py delete mode 100644 man/interfaces.5 delete mode 100755 sbin/ifupdown delete mode 100755 sbin/ifupdown2 delete mode 100644 stdeb.cfg delete mode 100755 tests/ifstatetest create mode 100644 tests/test_ifupdown2.py diff --git a/KNOWN_ISSUES b/KNOWN_ISSUES deleted file mode 100644 index bcc3a4e..0000000 --- a/KNOWN_ISSUES +++ /dev/null @@ -1,6 +0,0 @@ -- There is a state issue if multiple configuration blocks are present for the same interface in the interfaces file -- `ifquery -r` can give wrong result if dhclient is running + static addresses are configured -- `ifquery -r` status is success for success case and also for cases where there -is no support for query yet -- setup.py has ifupdown listed in data section instead of scripts: This is because default location for scripts is /usr/bin/. And ifupdown default location is /sbin. With newer versions we can specify --install-scripts directory. This needs to be fixed then. -- and more :) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..37b51d1 --- /dev/null +++ b/Makefile @@ -0,0 +1,96 @@ +.PHONY: clean clean-test clean-pyc clean-build docs help +.DEFAULT_GOAL := help + +define BROWSER_PYSCRIPT +import os, webbrowser, sys + +try: + from urllib import pathname2url +except: + from urllib.request import pathname2url + +webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) +endef +export BROWSER_PYSCRIPT + +define PRINT_HELP_PYSCRIPT +import re, sys + +for line in sys.stdin: + match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) + if match: + target, help = match.groups() + print("%-20s %s" % (target, help)) +endef +export PRINT_HELP_PYSCRIPT + +BROWSER := python -c "$$BROWSER_PYSCRIPT" + +help: + @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) + +clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts + +clean-build: ## remove build artifacts + rm -fr build/ + rm -fr dist/ + rm -fr .eggs/ + find . -name '*.egg-info' -exec rm -fr {} + + find . -name '*.egg' -exec rm -f {} + + +clean-pyc: ## remove Python file artifacts + find . -name '*.pyc' -exec rm -f {} + + find . -name '*.pyo' -exec rm -f {} + + find . -name '*~' -exec rm -f {} + + find . -name '__pycache__' -exec rm -fr {} + + +clean-test: ## remove test and coverage artifacts + rm -fr .tox/ + rm -f .coverage + rm -fr htmlcov/ + rm -fr .pytest_cache + +lint: ## check style with flake8 + flake8 ifupdown2 tests + +test: ## run tests quickly with the default Python + py.test + +test-all: ## run tests on every Python version with tox + tox + +coverage: ## check code coverage quickly with the default Python + coverage run --source ifupdown2 -m pytest + coverage report -m + coverage html + $(BROWSER) htmlcov/index.html + +docs: ## generate Sphinx HTML documentation, including API docs + rm -f docs/ifupdown2.rst + rm -f docs/modules.rst + sphinx-apidoc -o docs/ ifupdown2 + $(MAKE) -C docs clean + $(MAKE) -C docs html + $(BROWSER) docs/_build/html/index.html + +servedocs: docs ## compile the docs watching for changes + watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . + +testpy: ## package and upload to testpy + python setup.py sdist bdist_wheel + twine upload --repository-url https://test.pypi.org/legacy/ dist/* + @echo "Install ifupdown2 from testpy 'pip install --index-url https://test.pypi.org/simple/ ifupdown2'" + +release: dist ## package and upload a release + twine upload dist/* + +dist: clean ## builds source and wheel package + python setup.py sdist + python setup.py bdist_wheel + ls -l dist + +install: clean ## install the package to the active Python's site-packages + python setup.py install + +deb: clean ## create a debian package (.deb) + debuild -b -rfakeroot -us -uc diff --git a/README b/README new file mode 100644 index 0000000..b9d2815 --- /dev/null +++ b/README @@ -0,0 +1,162 @@ +========= +ifupdown2 +========= + +Linux Interface Network Manager + +* Free software: GNU General Public License v2 + +============ +Installation +============ + +As of today (early june 2018), the preferred method to install ifupdown2, is by +building the source code (as it will always install the most recent stable +release). See `Installing latest stable release from sources`_ chapter. + +Installing latest stable release from sources +--------------------------------------------- + +The sources for ifupdown2 can be downloaded from the `Github repo`_. + +You can either clone the public repository: + +.. code-block:: console + + $ git clone git://github.com/CumulusNetworks/ifupdown2 + +Or download the `tarball`_: + +.. code-block:: console + + $ curl -OL https://github.com/CumulusNetworks/ifupdown2/tarball/master + +Once you have a copy of the source, you should build a deb-package and install it + +.. code-block:: console + + $ cd ifupdown2 && make deb + +The generated deb should be in the root directory (``../ifupdown2_2.0.0_all.deb``) + +.. code-block:: console + + $ dpkg -i ../ifupdown2_2.0.0_all.deb + +We don't recommend using ``setup.py install`` directly, as it's still missing systemd/init.d scripts. +This capability should be added in the near future. + +You might need to manually download dependencies. Mandatory dependencies: + +.. code-block:: console + + $ apt-get install dh-systemd python-all python-docutils rst2man iproute2 python-ipaddr python-argcomplete + +Suggested dependencies: + +.. code-block:: console + + $ apt-get install ethtool bridge-utils python-gvgen python-mako + +.. _Github repo: https://github.com/CumulusNetworks/ifupdown2 +.. _tarball: https://github.com/CumulusNetworks/ifupdown2/tarball/master + + +============ +Contributing +============ + +Contributions are welcome, and they are greatly appreciated! Every little bit +helps, and credit will always be given. + +You can contribute in many ways: + +Types of Contributions +---------------------- + +Report Bugs +~~~~~~~~~~~ + +Report bugs at https://github.com/CumulusNetworks/ifupdown2/issues. + +If you are reporting a bug, please include: + +* Your operating system name and version (``uname -a``). +* Any details about your setup that might be helpful in troubleshooting. +* Content of configuration files such as ``/etc/network/interfaces`` +* Detailed steps to reproduce the bug. +* Debug output of the ifupdown2 command (see ``--debug`` option) + +Write Documentation +~~~~~~~~~~~~~~~~~~~ + +ifupdown2 could always use more documentation, whether as part of the +official ifupdown2 docs, in docstrings, or even on the web in blog posts, +articles, and such. + +Submit Feedback +~~~~~~~~~~~~~~~ + +The best way to send feedback is to file an issue at https://github.com/CumulusNetworks/ifupdown2/issues. + +If you are proposing a feature: + +* Explain in detail how it would work. +* Keep the scope as narrow as possible, to make it easier to implement. + +======= +Credits +======= + +Development Lead +---------------- + +* Roopa Prabhu +* Julien Fortin + +Contributors +------------ + +* Nikhil Gajendrakumar +* Maximilian Wilhelm +* Sven Auhagen +* skorpy +* Sam Tannous +* Wilson Kok +* John Berezovik +* Daniel Walton +* Anuradha Karuppiah +* Balakrishnan Raman +* Scott Emery +* Dave Olson +* David Ahern +* Jonathan Toppins <> +* Nolan Leake +* Sergey Sudakovich +* Andy Gospodarek <> +* Satish Ashok +* Scott Laffer +* Vidya Sagar Ravipati +* Marek Grzybowski +* Gaudenz Steinlin +* Nigel Kukard +* Jeffrey +* kokel + +Why not you too? :) + + +======= +History +======= + +See changelog here: https://github.com/CumulusNetworks/ifupdown2/blob/master/debian/changelog + + +Credits +------- + +This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template. + +.. _Cookiecutter: https://github.com/audreyr/cookiecutter +.. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage diff --git a/README.md b/README.md deleted file mode 100644 index ad44a4f..0000000 --- a/README.md +++ /dev/null @@ -1,75 +0,0 @@ -ifupdown2 -========= - -ifupdown2 is a alternate implementation of debian's network interface manager -ifupdown. - -ifupdown2 provides the required infrastructure to parse, schedule and -manage interface configuration. Also provides default python addon modules -for network interface configuration. - - -Note: Previously ifupdown2 came as two packages: python-ifupdown2 and -python-ifupdown2-addons. python-ifupdown2-addons contents are now merged into -python-ifupdown2 package (python-ifupdown2-addons package is hence deprecated). - -Install -======= - -## dependencies - -To install the mandatory dependencies please execute the following command: -``` -apt-get install python-ipaddr -apt-get install python-argcomplete -``` - -More recommended dependencies: -``` -apt-get install bridge-utils -apt-get install ethtool -``` - -## debian & Ubuntu - -To get our lastest version that is available on the debian repositories for your current OS just type -`apt-get install ifupdown2` - -## Ubuntu users (anything bellow version Artful) - -We highly recommend that you build your own debs or WGET a deb from the debian repo as we have trouble backporting our latest fixes and features in Zesty, Xenial and bellow. - -``` -wget http://ftp.us.debian.org/debian/pool/main/i/ifupdown2/ifupdown2_1.0~git20170314-1_all.deb -dpkg -i ifupdown2_1.0~git20170314-1_all.deb -``` - -## build your own deb - -If ever the repositories for your OS version doesn't include latest ifupdown2 you can always build your own deb - -To buid on Ubuntu and debian you'll some extra packages: - -``` -apt-get install build-essential dh-systemd dh-make python-docutils -``` - -Then run the following commands: - -``` -dpkg-buildpackage -us -uc -d -``` - -On the master branch this simple command should produce a `.deb` file that you can install using `dpkg -i` - -On the `debian-prep2` branch, you'll need to run: - -``` -cd /path/to/ifupdown2/source/folder -git archive --format=tar HEAD | xz -9 -c >../ifupdown2_1.0~git20170314.orig.tar.xz && dpkg-buildpackage -us -uc -d -``` - -Note that the name of the tar archive needs to match the latest version present in the changelog. We usually use the date of the upload to tag a new version. In this previous example it was `20170314` - -If you are experiencing any issues please feel free to open a new issue. - diff --git a/README.rst b/README.rst deleted file mode 100644 index e4bfd54..0000000 --- a/README.rst +++ /dev/null @@ -1,73 +0,0 @@ -python-ifupdown2 -================ - -This package is a replacement for the debian ifupdown package. -It is ifupdown re-written in python. It maintains the original ifupdown -pluggable architecture and extends it further. - -The python-ifupdown2 package provides the infrastructure for -parsing /etc/network/interfaces file, loading, scheduling and state -management of interfaces. - -It dynamically loads python modules from /usr/share/ifupdownaddons. -To remain compatible with other packages that depend on ifupdown, -it also executes scripts under /etc/network/. -To make the transition smoother, a python module under -/usr/share/ifupdownaddons will override a script by the same name under -/etc/network/. - -It publishes an interface object which is passed to all loadble python -modules. For more details on adding a addon module, see the section on -adding python modules. - - -pluggable python modules: -========================= -Unlike original ifupdown, all interface configuration is moved to external -python modules. That includes inet, inet6 and dhcp configurations. - -A set of default modules are included in the package. - -python-ifupdown2 expects a few things from the pluggable modules: -- the module should implement a class by the same name -- the interface object (class iface) and the operation to be performed is - passed to the modules -- the python addon class should provide a few methods: - - run() : method to configure the interface. - - get_ops() : must return a list of operations it supports. - eg: 'pre-up', 'post-down' - - get_dependent_ifacenames() : must return a list of interfaces the - interface is dependent on. This is used to build the dependency list - for sorting and executing interfaces in dependency order. - - if the module supports -r option to ifquery, ie ability to construct the - ifaceobj from running state, it can optionally implement the - get_dependent_ifacenames_running() method, to return the list of - dependent interfaces derived from running state of the interface. - This is different from get_dependent_ifacenames() where the dependent - interfaces are derived from the interfaces config file (provided by the - user). - -Example: Address handling module /usr/share/ifupdownaddons/address.py - - -build -===== -- get source - -- install build dependencies: - apt-get install python-stdeb python-docutils - -- cd && ./build.sh - - (generates python-ifupdown2-.deb) - -install -======= - -- remove existing ifupdown package - dpkg -r ifupdown - -- install python-ifupdown2 using `dpkg -i` - -- or install from deb - dpkg -i python-ifupdown2-.deb diff --git a/TODO b/TODO deleted file mode 100644 index aff9cd5..0000000 --- a/TODO +++ /dev/null @@ -1,36 +0,0 @@ -TODO: -==== -- support old ifupdown state file /run/network/ifstate. Because some app's seem to use it -- support for debian ifupdown methods: tunnel, v4tunnel, 6to4, ppp, wvdial, ipv4ll -- support for debian ifupdown ipv6 auto method -- support for debian ifupdown CAN address family -- Compat : support for LOGICAL interfaces -- dry-run improvement: It skips the cache completely. Which means It tells you the commands it would execute if the system is clean. Its not smart enought to say what it will really execute given the state of the system - -- Ifquery does not report link status, mainly because it reports only in terms of /etc/network/interfaces attributes. Plan to fix that -- more Documentation -- Priorities for addon modules -- have ability to also run uninstall on interfaces that dont have any config -- ifup hotplug support (basically needs some testing and fixing broken things) -- -q quiet option -- support for /etc/networking.defaults -- implement legacy ifupdown mapping feature -- support for the following ifupdown options: - -o OPTION=VALUE set OPTION to VALUE as though it were in - /etc/network/interfaces - --no-mappings don't run any mappings - --no-scripts don't run any hook scripts -- parallel implementation -- Test all original ifupdown options for compatibility -- Test with ifupdown-extra, ifmetric, ifupdown-scripts-zg2 -- export other environment variables to bash scripts (for backward compatibility): - IFACE physical name of the interface being processed - LOGICAL logical name of the interface being processed - ADDRFAM address family of the interface - METHOD method of the interface (e.g., static) - MODE start if run from ifup, stop if run from ifdown - PHASE as per MODE, but with finer granularity, distinguishing the pre- - up, post-up, pre-down and post-down phases. - VERBOSITY indicates whether --verbose was used; set to 1 if so, 0 if not. - PATH the command search path: /usr/local/sbin:/usr/local/bin:‐ - /usr/sbin:/usr/bin:/sbin:/bin diff --git a/TODO.addons b/TODO.addons deleted file mode 100644 index 5436284..0000000 --- a/TODO.addons +++ /dev/null @@ -1,13 +0,0 @@ -TODO: -==== -- run python code guideline checker -- more code documentation -- move all cache handling to decorators in ifupdownaddons package -- input validation (present in some cases not all) -- support the vlan0004 style vlans -- ifquery coverage. currently it is at 80%. -- vxlan module -- fix and release ifaddon utility to manage module priorities -- Deep compare in query for address module (does not compare address attributes like scope) -- Maybe a pure netlink backend -- improve caching diff --git a/addons/batman_adv.py b/addons/batman_adv.py deleted file mode 100644 index 0fda862..0000000 --- a/addons/batman_adv.py +++ /dev/null @@ -1,344 +0,0 @@ -#!/usr/bin/python -# -# Copyright 2016-2017 Maximilian Wilhelm -# Author: Maximilian Wilhelm, max@sdn.clinic -# - -from ifupdown.iface import * -from ifupdownaddons.modulebase import moduleBase -from ifupdownaddons.iproute2 import iproute2 -from ifupdown.netlink import netlink -import ifupdown.ifupdownflags as ifupdownflags -import logging -import re -import subprocess - - -class batman_adv (moduleBase): - """ ifupdown2 addon module to configure B.A.T.M.A.N. advanced interfaces """ - - _modinfo = { - 'mhelp' : 'batman_adv module configures B.A.T.M.A.N. advanced interfaces.' + - 'Every B.A.T.M.A.N. advanced interface needs at least on ethernet ' + - 'interface to be creatable. You can specify a space separated list' + - 'of interfaces by using the "batma-ifaces" paramater. If this parameter' + - 'is set for an interfaces this module will do the magic.', - - 'attrs' : { - 'batman-ifaces' : { - 'help' : 'Interfaces to be part of this B.A.T.M.A.N. advanced instance', - 'validvals' : [ '' ], - 'required' : True, - }, - - 'batman-ifaces-ignore-regex' : { - 'help' : 'Interfaces to ignore when verifying configuration (regexp)', - 'required' : False, - }, - - 'batman-distributed-arp-table' : { - 'help' : 'B.A.T.M.A.N. distributed ARP table', - 'validvals' : [ 'enabled', 'disabled' ], - 'required' : False, - 'batman-attr' : True, - }, - - 'batman-gw-mode' : { - 'help' : 'B.A.T.M.A.N. gateway mode', - 'validvals' : [ 'off', 'client', 'server' ], - 'required' : False, - 'example' : [ 'batman-gw-mode client' ], - 'batman-attr' : True, - }, - - 'batman-hop-penalty' : { - 'help' : 'B.A.T.M.A.N. hop penalty', - 'validvals' : [ '' ], - 'required' : False, - 'batman-attr' : True, - }, - - 'batman-multicast-mode' : { - 'help' : 'B.A.T.M.A.N. multicast mode', - 'validvals' : [ 'enabled', 'disabled' ], - 'required' : False, - 'batman-attr' : True, - }, - } - } - - _batman_attrs = { - } - - - def __init__ (self, *args, **kargs): - moduleBase.__init__ (self, *args, **kargs) - self.ipcmd = None - - for longname, entry in self._modinfo['attrs'].items (): - if entry.get ('batman-attr', False) == False: - continue - - attr = longname.replace ("batman-", "") - self._batman_attrs[attr] = { - 'filename' : attr.replace ("-", "_"), - } - - - def _is_batman_device (self, ifaceobj): - if ifaceobj.get_attr_value_first ('batman-ifaces'): - return True - return False - - - def _get_batman_ifaces (self, ifaceobj ): - batman_ifaces = ifaceobj.get_attr_value_first ('batman-ifaces') - if batman_ifaces: - return sorted (batman_ifaces.split ()) - return None - - - def _get_batman_ifaces_ignore_regex (self, ifaceobj): - ifaces_ignore_regex = ifaceobj.get_attr_value_first ('batman-ifaces-ignore-regex') - if ifaces_ignore_regex: - return re.compile (r"%s" % ifaces_ignore_regex) - return None - - - def _get_batman_attr (self, ifaceobj, attr): - if attr not in self._batman_attrs: - raise ValueError ("_get_batman_attr: Invalid or unsupported B.A.T.M.A.N. adv. attribute: %s" % attr) - - value = ifaceobj.get_attr_value_first ('batman-%s' % attr) - if value: - return value - - return None - - - def _read_current_batman_attr (self, ifaceobj, attr): - if attr not in self._batman_attrs: - raise ValueError ("_read_current_batman_attr: Invalid or unsupported B.A.T.M.A.N. adv. attribute: %s" % attr) - - attr_file_name = self._batman_attrs[attr]['filename'] - attr_file_path = "/sys/class/net/%s/mesh/%s" % (ifaceobj.name, attr_file_name) - try: - with open (attr_file_path, "r") as fh: - return fh.readline ().strip () - except IOError as i: - raise Exception ("_read_current_batman_attr (%s) %s" % (attr, i)) - except ValueError: - raise Exception ("_read_current_batman_attr: Integer value expected, got: %s" % value) - - - def _set_batman_attr (self, ifaceobj, attr, value): - if attr not in self._batman_attrs: - raise ValueError ("_set_batman_attr: Invalid or unsupported B.A.T.M.A.N. adv. attribute: %s" % attr) - - attr_file_name = self._batman_attrs[attr]['filename'] - attr_file_path = "/sys/class/net/%s/mesh/%s" % (ifaceobj.name, attr_file_name) - try: - with open (attr_file_path, "w") as fh: - fh.write ("%s\n" % value) - except IOError as i: - raise Exception ("_set_batman_attr (%s): %s" % (attr, i)) - - - def _batctl_if (self, bat_iface, mesh_iface, op): - if op not in [ 'add', 'del' ]: - raise Exception ("_batctl_if() called with invalid \"op\" value: %s" % op) - - try: - self.logger.debug ("Running batctl -m %s if %s %s" % (bat_iface, op, mesh_iface)) - batctl_output = subprocess.check_output (["batctl", "-m", bat_iface, "if", op, mesh_iface], stderr = subprocess.STDOUT) - except subprocess.CalledProcessError as c: - raise Exception ("Command \"batctl -m %s if %s %s\" failed: %s" % (bat_iface, op, mesh_iface, c.output)) - except Exception as e: - raise Exception ("_batctl_if: %s" % e) - - - def _find_member_ifaces (self, ifaceobj, ignore = True): - members = [] - iface_ignore_re = self._get_batman_ifaces_ignore_regex (ifaceobj) - batctl_fh = subprocess.Popen (["batctl", "-m", ifaceobj.name, "if"], bufsize = 4194304, stdout = subprocess.PIPE).stdout - for line in batctl_fh.readlines (): - iface = line.split (':')[0] - if iface_ignore_re and iface_ignore_re.match (iface) and ignore: - continue - - members.append (iface) - - return sorted (members) - - - def get_dependent_ifacenames (self, ifaceobj, ifaceobjs_all=None): - if not self._is_batman_device (ifaceobj): - return None - - ifaceobj.link_kind |= ifaceLinkKind.BATMAN_ADV - batman_ifaces = self._get_batman_ifaces (ifaceobj) - if batman_ifaces: - return batman_ifaces - - return [None] - - - def _up (self, ifaceobj): - if self._get_batman_ifaces (ifaceobj) == None: - raise Exception ('could not determine batman interfacaes') - - # Verify existance of batman interfaces (should be present already) - batman_ifaces = [] - for iface in self._get_batman_ifaces (ifaceobj): - if not self.ipcmd.link_exists (iface): - self.logger.warn ('batman iface %s not present' % iface) - continue - - batman_ifaces.append (iface) - - if len (batman_ifaces) == 0: - raise Exception ("None of the configured batman interfaces are available!") - - if_ignore_re = self._get_batman_ifaces_ignore_regex (ifaceobj) - # Is the batman main interface already present? - if self.ipcmd.link_exists (ifaceobj.name): - # Verify which member interfaces are present - members = self._find_member_ifaces (ifaceobj) - for iface in members: - if iface not in batman_ifaces: - self._batctl_if (ifaceobj.name, iface, 'del') - for iface in batman_ifaces: - if iface not in members: - self._batctl_if (ifaceobj.name, iface, 'add') - - # Batman interfaces no present, add member interfaces to create it - else: - for iface in batman_ifaces: - self._batctl_if (ifaceobj.name, iface, 'add') - - # Check/set any B.A.T.M.A.N. adv. set within interface configuration - for attr in self._batman_attrs: - value_cfg = self._get_batman_attr (ifaceobj, attr) - if value_cfg and value_cfg != self._read_current_batman_attr (ifaceobj, attr): - self._set_batman_attr (ifaceobj, attr, value_cfg) - - if ifaceobj.addr_method == 'manual': - netlink.link_set_updown(ifaceobj.name, "up") - - - - def _down (self, ifaceobj): - if not ifupdownflags.flags.PERFMODE and not self.ipcmd.link_exists (ifaceobj.name): - return - - members = self._find_member_ifaces (ifaceobj) - for iface in members: - self._batctl_if (ifaceobj.name, iface, 'del') - - # The main interface will automagically vanish after the last member - # interface has been deleted. - - - def _query_check (self, ifaceobj, ifaceobjcurr): - if not self.ipcmd.link_exists (ifaceobj.name): - return - - batman_ifaces_cfg = self._get_batman_ifaces (ifaceobj) - batman_ifaces_real = self._find_member_ifaces (ifaceobj, False) - # Produce list of all current interfaces, tag interfaces ignored by - # regex with () around the iface name. - batman_ifaces_real_tagged = [] - iface_ignore_re_str = ifaceobj.get_attr_value_first ('batman-ifaces-ignore-regex') - iface_ignore_re = self._get_batman_ifaces_ignore_regex (ifaceobj) - - # Assume everything's fine and wait for reality to prove us otherwise - ifaces_ok = 0 - - # Interfaces configured but not active? - for iface in batman_ifaces_cfg: - if iface not in batman_ifaces_real: - ifaces_ok = 1 - - # Interfaces active but not configured (or ignored)? - for iface in batman_ifaces_real: - if iface not in batman_ifaces_cfg: - if iface_ignore_re and iface_ignore_re.match (iface): - batman_ifaces_real_tagged.append ("(%s)" % iface) - continue - ifaces_ok = 1 - else: - batman_ifaces_real_tagged.append (iface) - - # Produce sorted list of active and ignored interfaces - ifaces_str = " ".join (batman_ifaces_real_tagged) - ifaceobjcurr.update_config_with_status ('batman-ifaces', ifaces_str, ifaces_ok) - ifaceobjcurr.update_config_with_status ('batman-ifaces-ignore-regex', iface_ignore_re_str, 0) - - # Check any B.A.T.M.A.N. adv. set within interface configuration - for attr in self._batman_attrs: - value_cfg = self._get_batman_attr (ifaceobj, attr) - value_curr = self._read_current_batman_attr (ifaceobj, attr) - - # Ignore this attribute if its'nt configured for this interface - if not value_cfg: - continue - - value_ok = 0 - if value_cfg != value_curr: - value_ok = 1 - - ifaceobjcurr.update_config_with_status ('batman-%s' % attr, value_curr, value_ok) - - - def _query_running (self, ifaceobjrunning): - if not self.ipcmd.link_exists (ifaceobjrunning.name): - return - - # XXX Now what? - - - _run_ops = {'pre-up' : _up, - 'post-down' : _down, - 'query-checkcurr' : _query_check} -# XXX 'query-running' : _query_running} - - - def get_ops (self): - """ returns list of ops supported by this module """ - return self._run_ops.keys () - - - def _init_command_handlers (self): - if not self.ipcmd: - self.ipcmd = iproute2() - - - def run (self, ifaceobj, operation, query_ifaceobj = None, **extra_args): - """ run B.A.T.M.A.N. configuration on the interface object passed as argument - - Args: - **ifaceobj** (object): iface object - - **operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr', - 'query-running' - Kwargs: - **query_ifaceobj** (object): query check ifaceobject. This is only - valid when op is 'query-checkcurr'. It is an object same as - ifaceobj, but contains running attribute values and its config - status. The modules can use it to return queried running state - of interfaces. status is success if the running state is same - as user required state in ifaceobj. error otherwise. - """ - op_handler = self._run_ops.get (operation) - if not op_handler: - return - - if (operation != 'query-running' and not self._is_batman_device (ifaceobj)): - return - - self._init_command_handlers () - - if operation == 'query-checkcurr': - op_handler (self, ifaceobj, query_ifaceobj) - else: - op_handler (self, ifaceobj) diff --git a/addons/bond.py b/addons/bond.py deleted file mode 100644 index 0516b6b..0000000 --- a/addons/bond.py +++ /dev/null @@ -1,485 +0,0 @@ -#!/usr/bin/python -# -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. -# Author: Roopa Prabhu, roopa@cumulusnetworks.com -# - -from sets import Set -from ifupdown.iface import * -import ifupdownaddons -from ifupdownaddons.modulebase import moduleBase -from ifupdownaddons.bondutil import bondutil -from ifupdownaddons.iproute2 import iproute2 -from ifupdown.netlink import netlink -import ifupdown.policymanager as policymanager -import ifupdown.ifupdownflags as ifupdownflags -from ifupdown.utils import utils - -class bond(moduleBase): - """ ifupdown2 addon module to configure bond interfaces """ - - overrides_ifupdown_scripts = ['ifenslave', ] - - _modinfo = { 'mhelp' : 'bond configuration module', - 'attrs' : { - 'bond-use-carrier': - {'help' : 'bond use carrier', - 'validvals' : ['yes', 'no', '0', '1'], - 'default' : 'yes', - 'example': ['bond-use-carrier yes']}, - 'bond-num-grat-arp': - {'help' : 'bond use carrier', - 'validrange' : ['0', '255'], - 'default' : '1', - 'example' : ['bond-num-grat-arp 1']}, - 'bond-num-unsol-na' : - {'help' : 'bond slave devices', - 'validrange' : ['0', '255'], - 'default' : '1', - 'example' : ['bond-num-unsol-na 1']}, - 'bond-xmit-hash-policy' : - {'help' : 'bond slave devices', - 'validvals' : ['layer2', 'layer3+4', 'layer2+3'], - 'default' : 'layer2', - 'example' : ['bond-xmit-hash-policy layer2']}, - 'bond-miimon' : - {'help' : 'bond miimon', - 'validrange' : ['0', '255'], - 'default' : '0', - 'example' : ['bond-miimon 0']}, - 'bond-mode' : - {'help': 'bond mode', - 'validvals': ['0', 'balance-rr', - '1', 'active-backup', - '2', 'balance-xor', - '3', 'broadcast', - '4', '802.3ad', - '5', 'balance-tlb', - '6', 'balance-alb'], - 'default': 'balance-rr', - 'example': ['bond-mode 802.3ad']}, - 'bond-lacp-rate': - {'help' : 'bond lacp rate', - 'validvals' : ['0', '1'], - 'default' : '0', - 'example' : ['bond-lacp-rate 0']}, - 'bond-min-links': - {'help' : 'bond min links', - 'default' : '0', - 'validrange' : ['0', '255'], - 'example' : ['bond-min-links 0']}, - 'bond-ad-sys-priority': - {'help' : '802.3ad system priority', - 'default' : '65535', - 'validrange' : ['0', '65535'], - 'example' : ['bond-ad-sys-priority 65535'], - 'deprecated' : True, - 'new-attribute' : 'bond-ad-actor-sys-prio'}, - 'bond-ad-actor-sys-prio': - {'help' : '802.3ad system priority', - 'default' : '65535', - 'validrange' : ['0', '65535'], - 'example' : ['bond-ad-actor-sys-prio 65535']}, - 'bond-ad-sys-mac-addr': - {'help' : '802.3ad system mac address', - 'default' : '00:00:00:00:00:00', - 'validvals': ['', ], - 'example' : ['bond-ad-sys-mac-addr 00:00:00:00:00:00'], - 'deprecated' : True, - 'new-attribute' : 'bond-ad-actor-system'}, - 'bond-ad-actor-system': - {'help' : '802.3ad system mac address', - 'default' : '00:00:00:00:00:00', - 'validvals': ['', ], - 'example' : ['bond-ad-actor-system 00:00:00:00:00:00'],}, - 'bond-lacp-bypass-allow': - {'help' : 'allow lacp bypass', - 'validvals' : ['yes', 'no', '0', '1'], - 'default' : 'no', - 'example' : ['bond-lacp-bypass-allow no']}, - 'bond-slaves' : - {'help' : 'bond slaves', - 'required' : True, - 'multivalue' : True, - 'validvals': [''], - 'example' : ['bond-slaves swp1 swp2', - 'bond-slaves glob swp1-2', - 'bond-slaves regex (swp[1|2)'], - 'aliases': ['bond-ports']}, - 'bond-updelay' : - {'help' : 'bond updelay', - 'default' : '0', - 'validrange' : ['0', '65535'], - 'example' : ['bond-updelay 100']}, - 'bond-downdelay': - {'help' : 'bond downdelay', - 'default' : '0', - 'validrange' : ['0', '65535'], - 'example' : ['bond-downdelay 100']} - }} - - _bond_mode_num = {'0': 'balance-rr', - '1': 'active-backup', - '2': 'balance-xor', - '3': 'broadcast', - '4': '802.3ad', - '5': 'balance-tlb', - '6': 'balance-alb'} - - _bond_mode_string = {'balance-rr': '0', - 'active-backup': '1', - 'balance-xor': '2', - 'broadcast': '3', - '802.3ad': '4', - 'balance-tlb': '5', - 'balance-alb': '6'} - - @staticmethod - def _get_readable_bond_mode(mode): - if mode in bond._bond_mode_num: - return bond._bond_mode_num[mode] - return mode - - @staticmethod - def _get_num_bond_mode(mode): - if mode in bond._bond_mode_string: - return bond._bond_mode_string[mode] - return mode - - def __init__(self, *args, **kargs): - ifupdownaddons.modulebase.moduleBase.__init__(self, *args, **kargs) - self.ipcmd = None - self.bondcmd = None - - def get_bond_slaves(self, ifaceobj): - slaves = ifaceobj.get_attr_value_first('bond-slaves') - if not slaves: - slaves = ifaceobj.get_attr_value_first('bond-ports') - return slaves - - def _is_bond(self, ifaceobj): - if self.get_bond_slaves(ifaceobj): - return True - return False - - def get_dependent_ifacenames(self, ifaceobj, ifacenames_all=None): - """ Returns list of interfaces dependent on ifaceobj """ - - if not self._is_bond(ifaceobj): - return None - slave_list = self.parse_port_list(ifaceobj.name, - self.get_bond_slaves(ifaceobj), - ifacenames_all) - ifaceobj.dependency_type = ifaceDependencyType.MASTER_SLAVE - # Also save a copy for future use - ifaceobj.priv_data = list(slave_list) - if ifaceobj.link_type != ifaceLinkType.LINK_NA: - ifaceobj.link_type = ifaceLinkType.LINK_MASTER - ifaceobj.link_kind |= ifaceLinkKind.BOND - ifaceobj.role |= ifaceRole.MASTER - - return slave_list - - def get_dependent_ifacenames_running(self, ifaceobj): - self._init_command_handlers() - return self.bondcmd.get_slaves(ifaceobj.name) - - def _get_slave_list(self, ifaceobj): - """ Returns slave list present in ifaceobj config """ - - # If priv data already has slave list use that first. - if ifaceobj.priv_data: - return ifaceobj.priv_data - slaves = self.get_bond_slaves(ifaceobj) - if slaves: - return self.parse_port_list(ifaceobj.name, slaves) - else: - return None - - def _is_clag_bond(self, ifaceobj): - if self.get_bond_slaves(ifaceobj): - attrval = ifaceobj.get_attr_value_first('clag-id') - if attrval and attrval != '0': - return True - return False - - def fetch_attr(self, ifaceobj, attrname): - attrval = ifaceobj.get_attr_value_first(attrname) - # grab the defaults from the policy file in case the - # user did not specify something. - policy_default_val = policymanager.policymanager_api.\ - get_iface_default(module_name=self.__class__.__name__, - ifname=ifaceobj.name, - attr=attrname) - if attrval: - if attrname == 'bond-mode': - attrval = bond._get_readable_bond_mode(attrval) - if attrval == '802.3ad': - dattrname = 'bond-min-links' - min_links = ifaceobj.get_attr_value_first(dattrname) - if not min_links: - min_links = self.bondcmd.get_min_links(ifaceobj.name) - if min_links == '0': - self.logger.warn('%s: attribute %s' - %(ifaceobj.name, dattrname) + - ' is set to \'0\'') - elif policy_default_val: - return policy_default_val - return attrval - - def _apply_master_settings(self, ifaceobj): - have_attrs_to_set = 0 - linkup = False - bondcmd_attrmap = OrderedDict([('bond-mode' , 'mode'), - ('bond-miimon' , 'miimon'), - ('bond-use-carrier', 'use_carrier'), - ('bond-lacp-rate' , 'lacp_rate'), - ('bond-xmit-hash-policy' , 'xmit_hash_policy'), - ('bond-min-links' , 'min_links'), - ('bond-num-grat-arp' , 'num_grat_arp'), - ('bond-num-unsol-na' , 'num_unsol_na'), - ('bond-ad-sys-mac-addr' , 'ad_actor_system'), - ('bond-ad-actor-system' , 'ad_actor_system'), - ('bond-ad-sys-priority' , 'ad_actor_sys_prio'), - ('bond-ad-actor-sys-prio' , 'ad_actor_sys_prio'), - ('bond-lacp-bypass-allow', 'lacp_bypass'), - ('bond-updelay', 'updelay'), - ('bond-downdelay', 'downdelay')]) - linkup = self.ipcmd.is_link_up(ifaceobj.name) - try: - # order of attributes set matters for bond, so - # construct the list sequentially - attrstoset = OrderedDict() - for k, dstk in bondcmd_attrmap.items(): - v = self.fetch_attr(ifaceobj, k) - if v: - attrstoset[dstk] = v - if not attrstoset: - return - - # support yes/no attrs - utils.support_yesno_attrs(attrstoset, ['use_carrier', 'lacp_bypass']) - - have_attrs_to_set = 1 - self.bondcmd.set_attrs(ifaceobj.name, attrstoset, - self.ipcmd.link_down if linkup else None) - except: - raise - finally: - if have_attrs_to_set and linkup: - netlink.link_set_updown(ifaceobj.name, 'up') - - def _add_slaves(self, ifaceobj): - runningslaves = [] - - slaves = self._get_slave_list(ifaceobj) - if not slaves: - self.logger.debug('%s: no slaves found' %ifaceobj.name) - return - - if not ifupdownflags.flags.PERFMODE: - runningslaves = self.bondcmd.get_slaves(ifaceobj.name); - - clag_bond = self._is_clag_bond(ifaceobj) - - for slave in Set(slaves).difference(Set(runningslaves)): - if (not ifupdownflags.flags.PERFMODE and - not self.ipcmd.link_exists(slave)): - self.log_error('%s: skipping slave %s, does not exist' - %(ifaceobj.name, slave), ifaceobj, - raise_error=False) - continue - link_up = False - if self.ipcmd.is_link_up(slave): - netlink.link_set_updown(slave, "down") - link_up = True - # If clag bond place the slave in a protodown state; clagd - # will protoup it when it is ready - if clag_bond: - try: - netlink.link_set_protodown(slave, "on") - except Exception, e: - self.logger.error('%s: %s' % (ifaceobj.name, str(e))) - netlink.link_set_master(slave, ifaceobj.name) - if link_up or ifaceobj.link_type != ifaceLinkType.LINK_NA: - try: - netlink.link_set_updown(slave, "up") - except Exception, e: - self.logger.debug('%s: %s' % (ifaceobj.name, str(e))) - pass - - if runningslaves: - for s in runningslaves: - if s not in slaves: - self.bondcmd.remove_slave(ifaceobj.name, s) - if clag_bond: - try: - netlink.link_set_protodown(s, "off") - except Exception, e: - self.logger.error('%s: %s' % (ifaceobj.name, str(e))) - - def _up(self, ifaceobj): - try: - if not self.ipcmd.link_exists(ifaceobj.name): - self.bondcmd.create_bond(ifaceobj.name) - self._apply_master_settings(ifaceobj) - self._add_slaves(ifaceobj) - except Exception, e: - self.log_error(str(e), ifaceobj) - - def _down(self, ifaceobj): - try: - self.bondcmd.delete_bond(ifaceobj.name) - except Exception, e: - self.log_warn(str(e)) - - def _query_check(self, ifaceobj, ifaceobjcurr): - slaves = None - - if not self.bondcmd.bond_exists(ifaceobj.name): - self.logger.debug('bond iface %s' %ifaceobj.name + - ' does not exist') - return - - ifaceattrs = self.dict_key_subset(ifaceobj.config, - self.get_mod_attrs()) - if not ifaceattrs: return - runningattrs = self._query_running_attrs(ifaceobj.name) - - # support yes/no attributes - utils.support_yesno_attrs(runningattrs, ['bond-use-carrier', - 'bond-lacp-bypass-allow'], - ifaceobj=ifaceobj) - - # support for numerical bond-mode - mode = ifaceobj.get_attr_value_first('bond-mode') - if mode in bond._bond_mode_num: - if 'bond-mode' in runningattrs: - runningattrs['bond-mode'] = bond._get_num_bond_mode(runningattrs['bond-mode']) - - bond_slaves = True - for k in ifaceattrs: - v = ifaceobj.get_attr_value_first(k) - if not v: - continue - if k == 'bond-slaves': - slaves = self._get_slave_list(ifaceobj) - continue - elif k == 'bond-ports': - bond_slaves = False - slaves = self._get_slave_list(ifaceobj) - continue - rv = runningattrs.get(k) - if not rv: - ifaceobjcurr.update_config_with_status(k, 'None', 1) - else: - ifaceobjcurr.update_config_with_status(k, rv, - 1 if v != rv else 0) - runningslaves = runningattrs.get('bond-slaves') - if not slaves and not runningslaves: - return - retslave = 1 - if slaves and runningslaves: - if slaves and runningslaves: - difference = set(slaves).symmetric_difference(runningslaves) - if not difference: - retslave = 0 - # we want to display the same bond-slaves list as provided - # in the interfaces file but if this list contains regexes or - # globs, for now, we won't try to change it. - if 'regex' in slaves or 'glob' in slaves: - slaves = runningslaves - else: - ordered = [] - for i in range(0, len(slaves)): - if slaves[i] in runningslaves: - ordered.append(slaves[i]) - slaves = ordered - ifaceobjcurr.update_config_with_status('bond-slaves' if bond_slaves else 'bond-ports', - ' '.join(slaves) - if slaves else 'None', retslave) - - def _query_running_attrs(self, bondname): - bondattrs = {'bond-mode' : - self.bondcmd.get_mode(bondname), - 'bond-miimon' : - self.bondcmd.get_miimon(bondname), - 'bond-use-carrier' : - self.bondcmd.get_use_carrier(bondname), - 'bond-lacp-rate' : - self.bondcmd.get_lacp_rate(bondname), - 'bond-min-links' : - self.bondcmd.get_min_links(bondname), - 'bond-ad-actor-system' : - self.bondcmd.get_ad_actor_system(bondname), - 'bond-ad-actor-sys-prio' : - self.bondcmd.get_ad_actor_sys_prio(bondname), - 'bond-xmit-hash-policy' : - self.bondcmd.get_xmit_hash_policy(bondname), - 'bond-lacp-bypass-allow' : - self.bondcmd.get_lacp_bypass_allow(bondname), - 'bond-num-unsol-na' : - self.bondcmd.get_num_unsol_na(bondname), - 'bond-num-grat-arp' : - self.bondcmd.get_num_grat_arp(bondname), - 'bond-updelay' : - self.bondcmd.get_updelay(bondname), - 'bond-downdelay' : - self.bondcmd.get_downdelay(bondname)} - slaves = self.bondcmd.get_slaves(bondname) - if slaves: - bondattrs['bond-slaves'] = slaves - return bondattrs - - def _query_running(self, ifaceobjrunning): - if not self.bondcmd.bond_exists(ifaceobjrunning.name): - return - bondattrs = self._query_running_attrs(ifaceobjrunning.name) - if bondattrs.get('bond-slaves'): - bondattrs['bond-slaves'] = ' '.join(bondattrs.get('bond-slaves')) - [ifaceobjrunning.update_config(k, v) - for k, v in bondattrs.items() - if v and v != self.get_mod_subattr(k, 'default')] - - _run_ops = {'pre-up' : _up, - 'post-down' : _down, - 'query-running' : _query_running, - 'query-checkcurr' : _query_check} - - def get_ops(self): - """ returns list of ops supported by this module """ - return self._run_ops.keys() - - def _init_command_handlers(self): - if not self.ipcmd: - self.ipcmd = iproute2() - if not self.bondcmd: - self.bondcmd = bondutil() - - def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): - """ run bond configuration on the interface object passed as argument - - Args: - **ifaceobj** (object): iface object - - **operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr', - 'query-running' - - Kwargs: - **query_ifaceobj** (object): query check ifaceobject. This is only - valid when op is 'query-checkcurr'. It is an object same as - ifaceobj, but contains running attribute values and its config - status. The modules can use it to return queried running state - of interfaces. status is success if the running state is same - as user required state in ifaceobj. error otherwise. - """ - op_handler = self._run_ops.get(operation) - if not op_handler: - return - if operation != 'query-running' and not self._is_bond(ifaceobj): - return - self._init_command_handlers() - if operation == 'query-checkcurr': - op_handler(self, ifaceobj, query_ifaceobj) - else: - op_handler(self, ifaceobj) diff --git a/addons/bridge.py b/addons/bridge.py deleted file mode 100644 index a75795c..0000000 --- a/addons/bridge.py +++ /dev/null @@ -1,2109 +0,0 @@ -#!/usr/bin/python -# -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. -# Author: Roopa Prabhu, roopa@cumulusnetworks.com -# - -from sets import Set -from ifupdown.iface import * -import ifupdown.policymanager as policymanager -from ifupdown.utils import utils -from ifupdownaddons.modulebase import moduleBase -from ifupdownaddons.bridgeutils import brctl -from ifupdownaddons.iproute2 import iproute2 -from collections import Counter -from ifupdown.netlink import netlink -import ifupdown.ifupdownflags as ifupdownflags -import itertools -import re -import time - -class bridgeFlags: - PORT_PROCESSED = 0x1 - PORT_PROCESSED_OVERRIDE = 0x2 - -class bridge(moduleBase): - """ ifupdown2 addon module to configure linux bridges """ - - _modinfo = { 'mhelp' : 'Bridge configuration module. Supports both ' + - 'vlan aware and non vlan aware bridges. For the vlan ' + - 'aware bridge, the port specific attributes must be ' + - 'specified under the port. And for vlan unaware bridge ' + - 'port specific attributes must be specified under the ' + - 'bridge.', - 'attrs' : { - 'bridge-vlan-aware' : - {'help' : 'vlan aware bridge. Setting this ' + - 'attribute to yes enables vlan filtering' + - ' on the bridge', - 'validvals' : ['yes', 'no'], - 'example' : ['bridge-vlan-aware yes/no']}, - 'bridge-ports' : - {'help' : 'bridge ports', - 'multivalue' : True, - 'required' : True, - 'validvals': [''], - 'example' : ['bridge-ports swp1.100 swp2.100 swp3.100', - 'bridge-ports glob swp1-3.100', - 'bridge-ports regex (swp[1|2|3].100)']}, - 'bridge-stp' : - {'help': 'bridge-stp yes/no', - 'example' : ['bridge-stp no'], - 'validvals' : ['yes', 'on', 'off', 'no'], - 'default' : 'no'}, - 'bridge-bridgeprio' : - {'help': 'bridge priority', - 'validrange' : ['0', '65535'], - 'example' : ['bridge-bridgeprio 32768'], - 'default' : '32768'}, - 'bridge-ageing' : - {'help': 'bridge ageing', - 'validrange' : ['0', '65535'], - 'example' : ['bridge-ageing 300'], - 'default' : '300'}, - 'bridge-fd' : - { 'help' : 'bridge forward delay', - 'validrange' : ['0', '255'], - 'example' : ['bridge-fd 15'], - 'default' : '15'}, - 'bridge-gcint' : - # XXX: recheck values - { 'help' : 'bridge garbage collection interval in secs', - 'validrange' : ['0', '255'], - 'example' : ['bridge-gcint 4'], - 'default' : '4', - 'compat' : True, - 'deprecated': True}, - 'bridge-hello' : - { 'help' : 'bridge set hello time', - 'validrange' : ['0', '255'], - 'example' : ['bridge-hello 2'], - 'default' : '2'}, - 'bridge-maxage' : - { 'help' : 'bridge set maxage', - 'validrange' : ['0', '255'], - 'example' : ['bridge-maxage 20'], - 'default' : '20'}, - 'bridge-pathcosts' : - { 'help' : 'bridge set port path costs', - 'validvals': [''], - 'validrange' : ['0', '65535'], - 'example' : ['under the bridge: bridge-pathcosts swp1=100 swp2=100', - 'under the port (recommended): bridge-pathcosts 100'], - 'default' : '100'}, - 'bridge-portprios' : - { 'help' : 'bridge port prios', - 'validvals': [''], - 'validrange' : ['0', '65535'], - 'example' : ['under the bridge: bridge-portprios swp1=32 swp2=32', - 'under the port (recommended): bridge-portprios 32'], - 'default' : '32'}, - 'bridge-mclmc' : - { 'help' : 'set multicast last member count', - 'validrange' : ['0', '255'], - 'example' : ['bridge-mclmc 2'], - 'default' : '2'}, - 'bridge-mcrouter' : - { 'help' : 'set multicast router', - 'validvals' : ['yes', 'no', '0', '1'], - 'default' : 'yes', - 'example' : ['bridge-mcrouter yes']}, - 'bridge-mcsnoop' : - { 'help' : 'set multicast snooping', - 'validvals' : ['yes', 'no', '0', '1'], - 'default' : 'yes', - 'example' : ['bridge-mcsnoop yes']}, - 'bridge-mcsqc' : - { 'help' : 'set multicast startup query count', - 'validrange' : ['0', '255'], - 'default' : '2', - 'example' : ['bridge-mcsqc 2']}, - 'bridge-mcqifaddr' : - { 'help' : 'set multicast query to use ifaddr', - 'validvals' : ['yes', 'no', '0', '1'], - 'default' : 'no', - 'example' : ['bridge-mcqifaddr no']}, - 'bridge-mcquerier' : - { 'help' : 'set multicast querier', - 'validvals' : ['yes', 'no', '0', '1'], - 'default' : 'no', - 'example' : ['bridge-mcquerier no']}, - 'bridge-hashel' : - { 'help' : 'set hash elasticity', - 'validrange' : ['0', '4096'], - 'default' : '4096', - 'example' : ['bridge-hashel 4096']}, - 'bridge-hashmax' : - { 'help' : 'set hash max', - 'validrange' : ['0', '4096'], - 'default' : '4096', - 'example' : ['bridge-hashmax 4096']}, - 'bridge-mclmi' : - { 'help' : 'set multicast last member interval (in secs)', - 'validrange' : ['0', '255'], - 'default' : '1', - 'example' : ['bridge-mclmi 1']}, - 'bridge-mcmi' : - { 'help' : 'set multicast membership interval (in secs)', - 'validrange' : ['0', '255'], - 'default' : '260', - 'example' : ['bridge-mcmi 260']}, - 'bridge-mcqpi' : - { 'help' : 'set multicast querier interval (in secs)', - 'validrange' : ['0', '255'], - 'default' : '255', - 'example' : ['bridge-mcqpi 255']}, - 'bridge-mcqi' : - { 'help' : 'set multicast query interval (in secs)', - 'validrange' : ['0', '255'], - 'default' : '125', - 'example' : ['bridge-mcqi 125']}, - 'bridge-mcqri' : - { 'help' : 'set multicast query response interval (in secs)', - 'validrange' : ['0', '255'], - 'default' : '10', - 'example' : ['bridge-mcqri 10']}, - 'bridge-mcsqi' : - { 'help' : 'set multicast startup query interval (in secs)', - 'validrange' : ['0', '255'], - 'default' : '31', - 'example' : ['bridge-mcsqi 31']}, - 'bridge-mcqv4src' : - { 'help' : 'set per VLAN v4 multicast querier source address', - 'validvals' : ['', ], - 'multivalue' : True, - 'compat' : True, - 'example' : ['bridge-mcqv4src 100=172.16.100.1 101=172.16.101.1']}, - 'bridge-portmcrouter' : - { 'help' : 'set port multicast routers', - 'validvals' : [''], - 'default' : 'yes', - 'example' : ['under the bridge: bridge-portmcrouter swp1=yes swp2=yes', - 'under the port (recommended): bridge-portmcrouter yes']}, - 'bridge-portmcfl' : - { 'help' : 'port multicast fast leave.', - 'validvals': [''], - 'validrange' : ['yes', 'no', '0', '1'], - 'default' : 'no', - 'example' : ['under the bridge: bridge-portmcfl swp1=no swp2=no', - 'under the port (recommended): bridge-portmcfl no']}, - 'bridge-waitport' : - { 'help' : 'wait for a max of time secs for the' + - ' specified ports to become available,' + - 'if no ports are specified then those' + - ' specified on bridge-ports will be' + - ' used here. Specifying no ports here ' + - 'should not be used if we are using ' + - 'regex or \"all\" on bridge_ports,' + - 'as it wouldnt work.', - 'default' : '0', - 'validvals': [''], - 'example' : ['bridge-waitport 4 swp1 swp2']}, - 'bridge-maxwait' : - { 'help' : 'forces to time seconds the maximum time ' + - 'that the Debian bridge setup scripts will ' + - 'wait for the bridge ports to get to the ' + - 'forwarding status, doesn\'t allow factional ' + - 'part. If it is equal to 0 then no waiting' + - ' is done', - 'validrange' : ['0', '255'], - 'default' : '0', - 'example' : ['bridge-maxwait 3']}, - 'bridge-vids' : - { 'help' : 'bridge port vids. Can be specified ' + - 'under the bridge or under the port. ' + - 'If specified under the bridge the ports ' + - 'inherit it unless overridden by a ' + - 'bridge-vids attribute under the port', - 'multivalue' : True, - 'validvals': [''], - 'example' : ['bridge-vids 4000', - 'bridge-vids 2000 2200-3000'], - 'aliases': ['bridge-trunk']}, - 'bridge-pvid' : - { 'help' : 'bridge port pvid. Must be specified under' + - ' the bridge port', - 'validrange' : ['0', '4096'], - 'example' : ['bridge-pvid 1']}, - 'bridge-access' : - { 'help' : 'bridge port access vlan. Must be ' + - 'specified under the bridge port', - 'validrange' : ['1', '4094'], - 'example' : ['bridge-access 300']}, - 'bridge-allow-untagged' : - { 'help' : 'indicate if the bridge port accepts ' + - 'untagged packets or not. Must be ' + - 'specified under the bridge port. ' + - 'Default is \'yes\'', - 'validvals' : ['yes', 'no'], - 'example' : ['bridge-allow-untagged yes'], - 'default' : 'yes'}, - 'bridge-port-vids' : - { 'help' : 'bridge vlans', - 'compat': True, - 'example' : ['bridge-port-vids bond0=1-1000,1010-1020']}, - 'bridge-port-pvids' : - { 'help' : 'bridge port vlans', - 'compat': True, - 'example' : ['bridge-port-pvids bond0=100 bond1=200']}, - 'bridge-learning' : - { 'help' : 'bridge port learning flag', - 'validvals': ['on', 'off'], - 'default': 'on', - 'example' : ['bridge-learning off']}, - }} - - def __init__(self, *args, **kargs): - moduleBase.__init__(self, *args, **kargs) - self.ipcmd = None - self.name = self.__class__.__name__ - self.brctlcmd = None - self._running_vidinfo = {} - self._running_vidinfo_valid = False - self._resv_vlan_range = self._get_reserved_vlan_range() - self.logger.debug('%s: using reserved vlan range %s' - %(self.__class__.__name__, str(self._resv_vlan_range))) - default_stp_attr = policymanager.policymanager_api.get_attr_default( - module_name=self.__class__.__name__, attr='bridge-stp') - if (default_stp_attr and (default_stp_attr == 'on' or default_stp_attr == 'yes')): - self.default_stp_on = True - else: - self.default_stp_on = False - - should_warn = policymanager.policymanager_api.\ - get_module_globals(module_name=self.__class__.__name__, - attr='warn_on_untagged_bridge_absence') - self.warn_on_untagged_bridge_absence = should_warn == 'yes' - - def syntax_check(self, ifaceobj, ifaceobj_getfunc): - retval = self.check_bridge_vlan_aware_port(ifaceobj, ifaceobj_getfunc) - if ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT: - if not self.check_bridge_port_vid_attrs(ifaceobj): - retval = False - c1 = self.syntax_check_vxlan_in_vlan_aware_br(ifaceobj, - ifaceobj_getfunc=ifaceobj_getfunc) - return retval and c1 - - def check_bridge_port_vid_attrs(self, ifaceobj): - if (ifaceobj.get_attr_value('bridge-access') and - (self.get_ifaceobj_bridge_vids_value(ifaceobj) or - ifaceobj.get_attr_value('bridge-pvid'))): - self.logger.warn('%s: bridge-access given, bridge-vids and bridge-pvid ' - 'will be ignored' % ifaceobj.name) - return False - return True - - def check_bridge_vlan_aware_port(self, ifaceobj, ifaceobj_getfunc): - if ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_VLAN_AWARE: - ports = self._get_bridge_port_list(ifaceobj) - if not ports: - return True - result = True - for port_name in ports: - port_obj_l = ifaceobj_getfunc(port_name) - if port_obj_l and port_obj_l[0].link_kind & ifaceLinkKind.VLAN: - self.logger.error('%s: %s: vlan sub-interface is not ' - 'supported in a vlan-aware bridge' - % (ifaceobj.name, port_name)) - result = False - return result - return True - - def _error_vxlan_in_vlan_aware_br(self, ifaceobj, bridgename): - self.log_error('`bridge-access` attribute is mandatory when vxlan ' - 'device (%s) is part of vlan aware bridge (%s)' - % (ifaceobj.name, bridgename), ifaceobj) - - def syntax_check_vxlan_in_vlan_aware_br(self, ifaceobj, ifaceobj_getfunc): - if not ifaceobj_getfunc: - return True - if (ifaceobj.link_kind & ifaceLinkKind.VXLAN - and ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT): - if ifaceobj.get_attr_value('bridge-access'): - return True - for iface in ifaceobj.upperifaces if ifaceobj.upperifaces else []: - ifaceobj_upper_list = ifaceobj_getfunc(iface) - if not ifaceobj_upper_list: - continue - ifaceobj_upper = ifaceobj_upper_list[0] - bridge_vids = self._get_bridge_vids(iface, ifaceobj_getfunc) - if ifaceobj_upper.link_privflags & ifaceLinkPrivFlags.BRIDGE_VLAN_AWARE: - vids = self.get_ifaceobj_bridge_vids_value(ifaceobj) - pvid = ifaceobj.get_attr_value_first('bridge-pvid') - if (not vids - or not pvid - or not self._compare_vids(bridge_vids, - vids, - pvid=pvid)): - self._error_vxlan_in_vlan_aware_br(ifaceobj, - ifaceobj_upper.name) - return False - return True - - def _is_bridge(self, ifaceobj): - if (ifaceobj.get_attr_value_first('bridge-ports') or - ifaceobj.get_attr_value_first('bridge-vlan-aware')): - return True - return False - - def _get_ifaceobj_bridge_ports(self, ifaceobj, warn=True): - ports = ifaceobj.get_attr_value('bridge-ports') - if warn and ports and len(ports) > 1: - self.log_warn('%s: ignoring duplicate bridge-ports lines: %s' - %(ifaceobj.name, ports[1:])) - if ports: - if 'none' in ports[0]: - return [] - - return ports[0] - - return None - - def _is_bridge_port(self, ifaceobj): - if self.brctlcmd.is_bridge_port(ifaceobj.name): - return True - return False - - def get_dependent_ifacenames(self, ifaceobj, ifacenames_all=None): - if not self._is_bridge(ifaceobj): - return None - if ifaceobj.link_type != ifaceLinkType.LINK_NA: - ifaceobj.link_type = ifaceLinkType.LINK_MASTER - ifaceobj.link_kind |= ifaceLinkKind.BRIDGE - # for special vlan aware bridges, we need to add another bit - if ifaceobj.get_attr_value_first('bridge-vlan-aware') == 'yes': - ifaceobj.link_kind |= ifaceLinkKind.BRIDGE - ifaceobj.link_privflags |= ifaceLinkPrivFlags.BRIDGE_VLAN_AWARE - ifaceobj.role |= ifaceRole.MASTER - ifaceobj.dependency_type = ifaceDependencyType.MASTER_SLAVE - return self.parse_port_list(ifaceobj.name, - self._get_ifaceobj_bridge_ports(ifaceobj), - ifacenames_all) - - def get_dependent_ifacenames_running(self, ifaceobj): - self._init_command_handlers() - if not self.brctlcmd.bridge_exists(ifaceobj.name): - return None - return self.brctlcmd.get_bridge_ports(ifaceobj.name) - - def _get_bridge_port_list(self, ifaceobj): - - # port list is also available in the previously - # parsed dependent list. Use that if available, instead - # of parsing port expr again - port_list = ifaceobj.lowerifaces - if port_list: - return port_list - ports = self._get_ifaceobj_bridge_ports(ifaceobj) - if ports: - return self.parse_port_list(ifaceobj.name, ports) - else: - return None - - def _process_bridge_waitport(self, ifaceobj, portlist): - waitport_value = ifaceobj.get_attr_value_first('bridge-waitport') - if not waitport_value: return - try: - waitportvals = re.split(r'[\s\t]\s*', waitport_value, 1) - if not waitportvals: return - try: - waitporttime = int(waitportvals[0]) - except: - self.log_warn('%s: invalid waitport value \'%s\'' - %(ifaceobj.name, waitporttime)) - return - if waitporttime <= 0: return - try: - waitportlist = self.parse_port_list(ifaceobj.name, - waitportvals[1]) - except IndexError, e: - # ignore error and use all bridge ports - waitportlist = portlist - pass - if not waitportlist: return - self.logger.info('%s: waiting for ports %s to exist ...' - %(ifaceobj.name, str(waitportlist))) - starttime = time.time() - while ((time.time() - starttime) < waitporttime): - if all([False for p in waitportlist - if not self.ipcmd.link_exists(p)]): - break; - time.sleep(1) - except Exception, e: - self.log_warn('%s: unable to process waitport: %s' - %(ifaceobj.name, str(e))) - - def _enable_disable_ipv6(self, port, enable='1'): - try: - self.write_file('/proc/sys/net/ipv6/conf/%s/disable_ipv6' % port, enable) - except Exception, e: - self.logger.info(str(e)) - - def handle_ipv6(self, ports, state, ifaceobj=None): - if ifaceobj and (ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_VXLAN): - self._enable_disable_ipv6(ifaceobj.name, state) - for p in ports: - self._enable_disable_ipv6(p, state) - - def _pretty_print_add_ports_error(self, errstr, bridgeifaceobj, bridgeports): - """ pretty print bridge port add errors. - since the commands are batched and the kernel only returns error - codes, this function tries to interpret some error codes - and prints clearer errors """ - - if re.search('RTNETLINK answers: Invalid argument', errstr): - # Cumulus Linux specific error checks - try: - if self.sysctl_get('net.bridge.bridge-allow-multiple-vlans') == '0': - vlanid = None - for bport in bridgeports: - ifattrs = bport.split('.') - if vlanid: - if (len(ifattrs) == 1 or ifattrs[1] != vlanid): - self.log_error('%s: ' %bridgeifaceobj.name + - 'net.bridge.bridge-allow-multiple-vlans not set, multiple vlans not allowed', bridgeifaceobj) - break - if len(ifattrs) == 2: - vlanid = ifattrs[1] - except: - pass - self.log_error(bridgeifaceobj.name + ': ' + errstr, bridgeifaceobj) - - def _add_ports(self, ifaceobj): - bridgeports = self._get_bridge_port_list(ifaceobj) - runningbridgeports = [] - removedbridgeports = [] - - self.ipcmd.batch_start() - self._process_bridge_waitport(ifaceobj, bridgeports) - self.ipcmd.batch_start() - # Delete active ports not in the new port list - if not ifupdownflags.flags.PERFMODE: - runningbridgeports = self.brctlcmd.get_bridge_ports(ifaceobj.name) - if runningbridgeports: - for bport in runningbridgeports: - if not bridgeports or bport not in bridgeports: - netlink.link_set_nomaster(bport) - removedbridgeports.append(bport) - else: - runningbridgeports = [] - if not bridgeports: - self.ipcmd.batch_commit() - return - err = 0 - ports = 0 - newbridgeports = Set(bridgeports).difference(Set(runningbridgeports)) - for bridgeport in newbridgeports: - try: - if (not ifupdownflags.flags.DRYRUN and - not self.ipcmd.link_exists(bridgeport)): - self.log_error('%s: bridge port %s does not exist' - %(ifaceobj.name, bridgeport), ifaceobj) - err += 1 - continue - hwaddress = self.ipcmd.link_get_hwaddress(bridgeport) - if not self._valid_ethaddr(hwaddress): - self.log_warn('%s: skipping port %s, ' %(ifaceobj.name, - bridgeport) + 'invalid ether addr %s' - %hwaddress) - continue - netlink.link_set_master(bridgeport, ifaceobj.name) - self.ipcmd.addr_flush(bridgeport) - ports += 1 - if ports == 250: - ports = 0 - self.ipcmd.batch_commit() - self.ipcmd.batch_start() - except Exception, e: - self.logger.error(str(e)) - pass - try: - self.ipcmd.batch_commit() - except Exception, e: - self._pretty_print_add_ports_error(str(e), ifaceobj, - bridgeports) - pass - - # enable ipv6 for ports that were removed - self.handle_ipv6(removedbridgeports, '0') - if err: - self.log_error('bridge configuration failed (missing ports)') - - - def _process_bridge_maxwait(self, ifaceobj, portlist): - maxwait = ifaceobj.get_attr_value_first('bridge-maxwait') - if not maxwait: return - try: - maxwait = int(maxwait) - except: - self.log_warn('%s: invalid maxwait value \'%s\'' %(ifaceobj.name, - maxwait)) - return - if not maxwait: return - self.logger.info('%s: waiting for ports to go to fowarding state ..' - %ifaceobj.name) - try: - starttime = time.time() - while ((time.time() - starttime) < maxwait): - if all([False for p in portlist - if self.read_file_oneline( - '/sys/class/net/%s/brif/%s/state' - %(ifaceobj.name, p)) != '3']): - break; - time.sleep(1) - except Exception, e: - self.log_warn('%s: unable to process maxwait: %s' - %(ifaceobj.name, str(e))) - - def _ints_to_ranges(self, ints): - for a, b in itertools.groupby(enumerate(ints), lambda (x, y): y - x): - b = list(b) - yield b[0][1], b[-1][1] - - def _ranges_to_ints(self, rangelist): - """ returns expanded list of integers given set of string ranges - example: ['1', '2-4', '6'] returns [1, 2, 3, 4, 6] - """ - result = [] - try: - for part in rangelist: - if '-' in part: - a, b = part.split('-') - a, b = int(a), int(b) - result.extend(range(a, b + 1)) - else: - a = int(part) - result.append(a) - except: - self.logger.warn('unable to parse vids \'%s\'' - %''.join(rangelist)) - pass - return result - - def _compress_into_ranges(self, vids_ints): - return ['%d' %start if start == end else '%d-%d' %(start, end) - for start, end in self._ints_to_ranges(vids_ints)] - - def _diff_vids(self, vids1_ints, vids2_ints): - vids_to_add = Set(vids1_ints).difference(vids2_ints) - vids_to_del = Set(vids2_ints).difference(vids1_ints) - - return (vids_to_del, vids_to_add) - - def _compare_vids(self, vids1, vids2, pvid=None): - """ Returns true if the vids are same else return false """ - - vids1_ints = self._ranges_to_ints(vids1) - vids2_ints = self._ranges_to_ints(vids2) - set_diff = Set(vids1_ints).symmetric_difference(vids2_ints) - if pvid and int(pvid) in set_diff: - set_diff.remove(int(pvid)) - if set_diff: - return False - else: - return True - - def _set_bridge_mcqv4src_compat(self, ifaceobj): - # - # Sets old style igmp querier - # - attrval = ifaceobj.get_attr_value_first('bridge-mcqv4src') - if attrval: - running_mcqv4src = {} - if not ifupdownflags.flags.PERFMODE: - running_mcqv4src = self.brctlcmd.get_mcqv4src(ifaceobj.name) - mcqs = {} - srclist = attrval.split() - for s in srclist: - k, v = s.split('=') - mcqs[k] = v - - k_to_del = Set(running_mcqv4src.keys()).difference(mcqs.keys()) - for v in k_to_del: - self.brctlcmd.del_mcqv4src(ifaceobj.name, v) - for v in mcqs.keys(): - self.brctlcmd.set_mcqv4src(ifaceobj.name, v, mcqs[v]) - - def _get_running_vidinfo(self): - if self._running_vidinfo_valid: - return self._running_vidinfo - self._running_vidinfo = {} - - # CM-8161. Removed check for PERFMODE. Need the get in all cases - # including reboot, so that we can configure the pvid correctly. - self._running_vidinfo = self.ipcmd.bridge_port_vids_get_all_json() - self._running_vidinfo_valid = True - return self._running_vidinfo - - def _flush_running_vidinfo(self): - self._running_vidinfo = {} - self._running_vidinfo_valid = False - - def _set_bridge_vidinfo_compat(self, ifaceobj): - # - # Supports old style vlan vid info format - # for compatibility - # - bridge_port_pvids = ifaceobj.get_attr_value_first('bridge-port-pvids') - bridge_port_vids = ifaceobj.get_attr_value_first('bridge-port-vids') - if not bridge_port_pvids and not bridge_port_vids: - return - - # Handle bridge vlan attrs - # Install pvids - if bridge_port_pvids: - portlist = self.parse_port_list(ifaceobj.name, bridge_port_pvids) - if not portlist: - self.log_warn('%s: could not parse \'%s %s\'' - %(ifaceobj.name, 'bridge-port-pvids', - bridge_port_pvids)) - return - for p in portlist: - try: - (port, pvid) = p.split('=') - pvid = int(pvid) - running_pvid = self._get_running_pvid(port) - if running_pvid: - if running_pvid == pvid: - continue - else: - self.ipcmd.bridge_port_pvid_del(port, running_pvid) - self.ipcmd.bridge_port_pvid_add(port, pvid) - except Exception, e: - self.log_warn('%s: failed to set pvid `%s` (%s)' - %(ifaceobj.name, p, str(e))) - - # install port vids - if bridge_port_vids: - portlist = self.parse_port_list(ifaceobj.name, bridge_port_vids) - if not portlist: - self.log_warn('%s: could not parse \'%s %s\'' %(ifaceobj.name, - 'bridge-port-vids', bridge_port_vids)) - return - for p in portlist: - try: - (port, val) = p.split('=') - vids = val.split(',') - vids_int = self._ranges_to_ints(vids) - running_vids = self._get_running_vids(port) - if running_vids: - (vids_to_del, vids_to_add) = \ - self._diff_vids(vids_int, running_vids) - if vids_to_del: - self.ipcmd.bridge_port_vids_del(port, - self._compress_into_ranges(vids_to_del)) - if vids_to_add: - self.ipcmd.bridge_port_vids_add(port, - self._compress_into_ranges(vids_to_add)) - else: - self.ipcmd.bridge_port_vids_add(port, vids_int) - except Exception, e: - self.log_warn('%s: failed to set vid `%s` (%s)' - %(ifaceobj.name, p, str(e))) - - def _is_running_stp_state_on(self, bridgename): - """ Returns True if running stp state is on, else False """ - - stp_state_file = '/sys/class/net/%s/bridge/stp_state' %bridgename - if not stp_state_file: - return False - running_stp_state = self.read_file_oneline(stp_state_file) - if running_stp_state and running_stp_state != '0': - return True - return False - - def _is_config_stp_state_on(self, ifaceobj): - """ Returns true if user specified stp state is on, else False """ - - stp_attr = ifaceobj.get_attr_value_first('bridge-stp') - if not stp_attr: - return self.default_stp_on - if (stp_attr and (stp_attr == 'on' or stp_attr == 'yes')): - return True - return False - - def _set_bridge_forwarding(self, ifaceobj): - """ set ip forwarding to 0 if bridge interface does not have a - ip nor svi """ - if not ifaceobj.upperifaces and not ifaceobj.get_attr_value('address'): - # set forwarding = 0 - if self.sysctl_get('net.ipv4.conf.%s.forwarding' %ifaceobj.name) == '1': - self.sysctl_set('net.ipv4.conf.%s.forwarding' %ifaceobj.name, 0) - if self.sysctl_get('net.ipv6.conf.%s.forwarding' %ifaceobj.name) == '1': - self.sysctl_set('net.ipv6.conf.%s.forwarding' %ifaceobj.name, 0) - else: - if self.sysctl_get('net.ipv4.conf.%s.forwarding' %ifaceobj.name) == '0': - self.sysctl_set('net.ipv4.conf.%s.forwarding' %ifaceobj.name, 1) - if self.sysctl_get('net.ipv6.conf.%s.forwarding' %ifaceobj.name) == '0': - self.sysctl_set('net.ipv6.conf.%s.forwarding' %ifaceobj.name, 1) - - def _apply_bridge_settings(self, ifaceobj): - try: - if self._is_config_stp_state_on(ifaceobj): - if not self._is_running_stp_state_on(ifaceobj.name): - self.brctlcmd.set_stp(ifaceobj.name, "on") - self.logger.info('%s: stp state reset, reapplying port ' - 'settings' %ifaceobj.name) - ifaceobj.module_flags[ifaceobj.name] = \ - ifaceobj.module_flags.setdefault(self.name,0) | \ - bridgeFlags.PORT_PROCESSED_OVERRIDE - else: - # If stp not specified and running stp state on, set it to off - if self._is_running_stp_state_on(ifaceobj.name): - self.brctlcmd.set_stp(ifaceobj.name, 'no') - - self._set_bridge_forwarding(ifaceobj) - - # Use the brctlcmd bulk set method: first build a dictionary - # and then call set - bridgeattrs = { k:v for k,v in - {'ageing' : - ifaceobj.get_attr_value_first('bridge-ageing'), - 'bridgeprio' : - ifaceobj.get_attr_value_first( - 'bridge-bridgeprio'), - 'fd' : - ifaceobj.get_attr_value_first('bridge-fd'), - 'hello' : - ifaceobj.get_attr_value_first('bridge-hello'), - 'maxage' : - ifaceobj.get_attr_value_first('bridge-maxage'), - 'mclmc' : - ifaceobj.get_attr_value_first('bridge-mclmc'), - 'mcrouter' : - ifaceobj.get_attr_value_first( - 'bridge-mcrouter'), - 'mcsnoop' : - ifaceobj.get_attr_value_first('bridge-mcsnoop'), - 'mcsqc' : - ifaceobj.get_attr_value_first('bridge-mcsqc'), - 'mcqifaddr' : - ifaceobj.get_attr_value_first( - 'bridge-mcqifaddr'), - 'mcquerier' : - ifaceobj.get_attr_value_first( - 'bridge-mcquerier'), - 'hashel' : - ifaceobj.get_attr_value_first('bridge-hashel'), - 'hashmax' : - ifaceobj.get_attr_value_first('bridge-hashmax'), - 'mclmi' : - ifaceobj.get_attr_value_first('bridge-mclmi'), - 'mcmi' : - ifaceobj.get_attr_value_first('bridge-mcmi'), - 'mcqpi' : - ifaceobj.get_attr_value_first('bridge-mcqpi'), - 'mcqi' : - ifaceobj.get_attr_value_first('bridge-mcqi'), - 'mcqri' : - ifaceobj.get_attr_value_first('bridge-mcqri'), - 'mcsqi' : - ifaceobj.get_attr_value_first('bridge-mcsqi') - }.items() - if v } - if bridgeattrs: - utils.support_yesno_attrs(bridgeattrs, ['mcqifaddr', - 'mcquerier', - 'mcrouter', - 'mcsnoop']) - self.brctlcmd.set_bridge_attrs(ifaceobj.name, bridgeattrs) - portattrs = {} - for attrname, dstattrname in {'bridge-pathcosts' : 'pathcost', - 'bridge-portprios' : 'portprio', - 'bridge-portmcrouter' : 'portmcrouter', - 'bridge-portmcfl' : 'portmcfl'}.items(): - attrval = ifaceobj.get_attr_value_first(attrname) - if not attrval: - continue - portlist = self.parse_port_list(ifaceobj.name, attrval) - if not portlist: - self.log_error('%s: could not parse \'%s %s\'' - %(ifaceobj.name, attrname, attrval), ifaceobj, - raise_error=False) - continue - for p in portlist: - try: - (port, val) = p.split('=') - if not portattrs.get(port): - portattrs[port] = {} - if (attrname == 'bridge-portmcrouter' - or attrname == 'bridge-portmcfl'): - portattrs[port].update({dstattrname: utils.boolean_support_binary(val)}) - else: - portattrs[port].update({dstattrname : val}) - except Exception, e: - self.log_error('%s: could not parse %s (%s)' - %(ifaceobj.name, attrname, str(e)), - ifaceobj, raise_error=False) - for port, attrdict in portattrs.iteritems(): - try: - self.brctlcmd.set_bridgeport_attrs(ifaceobj.name, port, - attrdict) - except Exception, e: - self.log_error('%s: %s' %(ifaceobj.name, str(e)), ifaceobj, - raise_error=False) - pass - self._set_bridge_vidinfo_compat(ifaceobj) - self._set_bridge_mcqv4src_compat(ifaceobj) - self._process_bridge_maxwait(ifaceobj, - self._get_bridge_port_list(ifaceobj)) - except Exception, e: - self.log_error(str(e), ifaceobj) - - def _check_vids(self, ifaceobj, vids): - ret = True - for v in vids: - try: - if '-' in v: - va, vb = v.split('-') - va, vb = int(va), int(vb) - if (self._handle_reserved_vlan(va, ifaceobj.name) or - self._handle_reserved_vlan(vb, ifaceobj.name)): - ret = False - else: - va = int(v) - if self._handle_reserved_vlan(va, ifaceobj.name): - ret = False - except Exception: - self.logger.warn('%s: unable to parse vid \'%s\'' - %(ifaceobj.name, v)) - return ret - - def _apply_bridge_port_pvids(self, bportifaceobj, pvid, running_pvid): - # Install pvids - try: - if running_pvid: - if running_pvid != pvid: - self.ipcmd.bridge_port_pvid_del(bportifaceobj.name, - running_pvid) - self.ipcmd.bridge_port_pvid_add(bportifaceobj.name, pvid) - else: - self.ipcmd.bridge_port_pvid_add(bportifaceobj.name, pvid) - except Exception, e: - self.log_error('%s: failed to set pvid `%s` (%s)' - %(bportifaceobj.name, pvid, str(e)), bportifaceobj) - - def _get_running_pvid(self, ifacename): - pvid = 0 - - running_vidinfo = self._get_running_vidinfo() - for vinfo in running_vidinfo.get(ifacename, {}): - v = vinfo.get('vlan') - pvid = v if 'PVID' in vinfo.get('flags', []) else 0 - if pvid: - return pvid - return pvid - - def _get_running_vids(self, ifacename): - vids = [] - - running_vidinfo = self._get_running_vidinfo() - for vinfo in running_vidinfo.get(ifacename, {}): - v = vinfo.get('vlan') - ispvid = True if 'PVID' in vinfo.get('flags', []) else False - if ispvid: - pvid = v if 'PVID' in vinfo.get('flags', []) else 0 - if pvid == 1: - continue - vEnd = vinfo.get('vlanEnd') - if vEnd: - vids.extend(range(v, vEnd + 1)) - else: - vids.append(v) - return vids - - def _get_running_vids_n_pvid(self, ifacename): - vids = [] - pvid = 0 - - running_vidinfo = self._get_running_vidinfo() - for vinfo in running_vidinfo.get(ifacename, {}): - v = vinfo.get('vlan') - ispvid = True if 'PVID' in vinfo.get('flags', []) else False - if ispvid: - pvid = v if 'PVID' in vinfo.get('flags', []) else 0 - vEnd = vinfo.get('vlanEnd') - if vEnd: - vids.extend(range(v, vEnd + 1)) - else: - vids.append(v) - return (vids, pvid) - - def _get_running_vids_n_pvid_str(self, ifacename): - vids = [] - pvid = None - - (vids, pvid) = self._get_running_vids_n_pvid(ifacename) - - if vids: - ret_vids = self._compress_into_ranges(vids) - else: - ret_vids = None - - if pvid: - ret_pvid = '%s' %pvid - else: - ret_pvid = None - return (ret_vids, ret_pvid) - - def _get_running_vids_n_pvid_str2(self, ifacename): - vids = [] - pvid = None - - - running_vidinfo = self._get_running_vidinfo() - for vinfo in running_vidinfo.get(ifacename, {}): - v = vinfo.get('vlan') - if not pvid: - pvid = '%s' %v if 'PVID' in vinfo.get('flags', []) else None - vEnd = vinfo.get('vlanEnd') - if vEnd: - vids.append('%s-%s' %(v, vEnd)) - else: - vids.append('%s' %v) - return (vids, pvid) - - def _apply_bridge_vids_and_pvid(self, bportifaceobj, vids, pvid, - isbridge): - """ This method is a combination of methods _apply_bridge_vids and - _apply_bridge_port_pvids above. A combined function is - found necessary to do the deletes first and the adds later - because kernel does honor vid info flags during deletes. - - """ - if not isbridge and bportifaceobj.link_kind & ifaceLinkKind.VXLAN: - if not vids or not pvid or len(vids) > 1 or vids[0] != pvid: - self._error_vxlan_in_vlan_aware_br(bportifaceobj, - bportifaceobj.upperifaces[0]) - return - - vids_int = self._ranges_to_ints(vids) - try: - pvid_int = int(pvid) if pvid else 0 - except Exception: - self.logger.warn('%s: unable to parse pvid \'%s\'' - %(bportifaceobj.name, pvid)) - pvid_int = 0 - pass - - vids_to_del = [] - vids_to_add = vids_int - pvid_to_del = None - pvid_to_add = pvid_int - - try: - if not self._check_vids(bportifaceobj, vids): - return - - (running_vids, running_pvid) = self._get_running_vids_n_pvid( - bportifaceobj.name) - - if not running_vids and not running_pvid: - # There cannot be a no running pvid. - # It might just not be in our cache: - # this can happen if at the time we were - # creating the bridge vlan cache, the port - # was not part of the bridge. And we need - # to make sure both vids and pvid is not in - # the cache, to declare that our cache may - # be stale. - running_pvid = 1 - running_vids = [1] - - if running_vids: - (vids_to_del, vids_to_add) = \ - self._diff_vids(vids_to_add, running_vids) - - if running_pvid: - if running_pvid != pvid_int and running_pvid != 0: - pvid_to_del = running_pvid - - if (pvid_to_del and (pvid_to_del in vids_int) and - (pvid_to_del not in vids_to_add)): - # kernel deletes dont take into account - # bridge vid flags and its possible that - # the pvid deletes we do end up deleting - # the vids. Be proactive and add the pvid - # to the vid add list if it is in the vids - # and not already part of vids_to_add. - # This helps with a small corner case: - # - running - # pvid 100 - # vid 101 102 - # - new change is going to move the state to - # pvid 101 - # vid 100 102 - vids_to_add.add(pvid_to_del) - except Exception, e: - self.log_error('%s: failed to process vids/pvids' - %bportifaceobj.name + ' vids = %s' %str(vids) + - 'pvid = %s ' %pvid + '(%s)' %str(e), - bportifaceobj, raise_error=False) - try: - if vids_to_del: - if pvid_to_add in vids_to_del: - vids_to_del.remove(pvid_to_add) - self.ipcmd.bridge_vids_del(bportifaceobj.name, - self._compress_into_ranges( - vids_to_del), isbridge) - except Exception, e: - self.log_warn('%s: failed to del vid `%s` (%s)' - %(bportifaceobj.name, str(vids_to_del), str(e))) - - try: - if pvid_to_del: - self.ipcmd.bridge_port_pvid_del(bportifaceobj.name, - pvid_to_del) - except Exception, e: - self.log_warn('%s: failed to del pvid `%s` (%s)' - %(bportifaceobj.name, pvid_to_del, str(e))) - - try: - if vids_to_add: - self.ipcmd.bridge_vids_add(bportifaceobj.name, - self._compress_into_ranges( - vids_to_add), isbridge) - except Exception, e: - self.log_error('%s: failed to set vid `%s` (%s)' - %(bportifaceobj.name, str(vids_to_add), - str(e)), bportifaceobj, raise_error=False) - - try: - if pvid_to_add and pvid_to_add != running_pvid: - self.ipcmd.bridge_port_pvid_add(bportifaceobj.name, - pvid_to_add) - except Exception, e: - self.log_error('%s: failed to set pvid `%s` (%s)' - %(bportifaceobj.name, pvid_to_add, str(e)), - bportifaceobj) - - def _apply_bridge_vlan_aware_port_settings_all(self, bportifaceobj, - bridge_vids=None, - bridge_pvid=None): - vids = None - pvids = None - vids_final = [] - pvid_final = None - bport_access = bportifaceobj.get_attr_value_first('bridge-access') - if bport_access: - vids = re.split(r'[\s\t]\s*', bport_access) - pvids = vids - allow_untagged = 'yes' - self.check_bridge_port_vid_attrs(bportifaceobj) - else: - allow_untagged = bportifaceobj.get_attr_value_first('bridge-allow-untagged') or 'yes' - - bport_vids = self.get_ifaceobj_bridge_vids_value(bportifaceobj) - if bport_vids: - vids = re.split(r'[\s\t,]\s*', bport_vids) - - bport_pvids = bportifaceobj.get_attr_value_first('bridge-pvid') - if bport_pvids: - pvids = re.split(r'[\s\t]\s*', bport_pvids) - - if vids: - vids_final = vids - elif bridge_vids: - vids_final = bridge_vids - - if allow_untagged == 'yes': - if pvids: - pvid_final = pvids[0] - elif bridge_pvid: - pvid_final = bridge_pvid - else: - pvid_final = '1' - else: - pvid_final = None - - self._apply_bridge_vids_and_pvid(bportifaceobj, vids_final, - pvid_final, False) - - def _apply_bridge_port_settings(self, bportifaceobj, bridgename=None, - bridgeifaceobj=None): - if not bridgename and bridgeifaceobj: - bridgename = bridgeifaceobj.name - # Set other stp and igmp attributes - portattrs = {} - for attrname, dstattrname in { - 'bridge-pathcosts' : 'pathcost', - 'bridge-portprios' : 'portprio'}.items(): - attrval = bportifaceobj.get_attr_value_first(attrname) - if not attrval: - # Check if bridge has that attribute - #if bridgeifaceobj: - # attrval = bridgeifaceobj.get_attr_value_first(attrname) - # if not attrval: - # continue - #else: - continue - portattrs[dstattrname] = attrval - - portmcrouter = bportifaceobj.get_attr_value_first('bridge-portmcrouter') - if portmcrouter: - portattrs['portmcrouter'] = utils.boolean_support_binary(portmcrouter) - - portmcfl = bportifaceobj.get_attr_value_first('bridge-portmcfl') - if portmcfl: - portattrs['portmcfl'] = utils.boolean_support_binary(portmcfl) - - try: - self.brctlcmd.set_bridgeport_attrs(bridgename, - bportifaceobj.name, portattrs) - except Exception, e: - self.log_error(str(e), bportifaceobj) - - # special handling for learning: - # bridge ports may have learning capability, in which case - # we need to keep the bridge ports internal learning in sync - # with bridge learning capability. This is true for vxlan ports - # hence special case this for vxlan ports. - try: - config_learn = bportifaceobj.get_attr_value_first('bridge-learning') - if not config_learn: - config_learn = self.get_mod_subattr('bridge-learning', 'default') - config_learn = utils.get_onoff_bool(config_learn) - running_learn = self.ipcmd.get_brport_learning(bportifaceobj.name) - if config_learn != running_learn: - self.ipcmd.set_brport_learning(bportifaceobj.name, config_learn) - except Exception, e: - self.log_error(str(e), bportifaceobj) - - if not (bportifaceobj.link_kind & ifaceLinkKind.VXLAN): - return - try: - vxlan_learn = self.ipcmd.get_vxlandev_learning(bportifaceobj.name) - if vxlan_learn: - vxlan_learn = utils.get_onoff_bool(vxlan_learn) - else: - vxlan_learn = 'on' - if vxlan_learn != config_learn: - self.ipcmd.set_vxlandev_learning(bportifaceobj.name, config_learn) - except Exception, e: - self.log_warn('%s: unable to propagate learning to vxlan dev (%s)' - %(bportifaceobj.name, str(e))) - - def _apply_bridge_port_settings_all(self, ifaceobj, - ifaceobj_getfunc=None, - bridge_vlan_aware=False): - err = False - - if (ifaceobj.get_attr_value_first('bridge-port-vids') and - ifaceobj.get_attr_value_first('bridge-port-pvids')): - # Old style bridge port vid info - # skip new style setting on ports - return - self.logger.info('%s: applying bridge configuration ' - %ifaceobj.name + 'specific to ports') - - bridge_vids = self.get_ifaceobj_bridge_vids_value(ifaceobj) - if bridge_vids: - bridge_vids = re.split(r'[\s\t,]\s*', bridge_vids) - else: - bridge_vids = None - - bridge_pvid = ifaceobj.get_attr_value_first('bridge-pvid') - if bridge_pvid: - bridge_pvid = re.split(r'[\s\t]\s*', bridge_pvid)[0] - else: - bridge_pvid = None - - if (ifaceobj.module_flags.get(self.name, 0x0) & - bridgeFlags.PORT_PROCESSED_OVERRIDE): - port_processed_override = True - else: - port_processed_override = False - - bridgeports = self._get_bridge_port_list(ifaceobj) - if not bridgeports: - self.logger.debug('%s: cannot find bridgeports' %ifaceobj.name) - return - self.ipcmd.batch_start() - for bport in bridgeports: - # Use the brctlcmd bulk set method: first build a dictionary - # and then call set - if not self.ipcmd.bridge_port_exists(ifaceobj.name, bport): - self.logger.info('%s: skipping bridge config' %ifaceobj.name + - ' for port %s (missing port)' %bport) - continue - self.logger.info('%s: processing bridge config for port %s' - %(ifaceobj.name, bport)) - bportifaceobjlist = ifaceobj_getfunc(bport) - if not bportifaceobjlist: - continue - for bportifaceobj in bportifaceobjlist: - # Dont process bridge port if it already has been processed - # and there is no override on port_processed - if (not port_processed_override and - (bportifaceobj.module_flags.get(self.name,0x0) & - bridgeFlags.PORT_PROCESSED)): - continue - try: - # Add attributes specific to the vlan aware bridge - if bridge_vlan_aware: - self._apply_bridge_vlan_aware_port_settings_all( - bportifaceobj, bridge_vids, bridge_pvid) - self._apply_bridge_port_settings(bportifaceobj, - bridgeifaceobj=ifaceobj) - elif self.warn_on_untagged_bridge_absence: - self._check_untagged_bridge(ifaceobj.name, bportifaceobj, ifaceobj_getfunc) - except Exception, e: - err = True - self.logger.warn('%s: %s' %(ifaceobj.name, str(e))) - pass - self.ipcmd.bridge_batch_commit() - if err: - raise Exception('%s: errors applying port settings' %ifaceobj.name) - - def _check_untagged_bridge(self, bridgename, bridgeportifaceobj, ifaceobj_getfunc): - if bridgeportifaceobj.link_kind & ifaceLinkKind.VLAN: - lower_ifaceobj_list = ifaceobj_getfunc(bridgeportifaceobj.lowerifaces[0]) - if lower_ifaceobj_list and lower_ifaceobj_list[0] and \ - not lower_ifaceobj_list[0].link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT: - self.logger.warn('%s: untagged bridge not found. Please configure a bridge with untagged bridge ports to avoid Spanning Tree Interoperability issue.' % bridgename) - self.warn_on_untagged_bridge_absence = False - - def _get_bridgename(self, ifaceobj): - for u in ifaceobj.upperifaces: - if self.ipcmd.is_bridge(u): - return u - return None - - def _up(self, ifaceobj, ifaceobj_getfunc=None): - # Check if bridge port and see if we need to add it to the bridge - add_port = False - bridgename = self.ipcmd.bridge_port_get_bridge_name(ifaceobj.name) - if (not bridgename and - (ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT)): - # get bridgename and add port to bridge - bridgename = self._get_bridgename(ifaceobj) - add_port = True - if bridgename: - if self.ipcmd.bridge_is_vlan_aware(bridgename): - if add_port: - # add ifaceobj to bridge - netlink.link_set_master(ifaceobj.name, bridgename) - bridge_vids = self._get_bridge_vids(bridgename, - ifaceobj_getfunc) - bridge_pvid = self._get_bridge_pvid(bridgename, - ifaceobj_getfunc) - try: - self._apply_bridge_vlan_aware_port_settings_all(ifaceobj, - bridge_vids, - bridge_pvid) - except Exception as e: - self.log_error('%s: %s' % (ifaceobj.name, str(e)), ifaceobj) - return - self._apply_bridge_port_settings(ifaceobj, bridgename=bridgename) - ifaceobj.module_flags[self.name] = ifaceobj.module_flags.setdefault(self.name,0) | \ - bridgeFlags.PORT_PROCESSED - return - if not self._is_bridge(ifaceobj): - return - err = False - errstr = '' - running_ports = '' - bridge_just_created = False - try: - if not ifupdownflags.flags.PERFMODE: - if not self.ipcmd.link_exists(ifaceobj.name): - self.ipcmd.link_create(ifaceobj.name, 'bridge') - bridge_just_created = True - else: - self.ipcmd.link_create(ifaceobj.name, 'bridge') - bridge_just_created = True - except Exception, e: - raise Exception(str(e)) - - try: - bridge_vlan_aware = False - if ifaceobj.get_attr_value_first('bridge-vlan-aware') == 'yes': - if not self.check_bridge_vlan_aware_port(ifaceobj, ifaceobj_getfunc): - return - bridge_vlan_aware = True - if (bridge_just_created or - not self.ipcmd.bridge_is_vlan_aware(ifaceobj.name)): - self.ipcmd.link_set(ifaceobj.name, 'vlan_filtering', '1', - False, "bridge") - if not bridge_just_created: - ifaceobj.module_flags[self.name] = ifaceobj.module_flags.setdefault(self.name,0) | bridgeFlags.PORT_PROCESSED_OVERRIDE - elif (not bridge_just_created and - ifaceobj.get_attr_value_first('bridge-vlan-aware') == 'no' - and self.ipcmd.bridge_is_vlan_aware(ifaceobj.name)): - self.ipcmd.link_set(ifaceobj.name, 'vlan_filtering', '0', - False, "bridge") - bridge_vlan_aware = False - except Exception, e: - raise Exception(str(e)) - - try: - self._add_ports(ifaceobj) - except Exception, e: - err = True - errstr = str(e) - pass - - try: - self._apply_bridge_settings(ifaceobj) - except Exception, e: - err = True - errstr = str(e) - pass - - try: - running_ports = self.brctlcmd.get_bridge_ports(ifaceobj.name) - if not running_ports: - return - # disable ipv6 for ports that were added to bridge - self.handle_ipv6(running_ports, '1', ifaceobj=ifaceobj) - self._apply_bridge_port_settings_all(ifaceobj, - ifaceobj_getfunc=ifaceobj_getfunc, - bridge_vlan_aware=bridge_vlan_aware) - except Exception, e: - err = True - errstr = str(e) - pass - finally: - if ifaceobj.link_type != ifaceLinkType.LINK_NA: - for p in running_ports: - try: - netlink.link_set_updown(p, "up") - except Exception, e: - self.logger.debug('%s: %s: link set up (%s)' - %(ifaceobj.name, p, str(e))) - pass - if err: - raise Exception(errstr) - - def _down(self, ifaceobj, ifaceobj_getfunc=None): - try: - if self._get_ifaceobj_bridge_ports(ifaceobj, warn=False): - ports = self.brctlcmd.get_bridge_ports(ifaceobj.name) - self.brctlcmd.delete_bridge(ifaceobj.name) - if ports: - self.handle_ipv6(ports, '0') - if ifaceobj.link_type != ifaceLinkType.LINK_NA: - map(lambda p: netlink.link_set_updown(p, "down"), - ports) - except Exception, e: - self.log_error('%s: %s' % (ifaceobj.name, str(e)), ifaceobj) - - def _query_running_vidinfo_compat(self, ifaceobjrunning, ports): - running_attrs = {} - if ports: - running_bridge_port_vids = '' - for p in ports: - try: - running_vids = self._get_runing_vids(p) - if running_vids: - running_bridge_port_vids += ' %s=%s' %(p, - ','.join(running_vids)) - except Exception: - pass - running_attrs['bridge-port-vids'] = running_bridge_port_vids - - running_bridge_port_pvid = '' - for p in ports: - try: - running_pvid = self._get_runing_pvid(p) - if running_pvid: - running_bridge_port_pvid += ' %s=%s' %(p, - running_pvid) - except Exception: - pass - running_attrs['bridge-port-pvids'] = running_bridge_port_pvid - - running_bridge_vids = self._get_running_vids(ifaceobjrunning.name) - if running_bridge_vids: - running_attrs['bridge-vids'] = ','.join(self._compress_into_ranges(running_bridge_vids)) - return running_attrs - - def _query_running_vidinfo(self, ifaceobjrunning, ifaceobj_getfunc, - bridgeports=None): - running_attrs = {} - - # 'bridge-vids' under the bridge is all about 'vids' on the port. - # so query the ports - running_bridgeport_vids = [] - running_bridgeport_pvids = [] - for bport in bridgeports: - (vids, pvid) = self._get_running_vids_n_pvid_str(bport) - if vids: - running_bridgeport_vids.append(' '.join(vids)) - if pvid: - running_bridgeport_pvids.append(pvid) - - bridge_vids = None - if running_bridgeport_vids: - (vidval, freq) = Counter(running_bridgeport_vids).most_common()[0] - if freq == len(bridgeports): - running_attrs['bridge-vids'] = vidval - bridge_vids = vidval.split() - - bridge_pvid = None - if running_bridgeport_pvids: - (vidval, freq) = Counter(running_bridgeport_pvids).most_common()[0] - if freq == len(bridgeports) and vidval != '1': - running_attrs['bridge-pvid'] = vidval - bridge_pvid = vidval.split()[0] - - # Go through all bridge ports and find their vids - for bport in bridgeports: - bportifaceobj = ifaceobj_getfunc(bport) - if not bportifaceobj: - continue - bport_vids = [] - bport_pvid = None - (vids, pvid) = self._get_running_vids_n_pvid_str(bport) - if vids and vids != bridge_vids: - bport_vids = vids - if pvid and pvid != bridge_pvid: - bport_pvid = pvid - if bport_vids and bport_pvid in bport_vids: - bport_vids.remove(bport_pvid) - if (not bport_vids and bport_pvid and bport_pvid != '1'): - bportifaceobj[0].replace_config('bridge-access', bport_pvid) - bportifaceobj[0].delete_config('bridge-pvid') - bportifaceobj[0].delete_config('bridge-vids') - else: - if bport_pvid and bport_pvid != '1': - bportifaceobj[0].replace_config('bridge-pvid', bport_pvid) - else: - # delete any stale bridge-vids under ports - bportifaceobj[0].delete_config('bridge-pvid') - if bport_vids: - bportifaceobj[0].replace_config('bridge-vids', - ' '.join(bport_vids)) - else: - # delete any stale bridge-vids under ports - bportifaceobj[0].delete_config('bridge-vids') - return running_attrs - - def _query_running_mcqv4src(self, ifaceobjrunning): - running_mcqv4src = self.brctlcmd.get_mcqv4src(ifaceobjrunning.name) - mcqs = ['%s=%s' %(v, i) for v, i in running_mcqv4src.items()] - mcqs.sort() - mcq = ' '.join(mcqs) - return mcq - - def _query_running_attrs(self, ifaceobjrunning, ifaceobj_getfunc, - bridge_vlan_aware=False): - bridgeattrdict = {} - userspace_stp = 0 - ports = None - skip_kernel_stp_attrs = 0 - - try: - if self.systcl_get_net_bridge_stp_user_space() == '1': - userspace_stp = 1 - except Exception as e: - self.logger.info('%s: %s' % (ifaceobjrunning.name, str(e))) - - tmpbridgeattrdict = self.brctlcmd.get_bridge_attrs(ifaceobjrunning.name) - if not tmpbridgeattrdict: - self.logger.warn('%s: unable to get bridge attrs' - %ifaceobjrunning.name) - return bridgeattrdict - - # Fill bridge_ports and bridge stp attributes first - ports = tmpbridgeattrdict.get('ports') - if ports: - bridgeattrdict['bridge-ports'] = [' '.join(ports.keys())] - stp = tmpbridgeattrdict.get('stp', 'no') - if stp != self.get_mod_subattr('bridge-stp', 'default'): - bridgeattrdict['bridge-stp'] = [stp] - - if stp == 'yes' and userspace_stp: - skip_kernel_stp_attrs = 1 - - bool2str = {'0': 'no', '1': 'yes'} - # pick all other attributes - for k,v in tmpbridgeattrdict.items(): - if not v: - continue - if k == 'ports' or k == 'stp': - continue - - if skip_kernel_stp_attrs and k[:2] != 'mc': - # only include igmp attributes if kernel stp is off - continue - attrname = 'bridge-' + k - mod_default = self.get_mod_subattr(attrname, 'default') - if v != mod_default: - # convert '0|1' running values to 'no|yes' - if v in bool2str.keys() and bool2str[v] == mod_default: - continue - bridgeattrdict[attrname] = [v] - - if bridge_vlan_aware: - bridgevidinfo = self._query_running_vidinfo(ifaceobjrunning, - ifaceobj_getfunc, - ports.keys()) - else: - bridgevidinfo = self._query_running_vidinfo_compat(ifaceobjrunning, - ports) - if bridgevidinfo: - bridgeattrdict.update({k : [v] for k, v in bridgevidinfo.items() - if v}) - - mcq = self._query_running_mcqv4src(ifaceobjrunning) - if mcq: - bridgeattrdict['bridge-mcqv4src'] = [mcq] - - if skip_kernel_stp_attrs: - return bridgeattrdict - - # Do this only for vlan-UNAWARE-bridge - if ports and not bridge_vlan_aware: - portconfig = {'bridge-pathcosts' : '', - 'bridge-portprios' : ''} - for p, v in ports.items(): - v = self.brctlcmd.get_pathcost(ifaceobjrunning.name, p) - if v and v != self.get_mod_subattr('bridge-pathcosts', - 'default'): - portconfig['bridge-pathcosts'] += ' %s=%s' %(p, v) - - v = self.brctlcmd.get_portprio(ifaceobjrunning.name, p) - if v and v != self.get_mod_subattr('bridge-portprios', - 'default'): - portconfig['bridge-portprios'] += ' %s=%s' %(p, v) - - bridgeattrdict.update({k : [v] for k, v in portconfig.items() - if v}) - - return bridgeattrdict - - def _query_check_mcqv4src(self, ifaceobj, ifaceobjcurr): - running_mcqs = self._query_running_mcqv4src(ifaceobj) - attrval = ifaceobj.get_attr_value_first('bridge-mcqv4src') - if attrval: - mcqs = attrval.split() - mcqs.sort() - mcqsout = ' '.join(mcqs) - ifaceobjcurr.update_config_with_status('bridge-mcqv4src', - running_mcqs, 1 if running_mcqs != mcqsout else 0) - - def _query_check_bridge_vidinfo(self, ifaceobj, ifaceobjcurr): - err = 0 - attrval = ifaceobj.get_attr_value_first('bridge-port-vids') - if attrval: - running_bridge_port_vids = '' - portlist = self.parse_port_list(ifaceobj.name, attrval) - if not portlist: - self.log_warn('%s: could not parse \'bridge-port-vids %s\'' - %(ifaceobj.name, attrval)) - return - err = 0 - for p in portlist: - try: - (port, val) = p.split('=') - vids = val.split(',') - running_vids = self._get_running_vids(port) - if running_vids: - if not self._compare_vids(vids, running_vids): - err += 1 - running_bridge_port_vids += ' %s=%s' %(port, - ','.join(running_vids)) - else: - running_bridge_port_vids += ' %s' %p - else: - err += 1 - except Exception, e: - self.log_warn('%s: failure checking vid %s (%s)' - %(ifaceobj.name, p, str(e))) - if err: - ifaceobjcurr.update_config_with_status('bridge-port-vids', - running_bridge_port_vids, 1) - else: - ifaceobjcurr.update_config_with_status('bridge-port-vids', - attrval, 0) - - attrval = ifaceobj.get_attr_value_first('bridge-port-pvids') - if attrval: - portlist = self.parse_port_list(ifaceobj.name, attrval) - if not portlist: - self.log_warn('%s: could not parse \'bridge-port-pvids %s\'' - %(ifaceobj.name, attrval)) - return - running_bridge_port_pvids = '' - err = 0 - for p in portlist: - try: - (port, pvid) = p.split('=') - running_pvid = self._get_running_vids(port) - if running_pvid and running_pvid == pvid: - running_bridge_port_pvids += ' %s' %p - else: - err += 1 - running_bridge_port_pvids += ' %s=%s' %(port, - running_pvid) - except Exception, e: - self.log_warn('%s: failure checking pvid %s (%s)' - %(ifaceobj.name, pvid, str(e))) - if err: - ifaceobjcurr.update_config_with_status('bridge-port-pvids', - running_bridge_port_pvids, 1) - else: - ifaceobjcurr.update_config_with_status('bridge-port-pvids', - running_bridge_port_pvids, 0) - - vids = self.get_ifaceobj_bridge_vids(ifaceobj) - if vids[1]: - ifaceobjcurr.update_config_with_status(vids[0], vids[1], -1) - - def _query_check_bridge(self, ifaceobj, ifaceobjcurr, - ifaceobj_getfunc=None): - if not self._is_bridge(ifaceobj): - return - if not self.brctlcmd.bridge_exists(ifaceobj.name): - self.logger.info('%s: bridge: does not exist' %(ifaceobj.name)) - return - - ifaceattrs = self.dict_key_subset(ifaceobj.config, - self.get_mod_attrs()) - #Add default attributes if --with-defaults is set - if ifupdownflags.flags.WITHDEFAULTS and 'bridge-stp' not in ifaceattrs: - ifaceattrs.append('bridge-stp') - if not ifaceattrs: - return - try: - runningattrs = self.brctlcmd.get_bridge_attrs(ifaceobj.name) - if not runningattrs: - self.logger.debug('%s: bridge: unable to get bridge attrs' - %ifaceobj.name) - runningattrs = {} - except Exception, e: - self.logger.warn(str(e)) - runningattrs = {} - - self._query_check_support_yesno_attrs(runningattrs, ifaceobj) - - filterattrs = ['bridge-vids', 'bridge-trunk', 'bridge-port-vids', - 'bridge-port-pvids'] - for k in Set(ifaceattrs).difference(filterattrs): - # get the corresponding ifaceobj attr - v = ifaceobj.get_attr_value_first(k) - if not v: - if ifupdownflags.flags.WITHDEFAULTS and k == 'bridge-stp': - v = 'on' if self.default_stp_on else 'off' - else: - continue - rv = runningattrs.get(k[7:]) - if k == 'bridge-mcqv4src': - continue - if k == 'bridge-maxwait' or k == 'bridge-waitport': - ifaceobjcurr.update_config_with_status(k, v, 0) - continue - if k == 'bridge-vlan-aware': - rv = self.ipcmd.bridge_is_vlan_aware(ifaceobj.name) - if (rv and v == 'yes') or (not rv and v == 'no'): - ifaceobjcurr.update_config_with_status('bridge-vlan-aware', - v, 0) - else: - ifaceobjcurr.update_config_with_status('bridge-vlan-aware', - v, 1) - elif k == 'bridge-stp': - # special case stp compare because it may - # contain more than one valid values - stp_on_vals = ['on', 'yes'] - stp_off_vals = ['off', 'no'] - if ((v in stp_on_vals and rv in stp_on_vals) or - (v in stp_off_vals and rv in stp_off_vals)): - ifaceobjcurr.update_config_with_status('bridge-stp', - rv, 0) - else: - ifaceobjcurr.update_config_with_status('bridge-stp', - rv, 1) - elif k == 'bridge-ports': - # special case ports because it can contain regex or glob - running_port_list = rv.keys() if rv else [] - bridge_port_list = self._get_bridge_port_list(ifaceobj) - if not running_port_list and not bridge_port_list: - continue - portliststatus = 1 - if running_port_list and bridge_port_list: - difference = set(running_port_list - ).symmetric_difference(bridge_port_list) - if not difference: - portliststatus = 0 - try: - port_list = self._get_ifaceobj_bridge_ports(ifaceobj, - warn=False).split() - # we want to display the same bridge-ports list as provided - # in the interfaces file but if this list contains regexes or - # globs, for now, we won't try to change it. - if 'regex' in port_list or 'glob' in port_list: - port_list = running_port_list - else: - ordered = [] - for i in range(0, len(port_list)): - if port_list[i] in running_port_list: - ordered.append(port_list[i]) - port_list = ordered - except: - port_list = running_port_list - ifaceobjcurr.update_config_with_status('bridge-ports', - (' '.join(port_list) - if port_list else ''), - portliststatus) - elif (k == 'bridge-pathcosts' or - k == 'bridge-portprios' or k == 'bridge-portmcrouter' - or k == 'bridge-portmcfl'): - brctlcmdattrname = k[7:].rstrip('s') - # for port attributes, the attributes are in a list - # = - status = 0 - currstr = '' - vlist = self.parse_port_list(ifaceobj.name, v) - if not vlist: - continue - for vlistitem in vlist: - try: - (p, v) = vlistitem.split('=') - currv = self.brctlcmd.get_bridgeport_attr( - ifaceobj.name, p, - brctlcmdattrname) - if currv: - currstr += ' %s=%s' %(p, currv) - else: - currstr += ' %s=%s' %(p, 'None') - if currv != v: - status = 1 - except Exception, e: - self.log_warn(str(e)) - pass - ifaceobjcurr.update_config_with_status(k, currstr, status) - elif not rv: - if k == 'bridge-pvid' or k == 'bridge-vids' or k == 'bridge-trunk' or k == 'bridge-allow-untagged': - # bridge-pvid and bridge-vids on a bridge does - # not correspond directly to a running config - # on the bridge. They correspond to default - # values for the bridge ports. And they are - # already checked against running config of the - # bridge port and reported against a bridge port. - # So, ignore these attributes under the bridge. - # Use '2' for ignore today. XXX: '2' will be - # mapped to a defined value in subsequent patches. - ifaceobjcurr.update_config_with_status(k, v, 2) - else: - ifaceobjcurr.update_config_with_status(k, 'notfound', 1) - continue - elif v != rv: - ifaceobjcurr.update_config_with_status(k, rv, 1) - else: - ifaceobjcurr.update_config_with_status(k, rv, 0) - - self._query_check_bridge_vidinfo(ifaceobj, ifaceobjcurr) - - self._query_check_mcqv4src(ifaceobj, ifaceobjcurr) - - def get_ifaceobj_bridge_vids(self, ifaceobj): - vids = ('bridge-vids', ifaceobj.get_attr_value_first('bridge-vids')) - if not vids[1]: - vids = ('bridge-trunk', ifaceobj.get_attr_value_first('bridge-trunk')) - return vids - - def get_ifaceobj_bridge_vids_value(self, ifaceobj): - return self.get_ifaceobj_bridge_vids(ifaceobj)[1] - - def _get_bridge_vids(self, bridgename, ifaceobj_getfunc): - ifaceobjs = ifaceobj_getfunc(bridgename) - for ifaceobj in ifaceobjs: - vids = self.get_ifaceobj_bridge_vids_value(ifaceobj) - if vids: return re.split(r'[\s\t,]\s*', vids) - return None - - def _get_bridge_pvid(self, bridgename, ifaceobj_getfunc): - ifaceobjs = ifaceobj_getfunc(bridgename) - pvid = None - for ifaceobj in ifaceobjs: - pvid = ifaceobj.get_attr_value_first('bridge-pvid') - if pvid: - break - return pvid - - def _get_bridge_name(self, ifaceobj): - return self.ipcmd.bridge_port_get_bridge_name(ifaceobj.name) - - def _query_check_bridge_port_vidinfo(self, ifaceobj, ifaceobjcurr, - ifaceobj_getfunc, bridgename): - attr_name = 'bridge-access' - vid = ifaceobj.get_attr_value_first(attr_name) - if vid: - (running_vids, running_pvid) = self._get_running_vids_n_pvid_str( - ifaceobj.name) - if (not running_pvid or running_pvid != vid or - (running_vids and running_vids[0] != vid)): - ifaceobjcurr.update_config_with_status(attr_name, - running_pvid, 1) - else: - ifaceobjcurr.update_config_with_status(attr_name, vid, 0) - return - - (running_vids, running_pvid) = self._get_running_vids_n_pvid_str( - ifaceobj.name) - attr_name = 'bridge-pvid' - pvid = ifaceobj.get_attr_value_first('bridge-pvid') - if pvid: - if running_pvid and running_pvid == pvid: - ifaceobjcurr.update_config_with_status(attr_name, - running_pvid, 0) - else: - ifaceobjcurr.update_config_with_status(attr_name, - running_pvid, 1) - elif (not (ifaceobj.flags & iface.HAS_SIBLINGS) or - ((ifaceobj.flags & iface.HAS_SIBLINGS) and - (ifaceobj.flags & iface.OLDEST_SIBLING))): - # if the interface has multiple iface sections, - # we check the below only for the oldest sibling - # or the last iface section - pvid = self._get_bridge_pvid(bridgename, ifaceobj_getfunc) - if pvid: - if not running_pvid or running_pvid != pvid: - ifaceobjcurr.status = ifaceStatus.ERROR - ifaceobjcurr.status_str = 'bridge pvid error' - elif not running_pvid or running_pvid != '1': - ifaceobjcurr.status = ifaceStatus.ERROR - ifaceobjcurr.status_str = 'bridge pvid error' - - attr_name, vids = self.get_ifaceobj_bridge_vids(ifaceobj) - if vids: - vids = re.split(r'[\s\t]\s*', vids) - if not running_vids or not self._compare_vids(vids, running_vids, - running_pvid): - ifaceobjcurr.update_config_with_status(attr_name, - ' '.join(running_vids), 1) - else: - ifaceobjcurr.update_config_with_status(attr_name, - ' '.join(vids), 0) - elif (not (ifaceobj.flags & iface.HAS_SIBLINGS) or - ((ifaceobj.flags & iface.HAS_SIBLINGS) and - (ifaceobj.flags & iface.OLDEST_SIBLING))): - # if the interface has multiple iface sections, - # we check the below only for the oldest sibling - # or the last iface section - - # check if it matches the bridge vids - bridge_vids = self._get_bridge_vids(bridgename, ifaceobj_getfunc) - if (bridge_vids and (not running_vids or - not self._compare_vids(bridge_vids, running_vids, running_pvid))): - ifaceobjcurr.status = ifaceStatus.ERROR - ifaceobjcurr.status_str = 'bridge vid error' - - def _query_check_bridge_port(self, ifaceobj, ifaceobjcurr, - ifaceobj_getfunc): - if not self._is_bridge_port(ifaceobj): - # Mark all bridge attributes as failed - ifaceobjcurr.check_n_update_config_with_status_many(ifaceobj, - ['bridge-vids', 'bridge-trunk', 'bridge-pvid', 'bridge-access', - 'bridge-pathcosts', 'bridge-portprios', - 'bridge-portmcrouter', - 'bridge-portmcfl'], 1) - return - bridgename = self._get_bridge_name(ifaceobj) - if not bridgename: - self.logger.warn('%s: unable to determine bridge name' - %ifaceobj.name) - return - - if self.ipcmd.bridge_is_vlan_aware(bridgename): - self._query_check_bridge_port_vidinfo(ifaceobj, ifaceobjcurr, - ifaceobj_getfunc, - bridgename) - for attr, dstattr in {'bridge-pathcosts' : 'pathcost', - 'bridge-portprios' : 'portprio', - 'bridge-portmcrouter' : 'portmcrouter', - 'bridge-portmcfl' : 'portmcfl' }.items(): - attrval = ifaceobj.get_attr_value_first(attr) - if not attrval: - continue - - try: - running_attrval = self.brctlcmd.get_bridgeport_attr( - bridgename, ifaceobj.name, dstattr) - - if dstattr == 'portmcrouter' or dstattr == 'portmcfl': - if not utils.is_binary_bool(attrval) and running_attrval: - running_attrval = utils.get_yesno_boolean( - utils.get_boolean_from_string(running_attrval)) - - if running_attrval != attrval: - ifaceobjcurr.update_config_with_status(attr, - running_attrval, 1) - else: - ifaceobjcurr.update_config_with_status(attr, - running_attrval, 0) - except Exception, e: - self.log_warn('%s: %s' %(ifaceobj.name, str(e))) - - try: - config_learn = ifaceobj.get_attr_value_first('bridge-learning') - if not config_learn: - return - config_learn = utils.get_onoff_bool(config_learn) - running_learn = self.ipcmd.get_brport_learning(ifaceobj.name) - if config_learn == running_learn: - ifaceobjcurr.update_config_with_status('bridge-learning', - running_learn, 0) - else: - ifaceobjcurr.update_config_with_status('bridge-learning', - running_learn, 1) - - except Exception, e: - self.log_warn('%s: %s' %(ifaceobj.name, str(e))) - - def _query_check(self, ifaceobj, ifaceobjcurr, ifaceobj_getfunc=None): - if self._is_bridge(ifaceobj): - self._query_check_bridge(ifaceobj, ifaceobjcurr) - else: - self._query_check_bridge_port(ifaceobj, ifaceobjcurr, - ifaceobj_getfunc) - - def _query_running_bridge(self, ifaceobjrunning, ifaceobj_getfunc): - if self.ipcmd.bridge_is_vlan_aware(ifaceobjrunning.name): - ifaceobjrunning.update_config('bridge-vlan-aware', 'yes') - ifaceobjrunning.update_config_dict(self._query_running_attrs( - ifaceobjrunning, - ifaceobj_getfunc, - bridge_vlan_aware=True)) - else: - ifaceobjrunning.update_config_dict(self._query_running_attrs( - ifaceobjrunning, None)) - - def _query_running_bridge_port_attrs(self, ifaceobjrunning, bridgename): - if self.systcl_get_net_bridge_stp_user_space() == '1': - return - - v = self.brctlcmd.get_pathcost(bridgename, ifaceobjrunning.name) - if v and v != self.get_mod_subattr('bridge-pathcosts', 'default'): - ifaceobjrunning.update_config('bridge-pathcosts', v) - - v = self.brctlcmd.get_pathcost(bridgename, ifaceobjrunning.name) - if v and v != self.get_mod_subattr('bridge-portprios', 'default'): - ifaceobjrunning.update_config('bridge-portprios', v) - - def _query_running_bridge_port(self, ifaceobjrunning, - ifaceobj_getfunc=None): - - bridgename = self.ipcmd.bridge_port_get_bridge_name( - ifaceobjrunning.name) - bridge_vids = None - bridge_pvid = None - if not bridgename: - self.logger.warn('%s: unable to find bridgename' - %ifaceobjrunning.name) - return - if not self.ipcmd.bridge_is_vlan_aware(bridgename): - return - - (bridge_port_vids, bridge_port_pvid) = self._get_running_vids_n_pvid_str( - ifaceobjrunning.name) - if bridge_port_vids and bridge_port_pvid in bridge_port_vids: - bridge_port_vids.remove(bridge_port_pvid) - - bridgeifaceobjlist = ifaceobj_getfunc(bridgename) - if bridgeifaceobjlist: - bridge_vids = bridgeifaceobjlist[0].get_attr_value('bridge-vids') - bridge_pvid = bridgeifaceobjlist[0].get_attr_value_first('bridge-pvid') - - if not bridge_port_vids and bridge_port_pvid: - # must be an access port - if bridge_port_pvid != '1': - ifaceobjrunning.update_config('bridge-access', - bridge_port_pvid) - else: - if bridge_port_vids: - if (not bridge_vids or bridge_port_vids != bridge_vids): - ifaceobjrunning.update_config('bridge-vids', - ' '.join(bridge_port_vids)) - if bridge_port_pvid and bridge_port_pvid != '1': - if (not bridge_pvid or (bridge_port_pvid != bridge_pvid)): - ifaceobjrunning.update_config('bridge-pvid', - bridge_port_pvid) - self._query_running_bridge_port_attrs(ifaceobjrunning, bridgename) - - def _query_running(self, ifaceobjrunning, ifaceobj_getfunc=None): - if self.brctlcmd.bridge_exists(ifaceobjrunning.name): - self._query_running_bridge(ifaceobjrunning, ifaceobj_getfunc) - elif self.brctlcmd.is_bridge_port(ifaceobjrunning.name): - self._query_running_bridge_port(ifaceobjrunning, ifaceobj_getfunc) - - def _query(self, ifaceobj, **kwargs): - """ add default policy attributes supported by the module """ - if (not (ifaceobj.link_kind & ifaceLinkKind.BRIDGE) or - ifaceobj.get_attr_value_first('bridge-stp')): - return - if self.default_stp_on: - ifaceobj.update_config('bridge-stp', 'yes') - - def _query_check_support_yesno_attrs(self, runningattrs, ifaceobj): - for attrl in [['mcqifaddr', 'bridge-mcqifaddr'], - ['mcquerier', 'bridge-mcquerier'], - ['mcrouter', 'bridge-mcrouter'], - ['mcsnoop', 'bridge-mcsnoop']]: - value = ifaceobj.get_attr_value_first(attrl[1]) - if value and not utils.is_binary_bool(value): - if attrl[0] in runningattrs: - bool = utils.get_boolean_from_string(runningattrs[attrl[0]]) - runningattrs[attrl[0]] = utils.get_yesno_boolean(bool) - self._query_check_support_yesno_attr_port(runningattrs, ifaceobj, 'portmcrouter', ifaceobj.get_attr_value_first('bridge-portmcrouter')) - self._query_check_support_yesno_attr_port(runningattrs, ifaceobj, 'portmcfl', ifaceobj.get_attr_value_first('bridge-portmcfl')) - - def _query_check_support_yesno_attr_port(self, runningattrs, ifaceobj, attr, attrval): - if attrval: - portlist = self.parse_port_list(ifaceobj.name, attrval) - if portlist: - to_convert = [] - for p in portlist: - (port, val) = p.split('=') - if not utils.is_binary_bool(val): - to_convert.append(port) - for port in to_convert: - runningattrs['ports'][port][attr] = utils.get_yesno_boolean( - utils.get_boolean_from_string(runningattrs['ports'][port][attr])) - - _run_ops = {'pre-up' : _up, - 'post-down' : _down, - 'query-checkcurr' : _query_check, - 'query-running' : _query_running, - 'query' : _query} - - def get_ops(self): - """ returns list of ops supported by this module """ - return self._run_ops.keys() - - def _init_command_handlers(self): - if not self.ipcmd: - self.ipcmd = iproute2() - if not self.brctlcmd: - self.brctlcmd = brctl() - - def run(self, ifaceobj, operation, query_ifaceobj=None, - ifaceobj_getfunc=None): - """ run bridge configuration on the interface object passed as - argument. Can create bridge interfaces if they dont exist already - - Args: - **ifaceobj** (object): iface object - - **operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr', - 'query-running' - - Kwargs: - **query_ifaceobj** (object): query check ifaceobject. This is only - valid when op is 'query-checkcurr'. It is an object same as - ifaceobj, but contains running attribute values and its config - status. The modules can use it to return queried running state - of interfaces. status is success if the running state is same - as user required state in ifaceobj. error otherwise. - """ - op_handler = self._run_ops.get(operation) - if not op_handler: - return - self._init_command_handlers() - #self._flush_running_vidinfo() - if operation == 'query-checkcurr': - op_handler(self, ifaceobj, query_ifaceobj, - ifaceobj_getfunc=ifaceobj_getfunc) - else: - op_handler(self, ifaceobj, ifaceobj_getfunc=ifaceobj_getfunc) diff --git a/addons/link.py b/addons/link.py deleted file mode 100644 index 0940a69..0000000 --- a/addons/link.py +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/python - -# This should be pretty simple and might not really even need to exist. -# The key is that we need to call link_create with a type of "dummy" -# since that will translate to 'ip link add loopbackX type dummy' -# The config file should probably just indicate that the type is -# loopback or dummy. - -from ifupdown.iface import * -from ifupdownaddons.modulebase import moduleBase -from ifupdownaddons.iproute2 import iproute2 -import ifupdown.ifupdownflags as ifupdownflags -import logging - -class link(moduleBase): - _modinfo = {'mhelp' : 'create/configure link types. similar to ip-link', - 'attrs' : { - 'link-type' : - {'help' : 'type of link as in \'ip link\' command.', - 'validvals' : ['dummy', 'veth'], - 'example' : ['link-type ']}, - 'link-down' : - {'help': 'keep link down', - 'example' : ['link-down yes/no'], - 'default' : 'no', - 'validvals' : ['yes', 'no']}}} - - def __init__(self, *args, **kargs): - moduleBase.__init__(self, *args, **kargs) - self.ipcmd = None - - def _is_my_interface(self, ifaceobj): - if ifaceobj.get_attr_value_first('link-type'): - return True - return False - - def get_dependent_ifacenames(self, ifaceobj, ifacenames_all=None): - if ifaceobj.get_attr_value_first('link-down') == 'yes': - ifaceobj.link_privflags |= ifaceLinkPrivFlags.KEEP_LINK_DOWN - - def _up(self, ifaceobj): - self.ipcmd.link_create(ifaceobj.name, - ifaceobj.get_attr_value_first('link-type')) - - def _down(self, ifaceobj): - if (not ifupdownflags.flags.PERFMODE and - not self.ipcmd.link_exists(ifaceobj.name)): - return - try: - self.ipcmd.link_delete(ifaceobj.name) - except Exception, e: - self.log_warn(str(e)) - - def _query_check(self, ifaceobj, ifaceobjcurr): - if not self.ipcmd.link_exists(ifaceobj.name): - ifaceobjcurr.update_config_with_status('link-type', 'None', 1) - else: - link_type = ifaceobj.get_attr_value_first('link-type') - if self.ipcmd.link_get_kind(ifaceobj.name) == link_type: - ifaceobjcurr.update_config_with_status('link-type', - link_type, 0) - else: - ifaceobjcurr.update_config_with_status('link-type', - link_type, 1) - - _run_ops = {'pre-up' : _up, - 'post-down' : _down, - 'query-checkcurr' : _query_check} - - def get_ops(self): - return self._run_ops.keys() - - def _init_command_handlers(self): - if not self.ipcmd: - self.ipcmd = iproute2() - - def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): - op_handler = self._run_ops.get(operation) - if not op_handler: - return - if (operation != 'query-running' and - not self._is_my_interface(ifaceobj)): - return - self._init_command_handlers() - if operation == 'query-checkcurr': - op_handler(self, ifaceobj, query_ifaceobj) - else: - op_handler(self, ifaceobj) diff --git a/addons/loopback.py b/addons/loopback.py deleted file mode 100644 index dd35917..0000000 --- a/addons/loopback.py +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/python - -# This should be pretty simple and might not really even need to exist. -# The key is that we need to call link_create with a type of "dummy" -# since that will translate to 'ip link add loopbackX type dummy' -# The config file should probably just indicate that the type is -# loopback or dummy. - -from ifupdown.iface import * -from ifupdownaddons.modulebase import moduleBase -from ifupdownaddons.iproute2 import iproute2 -import logging - -class loopback(moduleBase): - _modinfo = {'mhelp' : 'configure extra loopback module based on ' + - 'dummy device' } - - def __init__(self, *args, **kargs): - moduleBase.__init__(self, *args, **kargs) - self.ipcmd = None - - def _is_loopback_by_name(self, ifacename): - return 'loop' in ifacename - - def _up(self, ifaceobj): - if self._is_loopback_by_name(ifaceobj.name): - self.ipcmd.link_create(ifaceobj.name, 'dummy') - - def _down(self, ifaceobj): - if not self.PERFMODE and not self.ipcmd.link_exists(ifaceobj.name): - return - try: - self.ipcmd.link_delete(ifaceobj.name) - except Exception, e: - self.log_warn(str(e)) - - def _query_check(self, ifaceobj, ifaceobjcurr): - if not self.ipcmd.link_exists(ifaceobj.name): - return - - _run_ops = {'pre-up' : _up, - 'post-down' : _down, - 'query-checkcurr' : _query_check} - - def get_ops(self): - return self._run_ops.keys() - - def _init_command_handlers(self): - if not self.ipcmd: - self.ipcmd = iproute2(**self.get_flags()) - - def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): - op_handler = self._run_ops.get(operation) - if not op_handler: - return - if (operation != 'query-running' and - not self._is_loopback_by_name(ifaceobj.name)): - return - self._init_command_handlers() - if operation == 'query-checkcurr': - op_handler(self, ifaceobj, query_ifaceobj) - else: - op_handler(self, ifaceobj) diff --git a/addons/ppp.py b/addons/ppp.py deleted file mode 100644 index fe983d0..0000000 --- a/addons/ppp.py +++ /dev/null @@ -1,140 +0,0 @@ -#!/usr/bin/python - -try: - import os - import ifupdown.statemanager as statemanager - import ifupdown.ifupdownflags as ifupdownflags - import logging - import hashlib - from ifupdown.iface import * - from ifupdown.utils import utils - from ifupdownaddons.modulebase import moduleBase - from ifupdownaddons.iproute2 import iproute2 - from ifupdown.netlink import netlink - from ifupdown.exceptions import moduleNotSupported -except ImportError, e: - raise ImportError (str(e) + "- required module not found") - -class ppp (moduleBase): - _modinfo = { 'mhelp' : 'create/configure ppp interfaces', - 'attrs' : { - 'provider' : - { 'help' : 'Provider file in ppp', - 'validvals' : [''], - 'required' : True, - 'example' : ['dsl-provider']}, - 'ppp-physdev' : - { 'help' : 'Physical underlay device to use for ppp if any', - 'validvals' : [''], - 'required' : False, - 'example' : ['ppp-physdev eth1']}, - } - } - - - def __init__ (self, *args, **kargs): - moduleBase.__init__ (self, *args, **kargs) - if not os.path.exists('/usr/bin/pon'): - raise moduleNotSupported('module init failed: no /usr/bin/pon found') - self.ipcmd = None - - def _is_my_interface (self, ifaceobj): - if ifaceobj.addr_method == "ppp" and ifaceobj.get_attr_value_first ('provider'): - return True - return False - - def _up (self, ifaceobj): - ''' - Up the PPP connection - ''' - provider = ifaceobj.get_attr_value_first ('provider') - old_config = None - old_provider = None - - try: - ppp_file = os.path.join('/etc/ppp/peers', provider) - if not os.path.isfile(ppp_file): - self.log_warn('Invalid ppp provider file does not exist') - return - - # Load state data - saved_ifaceobjs = statemanager.statemanager_api.get_ifaceobjs(ifaceobj.name) - if saved_ifaceobjs: - old_provider = saved_ifaceobjs[0].get_attr_value_first ('provider') - old_config = saved_ifaceobjs[0].get_attr_value_first ('provider_file') - - config = hashlib.sha256(open(ppp_file, 'rb').read()).hexdigest() - # Always save the current config files hash - ifaceobj.update_config('provider_file', config) - - if not self.ipcmd.link_exists(ifaceobj.name): - utils.exec_commandl(['/usr/bin/pon', provider], stdout=None, stderr=None) - elif old_config and old_config != config: - # Restart on config change - utils.exec_commandl(['/usr/bin/poff', provider], stdout=None, stderr=None) - utils.exec_commandl(['/usr/bin/pon', provider], stdout=None, stderr=None) - elif old_provider and old_provider != provider: - # Restart on provider change - utils.exec_commandl(['/usr/bin/poff', old_provider], stdout=None, stderr=None) - utils.exec_commandl(['/usr/bin/pon', provider], stdout=None, stderr=None) - - except Exception, e: - self.log_warn (str (e)) - - def _down (self, ifaceobj): - if not ifupdownflags.flags.PERFMODE and not self.ipcmd.link_exists (ifaceobj.name): - return - try: - provider = ifaceobj.get_attr_value_first ('provider') - utils.exec_commandl(['/usr/bin/poff', provider], stdout=None, stderr=None) - except Exception, e: - self.log_warn (str (e)) - - def get_dependent_ifacenames(self, ifaceobj, ifacenames_all=None): - if not self._is_my_interface(ifaceobj): - return None - - device = ifaceobj.get_attr_value_first ('ppp-physdev') - if device: - return [device] - - return None - - def _query_check (self, ifaceobj, ifaceobjcurr): - if not self.ipcmd.link_exists(ifaceobj.name): - return - - ifaceobjcurr.status = ifaceStatus.SUCCESS - - def _query_running(self, ifaceobjrunning): - if not self.ipcmd.link_exists(ifaceobjrunning.name): - return - - # Operations supported by this addon (yet). - _run_ops = { - 'pre-up' : _up, - 'post-down' : _down, - 'query-checkcurr' : _query_check, - 'query-running' : _query_running, - } - - def get_ops (self): - return self._run_ops.keys() - - def _init_command_handlers (self): - if not self.ipcmd: - self.ipcmd = iproute2 () - - def run (self, ifaceobj, operation, query_ifaceobj = None, **extra_args): - op_handler = self._run_ops.get (operation) - if not op_handler: - return - - if operation != 'query-running' and not self._is_my_interface (ifaceobj): - return - - self._init_command_handlers () - if operation == 'query-checkcurr': - op_handler (self, ifaceobj, query_ifaceobj) - else: - op_handler (self, ifaceobj) diff --git a/addons/tunnel.py b/addons/tunnel.py deleted file mode 100644 index 4242b08..0000000 --- a/addons/tunnel.py +++ /dev/null @@ -1,192 +0,0 @@ -#!/usr/bin/python -# -# Maximilian Wilhelm -# -- Mon 10 Oct 2016 10:53:13 PM CEST -# - -from ifupdown.iface import * -from ifupdownaddons.modulebase import moduleBase -from ifupdownaddons.iproute2 import iproute2 -from ifupdown.netlink import netlink -import ifupdown.ifupdownflags as ifupdownflags -import logging - -# -# TODO: Add checks for ipip tunnels. -# -class tunnel (moduleBase): - _modinfo = { 'mhelp' : 'create/configure GRE/IPIP/SIT and GRETAP tunnel interfaces', - 'attrs' : { - 'mode' : - { 'help' : 'type of tunnel as in \'ip link\' command.', - 'validvals' : ['gre', 'gretap', 'ipip', 'sit', 'vti', 'ip6gre', 'ipip6', 'ip6ip6', 'vti6'], - 'required' : True, - 'example' : ['mode gre']}, - 'local' : - { 'help' : 'IP of local tunnel endpoint', - 'validvals' : ['', ''], - 'required' : True, - 'example' : ['local 192.2.0.42']}, - 'endpoint' : - { 'help' : 'IP of remote tunnel endpoint', - 'validvals' : ['', ''], - 'required' : True, - 'example' : ['endpoint 192.2.0.23']}, - 'ttl' : - { 'help' : 'TTL for tunnel packets', - 'validvals' : [''], - 'required' : False, - 'example' : ['ttl 64']}, - 'tunnel-physdev' : - { 'help' : 'Physical underlay device to use for tunnel packets', - 'validvals' : [''], - 'required' : False, - 'example' : ['tunnel-physdev eth1']}, - } - } - - - def __init__ (self, *args, **kargs): - moduleBase.__init__ (self, *args, **kargs) - self.ipcmd = None - - - def _is_my_interface (self, ifaceobj): - if ifaceobj.addr_method == "tunnel" and ifaceobj.get_attr_value_first ('mode'): - return True - return False - - - def _has_config_changed (self, attrs_present, attrs_configured): - for attr in set (attrs_present.keys () + attrs_configured.keys ()): - # 'mode' is not present in attrs_configured and checked explicitly - if attr == "mode": - continue - - if attrs_present.get (attr, None) != attrs_configured.get (attr, None): - return True - - return False - - - def _up (self, ifaceobj): - attr_map = { - # attr_name -> ip route param name - 'local' : 'local', - 'endpoint' : 'remote', - 'ttl' : 'ttl', - 'tunnel-physdev' : 'dev', - } - - mode = ifaceobj.get_attr_value_first ('mode') - attrs = {} - attrs_mapped = {} - - # Only include attributes which have been set and map ifupdown2 names - # to attribute names expected by iproute - for attr, iproute_attr in attr_map.items (): - attr_val = ifaceobj.get_attr_value_first (attr) - if attr_val != None: - attrs_mapped[iproute_attr] = attr_val - attrs[attr] = attr_val - - # Create the tunnel if it doesn't exist yet... - if not self.ipcmd.link_exists(ifaceobj.name): - self.ipcmd.tunnel_create (ifaceobj.name, mode, attrs_mapped) - return - - # If it's present, check if there were changes - current_attrs = self.ipcmd.link_get_linkinfo_attrs (ifaceobj.name) - - try: - if current_attrs and current_attrs['mode'] != mode or self._has_config_changed (current_attrs, attrs): - # Mode and some other changes are not possible without recreating the interface, - # so just recreate it IFF there have been changes. - self.ipcmd.link_delete (ifaceobj.name) - self.ipcmd.tunnel_create (ifaceobj.name, mode, attrs_mapped) - except Exception, e: - self.log_warn (str (e)) - - - def _down (self, ifaceobj): - if not ifupdownflags.flags.PERFMODE and not self.ipcmd.link_exists (ifaceobj.name): - return - try: - self.ipcmd.link_delete (ifaceobj.name) - except Exception, e: - self.log_warn (str (e)) - - - def get_dependent_ifacenames (self, ifaceobj, ifacenames_all=None): - if not self._is_my_interface (ifaceobj): - return None - - device = ifaceobj.get_attr_value_first ('tunnel-physdev') - if device: - return [device] - - return None - - - def _query_check_n_update (self, ifaceobj, ifaceobjcurr, attrname, attrval, - running_attrval): - if not ifaceobj.get_attr_value_first (attrname): - return - - if running_attrval and attrval == running_attrval: - ifaceobjcurr.update_config_with_status (attrname, attrval, 0) - else: - ifaceobjcurr.update_config_with_status (attrname, running_attrval, 1) - - - def _query_check (self, ifaceobj, ifaceobjcurr): - if not self.ipcmd.link_exists (ifaceobj.name): - return - - tunattrs = self.ipcmd.link_get_linkinfo_attrs (ifaceobj.name) - if not tunattrs: - ifaceobjcurr.check_n_update_config_with_status_many (ifaceobj, self.get_mod_attrs (), -1) - return - - for attr in self.get_mod_attrs (): - if not ifaceobj.get_attr_value_first (attr): - continue - - # Validate all interface attributes set in the config. - # Remote any leading 'tunnel-' prefix in front of the attr name - # when accessing tunattrs parsed from 'ip -d link'. - self._query_check_n_update (ifaceobj, ifaceobjcurr, attr, - ifaceobj.get_attr_value_first (attr), - tunattrs.get (attr.replace ("tunnel-", ""))) - - - # Operations supported by this addon (yet). - _run_ops = { - 'pre-up' : _up, - 'post-down' : _down, - 'query-checkcurr' : _query_check - } - - - def get_ops (self): - return self._run_ops.keys() - - - def _init_command_handlers (self): - if not self.ipcmd: - self.ipcmd = iproute2 () - - - def run (self, ifaceobj, operation, query_ifaceobj = None, **extra_args): - op_handler = self._run_ops.get (operation) - if not op_handler: - return - - if operation != 'query-running' and not self._is_my_interface (ifaceobj): - return - - self._init_command_handlers () - if operation == 'query-checkcurr': - op_handler (self, ifaceobj, query_ifaceobj) - else: - op_handler (self, ifaceobj) diff --git a/addons/vxlan.py b/addons/vxlan.py deleted file mode 100644 index c00fa82..0000000 --- a/addons/vxlan.py +++ /dev/null @@ -1,308 +0,0 @@ -#!/usr/bin/python - -from ifupdown.iface import * -from ifupdown.utils import utils -from ifupdownaddons.modulebase import moduleBase -from ifupdownaddons.iproute2 import iproute2 -from ifupdownaddons.systemutils import systemUtils -from ifupdown.netlink import netlink -from ipaddr import IPv4Address -import ifupdown.ifupdownflags as ifupdownflags -import logging -import ifupdown.policymanager as policymanager -import os -from sets import Set - -class vxlan(moduleBase): - _modinfo = {'mhelp' : 'vxlan module configures vxlan interfaces.', - 'attrs' : { - 'vxlan-id' : - {'help' : 'vxlan id', - 'validrange' : ['1', '16777214'], - 'required' : True, - 'example': ['vxlan-id 100']}, - 'vxlan-local-tunnelip' : - {'help' : 'vxlan local tunnel ip', - 'validvals' : [''], - 'example': ['vxlan-local-tunnelip 172.16.20.103']}, - 'vxlan-svcnodeip' : - {'help' : 'vxlan id', - 'validvals' : [''], - 'example': ['vxlan-svcnodeip 172.16.22.125']}, - 'vxlan-remoteip' : - {'help' : 'vxlan remote ip', - 'validvals' : [''], - 'example': ['vxlan-remoteip 172.16.22.127']}, - 'vxlan-physdev' : - {'help' : 'vxlan physical device', - 'example': ['vxlan-physdev eth1']}, - 'vxlan-learning' : - {'help' : 'vxlan learning yes/no', - 'validvals' : ['yes', 'no', 'on', 'off'], - 'example': ['vxlan-learning no'], - 'default': 'yes'}, - 'vxlan-ageing' : - {'help' : 'vxlan aging timer', - 'validrange' : ['0', '4096'], - 'example': ['vxlan-ageing 300'], - 'default': '300'}, - 'vxlan-purge-remotes' : - {'help' : 'vxlan purge existing remote entries', - 'validvals' : ['yes', 'no'], - 'example': ['vxlan-purge-remotes yes']} - }} - _clagd_vxlan_anycast_ip = "" - - def __init__(self, *args, **kargs): - moduleBase.__init__(self, *args, **kargs) - self.ipcmd = None - purge_remotes = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vxlan-purge-remotes') - if purge_remotes: - self._purge_remotes = utils.get_boolean_from_string(purge_remotes) - else: - self._purge_remotes = False - - def get_dependent_ifacenames(self, ifaceobj, ifaceobjs_all=None): - if self._is_vxlan_device(ifaceobj): - ifaceobj.link_kind |= ifaceLinkKind.VXLAN - elif ifaceobj.name == 'lo': - clagd_vxlan_list = ifaceobj.get_attr_value('clagd-vxlan-anycast-ip') - if clagd_vxlan_list: - if len(clagd_vxlan_list) != 1: - self.log_warn('%s: multiple clagd-vxlan-anycast-ip lines, using first one' - % (ifaceobj.name,)) - vxlan._clagd_vxlan_anycast_ip = clagd_vxlan_list[0] - - - # If we should use a specific underlay device for the VXLAN - # tunnel make sure this device is set up before the VXLAN iface. - physdev = ifaceobj.get_attr_value_first('vxlan-physdev') - if physdev: - return [ physdev ] - - return None - - def _is_vxlan_device(self, ifaceobj): - if ifaceobj.get_attr_value_first('vxlan-id'): - return True - return False - - def _get_purge_remotes(self, ifaceobj): - if not ifaceobj: - return self._purge_remotes - purge_remotes = ifaceobj.get_attr_value_first('vxlan-purge-remotes') - if purge_remotes: - purge_remotes = utils.get_boolean_from_string(purge_remotes) - else: - purge_remotes = self._purge_remotes - return purge_remotes - - def _vxlan_create(self, ifaceobj): - vxlanid = ifaceobj.get_attr_value_first('vxlan-id') - if vxlanid: - anycastip = self._clagd_vxlan_anycast_ip - group = ifaceobj.get_attr_value_first('vxlan-svcnodeip') - local = ifaceobj.get_attr_value_first('vxlan-local-tunnelip') - physdev = ifaceobj.get_attr_value_first('vxlan-physdev') - ageing = ifaceobj.get_attr_value_first('vxlan-ageing') - learning = utils.get_onoff_bool(ifaceobj.get_attr_value_first('vxlan-learning')) - purge_remotes = self._get_purge_remotes(ifaceobj) - - if self.ipcmd.link_exists(ifaceobj.name): - vxlanattrs = self.ipcmd.get_vxlandev_attrs(ifaceobj.name) - # on ifreload do not overwrite anycast_ip to individual ip - # if clagd has modified - if vxlanattrs: - running_localtunnelip = vxlanattrs.get('local') - if (anycastip and running_localtunnelip and - anycastip == running_localtunnelip): - local = running_localtunnelip - - netlink.link_add_vxlan(ifaceobj.name, vxlanid, - local=local, - learning=learning, - ageing=ageing, - group=group, - physdev=physdev) - - remoteips = ifaceobj.get_attr_value('vxlan-remoteip') - if purge_remotes or remoteips: - # figure out the diff for remotes and do the bridge fdb updates - # only if provisioned by user and not by an vxlan external - # controller. - peers = self.ipcmd.get_vxlan_peers(ifaceobj.name, group) - if local and remoteips and local in remoteips: - remoteips.remove(local) - cur_peers = set(peers) - if remoteips: - new_peers = set(remoteips) - del_list = cur_peers.difference(new_peers) - add_list = new_peers.difference(cur_peers) - else: - del_list = cur_peers - add_list = [] - - for addr in del_list: - try: - self.ipcmd.bridge_fdb_del(ifaceobj.name, - '00:00:00:00:00:00', - None, True, addr) - except: - pass - - for addr in add_list: - try: - self.ipcmd.bridge_fdb_append(ifaceobj.name, - '00:00:00:00:00:00', - None, True, addr) - except: - pass - - def _up(self, ifaceobj): - self._vxlan_create(ifaceobj) - - def _down(self, ifaceobj): - try: - self.ipcmd.link_delete(ifaceobj.name) - except Exception, e: - self.log_warn(str(e)) - - def _query_check_n_update(self, ifaceobj, ifaceobjcurr, attrname, attrval, - running_attrval): - if not ifaceobj.get_attr_value_first(attrname): - return - if running_attrval and attrval == running_attrval: - ifaceobjcurr.update_config_with_status(attrname, attrval, 0) - else: - ifaceobjcurr.update_config_with_status(attrname, running_attrval, 1) - - def _query_check_n_update_addresses(self, ifaceobjcurr, attrname, - addresses, running_addresses): - if addresses: - for a in addresses: - if a in running_addresses: - ifaceobjcurr.update_config_with_status(attrname, a, 0) - else: - ifaceobjcurr.update_config_with_status(attrname, a, 1) - running_addresses = Set(running_addresses).difference( - Set(addresses)) - [ifaceobjcurr.update_config_with_status(attrname, a, 1) - for a in running_addresses] - - def _query_check(self, ifaceobj, ifaceobjcurr): - if not self.ipcmd.link_exists(ifaceobj.name): - return - # Update vxlan object - vxlanattrs = self.ipcmd.get_vxlandev_attrs(ifaceobj.name) - if not vxlanattrs: - ifaceobjcurr.check_n_update_config_with_status_many(ifaceobj, - self.get_mod_attrs(), -1) - return - self._query_check_n_update(ifaceobj, ifaceobjcurr, 'vxlan-id', - ifaceobj.get_attr_value_first('vxlan-id'), - vxlanattrs.get('vxlanid')) - - running_attrval = vxlanattrs.get('local') - attrval = ifaceobj.get_attr_value_first('vxlan-local-tunnelip') - if running_attrval == self._clagd_vxlan_anycast_ip: - # if local ip is anycast_ip, then let query_check to go through - attrval = self._clagd_vxlan_anycast_ip - self._query_check_n_update(ifaceobj, ifaceobjcurr, 'vxlan-local-tunnelip', - attrval, running_attrval) - - self._query_check_n_update(ifaceobj, ifaceobjcurr, 'vxlan-svcnodeip', - ifaceobj.get_attr_value_first('vxlan-svcnodeip'), - vxlanattrs.get('svcnode')) - - purge_remotes = self._get_purge_remotes(ifaceobj) - if purge_remotes or ifaceobj.get_attr_value('vxlan-remoteip'): - # If purge remotes or if vxlan-remoteip's are set - # in the config file, we are owners of the installed - # remote-ip's, lets check and report any remote ips we don't - # understand - self._query_check_n_update_addresses(ifaceobjcurr, 'vxlan-remoteip', - ifaceobj.get_attr_value('vxlan-remoteip'), - vxlanattrs.get('remote', [])) - - learning = ifaceobj.get_attr_value_first('vxlan-learning') - if not learning: - learning = 'on' - - running_learning = vxlanattrs.get('learning') - if learning == 'yes' and running_learning == 'on': - running_learning = 'yes' - elif learning == 'no' and running_learning == 'off': - running_learning = 'no' - - if learning == running_learning: - ifaceobjcurr.update_config_with_status('vxlan-learning', - running_learning, 0) - else: - ifaceobjcurr.update_config_with_status('vxlan-learning', - running_learning, 1) - ageing = ifaceobj.get_attr_value_first('vxlan-ageing') - if not ageing: - ageing = self.get_mod_subattr('vxlan-ageing', 'default') - self._query_check_n_update(ifaceobj, ifaceobjcurr, 'vxlan-ageing', - ageing, vxlanattrs.get('ageing')) - - physdev = ifaceobj.get_attr_value_first('vxlan-physdev') - if physdev: - self._query_check_n_update(ifaceobj, ifaceobjcurr, 'vxlan-physdev', - physdev, vxlanattrs.get('physdev')) - - def _query_running(self, ifaceobjrunning): - vxlanattrs = self.ipcmd.get_vxlandev_attrs(ifaceobjrunning.name) - if not vxlanattrs: - return - attrval = vxlanattrs.get('vxlanid') - if attrval: - ifaceobjrunning.update_config('vxlan-id', vxlanattrs.get('vxlanid')) - else: - # if there is no vxlan id, this is not a vxlan port - return - attrval = vxlanattrs.get('local') - if attrval: - ifaceobjrunning.update_config('vxlan-local-tunnelip', attrval) - attrval = vxlanattrs.get('svcnode') - if attrval: - ifaceobjrunning.update_config('vxlan-svcnode', attrval) - purge_remotes = self._get_purge_remotes(None) - if purge_remotes: - # if purge_remotes is on, it means we own the - # remote ips. Query them and add it to the running config - attrval = vxlanattrs.get('remote') - if attrval: - [ifaceobjrunning.update_config('vxlan-remoteip', a) - for a in attrval] - attrval = vxlanattrs.get('learning') - if attrval and attrval == 'on': - ifaceobjrunning.update_config('vxlan-learning', 'on') - attrval = vxlanattrs.get('ageing') - if attrval: - ifaceobjrunning.update_config('vxlan-ageing', vxlanattrs.get('ageing')) - - _run_ops = {'pre-up' : _up, - 'post-down' : _down, - 'query-checkcurr' : _query_check, - 'query-running' : _query_running} - - def get_ops(self): - return self._run_ops.keys() - - def _init_command_handlers(self): - if not self.ipcmd: - self.ipcmd = iproute2() - - def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): - op_handler = self._run_ops.get(operation) - if not op_handler: - return - if (operation != 'query-running' and - not self._is_vxlan_device(ifaceobj)): - return - self._init_command_handlers() - if operation == 'query-checkcurr': - op_handler(self, ifaceobj, query_ifaceobj) - else: - op_handler(self, ifaceobj) diff --git a/build.sh b/build.sh deleted file mode 100755 index 9fd4f2b..0000000 --- a/build.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -TOPDIR=. - -${TOPDIR}/scripts/genmanpages.sh ${TOPDIR}/man.rst ${TOPDIR}/man - -python setup.py --command-packages=stdeb.command sdist_dsc bdist_deb - diff --git a/debian/changelog b/debian/changelog index e0ae2f8..772b13c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,51 +1,144 @@ -ifupdown2 (1.1-cl3u10) UNRELEASED; urgency=medium +ifupdown2 (2.0.0) UNRELEASED; urgency=medium + + * Package architecture refactoring and cleanups + * Package can be build/install as debian, pip or rpm package + * Makefile to easily perform tasks (i.e.: install, build, test, upload..) + * New. Enabled: traditional bridge support for mstpctl attributes + (portautoedge, portrestrrole) + + -- Julien Fortin Thu, 24 May 2018 15:42:42 +0200 + +ifupdown2 (1.1.18) RELEASED; urgency=medium + + * Fix: Link down does not work on SVI configured in a VRF + * Fix: ifreload causes MTU to drop on bridge SVIs + * Fix: addons: addressvirtual: check if SVI name is first in routing table + * Fix: ifreload error on deleting bond slaves from an already configured bond + * Fix: ifupdown2 error is confusing when netmask is specified for vxlan-local-tunnelip + * Fix: ifupdown2 syntax check needed for vxlan interfaces + * Fix: vxlan-ageing default timer doesn't align with bridge-ageing + * Fix: Error with "ifreload -a -n" when MGMT VRF is not Applied + * Fix: using reserved VLAN range reports error but ifreload returns 0 + * Fix: unable to set bridge-portmcrouter to "2" + * Fix: vxlan syntax-check warn on missing vxlan-local-tunnelip + * Fix: traditional bridge svi breaks when extra bridge added + * Fix: github #39: addons: vrf: fix vrf slave link kind + * New. Enabled: addons: vxlan: add support for vxlan-port attribute + + -- Julien Fortin Thu, 12 Apr 2018 11:10:04 +0200 + +ifupdown2 (1.1.17) RELEASED; urgency=medium + + * Fix: ip[6]-forward attributes not set at boot + + -- Julien Fortin Thu, 08 Feb 2018 09:48:37 +0100 + +ifupdown2 (1.1.16) RELEASED; urgency=medium + + * Fix: python exception on macvlans address dump + * Fix: eth0 doesn't acquire DHCP address when mgmt VRF is enabled + + -- Julien Fortin Tue, 09 Jan 2018 02:02:58 +0100 + +ifupdown2 (1.1.15) RELEASED; urgency=medium + + * New. Enabled: bridge: add support for bridge-l2protocol-tunnel + * New. Enabled: bridge attributes, when removed reset to default + * New. Enabled: vxlan attributes, when removed reset to default + * New. Enabled: improve handling of optional resources (if missing bridge-utils/ethtool) + * Fix: policy "iface_defaults" not supported for MTU + * Fix: address module: handling of ipv4 & ipv6 (add/remove) + * Fix: warning for vlan reserved range + * Fix: MTU handling on bridge SVIs + + -- Julien Fortin Wed, 22 Nov 2017 19:07:43 +0100 + +ifupdown2 (1.1.14) RELEASED; urgency=medium + + * New. Enabled: default policy for bridge MAC address + * Fix: ethtool: don't set link speed and duplex if autoneg is on + + -- Julien Fortin Wed, 25 Oct 2017 23:12:27 +0200 + +ifupdown2 (1.1.13) RELEASED; urgency=medium + + * Fix: VRF: ssh session not killed on ifreload + + -- Julien Fortin Fri, 15 Sep 2017 22:43:12 +0200 + +ifupdown2 (1.1.12) RELEASED; urgency=medium + + * New. Enabled: mpls-enable attribute + * New. Enabled: bond and bridge module moved to netlink configuration + * New. Enabled: handle a mix of auto and specified vrf table ids + * Fix: igmp attribute reset to defaults when removed from config + + -- Julien Fortin Mon, 07 Aug 2017 22:14:03 +0200 + +ifupdown2 (1.1.11) RELEASED; urgency=medium + + * Fix: link-down attribute not applied on slave ports + * Fix: bug that prevented config of mtu below 1280 + + -- Julien Fortin Thu, 18 May 2017 12:45:21 -0700 + +ifupdown2 (1.1.10) RELEASED; urgency=medium * New. Enabled: drop the dependency to pkg_resources, hardcode version number (closes: #855401) * New. Enabled: adjust allow-hotplug behavior to ifupdown (closes: #855598) - * Closes: CM-8424. bond: adding attribute bond-(up|down)delay + * New. Enabled: bond-lacp-rate support slow/fast argument + * New. Enabled: ifquery --syntax-help: add support for json output + * New. Enabled: vlan: add new attribute 'vlan-protocol' + * New. Enabled: address: add new attribute 'ip6-forward' + * New. Enabled: bridge: add new attribute 'bridge-mcstats' + * New. Enabled: bridge: add new attribute 'bridge-vlan-stats' + * New. Enabled: bridge: add new attribute 'bridge-vlan-protocol' + * New. Enabled: bridge: add new attribute 'bridge-arp-nd-suppress' + * Fix: bond: add attribute bond-(up|down)delay + * Fix: bridge-vids: --syntax-check accepts legacy syntax - -- Julien Fortin Thu, 23 Feb 2017 10:50:14 +0100 + -- Julien Fortin Mon, 17 Apr 2017 06:18:04 +0200 -ifupdown2 (1.1-cl3u9) RELEASED; urgency=medium +ifupdown2 (1.1.9) RELEASED; urgency=medium * New. Enabled: support for bridge-learning attribute - -- Cumulus Networks Mon, 06 Feb 2017 13:22:51 -0800 + -- Julien Fortin Mon, 06 Feb 2017 13:22:51 -0800 -ifupdown2 (1.1-cl3u8) RELEASED; urgency=medium +ifupdown2 (1.1.8) RELEASED; urgency=medium * New. Enabled: update link-speed values: add 25G and 50G * New. Enabled: new 'link-down' [yes|no] attribute to keep link down - -- dev-support Tue, 17 Jan 2017 08:39:29 +0100 + -- Julien Fortin Tue, 17 Jan 2017 08:39:29 +0100 -ifupdown2 (1.1-cl3u7) RELEASED; urgency=medium +ifupdown2 (1.1.7) RELEASED; urgency=medium * New. Enabled: fix for inet and inet6 dhcp on the same interface * New. Enabled: syntax check to warn on common configuration mistakes * New. Enabled: addons: bridge: disable ip fwding on a bridge with no ip and no upperifaces - * Closes: CM-13221: fixes for MTU handling - * Closes: CM-13248: dhcpv6 fails if interface doesn't have link-local addr + * Fix: fixes for MTU handling + * Fix: dhcpv6 fails if interface doesn't have link-local addr - -- dev-support Wed, 07 Dec 2016 05:48:45 +0100 + -- Julien Fortin Wed, 07 Dec 2016 05:48:45 +0100 -ifupdown2 (1.1-cl3u6) RELEASED; urgency=medium +ifupdown2 (1.1.6) RELEASED; urgency=medium * Closes: github #14. add environment variables passed to user scripts * New. Enabled: addons may provide a list of ifupdown scripts to ignore - -- dev-support Mon, 19 Sep 2016 16:37:36 -0700 + -- Julien Fortin Mon, 19 Sep 2016 16:37:36 -0700 -ifupdown2 (1.1-cl3u5) RELEASED; urgency=medium +ifupdown2 (1.1.5) RELEASED; urgency=medium - * Closes: CM-12798. fix handling of EXISTS errors on address add - * Closes: CM-11214. fix handling of mtu on addressvirtual macvlan devices - * Closes: CM-12884. fix mako namespace handling + * Fix: handling of EXISTS errors on address add + * Fix: handling of mtu on addressvirtual macvlan devices + * Fix: mako namespace handling - -- dev-support Fri, 16 Sep 2016 12:48:04 -0700 + -- Julien Fortin Fri, 16 Sep 2016 12:48:04 -0700 -ifupdown2 (1.1-cl3u4) RELEASED; urgency=medium +ifupdown2 (1.1.4) RELEASED; urgency=medium * Performance improvements * New. Enabled: sbin: start-networking: support hotplug class from init script @@ -53,24 +146,24 @@ ifupdown2 (1.1-cl3u4) RELEASED; urgency=medium * New. Enabled: extend ifquery support for mstpctl addons * New. Enabled: each addon may perform semantic and syntax checks by implementing a custom method - * Closes: CM-11745. Support for address-virtual lines under a vrf slave - * Closes: CM-11718. Defaults for link attributes were not applied - * Closes: CM-11511. Disable IPv6 duplicate address detection on VRR interfaces - * Closes: CM-11485. Fix ifquery to extract vlan-id from iface if not preset - * Closes: CM-8623. Fix for ifquery -c bridge pvid error on a valid config + * Fix: Support for address-virtual lines under a vrf slave + * Fix: Defaults for link attributes were not applied + * Fix: Disable IPv6 duplicate address detection on VRR interfaces + * Fix: ifquery to extract vlan-id from iface if not preset + * Fix: ifquery -c bridge pvid error on a valid config - -- dev-support Fri, 29 Jul 2016 08:55:50 -0700 + -- Julien Fortin Fri, 29 Jul 2016 08:55:50 -0700 -ifupdown2 (1.1-cl3u3) RELEASED; urgency=medium +ifupdown2 (1.1.3) RELEASED; urgency=medium - * Closes: CM-11214. Interface configuration parsing error when keyword vlan + * Fix: Interface configuration parsing error when keyword vlan is the interface name. - -- dev-support Sun, 05 Jun 2016 08:55:50 -0700 + -- Julien Fortin Sun, 05 Jun 2016 08:55:50 -0700 -ifupdown2 (1.1-cl3u2) RELEASED; urgency=medium +ifupdown2 (1.1.2) RELEASED; urgency=medium - * Closes: CM-10478. checks for invalid address-virtual attributes + * Fix: checks for invalid address-virtual attributes * New. Deprecated: `mstpctl-stp` attribute * New. Deprecated: lacp parameters: bond-ad-sys-priority, bond-ad-sys-mac-addr * New. Enabled: addon module for configuring vrf @@ -83,10 +176,10 @@ ifupdown2 (1.1-cl3u2) RELEASED; urgency=medium * New. Enabled: bridge: disabling ipv6 on bridge if any VXLAN port * New. Enabled: vrf awareness in dhcp addon module - -- dev-support Tue, 3 May 2016 14:42:42 -0700 + -- Julien Fortin Tue, 3 May 2016 14:42:42 -0700 -ifupdown2 (1.1-cl3u1) unstable; urgency=low +ifupdown2 (1.1.0) unstable; urgency=low * Initial release. - -- dev-support Thu, 20 Aug 2015 06:14:24 -0700 + -- Roopa Prabhu Thu, 20 Aug 2015 06:14:24 -0700 diff --git a/debian/clean b/debian/clean deleted file mode 100644 index 0723a0c..0000000 --- a/debian/clean +++ /dev/null @@ -1,4 +0,0 @@ -man/ifquery.8 -man/ifreload.8 -man/ifup.8 -man/ifupdown-addons-interfaces.5 diff --git a/debian/control b/debian/control index e0e68b6..a6178e4 100644 --- a/debian/control +++ b/debian/control @@ -2,18 +2,23 @@ Source: ifupdown2 Section: admin Priority: optional Maintainer: Julien Fortin +Build-Depends: debhelper (>=9), + dh-systemd, + dh-python, + python-all, + python-setuptools, + python-docutils Standards-Version: 3.9.8 -Build-Depends: python-setuptools, dh-python, python-all (>= 2.6.6-3), debhelper (>= 9~), python-docutils, dh-systemd -Homepage: https://github.com/CumulusNetworks/ifupdown2 -X-Python-Version: >= 2.6 +Homepage: https://github.com/cumulusnetworks/ifupdown2 +X-Python-Version: >= 2.7 Package: ifupdown2 Architecture: all -Suggests: python-gvgen, python-mako -Replaces: ifupdown -Conflicts: ifupdown Provides: ifupdown -Depends: ${python:Depends}, ${misc:Depends}, python-argcomplete, python-ipaddr, iproute2, isc-dhcp-client +Conflicts: ifupdown +Replaces: ifupdown +Depends: ${python:Depends}, ${misc:Depends}, iproute2, python-argcomplete, python-ipaddr +Suggests: isc-dhcp-client, bridge-utils, ethtool, python-gvgen, python-mako Description: Network Interface Management tool similar to ifupdown ifupdown2 is ifupdown re-written in Python. It replaces ifupdown and provides the same user interface as ifupdown for network interface configuration. diff --git a/debian/copyright b/debian/copyright index f52800a..51d5604 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,14 +1,14 @@ Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: ifupdown2 -Upstream-Contact: Roopa Prabhu , Julien Fortin -Source: http://www.cumulusnetworks.com +Upstream-Contact: Julien Fortin +Source: https://github.com/cumulusnetworks/ifupdown2 Files: * -Copyright: 2014 Cumulus Networks +Copyright: 2014,2015,2016,2017,2018 Cumulus Networks, Inc. License: GPL-2 Files: debian/* -Copyright: 2014 Cumulus Networks +Copyright: 2014,2015,2016,2017,2018 Cumulus Networks, Inc. License: GPL-2 License: GPL-2 @@ -25,4 +25,4 @@ License: GPL-2 along with this program. If not, see . On Debian systems, the complete text of the GNU General - Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". \ No newline at end of file + Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". diff --git a/debian/ifupdown2.bash-completion b/debian/ifupdown2.bash-completion index cf6dbfb..a136a3e 100644 --- a/debian/ifupdown2.bash-completion +++ b/debian/ifupdown2.bash-completion @@ -1 +1 @@ -completion/ifup +ifupdown2/completion/ifup diff --git a/debian/ifupdown2.dirs b/debian/ifupdown2.dirs index 675c5f6..edc63b0 100644 --- a/debian/ifupdown2.dirs +++ b/debian/ifupdown2.dirs @@ -1,3 +1,4 @@ -/etc/network/interfaces.d/ -/var/lib/ifupdown2/policy.d/ -/var/lib/ifupdown2/hooks/ +etc/network/interfaces.d/ +etc/network/ifupdown2/policy.d/ +var/lib/ifupdown2/hooks/ +var/lib/ifupdown2/policy.d/ diff --git a/debian/ifupdown2.install b/debian/ifupdown2.install index 446b2ac..7444031 100644 --- a/debian/ifupdown2.install +++ b/debian/ifupdown2.install @@ -1,3 +1,3 @@ -sbin/ifupdown2 /usr/share/ifupdown2/ -sbin/start-networking /usr/share/ifupdown2/sbin/ -debian/networking /etc/default/ +etc/default/networking /etc/default/ +etc/network/ifupdown2/addons.conf /etc/network/ifupdown2/ +etc/network/ifupdown2/ifupdown2.conf /etc/network/ifupdown2/ diff --git a/debian/ifupdown2.links b/debian/ifupdown2.links index 177c9d5..246d559 100644 --- a/debian/ifupdown2.links +++ b/debian/ifupdown2.links @@ -1,6 +1,5 @@ +usr/share/ifupdown2/__main__.py usr/share/ifupdown2/ifupdown2 usr/share/ifupdown2/ifupdown2 sbin/ifup usr/share/ifupdown2/ifupdown2 sbin/ifdown usr/share/ifupdown2/ifupdown2 sbin/ifquery usr/share/ifupdown2/ifupdown2 sbin/ifreload -usr/share/man/man8/ifup.8.gz usr/share/man/man8/ifdown.8.gz - diff --git a/debian/ifupdown2.manpages b/debian/ifupdown2.manpages index b7d0fab..7431730 100644 --- a/debian/ifupdown2.manpages +++ b/debian/ifupdown2.manpages @@ -1,4 +1,5 @@ man/ifup.8 +man/ifdown.8 man/ifquery.8 man/ifreload.8 man/ifupdown-addons-interfaces.5 diff --git a/debian/ifupdown2.networking.service b/debian/ifupdown2.networking.service index ecf5b91..06e1545 100644 --- a/debian/ifupdown2.networking.service +++ b/debian/ifupdown2.networking.service @@ -1,6 +1,9 @@ [Unit] Description=ifupdown2 networking initialization Documentation=man:interfaces(5) man:ifup(8) man:ifdown(8) +DefaultDependencies=no +Before=shutdown.target +Conflicts=shutdown.target [Service] Type=oneshot @@ -12,4 +15,4 @@ ExecStop=/usr/share/ifupdown2/sbin/start-networking stop ExecReload=/usr/share/ifupdown2/sbin/start-networking reload [Install] -WantedBy=basic.target network.target +WantedBy=basic.target network.target shutdown.target diff --git a/debian/ifupdown2.postinst b/debian/ifupdown2.postinst index 19365db..0948035 100644 --- a/debian/ifupdown2.postinst +++ b/debian/ifupdown2.postinst @@ -1,21 +1,49 @@ #!/bin/sh -# postinst script for ifupdown2 -# -# see: dh_installdeb(1) - set -e -# summary of how this script can be called: -# * `configure' -# * `abort-upgrade' -# * `abort-remove' `in-favour' -# -# * `abort-remove' -# * `abort-deconfigure' `in-favour' -# `removing' -# -# for details, see http://www.debian.org/doc/debian-policy/ or -# the debian-policy package +MYNAME="${0##*/}" + +report() { echo "${MYNAME}: $*" ; } +report_warn() { report "Warning: $*" >&2 ; } +report_err() { report "Error: $*" >&2 ; } + +fix_dhclient_file_with_space() +{ + # because of a typo an older ifupdown2 version was creating lease file + # with trailing space. In case we still have users with such files we + # need to strip that trailing whitespace. + for filename in `find /var/lib/dhcp/ -name "dhclient.*.leases "` + do + if [ -f "$filename " ]; + then + interface_name=`echo $filename | cut -d'.' -f2,3,4,5` + mv "$filename " /var/lib/dhcp/dhclient6.$interface_name + fi + done +} + +process_etc_network_interfaces() +{ + # Generic stuff done on all configurations + if [ -f /etc/network/interfaces ] ; then + if ! grep -q -E "^[[:space:]]*iface[[:space:]]+l[o0]([[:space:]]+inet([[:space:]]+loopback)?)?[[:space:]]*$" /etc/network/interfaces ; then + report_warn "No 'iface lo' definition found in /etc/network/interfaces" + fi + + if ! grep -q "^[[:space:]]*\(allow-\|\)auto[[:space:]]\+\(.*[[:space:]]\+\|\)lo0\?\([[:space:]]\+\|$\)" /etc/network/interfaces ; then + report_warn "No 'auto lo' statement found in /etc/network/interfaces" + fi + else # ! -f /etc/network/interfaces + if [ -z "$2" ]; then + echo "Creating /etc/network/interfaces." + echo "# interfaces(5) file used by ifup(8) and ifdown(8)" > /etc/network/interfaces + echo "auto lo" >> /etc/network/interfaces + echo "iface lo inet loopback" >> /etc/network/interfaces + else + report_warn "/etc/network/interfaces does not exist" + fi + fi +} process_udev() { @@ -35,52 +63,24 @@ process_udev() fi } -MYNAME="${0##*/}" - -report() { echo "${MYNAME}: $*" ; } -report_warn() { report "Warning: $*" >&2 ; } -report_err() { report "Error: $*" >&2 ; } - case "$1" in configure) - - # work around to rename the existing dhclient6 lease file containing a space - for filename in `find /var/lib/dhcp/ -name "dhclient.*.leases "` - do - if [ -f "$filename " ]; - then - interface_name=`echo $filename | cut -d'.' -f2,3,4,5` - mv "$filename " /var/lib/dhcp/dhclient6.$interface_name - fi - done - - # Generic stuff done on all configurations - if [ -f /etc/network/interfaces ] ; then - if ! grep -q -E "^[[:space:]]*iface[[:space:]]+l[o0]([[:space:]]+inet([[:space:]]+loopback)?)?[[:space:]]*$" /etc/network/interfaces ; then - report_warn "No 'iface lo' definition found in /etc/network/interfaces" - fi - - if ! grep -q "^[[:space:]]*\(allow-\|\)auto[[:space:]]\+\(.*[[:space:]]\+\|\)lo0\?\([[:space:]]\+\|$\)" /etc/network/interfaces ; then - report_warn "No 'auto lo' statement found in /etc/network/interfaces" - fi - else # ! -f /etc/network/interfaces - if [ -z "$2" ]; then - echo "Creating /etc/network/interfaces." - echo "# interfaces(5) file used by ifup(8) and ifdown(8)" > /etc/network/interfaces - echo "auto lo" >> /etc/network/interfaces - echo "iface lo inet loopback" >> /etc/network/interfaces - else - report_warn "/etc/network/interfaces does not exist" - fi - fi - + fix_dhclient_file_with_space + process_etc_network_interfaces process_udev - ;; + chmod +x /usr/share/ifupdown2/__main__.py + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; - purge) + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 ;; esac + #DEBHELPER# exit 0 diff --git a/debian/ifupdown2.postrm b/debian/ifupdown2.postrm index b66b07e..5d90b21 100644 --- a/debian/ifupdown2.postrm +++ b/debian/ifupdown2.postrm @@ -1,91 +1,45 @@ #!/bin/sh -# postrm script for ifupdown2 -# -# see: dh_installdeb(1) - set -e -# summary of how this script can be called: -# * `remove' -# * `purge' -# * `upgrade' -# * `failed-upgrade' -# * `abort-install' -# * `abort-install' -# * `abort-upgrade' -# * `disappear' -# -# for details, see http://www.debian.org/doc/debian-policy/ or -# the debian-policy package - -process_udev() -{ - if [ -e /etc/udev/rules.d/80-networking.rules ]; then - udevlink=$(readlink /etc/udev/rules.d/80-networking.rules 2>/dev/null || true) - [ -n "$udevlink" -a "$udevlink" = "/dev/null" ] && rm -f /etc/udev/rules.d/80-networking.rules - fi +MYNAME="${0##*/}" - if [ -e /etc/udev/rules.d/60-bridge-network-interface.rules ]; then - udevlink=$(readlink /etc/udev/rules.d/60-bridge-network-interface.rules 2>/dev/null || true) - [ -n "$udevlink" -a "$udevlink" = "/dev/null" ] && rm -f /etc/udev/rules.d/60-bridge-network-interface.rules - fi -} - -postrm_remove() -{ - process_udev -} +report() { echo "${MYNAME}: $*" ; } +report_warn() { report "Warning: $*" >&2 ; } +report_err() { report "Error: $*" >&2 ; } -# Note: We don't remove /etc/network/interfaces -postrm_purge() +process_state_file() { - rm -f /run/network/ifstatenew + rm -f /run/network/ifstatenew } -# restore file if we diverted it on install/upgrade -_postrm_divert() +process_udev() { - diversions=$(dpkg-divert --list | grep "$1" | grep 'by ifupdown2$' | wc -l 2> /dev/null) - if [ $diversions -gt 0 ] ; - then - dpkg-divert --remove --package ifupdown2 --rename $1 + if [ -e /etc/udev/rules.d/80-networking.rules ]; then + udevlink=$(readlink /etc/udev/rules.d/80-networking.rules 2>/dev/null || true) + [ -n "$udevlink" -a "$udevlink" = "/dev/null" ] && rm -f /etc/udev/rules.d/80-networking.rules fi -} -postrm_divert() -{ - for filename in ifup ifdown ifquery ifreload - do - _postrm_divert "/usr/share/bash-completion/completions/$filename" - _postrm_divert "/etc/bash_completion.d/$filename" - done + if [ -e /etc/udev/rules.d/60-bridge-network-interface.rules ]; then + udevlink=$(readlink /etc/udev/rules.d/60-bridge-network-interface.rules 2>/dev/null || true) + [ -n "$udevlink" -a "$udevlink" = "/dev/null" ] && rm -f /etc/udev/rules.d/60-bridge-network-interface.rules + fi } case "$1" in - purge) - postrm_purge - ;; - - remove) - postrm_remove - postrm_divert - ;; - - abort-install|disappear|failed-upgrade|abort-upgrade) - postrm_divert + purge|remove|abort-install|disappear) + process_state_file + process_udev ;; - upgrade) - ;; + upgrade|failed-upgrade|abort-upgrade|disappear) + ;; - *) - echo "postrm called with unknown argument \`$1'" >&2 - exit 1 - ;; + *) + echo "postrm called with unknown argument \`$1'" >&2 + exit 1 + ;; esac -# dh_installdeb will replace this with shell code automatically -# generated by other debhelper scripts. #DEBHELPER# diff --git a/debian/ifupdown2.preinst b/debian/ifupdown2.preinst deleted file mode 100644 index 9f41e8e..0000000 --- a/debian/ifupdown2.preinst +++ /dev/null @@ -1,39 +0,0 @@ -#! /bin/sh - -MYNAME="${0##*/}" - -report() { echo "${MYNAME}: $*" ; } -report_err() { report "Error: $*" >&2 ; } - -preinst_divert() -{ - diversions=$(dpkg-divert --list | grep "$1" | grep -v 'by ifupdown2$' | wc -l 2> /dev/null) - if [ $diversions -gt 0 ] ; - then - report_err "existing diversion for $1" - else - if [ -f $1 ]; - then - dpkg-divert --add --package ifupdown2 --rename --divert "$1.disabled" "$1" - fi - fi -} - -set -e -case "$1" in - install|upgrade) - for filename in ifup ifdown ifquery ifreload - do - preinst_divert "/usr/share/bash-completion/completions/$filename" - preinst_divert "/etc/bash_completion.d/$filename" - done - - # workaround 3.0.0 internal install error. This can be removed in a - # few weeks. - if [ -f /etc/default/networking/networking.default ]; then - dpkg-maintscript-helper rm_conffile /etc/default/networking/networking.default 1.1 -- $@ - rm -rf /etc/default/networking - fi - ;; -esac -#DEBHELPER# diff --git a/debian/networking b/debian/networking deleted file mode 100644 index 5856199..0000000 --- a/debian/networking +++ /dev/null @@ -1,23 +0,0 @@ -# -# -# Parameters for the networking service -# -# - -# Change the below to yes if you want verbose logging to be enabled -VERBOSE="no" - -# Change the below to yes if you want debug logging to be enabled -DEBUG="no" - -# Change the below to yes if you want logging to go to syslog -SYSLOG="no" - -# Exclude interfaces -EXCLUDE_INTERFACES= - -# Set to 'yes' if you want to skip ifdown during system reboot -# and shutdown. This is of interest in large scale interface -# deployments where you dont want to wait for interface -# deconfiguration to speed up shutdown/reboot -SKIP_DOWN_AT_SYSRESET="yes" diff --git a/debian/python-ifupdown2.postinst b/debian/python-ifupdown2.postinst deleted file mode 100644 index cf61919..0000000 --- a/debian/python-ifupdown2.postinst +++ /dev/null @@ -1,114 +0,0 @@ -#!/bin/sh -# postinst script for ifupdown2 -# -# see: dh_installdeb(1) - -set -e - -# summary of how this script can be called: -# * `configure' -# * `abort-upgrade' -# * `abort-remove' `in-favour' -# -# * `abort-remove' -# * `abort-deconfigure' `in-favour' -# `removing' -# -# for details, see http://www.debian.org/doc/debian-policy/ or -# the debian-policy package - -process_udev() -{ - # override default udev bridge and hotplug rules because they interfere with - # networking init script - udev_user_rulesdir=/etc/udev/rules.d/ - udev_sys_rulesdir=/lib/udev/rules.d/ - if [ -e $udev_user_rulesdir ]; then - udev_ifupdown2_overrides="80-networking.rules - 60-bridge-network-interface.rules" - for u in ${udev_ifupdown2_overrides} - do - if [ -e ${udev_sys_rulesdir}/$u -a ! -e ${udev_user_rulesdir}/$u ]; then - (cd ${udev_user_rulesdir} && ln -sf /dev/null $u) - fi - done - fi -} - -MYNAME="${0##*/}" - -report() { echo "${MYNAME}: $*" ; } -report_warn() { report "Warning: $*" >&2 ; } -report_err() { report "Error: $*" >&2 ; } - -case "$1" in - configure) - # Create /etc/network/run - [ -d /run/network ] || mkdir -p /run/network - - # for backward compatibility - if [ ! -f /etc/network/run ]; then - ln -sf /run/network /etc/network/run - fi - - ln -sf /usr/share/python-ifupdown2/generate_interfaces.py \ - /usr/share/doc/python-ifupdown2/examples/generate_interfaces.py - - [ -d /etc/network/if-pre-up.d ] || mkdir -p /etc/network/if-pre-up.d - [ -d /etc/network/if-up.d ] || mkdir -p /etc/network/if-up.d - [ -d /etc/network/if-post-up.d ] || mkdir -p /etc/network/if-post-up.d - - [ -d /etc/network/if-pre-down.d ] || mkdir -p /etc/network/if-pre-down.d - [ -d /etc/network/if-down.d ] || mkdir -p /etc/network/if-down.d - [ -d /etc/network/if-post-down.d ] || mkdir -p /etc/network/if-post-down.d - - - # Generic stuff done on all configurations - if [ -f /etc/network/interfaces ] ; then - # TODO: This should be handled with debconf and the script - # could introduce the line there directly - if ! grep -q "^[[:space:]]*iface[[:space:]]\+lo0\?[[:space:]]\+inet[[:space:]]\+loopback\>" /etc/network/interfaces ; then - report_warn "No 'iface lo' definition found in /etc/network/interfaces" - fi - - if ! grep -q "^[[:space:]]*\(allow-\|\)auto[[:space:]]\+\(.*[[:space:]]\+\|\)lo0\?\([[:space:]]\+\|$\)" /etc/network/interfaces ; then - report_warn "No 'auto lo' statement found in /etc/network/interfaces" - fi - else # ! -f /etc/network/interfaces - if [ -z "$2" ]; then - echo "Creating /etc/network/interfaces." - echo "# interfaces(5) file used by ifup(8) and ifdown(8)" > /etc/network/interfaces - echo "auto lo" >> /etc/network/interfaces - echo "iface lo inet loopback" >> /etc/network/interfaces - else - report_warn "/etc/network/interfaces does not exist" - fi - fi - - [ -e /sbin/ifup ] || ln -sf /sbin/ifupdown /sbin/ifup - [ -e /sbin/ifdown ] || ln -sf /sbin/ifupdown /sbin/ifdown - [ -e /sbin/ifquery ] || ln -sf /sbin/ifupdown /sbin/ifquery - [ -e /sbin/ifreload ] || ln -sf /sbin/ifupdown /sbin/ifreload - - (cd /usr/share/man/man8/ && ln -sf /usr/share/man/man8/ifup.8.gz ifdown.8.gz) - - mkdir -p /etc/network/interfaces.d/ - process_udev - update-rc.d networking start 40 S . start 35 0 6 . >/dev/null - ;; - - abort-upgrade|abort-remove|abort-deconfigure) - ;; - - *) - echo "postinst called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -# dh_installdeb will replace this with shell code automatically -# generated by other debhelper scripts. - -#DEBHELPER# - -exit 0 diff --git a/debian/python-ifupdown2.postrm b/debian/python-ifupdown2.postrm deleted file mode 100644 index fe68eb4..0000000 --- a/debian/python-ifupdown2.postrm +++ /dev/null @@ -1,71 +0,0 @@ -#!/bin/sh -# postrm script for ifupdown2 -# -# see: dh_installdeb(1) - -set -e - -# summary of how this script can be called: -# * `remove' -# * `purge' -# * `upgrade' -# * `failed-upgrade' -# * `abort-install' -# * `abort-install' -# * `abort-upgrade' -# * `disappear' -# -# for details, see http://www.debian.org/doc/debian-policy/ or -# the debian-policy package - -process_udev() -{ - udevlink=$(readlink /etc/udev/rules.d/80-networking.rules 2>/dev/null || true) - [ -n "$udevlink" -a "$udevlink" == "/dev/null" ] && rm -f /etc/udev/rules.d/80-networking.rules - udevlink=$(readlink /etc/udev/rules.d/60-bridge-network-interface.rules 2>/dev/null || true) - [ -n "$udevlink" -a "$udevlink" == "/dev/null" ] && rm -f /etc/udev/rules.d/60-bridge-network-interface.rules -} - -postrm_remove() -{ - rm -f /sbin/ifup /sbin/ifdown /sbin/ifquery - process_udev - update-rc.d networking remove >/dev/null -} - -# Note: We don't remove /etc/network/interfaces -postrm_purge() -{ - rm -f /var/tmp/network/ifstatenew - if [ -L /etc/network/run ] ; then - rm -f /etc/network/run - elif [ -d /etc/network/run ] ; then - rmdir --ignore-fail-on-non-empty /etc/network/run - fi -} - -case "$1" in - purge) - postrm_purge - ;; - - remove) - postrm_remove - ;; - - - upgrade|disappear|failed-upgrade|abort-install|abort-upgrade) - ;; - - *) - echo "postrm called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -# dh_installdeb will replace this with shell code automatically -# generated by other debhelper scripts. - -#DEBHELPER# - -exit 0 diff --git a/debian/python-ifupdown2.preinst b/debian/python-ifupdown2.preinst deleted file mode 100755 index 3fcaed1..0000000 --- a/debian/python-ifupdown2.preinst +++ /dev/null @@ -1,56 +0,0 @@ -#!/bin/sh -# preinst script for newpkg -# -# see: dh_installdeb(1) - -set -e - -# summary of how this script can be called: -# * `install' -# * `install' -# * `upgrade' -# * `abort-upgrade' -# for details, see http://www.debian.org/doc/debian-policy/ or -# the debian-policy package - -preinst_upgrade() -{ - local oldver="$1" - local udev_user_rulesdir="/etc/udev/rules.d" - - # we have to fixup the filesystem here as previous packages of - # ifupdown2 introduced a bug in the postrm script that require - # these files to exist, otherwise the postrm script will always - # fail. - local badver="0.1-cl2.5+2" - if dpkg --compare-versions "${oldver}" "lt" "${badver}"; then - local files="${udev_user_rulesdir}/80-networking.rules - ${udev_user_rulesdir}/60-bridge-network-interface.rules" - for f in ${files}; do - echo "touching udev rule: ${f}" - test ! -e "${f}" && ln -s /dev/null "${f}" || \ - /bin/echo -e "\tudev rule exists leaving" - done - fi -} - -case "$1" in - install|upgrade) - preinst_upgrade "$2" - ;; - - abort-upgrade) - ;; - - *) - echo "preinst called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -# dh_installdeb will replace this with shell code automatically -# generated by other debhelper scripts. - -#DEBHELPER# - -exit 0 diff --git a/debian/rules b/debian/rules index db0fef3..9560f7f 100755 --- a/debian/rules +++ b/debian/rules @@ -2,13 +2,13 @@ #export DH_VERBOSE=1 export PYBUILD_NAME=ifupdown2 -export PYBUILD_INSTALL_ARGS=--install-lib=/usr/share/ifupdown2 --install-scripts=/usr/share/ifupdown2 +export PYBUILD_INSTALL_ARGS=--install-lib=/usr/share/ --install-scripts=/usr/share/ %: dh $@ --with python2 --with systemd --buildsystem=pybuild override_dh_installman: - ./scripts/genmanpages.sh ./man.rst ./man + ./ifupdown2/man/genmanpages.sh ./ifupdown2/man ./man dh_installman override_dh_systemd_start: diff --git a/debian/source/format b/debian/source/format index af745b3..89ae9db 100644 --- a/debian/source/format +++ b/debian/source/format @@ -1 +1 @@ -3.0 (git) +3.0 (native) diff --git a/debian/watch b/debian/watch new file mode 100644 index 0000000..9e7c0da --- /dev/null +++ b/debian/watch @@ -0,0 +1 @@ +version=3 diff --git a/docs/examples/batman_adv/configure_batman_adv.sh b/docs/examples/batman_adv/configure_batman_adv.sh deleted file mode 100755 index eabfea8..0000000 --- a/docs/examples/batman_adv/configure_batman_adv.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env bash - -echo "Installing batman-adv module on debian/ubuntu" -echo "" -echo "Batman is a Layer2-Mesh protocol which uses Ethernet devices (like eth*, -vlans, etc.) to communicate with peers and provides access to the L2-mesh via -a batX interface. You can only create a batman instance if at least one batman- --iface (read: an interface where the mesh protocol is spoken on) is present and -added to the batman-mesh-instance." -echo "More info: https://en.wikipedia.org/wiki/B.A.T.M.A.N." -echo "" - -echo "installing batctl: apt-get install batctl" -apt-get install batctl -echo "" -echo "" - -echo "loading batman-adv module: modprobe batman-adv" -modprobe batman-adv -echo "" - -echo "usefull commands: -$ batctl if add \$IFACE -$ batctl -m bat0 if add \$IFACE" -echo "please read: man batctl" -echo "" -echo "" - -echo "configuration example: -$ cat /etc/network/interfaces - -auto bat0 -iface bat0 - batman-ifaces \$IFACE [\$IFACES...] - batman-ifaces-ignore-regex .*_nodes - batman-hop-penalty 23 - address 192.0.2.42/24 -$ -$ -$ ifreload -a -$ ifquery -a -c -auto bat0 -iface bat0 [pass] - batman-ifaces tap0 tap1 [pass] - batman-ifaces-ignore-regex .*_nodes [pass] - batman-hop-penalty 23 [pass] - address 192.0.2.42/24 [pass] - -$" diff --git a/docs/examples/batman_adv/interfaces_batman b/docs/examples/batman_adv/interfaces_batman deleted file mode 100644 index 14ced43..0000000 --- a/docs/examples/batman_adv/interfaces_batman +++ /dev/null @@ -1,12 +0,0 @@ -auto eth0 -iface eth0 inet dhcp - -auto lo -iface lo inet - -auto bat0 -iface bat0 - batman-ifaces tap0 tap1 - batman-ifaces-ignore-regex .*_nodes - batman-hop-penalty 23 - address 192.0.2.42/24 \ No newline at end of file diff --git a/docs/examples/generate_interfaces.py b/docs/examples/generate_interfaces.py index d4b2d65..ac711ea 100755 --- a/docs/examples/generate_interfaces.py +++ b/docs/examples/generate_interfaces.py @@ -3,10 +3,7 @@ import argparse import sys import subprocess -<<<<<<< HEAD -======= import os ->>>>>>> cumulus/dev """ This script prints to stdout /etc/network/interfaces entries for requested interfaces. @@ -34,21 +31,6 @@ import os """ -<<<<<<< HEAD -def get_swp_interfaces(): - porttab_path = '/var/lib/cumulus/porttab' - ports = [] - - ptfile = open(porttab_path, 'r') - for line in ptfile.readlines(): - line = line.strip() - if '#' in line: - continue - try: - ports.append(line.split()[0]) - except ValueError: - continue -======= def get_pci_interfaces(): ports = [] FNULL = open(os.devnull, 'w') @@ -90,12 +72,11 @@ def get_swp_interfaces(): if not ports: print 'Error: No ports found in %s' % porttab_path exit(1) ->>>>>>> cumulus/dev return ports def print_swp_defaults_header(): print ''' -# ** This file is autogenerated by /usr/share/doc/python-ifupdown2/generate_interfaces.py ** +# ** This file is autogenerated by /usr/share/doc/ifupdown2/generate_interfaces.py ** # # This is /etc/network/interfaces section for all available swp # ports on the system. @@ -112,7 +93,7 @@ def print_swp_defaults_header(): def print_bridge_untagged_defaults_header(): print ''' -# ** This file is autogenerated by /usr/share/doc/python-ifupdown2/generate_interfaces.py ** +# ** This file is autogenerated by /usr/share/doc/ifupdown2/generate_interfaces.py ** # # This is /etc/network/interfaces section for a bridge device with all swp # ports in the system. @@ -186,12 +167,6 @@ if args.bridgedefault and args.mergefile: exit(1) swp_intfs = get_swp_interfaces() -<<<<<<< HEAD -if not swp_intfs: - print 'error: no ports found' - exit(1) -======= ->>>>>>> cumulus/dev if args.swpdefaults: interfaces_print_swp_defaults(swp_intfs) diff --git a/docs/examples/vlan_aware_bridges/interfaces.with_bonds b/docs/examples/vlan_aware_bridges/interfaces.with_bonds index 692a1e6..b2b26c8 100644 --- a/docs/examples/vlan_aware_bridges/interfaces.with_bonds +++ b/docs/examples/vlan_aware_bridges/interfaces.with_bonds @@ -4,13 +4,13 @@ # uplink1, peerlink and downlink are bond interfaces. # 'bridge' is a vlan aware bridge with ports uplink1, peerlink # and downlink (swp2-20). -# +# # native vlan is by default 1 # # 'bridge-vids' attribute is used to declare vlans. # 'bridge-pvid' attribute is used to specify native vlans if other than 1 # 'bridge-access' attribute is used to declare access port -# +# auto lo iface lo diff --git a/docs/examples/vlan_aware_bridges/interfaces.with_clag b/docs/examples/vlan_aware_bridges/interfaces.with_clag index 42cfd28..7e655f0 100644 --- a/docs/examples/vlan_aware_bridges/interfaces.with_clag +++ b/docs/examples/vlan_aware_bridges/interfaces.with_clag @@ -7,13 +7,13 @@ # # All ports inherit 'vlans 10 20-23' from the 'bridge-vids' attribute # under the bridge -# +# # native vlan is by default 1 # # 'bridge-vids' attribute is used to declare vlans. # 'bridge-pvid' attribute is used to specify native vlans if other than 1 # 'bridge-access' attribute is used to declare access port -# +# # 'spine-bond host-bond-0[1-2]' are clag bonds and will be considered by # clagd for dual connection. clag-id has to be a non-zero and has to match # across the peer switches for the bonds to become dual connected. diff --git a/docs/source/addonsapiref.rst b/docs/source/addonsapiref.rst index 9b114fd..82a8906 100644 --- a/docs/source/addonsapiref.rst +++ b/docs/source/addonsapiref.rst @@ -32,21 +32,12 @@ ethtool .. autoclass:: ethtool -<<<<<<< HEAD -ifenslave -========= - -.. automodule:: ifenslave - -.. autoclass:: ifenslave -======= bond ==== .. automodule:: bond .. autoclass:: bond ->>>>>>> cumulus/dev mstpctl ======= diff --git a/docs/source/addonshelperapiref.rst b/docs/source/addonshelperapiref.rst index 110d80a..6eaa0ef 100644 --- a/docs/source/addonshelperapiref.rst +++ b/docs/source/addonshelperapiref.rst @@ -5,36 +5,14 @@ This package contains modules that provide helper methods for ifupdown2 addon modules to interact directly with tools like iproute2, brctl etc. - -bridgeutils +LinkUtils =========== -Helper module to work with bridgeutil commands - -.. automodule:: bridgeutils - -.. autoclass:: brctl - -<<<<<<< HEAD -ifenslaveutil -============= -======= -bondutil -======== ->>>>>>> cumulus/dev - -Helper module to interact with linux api to create bonds. -Currently this is via sysfs. +Helper module to work with brctl, bonds, iproute2, mstpctl commands -<<<<<<< HEAD -.. automodule:: ifenslaveutil +.. automodule:: LinkUtils -.. autoclass:: ifenslaveutil -======= -.. automodule:: bondutil - -.. autoclass:: bondutil ->>>>>>> cumulus/dev +.. autoclass:: LinkUtils dhclient ======== @@ -44,12 +22,3 @@ Helper module to interact with dhclient tools. .. automodule:: dhclient .. autoclass:: dhclient - -iproute2 -======== - -Helper module to interact with iproute2 tools. - -.. automodule:: iproute2 - -.. autoclass:: iproute2 diff --git a/docs/source/userguide.rst b/docs/source/userguide.rst index 818ce62..0d43770 100644 --- a/docs/source/userguide.rst +++ b/docs/source/userguide.rst @@ -10,7 +10,7 @@ Keep the following points in mind before you start configuring interfaces using ``ifupdown2``: * IPv4 and IPv6 addresses for an interface can be listed in the same ``iface`` - section. For examples, see ``/usr/share/doc/python-ifupdown2/examples/``. + section. For examples, see ``/usr/share/doc/ifupdown2/examples/``. * Do not use a legacy interface alias. They are only supported for backward compatibility with ``ifupdown``. They do get configured, but ``ifquery`` has @@ -66,11 +66,7 @@ Man Pages Configuration Files =================== -<<<<<<< HEAD -* /etc/network/interfaces -======= * config file defined in ifupdown2.conf (default /etc/network/interfaces) ->>>>>>> cumulus/dev ifupdown Built-in Interfaces @@ -113,11 +109,7 @@ following example configuration:: bond-slaves swp29 swp30 bond-mode 802.3ad bond-miimon 100 -<<<<<<< HEAD - bond-use-carrier 1 -======= bond-use-carrier yes ->>>>>>> cumulus/dev bond-lacp-rate 1 bond-min-links 1 bond-xmit-hash-policy layer3+4 @@ -128,11 +120,7 @@ following example configuration:: bond-slaves swp31 swp32 bond-mode 802.3ad bond-miimon 100 -<<<<<<< HEAD - bond-use-carrier 1 -======= bond-use-carrier yes ->>>>>>> cumulus/dev bond-lacp-rate 1 bond-min-links 1 bond-xmit-hash-policy layer3+4 @@ -310,11 +298,7 @@ The contents of the sourced file used above are:: bond-slaves swp25 swp26 bond-mode 802.3ad bond-miimon 100 -<<<<<<< HEAD - bond-use-carrier 1 -======= bond-use-carrier yes ->>>>>>> cumulus/dev bond-lacp-rate 1 bond-min-links 1 bond-xmit-hash-policy layer3+4 @@ -333,13 +317,10 @@ bridge ports and bond slaves:: iface br1 bridge-ports glob swp7-9.100 swp11.100 glob swp15-18.100 -<<<<<<< HEAD -======= auto br2 iface br2 bridge-ports glob swp[1-6]s[0-3].100 ->>>>>>> cumulus/dev Using Templates =============== @@ -382,11 +363,7 @@ file, run:: bond-slaves swp25 swp26 bond-mode 802.3ad bond-miimon 100 -<<<<<<< HEAD - bond-use-carrier 1 -======= bond-use-carrier yes ->>>>>>> cumulus/dev bond-lacp-rate 1 bond-min-links 1 bond-xmit-hash-policy layer3+4 @@ -402,11 +379,7 @@ does not match:: iface bond0 bond-mode 802.3ad (✓) bond-miimon 100 (✓) -<<<<<<< HEAD - bond-use-carrier 1 (✓) -======= bond-use-carrier yes (✓) ->>>>>>> cumulus/dev bond-lacp-rate 1 (✓) bond-min-links 1 (✓) bond-xmit-hash-policy layer3+4 (✓) @@ -444,17 +417,10 @@ the ``interfaces`` file. For complete syntax on the ``interfaces`` file, see { "auto": true, "config": { -<<<<<<< HEAD - "bond-use-carrier": "1", - "bond-xmit-hash-policy": "layer3+4", - "bond-miimon": "100", - "bond-lacp-rate": "1", -======= "bond-use-carrier": "yes", "bond-xmit-hash-policy": "layer3+4", "bond-miimon": "100", "bond-lacp-rate": "1", ->>>>>>> cumulus/dev "bond-min-links": "1", "bond-slaves": "swp25 swp26", "bond-mode": "802.3ad", diff --git a/config/networking b/etc/default/networking similarity index 100% rename from config/networking rename to etc/default/networking diff --git a/init.d/networking b/etc/init.d/networking similarity index 96% rename from init.d/networking rename to etc/init.d/networking index 09ead81..b52bfdc 100644 --- a/init.d/networking +++ b/etc/init.d/networking @@ -9,7 +9,6 @@ # Description: Prepare /run/network directory, ifstate file and raise network interfaces, or take them down. ### END INIT INFO -PATH="/sbin:/bin" RUN_DIR="/run/network" IFSTATE_LOCKFILE="${RUN_DIR}/ifstatelock" @@ -36,13 +35,13 @@ EXTRA_ARGS= gen_examples() { # Generate sample interfaces file. The interfaces files are - # generated under /usr/share/doc/python-ifupdown2/examples/ + # generated under /usr/share/doc/ifupdown2/examples/ # # generate files only at boot [ -f ${IFSTATE_LOCKFILE} ] && return - python_ifupdown2_docdir="/usr/share/doc/python-ifupdown2" + python_ifupdown2_docdir="/usr/share/doc/ifupdown2" swpfile=${python_ifupdown2_docdir}"/examples/swp_defaults" bridgedefaultfile=${python_ifupdown2_docdir}"/examples/bridge_untagged_default" interfaces_gen_script=${python_ifupdown2_docdir}"/examples/generate_interfaces.py" @@ -147,7 +146,7 @@ start) exclusions=$(process_exclusions) perfoptions=$(perf_options) log_action_begin_msg "Configuring network interfaces" - ifup -a $EXTRA_ARGS $exclusions $perfoptions + ifup -a $EXTRA_ARGS $exclusions $perfoptions log_action_end_msg $? ;; diff --git a/config/addons.conf b/etc/network/ifupdown2/addons.conf similarity index 81% rename from config/addons.conf rename to etc/network/ifupdown2/addons.conf index 2af9b73..c0d1ce9 100644 --- a/config/addons.conf +++ b/etc/network/ifupdown2/addons.conf @@ -1,8 +1,5 @@ pre-up,link -pre-up,tunnel -pre-up,ppp pre-up,bond -pre-up,batman_adv pre-up,vlan pre-up,vxlan pre-up,clagd @@ -11,16 +8,16 @@ pre-up,bridge pre-up,bridgevlan pre-up,mstpctl pre-up,vrf +pre-up,ethtool up,dhcp up,address up,addressvirtual up,usercmds -post-up,ethtool post-up,usercmds post-up,clagd post-up,vxrd -pre-down,usercmds pre-down,ethtool +pre-down,usercmds pre-down,vxrd pre-down,dhcp down,addressvirtual @@ -34,8 +31,5 @@ post-down,bridge post-down,vxlan post-down,vlan post-down,bond -post-down,batman_adv post-down,usercmds post-down,link -post-down,tunnel -post-down,ppp diff --git a/config/ifupdown2.conf b/etc/network/ifupdown2/ifupdown2.conf similarity index 99% rename from config/ifupdown2.conf rename to etc/network/ifupdown2/ifupdown2.conf index 88f1bc9..906fdea 100644 --- a/config/ifupdown2.conf +++ b/etc/network/ifupdown2/ifupdown2.conf @@ -29,7 +29,7 @@ disable_cli_interfacesfile=0 # are used. But when a mix of scripts and modules are used (which is the # default case), you may get false warnings for attributes supported # by scripts -addon_syntax_check=0 +addon_syntax_check=1 # Support executing of ifupdown style scripts. # Note that by default python addon modules override scripts with the same diff --git a/ifupdown/__init__.py b/ifupdown/__init__.py deleted file mode 100644 index 5f7d90a..0000000 --- a/ifupdown/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -""" ifupdown2 package. - -.. moduleauthor:: Roopa Prabhu - -""" diff --git a/ifupdown/exceptions.py b/ifupdown/exceptions.py deleted file mode 100644 index 68d3b7f..0000000 --- a/ifupdown/exceptions.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/python -# -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. -# Author: Roopa Prabhu, roopa@cumulusnetworks.com -# -# ifupdown -- -# exceptions -# - -class Error(Exception): - """Base class for exceptions in ifupdown""" - - pass - -class ifaceNotFoundError(Error): - pass - - -class invalidValueError(Error): - pass - -class errorReadingStateError(Error): - pass - -class moduleNotSupported(Error): - pass diff --git a/ifupdown/ifupdownconfig.py b/ifupdown/ifupdownconfig.py deleted file mode 100644 index b156b6a..0000000 --- a/ifupdown/ifupdownconfig.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2015 Cumulus Networks, Inc. All rights reserved. -# -# Author: Roopa Prabhu, roopa@cumulusnetworks.com -# -# - -class ifupdownConfig(): - - def __init__(self): - self.conf = {} - -config = ifupdownConfig() diff --git a/ifupdown/netlink.py b/ifupdown/netlink.py deleted file mode 100644 index 01a4df2..0000000 --- a/ifupdown/netlink.py +++ /dev/null @@ -1,155 +0,0 @@ -#!/usr/bin/python -# -# Copyright 2016 Cumulus Networks, Inc. All rights reserved. -# Author: Julien Fortin, julien@cumulusnetworks.com -# - -try: - from ifupdownaddons.utilsbase import utilsBase - import ifupdown.ifupdownflags as ifupdownflags -except ImportError, e: - raise ImportError(str(e) + "- required module not found") - - -class Netlink(utilsBase): - VXLAN_UDP_PORT = 4789 - - def __init__(self, *args, **kargs): - utilsBase.__init__(self, *args, **kargs) - try: - import sys - sys.path.insert(0, '/usr/share/ifupdown2/') - from nlmanager.nlmanager import NetlinkManager - # this should force the use of the local nlmanager - self._nlmanager_api = NetlinkManager(extra_debug=False) - except Exception as e: - self.logger.error('cannot initialize ifupdown2\'s ' - 'netlink manager: %s' % str(e)) - raise - - def get_iface_index(self, ifacename): - if ifupdownflags.flags.DRYRUN: return - try: - return self._nlmanager_api.get_iface_index(ifacename) - except Exception as e: - raise Exception('%s: netlink: %s: cannot get ifindex: %s' - % (ifacename, ifacename, str(e))) - - def link_add_vlan(self, vlanrawdevice, ifacename, vlanid): - self.logger.info('%s: netlink: ip link add link %s name %s type vlan id %s' - % (ifacename, vlanrawdevice, ifacename, vlanid)) - if ifupdownflags.flags.DRYRUN: return - ifindex = self.get_iface_index(vlanrawdevice) - try: - return self._nlmanager_api.link_add_vlan(ifindex, ifacename, vlanid) - except Exception as e: - raise Exception('netlink: %s: cannot create vlan %s: %s' - % (vlanrawdevice, vlanid, str(e))) - - def link_add_macvlan(self, ifacename, macvlan_ifacename): - self.logger.info('%s: netlink: ip link add link %s name %s type macvlan mode private' - % (ifacename, ifacename, macvlan_ifacename)) - if ifupdownflags.flags.DRYRUN: return - ifindex = self.get_iface_index(ifacename) - try: - return self._nlmanager_api.link_add_macvlan(ifindex, macvlan_ifacename) - except Exception as e: - raise Exception('netlink: %s: cannot create macvlan %s: %s' - % (ifacename, macvlan_ifacename, str(e))) - - def link_set_updown(self, ifacename, state): - self.logger.info('%s: netlink: ip link set dev %s %s' - % (ifacename, ifacename, state)) - if ifupdownflags.flags.DRYRUN: return - try: - return self._nlmanager_api.link_set_updown(ifacename, state) - except Exception as e: - raise Exception('netlink: cannot set link %s %s: %s' - % (ifacename, state, str(e))) - - def link_set_protodown(self, ifacename, state): - self.logger.info('%s: netlink: set link %s protodown %s' - % (ifacename, ifacename, state)) - if ifupdownflags.flags.DRYRUN: return - try: - return self._nlmanager_api.link_set_protodown(ifacename, state) - except Exception as e: - raise Exception('netlink: cannot set link %s protodown %s: %s' - % (ifacename, state, str(e))) - - def link_set_master(self, ifacename, master_dev, state=None): - self.logger.info('%s: netlink: ip link set dev %s master %s %s' - % (ifacename, ifacename, master_dev, - state if state else '')) - if ifupdownflags.flags.DRYRUN: return - try: - master = 0 if not master_dev else self.get_iface_index(master_dev) - return self._nlmanager_api.link_set_master(ifacename, master, - state=state) - except Exception as e: - raise Exception('netlink: %s: cannot set %s master %s: %s' - % (ifacename, ifacename, master_dev, str(e))) - - def link_set_nomaster(self, ifacename, state=None): - self.logger.info('%s: netlink: ip link set dev %s nomaster %s' - % (ifacename, ifacename, state if state else '')) - if ifupdownflags.flags.DRYRUN: return - try: - return self._nlmanager_api.link_set_master(ifacename, 0, - state=state) - except Exception as e: - raise Exception('netlink: %s: cannot set %s nomaster: %s' - % (ifacename, ifacename, str(e))) - - def link_add_bridge_vlan(self, ifacename, vlanid): - self.logger.info('%s: netlink: bridge vlan add vid %s dev %s' - % (ifacename, vlanid, ifacename)) - if ifupdownflags.flags.DRYRUN: return - ifindex = self.get_iface_index(ifacename) - try: - return self._nlmanager_api.link_add_bridge_vlan(ifindex, vlanid) - except Exception as e: - raise Exception('netlink: %s: cannot create bridge vlan %s: %s' - % (ifacename, vlanid, str(e))) - - def link_del_bridge_vlan(self, ifacename, vlanid): - self.logger.info('%s: netlink: bridge vlan del vid %s dev %s' - % (ifacename, vlanid, ifacename)) - if ifupdownflags.flags.DRYRUN: return - ifindex = self.get_iface_index(ifacename) - try: - return self._nlmanager_api.link_del_bridge_vlan(ifindex, vlanid) - except Exception as e: - raise Exception('netlink: %s: cannot remove bridge vlan %s: %s' - % (ifacename, vlanid, str(e))) - - def link_add_vxlan(self, ifacename, vxlanid, local=None, dstport=VXLAN_UDP_PORT, - group=None, learning='on', ageing=None, physdev=None): - cmd = 'ip link add %s type vxlan id %s dstport %s' % (ifacename, - vxlanid, - dstport) - - - cmd += ' local %s' % local if local else '' - cmd += ' ageing %s' % ageing if ageing else '' - cmd += ' remote %s' % group if group else ' noremote' - cmd += ' nolearning' if learning == 'off' else '' - cmd += ' dev %s' % physdev if physdev else '' - self.logger.info('%s: netlink: %s' % (ifacename, cmd)) - if ifupdownflags.flags.DRYRUN: return - try: - if physdev: - physdev = self.get_iface_index (physdev) - return self._nlmanager_api.link_add_vxlan(ifacename, - vxlanid, - dstport=dstport, - local=local, - group=group, - learning=learning, - ageing=ageing, - physdev=physdev) - except Exception as e: - raise Exception('netlink: %s: cannot create vxlan %s: %s' - % (ifacename, vxlanid, str(e))) - -netlink = Netlink() diff --git a/ifupdown/rtnetlink.py b/ifupdown/rtnetlink.py deleted file mode 100644 index 9b13ad5..0000000 --- a/ifupdown/rtnetlink.py +++ /dev/null @@ -1,860 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. -# -# Author: Scott Feldman, sfeldma@cumulusnetworks.com -# Author: Roopa Prabhu, roopa@cumulusnetworks.com -# -# -from socket import NETLINK_ROUTE, AF_INET, AF_INET6 -from string import printable -from ipaddr import * -from ctypes import * -from netlink import * -import logging - -logger = logging.getLogger(__name__) - -# -# from /usr/include/linux/rtnetlink.h -# - -RTMGRP_LINK = 0x1 -RTMGRP_IPV4_IFADDR = 0x10 -RTMGRP_IPV4_ROUTE = 0x40 -RTMGRP_IPV6_IFADDR = 0x100 -RTMGRP_IPV6_ROUTE = 0x400 - -RTM_NEWLINK = 16 -RTM_DELLINK = 17 -RTM_GETLINK = 18 -RTM_SETLINK = 19 -RTM_NEWADDR = 20 -RTM_DELADDR = 21 -RTM_GETADDR = 22 -RTM_NEWROUTE = 24 -RTM_DELROUTE = 25 -RTM_GETROUTE = 26 - -# Definitions used in routing table administration. - -class Nlmsg(Structure): - - def _stringify(self): - return string_at(addressof(self), sizeof(self)) - - def __eq__(self, other): - return self._stringify() == other._stringify() and \ - self.__dict__ == other.__dict__ - - def to_rta(self): - return Rtattr.from_address(addressof(self) + NLMSG_ALIGN(sizeof(self))) - - def pack_extra(self, extra, addr): - memmove(addr, addressof(extra), sizeof(extra)) - return NLMSG_ALIGN(sizeof(extra)) - - def pack_rtas(self, rtas, addr): - total_len = 0 - for rta_type, value in rtas.iteritems(): - rta = Rtattr.from_address(addr) - rta.rta_type = rta_type - pack_fn = self.rta_fn(rta_type) - rta_len = NLMSG_ALIGN(pack_fn(rta, value)) - total_len += rta_len - addr += rta_len - return total_len - - def pack_rtas_new(self, rtas, addr, policy): - total_len = 0 - - for rta_type, value in rtas.iteritems(): - if type(value) == dict: - rta = Rtattr.from_address(addr) - rta.rta_type = rta_type - rta.rta_len = RTA_LENGTH(0) - rta_len = NLMSG_ALIGN(rta.rta_len) - total_len += rta_len - addr += rta_len - pack_fn = policy.get(rta_type) - rta_len = NLMSG_ALIGN(pack_fn(addr, value)) - - rta.rta_len += rta_len - else: - rta = Rtattr.from_address(addr) - rta.rta_type = rta_type - pack_fn = policy.get(rta_type) - rta_len = NLMSG_ALIGN(pack_fn(rta, value)) - total_len += rta_len - addr += rta_len - return total_len - - def rta_linkinfo(self, addr, rtas): - total_len = 0 - - # Check interface kind - kind = rtas.get(IFLA_INFO_KIND) - if kind == 'vlan': - data_policy = self.rta_linkinfo_data_vlan_policy() - else: - data_policy = self.rta_linkinfo_data_macvlan_policy() - - # Pack info kind - rta = Rtattr.from_address(addr) - rta.rta_type = IFLA_INFO_KIND - rta_len = NLMSG_ALIGN(self.rta_string(rta, kind)) - total_len += rta_len - addr += rta_len - - # nest start link info data - rta = Rtattr.from_address(addr) - rta.rta_type = IFLA_INFO_DATA - rta.rta_len = RTA_LENGTH(0) - rta_len = NLMSG_ALIGN(rta.rta_len) - total_len += rta_len - addr += rta_len - rta_len = self.pack_rtas_new(rtas.get(IFLA_INFO_DATA), addr, - data_policy) - rta.rta_len += rta_len - - total_len += rta_len - addr += rta_len - - return total_len - - def rta_bridge_vlan_info(self, rta, value): - if value: - data = RTA_DATA(rta) - memmove(data, addressof(value), sizeof(value)) - rta.rta_len = RTA_LENGTH(sizeof(value)) - return rta.rta_len - - def rta_af_spec(self, addr, rtas): - total_len = 0 - - # XXX: Check family (Assumes bridge family for now) - rta_len = self.pack_rtas_new(rtas, addr, - self.rta_bridge_af_spec_policy()) - total_len += rta_len - return total_len - - def unpack_rtas(self, which_ones=[]): - len = self.nlh.nlmsg_len - NLMSG_LENGTH(sizeof(self)) - rta = self.to_rta() - rtas = {} - while RTA_OK(rta, len): - rta_type = rta.rta_type - if not which_ones or rta_type in which_ones: - unpack_fn = self.rta_fn(rta_type) - rtas[rta_type] = unpack_fn(rta) - len, rta = RTA_NEXT(rta, len) - return rtas - - def dump_rtas(self): - rtas = self.unpack_rtas() - for type, value in rtas.iteritems(): - print "rta", type, ":", value - - class _IPv6Addr(BigEndianStructure): - _fields_ = [ - ('upper', c_uint64), - ('lower', c_uint64), - ] - - class _IPv4Addr(BigEndianStructure): - _fields_ = [ - ('addr', c_uint32), - ] - - def rta_uint8(self, rta, value=None): - data = RTA_DATA(rta) - if value: - c_uint8.from_address(data).value = value - rta.rta_len = RTA_LENGTH(sizeof(c_uint8)) - return rta.rta_len - else: - return c_uint8.from_address(data).value - - def rta_uint16(self, rta, value=None): - data = RTA_DATA(rta) - if value: - c_uint16.from_address(data).value = value - rta.rta_len = RTA_LENGTH(sizeof(c_uint16)) - return rta.rta_len - else: - return c_uint16.from_address(data).value - - def rta_uint32(self, rta, value=None): - data = RTA_DATA(rta) - if value: - c_uint32.from_address(data).value = value - rta.rta_len = RTA_LENGTH(sizeof(c_uint32)) - return rta.rta_len - else: - return c_uint32.from_address(data).value - - def rta_string(self, rta, value=None): - data = RTA_DATA(rta) - if value: - s = create_string_buffer(value) - memmove(data, addressof(s), len(value)) - rta.rta_len = RTA_LENGTH(len(value)) - return rta.rta_len - else: - return c_char_p(data).value - - def rta_addr(self, rta, value=None): - data = RTA_DATA(rta) - if value: - if isinstance(value, IPv4Address): - self._IPv4Addr.from_address(data).addr = value._ip - rta.rta_len = RTA_LENGTH(sizeof(self._IPv4Addr)) - elif isinstance(value, IPv6Address): - addr = self._IPv6Addr.from_address(data) - addr.upper = value._ip >> 64 - addr.lower = value._ip & 0xffffffffffffffff - rta.rta_len = RTA_LENGTH(sizeof(self._IPv6Addr)) - else: - assert(False) - return rta.rta_len - else: - if RTA_PAYLOAD(rta) == 4: - addr = c_uint32.__ctype_be__.from_address(data).value - addr = IPv4Address(addr) - else: - addr = self._IPv6Addr.from_address(data) - addr = IPv6Address((addr.upper << 64) + addr.lower) - return addr - - def rta_uint8_array(self, rta, value=None): - data = RTA_DATA(rta) - if value: - s = (c_uint8 * len(value)).from_buffer_copy(value) - memmove(data, addressof(s), len(value)) - rta.rta_len = RTA_LENGTH(len(value)) - return rta.rta_len - else: - array = (c_uint8 * RTA_PAYLOAD(rta))() - memmove(array, data, RTA_PAYLOAD(rta)) - return array - - def rta_uint32_array(self, rta, value=None): - if value: - assert(False) - else: - data = RTA_DATA(rta) - size = RTA_PAYLOAD(rta) / sizeof(c_uint32) - array = (c_uint32 * size)() - memmove(array, data, RTA_PAYLOAD(rta)) - return array - - def rta_multipath(self, rta, value=None): - # XXX implement this - return None - - def rta_wtf(self, rta, value=None): - return None - - def rta_none(self, rta, value=None): - return None - - def rta_fn(self, rta_type): - return None - - -# rtm_type - -RTN_UNSPEC = 0 -RTN_UNICAST = 1 # Gateway or direct route -RTN_LOCAL = 2 # Accept locally -RTN_BROADCAST = 3 # Accept locally as broadcast, - # send as broadcast -RTN_ANYCAST = 4 # Accept locally as broadcast, - # but send as unicast -RTN_MULTICAST = 5 # Multicast route -RTN_BLACKHOLE = 6 # Drop -RTN_UNREACHABLE = 7 # Destination is unreachable -RTN_PROHIBIT = 8 # Administratively prohibited -RTN_THROW = 9 # Not in this table -RTN_NAT = 10 # Translate this address -RTN_XRESOLVE = 11 # Use external resolver -RTN_MAX = 11 - -# rtm_protocol - -RTPROT_UNSPEC = 0 -RTPROT_REDIRECT = 1 # Route installed by ICMP redirects; - # not used by current IPv4 -RTPROT_KERNEL = 2 # Route installed by kernel -RTPROT_BOOT = 3 # Route installed during boot -RTPROT_STATIC = 4 # Route installed by administrator - -# Values of protocol >= RTPROT_STATIC are not interpreted by kernel; -# they are just passed from user and back as is. -# It will be used by hypothetical multiple routing daemons. -# Note that protocol values should be standardized in order to -# avoid conflicts. - -RTPROT_GATED = 8 # Apparently, GateD -RTPROT_RA = 9 # RDISC/ND router advertisements -RTPROT_MRT = 10 # Merit MRT -RTPROT_ZEBRA = 11 # Zebra -RTPROT_BIRD = 12 # BIRD -RTPROT_DNROUTED = 13 # DECnet routing daemon -RTPROT_XORP = 14 # XORP -RTPROT_NTK = 15 # Netsukuku -RTPROT_DHCP = 16 # DHCP client - -# rtm_scope - -# Really it is not scope, but sort of distance to the destination. -# NOWHERE are reserved for not existing destinations, HOST is our -# local addresses, LINK are destinations, located on directly attached -# link and UNIVERSE is everywhere in the Universe. - -# Intermediate values are also possible f.e. interior routes -# could be assigned a value between UNIVERSE and LINK. - -RT_SCOPE_UNIVERSE = 0 -# User defined values -RT_SCOPE_SITE = 200 -RT_SCOPE_LINK = 253 -RT_SCOPE_HOST = 254 -RT_SCOPE_NOWHERE=255 - -# rtm_flags - -RTM_F_NOTIFY = 0x100 # Notify user of route change -RTM_F_CLONED = 0x200 # This route is cloned -RTM_F_EQUALIZE = 0x400 # Multipath equalizer: NI -RTM_F_PREFIX = 0x800 # Prefix addresses - -# Reserved table identifiers - -RT_TABLE_UNSPEC = 0 -# User defined values -RT_TABLE_COMPAT = 252 -RT_TABLE_DEFAULT = 253 -RT_TABLE_MAIN = 254 -RT_TABLE_LOCAL = 255 -RT_TABLE_MAX = 0xFFFFFFFF - -# Generic structure for encapsulation of optional route information. -# It is reminiscent of sockaddr, but with sa_family replaced -# with attribute type. - -class Rtattr(Structure): - - _fields_ = [ - ('rta_len', c_uint16), - ('rta_type', c_uint16), - ] - -# Routing message attributes - -RTA_UNSPEC = 0 -RTA_DST = 1 -RTA_SRC = 2 -RTA_IIF = 3 -RTA_OIF = 4 -RTA_GATEWAY = 5 -RTA_PRIORITY = 6 -RTA_PREFSRC = 7 -RTA_METRICS = 8 -RTA_MULTIPATH = 9 -RTA_PROTOINFO = 10 # no longer used -RTA_FLOW = 11 -RTA_CACHEINFO = 12 -RTA_SESSION = 13 # no longer used -RTA_MP_ALGO = 14 # no longer used -RTA_TABLE = 15 -RTA_MAX = 15 - -# Macros to handle rtattributes - -RTA_ALIGNTO = 4 -def RTA_ALIGN(len): - return (len + RTA_ALIGNTO - 1) & ~(RTA_ALIGNTO - 1) -def RTA_OK(rta, len): - return len >= sizeof(Rtattr) and \ - rta.rta_len >= sizeof(Rtattr) and \ - rta.rta_len <= len -def RTA_NEXT(rta, len): - cur = RTA_ALIGN(rta.rta_len) - rta = Rtattr.from_address(addressof(rta) + cur) - return len - cur, rta -def RTA_LENGTH(len): - return len + RTA_ALIGN(sizeof(Rtattr)) -def RTA_SPACE(len): - return RTA_ALIGN(RTA_LENGTH(len)) -def RTA_DATA(rta): - return addressof(rta) + RTA_LENGTH(0) -def RTA_PAYLOAD(rta): - return rta.rta_len - RTA_LENGTH(0) - -RTNH_F_DEAD = 1 # Nexthop is dead (used by multipath) -RTNH_F_PERVASIVE = 2 # Do recursive gateway lookup -RTNH_F_ONLINK = 4 # Gateway is forced on link - -# Reserved table identifiers - -RT_TABLE_UNSPEC = 0 -# User defined values -RT_TABLE_COMPAT = 252 -RT_TABLE_DEFAULT = 253 -RT_TABLE_MAIN = 254 -RT_TABLE_LOCAL = 255 -RT_TABLE_MAX = 0xFFFFFFFF - -class Rtmsg(Nlmsg): - - _fields_ = [ - ('rtm_family', c_uint8), - ('rtm_dst_len', c_uint8), - ('rtm_src_len', c_uint8), - ('rtm_tos', c_uint8), - ('rtm_table', c_uint8), - ('rtm_protocol', c_uint8), - ('rtm_scope', c_uint8), - ('rtm_type', c_uint8), - ('rtm_flags', c_uint32), - ] - - _table_str = { - RT_TABLE_UNSPEC: "unspecified", - RT_TABLE_COMPAT: "compat", - RT_TABLE_DEFAULT: "default", - RT_TABLE_MAIN: "main", - RT_TABLE_LOCAL: "local", - } - - _proto_str = { - RTPROT_UNSPEC: "none", - RTPROT_REDIRECT: "redirect", - RTPROT_KERNEL: "kernel", - RTPROT_BOOT: "boot", - RTPROT_STATIC: "static", - RTPROT_GATED: "gated", - RTPROT_RA: "ra", - RTPROT_MRT: "mrtmrt", - RTPROT_ZEBRA: "zebra", - RTPROT_BIRD: "bird", - RTPROT_DNROUTED: "dnrouted", - RTPROT_XORP: "xorp", - RTPROT_NTK: "ntk", - RTPROT_DHCP: "dhcp", - } - - _scope_str = { - RT_SCOPE_UNIVERSE: "universe", - RT_SCOPE_SITE: "site", - RT_SCOPE_LINK: "link", - RT_SCOPE_HOST: "host", - RT_SCOPE_NOWHERE: "nowhere", - } - - _type_str = { - RTN_UNSPEC: "unspecified", - RTN_UNICAST: "unicast", - RTN_LOCAL: "local", - RTN_BROADCAST: "broadcast", - RTN_ANYCAST: "anycast", - RTN_MULTICAST: "multicast", - RTN_BLACKHOLE: "blackhole", - RTN_UNREACHABLE: "unreachable", - RTN_PROHIBIT: "prohibit", - RTN_THROW: "throw", - RTN_NAT: "nat", - RTN_XRESOLVE: "xresolve", - } - - def dump(self): - print 'rtm_family', self.rtm_family - print 'rtm_dst_len', self.rtm_dst_len - print 'rtm_src_len', self.rtm_src_len - print 'rtm_tos', self.rtm_tos - print 'rtm_table', self._table_str.get(self.rtm_table, self.rtm_table) - print 'rtm_protocol', self._proto_str.get(self.rtm_protocol) - print 'rtm_scope', self._scope_str.get(self.rtm_scope) - print 'rtm_type', self._type_str.get(self.rtm_type) - print 'rtm_flags 0x%08x' % self.rtm_flags - - def rta_fn(self, rta_type): - fns = { - RTA_DST: self.rta_addr, - RTA_SRC: self.rta_addr, - RTA_IIF: self.rta_uint32, - RTA_OIF: self.rta_uint32, - RTA_GATEWAY: self.rta_addr, - RTA_PRIORITY: self.rta_uint32, - RTA_PREFSRC: self.rta_addr, - RTA_METRICS: self.rta_uint32_array, - RTA_MULTIPATH: self.rta_multipath, - RTA_PROTOINFO: self.rta_none, - RTA_FLOW: self.rta_uint32, - RTA_CACHEINFO: self.rta_none, - RTA_SESSION: self.rta_none, - RTA_MP_ALGO: self.rta_none, - RTA_TABLE: self.rta_uint32, - } - - return fns.get(rta_type) - -class Rtgenmsg(Nlmsg): - - _fields_ = [ - ('rtgen_family', c_uint8), - ] - - def dump(self): - print 'rtgen_family', self.rtgen_family - -# New extended info filters for IFLA_EXT_MASK -RTEXT_FILTER_VF = (1 << 0) - -# passes link level specific information, not dependent -# on network protocol. - -IFLA_UNSPEC = 0 -IFLA_ADDRESS = 1 -IFLA_BROADCAST = 2 -IFLA_IFNAME = 3 -IFLA_MTU = 4 -IFLA_LINK = 5 -IFLA_QDISC = 6 -IFLA_STATS = 7 -IFLA_COST = 8 -IFLA_PRIORITY = 9 -IFLA_MASTER = 10 -IFLA_WIRELESS = 11 # Wireless Extension event - see wireless.h -IFLA_PROTINFO = 12 # Protocol specific information for a link -IFLA_TXQLEN = 13 -IFLA_MAP = 14 -IFLA_WEIGHT = 15 -IFLA_OPERSTATE = 16 -IFLA_LINKMODE = 17 -IFLA_LINKINFO = 18 -IFLA_NET_NS_PID = 19 -IFLA_IFALIAS = 20 -IFLA_NUM_VF = 21 # Number of VFs if device is SR-IOV PF -IFLA_VFINFO_LIST = 22 -IFLA_STATS64 = 23 -IFLA_VF_PORTS = 24 -IFLA_PORT_SELF = 25 -IFLA_AF_SPEC = 26 -IFLA_GROUP = 27 # Group the device belongs to -IFLA_NET_NS_FD = 28 -IFLA_EXT_MASK = 29 # Extended info mask, VFs, etc -IFLA_MAX = 29 - - -# IFLA_LINKINFO attributes -IFLA_INFO_UNSPEC = 0 -IFLA_INFO_KIND = 1 -IFLA_INFO_DATA = 2 -IFLA_INFO_XSTATS = 3 -IFLA_INFO_MAX = 4 - -# IFLA_LINKINFO_DATA attributes for vlan -IFLA_VLAN_UNSPEC = 0 -IFLA_VLAN_ID = 1 - -# IFLA_LINKINFO_DATA attributes for macvlan -IFLA_MACVLAN_UNSPEC = 0 -IFLA_MACVLAN_MODE = 1 - -# macvlan modes -MACVLAN_MODE_PRIVATE = 1 -MACVLAN_MODE_VEPA = 2 -MACVLAN_MODE_BRIDGE = 3 -MACVLAN_MODE_PASSTHRU = 4 - -# BRIDGE IFLA_AF_SPEC attributes -IFLA_BRIDGE_FLAGS = 0 -IFLA_BRIDGE_MODE = 1 -IFLA_BRIDGE_VLAN_INFO = 2 - -# BRIDGE_VLAN_INFO flags -BRIDGE_VLAN_INFO_MASTER = 1 -BRIDGE_VLAN_INFO_PVID = 2 -BRIDGE_VLAN_INFO_UNTAGGED = 4 - -# Bridge flags -BRIDGE_FLAGS_MASTER = 1 -BRIDGE_FLAGS_SELF = 2 - -class BridgeVlanInfo(Structure): - _fields_ = [ - ('flags', c_uint16), - ('vid', c_uint16), - ('vid_end', c_uint16), - ] - -class Ifinfomsg(Nlmsg): - - _fields_ = [ - ('ifi_family', c_uint8), - ('__ifi_pad', c_uint8), - ('ifi_type', c_uint16), # ARPHRD_* - ('ifi_index', c_int32), # Link index - ('ifi_flags', c_uint32), # IFF_* flags - ('ifi_change', c_uint32), # IFF_* change mask - ] - - def dump(self): - print 'ifi_family', self.ifi_family - print 'ifi_type', self.ifi_type - print 'ifi_index', self.ifi_index - print 'ifi_flags 0x%08x' % self.ifi_flags - print 'ifi_change 0x%08x' % self.ifi_change - - def rta_linkinfo_data_vlan_policy(self): - fns = { - IFLA_VLAN_ID : self.rta_uint16, - } - return fns - - def rta_linkinfo_data_macvlan_policy(self): - fns = { - IFLA_MACVLAN_MODE : self.rta_uint32, - } - return fns - - def rta_linkinfo_policy(self): - fns = { - IFLA_INFO_KIND : self.rta_string, - IFLA_INFO_DATA : self.rta_linkinfo_data, - } - return fns - - def rta_bridge_af_spec_policy(self): - # Assume bridge family for now - fns = { - IFLA_BRIDGE_FLAGS : self.rta_uint16, - IFLA_BRIDGE_VLAN_INFO : self.rta_bridge_vlan_info, - } - return fns - - def rta_policy(self): - fns = { - IFLA_UNSPEC: self.rta_wtf, - IFLA_ADDRESS: self.rta_uint8_array, - IFLA_BROADCAST: self.rta_uint8_array, - IFLA_IFNAME: self.rta_string, - IFLA_MTU: self.rta_uint32, - IFLA_LINK: self.rta_uint32, - IFLA_QDISC: self.rta_string, - IFLA_STATS: self.rta_none, - IFLA_COST: self.rta_none, - IFLA_PRIORITY: self.rta_none, - IFLA_MASTER: self.rta_uint32, - IFLA_WIRELESS: self.rta_none, - IFLA_PROTINFO: self.rta_none, - IFLA_TXQLEN: self.rta_uint32, - IFLA_MAP: self.rta_none, - IFLA_WEIGHT: self.rta_uint32, - IFLA_OPERSTATE: self.rta_uint8, - IFLA_LINKMODE: self.rta_uint8, - IFLA_LINKINFO: self.rta_linkinfo, - IFLA_NET_NS_PID: self.rta_uint32, - IFLA_IFALIAS: self.rta_string, - IFLA_NUM_VF: self.rta_uint32, - IFLA_VFINFO_LIST: self.rta_none, - IFLA_STATS64: self.rta_none, - IFLA_VF_PORTS: self.rta_none, - IFLA_PORT_SELF: self.rta_none, - IFLA_AF_SPEC: self.rta_af_spec, - IFLA_GROUP: self.rta_none, - IFLA_NET_NS_FD: self.rta_none, - IFLA_EXT_MASK: self.rta_none, - } - return fns; - - def rta_fn(self, rta_type): - fns = { - IFLA_UNSPEC: self.rta_wtf, - IFLA_ADDRESS: self.rta_uint8_array, - IFLA_BROADCAST: self.rta_uint8_array, - IFLA_IFNAME: self.rta_string, - IFLA_MTU: self.rta_uint32, - IFLA_LINK: self.rta_uint32, - IFLA_QDISC: self.rta_string, - IFLA_STATS: self.rta_none, - IFLA_COST: self.rta_none, - IFLA_PRIORITY: self.rta_none, - IFLA_MASTER: self.rta_uint32, - IFLA_WIRELESS: self.rta_none, - IFLA_PROTINFO: self.rta_none, - IFLA_TXQLEN: self.rta_uint32, - IFLA_MAP: self.rta_none, - IFLA_WEIGHT: self.rta_uint32, - IFLA_OPERSTATE: self.rta_uint8, - IFLA_LINKMODE: self.rta_uint8, - IFLA_LINKINFO: self.rta_linkinfo, - IFLA_NET_NS_PID: self.rta_uint32, - IFLA_IFALIAS: self.rta_string, - IFLA_NUM_VF: self.rta_uint32, - IFLA_VFINFO_LIST: self.rta_none, - IFLA_STATS64: self.rta_none, - IFLA_VF_PORTS: self.rta_none, - IFLA_PORT_SELF: self.rta_none, - IFLA_AF_SPEC: self.rta_af_spec, - IFLA_GROUP: self.rta_none, - IFLA_NET_NS_FD: self.rta_none, - IFLA_EXT_MASK: self.rta_none, - } - return fns.get(rta_type) - -# passes address specific information - -# Important comment: -# IFA_ADDRESS is prefix address, rather than local interface address. -# It makes no difference for normally configured broadcast interfaces, -# but for point-to-point IFA_ADDRESS is DESTINATION address, -# local address is supplied in IFA_LOCAL attribute. - -IFA_UNSPEC = 0 -IFA_ADDRESS = 1 -IFA_LOCAL = 2 -IFA_LABEL = 3 -IFA_BROADCAST = 4 -IFA_ANYCAST = 5 -IFA_CACHEINFO = 6 -IFA_MULTICAST = 7 -IFA_MAX = 7 - -class Ifaddrmsg(Nlmsg): - - _fields_ = [ - ('ifa_family', c_uint8), - ('ifa_prefixlen', c_uint8), # The prefix length - ('ifa_flags', c_uint8), # Flags - ('ifa_scope', c_uint8), # Address scope - ('ifa_index', c_uint32), # Link index - ] - - _family_str = { - AF_INET: "inet", - AF_INET6: "inet6", - } - - def dump(self): - print 'ifa_family', self.ifa_family - print 'ifa_prefixlen', self.ifa_prefixlen - print 'ifa_flags 0x%02x' % self.ifa_flags - print 'ifa_scope', self.ifa_scope - print 'ifa_index', self.ifa_index - - def rta_fn(self, rta_type): - fns = { - IFA_ADDRESS: self.rta_addr, - IFA_LOCAL: self.rta_addr, - IFA_LABEL: self.rta_string, - IFA_BROADCAST: self.rta_addr, - IFA_ANYCAST: self.rta_addr, - IFA_CACHEINFO: self.rta_none, - IFA_MULTICAST: self.rta_addr, - } - return fns.get(rta_type) - -class RtNetlinkError(Exception): - - def __init__(self, message): - Exception.__init__(self, message) - logger.error(message) - -class RtNetlink(Netlink): - - def __init__(self, pid): - Netlink.__init__(self, pid, NETLINK_ROUTE) - - _rt_nlmsg_type_str = { - RTM_NEWROUTE: "RTM_NEWROUTE", - RTM_DELROUTE: "RTM_DELROUTE", - RTM_NEWLINK: "RTM_NEWLINK", - RTM_SETLINK: "RTM_SETLINK", - RTM_DELLINK: "RTM_DELLINK", - RTM_GETLINK: "RTM_GETLINK", - RTM_NEWADDR: "RTM_NEWADDR", - RTM_DELADDR: "RTM_DELADDR", - } - - def _hexdump(self, buf): - while buf: - chunk = buf[:16] - buf = buf[16:] - nums = ["%02x" % c for c in chunk] - txt = [chr(c) if chr(c) in printable[:-5] else '.' for c in chunk] - print " ".join(nums).ljust(48), "".join(txt) - - def dump(self, nlh): - nlmsg = self.nlmsg(nlh) - print - self._hexdump(self.sendbuf[:nlh.nlmsg_len]) - print - nlh.dump() - print - nlmsg.dump() - print - nlmsg.dump_rtas() - - def nlmsg(self, nlh): - nlmsg_struct = { - RTM_NEWROUTE: Rtmsg, - RTM_DELROUTE: Rtmsg, - RTM_GETROUTE: Rtmsg, - RTM_NEWLINK: Ifinfomsg, - RTM_SETLINK: Ifinfomsg, - RTM_DELLINK: Ifinfomsg, - RTM_GETLINK: Rtgenmsg, - RTM_NEWADDR: Ifaddrmsg, - RTM_DELADDR: Ifaddrmsg, - RTM_GETADDR: Rtgenmsg, - } - nldata = NLMSG_DATA(nlh) - nlmsg = nlmsg_struct[nlh.nlmsg_type].from_address(nldata) - nlmsg.nlh = nlh - return nlmsg - - def _nl_cb(self, nlh): -# print "nl cb", self._rt_nlmsg_type_str[nlh.nlmsg_type] - - if nlh.nlmsg_type in self._cbs: - - nlmsg = self.nlmsg(nlh) - - # validate nl length - if nlh.nlmsg_len - NLMSG_LENGTH(sizeof(nlmsg)) < 0: - raise RtNetlinkError("invalid nl length") - - self._cbs[nlh.nlmsg_type](nlh, nlmsg) - - def bind(self, groups, cbs): - self._cbs = cbs - Netlink.bind(self, groups, self._nl_cb) - - def request(self, nlmsg_type, flags, extra, rtas={}): - - nlh = Nlmsghdr.from_buffer(self.sendbuf) - nlh_p = addressof(nlh) - - seq = self.seq - pid = self.pid - - nlh.nlmsg_len = NLMSG_HDRLEN() - nlh.nlmsg_type = nlmsg_type - nlh.nlmsg_flags = flags - nlh.nlmsg_pid = pid - nlh.nlmsg_seq = seq - - nlmsg = self.nlmsg(nlh) - - nlh.nlmsg_len += nlmsg.pack_extra(extra, nlh_p + nlh.nlmsg_len) - nlh.nlmsg_len += nlmsg.pack_rtas_new(rtas, nlh_p + nlh.nlmsg_len, - nlmsg.rta_policy()) - #self.dump(nlh) - self.sendall(string_at(nlh_p, nlh.nlmsg_len)) - self.seq += 1 - - token = (pid, seq) - return token diff --git a/ifupdown/rtnetlink_api.py b/ifupdown/rtnetlink_api.py deleted file mode 100644 index f9bba10..0000000 --- a/ifupdown/rtnetlink_api.py +++ /dev/null @@ -1,237 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. -# -# Author: Roopa Prabhu, roopa@cumulusnetworks.com -# -# - -from os import getpid -from socket import AF_UNSPEC -from socket import AF_BRIDGE -from iff import IFF_UP -from rtnetlink import * -import os -import ifupdownmain - -class rtnetlinkApi(RtNetlink): - - bind_done = False - - def __init__(self, pid): - RtNetlink.__init__(self, pid) - self.logger = logging.getLogger('ifupdown.' + - self.__class__.__name__) - self.bind(0, None) - self.bind_done = True - self.ifindexmap = {} - - def do_bind(self): - if self.bind_done: - return True - self.bind(0, None) - self.bind_done = True - - def get_ifindex(self, ifname): - ifindex = self.ifindexmap.get(ifname) - if not ifindex: - with open('/sys/class/net/%s/ifindex' %ifname, 'r') as f: - ifindex = int(f.read()) - self.ifindexmap[ifname] = ifindex - return ifindex - - def create_vlan(self, link, ifname, vlanid): - self.logger.info('rtnetlink: creating vlan interface %s' %ifname) - if ifupdownmain.ifupdownFlags.DRYRUN: - return - try: - ifindex = self.get_ifindex(link) - except Exception, e: - raise Exception('cannot determine ifindex for link %s (%s)' - %(link, str(e))) - - ifm = Ifinfomsg(AF_UNSPEC) - rtas = {IFLA_IFNAME: ifname, - IFLA_LINK : ifindex, - IFLA_LINKINFO : { - IFLA_INFO_KIND : 'vlan', - IFLA_INFO_DATA : { - IFLA_VLAN_ID : vlanid, - } - } - } - token = self.request(RTM_NEWLINK, - NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK, ifm, rtas) - self.process_wait([token]) - - def create_macvlan(self, ifname, link, mode='private'): - self.logger.info('rtnetlink: creating macvlan interface %s' %ifname) - if ifupdownmain.ifupdownFlags.DRYRUN: - return - try: - ifindex = self.get_ifindex(link) - except Exception, e: - raise Exception('cannot determine ifindex for link %s (%s)' - %(link, str(e))) - - ifm = Ifinfomsg(AF_UNSPEC) - rtas = {IFLA_IFNAME: ifname, - IFLA_LINK : ifindex, - IFLA_LINKINFO : { - IFLA_INFO_KIND : 'macvlan', - IFLA_INFO_DATA : { - IFLA_MACVLAN_MODE : MACVLAN_MODE_PRIVATE, - } - } - } - token = self.request(RTM_NEWLINK, NLM_F_CREATE | NLM_F_REQUEST | - NLM_F_ACK, ifm, rtas) - self.process_wait([token]) - - def link_set(self, ifname, state): - flags = 0 - self.logger.info('rtnetlink: setting link %s %s' %(ifname, state)) - if ifupdownmain.ifupdownFlags.DRYRUN: - return - - if state == "up": - flags |= IFF_UP - else: - flags &= ~IFF_UP - - ifm = Ifinfomsg(AF_UNSPEC, ifi_change=IFF_UP, ifi_flags=flags) - rtas = {IFLA_IFNAME: ifname} - - token = self.request(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_ACK, ifm, rtas) - self.process_wait([token]) - - def link_set_hwaddress(self, ifname, hwaddress): - flags = 0 - self.logger.info('rtnetlink: setting link hwaddress %s %s' %(ifname, hwaddress)) - if ifupdownmain.ifupdownFlags.DRYRUN: - return - - flags &= ~IFF_UP - ifm = Ifinfomsg(AF_UNSPEC, ifi_change=IFF_UP) - rtas = {IFLA_IFNAME: ifname, - IFLA_ADDRESS : str(bytearray([int(a,16) for a in hwaddress.split(':')]))} - - self.logger.info(rtas) - - token = self.request(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_ACK, ifm, rtas) - self.process_wait([token]) - - def addr_add(self, ifname, address, broadcast=None, peer=None, scope=None, - preferred_lifetime=None): - self.logger.info('rtnetlink: setting address') - if ifupdownmain.ifupdownFlags.DRYRUN: - return - - try: - ifindex = self.get_ifindex(link) - except Exception, e: - raise Exception('cannot determine ifindex for link %s (%s)' - %(link, str(e))) - ifa_scope = RT_SCOPE_ - if scope: - if scope == "universe": - ifa_scope = RT_SCOPE_UNIVERSE - elif scope == "site": - ifa_scope = RT_SCOPE_SITE - elif scope == "link": - ifa_scope = RT_SCOPE_LINK - elif scope == "host": - ifa_scope = RT_SCOPE_HOST - elif scope == "nowhere": - ifa_scope = RT_SCOPE_NOWHERE - rtas = {IFLA_ADDRESS: ifname} - - ifa = Ifaddrmsg(AF_UNSPEC, ifa_scope=ifa_scope, ifa_index=ifindex) - - token = self.request(RTM_NEWADDR, NLM_F_REQUEST | NLM_F_ACK, ifa, rtas) - self.process_wait([token]) - - def link_set_many(self, ifname, ifattrs): - _ifattr_to_rta_map = {'dev' : IFLA_NAME, - 'address' : IFLA_ADDRESS, - 'broadcast' : IFLA_BROADCAST, - 'mtu' : IFLA_MTU, - 'master' : IFLA_MASTER} - flags = 0 - ifi_change = IFF_UP - rtas = {} - self.logger.info('rtnetlink: setting link %s %s' %(ifname, state)) - if ifupdownmain.ifupdownFlags.DRYRUN: - return - if not ifattrs: - return - state = ifattrs.get('state') - if state == 'up': - flags |= IFF_UP - elif state == 'down': - flags &= ~IFF_UP - else: - ifi_change = 0 - - if ifi_change: - ifm = Ifinfomsg(AF_UNSPEC, ifi_change=IFF_UP, ifi_flags=flags) - else: - ifm = Ifinfomsg(AF_UNSPEC) - - for attr, attrval in ifattrs.items(): - rta_attr = _ifattr_to_rta_map.get(attr) - if rta_attr: - if attr == 'hwaddress': - rtas[rta_attr] = str(bytearray([int(a,16) for a in attrval.split(':')])) - else: - rtas[rta_attr] = attrval - - token = self.request(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_ACK, ifm, rtas) - self.process_wait([token]) - - def bridge_vlan(self, add=True, vid=None, dev=None, pvid=False, - untagged=False, master=True): - flags = 0 - vflags = 0 - if not vid or not dev: - return - self.logger.info('rtnetlink: bridge vlan add vid %s %s %s dev %s %s' - %(vid, 'untagged' if untagged else '', - 'pvid' if pvid else '', dev, - 'self' if self else '')) - if ifupdownmain.ifupdownFlags.DRYRUN: - return - try: - ifindex = self.get_ifindex(dev) - except Exception, e: - raise Exception('cannot determine ifindex for dev %s (%s)' - %(dev, str(e))) - if not master: - flags = BRIDGE_FLAGS_SELF - - if pvid: - vflags = BRIDGE_VLAN_INFO_PVID - vflags |= BRIDGE_VLAN_INFO_UNTAGGED - elif untagged: - vflags |= BRIDGE_VLAN_INFO_UNTAGGED - - ifm = Ifinfomsg(AF_BRIDGE, ifi_index=ifindex) - rtas = {IFLA_AF_SPEC: { - IFLA_BRIDGE_FLAGS: flags, - IFLA_BRIDGE_VLAN_INFO : BridgeVlanInfo(vflags, int(vid), int(vid)) - } - } - if add: - token = self.request(RTM_SETLINK, - NLM_F_REQUEST | NLM_F_ACK, ifm, rtas) - else: - token = self.request(RTM_DELLINK, - NLM_F_REQUEST | NLM_F_ACK, ifm, rtas) - self.process_wait([token]) - - def bridge_vlan_many(self, add=True, vids=[], dev=None, pvid=False, - untagged=False, master=True): - for v in vids: - self.bridge_vlan_add(add, v, dev, ispvid, isuntagged, master) - -rtnl_api = rtnetlinkApi(os.getpid()) diff --git a/ifupdown2/__init__.py b/ifupdown2/__init__.py new file mode 100644 index 0000000..c720dc0 --- /dev/null +++ b/ifupdown2/__init__.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +__version__ = '2.0.0' + +# Copyright (C) 2014,2015,2016,2017,2018 Cumulus Networks, Inc. All rights reserved +# +# This program 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; version 2. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. +# +# https://www.gnu.org/licenses/gpl-2.0-standalone.html +# +# Authors: +# Roopa Prabhu, roopa@cumulusnetworks.com +# Julien Fortin, julien@cumulusnetworks.com + +__license__ = 'GPL-2' +__status__ = 'Production' +__copyright__ = 'Copyright (C) 2014,2015,2016,2017,2018 Cumulus Networks, Inc.' + + +__authors__ = [ + 'Roopa Prabhu', + 'Julien Fortin' +] +__credits__ = [ + 'Roopa Prabhu', + 'Julien Fortin' +] +__emails__ = [ + 'roopa@cumulusnetworks.com', + 'julien@cumulusnetworks.com' +] +__maintainer__ = 'Julien Fortin' +__email__ = 'julien@cumulusnetworks.com' diff --git a/ifupdown2/__main__.py b/ifupdown2/__main__.py new file mode 100755 index 0000000..4dd4270 --- /dev/null +++ b/ifupdown2/__main__.py @@ -0,0 +1,205 @@ +#!/usr/bin/python +# +# Copyright 2016 Cumulus Networks, Inc. All rights reserved. +# Author: Julien Fortin, julien@cumulusnetworks.com +# +# + +import os +import re +import sys +import json +import errno +import struct +import select +import socket +import signal + +try: + import ifupdown2.ifupdown.config as core_config + from ifupdown2.ifupdown.log import log + from ifupdown2 import __version__ + + core_config.__version__ = __version__ +except: + import ifupdown.config as core_config + from ifupdown.log import log + + core_config.__version__ = __import__('__init__').__version__ + + +class Ifupdown2Complete(Exception): + def __init__(self, status): + self.status = status + + +class Ifupdown2Client: + def __init__(self, argv): + + self.stdin = None + self.argv = argv + self.data = '' + self.HEADER_SIZE = 4 + self.daemon_pid = -1 + + self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + try: + self.socket.connect('/var/run/ifupdown2d/uds') + + signal.signal(signal.SIGINT, self.signal_handler) + signal.signal(signal.SIGTERM, self.signal_handler) + signal.signal(signal.SIGQUIT, self.signal_handler) + + try: + self.SO_PEERCRED = socket.SO_PEERCRED + except AttributeError: + # powerpc is the only non-generic we care about. alpha, mips, + # sparc, and parisc also have non-generic values. + machine = os.uname()[4] + if re.search(r'^(ppc|powerpc)', machine): + self.SO_PASSCRED = 20 + self.SO_PEERCRED = 21 + else: + self.SO_PASSCRED = 16 + self.SO_PEERCRED = 17 + try: + self.socket.setsockopt(socket.SOL_SOCKET, self.SO_PASSCRED, 1) + except Exception as e: + raise Exception('setsockopt: %s' % str(e)) + + except socket.error: + self.socket.close() + self.socket = None + sys.stderr.write(""" + ERROR: %s could not connect to ifupdown2d + + Try starting ifupdown2d with: + sudo systemctl start ifupdown2d + + To configure ifupdown2d to start when the box boots: + sudo systemctl enable ifupdown2d + """ % argv[0]) + + def __del__(self): + if self.socket: + self.socket.close() + + def signal_handler(self, sig, frame): + if self.daemon_pid > 0: + os.kill(self.daemon_pid, sig) + + def read_data(self): + ready = select.select([self.socket], [], []) + if ready and ready[0] and ready[0][0] == self.socket: + d = self.socket.recv(65536) + if self.data: + self.data += d + else: + self.data = d + return True + return False + + def get_packets(self): + """ + ifupdown2 output is divided into "json packets" + the first 4 bytes is the size of the next json + object to etract + + """ + data_size = len(self.data) + if not data_size: + raise Ifupdown2Complete(status=1) + + packets = [] + try: + while data_size > 0: + packet_len = struct.unpack('=I', self.data[:self.HEADER_SIZE])[0] + packet_data = self.data[self.HEADER_SIZE:packet_len + self.HEADER_SIZE] + + fmt = "=%ds" % packet_len + + packets.append(json.loads(struct.unpack(fmt, packet_data)[0])) + + self.data = self.data[self.HEADER_SIZE + packet_len:] + data_size -= self.HEADER_SIZE + packet_len + except: + pass + return packets + + def process_packets(self, packets): + for packet in packets: + if 'pid' in packet: + self.daemon_pid = packet['pid'] + if 'stdout' in packet: + sys.stdout.write(packet['stdout']) + if 'stderr' in packet: + sys.stderr.write(packet['stderr']) + if 'status' in packet: + raise Ifupdown2Complete(packet['status']) + + def run(self): + status = 1 + if self.socket: + for arg in ['-i', '--interfaces']: + try: + if self.argv[self.argv.index(arg) + 1] == '-': + self.stdin = sys.stdin.read() + continue + except (ValueError, IndexError): + pass + + self.socket.send(json.dumps({ + 'argv': self.argv, + 'stdin': self.stdin + })) + + try: + while True: + try: + self.read_data() + self.process_packets(self.get_packets()) + except Ifupdown2Complete as e: + status = e.status + break + except Exception as e: + if ((isinstance(e.args, tuple) and e[0] == 4) + or (hasattr(e, 'errno') and e.errno == errno.EINTR)): + pass + else: + raise + except Exception as e: + sys.stderr.write(str(e)) + finally: + self.socket.close() + return status if status != None else 1 + + +def ifupdown2_standalone(): + try: + import ifupdown2.ifupdown.main as main_ifupdown2 + except: + import ifupdown.main as main_ifupdown2 + ifupdown2 = main_ifupdown2.Ifupdown2(daemon=False, uid=os.geteuid()) + ifupdown2.parse_argv(sys.argv) + ifupdown2.update_logger() + return ifupdown2.main() + + +def main(): + try: + if 'use_daemon=yes' in open(core_config.IFUPDOWN2_CONF_PATH).read(): + return Ifupdown2Client(sys.argv).run() + else: + return ifupdown2_standalone() + except KeyboardInterrupt: + return 1 + except Exception as e: + log.error(str(e)) + return 1 + + +if __name__ == '__main__': + try: + sys.exit(main()) + except KeyboardInterrupt: + sys.exit(1) diff --git a/addons/__init__.py b/ifupdown2/addons/__init__.py similarity index 100% rename from addons/__init__.py rename to ifupdown2/addons/__init__.py diff --git a/addons/address.py b/ifupdown2/addons/address.py similarity index 62% rename from addons/address.py rename to ifupdown2/addons/address.py index e67c29f..dc43f77 100644 --- a/addons/address.py +++ b/ifupdown2/addons/address.py @@ -1,26 +1,38 @@ #!/usr/bin/python # -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. # Author: Roopa Prabhu, roopa@cumulusnetworks.com # -import os +from ipaddr import IPNetwork, IPv4Network, IPv6Network, _BaseV6 try: - from ipaddr import IPNetwork, IPv4Network, IPv6Network, IPv4Address, IPv6Address - from sets import Set + from ifupdown2.ifupdown.iface import * + from ifupdown2.ifupdown.utils import utils + from ifupdown2.ifupdown.netlink import netlink + + from ifupdown2.ifupdownaddons.dhclient import dhclient + from ifupdown2.ifupdownaddons.LinkUtils import LinkUtils + from ifupdown2.ifupdownaddons.modulebase import moduleBase + + import ifupdown2.ifupdown.statemanager as statemanager + import ifupdown2.ifupdown.policymanager as policymanager + import ifupdown2.ifupdown.ifupdownflags as ifupdownflags + import ifupdown2.ifupdown.ifupdownconfig as ifupdownconfig +except ImportError: from ifupdown.iface import * from ifupdown.utils import utils - from ifupdownaddons.modulebase import moduleBase - from ifupdownaddons.iproute2 import iproute2 + from ifupdown.netlink import netlink + from ifupdownaddons.dhclient import dhclient + from ifupdownaddons.LinkUtils import LinkUtils + from ifupdownaddons.modulebase import moduleBase + + import ifupdown.statemanager as statemanager import ifupdown.policymanager as policymanager - from ifupdown.netlink import netlink - import ifupdown.ifupdownconfig as ifupdownConfig import ifupdown.ifupdownflags as ifupdownflags - import ifupdown.statemanager as statemanager -except ImportError, e: - raise ImportError (str(e) + "- required module not found") + import ifupdown.ifupdownconfig as ifupdownconfig + class address(moduleBase): """ ifupdown2 addon module to configure address, mtu, hwaddress, alias @@ -81,7 +93,23 @@ class address(moduleBase): { 'help' : 'Anycast local IP address for ' + 'dual connected VxLANs', 'validvals' : ['', ], - 'example' : ['clagd-vxlan-anycast-ip 36.0.0.11']}}} + 'example' : ['clagd-vxlan-anycast-ip 36.0.0.11']}, + 'ip-forward' : + { 'help': 'ip forwarding flag', + 'validvals': ['on', 'off'], + 'default' : 'off', + 'example' : ['ip-forward off']}, + 'ip6-forward' : + { 'help': 'ipv6 forwarding flag', + 'validvals': ['on', 'off'], + 'default' : 'off', + 'example' : ['ip6-forward off']}, + 'mpls-enable' : + { 'help': 'mpls enable flag', + 'validvals': ['yes', 'no'], + 'default' : 'no', + 'example' : ['mpls-enable yes']}, + }} def __init__(self, *args, **kargs): moduleBase.__init__(self, *args, **kargs) @@ -89,6 +117,15 @@ class address(moduleBase): self._bridge_fdb_query_cache = {} self.default_mtu = policymanager.policymanager_api.get_attr_default(module_name=self.__class__.__name__, attr='mtu') self.max_mtu = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='max_mtu') + self.ipforward = policymanager.policymanager_api.get_attr_default(module_name=self.__class__.__name__, attr='ip-forward') + self.ip6forward = policymanager.policymanager_api.get_attr_default(module_name=self.__class__.__name__, attr='ip6-forward') + self.ifaces_defaults = policymanager.policymanager_api.get_iface_defaults(module_name=self.__class__.__name__) + self.enable_l3_iface_forwarding_checks = utils.get_boolean_from_string( + policymanager.policymanager_api.get_module_globals( + self.__class__.__name__, + 'enable_l3_iface_forwarding_checks' + ) + ) if not self.default_mtu: self.default_mtu = '1500' @@ -98,10 +135,63 @@ class address(moduleBase): if self.max_mtu: self.logger.info('address: using max mtu %s' %self.max_mtu) + self.lower_iface_mtu_checked_list = list() + def syntax_check(self, ifaceobj, ifaceobj_getfunc=None): return (self.syntax_check_multiple_gateway(ifaceobj) and self.syntax_check_addr_allowed_on(ifaceobj, True) - and self.syntax_check_mtu(ifaceobj, ifaceobj_getfunc)) + and self.syntax_check_mtu(ifaceobj, ifaceobj_getfunc) + and self.syntax_check_sysctls(ifaceobj) + and self.syntax_check_enable_l3_iface_forwardings(ifaceobj, ifaceobj_getfunc, syntax_check=True)) + + def syntax_check_enable_l3_iface_forwardings(self, ifaceobj, ifaceobj_getfunc, syntax_check=False): + if (self.enable_l3_iface_forwarding_checks + and (ifaceobj.link_kind & ifaceLinkKind.VLAN + or ifaceobj.link_kind & ifaceLinkKind.BRIDGE) + and not ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT): + + ifname = ifaceobj.name + vlan_addr = None + vlan_ipforward_off = None + + for obj in ifaceobj_getfunc(ifname) or [ifaceobj]: + if not vlan_addr: + vlan_addr = obj.get_attr_value('address') + + if not vlan_ipforward_off: + ip_forward_value = obj.get_attr_value_first('ip-forward') + + if ip_forward_value and not utils.get_boolean_from_string(ip_forward_value): + vlan_ipforward_off = True + + if vlan_addr and vlan_ipforward_off: + if syntax_check: + raise Exception( + 'configuring ip-forward off and ip address(es) (%s) is not compatible' + % (', '.join(vlan_addr)) + ) + else: + raise Exception( + '%s: configuring ip-forward off and ip address(es) (%s) is not compatible' + % (ifname, ', '.join(vlan_addr)) + ) + + return True + + def syntax_check_sysctls(self, ifaceobj): + result = True + bridge_port = (ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT) + ipforward = ifaceobj.get_attr_value_first('ip-forward') + if bridge_port and ipforward: + result = False + self.log_error('%s: \'ip-forward\' is not supported for ' + 'bridge port' %ifaceobj.name) + ip6forward = ifaceobj.get_attr_value_first('ip6-forward') + if bridge_port and ip6forward: + result = False + self.log_error('%s: \'ip6-forward\' is not supported for ' + 'bridge port' %ifaceobj.name) + return result def syntax_check_mtu(self, ifaceobj, ifaceobj_getfunc): mtu = ifaceobj.get_attr_value_first('mtu') @@ -150,10 +240,7 @@ class address(moduleBase): return False def _get_hwaddress(self, ifaceobj): - hwaddress = ifaceobj.get_attr_value_first('hwaddress') - if hwaddress and hwaddress.startswith("ether"): - hwaddress = hwaddress[5:].strip() - return hwaddress + return utils.strip_hwaddress(ifaceobj.get_attr_value_first('hwaddress')) def _process_bridge(self, ifaceobj, up): hwaddress = self._get_hwaddress(ifaceobj) @@ -161,8 +248,9 @@ class address(moduleBase): is_vlan_dev_on_vlan_aware_bridge = False is_bridge = self.ipcmd.is_bridge(ifaceobj.name) if not is_bridge: - if '.' in ifaceobj.name: - (bridgename, vlan) = ifaceobj.name.split('.') + if ifaceobj.link_kind & ifaceLinkKind.VLAN: + bridgename = ifaceobj.lowerifaces[0] + vlan = self._get_vlan_id(ifaceobj) is_vlan_dev_on_vlan_aware_bridge = self.ipcmd.bridge_is_vlan_aware(bridgename) if ((is_bridge and not self.ipcmd.bridge_is_vlan_aware(ifaceobj.name)) or is_vlan_dev_on_vlan_aware_bridge): @@ -250,8 +338,8 @@ class address(moduleBase): def _inet_address_config(self, ifaceobj, ifaceobj_getfunc=None, force_reapply=False): squash_addr_config = (True if \ - ifupdownConfig.config.get('addr_config_squash', \ - '0') == '1' else False) + ifupdownconfig.config.get('addr_config_squash', \ + '0') == '1' else False) if (squash_addr_config and not (ifaceobj.flags & ifaceobj.YOUNGEST_SIBLING)): @@ -266,8 +354,12 @@ class address(moduleBase): else: ifaceobjlist = [ifaceobj] + module_name = self.__class__.__name__ + ifname = ifaceobj.name + (addr_supported, newaddrs, newaddr_attrs) = self._inet_address_convert_to_cidr(ifaceobjlist) - newaddrs = utils.get_normalized_ip_addr(ifaceobj.name, newaddrs) + newaddrs = utils.get_ip_objs(module_name, ifname, newaddrs) + if not addr_supported: return if (not squash_addr_config and (ifaceobj.flags & iface.HAS_SIBLINGS)): @@ -281,7 +373,7 @@ class address(moduleBase): if not ifupdownflags.flags.PERFMODE and purge_addresses == 'yes': # if perfmode is not set and purge addresses is not set to 'no' # lets purge addresses not in the config - runningaddrs = utils.get_normalized_ip_addr(ifaceobj.name, self.ipcmd.addr_get(ifaceobj.name, details=False)) + runningaddrs = self.ipcmd.get_running_addrs(ifaceobj, details=False) # if anycast address is configured on 'lo' and is in running config # add it to newaddrs so that ifreload doesn't wipe it out @@ -289,35 +381,102 @@ class address(moduleBase): if runningaddrs and anycast_addr and anycast_addr in runningaddrs: newaddrs.append(anycast_addr) - if newaddrs == runningaddrs: + + user_ip4, user_ip6, newaddrs = self.order_user_configured_addrs(newaddrs) + + if newaddrs == runningaddrs or self.compare_running_ips_and_user_config(user_ip4, user_ip6, runningaddrs): if force_reapply: self._inet_address_list_config(ifaceobj, newaddrs, newaddr_attrs) return try: # if primary address is not same, there is no need to keep any. # reset all addresses - if (newaddrs and runningaddrs and - (newaddrs[0] != runningaddrs[0])): - self.ipcmd.del_addr_all(ifaceobj.name) + if newaddrs and runningaddrs and newaddrs[0] != runningaddrs[0]: + skip_addrs = [] else: - self.ipcmd.del_addr_all(ifaceobj.name, newaddrs) + skip_addrs = newaddrs or [] + for addr in runningaddrs or []: + if addr in skip_addrs: + continue + self.ipcmd.addr_del(ifaceobj.name, addr) except Exception, e: self.log_warn(str(e)) if not newaddrs: return self._inet_address_list_config(ifaceobj, newaddrs, newaddr_attrs) - def _add_delete_gateway(self, ifaceobj, gateways=[], prev_gw=[]): - vrf = ifaceobj.get_attr_value_first('vrf') - metric = ifaceobj.get_attr_value_first('metric') - for del_gw in list(set(prev_gw) - set(gateways)): + def compare_running_ips_and_user_config(self, user_ip4, user_ip6, running_addrs): + """ + We need to compare the user config ips and the running ips. + ip4 ordering matters (primary etc) but ip6 order doesn't matter + + this function replaces the strict comparison previously in place + if newaddrs == running_addrs ? + + We will compare if the ip4 ordering is correct, then check if all + ip6 are present in the list (without checking the ordering) + """ + if (user_ip4 or user_ip6) and not running_addrs: + return False + elif running_addrs and not user_ip4 and not user_ip6: + return False + elif not running_addrs and not user_ip4 and not user_ip6: + return True + + len_ip4 = len(user_ip4) + len_running_addrs = len(running_addrs) + + if len_ip4 > len_running_addrs: + return False + + i = 0 + while i < len_ip4: + if user_ip4[i] != running_addrs[i]: + return False + i += 1 + + if len_ip4 > 0: + running_ip6 = running_addrs[len_ip4:] + else: + running_ip6 = running_addrs + + i = 0 + len_ip6 = len(user_ip6) + + for ip6 in running_ip6: + if ip6 not in user_ip6: + return False + i += 1 + + return i == len_ip6 + + def order_user_configured_addrs(self, user_config_addrs): + ip4 = [] + ip6 = [] + + for a in user_config_addrs: + if isinstance(a, _BaseV6): + ip6.append(str(a)) + else: + ip4.append(str(a)) + + return ip4, ip6, ip4 + ip6 + + def _delete_gateway(self, ifaceobj, gateways, vrf, metric): + for del_gw in gateways: try: self.ipcmd.route_del_gateway(ifaceobj.name, del_gw, vrf, metric) except Exception as e: self.logger.debug('%s: %s' % (ifaceobj.name, str(e))) + + def _add_delete_gateway(self, ifaceobj, gateways=[], prev_gw=[]): + vrf = ifaceobj.get_attr_value_first('vrf') + metric = ifaceobj.get_attr_value_first('metric') + self._delete_gateway(ifaceobj, list(set(prev_gw) - set(gateways)), + vrf, metric) for add_gw in gateways: try: - self.ipcmd.route_add_gateway(ifaceobj.name, add_gw, vrf) + self.ipcmd.route_add_gateway(ifaceobj.name, add_gw, vrf, metric) except Exception as e: self.log_error('%s: %s' % (ifaceobj.name, str(e))) @@ -358,14 +517,15 @@ class address(moduleBase): if syntaxcheck: lowerdev_mtu = lowerobj[0].get_attr_value_first('mtu') else: - lowerdev_mtu = self.ipcmd.link_get_mtu(lowerobj[0].name) + lowerdev_mtu = self.ipcmd.link_get_mtu_sysfs(lowerobj[0].name) if lowerdev_mtu and int(mtu) > int(lowerdev_mtu): self.logger.warn('%s: vlan dev mtu %s is greater than lower realdev %s mtu %s' %(ifaceobj.name, mtu, lowerobj[0].name, lowerdev_mtu)) retval = False elif (not lowerobj[0].link_kind and not (lowerobj[0].link_privflags & ifaceLinkPrivFlags.LOOPBACK) and - self.default_mtu and (int(mtu) > int(self.default_mtu))): + not lowerdev_mtu and self.default_mtu and + (int(mtu) > int(self.default_mtu))): # only check default mtu on lower device which is a physical interface self.logger.warn('%s: vlan dev mtu %s is greater than lower realdev %s mtu %s' %(ifaceobj.name, mtu, lowerobj[0].name, self.default_mtu)) @@ -394,17 +554,18 @@ class address(moduleBase): if not running_mtu or (running_mtu != mtu): self.ipcmd.link_set(u, 'mtu', mtu) - def _process_mtu_config(self, ifaceobj, ifaceobj_getfunc): - mtu = ifaceobj.get_attr_value_first('mtu') + def _process_mtu_config(self, ifaceobj, ifaceobj_getfunc, mtu): if mtu: if not self._check_mtu_config(ifaceobj, mtu, ifaceobj_getfunc): return - running_mtu = self.ipcmd.link_get_mtu(ifaceobj.name) + cached_running_mtu = self.ipcmd.link_get_mtu(ifaceobj.name) + running_mtu = self.ipcmd.link_get_mtu_sysfs(ifaceobj.name) if not running_mtu or (running_mtu and running_mtu != mtu): - self.ipcmd.link_set(ifaceobj.name, 'mtu', mtu) + force = cached_running_mtu != running_mtu + self.ipcmd.link_set(ifaceobj.name, 'mtu', mtu, force=force) if (not ifupdownflags.flags.ALL and not ifaceobj.link_kind and - ifupdownConfig.config.get('adjust_logical_dev_mtu', '1') != '0'): + ifupdownconfig.config.get('adjust_logical_dev_mtu', '1') != '0'): # This is additional cost to us, so do it only when # ifupdown2 is called on a particular interface and # it is a physical interface @@ -420,12 +581,19 @@ class address(moduleBase): if (self.default_mtu and running_mtu != self.default_mtu): self.ipcmd.link_set(ifaceobj.name, 'mtu', self.default_mtu) return - if (ifupdownConfig.config.get('adjust_logical_dev_mtu', '1') != '0' + if (ifupdownconfig.config.get('adjust_logical_dev_mtu', '1') != '0' and ifaceobj.lowerifaces): # set vlan interface mtu to lower device mtu if (ifaceobj.link_kind & ifaceLinkKind.VLAN): - lower_iface_mtu = self.ipcmd.link_get_mtu(ifaceobj.lowerifaces[0], refresh=True) - if not lower_iface_mtu == self.ipcmd.link_get_mtu(ifaceobj.name): + lower_iface = ifaceobj.lowerifaces[0] + if lower_iface not in self.lower_iface_mtu_checked_list: + lower_iface_mtu = self.ipcmd.link_get_mtu_sysfs(lower_iface) + self.ipcmd.cache_update([lower_iface, 'mtu'], lower_iface_mtu) + self.lower_iface_mtu_checked_list.append(lower_iface) + else: + lower_iface_mtu = self.ipcmd.link_get_mtu(lower_iface) + + if lower_iface_mtu != self.ipcmd.link_get_mtu_sysfs(ifaceobj.name): self.ipcmd.link_set_mtu(ifaceobj.name, lower_iface_mtu) elif (not (ifaceobj.name == 'lo') and not ifaceobj.link_kind and @@ -443,10 +611,143 @@ class address(moduleBase): if running_mtu != self.default_mtu: self.ipcmd.link_set(ifaceobj.name, 'mtu', self.default_mtu) + def _set_bridge_forwarding(self, ifaceobj): + """ set ip forwarding to 0 if bridge interface does not have a + ip nor svi """ + if not ifaceobj.upperifaces and not ifaceobj.get_attr_value('address'): + # set forwarding = 0 + if self.sysctl_get('net.ipv4.conf.%s.forwarding' %ifaceobj.name) == '1': + self.sysctl_set('net.ipv4.conf.%s.forwarding' %ifaceobj.name, 0) + if self.sysctl_get('net.ipv6.conf.%s.forwarding' %ifaceobj.name) == '1': + self.sysctl_set('net.ipv6.conf.%s.forwarding' %ifaceobj.name, 0) + else: + if self.sysctl_get('net.ipv4.conf.%s.forwarding' %ifaceobj.name) == '0': + self.sysctl_set('net.ipv4.conf.%s.forwarding' %ifaceobj.name, 1) + if self.sysctl_get('net.ipv6.conf.%s.forwarding' %ifaceobj.name) == '0': + self.sysctl_set('net.ipv6.conf.%s.forwarding' %ifaceobj.name, 1) + + def _sysctl_config(self, ifaceobj): + setting_default_value = False + mpls_enable = ifaceobj.get_attr_value_first('mpls-enable'); + if not mpls_enable: + setting_default_value = True + mpls_enable = self.get_mod_subattr('mpls-enable', 'default') + mpls_enable = utils.boolean_support_binary(mpls_enable) + # File read has been used for better performance + # instead of using sysctl command + if ifupdownflags.flags.PERFMODE: + running_mpls_enable = '0' + else: + running_mpls_enable = self.read_file_oneline( + '/proc/sys/net/mpls/conf/%s/input' + % ifaceobj.name + ) + + if mpls_enable != running_mpls_enable: + try: + self.sysctl_set('net.mpls.conf.%s.input' + %('/'.join(ifaceobj.name.split("."))), + mpls_enable) + except Exception as e: + if not setting_default_value: + ifaceobj.status = ifaceStatus.ERROR + self.logger.error('%s: %s' %(ifaceobj.name, str(e))) + + if (ifaceobj.link_kind & ifaceLinkKind.BRIDGE): + self._set_bridge_forwarding(ifaceobj) + return + if not self.syntax_check_sysctls(ifaceobj): + return + ipforward = ifaceobj.get_attr_value_first('ip-forward') + ip6forward = ifaceobj.get_attr_value_first('ip6-forward') + if ifupdownflags.flags.PERFMODE: + if ipforward: + self.sysctl_set('net.ipv4.conf.%s.forwarding' + %('/'.join(ifaceobj.name.split("."))), + utils.boolean_support_binary(ipforward)) + if ip6forward: + self.sysctl_set('net.ipv6.conf.%s.forwarding' + %('/'.join(ifaceobj.name.split("."))), + utils.boolean_support_binary(ip6forward)) + return + bridge_port = ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT + if bridge_port: + if ipforward: + self.log_error('%s: \'ip-forward\' is not supported for ' + 'bridge port' %ifaceobj.name) + if ip6forward: + self.log_error('%s: \'ip6-forward\' is not supported for ' + 'bridge port' %ifaceobj.name) + return + setting_default_value = False + if not ipforward: + setting_default_value = True + ipforward = (self.ipforward or + self.get_mod_subattr('ip-forward', 'default')) + ipforward = utils.boolean_support_binary(ipforward) + # File read has been used for better performance + # instead of using sysctl command + running_ipforward = self.read_file_oneline( + '/proc/sys/net/ipv4/conf/%s/forwarding' + %ifaceobj.name) + if ipforward != running_ipforward: + try: + self.sysctl_set('net.ipv4.conf.%s.forwarding' + %('/'.join(ifaceobj.name.split("."))), + ipforward) + except Exception as e: + if not setting_default_value: + ifaceobj.status = ifaceStatus.ERROR + self.logger.error('%s: %s' %(ifaceobj.name, str(e))) + + setting_default_value = False + if not ip6forward: + setting_default_value = True + ip6forward = (self.ip6forward or + self.get_mod_subattr('ip6-forward', 'default')) + ip6forward = utils.boolean_support_binary(ip6forward) + # File read has been used for better performance + # instead of using sysctl command + running_ip6forward = self.read_file_oneline( + '/proc/sys/net/ipv6/conf/%s/forwarding' + %ifaceobj.name) + if ip6forward != running_ip6forward: + try: + self.sysctl_set('net.ipv6.conf.%s.forwarding' + %('/'.join(ifaceobj.name.split("."))), + ip6forward) + except Exception as e: + # There is chance of ipv6 being removed because of, + # for example, setting mtu < 1280 + # In such cases, log error only if user has configured + # ip6-forward + if not setting_default_value: + ifaceobj.status = ifaceStatus.ERROR + self.logger.error('%s: %s' %(ifaceobj.name, str(e))) + + def process_mtu(self, ifaceobj, ifaceobj_getfunc): + mtu = ifaceobj.get_attr_value_first('mtu') + + if not mtu: + default_iface_mtu = self.ifaces_defaults.get(ifaceobj.name, {}).get('mtu') + + if default_iface_mtu: + try: + mtu = default_iface_mtu + int(default_iface_mtu) + except Exception as e: + self.logger.warning('%s: MTU value from policy file: %s' % (ifaceobj.name, str(e))) + return + + self._process_mtu_config(ifaceobj, ifaceobj_getfunc, mtu) + def _up(self, ifaceobj, ifaceobj_getfunc=None): if not self.ipcmd.link_exists(ifaceobj.name): return + if not self.syntax_check_enable_l3_iface_forwardings(ifaceobj, ifaceobj_getfunc): + return + alias = ifaceobj.get_attr_value_first('alias') current_alias = self.ipcmd.link_get_alias(ifaceobj.name) if alias and alias != current_alias: @@ -454,11 +755,13 @@ class address(moduleBase): elif not alias and current_alias: self.ipcmd.link_set_alias(ifaceobj.name, '') + self._sysctl_config(ifaceobj) + addr_method = ifaceobj.addr_method force_reapply = False try: # release any stale dhcp addresses if present - if (addr_method not in ["dhcp", "ppp"] and not ifupdownflags.flags.PERFMODE and + if (addr_method != "dhcp" and not ifupdownflags.flags.PERFMODE and not (ifaceobj.flags & iface.HAS_SIBLINGS)): # if not running in perf mode and ifaceobj does not have # any sibling iface objects, kill any stale dhclient @@ -475,10 +778,11 @@ class address(moduleBase): pass self.ipcmd.batch_start() - if addr_method not in ["dhcp", "ppp"]: + if addr_method != "dhcp": self._inet_address_config(ifaceobj, ifaceobj_getfunc, force_reapply) - self._process_mtu_config(ifaceobj, ifaceobj_getfunc) + + self.process_mtu(ifaceobj, ifaceobj_getfunc) try: self.ipcmd.batch_commit() @@ -513,36 +817,39 @@ class address(moduleBase): except Exception, e: self.log_error('%s: %s' %(ifaceobj.name, str(e)), ifaceobj) - if addr_method not in ["dhcp", "ppp"]: - gateways = ifaceobj.get_attr_value('gateway') - if not gateways: - gateways = [] - prev_gw = self._get_prev_gateway(ifaceobj, gateways) - self._add_delete_gateway(ifaceobj, gateways, prev_gw) - return + gateways = ifaceobj.get_attr_value('gateway') + if not gateways: + gateways = [] + prev_gw = self._get_prev_gateway(ifaceobj, gateways) + self._add_delete_gateway(ifaceobj, gateways, prev_gw) def _down(self, ifaceobj, ifaceobj_getfunc=None): try: if not self.ipcmd.link_exists(ifaceobj.name): return addr_method = ifaceobj.addr_method - if addr_method not in ["dhcp", "ppp"]: + if addr_method != "dhcp": if ifaceobj.get_attr_value_first('address-purge')=='no': addrlist = ifaceobj.get_attr_value('address') for addr in addrlist: self.ipcmd.addr_del(ifaceobj.name, addr) #self.ipcmd.addr_del(ifaceobj.name, ifaceobj.get_attr_value('address')[0]) - else: + elif not ifaceobj.link_kind: + # for logical interfaces we don't need to remove the ip addresses + # kernel will do it for us on 'ip link del' self.ipcmd.del_addr_all(ifaceobj.name) + gateways = ifaceobj.get_attr_value('gateway') + if gateways: + self._delete_gateway(ifaceobj, gateways, + ifaceobj.get_attr_value_first('vrf'), + ifaceobj.get_attr_value_first('metric')) mtu = ifaceobj.get_attr_value_first('mtu') if (not ifaceobj.link_kind and mtu and self.default_mtu and (mtu != self.default_mtu)): self.ipcmd.link_set(ifaceobj.name, 'mtu', self.default_mtu) alias = ifaceobj.get_attr_value_first('alias') if alias: - filename = '/sys/class/net/%s/ifalias' %ifaceobj.name - self.logger.info('executing echo "" > %s' %filename) - os.system('echo "" > %s' %filename) + self.write_file('/sys/class/net/%s/ifalias' % ifaceobj.name, '\n') # XXX hwaddress reset cannot happen because we dont know last # address. @@ -579,14 +886,62 @@ class address(moduleBase): def _check_addresses_in_bridge(self, ifaceobj, hwaddress): """ If the device is a bridge, make sure the addresses are in the bridge """ - if '.' in ifaceobj.name: - (bridgename, vlan) = ifaceobj.name.split('.') + if ifaceobj.link_kind & ifaceLinkKind.VLAN: + bridgename = ifaceobj.lowerifaces[0] + vlan = self._get_vlan_id(ifaceobj) if self.ipcmd.bridge_is_vlan_aware(bridgename): - fdb_addrs = self._get_bridge_fdbs(bridgename, vlan) + fdb_addrs = self._get_bridge_fdbs(bridgename, str(vlan)) if not fdb_addrs or hwaddress not in fdb_addrs: return False return True + def _query_sysctl(self, ifaceobj, ifaceobjcurr): + bridge_port = ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT + ipforward = ifaceobj.get_attr_value_first('ip-forward') + if ipforward: + if bridge_port: + ifaceobjcurr.status = ifaceStatus.ERROR + ifaceobjcurr.status_str = ('\'ip-forward\' not supported ' + + 'for bridge port') + ifaceobjcurr.update_config_with_status('ip-forward', 1, None) + else: + running_ipforward = self.read_file_oneline( + '/proc/sys/net/ipv4/conf/%s/forwarding' + %ifaceobj.name) + running_ipforward = utils.get_onff_from_onezero( + running_ipforward) + ifaceobjcurr.update_config_with_status('ip-forward', + running_ipforward, + ipforward != running_ipforward) + + ip6forward = ifaceobj.get_attr_value_first('ip6-forward') + if ip6forward: + if bridge_port: + ifaceobjcurr.status = ifaceStatus.ERROR + ifaceobjcurr.status_str = ('\'ip6-forward\' not supported ' + + 'for bridge port') + ifaceobjcurr.update_config_with_status('ip6-forward', 1, None) + else: + running_ip6forward = self.read_file_oneline( + '/proc/sys/net/ipv6/conf/%s/forwarding' + %ifaceobj.name) + running_ip6forward = utils.get_onff_from_onezero( + running_ip6forward) + ifaceobjcurr.update_config_with_status('ip6-forward', + running_ip6forward, + ip6forward != running_ip6forward) + mpls_enable = ifaceobj.get_attr_value_first('mpls-enable'); + if mpls_enable: + running_mpls_enable = self.read_file_oneline( + '/proc/sys/net/mpls/conf/%s/input' + %ifaceobj.name) + running_mpls_enable = utils.get_yesno_from_onezero( + running_mpls_enable) + ifaceobjcurr.update_config_with_status('mpls-enable', + running_mpls_enable, + mpls_enable != running_mpls_enable) + return + def _query_check(self, ifaceobj, ifaceobjcurr, ifaceobj_getfunc=None): runningaddrsdict = None if not self.ipcmd.link_exists(ifaceobj.name): @@ -611,12 +966,13 @@ class address(moduleBase): 0) self.query_n_update_ifaceobjcurr_attr(ifaceobj, ifaceobjcurr, 'alias', self.ipcmd.link_get_alias) + self._query_sysctl(ifaceobj, ifaceobjcurr) # compare addresses - if addr_method in ["dhcp", "ppp"]: + if addr_method == 'dhcp': return addrs = utils.get_normalized_ip_addr(ifaceobj.name, self._get_iface_addresses(ifaceobj)) - runningaddrsdict = self.ipcmd.addr_get(ifaceobj.name) + runningaddrsdict = self.ipcmd.get_running_addrs(ifaceobj) # if anycast address is configured on 'lo' and is in running config # add it to addrs so that query_check doesn't fail anycast_addr = utils.get_normalized_ip_addr(ifaceobj.name, ifaceobj.get_attr_value_first('clagd-vxlan-anycast-ip')) @@ -680,7 +1036,7 @@ class address(moduleBase): ifaceobjrunning.addr_method = 'loopback' else: default_addrs = [] - runningaddrsdict = self.ipcmd.addr_get(ifaceobjrunning.name) + runningaddrsdict = self.ipcmd.get_running_addrs(ifaceobjrunning) if runningaddrsdict: [ifaceobjrunning.update_config('address', addr) for addr, addrattrs in runningaddrsdict.items() @@ -695,6 +1051,10 @@ class address(moduleBase): if alias: ifaceobjrunning.update_config('alias', alias) + ipforward = self.read_file_oneline( + '/proc/sys/net/ipv4/conf/%s/forwarding' + %ifaceobjrunning.name) + _run_ops = {'up' : _up, 'down' : _down, @@ -707,7 +1067,7 @@ class address(moduleBase): def _init_command_handlers(self): if not self.ipcmd: - self.ipcmd = iproute2() + self.ipcmd = LinkUtils() def run(self, ifaceobj, operation, query_ifaceobj=None, ifaceobj_getfunc=None): """ run address configuration on the interface object passed as argument diff --git a/addons/addressvirtual.py b/ifupdown2/addons/addressvirtual.py similarity index 86% rename from addons/addressvirtual.py rename to ifupdown2/addons/addressvirtual.py index fe32a2a..80541b2 100644 --- a/addons/addressvirtual.py +++ b/ifupdown2/addons/addressvirtual.py @@ -1,22 +1,36 @@ #!/usr/bin/python # -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. # Author: Roopa Prabhu, roopa@cumulusnetworks.com # -from ifupdown.iface import * -from ifupdownaddons.modulebase import moduleBase -from ifupdownaddons.iproute2 import iproute2 -import ifupdown.ifupdownconfig as ifupdownConfig -import ifupdown.statemanager as statemanager -from ifupdown.netlink import netlink -import ifupdown.ifupdownflags as ifupdownflags - -from ipaddr import IPNetwork, IPv4Network -import logging import os import glob +from ipaddr import IPNetwork, IPv6Network + +try: + from ifupdown2.ifupdown.iface import * + from ifupdown2.ifupdown.netlink import netlink + + from ifupdown2.ifupdownaddons.LinkUtils import LinkUtils + from ifupdown2.ifupdownaddons.modulebase import moduleBase + + import ifupdown2.ifupdown.statemanager as statemanager + import ifupdown2.ifupdown.ifupdownflags as ifupdownflags + import ifupdown2.ifupdown.ifupdownconfig as ifupdownconfig +except ImportError: + from ifupdown.iface import * + from ifupdown.netlink import netlink + + from ifupdownaddons.LinkUtils import LinkUtils + from ifupdownaddons.modulebase import moduleBase + + import ifupdown.statemanager as statemanager + import ifupdown.ifupdownflags as ifupdownflags + import ifupdown.ifupdownconfig as ifupdownconfig + + class addressvirtual(moduleBase): """ ifupdown2 addon module to configure virtual addresses """ @@ -46,8 +60,9 @@ class addressvirtual(moduleBase): def _add_addresses_to_bridge(self, ifaceobj, hwaddress): # XXX: batch the addresses - if '.' in ifaceobj.name: - (bridgename, vlan) = ifaceobj.name.split('.') + if ifaceobj.link_kind & ifaceLinkKind.VLAN: + bridgename = ifaceobj.lowerifaces[0] + vlan = self._get_vlan_id(ifaceobj) if self.ipcmd.bridge_is_vlan_aware(bridgename): [self.ipcmd.bridge_fdb_add(bridgename, addr, vlan) for addr in hwaddress] @@ -57,8 +72,9 @@ class addressvirtual(moduleBase): def _remove_addresses_from_bridge(self, ifaceobj, hwaddress): # XXX: batch the addresses - if '.' in ifaceobj.name: - (bridgename, vlan) = ifaceobj.name.split('.') + if ifaceobj.link_kind & ifaceLinkKind.VLAN: + bridgename = ifaceobj.lowerifaces[0] + vlan = self._get_vlan_id(ifaceobj) if self.ipcmd.bridge_is_vlan_aware(bridgename): for addr in hwaddress: try: @@ -86,23 +102,24 @@ class addressvirtual(moduleBase): def _check_addresses_in_bridge(self, ifaceobj, hwaddress): """ If the device is a bridge, make sure the addresses are in the bridge """ - if '.' in ifaceobj.name: - (bridgename, vlan) = ifaceobj.name.split('.') + if ifaceobj.link_kind & ifaceLinkKind.VLAN: + bridgename = ifaceobj.lowerifaces[0] + vlan = self._get_vlan_id(ifaceobj) if self.ipcmd.bridge_is_vlan_aware(bridgename): - fdb_addrs = self._get_bridge_fdbs(bridgename, vlan) + fdb_addrs = self._get_bridge_fdbs(bridgename, str(vlan)) if not fdb_addrs or hwaddress not in fdb_addrs: return False return True def _fix_connected_route(self, ifaceobj, vifacename, addr): # - # XXX: Hack to make sure the primary address + # XXX: Hack to make sure the primary address # is the first in the routing table. # # We use `ip route get` on the vrr network to see which # device the kernel returns. if it is the mac vlan device, # flap the macvlan device to adjust the routing table entry. - # + # # flapping the macvlan device makes sure the macvlan # connected route goes through delete + add, hence adjusting # the order in the routing table. @@ -110,10 +127,15 @@ class addressvirtual(moduleBase): try: self.logger.info('%s: checking route entry ...' %ifaceobj.name) ip = IPNetwork(addr) + + # we don't support ip6 route fix yet + if type(ip) == IPv6Network: + return + route_prefix = '%s/%d' %(ip.network, ip.prefixlen) dev = self.ipcmd.ip_route_get_dev(route_prefix) - if dev and dev == vifacename: + if dev and dev != ifaceobj.name: self.logger.info('%s: preferred routing entry ' %ifaceobj.name + 'seems to be of the macvlan dev %s' %vifacename + @@ -122,13 +144,13 @@ class addressvirtual(moduleBase): self.ipcmd.link_up(vifacename) except Exception, e: self.logger.debug('%s: fixing route entry failed (%s)' - %str(e)) + % (ifaceobj.name, str(e))) pass def _handle_vrf_slaves(self, macvlan_ifacename, ifaceobj): vrfname = self.ipcmd.link_get_master(ifaceobj.name) if vrfname: - netlink.link_set_master(macvlan_ifacename, vrfname) + self.ipcmd.link_set(macvlan_ifacename, 'master', vrfname) def _get_macs_from_old_config(self, ifaceobj=None): """ This method returns a list of the mac addresses @@ -154,7 +176,7 @@ class addressvirtual(moduleBase): purge_existing = False if ifupdownflags.flags.PERFMODE else True lower_iface_mtu = update_mtu = None - if ifupdownConfig.config.get('adjust_logical_dev_mtu', '1') != '0': + if ifupdownconfig.config.get('adjust_logical_dev_mtu', '1') != '0': if ifaceobj.lowerifaces and address_virtual_list: update_mtu = True @@ -179,15 +201,23 @@ class addressvirtual(moduleBase): link_created = False macvlan_ifacename = '%s%d' %(macvlan_prefix, av_idx) if not self.ipcmd.link_exists(macvlan_ifacename): - netlink.link_add_macvlan(ifaceobj.name, macvlan_ifacename) + try: + netlink.link_add_macvlan(ifaceobj.name, macvlan_ifacename) + except: + self.ipcmd.link_add_macvlan(ifaceobj.name, macvlan_ifacename) link_created = True + + # first thing we need to handle vrf enslavement + if (ifaceobj.link_privflags & ifaceLinkPrivFlags.VRF_SLAVE): + self._handle_vrf_slaves(macvlan_ifacename, ifaceobj) + ips = av_attrs[1:] if mac != 'None': mac = mac.lower() # customer could have used UPPERCASE for MAC self.ipcmd.link_set_hwaddress(macvlan_ifacename, mac) hwaddress.append(mac) - self.ipcmd.addr_add_multiple(macvlan_ifacename, ips, + self.ipcmd.addr_add_multiple(ifaceobj, macvlan_ifacename, ips, purge_existing) # If link existed before, flap the link @@ -198,7 +228,7 @@ class addressvirtual(moduleBase): lower_iface_mtu = self.ipcmd.link_get_mtu(ifaceobj.name, refresh=True) update_mtu = False - if lower_iface_mtu and lower_iface_mtu != self.ipcmd.link_get_mtu(macvlan_ifacename): + if lower_iface_mtu and lower_iface_mtu != self.ipcmd.link_get_mtu(macvlan_ifacename, refresh=True): try: self.ipcmd.link_set_mtu(macvlan_ifacename, lower_iface_mtu) @@ -211,10 +241,11 @@ class addressvirtual(moduleBase): # to bring them up here in the case they were brought down # by some other entity in the system. netlink.link_set_updown(macvlan_ifacename, "up") - - # handle vrf slaves - if (ifaceobj.link_privflags & ifaceLinkPrivFlags.VRF_SLAVE): - self._handle_vrf_slaves(macvlan_ifacename, ifaceobj) + else: + try: + self.ipcmd.fix_ipv6_route_metric(ifaceobj, macvlan_ifacename, ips) + except Exception as e: + self.logger.debug('fix_vrf_slave_ipv6_route_metric: failed: %s' % e) # Disable IPv6 duplicate address detection on VRR interfaces for key, sysval in { 'accept_dad' : '0', 'dad_transmits' : '0' }.iteritems(): @@ -285,7 +316,7 @@ class addressvirtual(moduleBase): def check_mac_address(self, ifaceobj, mac): if mac == 'None': - return True + return True mac = mac.lower() try: if int(mac.split(":")[0], 16) & 1 : @@ -332,7 +363,8 @@ class addressvirtual(moduleBase): # upper device list if u == ifaceobj.name: continue - netlink.link_set_master(u, ifaceobj.name, state='up') + self.ipcmd.link_set(u, 'master', ifaceobj.name, + state='up') elif ((ifaceobj.link_privflags & ifaceLinkPrivFlags.ADDRESS_VIRTUAL_SLAVE) and (ifaceobj.link_privflags & ifaceLinkPrivFlags.VRF_SLAVE) and self.ipcmd.link_exists(ifaceobj.name)): @@ -352,7 +384,8 @@ class addressvirtual(moduleBase): if u == vrfname: continue if u.startswith(macvlan_prefix): - netlink.link_set_master(u, vrfname, state='up') + self.ipcmd.link_set(u, 'master', vrfname, + state='up') def _up(self, ifaceobj, ifaceobj_getfunc=None): if not ifupdownflags.flags.ALL: @@ -406,12 +439,15 @@ class addressvirtual(moduleBase): continue # Check mac and ip address rhwaddress = self.ipcmd.link_get_hwaddress(macvlan_ifacename) - raddrs = self.ipcmd.addr_get(macvlan_ifacename) + raddrs = self.ipcmd.get_running_addrs( + ifname=macvlan_ifacename, + details=False, + addr_virtual_ifaceobj=ifaceobj + ) if not raddrs or not rhwaddress: ifaceobjcurr.update_config_with_status('address-virtual', '', 1) av_idx += 1 continue - raddrs = raddrs.keys() try: av_attrs[0] = ':'.join([i if len(i) == 2 else '0%s' % i for i in av_attrs[0].split(':')]) @@ -421,16 +457,9 @@ class addressvirtual(moduleBase): macvlan_ifacename, ' '.join(av_attrs))) try: - cmp_av_addr = av_attrs[1:][0] - cmp_raddr = raddrs[0] - - if '/' in cmp_raddr and '/' not in cmp_av_addr: - cmp_av_addr = str(IPNetwork(cmp_av_addr)) - elif '/' in cmp_av_addr and '/' not in cmp_raddr: - cmp_raddr = str(IPNetwork(cmp_raddr)) - - if (rhwaddress == av_attrs[0] and cmp_raddr == cmp_av_addr and - self._check_addresses_in_bridge(ifaceobj, av_attrs[0])): + if (rhwaddress == av_attrs[0].lower() and + self.ipcmd.compare_user_config_vs_running_state(raddrs, av_attrs[1:]) and + self._check_addresses_in_bridge(ifaceobj, av_attrs[0].lower())): ifaceobjcurr.update_config_with_status('address-virtual', address_virtual, 0) else: @@ -450,7 +479,7 @@ class addressvirtual(moduleBase): for av in address_virtuals: macvlan_ifacename = os.path.basename(av) rhwaddress = self.ipcmd.link_get_hwaddress(macvlan_ifacename) - raddress = self.ipcmd.addr_get(macvlan_ifacename) + raddress = self.ipcmd.get_running_addrs(None, macvlan_ifacename) if not raddress: self.logger.warn('%s: no running addresses' %ifaceobjrunning.name) @@ -470,7 +499,7 @@ class addressvirtual(moduleBase): def _init_command_handlers(self): if not self.ipcmd: - self.ipcmd = iproute2() + self.ipcmd = LinkUtils() def run(self, ifaceobj, operation, query_ifaceobj=None, ifaceobj_getfunc=None, **extra_args): diff --git a/ifupdown2/addons/bond.py b/ifupdown2/addons/bond.py new file mode 100644 index 0000000..3650a10 --- /dev/null +++ b/ifupdown2/addons/bond.py @@ -0,0 +1,773 @@ +#!/usr/bin/python +# +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. +# Authors: +# Roopa Prabhu, roopa@cumulusnetworks.com +# Julien Fortin, julien@cumulusnetworks.com +# + +import os + +from sets import Set + +try: + from ifupdown2.nlmanager.nlmanager import Link + + from ifupdown2.ifupdown.iface import * + from ifupdown2.ifupdown.utils import utils + from ifupdown2.ifupdown.netlink import netlink + from ifupdown2.ifupdown.statemanager import statemanager_api as statemanager + + import ifupdown2.ifupdown.policymanager as policymanager + import ifupdown2.ifupdown.ifupdownflags as ifupdownflags + + from ifupdown2.ifupdownaddons.LinkUtils import LinkUtils + from ifupdown2.ifupdownaddons.modulebase import moduleBase +except ImportError: + from nlmanager.nlmanager import Link + + from ifupdown.iface import * + from ifupdown.utils import utils + from ifupdown.netlink import netlink + from ifupdown.statemanager import statemanager_api as statemanager + + from ifupdownaddons.LinkUtils import LinkUtils + from ifupdownaddons.modulebase import moduleBase + + import ifupdown.policymanager as policymanager + import ifupdown.ifupdownflags as ifupdownflags + +class bond(moduleBase): + """ ifupdown2 addon module to configure bond interfaces """ + + overrides_ifupdown_scripts = ['ifenslave', ] + + _modinfo = { 'mhelp' : 'bond configuration module', + 'attrs' : { + 'bond-use-carrier': + {'help' : 'bond use carrier', + 'validvals' : ['yes', 'no', '0', '1'], + 'default' : 'yes', + 'example': ['bond-use-carrier yes']}, + 'bond-num-grat-arp': + {'help' : 'bond use carrier', + 'validrange' : ['0', '255'], + 'default' : '1', + 'example' : ['bond-num-grat-arp 1']}, + 'bond-num-unsol-na' : + {'help' : 'bond slave devices', + 'validrange' : ['0', '255'], + 'default' : '1', + 'example' : ['bond-num-unsol-na 1']}, + 'bond-xmit-hash-policy' : + {'help' : 'bond slave devices', + 'validvals' : ['0', 'layer2', + '1', 'layer3+4', + '2', 'layer2+3', + '3', 'encap2+3', + '4', 'encap3+4'], + 'default' : 'layer2', + 'example' : ['bond-xmit-hash-policy layer2']}, + 'bond-miimon' : + {'help' : 'bond miimon', + 'validrange' : ['0', '255'], + 'default' : '0', + 'example' : ['bond-miimon 0']}, + 'bond-mode' : + {'help': 'bond mode', + 'validvals': ['0', 'balance-rr', + '1', 'active-backup', + '2', 'balance-xor', + '3', 'broadcast', + '4', '802.3ad', + '5', 'balance-tlb', + '6', 'balance-alb'], + 'default': 'balance-rr', + 'example': ['bond-mode 802.3ad']}, + 'bond-lacp-rate': + {'help' : 'bond lacp rate', + 'validvals' : ['0', 'slow', '1', 'fast'], + 'default' : '0', + 'example' : ['bond-lacp-rate 0']}, + 'bond-min-links': + {'help' : 'bond min links', + 'default' : '0', + 'validrange' : ['0', '255'], + 'example' : ['bond-min-links 0']}, + 'bond-ad-sys-priority': + {'help' : '802.3ad system priority', + 'default' : '65535', + 'validrange' : ['0', '65535'], + 'example' : ['bond-ad-sys-priority 65535'], + 'deprecated' : True, + 'new-attribute' : 'bond-ad-actor-sys-prio'}, + 'bond-ad-actor-sys-prio': + {'help' : '802.3ad system priority', + 'default' : '65535', + 'validrange' : ['0', '65535'], + 'example' : ['bond-ad-actor-sys-prio 65535']}, + 'bond-ad-sys-mac-addr': + {'help' : '802.3ad system mac address', + 'validvals': ['', ], + 'example' : ['bond-ad-sys-mac-addr 00:00:00:00:00:00'], + 'deprecated' : True, + 'new-attribute' : 'bond-ad-actor-system'}, + 'bond-ad-actor-system': + {'help' : '802.3ad system mac address', + 'validvals': ['', ], + 'example' : ['bond-ad-actor-system 00:00:00:00:00:00'],}, + 'bond-lacp-bypass-allow': + {'help' : 'allow lacp bypass', + 'validvals' : ['yes', 'no', '0', '1'], + 'default' : 'no', + 'example' : ['bond-lacp-bypass-allow no']}, + 'bond-slaves' : + {'help' : 'bond slaves', + 'required' : True, + 'multivalue' : True, + 'validvals': [''], + 'example' : ['bond-slaves swp1 swp2', + 'bond-slaves glob swp1-2', + 'bond-slaves regex (swp[1|2)'], + 'aliases': ['bond-ports']}, + 'bond-updelay' : + {'help' : 'bond updelay', + 'default' : '0', + 'validrange' : ['0', '65535'], + 'example' : ['bond-updelay 100']}, + 'bond-downdelay': + {'help' : 'bond downdelay', + 'default' : '0', + 'validrange' : ['0', '65535'], + 'example' : ['bond-downdelay 100']} + }} + + _bond_attr_netlink_map = { + 'bond-mode': Link.IFLA_BOND_MODE, + 'bond-miimon': Link.IFLA_BOND_MIIMON, + 'bond-use-carrier': Link.IFLA_BOND_USE_CARRIER, + 'bond-lacp-rate': Link.IFLA_BOND_AD_LACP_RATE, + 'bond-xmit-hash-policy': Link.IFLA_BOND_XMIT_HASH_POLICY, + 'bond-min-links': Link.IFLA_BOND_MIN_LINKS, + 'bond-num-grat-arp': Link.IFLA_BOND_NUM_PEER_NOTIF, + 'bond-num-unsol-na': Link.IFLA_BOND_NUM_PEER_NOTIF, + 'bond-ad-sys-mac-addr': Link.IFLA_BOND_AD_ACTOR_SYSTEM, + 'bond-ad-actor-system': Link.IFLA_BOND_AD_ACTOR_SYSTEM, + 'bond-ad-sys-priority': Link.IFLA_BOND_AD_ACTOR_SYS_PRIO, + 'bond-ad-actor-sys-prio': Link.IFLA_BOND_AD_ACTOR_SYS_PRIO, + 'bond-lacp-bypass-allow': Link.IFLA_BOND_AD_LACP_BYPASS, + 'bond-updelay': Link.IFLA_BOND_UPDELAY, + 'bond-downdelay': Link.IFLA_BOND_DOWNDELAY + } + + # ifquery-check attr dictionary with callable object to translate user data to netlink format + _bond_attr_ifquery_check_translate_func = { + Link.IFLA_BOND_MODE: lambda x: Link.ifla_bond_mode_tbl[x], + Link.IFLA_BOND_MIIMON: int, + Link.IFLA_BOND_USE_CARRIER: utils.get_boolean_from_string, + Link.IFLA_BOND_AD_LACP_RATE: lambda x: int(utils.get_boolean_from_string(x)), + Link.IFLA_BOND_XMIT_HASH_POLICY: lambda x: Link.ifla_bond_xmit_hash_policy_tbl[x], + Link.IFLA_BOND_MIN_LINKS: int, + Link.IFLA_BOND_NUM_PEER_NOTIF: int, + Link.IFLA_BOND_AD_ACTOR_SYSTEM: str, + Link.IFLA_BOND_AD_ACTOR_SYS_PRIO: int, + Link.IFLA_BOND_AD_LACP_BYPASS: lambda x: int(utils.get_boolean_from_string(x)), + Link.IFLA_BOND_UPDELAY: int, + Link.IFLA_BOND_DOWNDELAY: int + } + + # ifup attr list with callable object to translate user data to netlink format + # in the future this can be moved to a dictionary, whenever we detect that some + # netlink capabilities are missing we can dynamically remove them from the dict. + _bond_attr_set_list = ( + ('bond-mode', Link.IFLA_BOND_MODE, lambda x: Link.ifla_bond_mode_tbl[x]), + ('bond-xmit-hash-policy', Link.IFLA_BOND_XMIT_HASH_POLICY, lambda x: Link.ifla_bond_xmit_hash_policy_tbl[x]), + ('bond-miimon', Link.IFLA_BOND_MIIMON, int), + ('bond-min-links', Link.IFLA_BOND_MIN_LINKS, int), + ('bond-num-grat-arp', Link.IFLA_BOND_NUM_PEER_NOTIF, int), + ('bond-num-unsol-na', Link.IFLA_BOND_NUM_PEER_NOTIF, int), + ('bond-ad-sys-priority', Link.IFLA_BOND_AD_ACTOR_SYS_PRIO, int), + ('bond-ad-actor-sys-prio', Link.IFLA_BOND_AD_ACTOR_SYS_PRIO, int), + ('bond-updelay', Link.IFLA_BOND_UPDELAY, int), + ('bond-downdelay', Link.IFLA_BOND_DOWNDELAY, int), + ('bond-use-carrier', Link.IFLA_BOND_USE_CARRIER, lambda x: int(utils.get_boolean_from_string(x))), + ('bond-lacp-rate', Link.IFLA_BOND_AD_LACP_RATE, lambda x: int(utils.get_boolean_from_string(x))), + ('bond-lacp-bypass-allow', Link.IFLA_BOND_AD_LACP_BYPASS, lambda x: int(utils.get_boolean_from_string(x))), + ('bond-ad-sys-mac-addr', Link.IFLA_BOND_AD_ACTOR_SYSTEM, str), + ('bond-ad-actor-system', Link.IFLA_BOND_AD_ACTOR_SYSTEM, str), + ) + + def __init__(self, *args, **kargs): + moduleBase.__init__(self, *args, **kargs) + self.ipcmd = None + self.bondcmd = None + + if not os.path.exists('/sys/class/net/bonding_masters'): + utils.exec_command('modprobe -q bonding') + + @staticmethod + def get_bond_slaves(ifaceobj): + slaves = ifaceobj.get_attr_value_first('bond-slaves') + if not slaves: + slaves = ifaceobj.get_attr_value_first('bond-ports') + return slaves + + def _is_bond(self, ifaceobj): + # at first link_kind is not set but once ifupdownmain + # calls get_dependent_ifacenames link_kind is set to BOND + if ifaceobj.link_kind & ifaceLinkKind.BOND or self.get_bond_slaves(ifaceobj): + return True + return False + + def get_dependent_ifacenames(self, ifaceobj, ifacenames_all=None): + """ Returns list of interfaces dependent on ifaceobj """ + + if not self._is_bond(ifaceobj): + return None + slave_list = self.parse_port_list(ifaceobj.name, + self.get_bond_slaves(ifaceobj), + ifacenames_all) + ifaceobj.dependency_type = ifaceDependencyType.MASTER_SLAVE + # Also save a copy for future use + ifaceobj.priv_data = list(slave_list) + if ifaceobj.link_type != ifaceLinkType.LINK_NA: + ifaceobj.link_type = ifaceLinkType.LINK_MASTER + ifaceobj.link_kind |= ifaceLinkKind.BOND + ifaceobj.role |= ifaceRole.MASTER + + return slave_list + + def syntax_check(self, ifaceobj, ifaceobj_getfunc): + return self.syntax_check_updown_delay(ifaceobj) + + def get_dependent_ifacenames_running(self, ifaceobj): + self._init_command_handlers() + return self.bondcmd.bond_get_slaves(ifaceobj.name) + + def _get_slave_list(self, ifaceobj): + """ Returns slave list present in ifaceobj config """ + + # If priv data already has slave list use that first. + if ifaceobj.priv_data: + return ifaceobj.priv_data + slaves = self.get_bond_slaves(ifaceobj) + if slaves: + return self.parse_port_list(ifaceobj.name, slaves) + else: + return None + + def _is_clag_bond(self, ifaceobj): + if self.get_bond_slaves(ifaceobj): + attrval = ifaceobj.get_attr_value_first('clag-id') + if attrval and attrval != '0': + return True + return False + + def _add_slaves(self, ifaceobj, ifaceobj_getfunc=None): + runningslaves = [] + + slaves = self._get_slave_list(ifaceobj) + if not slaves: + self.logger.debug('%s: no slaves found' %ifaceobj.name) + return + + if not ifupdownflags.flags.PERFMODE: + runningslaves = self.bondcmd.bond_get_slaves(ifaceobj.name) + + clag_bond = self._is_clag_bond(ifaceobj) + + for slave in Set(slaves).difference(Set(runningslaves)): + if (not ifupdownflags.flags.PERFMODE and + not self.ipcmd.link_exists(slave)): + self.log_error('%s: skipping slave %s, does not exist' + %(ifaceobj.name, slave), ifaceobj, + raise_error=False) + continue + link_up = False + if self.ipcmd.is_link_up(slave): + netlink.link_set_updown(slave, "down") + link_up = True + # If clag bond place the slave in a protodown state; clagd + # will protoup it when it is ready + if clag_bond: + try: + netlink.link_set_protodown(slave, "on") + except Exception, e: + self.logger.error('%s: %s' % (ifaceobj.name, str(e))) + netlink.link_set_master(slave, ifaceobj.name) + if link_up or ifaceobj.link_type != ifaceLinkType.LINK_NA: + try: + if (ifaceobj_getfunc(slave)[0].link_privflags & + ifaceLinkPrivFlags.KEEP_LINK_DOWN): + netlink.link_set_updown(slave, "down") + else: + netlink.link_set_updown(slave, "up") + except Exception, e: + self.logger.debug('%s: %s' % (ifaceobj.name, str(e))) + pass + + if runningslaves: + for s in runningslaves: + if s not in slaves: + self.bondcmd.bond_remove_slave(ifaceobj.name, s) + if clag_bond: + try: + netlink.link_set_protodown(s, "off") + except Exception, e: + self.logger.error('%s: %s' % (ifaceobj.name, str(e))) + else: + # apply link-down config changes on running slaves + try: + link_up = self.ipcmd.is_link_up(s) + config_link_down = (ifaceobj_getfunc(s)[0].link_privflags & + ifaceLinkPrivFlags.KEEP_LINK_DOWN) + if (config_link_down and link_up): + netlink.link_set_updown(s, "down") + elif (not config_link_down and not link_up): + netlink.link_set_updown(s, "up") + except Exception, e: + self.logger.warn('%s: %s' % (ifaceobj.name, str(e))) + + def _check_updown_delay_log(self, ifaceobj, attr_name, value): + ifaceobj.status = ifaceStatus.ERROR + self.logger.error('%s: unable to set %s %s as MII link monitoring is ' + 'disabled' % (ifaceobj.name, attr_name, value)) + # return False to notify syntax_check that an error has been logged + return False + + def syntax_check_updown_delay(self, ifaceobj): + result = True + updelay = ifaceobj.get_attr_value_first('bond-updelay') + downdelay = ifaceobj.get_attr_value_first('bond-downdelay') + + if not updelay and not downdelay: + return True + + try: + miimon = int(ifaceobj.get_attr_value_first('bond-miimon')) + except: + try: + miimon = int(policymanager.policymanager_api.get_iface_default( + module_name=self.__class__.__name__, + ifname=ifaceobj.name, + attr='bond-miimon')) + except: + miimon = 0 + + if not miimon: + # self._check_updown_delay_log returns False no matter what + if updelay and int(updelay): + result = self._check_updown_delay_log(ifaceobj, 'bond-updelay', updelay) + if downdelay and int(downdelay): + result = self._check_updown_delay_log(ifaceobj, 'bond-downdelay', downdelay) + + return result + + _bond_updown_delay_nl_list = ( + (Link.IFLA_BOND_UPDELAY, 'bond-updelay'), + (Link.IFLA_BOND_DOWNDELAY, 'bond-downdelay') + ) + + def check_updown_delay_nl(self, link_exists, ifaceobj, ifla_info_data): + """ + IFLA_BOND_MIIMON + Specifies the time, in milliseconds, to wait before enabling a slave + after a link recovery has been detected. This option is only valid + for the miimon link monitor. The updelay value should be a multiple + of the miimon value; if not, it will be rounded down to the nearest + multiple. The default value is 0. + + This ifla_bond_miimon code should be move to get_ifla_bond_attr_from_user_config + but we need to know if the operation was successful to update the cache accordingly + """ + ifla_bond_miimon = ifla_info_data.get(Link.IFLA_BOND_MIIMON) + if link_exists and ifla_bond_miimon is None: + ifla_bond_miimon = self.bondcmd.link_cache_get([ifaceobj.name, 'linkinfo', Link.IFLA_BOND_MIIMON]) + + if ifla_bond_miimon == 0: + for nl_attr, attr_name in self._bond_updown_delay_nl_list: + delay = ifla_info_data.get(nl_attr) + # if up-down-delay exists we need to remove it, if non zero log error + if delay is not None: + if delay > 0: + self._check_updown_delay_log(ifaceobj, attr_name, delay) + del ifla_info_data[nl_attr] + return True + return False + + _bond_lacp_attrs = ( + (Link.IFLA_BOND_AD_LACP_RATE, 'bond-lacp-rate'), + (Link.IFLA_BOND_AD_LACP_BYPASS, 'bond-lacp-bypass') + ) + + def _check_bond_mode_user_config(self, ifname, link_exists, ifla_info_data): + ifla_bond_mode = ifla_info_data.get(Link.IFLA_BOND_MODE) + if ifla_bond_mode is None and link_exists: + ifla_bond_mode = self.bondcmd.link_cache_get([ifname, 'linkinfo', Link.IFLA_BOND_MODE]) + # in this case the link already exists (we have a cached value): + # if IFLA_BOND_MODE is not present in ifla_info_data it means: + # - that bond-mode was present in the user config and didn't change + # - never was in the user config so bond mode should be the system default value + # - was removed from the stanza so we might have to reset it to default value + # nevertheless we need to add it back to the ifla_info_data dict to check + # if we need to reset the mode to system default + ifla_info_data[Link.IFLA_BOND_MODE] = ifla_bond_mode + + if ifla_bond_mode == 4: # 802.3ad + min_links = ifla_info_data.get(Link.IFLA_BOND_MIN_LINKS) + if min_links is None: + min_links = self.bondcmd.link_cache_get([ifname, 'linkinfo', Link.IFLA_BOND_MIN_LINKS]) + # get_min_links_nl may return None so we need to strictly check 0 + if min_links == 0: + self.logger.warn('%s: attribute bond-min-links is set to \'0\'' % ifname) + else: + # IFLA_BOND_AD_LACP_RATE and IFLA_BOND_AD_LACP_BYPASS only for 802.3ad mode (4) + for nl_attr, attr_name in self._bond_lacp_attrs: + if nl_attr in ifla_info_data: + self.logger.info('%s: ignoring %s: only available for 802.3ad mode (4)' % (ifname, attr_name)) + del ifla_info_data[nl_attr] + + @staticmethod + def get_saved_ifaceobj(link_exists, ifname): + if link_exists: + old_config = statemanager.get_ifaceobjs(ifname) + if old_config: + return old_config[0] + return None + + def get_ifla_bond_attr_from_user_config(self, ifaceobj, link_exists): + """ + Potential issue: if a user load the bond driver with custom + default values (say bond-mode 3), ifupdown2 has no knowledge + of these default values. + At bond creation everything should work, bonds will be created + with mode 3 (even if not specified under the stanza). + But, for example: if the user specifies a value under bond-mode + and later on the user removes the bond-mode line from the stanza + we will detect it and reset to MODINFO: BOND-MODE: DEFAULT aka 0 + which is not the real default value that the user may expect. + """ + ifname = ifaceobj.name + ifla_info_data = OrderedDict() + old_config = self.get_saved_ifaceobj(link_exists, ifname) + + # for each bond attribute we fetch the user configuration + # if no configuration is provided we look for a config in policy files + for attr_name, netlink_attr, func_ptr in self._bond_attr_set_list: + cached_value = None + user_config = ifaceobj.get_attr_value_first(attr_name) + + if not user_config: + user_config = policymanager.policymanager_api.get_iface_default( + module_name=self.__class__.__name__, + ifname=ifname, + attr=attr_name) + if user_config: + self.logger.debug('%s: %s %s: extracted from policy files' + % (ifname, attr_name, user_config)) + + # no policy override, do we need to reset an attr to default value? + if not user_config and old_config and old_config.get_attr_value_first(attr_name): + # if the link already exists but the value is set + # (potentially removed from the stanza, we need to reset it to default) + # might not work for specific cases, see explanation at the top of this function :) + user_config = self.get_attr_default_value(attr_name) + if user_config: + self.logger.debug('%s: %s: removed from stanza, resetting to default value: %s' + % (ifname, attr_name, user_config)) + + if user_config: + try: + nl_value = func_ptr(user_config.lower()) + + if link_exists: + cached_value = self.bondcmd.link_cache_get([ifname, 'linkinfo', netlink_attr]) + + if link_exists and cached_value is None: + # the link already exists but we don't have any value + # cached for this attr, it probably means that the + # capability is not available on this system (i.e old kernel) + self.logger.debug('%s: ignoring %s %s: capability ' + 'probably not supported on this system' + % (ifname, attr_name, user_config)) + continue + elif link_exists: + # there should be a cached value if the link already exists + if cached_value == nl_value: + # if the user value is already cached: continue + continue + + # else: the link doesn't exist so we create the bond with + # all the user/policy defined values without extra checks + ifla_info_data[netlink_attr] = nl_value + + if cached_value is not None: + self.logger.info('%s: set %s %s (cache %s)' % (ifname, attr_name, user_config, cached_value)) + else: + self.logger.info('%s: set %s %s' % (ifname, attr_name, user_config)) + + except KeyError: + self.logger.warning('%s: invalid %s value %s' % (ifname, attr_name, user_config)) + + self._check_bond_mode_user_config(ifname, link_exists, ifla_info_data) + return ifla_info_data + + _bond_down_nl_attributes_list = ( + Link.IFLA_BOND_MODE, + Link.IFLA_BOND_XMIT_HASH_POLICY, + Link.IFLA_BOND_AD_LACP_RATE, + Link.IFLA_BOND_MIN_LINKS + ) + + def _should_down_bond(self, ifla_info_data): + for nl_attr in self._bond_down_nl_attributes_list: + if nl_attr in ifla_info_data: + return True + return False + + def should_update_bond_mode(self, ifaceobj, ifname, is_link_up, ifla_info_data): + # if bond-mode was changed the bond needs to be brought + # down and slaves un-slaved before bond mode is changed. + cached_bond_mode = self.bondcmd.link_cache_get([ifname, 'linkinfo', Link.IFLA_BOND_MODE]) + ifla_bond_mode = ifla_info_data.get(Link.IFLA_BOND_MODE) + + # bond-mode was changed or is not specified + if ifla_bond_mode is not None: + if ifla_bond_mode != cached_bond_mode: + self.logger.info('%s: bond mode changed to %s: running ops on bond and slaves' + % (ifname, ifla_bond_mode)) + if is_link_up: + netlink.link_set_updown(ifname, 'down') + is_link_up = False + + for lower_dev in ifaceobj.lowerifaces: + netlink.link_set_nomaster(lower_dev) + + self.bondcmd.cache_delete([ifname, 'linkinfo', 'slaves']) + else: + # bond-mode user config value is the current running(cached) value + # no need to reset it again we can ignore this attribute + del ifla_info_data[Link.IFLA_BOND_MODE] + + return is_link_up + + def create_or_set_bond_config(self, ifaceobj): + ifname = ifaceobj.name + link_exists = self.ipcmd.link_exists(ifname) + is_link_up = self.ipcmd.is_link_up(ifname) if link_exists else False + ifla_info_data = self.get_ifla_bond_attr_from_user_config(ifaceobj, link_exists) + + remove_delay_from_cache = self.check_updown_delay_nl(link_exists, ifaceobj, ifla_info_data) + + # if link exists: down link if specific attributes are specified + if link_exists: + # did bond-mode changed? + is_link_up = self.should_update_bond_mode(ifaceobj, ifname, is_link_up, ifla_info_data) + + # if specific attributes need to be set we need to down the bond first + if ifla_info_data and is_link_up: + if self._should_down_bond(ifla_info_data): + netlink.link_set_updown(ifname, 'down') + is_link_up = False + + if link_exists and not ifla_info_data: + # if the bond already exists and no attrs need to be set + # ignore the netlink call + self.logger.info('%s: already exists, no change detected' % ifname) + else: + try: + netlink.link_add_set(kind='bond', ifname=ifname, ifla_info_data=ifla_info_data) + except Exception as e: + # defensive code + # if anything happens, we try to set up the bond with the sysfs api + self.logger.debug('%s: bond setup: %s' % (ifname, str(e))) + self.create_or_set_bond_config_sysfs(ifaceobj, ifla_info_data) + + if remove_delay_from_cache: + # making sure up/down delay attributes are set to 0 before caching + # this can be removed when moving to a nllistener/live cache + ifla_info_data[Link.IFLA_BOND_UPDELAY] = 0 + ifla_info_data[Link.IFLA_BOND_DOWNDELAY] = 0 + + # if link_add doesn't raise we can update the cache, the future + # netlink listener will update the cache based on the kernel response + for key, value in ifla_info_data.items(): + self.bondcmd.cache_update([ifname, 'linkinfo', key], value) + + if link_exists and ifla_info_data and not is_link_up: + netlink.link_set_updown(ifname, 'up') + + def create_or_set_bond_config_sysfs(self, ifaceobj, ifla_info_data): + if not self.ipcmd.link_exists(ifaceobj.name): + self.bondcmd.create_bond(ifaceobj.name) + self.bondcmd.bond_set_attrs_nl(ifaceobj.name, ifla_info_data) + + def _up(self, ifaceobj, ifaceobj_getfunc=None): + try: + self.create_or_set_bond_config(ifaceobj) + self._add_slaves(ifaceobj, ifaceobj_getfunc) + except Exception, e: + self.log_error(str(e), ifaceobj) + + def _down(self, ifaceobj, ifaceobj_getfunc=None): + try: + netlink.link_del(ifaceobj.name) + self.bondcmd.cache_delete([ifaceobj.name]) + except Exception as e: + self.log_warn('%s: %s' % (ifaceobj.name, str(e))) + + def _query_check_bond_slaves(self, ifaceobjcurr, attr, user_bond_slaves, running_bond_slaves): + query = 1 + + if user_bond_slaves and running_bond_slaves: + if not set(user_bond_slaves).symmetric_difference(running_bond_slaves): + query = 0 + + # we want to display the same bond-slaves list as provided + # in the interfaces file but if this list contains regexes or + # globs, for now, we won't try to change it. + if 'regex' in user_bond_slaves or 'glob' in user_bond_slaves: + user_bond_slaves = running_bond_slaves + else: + ordered = [] + for slave in user_bond_slaves: + if slave in running_bond_slaves: + ordered.append(slave) + user_bond_slaves = ordered + ifaceobjcurr.update_config_with_status(attr, ' '.join(user_bond_slaves) if user_bond_slaves else 'None', query) + + def _query_check(self, ifaceobj, ifaceobjcurr, ifaceobj_getfunc=None): + if not self.bondcmd.bond_exists(ifaceobj.name): + self.logger.debug('bond iface %s does not exist' % ifaceobj.name) + return + + iface_attrs = self.dict_key_subset(ifaceobj.config, self.get_mod_attrs()) + if not iface_attrs: + return + + # remove bond-slaves and bond-ports from the list, + # because there aren't any ifla_info_data netlink attr for slaves + # an exception is raised when index is not found, so query_slaves will stay False + query_slaves = False + + user_bond_slaves = None + running_bond_slaves = None + try: + del iface_attrs[iface_attrs.index('bond-slaves')] + + # if user specified bond-slaves we need to display it + query_slaves = True + if not user_bond_slaves: + user_bond_slaves = self._get_slave_list(ifaceobj) + running_bond_slaves = self.bondcmd.bond_get_slaves(ifaceobj.name) + + self._query_check_bond_slaves(ifaceobjcurr, 'bond-slaves', user_bond_slaves, running_bond_slaves) + except: + pass + try: + del iface_attrs[iface_attrs.index('bond-ports')] + + # if user specified bond-ports we need to display it + if not query_slaves and not user_bond_slaves: # if get_slave_list was already called for slaves + user_bond_slaves = self._get_slave_list(ifaceobj) + running_bond_slaves = self.bondcmd.bond_get_slaves(ifaceobj.name) + + self._query_check_bond_slaves(ifaceobjcurr, 'bond-ports', user_bond_slaves, running_bond_slaves) + except: + pass + + for attr in iface_attrs: + nl_attr = self._bond_attr_netlink_map[attr] + translate_func = self._bond_attr_ifquery_check_translate_func[nl_attr] + current_config = self.bondcmd.link_cache_get([ifaceobj.name, 'linkinfo', nl_attr]) + user_config = ifaceobj.get_attr_value_first(attr) + + if current_config == translate_func(user_config): + ifaceobjcurr.update_config_with_status(attr, user_config, 0) + else: + ifaceobjcurr.update_config_with_status(attr, str(current_config), 1) + + @staticmethod + def translate_nl_value_yesno(value): + return 'yes' if value else 'no' + + @staticmethod + def translate_nl_value_slowfast(value): + return 'fast' if value else 'slow' + + def _query_running_attrs(self, bondname): + bond_attrs = { + 'bond-mode': Link.ifla_bond_mode_pretty_tbl.get(self.bondcmd.link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_MODE])), + 'bond-miimon': self.bondcmd.link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_MIIMON]), + 'bond-use-carrier': self.translate_nl_value_yesno(self.bondcmd.link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_USE_CARRIER])), + 'bond-lacp-rate': self.translate_nl_value_slowfast(self.bondcmd.link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_AD_LACP_RATE])), + 'bond-min-links': self.bondcmd.link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_MIN_LINKS]), + 'bond-ad-actor-system': self.bondcmd.link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_AD_ACTOR_SYSTEM]), + 'bond-ad-actor-sys-prio': self.bondcmd.link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_AD_ACTOR_SYS_PRIO]), + 'bond-xmit-hash-policy': Link.ifla_bond_xmit_hash_policy_pretty_tbl.get(self.bondcmd.link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_XMIT_HASH_POLICY])), + 'bond-lacp-bypass-allow': self.translate_nl_value_yesno(self.bondcmd.link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_AD_LACP_BYPASS])), + 'bond-num-unsol-na': self.bondcmd.link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_NUM_PEER_NOTIF]), + 'bond-num-grat-arp': self.bondcmd.link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_NUM_PEER_NOTIF]), + 'bond-updelay': self.bondcmd.link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_UPDELAY]), + 'bond-downdelay': self.bondcmd.link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_DOWNDELAY]) + } + slaves = self.bondcmd.bond_get_slaves(bondname) + if slaves: + bond_attrs['bond-slaves'] = slaves + return bond_attrs + + def _query_running(self, ifaceobjrunning, ifaceobj_getfunc=None): + if not self.bondcmd.bond_exists(ifaceobjrunning.name): + return + bond_attrs = self._query_running_attrs(ifaceobjrunning.name) + if bond_attrs.get('bond-slaves'): + bond_attrs['bond-slaves'] = ' '.join(bond_attrs.get('bond-slaves')) + + [ifaceobjrunning.update_config(k, str(v)) + for k, v in bond_attrs.items() + if v is not None] + + _run_ops = { + 'pre-up': _up, + 'post-down': _down, + 'query-running': _query_running, + 'query-checkcurr': _query_check + } + + def get_ops(self): + """ returns list of ops supported by this module """ + return self._run_ops.keys() + + def _init_command_handlers(self): + if not self.ipcmd: + self.ipcmd = self.bondcmd = LinkUtils() + + def run(self, ifaceobj, operation, query_ifaceobj=None, + ifaceobj_getfunc=None): + """ run bond configuration on the interface object passed as argument + + Args: + **ifaceobj** (object): iface object + + **operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr', + 'query-running' + + Kwargs: + **query_ifaceobj** (object): query check ifaceobject. This is only + valid when op is 'query-checkcurr'. It is an object same as + ifaceobj, but contains running attribute values and its config + status. The modules can use it to return queried running state + of interfaces. status is success if the running state is same + as user required state in ifaceobj. error otherwise. + """ + op_handler = self._run_ops.get(operation) + if not op_handler: + return + if operation != 'query-running' and not self._is_bond(ifaceobj): + return + self._init_command_handlers() + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj, + ifaceobj_getfunc=ifaceobj_getfunc) + else: + op_handler(self, ifaceobj, ifaceobj_getfunc=ifaceobj_getfunc) diff --git a/ifupdown2/addons/bridge.py b/ifupdown2/addons/bridge.py new file mode 100644 index 0000000..02cbebf --- /dev/null +++ b/ifupdown2/addons/bridge.py @@ -0,0 +1,3146 @@ +#!/usr/bin/python +# +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +import re +import time +import itertools + +from sets import Set +from collections import Counter + +try: + import ifupdown2.ifupdown.exceptions as exceptions + import ifupdown2.ifupdown.policymanager as policymanager + import ifupdown2.ifupdown.ifupdownflags as ifupdownflags + + from ifupdown2.nlmanager.nlmanager import Link + + from ifupdown2.ifupdown.iface import * + from ifupdown2.ifupdown.utils import utils + from ifupdown2.ifupdown.netlink import netlink + + from ifupdown2.ifupdownaddons.cache import * + from ifupdown2.ifupdownaddons.LinkUtils import LinkUtils + from ifupdown2.ifupdownaddons.modulebase import moduleBase +except ImportError: + import ifupdown.exceptions as exceptions + import ifupdown.policymanager as policymanager + import ifupdown.ifupdownflags as ifupdownflags + + from nlmanager.nlmanager import Link + + from ifupdown.iface import * + from ifupdown.utils import utils + from ifupdown.netlink import netlink + + from ifupdownaddons.cache import * + from ifupdownaddons.LinkUtils import LinkUtils + from ifupdownaddons.modulebase import moduleBase + + +class bridgeFlags: + PORT_PROCESSED = 0x1 + PORT_PROCESSED_OVERRIDE = 0x2 + + +class bridge(moduleBase): + """ ifupdown2 addon module to configure linux bridges """ + + _modinfo = { 'mhelp' : 'Bridge configuration module. Supports both ' + + 'vlan aware and non vlan aware bridges. For the vlan ' + + 'aware bridge, the port specific attributes must be ' + + 'specified under the port. And for vlan unaware bridge ' + + 'port specific attributes must be specified under the ' + + 'bridge.', + 'attrs' : { + 'bridge-vlan-aware' : + {'help' : 'vlan aware bridge. Setting this ' + + 'attribute to yes enables vlan filtering' + + ' on the bridge', + 'validvals' : ['yes', 'no'], + 'example' : ['bridge-vlan-aware yes/no'], + 'default': 'no' + }, + 'bridge-ports' : + {'help' : 'bridge ports', + 'multivalue' : True, + 'required' : True, + 'validvals': [''], + 'example' : ['bridge-ports swp1.100 swp2.100 swp3.100', + 'bridge-ports glob swp1-3.100', + 'bridge-ports regex (swp[1|2|3].100)']}, + 'bridge-stp' : + {'help': 'bridge-stp yes/no', + 'example' : ['bridge-stp no'], + 'validvals' : ['yes', 'on', 'off', 'no'], + 'default' : 'no'}, + 'bridge-bridgeprio' : + {'help': 'bridge priority', + 'validrange' : ['0', '65535'], + 'example' : ['bridge-bridgeprio 32768'], + 'default' : '32768'}, + 'bridge-ageing' : + {'help': 'bridge ageing', + 'validrange' : ['0', '65535'], + 'example' : ['bridge-ageing 300'], + 'default' : '300'}, + 'bridge-fd' : + { 'help' : 'bridge forward delay', + 'validrange' : ['0', '255'], + 'example' : ['bridge-fd 15'], + 'default' : '15'}, + 'bridge-gcint' : + # XXX: recheck values + { 'help' : 'bridge garbage collection interval in secs', + 'validrange' : ['0', '255'], + 'example' : ['bridge-gcint 4'], + 'default' : '4', + 'compat' : True, + 'deprecated': True}, + 'bridge-hello' : + { 'help' : 'bridge set hello time', + 'validrange' : ['0', '255'], + 'example' : ['bridge-hello 2'], + 'default' : '2'}, + 'bridge-maxage' : + { 'help' : 'bridge set maxage', + 'validrange' : ['0', '255'], + 'example' : ['bridge-maxage 20'], + 'default' : '20'}, + 'bridge-pathcosts' : + { 'help' : 'bridge set port path costs', + 'validvals': [''], + 'validrange' : ['0', '65535'], + 'example' : ['under the port (for vlan aware bridge): bridge-pathcosts 100', + 'under the bridge (for vlan unaware bridge): bridge-pathcosts swp1=100 swp2=100'], + 'default' : '100'}, + 'bridge-portprios' : + { 'help' : 'bridge port prios', + 'validvals': [''], + 'validrange' : ['0', '65535'], + 'example' : ['under the port (for vlan aware bridge): bridge-portprios 32', + 'under the bridge (for vlan unaware bridge): bridge-portprios swp1=32 swp2=32'], + 'default' : '32'}, + 'bridge-mclmc' : + { 'help' : 'set multicast last member count', + 'validrange' : ['0', '255'], + 'example' : ['bridge-mclmc 2'], + 'default' : '2'}, + 'bridge-mcrouter' : + { 'help' : 'set multicast router', + 'validvals' : ['yes', 'no', '0', '1', '2'], + 'example' : ['bridge-mcrouter 1'], + 'default': 'yes' + }, + 'bridge-mcsnoop' : + { 'help' : 'set multicast snooping', + 'validvals' : ['yes', 'no', '0', '1'], + 'default' : 'yes', + 'example' : ['bridge-mcsnoop yes']}, + 'bridge-mcsqc' : + { 'help' : 'set multicast startup query count', + 'validrange' : ['0', '255'], + 'default' : '2', + 'example' : ['bridge-mcsqc 2']}, + 'bridge-mcqifaddr' : + { 'help' : 'set multicast query to use ifaddr', + 'validvals' : ['yes', 'no', '0', '1'], + 'default' : 'no', + 'example' : ['bridge-mcqifaddr no']}, + 'bridge-mcquerier' : + { 'help' : 'set multicast querier', + 'validvals' : ['yes', 'no', '0', '1'], + 'default' : 'no', + 'example' : ['bridge-mcquerier no']}, + 'bridge-hashel' : + { 'help' : 'set hash elasticity', + 'validrange' : ['0', '4096'], + 'default' : '4', + 'example' : ['bridge-hashel 4096']}, + 'bridge-hashmax' : + { 'help' : 'set hash max', + 'validrange' : ['0', '4096'], + 'default' : '512', + 'example' : ['bridge-hashmax 4096']}, + 'bridge-mclmi' : + { 'help' : 'set multicast last member interval (in secs)', + 'validrange' : ['0', '255'], + 'default' : '1', + 'example' : ['bridge-mclmi 1']}, + 'bridge-mcmi' : + { 'help' : 'set multicast membership interval (in secs)', + 'validrange' : ['0', '255'], + 'default' : '260', + 'example' : ['bridge-mcmi 260']}, + 'bridge-mcqpi' : + { 'help' : 'set multicast querier interval (in secs)', + 'validrange' : ['0', '255'], + 'default' : '255', + 'example' : ['bridge-mcqpi 255']}, + 'bridge-mcqi' : + { 'help' : 'set multicast query interval (in secs)', + 'validrange' : ['0', '255'], + 'default' : '125', + 'example' : ['bridge-mcqi 125']}, + 'bridge-mcqri' : + { 'help' : 'set multicast query response interval (in secs)', + 'validrange' : ['0', '255'], + 'default' : '10', + 'example' : ['bridge-mcqri 10']}, + 'bridge-mcsqi' : + { 'help' : 'set multicast startup query interval (in secs)', + 'validrange' : ['0', '255'], + 'default' : '31', + 'example' : ['bridge-mcsqi 31']}, + 'bridge-mcqv4src' : + { 'help' : 'set per VLAN v4 multicast querier source address', + 'validvals' : ['', ], + 'multivalue' : True, + 'compat' : True, + 'example' : ['bridge-mcqv4src 100=172.16.100.1 101=172.16.101.1']}, + 'bridge-portmcrouter': + { + 'help': 'set port multicast routers', + 'validvals': [''], + 'example': [ + 'under the port (for vlan aware bridge): bridge-portmcrouter 0', + 'under the port (for vlan aware bridge): bridge-portmcrouter 1', + 'under the port (for vlan aware bridge): bridge-portmcrouter 2', + 'under the port (for vlan aware bridge): bridge-portmcrouter disabled', + 'under the port (for vlan aware bridge): bridge-portmcrouter automatic', + 'under the port (for vlan aware bridge): bridge-portmcrouter enabled', + 'under the bridge (for vlan unaware bridge): bridge-portmcrouter swp1=0 swp2=1 swp2=2', + 'under the bridge (for vlan unaware bridge): bridge-portmcrouter swp1=disabled swp2=automatic swp3=enabled', + 'under the bridge (for vlan unaware bridge): bridge-portmcrouter swp1=2 swp2=disabled swp3=1', + ] + }, + 'bridge-portmcfl' : + { 'help' : 'port multicast fast leave.', + 'validvals': [''], + 'validrange' : ['yes', 'no', '0', '1'], + 'default' : 'no', + 'example' : ['under the port (for vlan aware bridge): bridge-portmcfl no', + 'under the bridge (for vlan unaware bridge): bridge-portmcfl swp1=no swp2=no']}, + 'bridge-waitport' : + { 'help' : 'wait for a max of time secs for the' + + ' specified ports to become available,' + + 'if no ports are specified then those' + + ' specified on bridge-ports will be' + + ' used here. Specifying no ports here ' + + 'should not be used if we are using ' + + 'regex or \"all\" on bridge_ports,' + + 'as it wouldnt work.', + 'default' : '0', + 'validvals': [''], + 'example' : ['bridge-waitport 4 swp1 swp2']}, + 'bridge-maxwait' : + { 'help' : 'forces to time seconds the maximum time ' + + 'that the Debian bridge setup scripts will ' + + 'wait for the bridge ports to get to the ' + + 'forwarding status, doesn\'t allow factional ' + + 'part. If it is equal to 0 then no waiting' + + ' is done', + 'validrange' : ['0', '255'], + 'default' : '0', + 'example' : ['bridge-maxwait 3']}, + 'bridge-vids' : + { 'help' : 'bridge port vids. Can be specified ' + + 'under the bridge or under the port. ' + + 'If specified under the bridge the ports ' + + 'inherit it unless overridden by a ' + + 'bridge-vids attribute under the port', + 'multivalue' : True, + 'validvals': [''], + 'example' : ['bridge-vids 4000', + 'bridge-vids 2000 2200-3000'], + 'aliases': ['bridge-trunk']}, + 'bridge-pvid' : + { 'help' : 'bridge port pvid. Must be specified under' + + ' the bridge port', + 'validrange' : ['0', '4096'], + 'example' : ['bridge-pvid 1']}, + 'bridge-access' : + { 'help' : 'bridge port access vlan. Must be ' + + 'specified under the bridge port', + 'validrange' : ['1', '4094'], + 'example' : ['bridge-access 300']}, + 'bridge-allow-untagged' : + { 'help' : 'indicate if the bridge port accepts ' + + 'untagged packets or not. Must be ' + + 'specified under the bridge port. ' + + 'Default is \'yes\'', + 'validvals' : ['yes', 'no'], + 'example' : ['bridge-allow-untagged yes'], + 'default' : 'yes'}, + 'bridge-port-vids' : + { 'help' : 'bridge vlans', + 'compat': True, + 'example' : ['bridge-port-vids bond0=1-1000,1010-1020']}, + 'bridge-port-pvids' : + { 'help' : 'bridge port vlans', + 'compat': True, + 'example' : ['bridge-port-pvids bond0=100 bond1=200']}, + 'bridge-learning' : + { 'help' : 'bridge port learning flag', + 'validvals': ['on', 'off', ''], + 'default': 'on', + 'example' : ['bridge-learning off']}, + 'bridge-igmp-version' : + { 'help' : 'mcast igmp version', + 'validvals': ['2', '3'], + 'default' : '2', + 'example' : ['bridge-igmp-version 2']}, + 'bridge-mld-version': + { 'help' : 'mcast mld version', + 'validvals': ['1', '2'], + 'default' : '1', + 'example' : ['bridge-mld-version 1']}, + 'bridge-unicast-flood' : + { 'help' : 'bridge port unicast flood flag', + 'validvals': ['on', 'off', ''], + 'default': 'on', + 'example' : ['under the port (for vlan aware bridge): bridge-unicast-flood on', + 'under the bridge (for vlan unaware bridge): bridge-unicast-flood swp1=on swp2=on']}, + 'bridge-multicast-flood' : + { 'help' : 'bridge port multicast flood flag', + 'validvals': ['on', 'off', ''], + 'default': 'on', + 'example' : ['under the port (for vlan aware bridge): bridge-multicast-flood on', + 'under the bridge (for vlan unaware bridge): bridge-multicast-flood swp1=on swp2=on']}, + 'bridge-vlan-protocol' : + { 'help' : 'bridge vlan protocol', + 'default' : '802.1q', + 'validvals': ['802.1q', '802.1ad'], + 'example' : ['bridge-vlan-protocol 802.1q']}, + 'bridge-vlan-stats' : + { 'help' : 'bridge vlan stats', + 'default' : 'off', + 'validvals': ['on', 'off'], + 'example' : ['bridge-vlan-stats off']}, + 'bridge-arp-nd-suppress' : + { 'help' : 'bridge port arp nd suppress flag', + 'validvals': ['on', 'off', ''], + 'default': 'off', + 'example' : ['under the port (for vlan aware bridge): bridge-arp-nd-suppress on', + 'under the bridge (for vlan unaware bridge): bridge-arp-nd-suppress swp1=on swp2=on']}, + 'bridge-mcstats' : + { 'help' : 'bridge multicast stats', + 'default' : 'off', + 'validvals': ['on', 'off'], + 'example' : ['bridge-mcstats off']}, + 'bridge-l2protocol-tunnel': { + 'help': 'layer 2 protocol tunneling', + 'validvals': [ # XXX: lists all combinations, should move to + # a better representation + 'all', + 'cdp', + 'cdp lacp', + 'cdp lacp lldp', + 'cdp lacp lldp pvst', + 'cdp lacp lldp stp', + 'cdp lacp pvst', + 'cdp lacp pvst stp', + 'cdp lacp stp', + 'cdp lldp', + 'cdp lldp pvst', + 'cdp lldp pvst stp', + 'cdp lldp stp', + 'cdp pvst', + 'cdp pvst stp', + 'cdp stp', + 'lacp', + 'lacp lldp', + 'lacp lldp pvst', + 'lacp lldp pvst stp', + 'lacp lldp stp', + 'lacp pvst', + 'lacp pvst stp', + 'lacp stp', + 'lldp', + 'lldp pvst', + 'lldp pvst stp', + 'lldp stp', + 'pvst', + 'pvst stp', + 'stp', + ''], + 'example': [ + 'under the bridge (for vlan unaware bridge): bridge-l2protocol-tunnel swpX=lacp,stp swpY=cdp swpZ=all', + 'under the port (for vlan aware bridge): bridge-l2protocol-tunnel lacp stp lldp cdp pvst', + 'under the port (for vlan aware bridge): bridge-l2protocol-tunnel lldp pvst', + 'under the port (for vlan aware bridge): bridge-l2protocol-tunnel stp', + 'under the port (for vlan aware bridge): bridge-l2protocol-tunnel all' + ] + } + }} + + # Netlink attributes not associated with ifupdown2 + # attributes are left commented-out for a future use + # and kept in order :) + _ifla_br_attributes_map = ( + # Link.IFLA_BR_UNSPEC, + ('bridge-fd', Link.IFLA_BR_FORWARD_DELAY), + ('bridge-hello', Link.IFLA_BR_HELLO_TIME), + ('bridge-maxage', Link.IFLA_BR_MAX_AGE), + ('bridge-ageing', Link.IFLA_BR_AGEING_TIME), + ('bridge-stp', Link.IFLA_BR_STP_STATE), + ('bridge-bridgeprio', Link.IFLA_BR_PRIORITY), + ('bridge-vlan-aware', Link.IFLA_BR_VLAN_FILTERING), + ('bridge-vlan-protocol', Link.IFLA_BR_VLAN_PROTOCOL), + # Link.IFLA_BR_GROUP_FWD_MASK, + # Link.IFLA_BR_ROOT_ID, + # Link.IFLA_BR_BRIDGE_ID, + # Link.IFLA_BR_ROOT_PORT, + # (Link.IFLA_BR_ROOT_PATH_COST,, + # Link.IFLA_BR_TOPOLOGY_CHANGE, + # Link.IFLA_BR_TOPOLOGY_CHANGE_DETECTED, + # Link.IFLA_BR_HELLO_TIMER, + # Link.IFLA_BR_TCN_TIMER, + # Link.IFLA_BR_TOPOLOGY_CHANGE_TIMER, + # Link.IFLA_BR_GC_TIMER, + # Link.IFLA_BR_GROUP_ADDR, + # Link.IFLA_BR_FDB_FLUSH, + ('bridge-mcrouter', Link.IFLA_BR_MCAST_ROUTER), + #('bridge-mcsnoop', Link.IFLA_BR_MCAST_SNOOPING), # requires special handling so we won't loop on this attr + ('bridge-mcqifaddr', Link.IFLA_BR_MCAST_QUERY_USE_IFADDR), + ('bridge-mcquerier', Link.IFLA_BR_MCAST_QUERIER), + ('bridge-hashel', Link.IFLA_BR_MCAST_HASH_ELASTICITY), + ('bridge-hashmax', Link.IFLA_BR_MCAST_HASH_MAX), + ('bridge-mclmc', Link.IFLA_BR_MCAST_LAST_MEMBER_CNT), + ('bridge-mcsqc', Link.IFLA_BR_MCAST_STARTUP_QUERY_CNT), + ('bridge-mclmi', Link.IFLA_BR_MCAST_LAST_MEMBER_INTVL), + ('bridge-mcmi', Link.IFLA_BR_MCAST_MEMBERSHIP_INTVL), + ('bridge-mcqpi', Link.IFLA_BR_MCAST_QUERIER_INTVL), + ('bridge-mcqi', Link.IFLA_BR_MCAST_QUERY_INTVL), + ('bridge-mcqri', Link.IFLA_BR_MCAST_QUERY_RESPONSE_INTVL), + ('bridge-mcsqi', Link.IFLA_BR_MCAST_STARTUP_QUERY_INTVL), + # Link.IFLA_BR_NF_CALL_IPTABLES, + # Link.IFLA_BR_NF_CALL_IP6TABLES, + # Link.IFLA_BR_NF_CALL_ARPTABLES, + # Link.IFLA_BR_VLAN_DEFAULT_PVID, + # Link.IFLA_BR_PAD, + # (Link.IFLA_BR_VLAN_STATS_ENABLED, 'bridge-vlan-stats'), # already dealt with, in a separate loop + ('bridge-igmp-version', Link.IFLA_BR_MCAST_IGMP_VERSION, ), + ('bridge-mcstats', Link.IFLA_BR_MCAST_STATS_ENABLED), + ('bridge-mld-version', Link.IFLA_BR_MCAST_MLD_VERSION) + ) + # 'bridge-vlan-stats & bridge-mcstat are commented out even though, today + # they are supported. It is done this way because this dictionary is used + # in a loop, but these attributes require additional work. Thus they are + # excluded from this loop without overhead. + + # we are still using the old linkCache we need an easy way + # to use this cache with the new full-netlink approach + _ifla_br_attributes_old_cache_key_map = dict( + ( + (Link.IFLA_BR_FORWARD_DELAY, 'fd'), + (Link.IFLA_BR_HELLO_TIME, 'hello'), + (Link.IFLA_BR_MAX_AGE, 'maxage'), + (Link.IFLA_BR_AGEING_TIME, 'ageing'), + (Link.IFLA_BR_STP_STATE, 'stp'), + (Link.IFLA_BR_PRIORITY, 'bridgeprio'), + (Link.IFLA_BR_VLAN_FILTERING, 'vlan_filtering'), + (Link.IFLA_BR_VLAN_PROTOCOL, 'vlan-protocol'), + (Link.IFLA_BR_MCAST_ROUTER, 'mcrouter'), + (Link.IFLA_BR_MCAST_SNOOPING, 'mcsnoop'), + (Link.IFLA_BR_MCAST_QUERY_USE_IFADDR, 'mcqifaddr'), + (Link.IFLA_BR_MCAST_QUERIER, 'mcquerier'), + (Link.IFLA_BR_MCAST_HASH_ELASTICITY, 'hashel'), + (Link.IFLA_BR_MCAST_HASH_MAX, 'hashmax'), + (Link.IFLA_BR_MCAST_LAST_MEMBER_CNT, 'mclmc'), + (Link.IFLA_BR_MCAST_STARTUP_QUERY_CNT, 'mcsqc'), + (Link.IFLA_BR_MCAST_LAST_MEMBER_INTVL, 'mclmi'), + (Link.IFLA_BR_MCAST_MEMBERSHIP_INTVL, 'mcmi'), + (Link.IFLA_BR_MCAST_QUERIER_INTVL, 'mcqpi'), + (Link.IFLA_BR_MCAST_QUERY_INTVL, 'mcqi'), + (Link.IFLA_BR_MCAST_QUERY_RESPONSE_INTVL, 'mcqri'), + (Link.IFLA_BR_MCAST_STARTUP_QUERY_INTVL, 'mcsqi'), + (Link.IFLA_BR_VLAN_STATS_ENABLED, 'vlan-stats'), + (Link.IFLA_BR_MCAST_STATS_ENABLED, 'mcstats'), + (Link.IFLA_BR_MCAST_IGMP_VERSION, 'igmp-version'), + (Link.IFLA_BR_MCAST_MLD_VERSION, 'mld-version') + ) + ) + + _ifla_br_attributes_translate_user_config_to_netlink_map = dict( + ( + # Link.IFLA_BR_UNSPEC, + (Link.IFLA_BR_FORWARD_DELAY, lambda x: int(x) * 100), + (Link.IFLA_BR_HELLO_TIME, lambda x: int(x) * 100), + (Link.IFLA_BR_MAX_AGE, lambda x: int(x) * 100), + (Link.IFLA_BR_AGEING_TIME, lambda x: int(x) * 100), + # Link.IFLA_BR_STP_STATE, # STP is treated outside the loop + (Link.IFLA_BR_PRIORITY, int), + (Link.IFLA_BR_VLAN_FILTERING, utils.get_boolean_from_string), + (Link.IFLA_BR_VLAN_PROTOCOL, str), + # Link.IFLA_BR_GROUP_FWD_MASK, + # Link.IFLA_BR_ROOT_ID, + # Link.IFLA_BR_BRIDGE_ID, + # Link.IFLA_BR_ROOT_PORT, + # Link.IFLA_BR_ROOT_PATH_COST, + # Link.IFLA_BR_TOPOLOGY_CHANGE, + # Link.IFLA_BR_TOPOLOGY_CHANGE_DETECTED, + # Link.IFLA_BR_HELLO_TIMER, + # Link.IFLA_BR_TCN_TIMER, + # Link.IFLA_BR_TOPOLOGY_CHANGE_TIMER, + # Link.IFLA_BR_GC_TIMER, + # Link.IFLA_BR_GROUP_ADDR, + # Link.IFLA_BR_FDB_FLUSH, + (Link.IFLA_BR_MCAST_ROUTER, utils.get_int_from_boolean_and_string), + (Link.IFLA_BR_MCAST_SNOOPING, utils.get_boolean_from_string), + (Link.IFLA_BR_MCAST_QUERY_USE_IFADDR, utils.get_boolean_from_string), + (Link.IFLA_BR_MCAST_QUERIER, utils.get_boolean_from_string), + (Link.IFLA_BR_MCAST_HASH_ELASTICITY, int), + (Link.IFLA_BR_MCAST_HASH_MAX, int), + (Link.IFLA_BR_MCAST_LAST_MEMBER_CNT, int), + (Link.IFLA_BR_MCAST_STARTUP_QUERY_CNT, int), + (Link.IFLA_BR_MCAST_LAST_MEMBER_INTVL, lambda x: int(x) * 100), + (Link.IFLA_BR_MCAST_MEMBERSHIP_INTVL, lambda x: int(x) * 100), + (Link.IFLA_BR_MCAST_QUERIER_INTVL, lambda x: int(x) * 100), + (Link.IFLA_BR_MCAST_QUERY_INTVL, lambda x: int(x) * 100), + (Link.IFLA_BR_MCAST_QUERY_RESPONSE_INTVL, lambda x: int(x) * 100), + (Link.IFLA_BR_MCAST_STARTUP_QUERY_INTVL, lambda x: int(x) * 100), + # Link.IFLA_BR_NF_CALL_IPTABLES, + # Link.IFLA_BR_NF_CALL_IP6TABLES, + # Link.IFLA_BR_NF_CALL_ARPTABLES, + # Link.IFLA_BR_VLAN_DEFAULT_PVID, + # Link.IFLA_BR_PAD, + (Link.IFLA_BR_VLAN_STATS_ENABLED, utils.get_boolean_from_string), + (Link.IFLA_BR_MCAST_IGMP_VERSION, int), + (Link.IFLA_BR_MCAST_STATS_ENABLED, utils.get_boolean_from_string), + (Link.IFLA_BR_MCAST_MLD_VERSION, int) + ) + ) + + _ifla_brport_attributes_map = ( + # Link.IFLA_BRPORT_UNSPEC, + # Link.IFLA_BRPORT_STATE, + ('bridge-portprios', Link.IFLA_BRPORT_PRIORITY), + ('bridge-pathcosts', Link.IFLA_BRPORT_COST), + # Link.IFLA_BRPORT_MODE, + # Link.IFLA_BRPORT_GUARD, + # Link.IFLA_BRPORT_PROTECT, + ('bridge-portmcfl', Link.IFLA_BRPORT_FAST_LEAVE), + ('bridge-learning', Link.IFLA_BRPORT_LEARNING), + ('bridge-unicast-flood', Link.IFLA_BRPORT_UNICAST_FLOOD), + # Link.IFLA_BRPORT_PROXYARP, + # Link.IFLA_BRPORT_LEARNING_SYNC, + # Link.IFLA_BRPORT_PROXYARP_WIFI, + # Link.IFLA_BRPORT_ROOT_ID, + # Link.IFLA_BRPORT_BRIDGE_ID, + # Link.IFLA_BRPORT_DESIGNATED_PORT, + # Link.IFLA_BRPORT_DESIGNATED_COST, + # Link.IFLA_BRPORT_ID, + # Link.IFLA_BRPORT_NO, + # Link.IFLA_BRPORT_TOPOLOGY_CHANGE_ACK, + # Link.IFLA_BRPORT_CONFIG_PENDING, + # Link.IFLA_BRPORT_MESSAGE_AGE_TIMER, + # Link.IFLA_BRPORT_FORWARD_DELAY_TIMER, + # Link.IFLA_BRPORT_HOLD_TIMER, + # Link.IFLA_BRPORT_FLUSH, + ('bridge-portmcrouter', Link.IFLA_BRPORT_MULTICAST_ROUTER), + # Link.IFLA_BRPORT_PAD, + ('bridge-multicast-flood', Link.IFLA_BRPORT_MCAST_FLOOD), + # Link.IFLA_BRPORT_MCAST_TO_UCAST, + # Link.IFLA_BRPORT_VLAN_TUNNEL, + # Link.IFLA_BRPORT_BCAST_FLOOD + ('bridge-l2protocol-tunnel', Link.IFLA_BRPORT_GROUP_FWD_MASK), + # Link.IFLA_BRPORT_PEER_LINK, + # Link.IFLA_BRPORT_DUAL_LINK, + ('bridge-arp-nd-suppress', Link.IFLA_BRPORT_ARP_SUPPRESS), + ) + + _ifla_brport_multicast_router_dict_to_int = { + 'disabled': 0, + '0': 0, + 'no': 0, + 'automatic': 1, + '1': 1, + 'yes': 1, + 'enabled': 2, + '2': 2, + } + + # callable to translate to netlink value + _ifla_brport_attributes_translate_user_config_to_netlink_map = dict( + ( + (Link.IFLA_BRPORT_PRIORITY, int), + (Link.IFLA_BRPORT_COST, int), + (Link.IFLA_BRPORT_MULTICAST_ROUTER, lambda x: bridge._ifla_brport_multicast_router_dict_to_int.get(x, 0)), + (Link.IFLA_BRPORT_FAST_LEAVE, utils.get_boolean_from_string), + (Link.IFLA_BRPORT_LEARNING, utils.get_boolean_from_string), + (Link.IFLA_BRPORT_UNICAST_FLOOD, utils.get_boolean_from_string), + (Link.IFLA_BRPORT_MCAST_FLOOD, utils.get_boolean_from_string), + (Link.IFLA_BRPORT_GROUP_FWD_MASK, lambda x: x), + (Link.IFLA_BRPORT_ARP_SUPPRESS, utils.get_boolean_from_string) + ) + ) + + def __init__(self, *args, **kargs): + moduleBase.__init__(self, *args, **kargs) + self.ipcmd = None + self.name = self.__class__.__name__ + self.brctlcmd = None + self._running_vidinfo = {} + self._running_vidinfo_valid = False + self._resv_vlan_range = self._get_reserved_vlan_range() + self.logger.debug('%s: using reserved vlan range %s' % (self.__class__.__name__, str(self._resv_vlan_range))) + + self.default_stp_on = utils.get_boolean_from_string( + policymanager.policymanager_api.get_attr_default( + module_name=self.__class__.__name__, + attr='bridge-stp' + ) + ) + + self.default_vlan_stats = policymanager.policymanager_api.get_attr_default( + module_name=self.__class__.__name__, + attr='bridge-vlan-stats' + ) + + self.warn_on_untagged_bridge_absence = utils.get_boolean_from_string( + policymanager.policymanager_api.get_module_globals( + module_name=self.__class__.__name__, + attr='warn_on_untagged_bridge_absence' + ) + ) + self.logger.debug('bridge: init: warn_on_untagged_bridge_absence=%s' + % self.warn_on_untagged_bridge_absence) + + self._vxlan_bridge_default_igmp_snooping = policymanager.policymanager_api.get_module_globals( + self.__class__.__name__, + 'vxlan_bridge_default_igmp_snooping' + ) + self.logger.debug('bridge: init: vxlan_bridge_default_igmp_snooping=%s' + % self._vxlan_bridge_default_igmp_snooping) + + self.arp_nd_suppress_only_on_vxlan = utils.get_boolean_from_string( + policymanager.policymanager_api.get_module_globals( + module_name=self.__class__.__name__, + attr='allow_arp_nd_suppress_only_on_vxlan' + ) + ) + self.logger.debug('bridge: init: arp_nd_suppress_only_on_vxlan=%s' % self.arp_nd_suppress_only_on_vxlan) + + try: + self.bridge_allow_multiple_vlans = utils.get_boolean_from_string( + self.sysctl_get('net.bridge.bridge-allow-multiple-vlans') + ) + except: + # Cumulus Linux specific variable. Failure probably means that + # ifupdown2 is running a a different system. + self.bridge_allow_multiple_vlans = True + self.logger.debug('bridge: init: multiple vlans allowed %s' % self.bridge_allow_multiple_vlans) + + self.bridge_mac_iface_list = policymanager.policymanager_api.get_module_globals(self.__class__.__name__, 'bridge_mac_iface') or [] + self.bridge_mac_iface = None, None # ifname, mac + + self.bridge_set_static_mac_from_port = utils.get_boolean_from_string( + policymanager.policymanager_api.get_module_globals( + self.__class__.__name__, 'bridge_set_static_mac_from_port' + ) + ) + + self.l2protocol_tunnel_callback = { + 'all': self._l2protocol_tunnel_set_all, + 'stp': self._l2protocol_tunnel_set_stp, + 'cdp': self._l2protocol_tunnel_set_cdp, + 'pvst': self._l2protocol_tunnel_set_pvst, + 'lldp': self._l2protocol_tunnel_set_lldp, + 'lacp': self._l2protocol_tunnel_set_lacp + } + + self.query_check_l2protocol_tunnel_callback = { + 'all': self._query_check_l2protocol_tunnel_all, + 'stp': self._query_check_l2protocol_tunnel_stp, + 'cdp': self._query_check_l2protocol_tunnel_cdp, + 'pvst': self._query_check_l2protocol_tunnel_pvst, + 'lldp': self._query_check_l2protocol_tunnel_lldp, + 'lacp': self._query_check_l2protocol_tunnel_lacp + } + + @staticmethod + def _l2protocol_tunnel_set_pvst(ifla_brport_group_mask, ifla_brport_group_maskhi): + ifla_brport_group_maskhi |= 0x1 + return ifla_brport_group_mask, ifla_brport_group_maskhi + + @staticmethod + def _l2protocol_tunnel_set_cdp(ifla_brport_group_mask, ifla_brport_group_maskhi): + ifla_brport_group_maskhi |= 0x2 + return ifla_brport_group_mask, ifla_brport_group_maskhi + + @staticmethod + def _l2protocol_tunnel_set_stp(ifla_brport_group_mask, ifla_brport_group_maskhi): + ifla_brport_group_mask |= 0x1 + return ifla_brport_group_mask, ifla_brport_group_maskhi + + @staticmethod + def _l2protocol_tunnel_set_lacp(ifla_brport_group_mask, ifla_brport_group_maskhi): + ifla_brport_group_mask |= 0x4 + return ifla_brport_group_mask, ifla_brport_group_maskhi + + @staticmethod + def _l2protocol_tunnel_set_lldp(ifla_brport_group_mask, ifla_brport_group_maskhi): + ifla_brport_group_mask |= 0x4000 + return ifla_brport_group_mask, ifla_brport_group_maskhi + + @staticmethod + def _l2protocol_tunnel_set_all(ifla_brport_group_mask, ifla_brport_group_maskhi): + # returns new values for ifla_brport_group_mask and ifla_brport_group_maskhi + return 0x1 | 0x4 | 0x4000, 0x1 | 0x2 + + @staticmethod + def _query_check_l2protocol_tunnel_stp(ifla_brport_group_mask, ifla_brport_group_maskhi): + return ifla_brport_group_mask & 0x1 + + @staticmethod + def _query_check_l2protocol_tunnel_cdp(ifla_brport_group_mask, ifla_brport_group_maskhi): + return ifla_brport_group_maskhi & 0x2 + + @staticmethod + def _query_check_l2protocol_tunnel_pvst(ifla_brport_group_mask, ifla_brport_group_maskhi): + return ifla_brport_group_maskhi & 0x1 + + @staticmethod + def _query_check_l2protocol_tunnel_lldp(ifla_brport_group_mask, ifla_brport_group_maskhi): + return ifla_brport_group_mask & 0x4000 + + @staticmethod + def _query_check_l2protocol_tunnel_lacp(ifla_brport_group_mask, ifla_brport_group_maskhi): + return ifla_brport_group_mask & 0x4 + + @staticmethod + def _query_check_l2protocol_tunnel_all(ifla_brport_group_mask, ifla_brport_group_maskhi): + return ifla_brport_group_mask == (0x1 | 0x4 | 0x4000) and ifla_brport_group_maskhi == (0x1 | 0x2) + + def syntax_check(self, ifaceobj, ifaceobj_getfunc): + retval = self.check_bridge_vlan_aware_port(ifaceobj, ifaceobj_getfunc) + if ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT: + if not self.check_bridge_port_vid_attrs(ifaceobj): + retval = False + c1 = self.syntax_check_vxlan_in_vlan_aware_br(ifaceobj, ifaceobj_getfunc) + c2 = self.syntax_check_bridge_allow_multiple_vlans(ifaceobj, ifaceobj_getfunc) + return retval and c1 #and c2 + + def syntax_check_bridge_allow_multiple_vlans(self, ifaceobj, ifaceobj_getfunc): + result = True + if not self.bridge_allow_multiple_vlans and ifaceobj.link_kind & ifaceLinkKind.BRIDGE and ifaceobj.lowerifaces: + vlan_id = None + for brport_name in ifaceobj.lowerifaces: + for obj in ifaceobj_getfunc(brport_name) or []: + if obj.link_kind & ifaceLinkKind.VLAN: + sub_intf_vlan_id = self._get_vlan_id(obj) + if vlan_id and vlan_id != sub_intf_vlan_id: + self.logger.error('%s: ignore %s: multiple vlans not allowed under bridge ' + '(sysctl net.bridge.bridge-allow-multiple-vlans not set)' + % (ifaceobj.name, brport_name)) + result = False + continue + vlan_id = sub_intf_vlan_id + return result + + def check_bridge_port_vid_attrs(self, ifaceobj): + if (ifaceobj.get_attr_value('bridge-access') and + (self.get_ifaceobj_bridge_vids_value(ifaceobj) or + ifaceobj.get_attr_value('bridge-pvid'))): + self.logger.warn('%s: bridge-access given, bridge-vids and bridge-pvid ' + 'will be ignored' % ifaceobj.name) + return False + return True + + def check_bridge_vlan_aware_port(self, ifaceobj, ifaceobj_getfunc): + if ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_VLAN_AWARE: + ports = self._get_bridge_port_list(ifaceobj) + if not ports: + return True + result = True + for port_name in ports: + port_obj_l = ifaceobj_getfunc(port_name) + if port_obj_l and port_obj_l[0].link_kind & ifaceLinkKind.VLAN: + self.logger.error('%s: %s: vlan sub-interface is not ' + 'supported in a vlan-aware bridge' + % (ifaceobj.name, port_name)) + result = False + if (port_obj_l and + port_obj_l[0].get_attr_value('bridge-arp-nd-suppress') and + self.arp_nd_suppress_only_on_vxlan and + not port_obj_l[0].link_kind & ifaceLinkKind.VXLAN): + self.log_error('\'bridge-arp-nd-suppress\' is not ' + 'supported on a non-vxlan port %s' + %port_obj_l[0].name) + result = False + return result + return True + + def _error_vxlan_in_vlan_aware_br(self, ifaceobj, bridgename): + self.log_error('`bridge-access` attribute is mandatory when vxlan ' + 'device (%s) is part of vlan aware bridge (%s)' + % (ifaceobj.name, bridgename), ifaceobj) + + def syntax_check_vxlan_in_vlan_aware_br(self, ifaceobj, ifaceobj_getfunc): + if not ifaceobj_getfunc: + return True + if (ifaceobj.link_kind & ifaceLinkKind.VXLAN + and ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT): + if ifaceobj.get_attr_value('bridge-access'): + return True + for iface in ifaceobj.upperifaces if ifaceobj.upperifaces else []: + ifaceobj_upper_list = ifaceobj_getfunc(iface) + if not ifaceobj_upper_list: + continue + ifaceobj_upper = ifaceobj_upper_list[0] + bridge_vids = self._get_bridge_vids(iface, ifaceobj_getfunc) + if ifaceobj_upper.link_privflags & ifaceLinkPrivFlags.BRIDGE_VLAN_AWARE: + vids = self.get_ifaceobj_bridge_vids_value(ifaceobj) + pvid = ifaceobj.get_attr_value_first('bridge-pvid') + if (not vids + or not pvid + or not self._compare_vids(bridge_vids, + vids, + pvid=pvid)): + self._error_vxlan_in_vlan_aware_br(ifaceobj, + ifaceobj_upper.name) + return False + return True + + @staticmethod + def _is_bridge(ifaceobj): + return (ifaceobj.link_kind & ifaceLinkKind.BRIDGE or + ifaceobj.get_attr_value_first('bridge-ports') or + ifaceobj.get_attr_value_first('bridge-vlan-aware')) + + def _get_ifaceobj_bridge_ports(self, ifaceobj, warn=True): + ports = ifaceobj.get_attr_value('bridge-ports') + if warn and ports and len(ports) > 1: + self.log_warn('%s: ignoring duplicate bridge-ports lines: %s' + %(ifaceobj.name, ports[1:])) + if ports: + if 'none' in ports[0]: + return [] + return ports[0] + return None + + def _is_bridge_port(self, ifaceobj): + if self.brctlcmd.is_bridge_port(ifaceobj.name): + return True + return False + + def get_dependent_ifacenames(self, ifaceobj, ifacenames_all=None): + if not self._is_bridge(ifaceobj): + return None + if ifaceobj.link_type != ifaceLinkType.LINK_NA: + ifaceobj.link_type = ifaceLinkType.LINK_MASTER + ifaceobj.link_kind |= ifaceLinkKind.BRIDGE + # for special vlan aware bridges, we need to add another bit + if utils.get_boolean_from_string(ifaceobj.get_attr_value_first('bridge-vlan-aware')): + ifaceobj.link_kind |= ifaceLinkKind.BRIDGE + ifaceobj.link_privflags |= ifaceLinkPrivFlags.BRIDGE_VLAN_AWARE + ifaceobj.role |= ifaceRole.MASTER + ifaceobj.dependency_type = ifaceDependencyType.MASTER_SLAVE + return self.parse_port_list(ifaceobj.name, + self._get_ifaceobj_bridge_ports(ifaceobj), + ifacenames_all) + + def get_dependent_ifacenames_running(self, ifaceobj): + self._init_command_handlers() + if not self.brctlcmd.bridge_exists(ifaceobj.name): + return None + return self.brctlcmd.get_bridge_ports(ifaceobj.name) + + def _get_bridge_port_list(self, ifaceobj): + + # port list is also available in the previously + # parsed dependent list. Use that if available, instead + # of parsing port expr again + port_list = ifaceobj.lowerifaces + if port_list: + return port_list + ports = self._get_ifaceobj_bridge_ports(ifaceobj) + if ports: + return self.parse_port_list(ifaceobj.name, ports) + else: + return None + + def _get_bridge_port_list_user_ordered(self, ifaceobj): + # When enslaving bridge-ports we need to return the exact user + # configured bridge ports list (bridge will inherit the mac of the + # first device. + ports = self._get_ifaceobj_bridge_ports(ifaceobj) + return self.parse_port_list(ifaceobj.name, ports) if ports else None + + def _process_bridge_waitport(self, ifaceobj, portlist): + waitport_value = ifaceobj.get_attr_value_first('bridge-waitport') + if not waitport_value: return + try: + waitportvals = re.split(r'[\s\t]\s*', waitport_value, 1) + if not waitportvals: return + try: + waitporttime = int(waitportvals[0]) + except: + self.log_warn('%s: invalid waitport value \'%s\'' + %(ifaceobj.name, waitportvals[0])) + return + if waitporttime <= 0: return + try: + waitportlist = self.parse_port_list(ifaceobj.name, + waitportvals[1]) + except IndexError, e: + # ignore error and use all bridge ports + waitportlist = portlist + pass + if not waitportlist: return + self.logger.info('%s: waiting for ports %s to exist ...' + %(ifaceobj.name, str(waitportlist))) + starttime = time.time() + while ((time.time() - starttime) < waitporttime): + if all([False for p in waitportlist + if not self.ipcmd.link_exists(p)]): + break; + time.sleep(1) + except Exception, e: + self.log_warn('%s: unable to process waitport: %s' + %(ifaceobj.name, str(e))) + + def _enable_disable_ipv6(self, port, enable='1'): + try: + self.write_file('/proc/sys/net/ipv6/conf/%s/disable_ipv6' % port, enable) + except Exception, e: + self.logger.info(str(e)) + + def handle_ipv6(self, ports, state, ifaceobj=None): + if (ifaceobj and + (ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_VXLAN) and + not ifaceobj.get_attr_value('address')): + self._enable_disable_ipv6(ifaceobj.name, state) + for p in ports: + self._enable_disable_ipv6(p, state) + + def _pretty_print_add_ports_error(self, errstr, bridgeifaceobj, bridgeports): + """ pretty print bridge port add errors. + since the commands are batched and the kernel only returns error + codes, this function tries to interpret some error codes + and prints clearer errors """ + + if re.search('RTNETLINK answers: Invalid argument', errstr): + # Cumulus Linux specific error checks + try: + if self.sysctl_get('net.bridge.bridge-allow-multiple-vlans') == '0': + vlanid = None + for bport in bridgeports: + currvlanid = self._get_vlan_id_from_ifacename(bport) + if vlanid: + if currvlanid != vlanid: + self.log_error('%s: ' %bridgeifaceobj.name + + 'net.bridge.bridge-allow-multiple-vlans not set, multiple vlans not allowed', bridgeifaceobj) + break + if currvlanid: + vlanid = currvlanid + except Exception as e: + errstr += '\n%s' % str(e) + self.log_error(bridgeifaceobj.name + ': ' + errstr, bridgeifaceobj) + + def _add_ports(self, ifaceobj, ifaceobj_getfunc): + bridgeports = self._get_bridge_port_list(ifaceobj) + runningbridgeports = [] + + self.ipcmd.batch_start() + self._process_bridge_waitport(ifaceobj, bridgeports) + self.ipcmd.batch_start() + # Delete active ports not in the new port list + if not ifupdownflags.flags.PERFMODE: + runningbridgeports = self.brctlcmd.get_bridge_ports(ifaceobj.name) + if runningbridgeports: + for bport in runningbridgeports: + if not bridgeports or bport not in bridgeports: + self.ipcmd.link_set(bport, 'nomaster') + # set admin DOWN on all removed ports + # that don't have config outside bridge + if not ifaceobj_getfunc(bport): + netlink.link_set_updown(bport, "down") + # enable ipv6 for ports that were removed + self.handle_ipv6([bport], '0') + else: + runningbridgeports = [] + if not bridgeports: + self.ipcmd.batch_commit() + return [] + err = 0 + ports = 0 + newbridgeports = Set(bridgeports).difference(Set(runningbridgeports)) + newly_enslaved_ports = [] + + newbridgeports_ordered = [] + for br_port in self._get_bridge_port_list_user_ordered(ifaceobj): + if br_port in newbridgeports: + newbridgeports_ordered.append(br_port) + + for bridgeport in newbridgeports_ordered: + try: + if (not ifupdownflags.flags.DRYRUN and + not self.ipcmd.link_exists(bridgeport)): + self.log_error('%s: bridge port %s does not exist' + %(ifaceobj.name, bridgeport), ifaceobj) + err += 1 + continue + hwaddress = self.ipcmd.link_get_hwaddress(bridgeport) + if not self._valid_ethaddr(hwaddress): + self.log_warn('%s: skipping port %s, ' %(ifaceobj.name, + bridgeport) + 'invalid ether addr %s' + %hwaddress) + continue + self.ipcmd.link_set(bridgeport, 'master', ifaceobj.name) + newly_enslaved_ports.append(bridgeport) + self.handle_ipv6([bridgeport], '1') + self.ipcmd.addr_flush(bridgeport) + ports += 1 + if ports == 250: + ports = 0 + self.ipcmd.batch_commit() + self.ipcmd.batch_start() + except Exception, e: + self.logger.error(str(e)) + pass + try: + self.ipcmd.batch_commit() + except Exception, e: + self._pretty_print_add_ports_error(str(e), ifaceobj, + bridgeports) + pass + + if err: + self.log_error('bridge configuration failed (missing ports)') + + return newly_enslaved_ports + + def _process_bridge_maxwait(self, ifaceobj, portlist): + maxwait = ifaceobj.get_attr_value_first('bridge-maxwait') + if not maxwait: return + try: + maxwait = int(maxwait) + except: + self.log_warn('%s: invalid maxwait value \'%s\'' %(ifaceobj.name, + maxwait)) + return + if not maxwait: return + self.logger.info('%s: waiting for ports to go to fowarding state ..' + %ifaceobj.name) + try: + starttime = time.time() + while ((time.time() - starttime) < maxwait): + if all([False for p in portlist + if self.read_file_oneline( + '/sys/class/net/%s/brif/%s/state' + %(ifaceobj.name, p)) != '3']): + break; + time.sleep(1) + except Exception, e: + self.log_warn('%s: unable to process maxwait: %s' + %(ifaceobj.name, str(e))) + + def _ints_to_ranges(self, ints): + for a, b in itertools.groupby(enumerate(ints), lambda (x, y): y - x): + b = list(b) + yield b[0][1], b[-1][1] + + def _ranges_to_ints(self, rangelist): + """ returns expanded list of integers given set of string ranges + example: ['1', '2-4', '6'] returns [1, 2, 3, 4, 6] + """ + result = [] + try: + for part in rangelist: + if '-' in part: + a, b = part.split('-') + a, b = int(a), int(b) + result.extend(range(a, b + 1)) + else: + a = int(part) + result.append(a) + except: + self.logger.warn('unable to parse vids \'%s\'' + %''.join(rangelist)) + pass + return result + + def _compress_into_ranges(self, vids_ints): + return ['%d' %start if start == end else '%d-%d' %(start, end) + for start, end in self._ints_to_ranges(vids_ints)] + + def _diff_vids(self, vids1_ints, vids2_ints): + return Set(vids2_ints).difference(vids1_ints), Set(vids1_ints).difference(vids2_ints) + + def _compare_vids(self, vids1, vids2, pvid=None): + """ Returns true if the vids are same else return false """ + + vids1_ints = self._ranges_to_ints(vids1) + vids2_ints = self._ranges_to_ints(vids2) + set_diff = Set(vids1_ints).symmetric_difference(vids2_ints) + if pvid and int(pvid) in set_diff: + set_diff.remove(int(pvid)) + if set_diff: + return False + else: + return True + + def _set_bridge_mcqv4src_compat(self, ifaceobj): + # + # Sets old style igmp querier + # + attrval = ifaceobj.get_attr_value_first('bridge-mcqv4src') + if attrval: + running_mcqv4src = {} + if not ifupdownflags.flags.PERFMODE: + running_mcqv4src = self.brctlcmd.bridge_get_mcqv4src(ifaceobj.name) + mcqs = {} + srclist = attrval.split() + for s in srclist: + k, v = s.split('=') + mcqs[k] = v + + k_to_del = Set(running_mcqv4src.keys()).difference(mcqs.keys()) + for v in k_to_del: + self.brctlcmd.bridge_del_mcqv4src(ifaceobj.name, v) + for v in mcqs.keys(): + self.brctlcmd.bridge_set_mcqv4src(ifaceobj.name, v, mcqs[v]) + elif not ifupdownflags.flags.PERFMODE: + running_mcqv4src = self.brctlcmd.bridge_get_mcqv4src(ifaceobj.name) + if running_mcqv4src: + for v in running_mcqv4src.keys(): + self.brctlcmd.bridge_del_mcqv4src(ifaceobj.name, v) + + def _get_running_vidinfo(self): + if self._running_vidinfo_valid: + return self._running_vidinfo + self._running_vidinfo = {} + + # Removed check for PERFMODE. Need the get in all cases + # including reboot, so that we can configure the pvid correctly. + self._running_vidinfo = self.ipcmd.bridge_port_vids_get_all_json() + self._running_vidinfo_valid = True + return self._running_vidinfo + + def _set_bridge_vidinfo_compat(self, ifaceobj): + # + # Supports old style vlan vid info format + # for compatibility + # + bridge_port_pvids = ifaceobj.get_attr_value_first('bridge-port-pvids') + bridge_port_vids = ifaceobj.get_attr_value_first('bridge-port-vids') + if not bridge_port_pvids and not bridge_port_vids: + return + + # Handle bridge vlan attrs + # Install pvids + if bridge_port_pvids: + portlist = self.parse_port_list(ifaceobj.name, bridge_port_pvids) + if not portlist: + self.log_warn('%s: could not parse \'%s %s\'' + %(ifaceobj.name, 'bridge-port-pvids', + bridge_port_pvids)) + return + for p in portlist: + try: + (port, pvid) = p.split('=') + pvid = int(pvid) + running_pvid = self._get_running_pvid(port) + if running_pvid: + if running_pvid == pvid: + continue + else: + self.ipcmd.bridge_port_pvid_del(port, running_pvid) + self.ipcmd.bridge_port_pvid_add(port, pvid) + except Exception, e: + self.log_warn('%s: failed to set pvid `%s` (%s)' + %(ifaceobj.name, p, str(e))) + + # install port vids + if bridge_port_vids: + portlist = self.parse_port_list(ifaceobj.name, bridge_port_vids) + if not portlist: + self.log_warn('%s: could not parse \'%s %s\'' %(ifaceobj.name, + 'bridge-port-vids', bridge_port_vids)) + return + for p in portlist: + try: + (port, val) = p.split('=') + vids = val.split(',') + vids_int = self._ranges_to_ints(vids) + running_vids = self.ipcmd.bridge_vlan_get_vids(port) + if running_vids: + (vids_to_del, vids_to_add) = \ + self._diff_vids(vids_int, running_vids) + if vids_to_del: + self.ipcmd.bridge_port_vids_del(port, + self._compress_into_ranges(vids_to_del)) + if vids_to_add: + self.ipcmd.bridge_port_vids_add(port, + self._compress_into_ranges(vids_to_add)) + else: + self.ipcmd.bridge_port_vids_add(port, vids_int) + except Exception, e: + self.log_warn('%s: failed to set vid `%s` (%s)' + %(ifaceobj.name, p, str(e))) + + def _is_running_stp_state_on(self, bridgename): + """ Returns True if running stp state is on, else False """ + + stp_state_file = '/sys/class/net/%s/bridge/stp_state' %bridgename + try: + running_stp_state = self.read_file_oneline(stp_state_file) + return running_stp_state and running_stp_state != '0' + except: + return False + + def _is_config_stp_state_on(self, ifaceobj): + """ Returns true if user specified stp state is on, else False """ + + stp_attr = ifaceobj.get_attr_value_first('bridge-stp') + if not stp_attr: + return self.default_stp_on + return utils.get_boolean_from_string(stp_attr) + + def get_bridge_mcsnoop_value(self, ifaceobj): + mcsnoop = ifaceobj.get_attr_value_first('bridge-mcsnoop') + if not mcsnoop and ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_VXLAN: + return self._vxlan_bridge_default_igmp_snooping + return mcsnoop + + def fill_ifla_info_data_with_ifla_br_attribute(self, + ifla_info_data, + link_just_created, + ifname, + nl_attr, + attr_name, + user_config): + try: + translate_func = self._ifla_br_attributes_translate_user_config_to_netlink_map.get(nl_attr) + + if not callable(translate_func): + return + + if not user_config: + user_config = policymanager.policymanager_api.get_iface_default( + module_name=self.__class__.__name__, + ifname=ifname, + attr=attr_name + ) + + old_cache_key = self._ifla_br_attributes_old_cache_key_map.get(nl_attr) + if old_cache_key and not link_just_created: + cached_value = self.brctlcmd.link_cache_get([ifname, 'linkinfo', old_cache_key]) + if not cached_value: + # the link already exists but we don't have any value + # cached for this attr, it probably means that the + # capability is not available on this system (i.e old kernel) + self.logger.debug('%s: ignoring %s %s: capability ' + 'probably not supported on this system' + % (ifname, attr_name, user_config)) + return + # we need to convert the cache value to "netlink" format + cached_value = translate_func(cached_value.lower()) + else: + cached_value = None + + if not user_config and not link_just_created and cached_value is not None: + # there is no user configuration for this attribute + # if the bridge existed before we need to check if + # this attribute needs to be reset to default value + default_value = self.get_attr_default_value(attr_name) + + if default_value: + # the attribute has a default value, we need to convert it to + # netlink format to compare it with the cache value + default_value_nl = translate_func(default_value) # default_value.lower() + + if default_value_nl != cached_value: + # the running value difers from the default value + # but the user didn't specify any config + # resetting attribute to default + ifla_info_data[nl_attr] = default_value_nl + self.logger.info('%s: reset %s to default: %s' % (ifname, attr_name, default_value)) + elif user_config: + user_config_nl = translate_func(user_config) # user_config.lower() + + if user_config_nl != cached_value: + ifla_info_data[nl_attr] = user_config_nl + + if cached_value is not None: + self.logger.info('%s: set %s %s (cache %s)' % (ifname, attr_name, user_config, cached_value)) + else: + self.logger.info('%s: set %s %s' % (ifname, attr_name, user_config)) + except Exception as e: + self.logger.warning('%s: %s: %s' % (ifname, attr_name, str(e))) + + def up_apply_bridge_settings(self, ifaceobj, link_just_created, bridge_vlan_aware): + ifla_info_data = dict() + ifname = ifaceobj.name + + self.logger.info('%s: apply bridge settings' % ifname) + + for attr_name, nl_attr in self._ifla_br_attributes_map: + self.fill_ifla_info_data_with_ifla_br_attribute( + ifla_info_data=ifla_info_data, + link_just_created=link_just_created, + ifname=ifname, + nl_attr=nl_attr, + attr_name=attr_name, + user_config=ifaceobj.get_attr_value_first(attr_name) + ) + + # bridge-mcsnoop + self.fill_ifla_info_data_with_ifla_br_attribute( + ifla_info_data=ifla_info_data, + link_just_created=link_just_created, + ifname=ifname, + nl_attr=Link.IFLA_BR_MCAST_SNOOPING, + attr_name='bridge-mcsnoop', + user_config=self.get_bridge_mcsnoop_value(ifaceobj) + ) + + # bridge-vlan-stats + if bridge_vlan_aware: + self.fill_ifla_info_data_with_ifla_br_attribute( + ifla_info_data=ifla_info_data, + link_just_created=link_just_created, + ifname=ifname, + nl_attr=Link.IFLA_BR_VLAN_STATS_ENABLED, + attr_name='bridge-vlan-stats', + user_config=ifaceobj.get_attr_value_first('bridge-vlan-stats') or self.default_vlan_stats + ) + + try: + if self._is_config_stp_state_on(ifaceobj): + if not self._is_running_stp_state_on(ifname): + ifla_info_data[Link.IFLA_BR_STP_STATE] = 1 + self.logger.info('%s: stp state reset, reapplying port settings' % ifname) + ifaceobj.module_flags[ifaceobj.name] = \ + ifaceobj.module_flags.setdefault(self.name, 0) | \ + bridgeFlags.PORT_PROCESSED_OVERRIDE + else: + # If stp not specified and running stp state on, set it to off + if self._is_running_stp_state_on(ifname): + self.logger.info('%s: bridge-stp not specified but running: turning stp off') + ifla_info_data[Link.IFLA_BR_STP_STATE] = 0 + except Exception as e: + self.logger.warning('%s: bridge stp: %s' % (ifname, str(e))) + + if ifla_info_data: + netlink.link_add_set(ifname=ifname, kind='bridge', ifla_info_data=ifla_info_data, link_exists=True) + + def _check_vids(self, ifaceobj, vids): + ret = True + for v in vids: + try: + if '-' in v: + va, vb = v.split('-') + va, vb = int(va), int(vb) + self._handle_reserved_vlan(va, ifaceobj.name, end=vb) + else: + va = int(v) + self._handle_reserved_vlan(va, ifaceobj.name) + except exceptions.ReservedVlanException as e: + raise e + except Exception: + self.logger.warn('%s: unable to parse vid \'%s\'' + %(ifaceobj.name, v)) + return ret + + def _get_running_pvid(self, ifacename): + pvid = 0 + + running_vidinfo = self._get_running_vidinfo() + for vinfo in running_vidinfo.get(ifacename, {}): + v = vinfo.get('vlan') + pvid = v if 'PVID' in vinfo.get('flags', []) else 0 + if pvid: + return pvid + return pvid + + def _get_running_vids_n_pvid_str(self, ifacename): + vids = [] + pvid = None + + (vids, pvid) = self.ipcmd.bridge_vlan_get_vids_n_pvid(ifacename) + + if vids: + ret_vids = self._compress_into_ranges(vids) + else: + ret_vids = None + + if pvid: + ret_pvid = '%s' %pvid + else: + ret_pvid = None + return (ret_vids, ret_pvid) + + def _apply_bridge_vids_and_pvid(self, bportifaceobj, vids, pvid, + isbridge): + """ This method is a combination of methods _apply_bridge_vids and + _apply_bridge_port_pvids above. A combined function is + found necessary to do the deletes first and the adds later + because kernel does honor vid info flags during deletes. + + """ + if not isbridge and bportifaceobj.link_kind & ifaceLinkKind.VXLAN: + if not vids or not pvid or len(vids) > 1 or vids[0] != pvid: + self._error_vxlan_in_vlan_aware_br(bportifaceobj, + bportifaceobj.upperifaces[0]) + return + + vids_int = self._ranges_to_ints(vids) + try: + pvid_int = int(pvid) if pvid else 0 + except Exception: + self.logger.warn('%s: unable to parse pvid \'%s\'' + %(bportifaceobj.name, pvid)) + pvid_int = 0 + pass + + vids_to_del = [] + vids_to_add = vids_int + pvid_to_del = None + pvid_to_add = pvid_int + + try: + if not self._check_vids(bportifaceobj, vids): + return + + (running_vids, running_pvid) = self.ipcmd.bridge_vlan_get_vids_n_pvid( + bportifaceobj.name) + + if not running_vids and not running_pvid: + # There cannot be a no running pvid. + # It might just not be in our cache: + # this can happen if at the time we were + # creating the bridge vlan cache, the port + # was not part of the bridge. And we need + # to make sure both vids and pvid is not in + # the cache, to declare that our cache may + # be stale. + running_pvid = 1 + running_vids = [1] + + if running_vids: + (vids_to_del, vids_to_add) = \ + self._diff_vids(vids_to_add, running_vids) + + if running_pvid: + if running_pvid != pvid_int and running_pvid != 0: + pvid_to_del = running_pvid + + if (pvid_to_del and (pvid_to_del in vids_int) and + (pvid_to_del not in vids_to_add)): + # kernel deletes dont take into account + # bridge vid flags and its possible that + # the pvid deletes we do end up deleting + # the vids. Be proactive and add the pvid + # to the vid add list if it is in the vids + # and not already part of vids_to_add. + # This helps with a small corner case: + # - running + # pvid 100 + # vid 101 102 + # - new change is going to move the state to + # pvid 101 + # vid 100 102 + vids_to_add.add(pvid_to_del) + except exceptions.ReservedVlanException as e: + raise e + except Exception, e: + self.log_error('%s: failed to process vids/pvids' + %bportifaceobj.name + ' vids = %s' %str(vids) + + 'pvid = %s ' %pvid + '(%s)' %str(e), + bportifaceobj, raise_error=False) + try: + if vids_to_del: + if pvid_to_add in vids_to_del: + vids_to_del.remove(pvid_to_add) + self.ipcmd.bridge_vids_del(bportifaceobj.name, + self._compress_into_ranges( + vids_to_del), isbridge) + except Exception, e: + self.log_warn('%s: failed to del vid `%s` (%s)' + %(bportifaceobj.name, str(vids_to_del), str(e))) + + try: + if pvid_to_del: + self.ipcmd.bridge_port_pvid_del(bportifaceobj.name, + pvid_to_del) + except Exception, e: + self.log_warn('%s: failed to del pvid `%s` (%s)' + %(bportifaceobj.name, pvid_to_del, str(e))) + + try: + if vids_to_add: + self.ipcmd.bridge_vids_add(bportifaceobj.name, + self._compress_into_ranges( + vids_to_add), isbridge) + except Exception, e: + self.log_error('%s: failed to set vid `%s` (%s)' + %(bportifaceobj.name, str(vids_to_add), + str(e)), bportifaceobj, raise_error=False) + + try: + if pvid_to_add and pvid_to_add != running_pvid: + self.ipcmd.bridge_port_pvid_add(bportifaceobj.name, + pvid_to_add) + except Exception, e: + self.log_error('%s: failed to set pvid `%s` (%s)' + %(bportifaceobj.name, pvid_to_add, str(e)), + bportifaceobj) + + def _apply_bridge_vlan_aware_port_settings_all(self, bportifaceobj, + bridge_vids=None, + bridge_pvid=None): + vids = None + pvids = None + vids_final = [] + pvid_final = None + bport_access = bportifaceobj.get_attr_value_first('bridge-access') + if bport_access: + vids = re.split(r'[\s\t]\s*', bport_access) + pvids = vids + allow_untagged = 'yes' + self.check_bridge_port_vid_attrs(bportifaceobj) + else: + allow_untagged = bportifaceobj.get_attr_value_first('bridge-allow-untagged') or 'yes' + + bport_vids = self.get_ifaceobj_bridge_vids_value(bportifaceobj) + if bport_vids: + vids = re.split(r'[\s\t,]\s*', bport_vids) + + bport_pvids = bportifaceobj.get_attr_value_first('bridge-pvid') + if bport_pvids: + pvids = re.split(r'[\s\t]\s*', bport_pvids) + + if vids: + vids_final = vids + elif bridge_vids: + vids_final = bridge_vids + + if allow_untagged == 'yes': + if pvids: + pvid_final = pvids[0] + elif bridge_pvid: + pvid_final = bridge_pvid + else: + pvid_final = '1' + else: + pvid_final = None + + self._apply_bridge_vids_and_pvid(bportifaceobj, vids_final, + pvid_final, False) + + def _apply_bridge_port_settings_all(self, ifaceobj, ifaceobj_getfunc, bridge_vlan_aware): + err = False + + if (ifaceobj.get_attr_value_first('bridge-port-vids') and + ifaceobj.get_attr_value_first('bridge-port-pvids')): + # Old style bridge port vid info + # skip new style setting on ports + return + self.logger.info('%s: applying bridge configuration ' + %ifaceobj.name + 'specific to ports') + + bridge_vids = self.get_ifaceobj_bridge_vids_value(ifaceobj) + if bridge_vids: + bridge_vids = re.split(r'[\s\t,]\s*', bridge_vids) + else: + bridge_vids = None + + bridge_pvid = ifaceobj.get_attr_value_first('bridge-pvid') + if bridge_pvid: + bridge_pvid = re.split(r'[\s\t]\s*', bridge_pvid)[0] + else: + bridge_pvid = None + + if (ifaceobj.module_flags.get(self.name, 0x0) & + bridgeFlags.PORT_PROCESSED_OVERRIDE): + port_processed_override = True + else: + port_processed_override = False + + bridgeports = self._get_bridge_port_list(ifaceobj) + if not bridgeports: + self.logger.debug('%s: cannot find bridgeports' %ifaceobj.name) + return + self.ipcmd.batch_start() + for bport in bridgeports: + # Use the brctlcmd bulk set method: first build a dictionary + # and then call set + if not self.ipcmd.bridge_port_exists(ifaceobj.name, bport): + self.logger.info('%s: skipping bridge config' %ifaceobj.name + + ' for port %s (missing port)' %bport) + continue + self.logger.info('%s: processing bridge config for port %s' + %(ifaceobj.name, bport)) + bportifaceobjlist = ifaceobj_getfunc(bport) + if not bportifaceobjlist: + continue + for bportifaceobj in bportifaceobjlist: + # Dont process bridge port if it already has been processed + # and there is no override on port_processed + if (not port_processed_override and + (bportifaceobj.module_flags.get(self.name,0x0) & + bridgeFlags.PORT_PROCESSED)): + continue + try: + # Add attributes specific to the vlan aware bridge + if bridge_vlan_aware: + self._apply_bridge_vlan_aware_port_settings_all( + bportifaceobj, bridge_vids, bridge_pvid) + elif self.warn_on_untagged_bridge_absence: + self._check_untagged_bridge(ifaceobj.name, bportifaceobj, ifaceobj_getfunc) + except exceptions.ReservedVlanException as e: + raise e + except Exception, e: + err = True + self.logger.warn('%s: %s' %(ifaceobj.name, str(e))) + pass + self.ipcmd.bridge_batch_commit() + if err: + raise Exception('%s: errors applying port settings' %ifaceobj.name) + + def _check_untagged_bridge(self, bridgename, bridgeportifaceobj, ifaceobj_getfunc): + if bridgeportifaceobj.link_kind & ifaceLinkKind.VLAN: + lower_ifaceobj_list = ifaceobj_getfunc(bridgeportifaceobj.lowerifaces[0]) + if lower_ifaceobj_list and lower_ifaceobj_list[0] and \ + not lower_ifaceobj_list[0].link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT: + self.logger.warn('%s: untagged bridge not found. Please configure a bridge with untagged bridge ports to avoid Spanning Tree Interoperability issue.' % bridgename) + self.warn_on_untagged_bridge_absence = False + + def bridge_port_get_bridge_name(self, ifaceobj): + bridgename = self.ipcmd.bridge_port_get_bridge_name(ifaceobj.name) + if not bridgename: + # bridge port is not enslaved to a bridge we need to find + # the bridge in it's upper ifaces then enslave it + for u in ifaceobj.upperifaces: + if self.ipcmd.is_bridge(u): + return True, u + return False, None + # return should_enslave port, bridgename + return False, bridgename + + def up_bridge_port_vlan_aware_bridge(self, ifaceobj, ifaceobj_getfunc, bridge_name, should_enslave_port): + if should_enslave_port: + netlink.link_set_master(ifaceobj.name, bridge_name) + self.handle_ipv6([ifaceobj.name], '1') + + bridge_vids = self._get_bridge_vids(bridge_name, ifaceobj_getfunc) + bridge_pvid = self._get_bridge_pvid(bridge_name, ifaceobj_getfunc) + try: + self._apply_bridge_vlan_aware_port_settings_all(ifaceobj, bridge_vids, bridge_pvid) + except Exception as e: + self.log_error('%s: %s' % (ifaceobj.name, str(e)), ifaceobj) + return + + def up_bridge_port(self, ifaceobj, ifaceobj_getfunc): + should_enslave_port, bridge_name = self.bridge_port_get_bridge_name(ifaceobj) + + if not bridge_name: + # bridge doesn't exist + return + + vlan_aware_bridge = self.ipcmd.bridge_is_vlan_aware(bridge_name) + if vlan_aware_bridge: + self.up_bridge_port_vlan_aware_bridge(ifaceobj, + ifaceobj_getfunc, + bridge_name, + should_enslave_port) + + bridge_ifaceobj = ifaceobj_getfunc(bridge_name)[0] + + self.up_apply_brports_attributes(target_ports=[ifaceobj.name], + ifaceobj=bridge_ifaceobj, + ifaceobj_getfunc=ifaceobj_getfunc, + bridge_vlan_aware=vlan_aware_bridge) + + ifaceobj.module_flags[self.name] = ifaceobj.module_flags.setdefault(self.name, 0) | bridgeFlags.PORT_PROCESSED + + def up_check_bridge_vlan_aware(self, ifaceobj, ifaceobj_getfunc, link_exists): + if ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_VLAN_AWARE: + if not self.check_bridge_vlan_aware_port(ifaceobj, ifaceobj_getfunc): + return False + if link_exists: + ifaceobj.module_flags[self.name] = ifaceobj.module_flags.setdefault(self.name, 0) | bridgeFlags.PORT_PROCESSED_OVERRIDE + return True + return False + + @staticmethod + def parse_interface_list_value(user_config): + config = dict() + for entry in user_config.split(): + ifname, value = entry.split('=') + config[ifname] = value + return config + + def sync_bridge_learning_to_vxlan_brport(self, bridge_name, bridge_vlan_aware, brport_ifaceobj, + brport_name, brport_ifla_info_slave_data, brport_learning): + """ + brport_ifaceobj.link_kind & ifaceLinkKind.VXLAN + and + brport_ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT + + Checks are not performed in this function and must be verified + before. This is done this way to avoid calling this method on + non vlan & bridge port interfaces thus wasting a bit less time + """ + + kind = None + ifla_info_data = {} + + brport_vxlan_learning_config = brport_ifaceobj.get_attr_value_first('vxlan-learning') + # if the user explicitly defined vxlan-learning we need to honor his config + # and not sync vxlan-learning with bridge-learning + + brport_vxlan_learning = self.ipcmd.get_vxlandev_learning(brport_name) + + # if BRIDGE_LEARNING is in the desired configuration + # and differs from the running vxlan configuration + if brport_learning is not None and brport_learning != brport_vxlan_learning and not brport_vxlan_learning_config: + kind = 'vxlan' + ifla_info_data = {Link.IFLA_VXLAN_LEARNING: brport_learning} + self.logger.info('%s: %s: vxlan learning and bridge learning out of sync: set %s' + % (bridge_name, brport_name, brport_learning)) + + elif brport_learning is None and bridge_vlan_aware: + # is bridge-learning is not configured but the bridge is vlan-aware + + running_value = self.ipcmd.get_brport_learning_bool(brport_name) + default_value = utils.get_boolean_from_string(self.get_mod_subattr('bridge-learning', 'default')) + + if default_value != running_value: + brport_ifla_info_slave_data[Link.IFLA_BRPORT_LEARNING] = default_value + + if not brport_vxlan_learning_config: + kind = 'vxlan' + ifla_info_data = {Link.IFLA_VXLAN_LEARNING: default_value} + self.logger.info('%s: %s: reset brport learning to %s and sync vxlan learning' + % (bridge_name, brport_name, default_value)) + + # if kind and ifla_info_data are set they will be added to the + # netlink request on the VXLAN brport, to sync IFLA_VXLAN_LEARNING + return kind, ifla_info_data + + def check_vxlan_brport_arp_suppress(self, ifaceobj, bridge_vlan_aware, brport_ifaceobj, brport_name, user_config): + if user_config: + if self.arp_nd_suppress_only_on_vxlan and not brport_ifaceobj.link_kind & ifaceLinkKind.VXLAN: + self.logger.warning('%s: %s: \'bridge-arp-nd-suppress\' ' + 'is not supported on a non-vxlan port' + % (ifaceobj.name, brport_name)) + raise Exception() + elif (bridge_vlan_aware and + (not self.arp_nd_suppress_only_on_vxlan or + (self.arp_nd_suppress_only_on_vxlan and + brport_ifaceobj.link_kind & ifaceLinkKind.VXLAN))): + return self.get_mod_subattr('bridge-arp-nd-suppress', 'default') + return None + + def up_apply_brports_attributes(self, ifaceobj, ifaceobj_getfunc, bridge_vlan_aware, target_ports=[], newly_enslaved_ports=[]): + ifname = ifaceobj.name + + try: + brports_ifla_info_slave_data = dict() + brport_ifaceobj_dict = dict() + + running_brports = self.brctlcmd.get_bridge_ports(ifname) + + if target_ports: + new_targets = [] + for brport_name in target_ports: + if brport_name not in running_brports: + self.logger.info('%s: not enslaved to bridge %s: ignored for now' % (brport_name, ifname)) + else: + new_targets.append(brport_name) + running_brports = new_targets + + self.logger.info('%s: applying bridge port configuration: %s' % (ifname, running_brports)) + + # If target_ports is specified we want to configure only this + # sub-list of port we need to check if these ports are already + # enslaved, if not they will be ignored. + # If target_ports is not populated we will apply the brport + # attributes on all running brport. + + for port in running_brports: + brport_list = ifaceobj_getfunc(port) + if brport_list: + brport_ifaceobj_dict[port] = brport_list[0] + brports_ifla_info_slave_data[port] = dict() + + bridge_ports_learning = {} + + # we iterate through all IFLA_BRPORT supported attributes + for attr_name, nl_attr in self._ifla_brport_attributes_map: + br_config = ifaceobj.get_attr_value_first(attr_name) + translate_func = self._ifla_brport_attributes_translate_user_config_to_netlink_map.get(nl_attr) + + if not translate_func: + # if no translation function is found, + # we ignore this attribute and continue + continue + + if not br_config: + # user didn't specify any value for this attribute + # looking at policy overrides + br_config = policymanager.policymanager_api.get_iface_default( + module_name=self.__class__.__name__, + ifname=ifname, + attr=attr_name + ) + + if br_config: + #if bridge_vlan_aware: + # self.logger.info('%s: is a vlan-aware bridge, "%s %s" ' + # 'should be configured under the ports' + # % (ifname, attr_name, br_config)) + + # convert the and value to subdict + # brport_name: { attr: value } + # example: + # bridge-portprios swp1=5 swp2=32 + # swp1: { bridge-portprios: 5 } swp2: { bridge-portprios: 32} + if '=' in br_config: + try: + br_config = self.parse_interface_list_value(br_config) + except: + self.log_error('error while parsing \'%s %s\'' % (attr_name, br_config)) + continue + + for brport_ifaceobj in brport_ifaceobj_dict.values(): + brport_config = brport_ifaceobj.get_attr_value_first(attr_name) + brport_name = brport_ifaceobj.name + + if not ifupdownflags.flags.PERFMODE and brport_name not in newly_enslaved_ports: + # if the port has just been enslaved, info_slave_data is not cached yet + cached_value = self.ipcmd.cache_get_info_slave([brport_name, 'info_slave_data', nl_attr]) + else: + cached_value = None + + if not brport_config: + # if a brport attribute was specified under the bridge and not under the port + # we assign the bridge value to the port. If an attribute is both defined under + # the bridge and the brport we keep the value of the port and ignore the br val. + if type(br_config) == dict: + # if the attribute value was in the format interface-list-value swp1=XX swp2=YY + # br_config is a dictionary, example: + # bridge-portprios swp1=5 swp2=32 = {swp1: 5, swp2: 32} + brport_config = br_config.get(brport_name) + else: + brport_config = br_config + + if not brport_config: + brport_config = policymanager.policymanager_api.get_iface_default( + module_name=self.__class__.__name__, + ifname=brport_name, + attr=attr_name + ) + + user_config = brport_config + + # attribute specific work + # This shouldn't be here but we don't really have a choice otherwise this + # will require too much code duplication and will make the code very complex + if nl_attr == Link.IFLA_BRPORT_ARP_SUPPRESS: + try: + arp_suppress = self.check_vxlan_brport_arp_suppress(ifaceobj, + bridge_vlan_aware, + brport_ifaceobj, + brport_name, + user_config) + if arp_suppress: + user_config = arp_suppress + except: + continue + elif nl_attr == Link.IFLA_BRPORT_GROUP_FWD_MASK: + # special handking for group_fwd_mask because Cisco proprietary + # protocol needs to be set via a private netlink attribute + self.ifla_brport_group_fwd_mask(ifname, brport_name, + brports_ifla_info_slave_data, + user_config, cached_value) + continue + + #if brport_config: + # if not bridge_vlan_aware: + # self.logger.info('%s: %s: is not a vlan-aware bridge, "%s %s" ' + # 'should be configured under the bridge' + # % (ifname, brport_name, + # attr_name, brport_config)) + + if user_config: + user_config_nl = translate_func(user_config) + # check config value against running value + if user_config_nl != cached_value: + brports_ifla_info_slave_data[brport_name][nl_attr] = user_config_nl + self.logger.info('%s: %s: set %s %s' % (ifname, brport_name, attr_name, user_config)) + self.logger.debug('(cache %s)' % cached_value) + + if nl_attr == Link.IFLA_BRPORT_LEARNING: + # for vxlan-learning sync purposes we need to save the user config for each brports. + # The dictionary 'brports_ifla_info_slave_data' might not contain any value for + # IFLA_BRPORT_LEARNING if the user value is already configured and running + # nevertheless we still need to check if the vxlan-learning is rightly synced with + # the brport since it might go out of sync for X and Y reasons. + bridge_ports_learning[brport_name] = user_config_nl + + elif cached_value is not None: + # no config found, do we need to reset to default? + default = self.get_attr_default_value(attr_name) + if default: + default_netlink = translate_func(default) + + if (nl_attr == Link.IFLA_BRPORT_LEARNING + and not ifupdownflags.flags.PERFMODE + and brport_name not in newly_enslaved_ports): + try: + if self.ipcmd.get_brport_peer_link(brport_name): + if default_netlink != cached_value: + self.logger.debug('%s: %s: bridge port peerlink: ignoring bridge-learning' + % (ifname, brport_name)) + continue + bridge_ports_learning[brport_name] = default_netlink + except Exception as e: + self.logger.debug('%s: %s: peerlink check: %s' % (ifname, brport_name, str(e))) + + if default_netlink != cached_value: + self.logger.info('%s: %s: %s: no configuration detected, resetting to default %s' + % (ifname, brport_name, attr_name, default)) + self.logger.debug('(cache %s)' % cached_value) + brports_ifla_info_slave_data[brport_name][nl_attr] = default_netlink + + # applying bridge port configuration via netlink + for brport_name, brport_ifla_info_slave_data in brports_ifla_info_slave_data.items(): + + brport_ifaceobj = brport_ifaceobj_dict.get(brport_name) + if (brport_ifaceobj + and brport_ifaceobj.link_kind & ifaceLinkKind.VXLAN + and brport_ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT): + # if the brport is a VXLAN, we might need to sync the VXLAN learning with the brport_learning val + # we use the same netlink request, by specfying kind=vxlan and ifla_info_data={vxlan_learning=0/1} + kind, ifla_info_data = self.sync_bridge_learning_to_vxlan_brport(ifaceobj.name, + bridge_vlan_aware, + brport_ifaceobj, + brport_name, + brport_ifla_info_slave_data, + bridge_ports_learning.get(brport_name)) + else: + kind = None + ifla_info_data = {} + + if brport_ifla_info_slave_data or ifla_info_data: + try: + netlink.link_add_set(ifname=brport_name, + kind=kind, + ifla_info_data=ifla_info_data, + slave_kind='bridge', + ifla_info_slave_data=brport_ifla_info_slave_data) + except Exception as e: + self.logger.warning('%s: %s: %s' % (ifname, brport_name, str(e))) + + self._set_bridge_vidinfo_compat(ifaceobj) + self._set_bridge_mcqv4src_compat(ifaceobj) + self._process_bridge_maxwait(ifaceobj, self._get_bridge_port_list(ifaceobj)) + + except Exception as e: + self.log_error(str(e), ifaceobj) + + def ifla_brport_group_fwd_mask(self, ifname, brport_name, brports_ifla_info_slave_data, user_config, cached_ifla_brport_group_fwd_mask): + """ + Support for IFLA_BRPORT_GROUP_FWD_MASK and IFLA_BRPORT_GROUP_FWD_MASKHI + Since this is the only ifupdown2 attribute dealing with more than 1 netlink + field we need to have special handling for that. + """ + ifla_brport_group_fwd_mask = 0 + ifla_brport_group_fwd_maskhi = 0 + if user_config: + for group in re.split(',|\s*', user_config): + if not group: + continue + + callback = self.l2protocol_tunnel_callback.get(group) + + if not callable(callback): + self.logger.warning('%s: %s: bridge-l2protocol-tunnel ignoring invalid parameter \'%s\'' % (ifname, brport_name, group)) + else: + ifla_brport_group_fwd_mask, ifla_brport_group_fwd_maskhi = callback(ifla_brport_group_fwd_mask, ifla_brport_group_fwd_maskhi) + + # cached_ifla_brport_group_fwd_mask is given as parameter because it was already pulled out from the cache in the functio above + cached_ifla_brport_group_fwd_maskhi = self.ipcmd.cache_get_info_slave([brport_name, 'info_slave_data', Link.IFLA_BRPORT_GROUP_FWD_MASKHI]) + + log_mask_change = True + # if user specify bridge-l2protocol-tunnel stp cdp + # we need to set both MASK and MASKHI but we only want to log once + + if cached_ifla_brport_group_fwd_mask is None: + cached_ifla_brport_group_fwd_mask = 0 + if cached_ifla_brport_group_fwd_maskhi is None: + cached_ifla_brport_group_fwd_maskhi = 0 + + # if the cache value is None it means that the kernel doesn't support this attribute + # or that the cache is stale, we dumped this intf before it was enslaved in the bridge + + if ifla_brport_group_fwd_mask != cached_ifla_brport_group_fwd_mask: + if log_mask_change: + self.logger.info('%s: %s: set bridge-l2protocol-tunnel %s' % (ifname, brport_name, user_config)) + self.logger.debug('(cache %s)' % cached_ifla_brport_group_fwd_mask) + log_mask_change = False + brports_ifla_info_slave_data[brport_name][Link.IFLA_BRPORT_GROUP_FWD_MASK] = ifla_brport_group_fwd_mask + + if ifla_brport_group_fwd_maskhi != cached_ifla_brport_group_fwd_maskhi: + if log_mask_change: + self.logger.info('%s: %s: set bridge-l2protocol-tunnel %s' % (ifname, brport_name, user_config)) + self.logger.debug('(cache %s)' % cached_ifla_brport_group_fwd_maskhi) + brports_ifla_info_slave_data[brport_name][Link.IFLA_BRPORT_GROUP_FWD_MASKHI] = ifla_brport_group_fwd_maskhi + + def up_bridge(self, ifaceobj, ifaceobj_getfunc): + ifname = ifaceobj.name + + if ifupdownflags.flags.PERFMODE: + link_just_created = True + link_exists = False + else: + link_exists = self.ipcmd.link_exists(ifaceobj.name) + link_just_created = not link_exists + + if not link_exists: + netlink.link_add_bridge(ifname) + else: + self.logger.info('%s: bridge already exists' % ifname) + + bridge_vlan_aware = self.up_check_bridge_vlan_aware(ifaceobj, ifaceobj_getfunc, not link_just_created) + + self.up_apply_bridge_settings(ifaceobj, link_just_created, bridge_vlan_aware) + + try: + newly_enslaved_ports = self._add_ports(ifaceobj, ifaceobj_getfunc) + self.up_apply_brports_attributes(ifaceobj, ifaceobj_getfunc, bridge_vlan_aware, + newly_enslaved_ports=newly_enslaved_ports) + except Exception as e: + self.logger.warning('%s: apply bridge ports settings: %s' % (ifname, str(e))) + + running_ports = '' + try: + running_ports = self.brctlcmd.get_bridge_ports(ifaceobj.name) + if not running_ports: + return + self.handle_ipv6([], '1', ifaceobj=ifaceobj) + self._apply_bridge_port_settings_all(ifaceobj, + ifaceobj_getfunc=ifaceobj_getfunc, + bridge_vlan_aware=bridge_vlan_aware) + except exceptions.ReservedVlanException as e: + raise e + except Exception as e: + self.logger.warning('%s: apply bridge settings: %s' % (ifname, str(e))) + finally: + if ifaceobj.link_type != ifaceLinkType.LINK_NA: + for p in running_ports: + if (ifaceobj_getfunc(p)[0].link_privflags & + ifaceLinkPrivFlags.KEEP_LINK_DOWN): + netlink.link_set_updown(p, "down") + continue + try: + netlink.link_set_updown(p, "up") + except Exception, e: + self.logger.debug('%s: %s: link set up (%s)' + % (ifaceobj.name, p, str(e))) + pass + + try: + self._up_bridge_mac(ifaceobj, ifaceobj_getfunc) + except Exception as e: + self.logger.warning('%s: setting bridge mac address: %s' % (ifaceobj.name, str(e))) + + def _get_bridge_mac(self, ifaceobj, ifname, ifaceobj_getfunc): + if self.bridge_mac_iface and self.bridge_mac_iface[0] and self.bridge_mac_iface[1]: + return self.bridge_mac_iface + + if self.bridge_mac_iface_list: + self.logger.debug('bridge mac iface list: %s' % self.bridge_mac_iface_list) + + for bridge_mac_intf in self.bridge_mac_iface_list: + ifaceobj_list = ifaceobj_getfunc(bridge_mac_intf) + iface_mac = None + + if ifaceobj_list: + for obj in ifaceobj_list: + iface_user_configured_hwaddress = utils.strip_hwaddress(obj.get_attr_value_first('hwaddress')) + # if user did configured 'hwaddress' we need to use this value instead of the cached value. + if iface_user_configured_hwaddress: + iface_mac = iface_user_configured_hwaddress + + if not iface_mac and not self.ipcmd.link_exists(bridge_mac_intf): + continue + + if not iface_mac: + iface_mac = self.ipcmd.cache_get('link', [bridge_mac_intf, 'hwaddress']) + # if hwaddress attribute is not configured we use the running mac addr + + self.bridge_mac_iface = (bridge_mac_intf, iface_mac) + return self.bridge_mac_iface + elif self.bridge_set_static_mac_from_port: + # no policy was provided, we need to get the first physdev or bond ports + # and use its hwaddress to set the bridge mac + for port in self._get_bridge_port_list_user_ordered(ifaceobj) or []: + # iterate through the bridge-port list + for port_obj in ifaceobj_getfunc(port) or []: + # check if the port is a physdev (link_kind is null) or a bon + if port_obj.link_kind != ifaceLinkKind.VXLAN: + iface_user_configured_hwaddress = utils.strip_hwaddress(port_obj.get_attr_value_first('hwaddress')) + # if user did configured 'hwaddress' we need to use this value instead of the cached value. + if iface_user_configured_hwaddress: + iface_mac = iface_user_configured_hwaddress.lower() + # we need to "normalize" the user provided MAC so it can match with + # what we have in the cache (data retrieved via a netlink dump by + # nlmanager). nlmanager return all macs in lower-case + else: + iface_mac = self.ipcmd.link_get_hwaddress(port) + + if iface_mac: + self.bridge_mac_iface = (port, iface_mac) + return self.bridge_mac_iface + + return None, None + + def _add_bridge_mac_to_fdb(self, ifaceobj, bridge_mac): + if not ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_VLAN_AWARE and bridge_mac and ifaceobj.get_attr_value('address'): + self.ipcmd.bridge_fdb_add(ifaceobj.name, bridge_mac, vlan=None, bridge=True, remote=None) + + def _up_bridge_mac(self, ifaceobj, ifaceobj_getfunc): + """ + We have a day one bridge mac changing problem with changing ports + (basically bridge mac changes when the port it inherited the mac from + gets de-enslaved). + + We have discussed this problem many times before and tabled it. + The issue has aggravated with vxlan bridge ports having auto-generated + random macs...which change on every reboot. + + ifupdown2 extract from policy files an iface to select a mac from and + configure it automatically. + """ + if ifaceobj.get_attr_value('hwaddress'): + # if the user configured a static hwaddress + # there is no need to assign one + return + + ifname = ifaceobj.name + mac_intf, bridge_mac = self._get_bridge_mac(ifaceobj, ifname, ifaceobj_getfunc) + self.logger.debug("%s: _get_bridge_mac returned (%s, %s)" + %(ifname, mac_intf, bridge_mac)) + + if bridge_mac: + # if an interface is configured with the following attribute: + # hwaddress 08:00:27:42:42:4 + # the cache_check won't match because nlmanager return "08:00:27:42:42:04" + # from the kernel. The only way to counter that is to convert all mac to int + # and compare the ints, it will increase perfs and be safer. + cached_value = self.ipcmd.cache_get('link', [ifname, 'hwaddress']) + self.logger.debug('%s: cached hwaddress value: %s' % (ifname, cached_value)) + if cached_value and cached_value == bridge_mac: + # the bridge mac is already set to the bridge_mac_intf's mac + return + + self.logger.info('%s: setting bridge mac to port %s mac' % (ifname, mac_intf)) + try: + self.ipcmd.link_set(ifname, 'address', value=bridge_mac, force=True) + except Exception as e: + self.logger.info('%s: %s' % (ifname, str(e))) + # log info this error because the user didn't explicitly configured this + else: + self._add_bridge_mac_to_fdb(ifaceobj, self.ipcmd.link_get_hwaddress(ifname)) + + def _up(self, ifaceobj, ifaceobj_getfunc=None): + if ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT: + self.up_bridge_port(ifaceobj, ifaceobj_getfunc) + + elif ifaceobj.link_kind & ifaceLinkKind.BRIDGE: + self.up_bridge(ifaceobj, ifaceobj_getfunc) + + def _down(self, ifaceobj, ifaceobj_getfunc=None): + if not self._is_bridge(ifaceobj): + return + ifname = ifaceobj.name + if not self.ipcmd.link_exists(ifname): + return + try: + running_ports = self.brctlcmd.get_bridge_ports(ifname) + if running_ports: + self.handle_ipv6(running_ports, '0') + if ifaceobj.link_type != ifaceLinkType.LINK_NA: + map(lambda p: netlink.link_set_updown(p, 'down'), running_ports) + except Exception as e: + self.log_error('%s: %s' % (ifaceobj.name, str(e)), ifaceobj) + try: + netlink.link_del(ifname) + except Exception as e: + ifaceobj.set_status(ifaceStatus.ERROR) + self.logger.error(str(e)) + # netlink exception already contains the ifname + + def _query_running_vidinfo_compat(self, ifaceobjrunning, ports): + running_attrs = {} + if ports: + running_bridge_port_vids = '' + for p in ports: + try: + running_vids = self._get_runing_vids(p) + if running_vids: + running_bridge_port_vids += ' %s=%s' %(p, + ','.join(running_vids)) + except Exception: + pass + running_attrs['bridge-port-vids'] = running_bridge_port_vids + + running_bridge_port_pvid = '' + for p in ports: + try: + running_pvid = self._get_runing_pvid(p) + if running_pvid: + running_bridge_port_pvid += ' %s=%s' %(p, + running_pvid) + except Exception: + pass + running_attrs['bridge-port-pvids'] = running_bridge_port_pvid + + running_bridge_vids = self.ipcmd.bridge_vlan_get_vids(ifaceobjrunning.name) + if running_bridge_vids: + running_attrs['bridge-vids'] = ','.join(self._compress_into_ranges(running_bridge_vids)) + return running_attrs + + def _query_running_vidinfo(self, ifaceobjrunning, ifaceobj_getfunc, + bridgeports=None): + running_attrs = {} + + # 'bridge-vids' under the bridge is all about 'vids' on the port. + # so query the ports + running_bridgeport_vids = [] + running_bridgeport_pvids = [] + for bport in bridgeports: + (vids, pvid) = self._get_running_vids_n_pvid_str(bport) + if vids: + running_bridgeport_vids.append(' '.join(vids)) + if pvid: + running_bridgeport_pvids.append(pvid) + + bridge_vids = None + if running_bridgeport_vids: + (vidval, freq) = Counter(running_bridgeport_vids).most_common()[0] + if freq == len(bridgeports): + running_attrs['bridge-vids'] = vidval + bridge_vids = vidval.split() + + bridge_pvid = None + if running_bridgeport_pvids: + (vidval, freq) = Counter(running_bridgeport_pvids).most_common()[0] + if freq == len(bridgeports) and vidval != '1': + running_attrs['bridge-pvid'] = vidval + bridge_pvid = vidval.split()[0] + + # Go through all bridge ports and find their vids + for bport in bridgeports: + bportifaceobj = ifaceobj_getfunc(bport) + if not bportifaceobj: + continue + bport_vids = [] + bport_pvid = None + (vids, pvid) = self._get_running_vids_n_pvid_str(bport) + if vids and vids != bridge_vids: + bport_vids = vids + if pvid and pvid != bridge_pvid: + bport_pvid = pvid + if bport_vids and bport_pvid in bport_vids: + bport_vids.remove(bport_pvid) + if (not bport_vids and bport_pvid and bport_pvid != '1'): + bportifaceobj[0].replace_config('bridge-access', bport_pvid) + bportifaceobj[0].delete_config('bridge-pvid') + bportifaceobj[0].delete_config('bridge-vids') + else: + if bport_pvid and bport_pvid != '1': + bportifaceobj[0].replace_config('bridge-pvid', bport_pvid) + else: + # delete any stale bridge-vids under ports + bportifaceobj[0].delete_config('bridge-pvid') + if bport_vids: + bportifaceobj[0].replace_config('bridge-vids', + ' '.join(bport_vids)) + else: + # delete any stale bridge-vids under ports + bportifaceobj[0].delete_config('bridge-vids') + return running_attrs + + def _query_running_mcqv4src(self, ifaceobjrunning): + running_mcqv4src = self.brctlcmd.bridge_get_mcqv4src(ifaceobjrunning.name) + mcqs = ['%s=%s' %(v, i) for v, i in running_mcqv4src.items()] + mcqs.sort() + mcq = ' '.join(mcqs) + return mcq + + def _query_running_attrs(self, ifaceobjrunning, ifaceobj_getfunc, + bridge_vlan_aware=False): + bridgeattrdict = {} + userspace_stp = 0 + ports = None + skip_kernel_stp_attrs = 0 + + try: + if self.systcl_get_net_bridge_stp_user_space() == '1': + userspace_stp = 1 + except Exception as e: + self.logger.info('%s: %s' % (ifaceobjrunning.name, str(e))) + + tmpbridgeattrdict = self.brctlcmd.get_bridge_attrs(ifaceobjrunning.name) + if not tmpbridgeattrdict: + self.logger.warn('%s: unable to get bridge attrs' + %ifaceobjrunning.name) + return bridgeattrdict + + # Fill bridge_ports and bridge stp attributes first + ports = tmpbridgeattrdict.get('ports') + if ports: + bridgeattrdict['bridge-ports'] = [' '.join(ports.keys())] + stp = tmpbridgeattrdict.get('stp', 'no') + if stp != self.get_mod_subattr('bridge-stp', 'default'): + bridgeattrdict['bridge-stp'] = [stp] + + if stp == 'yes' and userspace_stp: + skip_kernel_stp_attrs = 1 + + vlan_stats = utils.get_onff_from_onezero( + tmpbridgeattrdict.get('vlan-stats', None)) + if (vlan_stats and + vlan_stats != self.get_mod_subattr('bridge-vlan-stats', 'default')): + bridgeattrdict['bridge-vlan-stats'] = [vlan_stats] + + bool2str = {'0': 'no', '1': 'yes'} + # pick all other attributes + for k,v in tmpbridgeattrdict.items(): + if not v: + continue + if k == 'ports' or k == 'stp': + continue + + if skip_kernel_stp_attrs and k[:2] != 'mc': + # only include igmp attributes if kernel stp is off + continue + attrname = 'bridge-' + k + mod_default = self.get_mod_subattr(attrname, 'default') + if v != mod_default: + # convert '0|1' running values to 'no|yes' + if v in bool2str.keys() and bool2str[v] == mod_default: + continue + bridgeattrdict[attrname] = [v] + + if bridge_vlan_aware: + if not ports: + ports = {} + bridgevidinfo = self._query_running_vidinfo(ifaceobjrunning, + ifaceobj_getfunc, + ports.keys()) + else: + bridgevidinfo = self._query_running_vidinfo_compat(ifaceobjrunning, + ports) + if bridgevidinfo: + bridgeattrdict.update({k : [v] for k, v in bridgevidinfo.items() + if v}) + + mcq = self._query_running_mcqv4src(ifaceobjrunning) + if mcq: + bridgeattrdict['bridge-mcqv4src'] = [mcq] + + if skip_kernel_stp_attrs: + return bridgeattrdict + + # Do this only for vlan-UNAWARE-bridge + if ports and not bridge_vlan_aware: + portconfig = {'bridge-pathcosts' : '', + 'bridge-portprios' : '', + 'bridge-learning' : '', + 'bridge-unicast-flood' : '', + 'bridge-multicast-flood' : '', + 'bridge-arp-nd-suppress' : '', + } + for p, v in ports.items(): + v = self.brctlcmd.bridge_get_pathcost(ifaceobjrunning.name, p) + if v and v != self.get_mod_subattr('bridge-pathcosts', + 'default'): + portconfig['bridge-pathcosts'] += ' %s=%s' %(p, v) + + v = self.brctlcmd.bridge_get_portprio(ifaceobjrunning.name, p) + if v and v != self.get_mod_subattr('bridge-portprios', + 'default'): + portconfig['bridge-portprios'] += ' %s=%s' %(p, v) + + v = utils.get_onff_from_onezero( + self.brctlcmd.get_bridgeport_attr(ifaceobjrunning.name, + p, 'learning')) + if (v and + v != self.get_mod_subattr('bridge-learning', 'default')): + portconfig['bridge-learning'] += ' %s=%s' %(p, v) + + v = utils.get_onff_from_onezero( + self.brctlcmd.get_bridgeport_attr(ifaceobjrunning.name, + p, 'unicast-flood')) + if (v and + v != self.get_mod_subattr('bridge-unicast-flood', + 'default')): + portconfig['bridge-unicast-flood'] += ' %s=%s' %(p, v) + + v = utils.get_onff_from_onezero( + self.brctlcmd.get_bridgeport_attr(ifaceobjrunning.name, + p, 'multicast-flood')) + if (v and + v != self.get_mod_subattr('bridge-multicast-flood', + 'default')): + portconfig['bridge-multicast-flood'] += ' %s=%s' %(p, v) + + v = utils.get_onff_from_onezero( + self.brctlcmd.get_bridgeport_attr(ifaceobjrunning.name, + p, 'arp-nd-suppress')) + if (v and + v != self.get_mod_subattr('bridge-arp-nd-suppress', + 'default')): + portconfig['bridge-arp-nd-suppress'] += ' %s=%s' %(p, v) + + bridgeattrdict.update({k : [v] for k, v in portconfig.items() + if v}) + + return bridgeattrdict + + def _query_check_mcqv4src(self, ifaceobj, ifaceobjcurr): + running_mcqs = self._query_running_mcqv4src(ifaceobj) + attrval = ifaceobj.get_attr_value_first('bridge-mcqv4src') + if attrval: + mcqs = attrval.split() + mcqs.sort() + mcqsout = ' '.join(mcqs) + ifaceobjcurr.update_config_with_status('bridge-mcqv4src', + running_mcqs, 1 if running_mcqs != mcqsout else 0) + + def _query_check_bridge_vidinfo(self, ifaceobj, ifaceobjcurr): + err = 0 + attrval = ifaceobj.get_attr_value_first('bridge-port-vids') + if attrval: + running_bridge_port_vids = '' + portlist = self.parse_port_list(ifaceobj.name, attrval) + if not portlist: + self.log_warn('%s: could not parse \'bridge-port-vids %s\'' + %(ifaceobj.name, attrval)) + return + err = 0 + for p in portlist: + try: + (port, val) = p.split('=') + vids = val.split(',') + running_vids = self.ipcmd.bridge_vlan_get_vids(port) + if running_vids: + if not self._compare_vids(vids, running_vids): + err += 1 + running_bridge_port_vids += ' %s=%s' %(port, + ','.join(running_vids)) + else: + running_bridge_port_vids += ' %s' %p + else: + err += 1 + except Exception, e: + self.log_warn('%s: failure checking vid %s (%s)' + %(ifaceobj.name, p, str(e))) + if err: + ifaceobjcurr.update_config_with_status('bridge-port-vids', + running_bridge_port_vids, 1) + else: + ifaceobjcurr.update_config_with_status('bridge-port-vids', + attrval, 0) + + attrval = ifaceobj.get_attr_value_first('bridge-port-pvids') + if attrval: + portlist = self.parse_port_list(ifaceobj.name, attrval) + if not portlist: + self.log_warn('%s: could not parse \'bridge-port-pvids %s\'' + %(ifaceobj.name, attrval)) + return + running_bridge_port_pvids = '' + err = 0 + for p in portlist: + try: + (port, pvid) = p.split('=') + running_pvid = self.ipcmd.bridge_vlan_get_vids(port) + if running_pvid and running_pvid == pvid: + running_bridge_port_pvids += ' %s' %p + else: + err += 1 + running_bridge_port_pvids += ' %s=%s' %(port, + running_pvid) + except Exception, e: + self.log_warn('%s: failure checking pvid %s (%s)' + %(ifaceobj.name, pvid, str(e))) + if err: + ifaceobjcurr.update_config_with_status('bridge-port-pvids', + running_bridge_port_pvids, 1) + else: + ifaceobjcurr.update_config_with_status('bridge-port-pvids', + running_bridge_port_pvids, 0) + + vids = self.get_ifaceobj_bridge_vids(ifaceobj) + if vids[1]: + ifaceobjcurr.update_config_with_status(vids[0], vids[1], -1) + + def _query_check_snooping_wdefault(self, ifaceobj): + if (ifupdownflags.flags.WITHDEFAULTS + and not self._vxlan_bridge_default_igmp_snooping + and ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_VXLAN): + ifaceobj.replace_config('bridge-mcsnoop', 'no') + + def _query_check_bridge(self, ifaceobj, ifaceobjcurr, + ifaceobj_getfunc=None): + if not self._is_bridge(ifaceobj): + return + if not self.brctlcmd.bridge_exists(ifaceobj.name): + self.logger.info('%s: bridge: does not exist' %(ifaceobj.name)) + return + + self._query_check_snooping_wdefault(ifaceobj) + + ifaceattrs = self.dict_key_subset(ifaceobj.config, + self.get_mod_attrs()) + #Add default attributes if --with-defaults is set + if ifupdownflags.flags.WITHDEFAULTS and 'bridge-stp' not in ifaceattrs: + ifaceattrs.append('bridge-stp') + if not ifaceattrs: + return + try: + runningattrs = self.brctlcmd.get_bridge_attrs(ifaceobj.name) + if not runningattrs: + self.logger.debug('%s: bridge: unable to get bridge attrs' + %ifaceobj.name) + runningattrs = {} + except Exception, e: + self.logger.warn(str(e)) + runningattrs = {} + + self._query_check_support_yesno_attrs(runningattrs, ifaceobj) + + filterattrs = ['bridge-vids', 'bridge-trunk', 'bridge-port-vids', + 'bridge-port-pvids'] + + diff = Set(ifaceattrs).difference(filterattrs) + + if 'bridge-l2protocol-tunnel' in diff: + diff.remove('bridge-l2protocol-tunnel') + # bridge-l2protocol-tunnel requires separate handling + + for k in diff: + # get the corresponding ifaceobj attr + v = ifaceobj.get_attr_value_first(k) + if not v: + if ifupdownflags.flags.WITHDEFAULTS and k == 'bridge-stp': + v = 'on' if self.default_stp_on else 'off' + else: + continue + rv = runningattrs.get(k[7:]) + if k == 'bridge-mcqv4src': + continue + if k == 'bridge-maxwait' or k == 'bridge-waitport': + ifaceobjcurr.update_config_with_status(k, v, 0) + continue + if k == 'bridge-vlan-aware': + rv = self.ipcmd.bridge_is_vlan_aware(ifaceobj.name) + if (rv and v == 'yes') or (not rv and v == 'no'): + ifaceobjcurr.update_config_with_status('bridge-vlan-aware', + v, 0) + else: + ifaceobjcurr.update_config_with_status('bridge-vlan-aware', + v, 1) + elif k == 'bridge-stp': + # special case stp compare because it may + # contain more than one valid values + stp_on_vals = ['on', 'yes'] + stp_off_vals = ['off', 'no'] + if ((v in stp_on_vals and rv in stp_on_vals) or + (v in stp_off_vals and rv in stp_off_vals)): + ifaceobjcurr.update_config_with_status('bridge-stp', + rv, 0) + else: + ifaceobjcurr.update_config_with_status('bridge-stp', + rv, 1) + elif k == 'bridge-ports': + # special case ports because it can contain regex or glob + running_port_list = rv.keys() if rv else [] + bridge_port_list = self._get_bridge_port_list(ifaceobj) + if not running_port_list and not bridge_port_list: + continue + portliststatus = 1 + if running_port_list and bridge_port_list: + difference = set(running_port_list + ).symmetric_difference(bridge_port_list) + if not difference: + portliststatus = 0 + try: + port_list = self._get_ifaceobj_bridge_ports(ifaceobj, + warn=False).split() + # we want to display the same bridge-ports list as provided + # in the interfaces file but if this list contains regexes or + # globs, for now, we won't try to change it. + if 'regex' in port_list or 'glob' in port_list: + port_list = running_port_list + else: + ordered = [] + for i in range(0, len(port_list)): + if port_list[i] in running_port_list: + ordered.append(port_list[i]) + port_list = ordered + except: + port_list = running_port_list + ifaceobjcurr.update_config_with_status('bridge-ports', + (' '.join(port_list) + if port_list else ''), + portliststatus) + elif k in ['bridge-pathcosts', + 'bridge-portprios', + 'bridge-portmcrouter', + 'bridge-portmcfl', + 'bridge-learning', + 'bridge-unicast-flood', + 'bridge-multicast-flood', + 'bridge-arp-nd-suppress', + ]: + if k == 'bridge-arp-nd-suppress': + brctlcmdattrname = k[7:] + else: + brctlcmdattrname = k[7:].rstrip('s') + # for port attributes, the attributes are in a list + # = + status = 0 + currstr = '' + vlist = self.parse_port_list(ifaceobj.name, v) + if not vlist: + continue + for vlistitem in vlist: + try: + (p, v) = vlistitem.split('=') + if k in ['bridge-learning', + 'bridge-unicast-flood', + 'bridge-multicast-flood', + 'bridge-arp-nd-suppress', + ]: + currv = utils.get_onoff_bool( + self.brctlcmd.get_bridgeport_attr( + ifaceobj.name, p, + brctlcmdattrname)) + else: + currv = self.brctlcmd.get_bridgeport_attr( + ifaceobj.name, p, + brctlcmdattrname) + if currv: + currstr += ' %s=%s' %(p, currv) + else: + currstr += ' %s=%s' %(p, 'None') + + if k == 'bridge-portmcrouter': + if self._ifla_brport_multicast_router_dict_to_int.get(v) != int(currv): + status = 1 + elif currv != v: + status = 1 + except Exception, e: + self.log_warn(str(e)) + pass + ifaceobjcurr.update_config_with_status(k, currstr, status) + elif k == 'bridge-vlan-stats' or k == 'bridge-mcstats': + rv = utils.get_onff_from_onezero(rv) + if v != rv: + ifaceobjcurr.update_config_with_status(k, rv, 1) + else: + ifaceobjcurr.update_config_with_status(k, rv, 0) + elif not rv: + if k == 'bridge-pvid' or k == 'bridge-vids' or k == 'bridge-trunk' or k == 'bridge-allow-untagged': + # bridge-pvid and bridge-vids on a bridge does + # not correspond directly to a running config + # on the bridge. They correspond to default + # values for the bridge ports. And they are + # already checked against running config of the + # bridge port and reported against a bridge port. + # So, ignore these attributes under the bridge. + # Use '2' for ignore today. XXX: '2' will be + # mapped to a defined value in subsequent patches. + ifaceobjcurr.update_config_with_status(k, v, 2) + else: + ifaceobjcurr.update_config_with_status(k, 'notfound', 1) + continue + elif v.upper() != rv.upper(): + ifaceobjcurr.update_config_with_status(k, rv, 1) + else: + ifaceobjcurr.update_config_with_status(k, rv, 0) + + self._query_check_bridge_vidinfo(ifaceobj, ifaceobjcurr) + + self._query_check_mcqv4src(ifaceobj, ifaceobjcurr) + self._query_check_l2protocol_tunnel_on_bridge(ifaceobj, ifaceobjcurr, runningattrs) + + def get_ifaceobj_bridge_vids(self, ifaceobj): + vids = ('bridge-vids', ifaceobj.get_attr_value_first('bridge-vids')) + if not vids[1]: + vids = ('bridge-trunk', ifaceobj.get_attr_value_first('bridge-trunk')) + return vids + + def get_ifaceobj_bridge_vids_value(self, ifaceobj): + return self.get_ifaceobj_bridge_vids(ifaceobj)[1] + + def _get_bridge_vids(self, bridgename, ifaceobj_getfunc): + ifaceobjs = ifaceobj_getfunc(bridgename) + for ifaceobj in ifaceobjs: + vids = self.get_ifaceobj_bridge_vids_value(ifaceobj) + if vids: return re.split(r'[\s\t,]\s*', vids) + return None + + def _get_bridge_pvid(self, bridgename, ifaceobj_getfunc): + ifaceobjs = ifaceobj_getfunc(bridgename) + pvid = None + for ifaceobj in ifaceobjs: + pvid = ifaceobj.get_attr_value_first('bridge-pvid') + if pvid: + break + return pvid + + def _get_bridge_name(self, ifaceobj): + return self.ipcmd.bridge_port_get_bridge_name(ifaceobj.name) + + def _query_check_bridge_port_vidinfo(self, ifaceobj, ifaceobjcurr, + ifaceobj_getfunc, bridgename): + attr_name = 'bridge-access' + vid = ifaceobj.get_attr_value_first(attr_name) + if vid: + (running_vids, running_pvid) = self._get_running_vids_n_pvid_str( + ifaceobj.name) + if (not running_pvid or running_pvid != vid or + (running_vids and running_vids[0] != vid)): + ifaceobjcurr.update_config_with_status(attr_name, + running_pvid, 1) + else: + ifaceobjcurr.update_config_with_status(attr_name, vid, 0) + return + + (running_vids, running_pvid) = self._get_running_vids_n_pvid_str( + ifaceobj.name) + attr_name = 'bridge-pvid' + pvid = ifaceobj.get_attr_value_first('bridge-pvid') + if pvid: + if running_pvid and running_pvid == pvid: + ifaceobjcurr.update_config_with_status(attr_name, + running_pvid, 0) + else: + ifaceobjcurr.update_config_with_status(attr_name, + running_pvid, 1) + elif (not (ifaceobj.flags & iface.HAS_SIBLINGS) or + ((ifaceobj.flags & iface.HAS_SIBLINGS) and + (ifaceobj.flags & iface.OLDEST_SIBLING))): + # if the interface has multiple iface sections, + # we check the below only for the oldest sibling + # or the last iface section + pvid = self._get_bridge_pvid(bridgename, ifaceobj_getfunc) + if pvid: + if not running_pvid or running_pvid != pvid: + ifaceobjcurr.status = ifaceStatus.ERROR + ifaceobjcurr.status_str = 'bridge pvid error' + elif not running_pvid or running_pvid != '1': + ifaceobjcurr.status = ifaceStatus.ERROR + ifaceobjcurr.status_str = 'bridge pvid error' + + attr_name, vids = self.get_ifaceobj_bridge_vids(ifaceobj) + if vids: + vids = re.split(r'[\s\t]\s*', vids) + if not running_vids or not self._compare_vids(vids, running_vids, + running_pvid): + ifaceobjcurr.update_config_with_status(attr_name, + ' '.join(running_vids), 1) + else: + ifaceobjcurr.update_config_with_status(attr_name, + ' '.join(vids), 0) + elif (not (ifaceobj.flags & iface.HAS_SIBLINGS) or + ((ifaceobj.flags & iface.HAS_SIBLINGS) and + (ifaceobj.flags & iface.OLDEST_SIBLING))): + # if the interface has multiple iface sections, + # we check the below only for the oldest sibling + # or the last iface section + + # check if it matches the bridge vids + bridge_vids = self._get_bridge_vids(bridgename, ifaceobj_getfunc) + if (bridge_vids and (not running_vids or + not self._compare_vids(bridge_vids, running_vids, running_pvid))): + ifaceobjcurr.status = ifaceStatus.ERROR + ifaceobjcurr.status_str = 'bridge vid error' + + def _query_check_bridge_port(self, ifaceobj, ifaceobjcurr, + ifaceobj_getfunc): + if not self._is_bridge_port(ifaceobj): + # Mark all bridge attributes as failed + ifaceobjcurr.check_n_update_config_with_status_many(ifaceobj, + ['bridge-vids', 'bridge-trunk', 'bridge-pvid', 'bridge-access', + 'bridge-pathcosts', 'bridge-portprios', + 'bridge-portmcrouter', + 'bridge-learning', + 'bridge-portmcfl', 'bridge-unicast-flood', + 'bridge-multicast-flood', + 'bridge-arp-nd-suppress', 'bridge-l2protocol-tunnel' + ], 1) + return + bridgename = self._get_bridge_name(ifaceobj) + if not bridgename: + self.logger.warn('%s: unable to determine bridge name' + %ifaceobj.name) + return + + if self.ipcmd.bridge_is_vlan_aware(bridgename): + self._query_check_bridge_port_vidinfo(ifaceobj, ifaceobjcurr, + ifaceobj_getfunc, + bridgename) + for attr, dstattr in {'bridge-pathcosts' : 'pathcost', + 'bridge-portprios' : 'portprio', + 'bridge-portmcrouter' : 'portmcrouter', + 'bridge-portmcfl' : 'portmcfl', + 'bridge-learning' : 'learning', + 'bridge-unicast-flood' : 'unicast-flood', + 'bridge-multicast-flood' : 'multicast-flood', + 'bridge-arp-nd-suppress' : 'arp-nd-suppress', + }.items(): + attrval = ifaceobj.get_attr_value_first(attr) + if not attrval: + continue + + try: + running_attrval = self.brctlcmd.get_bridgeport_attr( + bridgename, ifaceobj.name, dstattr) + + if dstattr == 'portmcfl': + if not utils.is_binary_bool(attrval) and running_attrval: + running_attrval = utils.get_yesno_boolean( + utils.get_boolean_from_string(running_attrval)) + elif dstattr == 'portmcrouter': + if self._ifla_brport_multicast_router_dict_to_int.get(attrval) == int(running_attrval): + ifaceobjcurr.update_config_with_status(attr, attrval, 0) + else: + ifaceobjcurr.update_config_with_status(attr, attrval, 1) + continue + elif dstattr in ['learning', + 'unicast-flood', + 'multicast-flood', + 'arp-nd-suppress', + ]: + if not utils.is_binary_bool(attrval) and running_attrval: + running_attrval = utils.get_onff_from_onezero( + running_attrval) + + if running_attrval != attrval: + ifaceobjcurr.update_config_with_status(attr, + running_attrval, 1) + else: + ifaceobjcurr.update_config_with_status(attr, + running_attrval, 0) + except Exception, e: + self.log_warn('%s: %s' %(ifaceobj.name, str(e))) + + self._query_check_l2protocol_tunnel_on_port(ifaceobj, ifaceobjcurr) + + def _query_check_l2protocol_tunnel_on_port(self, ifaceobj, ifaceobjcurr): + user_config_l2protocol_tunnel = ifaceobj.get_attr_value_first('bridge-l2protocol-tunnel') + + if user_config_l2protocol_tunnel: + result = 0 + try: + self._query_check_l2protocol_tunnel(ifaceobj.name, user_config_l2protocol_tunnel) + except Exception as e: + self.logger.debug('query: %s: %s' % (ifaceobj.name, str(e))) + result = 1 + ifaceobjcurr.update_config_with_status('bridge-l2protocol-tunnel', user_config_l2protocol_tunnel, result) + + def _query_check_l2protocol_tunnel_on_bridge(self, ifaceobj, ifaceobjcurr, bridge_running_attrs): + """ + In case the bridge-l2protocol-tunnel is specified under the bridge and not the brport + We need to make sure that all ports comply with the mask given under the bridge + """ + user_config_l2protocol_tunnel = ifaceobj.get_attr_value_first('bridge-l2protocol-tunnel') + + if user_config_l2protocol_tunnel: + if '=' in user_config_l2protocol_tunnel: + try: + config_per_port_dict = self.parse_interface_list_value(user_config_l2protocol_tunnel) + brport_list = config_per_port_dict.keys() + except: + ifaceobjcurr.update_config_with_status('bridge-l2protocol-tunnel', user_config_l2protocol_tunnel, 1) + return + else: + config_per_port_dict = {} + brport_list = bridge_running_attrs.get('ports', {}).keys() + result = 1 + try: + for brport_name in brport_list: + self._query_check_l2protocol_tunnel( + brport_name, + config_per_port_dict.get(brport_name) if config_per_port_dict else user_config_l2protocol_tunnel + ) + result = 0 + except Exception as e: + self.logger.debug('query: %s: %s' % (ifaceobj.name, str(e))) + result = 1 + ifaceobjcurr.update_config_with_status('bridge-l2protocol-tunnel', user_config_l2protocol_tunnel, result) + + def _query_check_l2protocol_tunnel(self, brport_name, user_config_l2protocol_tunnel): + cached_ifla_brport_group_maskhi = self.ipcmd.cache_get_info_slave([brport_name, 'info_slave_data', Link.IFLA_BRPORT_GROUP_FWD_MASKHI]) + cached_ifla_brport_group_mask = self.ipcmd.cache_get_info_slave([brport_name, 'info_slave_data', Link.IFLA_BRPORT_GROUP_FWD_MASK]) + + for protocol in re.split(',|\s*', user_config_l2protocol_tunnel): + callback = self.query_check_l2protocol_tunnel_callback.get(protocol) + + if callable(callback): + if not callback(cached_ifla_brport_group_mask, cached_ifla_brport_group_maskhi): + raise Exception('%s: bridge-l2protocol-tunnel: protocol \'%s\' not present (cached value: %d | %d)' + % (brport_name, protocol, cached_ifla_brport_group_mask, cached_ifla_brport_group_maskhi)) + + def _query_running_bridge_l2protocol_tunnel(self, brport_name, brport_ifaceobj=None, bridge_ifaceobj=None): + cached_ifla_brport_group_maskhi = self.ipcmd.cache_get_info_slave([brport_name, 'info_slave_data', Link.IFLA_BRPORT_GROUP_FWD_MASKHI]) + cached_ifla_brport_group_mask = self.ipcmd.cache_get_info_slave([brport_name, 'info_slave_data', Link.IFLA_BRPORT_GROUP_FWD_MASK]) + running_protocols = [] + for protocol_name, callback in self.query_check_l2protocol_tunnel_callback.items(): + if protocol_name == 'all' and callback(cached_ifla_brport_group_mask, cached_ifla_brport_group_maskhi): + running_protocols = self.query_check_l2protocol_tunnel_callback.keys() + running_protocols.remove('all') + break + elif callback(cached_ifla_brport_group_mask, cached_ifla_brport_group_maskhi): + running_protocols.append(protocol_name) + if running_protocols: + if brport_ifaceobj: + brport_ifaceobj.update_config('bridge-l2protocol-tunnel', ' '.join(running_protocols)) + elif bridge_ifaceobj: + current_config = bridge_ifaceobj.get_attr_value_first('bridge-l2protocol-tunnel') + + if current_config: + bridge_ifaceobj.replace_config('bridge-l2protocol-tunnel', '%s %s=%s' % (current_config, brport_name, ','.join(running_protocols))) + else: + bridge_ifaceobj.replace_config('bridge-l2protocol-tunnel', '%s=%s' % (brport_name, ','.join(running_protocols))) + + def _query_check(self, ifaceobj, ifaceobjcurr, ifaceobj_getfunc=None): + if self._is_bridge(ifaceobj): + self._query_check_bridge(ifaceobj, ifaceobjcurr) + else: + self._query_check_bridge_port(ifaceobj, ifaceobjcurr, + ifaceobj_getfunc) + + def _query_running_bridge(self, ifaceobjrunning, ifaceobj_getfunc): + if self.ipcmd.bridge_is_vlan_aware(ifaceobjrunning.name): + ifaceobjrunning.update_config('bridge-vlan-aware', 'yes') + ifaceobjrunning.update_config_dict(self._query_running_attrs( + ifaceobjrunning, + ifaceobj_getfunc, + bridge_vlan_aware=True)) + else: + ifaceobjrunning.update_config_dict(self._query_running_attrs( + ifaceobjrunning, None)) + + def _query_running_bridge_port_attrs(self, ifaceobjrunning, bridgename): + if self.systcl_get_net_bridge_stp_user_space() == '1': + return + + v = self.brctlcmd.bridge_get_pathcost(bridgename, ifaceobjrunning.name) + if v and v != self.get_mod_subattr('bridge-pathcosts', 'default'): + ifaceobjrunning.update_config('bridge-pathcosts', v) + + v = self.brctlcmd.bridge_get_pathcost(bridgename, ifaceobjrunning.name) + if v and v != self.get_mod_subattr('bridge-portprios', 'default'): + ifaceobjrunning.update_config('bridge-portprios', v) + + def _query_running_bridge_port(self, ifaceobjrunning, + ifaceobj_getfunc=None): + + bridgename = self.ipcmd.bridge_port_get_bridge_name( + ifaceobjrunning.name) + bridge_vids = None + bridge_pvid = None + if not bridgename: + self.logger.warn('%s: unable to find bridgename' + %ifaceobjrunning.name) + return + + if not self.ipcmd.bridge_is_vlan_aware(bridgename): + try: + self._query_running_bridge_l2protocol_tunnel(ifaceobjrunning.name, bridge_ifaceobj=ifaceobj_getfunc(bridgename)[0]) + except Exception as e: + self.logger.debug('%s: q_query_running_bridge_l2protocol_tunnel: %s' % (ifaceobjrunning.name, str(e))) + return + + self._query_running_bridge_l2protocol_tunnel(ifaceobjrunning.name, brport_ifaceobj=ifaceobjrunning) + + (bridge_port_vids, bridge_port_pvid) = self._get_running_vids_n_pvid_str( + ifaceobjrunning.name) + if bridge_port_vids and bridge_port_pvid in bridge_port_vids: + bridge_port_vids.remove(bridge_port_pvid) + + bridgeifaceobjlist = ifaceobj_getfunc(bridgename) + if bridgeifaceobjlist: + bridge_vids = bridgeifaceobjlist[0].get_attr_value('bridge-vids') + bridge_pvid = bridgeifaceobjlist[0].get_attr_value_first('bridge-pvid') + + if not bridge_port_vids and bridge_port_pvid: + # must be an access port + if bridge_port_pvid != '1': + ifaceobjrunning.update_config('bridge-access', + bridge_port_pvid) + else: + if bridge_port_vids: + if (not bridge_vids or bridge_port_vids != bridge_vids): + ifaceobjrunning.update_config('bridge-vids', + ' '.join(bridge_port_vids)) + if bridge_port_pvid and bridge_port_pvid != '1': + if (not bridge_pvid or (bridge_port_pvid != bridge_pvid)): + ifaceobjrunning.update_config('bridge-pvid', + bridge_port_pvid) + + v = utils.get_onff_from_onezero( + self.brctlcmd.get_bridgeport_attr(bridgename, + ifaceobjrunning.name, + 'learning')) + if v and v != self.get_mod_subattr('bridge-learning', 'default'): + ifaceobjrunning.update_config('bridge-learning', v) + + v = utils.get_onff_from_onezero( + self.brctlcmd.get_bridgeport_attr(bridgename, + ifaceobjrunning.name, + 'unicast-flood')) + if v and v != self.get_mod_subattr('bridge-unicast-flood', 'default'): + ifaceobjrunning.update_config('bridge-unicast-flood', v) + + v = utils.get_onff_from_onezero( + self.brctlcmd.get_bridgeport_attr(bridgename, + ifaceobjrunning.name, + 'multicast-flood')) + if v and v != self.get_mod_subattr('bridge-multicast-flood', 'default'): + ifaceobjrunning.update_config('bridge-multicast-flood', v) + + v = utils.get_onff_from_onezero( + self.brctlcmd.get_bridgeport_attr(bridgename, + ifaceobjrunning.name, + 'arp-nd-suppress')) + # Display running 'arp-nd-suppress' only on vxlan ports + # if 'allow_arp_nd_suppress_only_on_vxlan' is set to 'yes' + # otherwise, display on all bridge-ports + + bportifaceobj = ifaceobj_getfunc(ifaceobjrunning.name)[0] + if (v and + v != self.get_mod_subattr('bridge-arp-nd-suppress', 'default') and + (not self.arp_nd_suppress_only_on_vxlan or + (self.arp_nd_suppress_only_on_vxlan and + bportifaceobj.link_kind & ifaceLinkKind.VXLAN))): + ifaceobjrunning.update_config('bridge-arp-nd-suppress', v) + + self._query_running_bridge_port_attrs(ifaceobjrunning, bridgename) + + def _query_running(self, ifaceobjrunning, ifaceobj_getfunc=None): + try: + if self.brctlcmd.bridge_exists(ifaceobjrunning.name): + self._query_running_bridge(ifaceobjrunning, ifaceobj_getfunc) + elif self.brctlcmd.is_bridge_port(ifaceobjrunning.name): + self._query_running_bridge_port(ifaceobjrunning, ifaceobj_getfunc) + except Exception as e: + raise Exception('%s: %s' % (ifaceobjrunning.name, str(e))) + + def _query(self, ifaceobj, **kwargs): + """ add default policy attributes supported by the module """ + if (not (ifaceobj.link_kind & ifaceLinkKind.BRIDGE) or + ifaceobj.get_attr_value_first('bridge-stp')): + return + if self.default_stp_on: + ifaceobj.update_config('bridge-stp', 'yes') + + def _query_check_support_yesno_attrs(self, runningattrs, ifaceobj): + for attrl in [['mcqifaddr', 'bridge-mcqifaddr'], + ['mcquerier', 'bridge-mcquerier'], + ['mcsnoop', 'bridge-mcsnoop']]: + value = ifaceobj.get_attr_value_first(attrl[1]) + if value and not utils.is_binary_bool(value): + if attrl[0] in runningattrs: + bool = utils.get_boolean_from_string(runningattrs[attrl[0]]) + runningattrs[attrl[0]] = utils.get_yesno_boolean(bool) + + self._query_check_mcrouter(ifaceobj, runningattrs) + self._query_check_support_yesno_attr_port(runningattrs, ifaceobj, 'portmcfl', ifaceobj.get_attr_value_first('bridge-portmcfl')) + self._query_check_support_yesno_attr_port(runningattrs, ifaceobj, 'learning', ifaceobj.get_attr_value_first('bridge-learning')) + self._query_check_support_yesno_attr_port(runningattrs, ifaceobj, 'unicast-flood', ifaceobj.get_attr_value_first('bridge-unicast-flood')) + self._query_check_support_yesno_attr_port(runningattrs, ifaceobj, 'multicast-flood', ifaceobj.get_attr_value_first('bridge-multicast-flood')) + self._query_check_support_yesno_attr_port(runningattrs, ifaceobj, 'arp-nd-suppress', ifaceobj.get_attr_value_first('bridge-arp-nd-suppress')) + + def _query_check_mcrouter(self, ifaceobj, running_attrs): + """ + bridge-mcrouter and bridge-portmcrouter supports: yes-no-0-1-2 + """ + if 'mcrouter' in running_attrs: + value = ifaceobj.get_attr_value_first('bridge-mcrouter') + if value: + try: + int(value) + except: + running_attrs['mcrouter'] = 'yes' if utils.get_boolean_from_string(running_attrs['mcrouter']) else 'no' + + def _query_check_support_yesno_attr_port(self, runningattrs, ifaceobj, attr, attrval): + if attrval: + portlist = self.parse_port_list(ifaceobj.name, attrval) + if portlist: + to_convert = [] + for p in portlist: + (port, val) = p.split('=') + if not utils.is_binary_bool(val): + to_convert.append(port) + for port in to_convert: + runningattrs['ports'][port][attr] = utils.get_yesno_boolean( + utils.get_boolean_from_string(runningattrs['ports'][port][attr])) + + _run_ops = { + 'pre-up': _up, + 'post-down': _down, + 'query-checkcurr': _query_check, + 'query-running': _query_running, + 'query': _query + } + + def get_ops(self): + """ returns list of ops supported by this module """ + return self._run_ops.keys() + + def _init_command_handlers(self): + if not self.ipcmd: + self.ipcmd = self.brctlcmd = LinkUtils() + + def run(self, ifaceobj, operation, query_ifaceobj=None, ifaceobj_getfunc=None): + """ run bridge configuration on the interface object passed as + argument. Can create bridge interfaces if they dont exist already + + Args: + **ifaceobj** (object): iface object + + **operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr', + 'query-running' + + Kwargs: + **query_ifaceobj** (object): query check ifaceobject. This is only + valid when op is 'query-checkcurr'. It is an object same as + ifaceobj, but contains running attribute values and its config + status. The modules can use it to return queried running state + of interfaces. status is success if the running state is same + as user required state in ifaceobj. error otherwise. + """ + op_handler = self._run_ops.get(operation) + if not op_handler: + return + self._init_command_handlers() + + if (not LinkUtils.bridge_utils_is_installed + and (ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT or ifaceobj.link_kind & ifaceLinkKind.BRIDGE) + and LinkUtils.bridge_utils_missing_warning): + self.logger.warning('%s: missing - bridge operation may not work as expected. ' + 'Please check if \'bridge-utils\' package is installed' % utils.brctl_cmd) + LinkUtils.bridge_utils_missing_warning = False + + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj, + ifaceobj_getfunc=ifaceobj_getfunc) + else: + op_handler(self, ifaceobj, ifaceobj_getfunc=ifaceobj_getfunc) diff --git a/addons/bridgevlan.py b/ifupdown2/addons/bridgevlan.py similarity index 75% rename from addons/bridgevlan.py rename to ifupdown2/addons/bridgevlan.py index ef3cd02..aadecc8 100644 --- a/addons/bridgevlan.py +++ b/ifupdown2/addons/bridgevlan.py @@ -1,16 +1,24 @@ #!/usr/bin/python # -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. # Author: Roopa Prabhu, roopa@cumulusnetworks.com # -from ifupdown.iface import * -from ifupdownaddons.modulebase import moduleBase -from ifupdownaddons.iproute2 import iproute2 -from ifupdownaddons.bridgeutils import brctl -from ipaddr import IPv4Address -import ifupdown.ifupdownflags as ifupdownflags -import logging +try: + from ifupdown2.ifupdown.iface import * + + from ifupdown2.ifupdownaddons.LinkUtils import LinkUtils + from ifupdown2.ifupdownaddons.modulebase import moduleBase + + import ifupdown2.ifupdown.ifupdownflags as ifupdownflags +except ImportError: + from ifupdown.iface import * + + from ifupdownaddons.LinkUtils import LinkUtils + from ifupdownaddons.modulebase import moduleBase + + import ifupdown.ifupdownflags as ifupdownflags + class bridgevlan(moduleBase): """ ifupdown2 addon module to configure vlan attributes on a vlan @@ -71,7 +79,7 @@ class bridgevlan(moduleBase): running_mcqv4src = {} if not ifupdownflags.flags.PERFMODE: - running_mcqv4src = self.brctlcmd.get_mcqv4src(bridgename) + running_mcqv4src = self.brctlcmd.bridge_get_mcqv4src(bridgename) if running_mcqv4src: r_mcqv4src = running_mcqv4src.get(vlan) else: @@ -79,14 +87,14 @@ class bridgevlan(moduleBase): mcqv4src = ifaceobj.get_attr_value_first('bridge-igmp-querier-src') if not mcqv4src: if r_mcqv4src: - self.brctlcmd.del_mcqv4src(bridgename, vlanid) + self.brctlcmd.bridge_del_mcqv4src(bridgename, vlanid) return if r_mcqv4src and r_mcqv4src != mcqv4src: - self.brctlcmd.del_mcqv4src(bridgename, vlanid) - self.brctlcmd.set_mcqv4src(bridgename, vlanid, mcqv4src) + self.brctlcmd.bridge_del_mcqv4src(bridgename, vlanid) + self.brctlcmd.bridge_set_mcqv4src(bridgename, vlanid, mcqv4src) else: - self.brctlcmd.set_mcqv4src(bridgename, vlanid, mcqv4src) + self.brctlcmd.bridge_set_mcqv4src(bridgename, vlanid, mcqv4src) def _down(self, ifaceobj): try: @@ -103,11 +111,11 @@ class bridgevlan(moduleBase): return mcqv4src = ifaceobj.get_attr_value_first('bridge-igmp-querier-src') if mcqv4src: - self.brctlcmd.del_mcqv4src(bridgename, vlanid) + self.brctlcmd.bridge_del_mcqv4src(bridgename, vlanid) def _query_running_bridge_igmp_querier_src(self, ifaceobj): (bridgename, vlanid) = ifaceobj.name.split('.') - running_mcqv4src = self.brctlcmd.get_mcqv4src(bridgename) + running_mcqv4src = self.brctlcmd.bridge_get_mcqv4src(bridgename) if running_mcqv4src: return running_mcqv4src.get(vlanid) return None @@ -129,6 +137,15 @@ class bridgevlan(moduleBase): # XXX not supported return + def syntax_check(self, ifaceobj, ifaceobj_getfunc): + ret = True + bvlan_intf = self._is_bridge_vlan_device(ifaceobj) + if (ifaceobj.get_attr_value_first('bridge-igmp-querier-src') and + not bvlan_intf): + self.logger.error('%s: bridge-igmp-querier-src only allowed under vlan stanza' %ifaceobj.name) + ret = False + return ret + _run_ops = {'pre-up' : _up, 'post-down' : _down, 'query-checkcurr' : _query_check, @@ -140,9 +157,7 @@ class bridgevlan(moduleBase): def _init_command_handlers(self): if not self.ipcmd: - self.ipcmd = iproute2() - if not self.brctlcmd: - self.brctlcmd = brctl() + self.ipcmd = self.brctlcmd = LinkUtils() def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): """ run vlan configuration on the interface object passed as argument @@ -165,6 +180,14 @@ class bridgevlan(moduleBase): return if (operation != 'query-running' and not self._is_bridge_vlan_device(ifaceobj)): + # most common problem is people specify BRIDGE_VLAN + # attribute on a bridge or a vlan device, which + # is incorrect. So, catch them here and warn before + # giving up processing the interface + if ((ifaceobj.link_kind & ifaceLinkKind.BRIDGE or + ifaceobj.link_kind & ifaceLinkKind.VLAN) and + not self.syntax_check(ifaceobj, None)): + ifaceobj.status = ifaceStatus.ERROR return self._init_command_handlers() if operation == 'query-checkcurr': diff --git a/addons/dhcp.py b/ifupdown2/addons/dhcp.py similarity index 91% rename from addons/dhcp.py rename to ifupdown2/addons/dhcp.py index 5faa84b..09251a9 100644 --- a/addons/dhcp.py +++ b/ifupdown2/addons/dhcp.py @@ -1,24 +1,33 @@ #!/usr/bin/python # -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. # Author: Roopa Prabhu, roopa@cumulusnetworks.com # +import re +import time + try: - import re - from ipaddr import IPNetwork - from sets import Set - from ifupdown.iface import * + import ifupdown2.ifupdown.policymanager as policymanager + import ifupdown2.ifupdown.ifupdownflags as ifupdownflags + + from ifupdown2.ifupdown.iface import * + from ifupdown2.ifupdown.utils import utils + + from ifupdown2.ifupdownaddons.dhclient import dhclient + from ifupdown2.ifupdownaddons.LinkUtils import LinkUtils + from ifupdown2.ifupdownaddons.modulebase import moduleBase +except ImportError: import ifupdown.policymanager as policymanager - from ifupdownaddons.modulebase import moduleBase - from ifupdownaddons.dhclient import dhclient - from ifupdownaddons.iproute2 import iproute2 import ifupdown.ifupdownflags as ifupdownflags + + from ifupdown.iface import * from ifupdown.utils import utils - import time - from ifupdown.netlink import netlink -except ImportError, e: - raise ImportError (str(e) + "- required module not found") + + from ifupdownaddons.dhclient import dhclient + from ifupdownaddons.LinkUtils import LinkUtils + from ifupdownaddons.modulebase import moduleBase + class dhcp(moduleBase): """ ifupdown2 addon module to configure dhcp on interface """ @@ -95,8 +104,8 @@ class dhcp(moduleBase): timeout = 10 while timeout: timeout -= 2 - addr_output = utils.exec_command('ip -6 addr show %s' - % ifaceobj.name) + addr_output = utils.exec_command('%s -6 addr show %s' + %(utils.ip_cmd, ifaceobj.name)) r = re.search('inet6 .* scope link', addr_output) if r: self.dhclientcmd.start6(ifaceobj.name, @@ -179,7 +188,7 @@ class dhcp(moduleBase): def _init_command_handlers(self): if not self.ipcmd: - self.ipcmd = iproute2() + self.ipcmd = LinkUtils() def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): """ run dhcp configuration on the interface object passed as argument @@ -203,7 +212,7 @@ class dhcp(moduleBase): return try: if (operation != 'query-running' and - (ifaceobj.addr_method != 'dhcp' and + (ifaceobj.addr_method != 'dhcp' and ifaceobj.addr_method != 'dhcp6')): return except: diff --git a/addons/ethtool.py b/ifupdown2/addons/ethtool.py similarity index 64% rename from addons/ethtool.py rename to ifupdown2/addons/ethtool.py index 7cb52c6..3c8be20 100644 --- a/addons/ethtool.py +++ b/ifupdown2/addons/ethtool.py @@ -1,24 +1,34 @@ #!/usr/bin/python # -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. # Author: Roopa Prabhu, roopa@cumulusnetworks.com # -import json -import ifupdown.policymanager as policymanager + +import os try: - import os - from ipaddr import IPNetwork - from sets import Set + import ifupdown2.ifupdown.ifupdownflags as ifupdownflags + import ifupdown2.ifupdown.policymanager as policymanager + + from ifupdown2.ifupdown.iface import * + from ifupdown2.ifupdown.utils import utils + from ifupdown2.ifupdown.exceptions import moduleNotSupported + + from ifupdown2.ifupdownaddons.utilsbase import * + from ifupdown2.ifupdownaddons.LinkUtils import LinkUtils + from ifupdown2.ifupdownaddons.modulebase import moduleBase +except ImportError: + import ifupdown.ifupdownflags as ifupdownflags + import ifupdown.policymanager as policymanager + from ifupdown.iface import * - from ifupdown.exceptions import moduleNotSupported from ifupdown.utils import utils + from ifupdown.exceptions import moduleNotSupported + from ifupdownaddons.utilsbase import * - from ifupdownaddons.modulebase import moduleBase, NotSupported - from ifupdownaddons.iproute2 import iproute2 - import ifupdown.ifupdownflags as ifupdownflags -except ImportError, e: - raise ImportError (str(e) + "- required module not found") + from ifupdownaddons.LinkUtils import LinkUtils + from ifupdownaddons.modulebase import moduleBase + class ethtool(moduleBase,utilsBase): """ ifupdown2 addon module to configure ethtool attributes """ @@ -49,127 +59,161 @@ class ethtool(moduleBase,utilsBase): 'link-fec' : {'help': 'set forward error correction mode', 'example' : ['link-fec rs'], - 'validvals' : ['rs', 'baser', 'on', 'off'], + 'validvals' : ['rs', 'baser', 'auto', 'off'], 'default' : 'varies by platform and port'}}} def __init__(self, *args, **kargs): moduleBase.__init__(self, *args, **kargs) - if not os.path.exists('/sbin/ethtool'): - raise moduleNotSupported('module init failed: no /sbin/ethtool found') + if not os.path.exists(utils.ethtool_cmd): + raise moduleNotSupported('module init failed: %s: not found' % utils.ethtool_cmd) self.ipcmd = None # keep a list of iface objects who have modified link attributes self.ifaceobjs_modified_configs = [] - def _post_up(self, ifaceobj, operation='post_up'): - """ - _post_up and _pre_down will reset the layer 2 attributes to default policy - settings. - """ - if not self.ipcmd.link_exists(ifaceobj.name): - return - cmd = '' + def do_fec_settings(self, ifaceobj): feccmd = '' - for attr in ['speed', 'duplex', 'autoneg', 'fec']: - # attribute existed before but we must reset to default - config_val = ifaceobj.get_attr_value_first('link-%s'%attr) - default_val = policymanager.policymanager_api.get_iface_default( - module_name='ethtool', - ifname=ifaceobj.name, - attr='link-%s'%attr) - if not default_val and not config_val: - # there is no point in checking the running config - # if we have no default and the user did not have settings - continue - # check running values - running_val = self.get_running_attr(attr, ifaceobj) + # attribute existed before but we must reset to default + config_val = ifaceobj.get_attr_value_first('link-fec') + default_val = policymanager.policymanager_api.get_iface_default( + module_name='ethtool', + ifname=ifaceobj.name, + attr='link-fec') - if attr == 'autoneg': - config_val = utils.get_onoff_bool(config_val) - - # we need to track if an interface has a configured value - # this will be used if there are duplicate iface stanza and - # the configured interface will always take precedence. - # so even if we do not change the settings because they match - # what is configured, we need to append it here so that later duplicate - # ifaces will see that we had a configured iface and not change things. - if config_val and config_val == running_val: - # running value is what is configured, do nothing - # this prevents unconfigured ifaces from resetting to default - self.ifaceobjs_modified_configs.append(ifaceobj.name) - continue + if not default_val and not config_val: + # there is no point in checking the running config + # if we have no default and the user did not have settings + return - if not config_val and default_val and default_val == running_val: - # nothing configured but the default is running - continue - # if we are the oldest sibling, we have to reset to defaults - # unless a previous sibling had link attr configured and made changes - if ((ifaceobj.flags & iface.HAS_SIBLINGS) and - (ifaceobj.flags & iface.OLDEST_SIBLING) and - (ifaceobj.name in self.ifaceobjs_modified_configs)): - continue + # check running values + running_val = self.get_running_attr('fec', ifaceobj) + if config_val and config_val == running_val: + return - # If we have siblings AND are not the oldest AND we have no configs, - # do not change anything. The only way a non-oldest sibling would - # change values is if it had configured settings. iface stanzas may - # not be squashed if addr_config_squash is not set so we still need this. - if ((ifaceobj.flags & iface.HAS_SIBLINGS) and - not (ifaceobj.flags & iface.OLDEST_SIBLING) and - not config_val): - continue + if not config_val and default_val and default_val == running_val: + # nothing configured but the default is running + return - if attr == 'fec': - # if we got this far, we need to change it - if config_val and (config_val != running_val): - # if the configured value is not set, set it - feccmd = ' %s %s' % ("encoding", config_val) - elif default_val and (default_val != running_val): - # or if it has a default not equal to running value, set it - feccmd = ' %s %s' % ("encoding", default_val) - else: - # no value set nor default, leave it alone - pass - else: - # if we got this far, we need to change it - if config_val and (config_val != running_val): - # if the configured value is not set, set it - cmd += ' %s %s' % (attr, config_val) - elif default_val and (default_val != running_val): - # or if it has a default not equal to running value, set it - cmd += ' %s %s' % (attr, default_val) - else: - # no value set nor default, leave it alone - pass + # if we got this far, we need to change it + if config_val and (config_val != running_val): + # if the configured value is not set, set it + feccmd = ' %s %s' % ("encoding", config_val) + elif default_val and (default_val != running_val): + # or if it has a default not equal to running value, set it + feccmd = ' %s %s' % ("encoding", default_val) - if cmd: + if feccmd: try: - # we should only be calling ethtool if there - # is a speed set or we can find a default speed - # because we should only be calling ethtool on swp ports - # we also need to set this here in case we changed - # something. this prevents unconfigured ifaces from resetting to default - self.ifaceobjs_modified_configs.append(ifaceobj.name) - cmd = 'ethtool -s %s %s' %(ifaceobj.name, cmd) - utils.exec_command(cmd) + feccmd = ('%s --set-fec %s %s' % + (utils.ethtool_cmd, ifaceobj.name, feccmd)) + utils.exec_command(feccmd) except Exception, e: self.log_error('%s: %s' %(ifaceobj.name, str(e)), ifaceobj) else: pass - if feccmd: + def do_speed_settings(self, ifaceobj, operation='post_up'): + cmd = '' + + autoneg_to_configure = None + speed_to_configure = None + duplex_to_configure = None + + config_speed = ifaceobj.get_attr_value_first('link-speed') + config_duplex = ifaceobj.get_attr_value_first('link-duplex') + config_autoneg = ifaceobj.get_attr_value_first('link-autoneg') + + default_speed = policymanager.policymanager_api.get_iface_default( + module_name='ethtool', + ifname=ifaceobj.name, + attr='link-speed' + ) + + default_duplex = policymanager.policymanager_api.get_iface_default( + module_name='ethtool', + ifname=ifaceobj.name, + attr='link-duplex' + ) + + default_autoneg = policymanager.policymanager_api.get_iface_default( + module_name='ethtool', + ifname=ifaceobj.name, + attr='link-autoneg' + ) + + # autoneg wins if provided by user and is on + if config_autoneg and utils.get_boolean_from_string(config_autoneg): + autoneg_to_configure = config_autoneg + speed_to_configure = None + duplex_to_configure = None + elif config_speed: + # Any speed settings configured by the user wins + autoneg_to_configure = None + speed_to_configure = config_speed + duplex_to_configure = config_duplex + if not config_duplex: + duplex_to_configure = default_duplex + else: + # if user given autoneg config is off, we must respect that and + # override any default autoneg config + if config_autoneg and not utils.get_boolean_from_string(config_autoneg): + default_autoneg = 'off' + + if default_autoneg and utils.get_boolean_from_string(default_autoneg): + autoneg_to_configure = utils.get_onoff_bool(default_autoneg) + speed_to_configure = None + duplex_to_configure = None + else: + autoneg_to_configure = None + speed_to_configure = default_speed + duplex_to_configure = default_duplex + + if autoneg_to_configure: + autoneg_to_configure = utils.get_onoff_bool(autoneg_to_configure) + # check running values + running_val = self.get_running_attr('autoneg', ifaceobj) + if autoneg_to_configure != running_val: + # if the configured value is not set, set it + cmd += ' autoneg %s' % autoneg_to_configure + else: + force_set = False + if speed_to_configure: + # check running values + if utils.get_boolean_from_string(self.get_running_attr('autoneg', ifaceobj) or 'off'): + cmd = 'autoneg off' + # if we are transitioning from autoneg 'on' to 'off' + # don't check running speed + force_set = True + + running_val = self.get_running_attr('speed', ifaceobj) + if force_set or (speed_to_configure != running_val): + # if the configured value is not set, set it + cmd += ' speed %s' % speed_to_configure + + if duplex_to_configure: + # check running values + running_val = self.get_running_attr('duplex', ifaceobj) + if force_set or (duplex_to_configure != running_val): + # if the configured value is not set, set it + cmd += ' duplex %s' % duplex_to_configure + + if cmd: try: - # we should only be calling ethtool if there - # is a speed set or we can find a default speed - # because we should only be calling ethtool on swp ports - # we also need to set this here in case we changed - # something. this prevents unconfigured ifaces from resetting to default - self.ifaceobjs_modified_configs.append(ifaceobj.name) - feccmd = 'ethtool --set-fec %s %s' %(ifaceobj.name, feccmd) - utils.exec_command(feccmd) + cmd = ('%s -s %s %s' % (utils.ethtool_cmd, ifaceobj.name, cmd)) + utils.exec_command(cmd) except Exception, e: - self.log_error('%s: %s' %(ifaceobj.name, str(e)), ifaceobj) - else: - pass + self.log_error('%s: %s' %(ifaceobj.name, str(e)), ifaceobj, raise_error=False) + + def _pre_up(self, ifaceobj, operation='post_up'): + """ + _pre_up and _pre_down will reset the layer 2 attributes to default policy + settings. + """ + if not self.ipcmd.link_exists(ifaceobj.name): + return + + self.do_speed_settings(ifaceobj) + self.do_fec_settings(ifaceobj) def _pre_down(self, ifaceobj): pass #self._post_up(ifaceobj,operation="_pre_down") @@ -252,7 +296,7 @@ class ethtool(moduleBase,utilsBase): for attr in ethtool_output.splitlines(): if attr.startswith('FEC encodings'): fec_attrs = attr.split() - return(fec_attrs[fec_attrs.index(':')+1]) + return(fec_attrs[fec_attrs.index(':')+1]) except Exception as e: self.logger.debug('ethtool: problems in ethtool set-fec output' ' %s: %s' %(ethtool_output.splitlines(), str(e))) @@ -265,10 +309,11 @@ class ethtool(moduleBase,utilsBase): running_attr = None try: if attr == 'autoneg': - output = utils.exec_commandl(['ethtool', ifaceobj.name]) + output = utils.exec_commandl([utils.ethtool_cmd, ifaceobj.name]) running_attr = self.get_autoneg(ethtool_output=output) elif attr == 'fec': - output = utils.exec_command('ethtool --show-fec %s'%(ifaceobj.name)) + output = utils.exec_command('%s --show-fec %s'% + (utils.ethtool_cmd, ifaceobj.name)) running_attr = self.get_fec_encoding(ethtool_output=output) else: running_attr = self.read_file_oneline('/sys/class/net/%s/%s' % \ @@ -330,7 +375,7 @@ class ethtool(moduleBase,utilsBase): ifaceobj.update_config('link-%s' %attr, default) _run_ops = {'pre-down' : _pre_down, - 'post-up' : _post_up, + 'pre-up' : _pre_up, 'query-checkcurr' : _query_check, 'query-running' : _query_running, 'query' : _query} @@ -341,7 +386,7 @@ class ethtool(moduleBase,utilsBase): def _init_command_handlers(self): if not self.ipcmd: - self.ipcmd = iproute2() + self.ipcmd = LinkUtils() def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): """ run ethtool configuration on the interface object passed as diff --git a/ifupdown2/addons/link.py b/ifupdown2/addons/link.py new file mode 100644 index 0000000..3aaa969 --- /dev/null +++ b/ifupdown2/addons/link.py @@ -0,0 +1,142 @@ +#!/usr/bin/python +# +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# +# This should be pretty simple and might not really even need to exist. +# The key is that we need to call link_create with a type of "dummy" +# since that will translate to 'ip link add loopbackX type dummy' +# The config file should probably just indicate that the type is +# loopback or dummy. + +try: + from ifupdown2.ifupdown.iface import * + from ifupdown2.ifupdown.utils import utils + + from ifupdown2.ifupdownaddons.LinkUtils import LinkUtils + from ifupdown2.ifupdownaddons.modulebase import moduleBase + + import ifupdown2.ifupdown.ifupdownflags as ifupdownflags + import ifupdown2.ifupdown.policymanager as policymanager +except ImportError: + from ifupdown.iface import * + from ifupdown.utils import utils + + from ifupdownaddons.LinkUtils import LinkUtils + from ifupdownaddons.modulebase import moduleBase + + import ifupdown.ifupdownflags as ifupdownflags + import ifupdown.policymanager as policymanager + + +class link(moduleBase): + _modinfo = {'mhelp' : 'create/configure link types. similar to ip-link', + 'attrs' : { + 'link-type' : + {'help' : 'type of link as in \'ip link\' command.', + 'validvals' : ['dummy', 'veth'], + 'example' : ['link-type ']}, + 'link-down' : + {'help': 'keep link down', + 'example' : ['link-down yes/no'], + 'default' : 'no', + 'validvals' : ['yes', 'no']}}} + + def __init__(self, *args, **kargs): + moduleBase.__init__(self, *args, **kargs) + self.ipcmd = None + + self.check_physical_port_existance = utils.get_boolean_from_string(policymanager.policymanager_api.get_module_globals( + self.__class__.__name__, + 'warn_on_physdev_not_present' + )) + + def syntax_check(self, ifaceobj, ifaceobj_getfunc): + if self.check_physical_port_existance: + if not ifaceobj.link_kind and not LinkUtils.link_exists(ifaceobj.name): + self.logger.warning('%s: interface does not exist' % ifaceobj.name) + return False + return True + + def _is_my_interface(self, ifaceobj): + if (ifaceobj.get_attr_value_first('link-type') + or ifaceobj.get_attr_value_first('link-down')): + return True + return False + + def get_dependent_ifacenames(self, ifaceobj, ifacenames_all=None): + if ifaceobj.get_attr_value_first('link-down') == 'yes': + ifaceobj.link_privflags |= ifaceLinkPrivFlags.KEEP_LINK_DOWN + if ifaceobj.get_attr_value_first('link-type'): + ifaceobj.link_kind = ifaceLinkKind.OTHER + + def _up(self, ifaceobj): + link_type = ifaceobj.get_attr_value_first('link-type') + if link_type: + self.ipcmd.link_create(ifaceobj.name, + ifaceobj.get_attr_value_first('link-type')) + + def _down(self, ifaceobj): + if not ifaceobj.get_attr_value_first('link-type'): + return + if (not ifupdownflags.flags.PERFMODE and + not self.ipcmd.link_exists(ifaceobj.name)): + return + try: + self.ipcmd.link_delete(ifaceobj.name) + except Exception, e: + self.log_warn(str(e)) + + def _query_check(self, ifaceobj, ifaceobjcurr): + if ifaceobj.get_attr_value('link-type'): + if not self.ipcmd.link_exists(ifaceobj.name): + ifaceobjcurr.update_config_with_status('link-type', 'None', 1) + else: + link_type = ifaceobj.get_attr_value_first('link-type') + if self.ipcmd.link_get_kind(ifaceobj.name) == link_type: + ifaceobjcurr.update_config_with_status('link-type', + link_type, 0) + else: + ifaceobjcurr.update_config_with_status('link-type', + link_type, 1) + + link_down = ifaceobj.get_attr_value_first('link-down') + if link_down: + link_up = self.ipcmd.is_link_up(ifaceobj.name) + link_should_be_down = utils.get_boolean_from_string(link_down) + + if link_should_be_down and link_up: + status = 1 + link_down = 'no' + elif link_should_be_down and not link_up: + status = 0 + elif not link_should_be_down and link_up: + status = 0 + else: + status = 1 + + ifaceobjcurr.update_config_with_status('link-down', link_down, status) + + _run_ops = {'pre-up' : _up, + 'post-down' : _down, + 'query-checkcurr' : _query_check} + + def get_ops(self): + return self._run_ops.keys() + + def _init_command_handlers(self): + if not self.ipcmd: + self.ipcmd = LinkUtils() + + def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): + op_handler = self._run_ops.get(operation) + if not op_handler: + return + if (operation != 'query-running' and + not self._is_my_interface(ifaceobj)): + return + self._init_command_handlers() + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj) + else: + op_handler(self, ifaceobj) diff --git a/addons/mstpctl.py b/ifupdown2/addons/mstpctl.py similarity index 88% rename from addons/mstpctl.py rename to ifupdown2/addons/mstpctl.py index 4ad1157..a13e8b6 100644 --- a/addons/mstpctl.py +++ b/ifupdown2/addons/mstpctl.py @@ -1,21 +1,38 @@ #!/usr/bin/python # -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. # Author: Roopa Prabhu, roopa@cumulusnetworks.com # import os + from sets import Set -from ifupdown.iface import * -from ifupdown.utils import utils -from ifupdownaddons.modulebase import moduleBase -from ifupdownaddons.bridgeutils import brctl -from ifupdownaddons.iproute2 import iproute2 -from ifupdown.netlink import netlink -from ifupdownaddons.mstpctlutil import mstpctlutil -from ifupdownaddons.systemutils import systemUtils -import ifupdown.ifupdownflags as ifupdownflags -import ifupdown.policymanager as policymanager + +try: + from ifupdown2.ifupdown.iface import * + from ifupdown2.ifupdown.utils import utils + from ifupdown2.ifupdown.netlink import netlink + + import ifupdown2.ifupdown.ifupdownflags as ifupdownflags + import ifupdown2.ifupdown.policymanager as policymanager + + from ifupdown2.ifupdownaddons.LinkUtils import LinkUtils + from ifupdown2.ifupdownaddons.modulebase import moduleBase + from ifupdown2.ifupdownaddons.mstpctlutil import mstpctlutil + from ifupdown2.ifupdownaddons.systemutils import systemUtils +except ImportError: + from ifupdown.iface import * + from ifupdown.utils import utils + from ifupdown.netlink import netlink + + import ifupdown.ifupdownflags as ifupdownflags + import ifupdown.policymanager as policymanager + + from ifupdownaddons.LinkUtils import LinkUtils + from ifupdownaddons.modulebase import moduleBase + from ifupdownaddons.mstpctlutil import mstpctlutil + from ifupdownaddons.systemutils import systemUtils + class mstpctlFlags: PORT_PROCESSED = 0x1 @@ -57,20 +74,20 @@ class mstpctl(moduleBase): { 'help' : 'max message age', 'validrange' : ['0', '255'], 'default' : '20', - 'jsonAttr': 'maxAge', + 'jsonAttr': 'bridgeMaxAge', 'required' : False, 'example' : ['mstpctl-maxage 20']}, 'mstpctl-fdelay' : { 'help' : 'set forwarding delay', 'validrange' : ['0', '255'], 'default' : '15', - 'jsonAttr': 'fwdDelay', + 'jsonAttr': 'bridgeFwdDelay', 'required' : False, 'example' : ['mstpctl-fdelay 15']}, 'mstpctl-maxhops' : { 'help' : 'bridge max hops', 'validrange' : ['0', '255'], - 'default' : '15', + 'default' : '20', 'jsonAttr': 'maxHops', 'required' : False, 'example' : ['mstpctl-maxhops 15']}, @@ -132,11 +149,11 @@ class mstpctl(moduleBase): 'required' : False, 'example' : ['under the bridge: mstpctl-bpduguard swp1=yes swp2=no', 'under the port (recommended): mstpctl-bpduguard yes']}, - 'mstpctl-treeportprio' : - { 'help' : - 'port priority for MSTI instance', + 'mstpctl-treeportprio' : + { 'help': 'Sets the \'s priority MSTI instance. ' + 'The priority value must be a number between 0 and 240 and a multiple of 16.', 'default' : '128', - 'validvals': [''], + 'validvals': [''], 'validrange' : ['0', '240'], 'jsonAttr': 'treeportprio', 'required' : False, @@ -149,7 +166,7 @@ class mstpctl(moduleBase): 'required' : False, 'jsonAttr': 'helloTime', 'example' : ['mstpctl-hello 2']}, - 'mstpctl-portnetwork' : + 'mstpctl-portnetwork' : { 'help' : 'enable/disable bridge assurance capability for a port', 'validvals' : [''], 'default' : 'no', @@ -157,7 +174,7 @@ class mstpctl(moduleBase): 'required' : False, 'example' : ['under the bridge: mstpctl-portnetwork swp1=yes swp2=no', 'under the port (recommended): mstpctl-portnetwork yes']}, - 'mstpctl-portadminedge' : + 'mstpctl-portadminedge' : { 'help' : 'enable/disable initial edge state of the port', 'validvals' : [''], 'default' : 'no', @@ -165,7 +182,7 @@ class mstpctl(moduleBase): 'required' : False, 'example' : ['under the bridge: mstpctl-portadminedge swp1=yes swp2=no', 'under the port (recommended): mstpctl-portadminedge yes']}, - 'mstpctl-portautoedge' : + 'mstpctl-portautoedge' : { 'help' : 'enable/disable auto transition to/from edge state of the port', 'validvals' : [''], 'default' : 'yes', @@ -173,13 +190,13 @@ class mstpctl(moduleBase): 'required' : False, 'example' : ['under the bridge: mstpctl-portautoedge swp1=yes swp2=no', 'under the port (recommended): mstpctl-portautoedge yes']}, - 'mstpctl-treeportcost' : + 'mstpctl-treeportcost' : { 'help' : 'port tree cost', 'validrange' : ['0', '255'], 'required' : False, 'jsonAttr': 'extPortCost', }, - 'mstpctl-portbpdufilter' : + 'mstpctl-portbpdufilter' : { 'help' : 'enable/disable bpdu filter on a port. ' + 'syntax varies when defined under a bridge ' + 'vs under a port', @@ -224,11 +241,38 @@ class mstpctl(moduleBase): self.mstpctlcmd = None self.mstpd_running = (True if systemUtils.is_process_running('mstpd') else False) - self.default_vxlan_ports_set_bpduparams = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='mstpctl-vxlan-always-set-bpdu-params') - if self.default_vxlan_ports_set_bpduparams == 'yes': - self.default_vxlan_ports_set_bpduparams = True - else: - self.default_vxlan_ports_set_bpduparams = False + + # Background - + # The ask is to make "mstpctl-portadminedge yes" part of the default ifupdown2 + # policy for all vxlan interfaces. In the absence of this, the mstp work flow + # is flawed in the event of vxlan flap. + # Details - + # As of today, for vxlan interfaces "oper edge port" is set to 'yes' and also + # "bpdufilter port" is also set to 'yes'. So, in a case where bridge has multiple + # vxlan interfaces, if one vxlan interface is flapped, this would trigger mstp + # re-evaluation of states on other vxlan interfaces, creating momentary traffic + # glitch on those vxlans. Setting "admin edge port" to yes (in addition to the + # defaults we already have) prevents this. + # + # We use to only support 'mstpctl-vxlan-always-set-bpdu-params' but introducing a + # separate policy attribute doesn't make sense, we should have one single + # attribute to handle the whole thing (and deprecate mstpctl-vxlan-always-set-bpdu-params) + # mstpctl-set-default-vxlan-bridge-attrs=yes will set + # mstpctl-portbpdufilter + # mstpctl-bpduguard + # mstpctl-portadminedge + # + self.set_default_mstp_vxlan_bridge_config = utils.get_boolean_from_string( + policymanager.policymanager_api.get_module_globals( + module_name=self.__class__.__name__, + attr='mstpctl-vxlan-always-set-bpdu-params' + ) + ) or utils.get_boolean_from_string( + policymanager.policymanager_api.get_module_globals( + module_name=self.__class__.__name__, + attr='mstpctl-set-default-vxlan-bridge-attrs' + ) + ) def syntax_check(self, ifaceobj, ifaceobj_getfunc): if self._is_bridge(ifaceobj): @@ -303,7 +347,7 @@ class mstpctl(moduleBase): if not ifupdownflags.flags.PERFMODE: runningbridgeports = self.brctlcmd.get_bridge_ports(ifaceobj.name) if runningbridgeports: - [netlink.link_set_nomaster(bport) + [self.ipcmd.link_set(bport, 'nomaster') for bport in runningbridgeports if not bridgeports or bport not in bridgeports] else: @@ -319,7 +363,7 @@ class mstpctl(moduleBase): %(ifaceobj.name, bridgeport)) err += 1 continue - netlink.link_set_master(bridgeport, ifaceobj.name) + self.ipcmd.link_set(bridgeport, 'master', ifaceobj.name) self.ipcmd.addr_flush(bridgeport) except Exception, e: self.log_error(str(e), ifaceobj) @@ -327,7 +371,7 @@ class mstpctl(moduleBase): if err: self.log_error('error configuring bridge (missing ports)') - def _apply_bridge_settings(self, ifaceobj): + def _apply_bridge_settings(self, ifaceobj, ifaceobj_getfunc): check = False if ifupdownflags.flags.PERFMODE else True try: # set bridge attributes @@ -335,8 +379,8 @@ class mstpctl(moduleBase): config_val = ifaceobj.get_attr_value_first(attrname) default_val = policymanager.policymanager_api.get_iface_default(module_name=self.__class__.__name__, ifname=ifaceobj.name, attr=attrname) if not default_val: - default_val = self.get_mod_subattr(attrname,'default') - jsonAttr = self.get_mod_subattr(attrname, 'jsonAttr') + default_val = self.get_mod_subattr(attrname, 'default') + jsonAttr = self.get_mod_subattr(attrname, 'jsonAttr') try: running_val = self.mstpctlcmd.get_bridge_attr( ifaceobj.name, jsonAttr) @@ -378,12 +422,18 @@ class mstpctl(moduleBase): for port in bridgeports: if not self.brctlcmd.is_bridge_port(port): continue + + bport_ifaceobj = ifaceobj_getfunc(port) + if bport_ifaceobj: + default_val = self._get_default_val(attrname, bport_ifaceobj[0], ifaceobj) + self.mstpctlcmd.set_bridge_port_attr(ifaceobj.name, port, dstattrname, default_val, json_attr=jsonAttr) - except: + except Exception as e: + self.logger.debug('%s' % str(e)) self.logger.info('%s: not resetting %s config' %(ifaceobj.name, attrname)) # leave the loop for this attribute @@ -417,10 +467,14 @@ class mstpctl(moduleBase): pass def _get_default_val(self, attr, ifaceobj, bridgeifaceobj): - if ((attr == 'mstpctl-portbpdufilter' or - attr == 'mstpctl-bpduguard') and - self.default_vxlan_ports_set_bpduparams and - (ifaceobj.link_kind & ifaceLinkKind.VXLAN)): + if (self.set_default_mstp_vxlan_bridge_config + and ifaceobj.link_kind & ifaceLinkKind.VXLAN + and attr in ( + 'mstpctl-portbpdufilter', + 'mstpctl-bpduguard', + 'mstpctl-portadminedge', + ) + ): try: config_val = bridgeifaceobj.get_attr_value_first(attr) except Exception, e: @@ -459,10 +513,13 @@ class mstpctl(moduleBase): not os.path.exists('/sys/class/net/%s/brport' %ifaceobj.name) or not bvlan_aware): if (not bvlan_aware and - self.default_vxlan_ports_set_bpduparams and + self.set_default_mstp_vxlan_bridge_config and (ifaceobj.link_kind & ifaceLinkKind.VXLAN)): - for attr in ['mstpctl-portbpdufilter', - 'mstpctl-bpduguard']: + for attr in ( + 'mstpctl-portbpdufilter', + 'mstpctl-bpduguard', + 'mstpctl-portadminedge' + ): json_attr = self.get_mod_subattr(attr, 'jsonAttr') config_val = self._get_default_val(attr, ifaceobj, bridgeifaceobj) @@ -475,10 +532,32 @@ class mstpctl(moduleBase): except Exception, e: self.log_warn('%s: error setting %s (%s)' % (ifaceobj.name, attr, str(e))) + + if not bvlan_aware: + # for "traditional" bridges we also want to let the user configure + # some attributes (possibly all of them in the future) + applied = self._apply_bridge_port_settings_attributes_list( + ( + ('mstpctl-portrestrrole', 'portrestrrole'), + ('mstpctl-portautoedge', 'portautoedge') + ), + ifaceobj, + bridgeifaceobj, + bridgename, + applied + ) return applied # set bridge port attributes - for attrname, dstattrname in self._port_attrs_map.items(): - attrval = ifaceobj.get_attr_value_first(attrname) + return self._apply_bridge_port_settings_attributes_list( + self._port_attrs_map.items(), + ifaceobj, + bridgeifaceobj, + bridgename, + applied + ) + + def _apply_bridge_port_settings_attributes_list(self, attributes_list, ifaceobj, bridgeifaceobj, bridgename, applied): + for attrname, dstattrname in attributes_list: config_val = ifaceobj.get_attr_value_first(attrname) default_val = self._get_default_val(attrname, ifaceobj, bridgeifaceobj) jsonAttr = self.get_mod_subattr(attrname, 'jsonAttr') @@ -595,12 +674,12 @@ class mstpctl(moduleBase): stp = ifaceobj.get_attr_value_first('mstpctl-stp') if stp: self.set_iface_attr(ifaceobj, 'mstpctl-stp', - self.brctlcmd.set_stp) + self.brctlcmd.bridge_set_stp) else: - stp = self.brctlcmd.get_stp(ifaceobj.name) + stp = self.brctlcmd.bridge_get_stp(ifaceobj.name) if (self.mstpd_running and (stp == 'yes' or stp == 'on')): - self._apply_bridge_settings(ifaceobj) + self._apply_bridge_settings(ifaceobj, ifaceobj_getfunc) self._apply_bridge_port_settings_all(ifaceobj, ifaceobj_getfunc=ifaceobj_getfunc) except Exception, e: @@ -691,7 +770,7 @@ class mstpctl(moduleBase): return utils.get_boolean_from_string(stp) def _get_running_stp(self, ifaceobj): - stp = self.brctlcmd.get_stp(ifaceobj.name) + stp = self.brctlcmd.bridge_get_stp(ifaceobj.name) return utils.get_boolean_from_string(stp) def _query_check_bridge(self, ifaceobj, ifaceobjcurr, @@ -704,8 +783,8 @@ class mstpctl(moduleBase): return ifaceattrs = self.dict_key_subset(ifaceobj.config, self.get_mod_attrs()) - if self.default_vxlan_ports_set_bpduparams: - for attr in ['mstpctl-portbpdufilter', 'mstpctl-bpduguard']: + if self.set_default_mstp_vxlan_bridge_config: + for attr in ('mstpctl-portbpdufilter', 'mstpctl-bpduguard', 'mstpctl-portadminedge'): if attr not in ifaceattrs: ifaceattrs.append(attr) if not ifaceattrs: @@ -721,8 +800,7 @@ class mstpctl(moduleBase): # for all mstpctl options if k in blacklistedattrs: continue - if ((k == 'mstpctl-portbpdufilter' or - k == 'mstpctl-bpduguard')): + if k in ('mstpctl-portbpdufilter', 'mstpctl-bpduguard', 'mstpctl-portadminedge'): #special case, 'ifquery --check --with-defaults' on a VLAN #unaware bridge if not running_port_list: @@ -788,7 +866,7 @@ class mstpctl(moduleBase): # contain more than one valid values stp_on_vals = ['on', 'yes'] stp_off_vals = ['off'] - rv = self.brctlcmd.get_stp(ifaceobj.name) + rv = self.brctlcmd.bridge_get_stp(ifaceobj.name) if ((v in stp_on_vals and rv in stp_on_vals) or (v in stp_off_vals and rv in stp_off_vals)): ifaceobjcurr.update_config_with_status('mstpctl-stp', v, 0) @@ -855,14 +933,17 @@ class mstpctl(moduleBase): bifaceobjlist = ifaceobj_getfunc(bridge) for bifaceobj in bifaceobjlist: if (self._is_bridge(bifaceobj) and - self.default_vxlan_ports_set_bpduparams and + self.set_default_mstp_vxlan_bridge_config and (bifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_VLAN_AWARE)): config_stp = self._get_config_stp(bifaceobj) running_stp = self._get_running_stp(bifaceobj) if (not config_stp or not running_stp): continue - for attr in ['mstpctl-portbpdufilter', - 'mstpctl-bpduguard']: + for attr in ( + 'mstpctl-portbpdufilter', + 'mstpctl-bpduguard', + 'mstpctl-portadminedge' + ): jsonAttr = self.get_mod_subattr(attr, 'jsonAttr') config_val = bifaceobj.get_attr_value_first(attr) if config_val: @@ -954,7 +1035,7 @@ class mstpctl(moduleBase): self.logger.warn('%s: unable to determine bridgename' %ifaceobjrunning.name) return - if self.brctlcmd.get_stp(bridgename) == 'no': + if self.brctlcmd.bridge_get_stp(bridgename) == 'no': # This bridge does not run stp, return return # if userspace stp not set, return @@ -1013,7 +1094,7 @@ class mstpctl(moduleBase): # portconfig['mstpctl-treeportcost'] += ' %s=%s' %(p, v) def _query_running_bridge(self, ifaceobjrunning): - if self.brctlcmd.get_stp(ifaceobjrunning.name) == 'no': + if self.brctlcmd.bridge_get_stp(ifaceobjrunning.name) == 'no': # This bridge does not run stp, return return # if userspace stp not set, return @@ -1072,10 +1153,11 @@ class mstpctl(moduleBase): for bridge in masters: bifaceobj = ifaceobj_getfunc(bridge)[0] if (self._is_bridge(bifaceobj) and - self.default_vxlan_ports_set_bpduparams and + self.set_default_mstp_vxlan_bridge_config and (bifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_VLAN_AWARE)): - for attr in ['mstpctl-portbpdufilter', - 'mstpctl-bpduguard']: + for attr in ('mstpctl-portbpdufilter', + 'mstpctl-bpduguard', + 'mstpctl-portadminedge'): jsonAttr = self.get_mod_subattr(attr, 'jsonAttr') config_val = ifaceobj.get_attr_value_first(attr) if config_val or not ifupdownflags.flags.WITHDEFAULTS: @@ -1102,7 +1184,7 @@ class mstpctl(moduleBase): if not lowerinfs: return if ifaceobj.get_attr_value_first('bridge-vlan-aware') != 'yes': - for attr in ['mstpctl-portbpdufilter', 'mstpctl-bpduguard']: + for attr in ('mstpctl-portbpdufilter', 'mstpctl-bpduguard', 'mstpctl-portadminedge'): state = '' config = ifaceobj.get_attr_value_first(attr) for port in lowerinfs: @@ -1116,7 +1198,7 @@ class mstpctl(moduleBase): state += '%s=yes ' %port ifaceobj.replace_config(attr, config if config else state) else: - for attr in ['mstpctl-portbpdufilter', 'mstpctl-bpduguard']: + for attr in ('mstpctl-portbpdufilter', 'mstpctl-bpduguard', 'mstpctl-portadminedge'): state = '' config = ifaceobj.get_attr_value_first(attr) for port in lowerinfs: @@ -1158,9 +1240,7 @@ class mstpctl(moduleBase): def _init_command_handlers(self): if not self.ipcmd: - self.ipcmd = iproute2() - if not self.brctlcmd: - self.brctlcmd = brctl() + self.ipcmd = self.brctlcmd = LinkUtils() if not self.mstpctlcmd: self.mstpctlcmd = mstpctlutil() diff --git a/addons/usercmds.py b/ifupdown2/addons/usercmds.py similarity index 92% rename from addons/usercmds.py rename to ifupdown2/addons/usercmds.py index 212d661..863f57f 100644 --- a/addons/usercmds.py +++ b/ifupdown2/addons/usercmds.py @@ -1,16 +1,22 @@ #!/usr/bin/python # -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. # Author: Roopa Prabhu, roopa@cumulusnetworks.com # import os -import ifupdownaddons -from ifupdown.utils import utils -import ifupdown.ifupdownflags as ifupdownflags +try: + from ifupdown2.ifupdown.utils import utils -class usercmds(ifupdownaddons.modulebase.moduleBase): + from ifupdown2.ifupdownaddons.modulebase import moduleBase +except ImportError: + from ifupdown.utils import utils + + from ifupdownaddons.modulebase import moduleBase + + +class usercmds(moduleBase): """ ifupdown2 addon module to configure user specified commands """ _modinfo = {'mhelp' : 'user commands for interfaces', diff --git a/addons/vlan.py b/ifupdown2/addons/vlan.py similarity index 73% rename from addons/vlan.py rename to ifupdown2/addons/vlan.py index 0bdab46..c7b2c0f 100644 --- a/addons/vlan.py +++ b/ifupdown2/addons/vlan.py @@ -1,18 +1,27 @@ #!/usr/bin/python # -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. # Author: Roopa Prabhu, roopa@cumulusnetworks.com # -from ifupdown.iface import * -from ifupdownaddons.modulebase import moduleBase -from ifupdownaddons.iproute2 import iproute2 -import ifupdown.ifupdownconfig as ifupdownConfig +try: + import ifupdown2.ifupdown.ifupdownflags as ifupdownflags + + from ifupdown2.ifupdown.iface import * + from ifupdown2.ifupdown.netlink import netlink + + from ifupdown2.ifupdownaddons.LinkUtils import LinkUtils + from ifupdown2.ifupdownaddons.modulebase import moduleBase +except ImportError: + import ifupdown.ifupdownflags as ifupdownflags + + from ifupdown.iface import * + from ifupdown.netlink import netlink + + from ifupdownaddons.LinkUtils import LinkUtils + from ifupdownaddons.modulebase import moduleBase + -from ifupdown.netlink import netlink -import ifupdown.ifupdownflags as ifupdownflags -import logging -import re class vlan(moduleBase): """ ifupdown2 addon module to configure vlans """ @@ -28,16 +37,18 @@ class vlan(moduleBase): 'validvals': ['']}, 'vlan-id' : {'help' : 'vlan id', - 'validrange' : ['0', '4096']}}} + 'validrange' : ['0', '4096']}, + 'vlan-protocol' : + {'help' : 'vlan protocol', + 'default' : '802.1q', + 'validvals': ['802.1q', '802.1ad'], + 'example' : ['vlan-protocol 802.1q']}, + }} def __init__(self, *args, **kargs): moduleBase.__init__(self, *args, **kargs) self.ipcmd = None - self._bridge_vids_query_cache = {} - self._resv_vlan_range = self._get_reserved_vlan_range() - self.logger.debug('%s: using reserved vlan range %s' - %(self.__class__.__name__, str(self._resv_vlan_range))) def _is_vlan_device(self, ifaceobj): vlan_raw_device = ifaceobj.get_attr_value_first('vlan-raw-device') @@ -47,34 +58,6 @@ class vlan(moduleBase): return True return False - def _get_vlan_id(self, ifaceobj): - """ Derives vlanid from iface name - - Example: - Returns 1 for ifname vlan0001 returns 1 - Returns 1 for ifname vlan1 - Returns 1 for ifname eth0.1 - - Returns -1 if vlan id cannot be determined - """ - vid_str = ifaceobj.get_attr_value_first('vlan-id') - try: - if vid_str: return int(vid_str) - except: - return -1 - - if '.' in ifaceobj.name: - vid_str = ifaceobj.name.split('.', 1)[1] - elif ifaceobj.name.startswith('vlan'): - vid_str = ifaceobj.name[4:] - else: - return -1 - try: - vid = int(vid_str) - except: - return -1 - return vid - def _is_vlan_by_name(self, ifacename): return '.' in ifacename @@ -82,13 +65,15 @@ class vlan(moduleBase): """ Returns vlan raw device from ifname Example: Returns eth0 for ifname eth0.100 - + Returns eth0.100 for ifname eth0.100.200 Returns None if vlan raw device name cannot be determined """ - vlist = ifacename.split('.', 1) + vlist = ifacename.split('.', 2) if len(vlist) == 2: return vlist[0] + elif len(vlist) == 3: + return vlist[0] + "." + vlist[1] return None def _get_vlan_raw_device(self, ifaceobj): @@ -118,10 +103,7 @@ class vlan(moduleBase): is configured on the bridge """ if not self.ipcmd.bridge_is_vlan_aware(bridgename): return - vids = self._bridge_vids_query_cache.get(bridgename) - if vids == None: - vids = self.ipcmd.bridge_port_vids_get(bridgename) - self._bridge_vids_query_cache[bridgename] = vids + vids = self.ipcmd.bridge_vlan_get_vids(bridgename) if not vids or vlanid not in vids: ifaceobjcurr.status = ifaceStatus.ERROR ifaceobjcurr.status_str = 'bridge vid error' @@ -133,13 +115,27 @@ class vlan(moduleBase): vlanrawdevice = self._get_vlan_raw_device(ifaceobj) if not vlanrawdevice: raise Exception('could not determine vlan raw device') + + vlan_protocol = ifaceobj.get_attr_value_first('vlan-protocol') + cached_vlan_protocol = self.ipcmd.get_vlan_protocol(ifaceobj.name) + + if not vlan_protocol: + vlan_protocol = self.get_attr_default_value('vlan-protocol') + + if cached_vlan_protocol and vlan_protocol.lower() != cached_vlan_protocol.lower(): + raise Exception('%s: cannot change vlan-protocol to %s: operation not supported. ' + 'Please delete the device with \'ifdown %s\' and recreate it to ' + 'apply the change.' + % (ifaceobj.name, vlan_protocol, ifaceobj.name)) + if not ifupdownflags.flags.PERFMODE: if not self.ipcmd.link_exists(vlanrawdevice): raise Exception('rawdevice %s not present' %vlanrawdevice) if self.ipcmd.link_exists(ifaceobj.name): self._bridge_vid_add_del(ifaceobj, vlanrawdevice, vlanid) return - netlink.link_add_vlan(vlanrawdevice, ifaceobj.name, vlanid) + + netlink.link_add_vlan(vlanrawdevice, ifaceobj.name, vlanid, vlan_protocol) self._bridge_vid_add_del(ifaceobj, vlanrawdevice, vlanid) def _down(self, ifaceobj): @@ -163,7 +159,7 @@ class vlan(moduleBase): return if not '.' in ifaceobj.name: # if vlan name is not in the dot format, check its running state - (vlanrawdev, vlanid) = self.ipcmd.get_vlandev_attrs(ifaceobj.name) + (vlanrawdev, vlanid, protocol) = self.ipcmd.get_vlandev_attrs(ifaceobj.name) if vlanrawdev != ifaceobj.get_attr_value_first('vlan-raw-device'): ifaceobjcurr.update_config_with_status('vlan-raw-device', vlanrawdev, 1) @@ -177,20 +173,29 @@ class vlan(moduleBase): ifaceobjcurr.update_config_with_status('vlan-id', vlanid, 1) else: ifaceobjcurr.update_config_with_status('vlan-id', vlanid, 0) - self._bridge_vid_check(ifaceobj, ifaceobjcurr, vlanrawdev, vlanid) + protocol_config = ifaceobj.get_attr_value_first('vlan-protocol') + if protocol_config: + if protocol_config.upper() != protocol.upper(): + ifaceobjcurr.update_config_with_status('vlan-protocol', + protocol, 1) + else: + ifaceobjcurr.update_config_with_status('vlan-protocol', + protocol, 0) + self._bridge_vid_check(ifaceobj, ifaceobjcurr, vlanrawdev, int(vlanid)) def _query_running(self, ifaceobjrunning): if not self.ipcmd.link_exists(ifaceobjrunning.name): return - (vlanrawdev, vlanid) = self.ipcmd.get_vlandev_attrs(ifaceobjrunning.name) + (vlanrawdev, vlanid, protocol) = self.ipcmd.get_vlandev_attrs(ifaceobjrunning.name) if not vlanid: return # If vlan name is not in the dot format, get the # vlan dev and vlan id if not '.' in ifaceobjrunning.name: - ifaceobjrunning.update_config_dict({(k, v) for k, v in + ifaceobjrunning.update_config_dict({k: [v] for k, v in {'vlan-raw-device' : vlanrawdev, - 'vlan-id' : vlanid}.items() + 'vlan-id' : vlanid, + 'vlan-protocol' : protocol}.items() if v}) _run_ops = {'pre-up' : _up, @@ -204,7 +209,7 @@ class vlan(moduleBase): def _init_command_handlers(self): if not self.ipcmd: - self.ipcmd = iproute2() + self.ipcmd = LinkUtils() def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): """ run vlan configuration on the interface object passed as argument diff --git a/addons/vrf.py b/ifupdown2/addons/vrf.py similarity index 85% rename from addons/vrf.py rename to ifupdown2/addons/vrf.py index 5e46aa3..7c98d9a 100644 --- a/addons/vrf.py +++ b/ifupdown2/addons/vrf.py @@ -1,27 +1,42 @@ #!/usr/bin/python # -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. # Author: Roopa Prabhu, roopa@cumulusnetworks.com # +import re import os -import signal -import errno import fcntl import atexit -import re +import signal + from sets import Set -from ifupdown.iface import * -from ifupdown.utils import utils -import ifupdown.policymanager as policymanager -import ifupdownaddons -from ifupdown.netlink import netlink -import ifupdown.ifupdownflags as ifupdownflags -from ifupdownaddons.modulebase import moduleBase -from ifupdownaddons.bondutil import bondutil -from ifupdownaddons.iproute2 import iproute2 -from ifupdownaddons.dhclient import dhclient -from ifupdownaddons.utilsbase import * + +try: + import ifupdown2.ifupdown.policymanager as policymanager + import ifupdown2.ifupdown.ifupdownflags as ifupdownflags + + from ifupdown2.ifupdown.iface import * + from ifupdown2.ifupdown.utils import utils + from ifupdown2.ifupdown.netlink import netlink + + from ifupdown2.ifupdownaddons.dhclient import dhclient + from ifupdown2.ifupdownaddons.utilsbase import * + from ifupdown2.ifupdownaddons.LinkUtils import LinkUtils + from ifupdown2.ifupdownaddons.modulebase import moduleBase +except ImportError: + import ifupdown.policymanager as policymanager + import ifupdown.ifupdownflags as ifupdownflags + + from ifupdown.iface import * + from ifupdown.utils import utils + from ifupdown.netlink import netlink + + from ifupdownaddons.dhclient import dhclient + from ifupdownaddons.utilsbase import * + from ifupdownaddons.LinkUtils import LinkUtils + from ifupdownaddons.modulebase import moduleBase + class vrfPrivFlags: PROCESSED = 0x1 @@ -49,17 +64,19 @@ class vrf(moduleBase): VRF_TABLE_START = 1001 VRF_TABLE_END = 5000 - system_reserved_rt_tables = {'255' : 'local', '254' : 'main', + system_reserved_rt_tables = {'255' : 'local', '254' : 'main', '253' : 'default', '0' : 'unspec'} def __init__(self, *args, **kargs): - ifupdownaddons.modulebase.moduleBase.__init__(self, *args, **kargs) + moduleBase.__init__(self, *args, **kargs) self.ipcmd = None self.bondcmd = None self.dhclientcmd = None self.name = self.__class__.__name__ self.vrf_mgmt_devname = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vrf-mgmt-devname') + self.user_reserved_vrf_table = [] + if (ifupdownflags.flags.PERFMODE and not (self.vrf_mgmt_devname and os.path.exists('/sys/class/net/%s' %self.vrf_mgmt_devname))): @@ -79,14 +96,16 @@ class vrf(moduleBase): self.logger.debug('vrf: removing file failed (%s)' %str(e)) try: - ip_rules = utils.exec_command('/sbin/ip rule show').splitlines() + ip_rules = utils.exec_command('%s rule show' + %utils.ip_cmd).splitlines() self.ip_rule_cache = [' '.join(r.split()) for r in ip_rules] except Exception, e: self.ip_rule_cache = [] self.logger.warn('vrf: cache v4: %s' % str(e)) try: - ip_rules = utils.exec_command('/sbin/ip -6 rule show').splitlines() + ip_rules = utils.exec_command('%s -6 rule show' + %utils.ip_cmd).splitlines() self.ip6_rule_cache = [' '.join(r.split()) for r in ip_rules] except Exception, e: self.ip6_rule_cache = [] @@ -118,6 +137,12 @@ class vrf(moduleBase): self.vrf_table_id_end = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vrf-table-id-end') if not self.vrf_table_id_end: self.vrf_table_id_end = self.VRF_TABLE_END + + self._modinfo['attrs']['vrf-table']['validrange'] = [ + str(self.vrf_table_id_start), + str(self.vrf_table_id_end) + ] + self.vrf_max_count = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vrf-max-count') self.vrf_fix_local_table = True @@ -126,6 +151,40 @@ class vrf(moduleBase): self.vrf_close_socks_on_down = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vrf-close-socks-on-down') self.warn_on_vrf_map_write_err = True + def _check_vrf_table_id(self, ifaceobj): + vrf_table = ifaceobj.get_attr_value_first('vrf-table') + if not vrf_table: + return False + if (vrf_table != 'auto' and + (int(vrf_table) < self.vrf_table_id_start or + int(vrf_table) > self.vrf_table_id_end)): + self.logger.error('%s: vrf table id %s out of reserved range [%d,%d]' + %(ifaceobj.name, + vrf_table, + self.vrf_table_id_start, + self.vrf_table_id_end)) + return False + return True + + def syntax_check(self, ifaceobj, ifaceobj_getfunc): + if ifaceobj.link_kind & ifaceLinkKind.VRF: + try: + check_vrf_table_id = self._check_vrf_table_id(ifaceobj) + check_vrf_sys_names = self._check_vrf_system_reserved_names(ifaceobj) + return check_vrf_table_id and check_vrf_sys_names + except Exception as e: + self.logger.error('%s: %s' % (ifaceobj.name, str(e))) + return False + return True + + def _check_vrf_system_reserved_names(self, ifaceobj): + system_reserved_names = self.system_reserved_rt_tables.values() + if ifaceobj.name in system_reserved_names: + self.log_error('cannot use system reserved %s vrf names' + % (str(system_reserved_names)), ifaceobj) + return False + return True + def _iproute2_vrf_map_initialize(self, writetodisk=True): if self._iproute2_vrf_map_initialized: return @@ -256,6 +315,14 @@ class vrf(moduleBase): ifaceobj.link_type = ifaceLinkType.LINK_MASTER ifaceobj.link_kind |= ifaceLinkKind.VRF ifaceobj.role |= ifaceRole.MASTER + + if vrf_table != 'auto': + # if the user didn't specify auto we need to store the desired + # vrf tables ids, in case the configuration has both auto and + # hardcoded vrf-table ids. We need to create them all without + # collisions. + self.user_reserved_vrf_table.append(int(vrf_table)) + vrf_iface_name = ifaceobj.get_attr_value_first('vrf') if not vrf_iface_name: return None @@ -278,9 +345,9 @@ class vrf(moduleBase): table_id_start = self.vrf_table_id_start else: table_id_start = self.last_used_vrf_table + 1 - for t in range(table_id_start, - self.vrf_table_id_end): - if not self.iproute2_vrf_map.get(t): + for t in range(table_id_start, self.vrf_table_id_end): + if (not self.iproute2_vrf_map.get(t) + and t not in self.user_reserved_vrf_table): self.last_used_vrf_table = t return str(t) return None @@ -330,13 +397,22 @@ class vrf(moduleBase): return False return True - def _up_vrf_slave_without_master(self, ifacename, vrfname, ifaceobj, - vrf_master_objs): + def _up_vrf_slave_without_master(self, ifacename, vrfname, ifaceobj, vrf_master_objs, ifaceobj_getfunc=None): """ If we have a vrf slave that has dhcp configured, bring up the vrf master now. This is needed because vrf has special handling in dhclient hook which requires the vrf master to be present """ - - vrf_master = ifaceobj.upperifaces[0] + vrf_master = None + if len(ifaceobj.upperifaces) > 1 and ifaceobj_getfunc: + for upper_iface in ifaceobj.upperifaces: + upper_ifaceobjs = ifaceobj_getfunc(upper_iface) + + if upper_ifaceobjs: + for upper_obj in upper_ifaceobjs: + if upper_obj.link_kind & ifaceLinkKind.VRF: + vrf_master = upper_obj.name + break + elif ifaceobj.upperifaces: + vrf_master = ifaceobj.upperifaces[0] if not vrf_master: self.logger.warn('%s: vrf master not found' %ifacename) return @@ -362,7 +438,7 @@ class vrf(moduleBase): raise break self._handle_existing_connections(ifaceobj, vrfname) - netlink.link_set_master(ifacename, vrfname) + self.ipcmd.link_set(ifacename, 'master', vrfname) return def _down_dhcp_slave(self, ifaceobj, vrfname): @@ -394,7 +470,7 @@ class vrf(moduleBase): uppers = self.ipcmd.link_get_uppers(ifacename) if not uppers or vrfname not in uppers: self._handle_existing_connections(ifaceobj, vrfname) - netlink.link_set_master(ifacename, vrfname) + self.ipcmd.link_set(ifacename, 'master', vrfname) elif ifaceobj: vrf_master_objs = ifaceobj_getfunc(vrfname) if not vrf_master_objs: @@ -413,7 +489,8 @@ class vrf(moduleBase): Set(ifaceobj.classes).intersection(vrf_master_objs[0].classes))): self._up_vrf_slave_without_master(ifacename, vrfname, ifaceobj, - vrf_master_objs) + vrf_master_objs, + ifaceobj_getfunc) else: master_exists = False else: @@ -429,29 +506,33 @@ class vrf(moduleBase): def _del_vrf_rules(self, vrf_dev_name, vrf_table): pref = 200 ip_rule_out_format = '%s: from all %s %s lookup %s' - ip_rule_cmd = 'ip %s rule del pref %s %s %s table %s' + ip_rule_cmd = '%s %s rule del pref %s %s %s table %s' rule = ip_rule_out_format %(pref, 'oif', vrf_dev_name, vrf_dev_name) if rule in self.ip_rule_cache: - rule_cmd = ip_rule_cmd %('', pref, 'oif', vrf_dev_name, + rule_cmd = ip_rule_cmd %(utils.ip_cmd, + '', pref, 'oif', vrf_dev_name, vrf_dev_name) utils.exec_command(rule_cmd) rule = ip_rule_out_format %(pref, 'iif', vrf_dev_name, vrf_dev_name) if rule in self.ip_rule_cache: - rule_cmd = ip_rule_cmd %('', pref, 'iif', vrf_dev_name, + rule_cmd = ip_rule_cmd %(utils.ip_cmd, + '', pref, 'iif', vrf_dev_name, vrf_dev_name) utils.exec_command(rule_cmd) rule = ip_rule_out_format %(pref, 'oif', vrf_dev_name, vrf_dev_name) if rule in self.ip6_rule_cache: - rule_cmd = ip_rule_cmd %('-6', pref, 'oif', vrf_dev_name, + rule_cmd = ip_rule_cmd %(utils.ip_cmd, + '-6', pref, 'oif', vrf_dev_name, vrf_dev_name) utils.exec_command(rule_cmd) rule = ip_rule_out_format %(pref, 'iif', vrf_dev_name, vrf_dev_name) if rule in self.ip6_rule_cache: - rule_cmd = ip_rule_cmd %('-6', pref, 'iif', vrf_dev_name, + rule_cmd = ip_rule_cmd %(utils.ip_cmd, + '-6', pref, 'iif', vrf_dev_name, vrf_dev_name) utils.exec_command(rule_cmd) @@ -464,31 +545,37 @@ class vrf(moduleBase): return False def _rule_cache_fill(self): - ip_rules = utils.exec_command('/sbin/ip rule show').splitlines() + ip_rules = utils.exec_command('%s rule show' + %utils.ip_cmd).splitlines() self.ip_rule_cache = [' '.join(r.split()) for r in ip_rules] self.l3mdev4_rule = self._l3mdev_rule(self.ip_rule_cache) - ip_rules = utils.exec_command('/sbin/ip -6 rule show').splitlines() + ip_rules = utils.exec_command('%s -6 rule show' + %utils.ip_cmd).splitlines() self.ip6_rule_cache = [' '.join(r.split()) for r in ip_rules] self.l3mdev6_rule = self._l3mdev_rule(self.ip6_rule_cache) def _add_vrf_rules(self, vrf_dev_name, vrf_table): pref = 200 ip_rule_out_format = '%s: from all %s %s lookup %s' - ip_rule_cmd = 'ip %s rule add pref %s %s %s table %s' + ip_rule_cmd = '%s %s rule add pref %s %s %s table %s' if self.vrf_fix_local_table: self.vrf_fix_local_table = False rule = '0: from all lookup local' if rule in self.ip_rule_cache: try: - utils.exec_command('ip rule del pref 0') - utils.exec_command('ip rule add pref 32765 table local') + utils.exec_command('%s rule del pref 0' + %utils.ip_cmd) + utils.exec_command('%s rule add pref 32765 table local' + %utils.ip_cmd) except Exception, e: self.logger.info('%s: %s' % (vrf_dev_name, str(e))) pass if rule in self.ip6_rule_cache: try: - utils.exec_command('ip -6 rule del pref 0') - utils.exec_command('ip -6 rule add pref 32765 table local') + utils.exec_command('%s -6 rule del pref 0' + %utils.ip_cmd) + utils.exec_command('%s -6 rule add pref 32765 table local' + %utils.ip_cmd) except Exception, e: self.logger.info('%s: %s' % (vrf_dev_name, str(e))) pass @@ -502,25 +589,29 @@ class vrf(moduleBase): rule = ip_rule_out_format %(pref, 'oif', vrf_dev_name, vrf_dev_name) if not self.l3mdev4_rule and rule not in self.ip_rule_cache: - rule_cmd = ip_rule_cmd %('', pref, 'oif', vrf_dev_name, + rule_cmd = ip_rule_cmd %(utils.ip_cmd, + '', pref, 'oif', vrf_dev_name, vrf_dev_name) utils.exec_command(rule_cmd) rule = ip_rule_out_format %(pref, 'iif', vrf_dev_name, vrf_dev_name) if not self.l3mdev4_rule and rule not in self.ip_rule_cache: - rule_cmd = ip_rule_cmd %('', pref, 'iif', vrf_dev_name, + rule_cmd = ip_rule_cmd %(utils.ip_cmd, + '', pref, 'iif', vrf_dev_name, vrf_dev_name) utils.exec_command(rule_cmd) rule = ip_rule_out_format %(pref, 'oif', vrf_dev_name, vrf_dev_name) if not self.l3mdev6_rule and rule not in self.ip6_rule_cache: - rule_cmd = ip_rule_cmd %('-6', pref, 'oif', vrf_dev_name, + rule_cmd = ip_rule_cmd %(utils.ip_cmd, + '-6', pref, 'oif', vrf_dev_name, vrf_dev_name) utils.exec_command(rule_cmd) rule = ip_rule_out_format %(pref, 'iif', vrf_dev_name, vrf_dev_name) if not self.l3mdev6_rule and rule not in self.ip6_rule_cache: - rule_cmd = ip_rule_cmd %('-6', pref, 'iif', vrf_dev_name, + rule_cmd = ip_rule_cmd %(utils.ip_cmd, + '-6', pref, 'iif', vrf_dev_name, vrf_dev_name) utils.exec_command(rule_cmd) @@ -533,7 +624,7 @@ class vrf(moduleBase): # XXX: additional possible checks that can be done here # are: # - check if it is also a macvlan device of the - # format -v created by the + # format -v created by the # address virtual module vrfslave_lowers = self.ipcmd.link_get_lowers(vrfslave) if vrfslave_lowers: @@ -599,10 +690,8 @@ class vrf(moduleBase): def _create_vrf_dev(self, ifaceobj, vrf_table): if not self.ipcmd.link_exists(ifaceobj.name): - if ifaceobj.name in self.system_reserved_rt_tables.values(): - self.log_error('cannot use system reserved %s vrf names' - %(str(self.system_reserved_rt_tables.values())), - ifaceobj) + self._check_vrf_system_reserved_names(ifaceobj) + if self.vrf_count == self.vrf_max_count: self.log_error('max vrf count %d hit...not ' 'creating vrf' % self.vrf_count, ifaceobj) @@ -641,7 +730,7 @@ class vrf(moduleBase): else: if vrf_table == 'auto': vrf_table = self._get_iproute2_vrf_table(ifaceobj.name) - if not vrf_table: + if not vrf_table and not ifupdownflags.flags.DRYRUN: self.log_error('unable to get vrf table id', ifaceobj) # if the device exists, check if table id is same @@ -678,7 +767,7 @@ class vrf(moduleBase): vrf_table = self._create_vrf_dev(ifaceobj, vrf_table) except Exception, e: self.log_error('%s: %s' %(ifaceobj.name, str(e)), ifaceobj) - + try: self._add_vrf_rules(ifaceobj.name, vrf_table) self._up_vrf_helper(ifaceobj, vrf_table) @@ -691,7 +780,7 @@ class vrf(moduleBase): def _kill_ssh_connections(self, ifacename): try: - runningaddrsdict = self.ipcmd.addr_get(ifacename) + runningaddrsdict = self.ipcmd.get_running_addrs(None, ifacename) if not runningaddrsdict: return iplist = [i.split('/', 1)[0] for i in runningaddrsdict.keys()] @@ -699,9 +788,9 @@ class vrf(moduleBase): return proc=[] #Example output: - #ESTAB 0 0 10.0.1.84:ssh 10.0.1.228:45186 + #ESTAB 0 0 10.0.1.84:ssh 10.0.1.228:45186 #users:(("sshd",pid=2528,fd=3)) - cmdl = ['/bin/ss', '-t', '-p'] + cmdl = [utils.ss_cmd, '-t', '-p'] for line in utils.exec_commandl(cmdl).splitlines(): citems = line.split() addr = None @@ -722,7 +811,8 @@ class vrf(moduleBase): # 'systemd(1)---sshd(990)---sshd(16112)---sshd(16126)---bash(16127)---sudo(16756)---ifreload(16761)---pstree(16842)\n' # get the above output to following format # ['systemd(1)', 'sshd(990)', 'sshd(16112)', 'sshd(16126)', 'bash(16127)', 'sudo(16756)', 'ifreload(16761)', 'pstree(16850)'] - pstree = list(reversed(utils.exec_command('/usr/bin/pstree -Aps %s' %os.getpid()).strip().split('---'))) + pstree = list(reversed(utils.exec_command('%s -Aps %s' % + (utils.pstree_cmd, os.getpid())).strip().split('---'))) for index, process in enumerate(pstree): # check the parent of SSH process to make sure # we don't kill SSH server or systemd process @@ -778,7 +868,7 @@ class vrf(moduleBase): # This is a vrf slave self._up_vrf_slave(ifaceobj.name, vrf, ifaceobj, ifaceobj_getfunc) - else: + elif not ifupdownflags.flags.PERFMODE: # check if we were a slave before master = self.ipcmd.link_get_master(ifaceobj.name) if master: @@ -805,8 +895,8 @@ class vrf(moduleBase): return try: - utils.exec_command('/bin/ss -aK \"dev == %s\"' - %ifindex) + utils.exec_command('%s -aK \"dev == %s\"' + %(utils.ss_cmd, ifindex)) except Exception, e: self.logger.info('%s: closing socks using ss' ' failed (%s)\n' %(ifaceobj.name, str(e))) @@ -853,11 +943,12 @@ class vrf(moduleBase): ifindex = self.ipcmd.link_get_ifindex(ifaceobj.name) - try: - self.ipcmd.link_delete(ifaceobj.name) - except Exception, e: - self.logger.info('%s: %s' %(ifaceobj.name, str(e))) - pass + if ifindex: + try: + self.ipcmd.link_delete(ifaceobj.name) + except Exception, e: + self.logger.info('%s: %s' %(ifaceobj.name, str(e))) + pass self._close_sockets(ifaceobj, ifindex) @@ -871,7 +962,7 @@ class vrf(moduleBase): def _down_vrf_slave(self, ifacename, ifaceobj=None, vrfname=None): try: self._handle_existing_connections(ifaceobj, vrfname) - netlink.link_set_nomaster(ifacename) + self.ipcmd.link_set(ifacename, 'nomaster') # Down this slave only if it is a slave ifupdown2 manages. # we dont want to down slaves that maybe up'ed by # somebody else. One such example is a macvlan device @@ -1002,9 +1093,7 @@ class vrf(moduleBase): def _init_command_handlers(self): if not self.ipcmd: - self.ipcmd = iproute2() - if not self.bondcmd: - self.bondcmd = bondutil() + self.ipcmd = self.bondcmd = LinkUtils() if not self.dhclientcmd: self.dhclientcmd = dhclient() diff --git a/addons/vrrpd.py b/ifupdown2/addons/vrrpd.py similarity index 87% rename from addons/vrrpd.py rename to ifupdown2/addons/vrrpd.py index 6d1ee7d..77ada84 100644 --- a/addons/vrrpd.py +++ b/ifupdown2/addons/vrrpd.py @@ -1,24 +1,29 @@ #!/usr/bin/python # -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. # Author: Roopa Prabhu, roopa@cumulusnetworks.com # +import os +import re +import glob +import signal + try: - from ipaddr import IPNetwork - from sets import Set + import ifupdown2.ifupdown.ifupdownflags as ifupdownflags + + from ifupdown2.ifupdownaddons.modulebase import moduleBase + + from ifupdown2.ifupdown.iface import * + from ifupdown2.ifupdown.utils import utils +except ImportError: + import ifupdown.ifupdownflags as ifupdownflags + + from ifupdownaddons.modulebase import moduleBase + from ifupdown.iface import * from ifupdown.utils import utils - from ifupdownaddons.modulebase import moduleBase - from ifupdownaddons.iproute2 import iproute2 - import ifupdown.ifupdownflags as ifupdownflags - import os - import glob - import logging - import signal - import re -except ImportError, e: - raise ImportError (str(e) + "- required module not found") + class vrrpd(moduleBase): """ ifupdown2 addon module to configure vrrpd attributes """ @@ -40,13 +45,12 @@ class vrrpd(moduleBase): def __init__(self, *args, **kargs): moduleBase.__init__(self, *args, **kargs) - self.ipcmd = None def _check_if_process_is_running(self, cmdname, cmdline): targetpids = [] pidstr = '' try: - cmdl = ['/bin/pidof', cmdname] + cmdl = [utils.pidof_cmd, cmdname] pidstr = utils.exec_commandl(cmdl, stderr=None).strip('\n') except: pass @@ -69,7 +73,7 @@ class vrrpd(moduleBase): except: pass return targetpids - + def _up(self, ifaceobj): """ up vrrpd -n -D -i $IFACE -v 1 -p 20 10.0.1.254 up ifplugd -i $IFACE -b -f -u0 -d1 -I -p -q """ @@ -97,11 +101,13 @@ class vrrpd(moduleBase): self.logger.warn('%s: incomplete vrrp arguments ' %ifaceobj.name, '(virtual ip not found)') return - cmd = '/usr/sbin/vrrpd -n -D -i %s %s' %(ifaceobj.name, cmd) + cmd = ('%s -n -D -i %s %s' % + (utils.vrrpd_cmd, ifaceobj.name, cmd)) utils.exec_command(cmd) - cmd = '/usr/sbin/ifplugd -i %s -b -f -u0 -d1 -I -p -q' %ifaceobj.name - if self._check_if_process_is_running('/usr/sbin/ifplugd', cmd): + cmd = ('%s -i %s -b -f -u0 -d1 -I -p -q' % + (utils.ifplugd_cmd, ifaceobj.name)) + if self._check_if_process_is_running(utils.ifplugd_cmd, cmd): self.logger.info('%s: ifplugd already running' %ifaceobj.name) return utils.exec_command(cmd) @@ -119,7 +125,8 @@ class vrrpd(moduleBase): if not attrval: return try: - utils.exec_command('/usr/sbin/ifplugd -k -i %s' % ifaceobj.name) + utils.exec_command('%s -k -i %s' % + (utils.ifplugd_cmd, ifaceobj.name)) except Exception, e: self.logger.debug('%s: ifplugd down error (%s)' %(ifaceobj.name, str(e))) diff --git a/ifupdown2/addons/vxlan.py b/ifupdown2/addons/vxlan.py new file mode 100644 index 0000000..3148e44 --- /dev/null +++ b/ifupdown2/addons/vxlan.py @@ -0,0 +1,523 @@ +#!/usr/bin/python +# +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + + +from sets import Set +from ipaddr import IPNetwork, IPv4Address, IPv4Network, AddressValueError + +try: + import ifupdown2.ifupdown.policymanager as policymanager + + from ifupdown2.nlmanager.nlmanager import Link + + from ifupdown2.ifupdown.iface import * + from ifupdown2.ifupdown.utils import utils + from ifupdown2.ifupdown.netlink import netlink + from ifupdown2.ifupdownaddons.cache import * + from ifupdown2.ifupdownaddons.LinkUtils import LinkUtils + from ifupdown2.ifupdownaddons.modulebase import moduleBase +except ImportError: + import ifupdown.policymanager as policymanager + + from nlmanager.nlmanager import Link + + from ifupdown.iface import * + from ifupdown.utils import utils + from ifupdown.netlink import netlink + + from ifupdownaddons.cache import * + from ifupdownaddons.LinkUtils import LinkUtils + from ifupdownaddons.modulebase import moduleBase + + +class vxlan(moduleBase): + _modinfo = {'mhelp' : 'vxlan module configures vxlan interfaces.', + 'attrs' : { + 'vxlan-id' : + {'help' : 'vxlan id', + 'validrange' : ['1', '16777214'], + 'required' : True, + 'example': ['vxlan-id 100']}, + 'vxlan-local-tunnelip' : + {'help' : 'vxlan local tunnel ip', + 'validvals' : [''], + 'example': ['vxlan-local-tunnelip 172.16.20.103']}, + 'vxlan-svcnodeip' : + {'help' : 'vxlan id', + 'validvals' : [''], + 'example': ['vxlan-svcnodeip 172.16.22.125']}, + 'vxlan-remoteip' : + {'help' : 'vxlan remote ip', + 'validvals' : [''], + 'example': ['vxlan-remoteip 172.16.22.127'], + 'multiline': True}, + 'vxlan-learning' : + {'help' : 'vxlan learning yes/no', + 'validvals' : ['yes', 'no', 'on', 'off'], + 'example': ['vxlan-learning no'], + 'default': 'yes'}, + 'vxlan-ageing' : + {'help' : 'vxlan aging timer', + 'validrange' : ['0', '4096'], + 'example': ['vxlan-ageing 300'], + 'default': '300'}, + 'vxlan-purge-remotes' : + {'help' : 'vxlan purge existing remote entries', + 'validvals' : ['yes', 'no'], + 'example': ['vxlan-purge-remotes yes'],}, + 'vxlan-port': { + 'help': 'vxlan UDP port (transmitted to vxlan driver)', + 'validvals': [''], + 'example': 'vxlan-port 4789', + 'validrange': ['1', '65536'], + 'default': '4789', + } + }} + _clagd_vxlan_anycast_ip = "" + _vxlan_local_tunnelip = None + + def __init__(self, *args, **kargs): + moduleBase.__init__(self, *args, **kargs) + self.ipcmd = None + purge_remotes = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vxlan-purge-remotes') + if purge_remotes: + self._purge_remotes = utils.get_boolean_from_string(purge_remotes) + else: + self._purge_remotes = False + + def syntax_check(self, ifaceobj, ifaceobj_getfunc): + if self._is_vxlan_device(ifaceobj): + if not ifaceobj.get_attr_value_first('vxlan-local-tunnelip') and not vxlan._vxlan_local_tunnelip: + self.logger.warning('%s: missing vxlan-local-tunnelip' % ifaceobj.name) + return False + return self.syntax_check_localip_anycastip_equal( + ifaceobj.name, + ifaceobj.get_attr_value_first('vxlan-local-tunnelip') or vxlan._vxlan_local_tunnelip, + vxlan._clagd_vxlan_anycast_ip + ) + return True + + def syntax_check_localip_anycastip_equal(self, ifname, local_ip, anycast_ip): + try: + if IPNetwork(local_ip) == IPNetwork(anycast_ip): + self.logger.warning('%s: vxlan-local-tunnelip and clagd-vxlan-anycast-ip are identical (%s)' + % (ifname, local_ip)) + return False + except: + pass + return True + + def get_dependent_ifacenames(self, ifaceobj, ifaceobjs_all=None): + if self._is_vxlan_device(ifaceobj): + ifaceobj.link_kind |= ifaceLinkKind.VXLAN + self._set_global_local_ip(ifaceobj) + elif ifaceobj.name == 'lo': + clagd_vxlan_list = ifaceobj.get_attr_value('clagd-vxlan-anycast-ip') + if clagd_vxlan_list: + if len(clagd_vxlan_list) != 1: + self.log_warn('%s: multiple clagd-vxlan-anycast-ip lines, using first one' + % (ifaceobj.name,)) + vxlan._clagd_vxlan_anycast_ip = clagd_vxlan_list[0] + + self._set_global_local_ip(ifaceobj) + return None + + def _set_global_local_ip(self, ifaceobj): + vxlan_local_tunnel_ip = ifaceobj.get_attr_value_first('vxlan-local-tunnelip') + if vxlan_local_tunnel_ip and not vxlan._vxlan_local_tunnelip: + vxlan._vxlan_local_tunnelip = vxlan_local_tunnel_ip + + def _is_vxlan_device(self, ifaceobj): + if ifaceobj.get_attr_value_first('vxlan-id'): + return True + return False + + def _get_purge_remotes(self, ifaceobj): + if not ifaceobj: + return self._purge_remotes + purge_remotes = ifaceobj.get_attr_value_first('vxlan-purge-remotes') + if purge_remotes: + purge_remotes = utils.get_boolean_from_string(purge_remotes) + else: + purge_remotes = self._purge_remotes + return purge_remotes + + def should_create_set_vxlan(self, link_exists, ifname, vxlan_id, local, learning, ageing, group): + """ + should we issue a netlink: ip link add dev %ifname type vxlan ...? + checking each attribute against the cache + """ + if not link_exists: + return True + + try: + if ageing: + ageing = int(ageing) + except: + pass + + for attr_list, value in ( + ((ifname, 'linkinfo', Link.IFLA_VXLAN_ID), vxlan_id), + ((ifname, 'linkinfo', Link.IFLA_VXLAN_AGEING), ageing), + ((ifname, 'linkinfo', Link.IFLA_VXLAN_LOCAL), local), + ((ifname, 'linkinfo', Link.IFLA_VXLAN_LEARNING), learning), + ((ifname, 'linkinfo', Link.IFLA_VXLAN_GROUP), group), + ): + if value and not self.ipcmd.cache_check(attr_list, value): + return True + return False + + def _vxlan_create(self, ifaceobj): + vxlanid = ifaceobj.get_attr_value_first('vxlan-id') + if vxlanid: + ifname = ifaceobj.name + anycastip = self._clagd_vxlan_anycast_ip + group = ifaceobj.get_attr_value_first('vxlan-svcnodeip') + + local = ifaceobj.get_attr_value_first('vxlan-local-tunnelip') + if not local and vxlan._vxlan_local_tunnelip: + local = vxlan._vxlan_local_tunnelip + + self.syntax_check_localip_anycastip_equal(ifname, local, anycastip) + # if both local-ip and anycast-ip are identical the function prints a warning + + ageing = ifaceobj.get_attr_value_first('vxlan-ageing') + vxlan_port = ifaceobj.get_attr_value_first('vxlan-port') + purge_remotes = self._get_purge_remotes(ifaceobj) + + link_exists = self.ipcmd.link_exists(ifname) + + if (not link_exists or + not ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT): + vxlan_learning = ifaceobj.get_attr_value_first('vxlan-learning') + if not vxlan_learning: + vxlan_learning = self.get_attr_default_value('vxlan-learning') + learning = utils.get_boolean_from_string(vxlan_learning) + else: + learning = utils.get_boolean_from_string( + self.ipcmd.get_vxlandev_learning(ifname)) + + if link_exists: + vxlanattrs = self.ipcmd.get_vxlandev_attrs(ifname) + # on ifreload do not overwrite anycast_ip to individual ip + # if clagd has modified + if vxlanattrs: + running_localtunnelip = vxlanattrs.get('local') + if (anycastip and running_localtunnelip and + anycastip == running_localtunnelip): + local = running_localtunnelip + if vxlanattrs.get('vxlanid') != vxlanid: + self.log_error('%s: Cannot change running vxlan id: ' + 'Operation not supported' % ifname, ifaceobj) + try: + vxlanid = int(vxlanid) + except: + self.log_error('%s: invalid vxlan-id \'%s\'' % (ifname, vxlanid), ifaceobj) + + if not group: + group = policymanager.policymanager_api.get_attr_default( + module_name=self.__class__.__name__, + attr='vxlan-svcnodeip' + ) + + if group: + try: + group = IPv4Address(group) + except AddressValueError: + try: + group_ip = IPv4Network(group).ip + self.logger.warning('%s: vxlan-svcnodeip %s: netmask ignored' % (ifname, group)) + group = group_ip + except: + raise Exception('%s: invalid vxlan-svcnodeip %s: must be in ipv4 format' % (ifname, group)) + + if not local: + local = policymanager.policymanager_api.get_attr_default( + module_name=self.__class__.__name__, + attr='vxlan-local-tunnelip' + ) + + if local: + try: + local = IPv4Address(local) + except AddressValueError: + try: + local_ip = IPv4Network(local).ip + self.logger.warning('%s: vxlan-local-tunnelip %s: netmask ignored' % (ifname, local)) + local = local_ip + except: + raise Exception('%s: invalid vxlan-local-tunnelip %s: must be in ipv4 format' % (ifname, local)) + + if not ageing: + ageing = policymanager.policymanager_api.get_attr_default( + module_name=self.__class__.__name__, + attr='vxlan-ageing' + ) + + if not ageing and link_exists: + # if link doesn't exist we let the kernel define ageing + ageing = self.get_attr_default_value('vxlan-ageing') + + if not vxlan_port: + vxlan_port = policymanager.policymanager_api.get_attr_default( + module_name=self.__class__.__name__, + attr='vxlan-port' + ) + + try: + vxlan_port = int(vxlan_port) + except TypeError: + # TypeError means vxlan_port was None + # ie: not provided by the user or the policy + vxlan_port = netlink.VXLAN_UDP_PORT + except ValueError as e: + self.logger.warning('%s: vxlan-port: using default %s: invalid configured value %s' % (ifname, netlink.VXLAN_UDP_PORT, str(e))) + vxlan_port = netlink.VXLAN_UDP_PORT + + if link_exists: + cache_port = vxlanattrs.get(Link.IFLA_VXLAN_PORT) + if vxlan_port != cache_port: + self.logger.warning('%s: vxlan-port (%s) cannot be changed - to apply the desired change please run: ifdown %s && ifup %s' + % (ifname, cache_port, ifname, ifname)) + vxlan_port = cache_port + + if self.should_create_set_vxlan(link_exists, ifname, vxlanid, local, learning, ageing, group): + try: + netlink.link_add_vxlan(ifname, vxlanid, + local=local, + learning=learning, + ageing=ageing, + group=group, + dstport=vxlan_port) + except Exception as e_netlink: + self.logger.debug('%s: vxlan netlink: %s' % (ifname, str(e_netlink))) + try: + self.ipcmd.link_create_vxlan(ifname, vxlanid, + localtunnelip=local, + svcnodeip=group, + remoteips=ifaceobj.get_attr_value('vxlan-remoteip'), + learning='on' if learning else 'off', + ageing=ageing) + except Exception as e_iproute2: + self.logger.warning('%s: vxlan add/set failed: %s' % (ifname, str(e_iproute2))) + return + + try: + # manually adding an entry to the caching after creating/updating the vxlan + if not ifname in linkCache.links: + linkCache.links[ifname] = {'linkinfo': {}} + linkCache.links[ifname]['linkinfo'].update({ + 'learning': learning, + Link.IFLA_VXLAN_LEARNING: learning, + 'vxlanid': str(vxlanid), + Link.IFLA_VXLAN_ID: vxlanid + }) + if ageing: + linkCache.links[ifname]['linkinfo'].update({ + 'ageing': ageing, + Link.IFLA_VXLAN_AGEING: int(ageing) + }) + except: + pass + else: + self.logger.info('%s: vxlan already exists' % ifname) + # if the vxlan already exists it's already cached + + remoteips = ifaceobj.get_attr_value('vxlan-remoteip') + if remoteips: + try: + for remoteip in remoteips: + IPv4Address(remoteip) + except Exception as e: + self.log_error('%s: vxlan-remoteip: %s' %(ifaceobj.name, str(e))) + + if purge_remotes or remoteips: + # figure out the diff for remotes and do the bridge fdb updates + # only if provisioned by user and not by an vxlan external + # controller. + peers = self.ipcmd.get_vxlan_peers(ifaceobj.name, group) + if local and remoteips and local in remoteips: + remoteips.remove(local) + cur_peers = set(peers) + if remoteips: + new_peers = set(remoteips) + del_list = cur_peers.difference(new_peers) + add_list = new_peers.difference(cur_peers) + else: + del_list = cur_peers + add_list = [] + + for addr in del_list: + try: + self.ipcmd.bridge_fdb_del(ifaceobj.name, + '00:00:00:00:00:00', + None, True, addr) + except: + pass + + for addr in add_list: + try: + self.ipcmd.bridge_fdb_append(ifaceobj.name, + '00:00:00:00:00:00', + None, True, addr) + except: + pass + + def _up(self, ifaceobj): + self._vxlan_create(ifaceobj) + + def _down(self, ifaceobj): + try: + self.ipcmd.link_delete(ifaceobj.name) + except Exception, e: + self.log_warn(str(e)) + + def _query_check_n_update(self, ifaceobj, ifaceobjcurr, attrname, attrval, + running_attrval): + if not ifaceobj.get_attr_value_first(attrname): + return + if running_attrval and attrval == running_attrval: + ifaceobjcurr.update_config_with_status(attrname, attrval, 0) + else: + ifaceobjcurr.update_config_with_status(attrname, running_attrval, 1) + + def _query_check_n_update_addresses(self, ifaceobjcurr, attrname, + addresses, running_addresses): + if addresses: + for a in addresses: + if a in running_addresses: + ifaceobjcurr.update_config_with_status(attrname, a, 0) + else: + ifaceobjcurr.update_config_with_status(attrname, a, 1) + running_addresses = Set(running_addresses).difference( + Set(addresses)) + [ifaceobjcurr.update_config_with_status(attrname, a, 1) + for a in running_addresses] + + def _query_check(self, ifaceobj, ifaceobjcurr): + if not self.ipcmd.link_exists(ifaceobj.name): + return + # Update vxlan object + vxlanattrs = self.ipcmd.get_vxlandev_attrs(ifaceobj.name) + if not vxlanattrs: + ifaceobjcurr.check_n_update_config_with_status_many(ifaceobj, + self.get_mod_attrs(), -1) + return + self._query_check_n_update(ifaceobj, ifaceobjcurr, 'vxlan-id', + ifaceobj.get_attr_value_first('vxlan-id'), + vxlanattrs.get('vxlanid')) + + self._query_check_n_update( + ifaceobj, + ifaceobjcurr, + 'vxlan-port', + ifaceobj.get_attr_value_first('vxlan-port'), + str(vxlanattrs.get(Link.IFLA_VXLAN_PORT)) + ) + + running_attrval = vxlanattrs.get('local') + attrval = ifaceobj.get_attr_value_first('vxlan-local-tunnelip') + if not attrval: + attrval = vxlan._vxlan_local_tunnelip + ifaceobj.update_config('vxlan-local-tunnelip', attrval) + + if running_attrval == self._clagd_vxlan_anycast_ip: + # if local ip is anycast_ip, then let query_check to go through + attrval = self._clagd_vxlan_anycast_ip + self._query_check_n_update(ifaceobj, ifaceobjcurr, 'vxlan-local-tunnelip', + attrval, running_attrval) + + self._query_check_n_update(ifaceobj, ifaceobjcurr, 'vxlan-svcnodeip', + ifaceobj.get_attr_value_first('vxlan-svcnodeip'), + vxlanattrs.get('svcnode')) + + purge_remotes = self._get_purge_remotes(ifaceobj) + if purge_remotes or ifaceobj.get_attr_value('vxlan-remoteip'): + # If purge remotes or if vxlan-remoteip's are set + # in the config file, we are owners of the installed + # remote-ip's, lets check and report any remote ips we don't + # understand + self._query_check_n_update_addresses(ifaceobjcurr, 'vxlan-remoteip', + ifaceobj.get_attr_value('vxlan-remoteip'), + self.ipcmd.get_vxlan_peers(ifaceobj.name, vxlanattrs.get('svcnode'))) + + learning = ifaceobj.get_attr_value_first('vxlan-learning') + if learning: + running_learning = vxlanattrs.get('learning') + if learning == 'yes' and running_learning == 'on': + running_learning = 'yes' + elif learning == 'no' and running_learning == 'off': + running_learning = 'no' + if learning == running_learning: + ifaceobjcurr.update_config_with_status('vxlan-learning', + running_learning, 0) + else: + ifaceobjcurr.update_config_with_status('vxlan-learning', + running_learning, 1) + ageing = ifaceobj.get_attr_value_first('vxlan-ageing') + if not ageing: + ageing = self.get_mod_subattr('vxlan-ageing', 'default') + self._query_check_n_update(ifaceobj, ifaceobjcurr, 'vxlan-ageing', + ageing, vxlanattrs.get('ageing')) + + def _query_running(self, ifaceobjrunning): + vxlanattrs = self.ipcmd.get_vxlandev_attrs(ifaceobjrunning.name) + if not vxlanattrs: + return + attrval = vxlanattrs.get('vxlanid') + if attrval: + ifaceobjrunning.update_config('vxlan-id', vxlanattrs.get('vxlanid')) + else: + # if there is no vxlan id, this is not a vxlan port + return + + ifaceobjrunning.update_config('vxlan-port', vxlanattrs.get(Link.IFLA_VXLAN_PORT)) + + attrval = vxlanattrs.get('local') + if attrval: + ifaceobjrunning.update_config('vxlan-local-tunnelip', attrval) + attrval = vxlanattrs.get('svcnode') + if attrval: + ifaceobjrunning.update_config('vxlan-svcnode', attrval) + purge_remotes = self._get_purge_remotes(None) + if purge_remotes: + # if purge_remotes is on, it means we own the + # remote ips. Query them and add it to the running config + attrval = self.ipcmd.get_vxlan_peers(ifaceobjrunning.name, vxlanattrs.get('svcnode')) + if attrval: + [ifaceobjrunning.update_config('vxlan-remoteip', a) + for a in attrval] + attrval = vxlanattrs.get('learning') + if attrval and attrval == 'on': + ifaceobjrunning.update_config('vxlan-learning', 'on') + attrval = vxlanattrs.get('ageing') + if attrval: + ifaceobjrunning.update_config('vxlan-ageing', vxlanattrs.get('ageing')) + + _run_ops = {'pre-up' : _up, + 'post-down' : _down, + 'query-checkcurr' : _query_check, + 'query-running' : _query_running} + + def get_ops(self): + return self._run_ops.keys() + + def _init_command_handlers(self): + if not self.ipcmd: + self.ipcmd = LinkUtils() + + def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): + op_handler = self._run_ops.get(operation) + if not op_handler: + return + if (operation != 'query-running' and + not self._is_vxlan_device(ifaceobj)): + return + self._init_command_handlers() + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj) + else: + op_handler(self, ifaceobj) diff --git a/completion/ifup b/ifupdown2/completion/ifup similarity index 100% rename from completion/ifup rename to ifupdown2/completion/ifup diff --git a/ifupdownaddons/__init__.py b/ifupdown2/ifupdown/__init__.py similarity index 100% rename from ifupdownaddons/__init__.py rename to ifupdown2/ifupdown/__init__.py diff --git a/ifupdown2/ifupdown/argv.py b/ifupdown2/ifupdown/argv.py new file mode 100644 index 0000000..5c44267 --- /dev/null +++ b/ifupdown2/ifupdown/argv.py @@ -0,0 +1,231 @@ +#!/usr/bin/python +# +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. +# Authors: +# Roopa Prabhu, roopa@cumulusnetworks.com +# Julien Fortin, julien@cumulusnetworks.com +# + +import sys +import argparse +import argcomplete + +try: + from ifupdown2.ifupdown.utils import utils + from ifupdown2.ifupdown.exceptions import ArgvParseError +except: + from ifupdown.utils import utils + from ifupdown.exceptions import ArgvParseError + + +class VersionAction(argparse.Action): + def __call__(self, parser, namespace, values, option_string=None): + + try: + dpkg = utils.exec_commandl([utils.dpkg_cmd, '-l', 'ifupdown2']) + + if not dpkg: + raise Exception('dpkg -l ifupdown2 returns without output') + + dpkg = dpkg.split('\n') + + if not dpkg: + raise Exception('dpkg -l ifupdown2 returns without output') + + for line in dpkg: + if 'ifupdown2' in line: + info = line.split() + + sys.stdout.write('ifupdown2:%s\n' % (info[2])) + sys.exit(0) + + raise Exception('ifupdown2 package not found using dpkg -l') + + except Exception as e: + sys.stderr.write('error: cannot get current version using dpkg: %s\n' % str(e)) + sys.exit(1) + + +class Parse: + valid_ops = { + 'ifup': 'up', + 'ifdown': 'down', + 'ifreload': 'reload', + 'ifquery': 'query' + } + + def __init__(self, argv): + self.executable_name = argv[0] + self.op = self.get_op() + self.argv = argv[1:] + + if self.op == 'query': + descr = 'query interfaces (all or interface list)' + elif self.op == 'reload': + descr = 'reload interface configuration.' + else: + descr = 'interface management' + argparser = argparse.ArgumentParser(description=descr) + if self.op == 'reload': + self.update_ifreload_argparser(argparser) + else: + self.update_argparser(argparser) + if self.op == 'up': + self.update_ifup_argparser(argparser) + elif self.op == 'down': + self.update_ifdown_argparser(argparser) + elif self.op == 'query': + self.update_ifquery_argparser(argparser) + self.update_common_argparser(argparser) + argcomplete.autocomplete(argparser) + self.args = argparser.parse_args(self.argv) + + def validate(self): + if self.op == 'query' and (self.args.syntaxhelp or self.args.list): + return True + + if self.op == 'reload': + if not self.args.all and not self.args.currentlyup and not self.args.CLASS: + raise ArgvParseError("'-a' or '-c' or '-allow' option is required") + elif not self.args.iflist and not self.args.all and not self.args.CLASS: + raise ArgvParseError("'-a' option or interface list are required") + + if self.args.iflist and self.args.all: + raise ArgvParseError("'-a' option and interface list are mutually exclusive") + + if self.op != 'reload' and self.args.CLASS and self.args.all: + raise ArgvParseError("'--allow' option is mutually exclusive with '-a'") + return True + + def get_op(self): + try: + for key, value in self.valid_ops.iteritems(): + if self.executable_name.endswith(key): + return value + except: + raise ArgvParseError("Unexpected executable. Should be 'ifup' or 'ifdown' or 'ifquery'") + + def get_args(self): + return self.args + + def update_argparser(self, argparser): + """ base parser, common to all commands """ + argparser.add_argument('-a', '--all', action='store_true', required=False, + help='process all interfaces marked "auto"') + argparser.add_argument('iflist', metavar='IFACE', nargs='*', + help='interface list separated by spaces. ' + 'IFACE list is mutually exclusive with -a option.') + argparser.add_argument('-v', '--verbose', dest='verbose', action='store_true', help='verbose') + argparser.add_argument('-d', '--debug', dest='debug', action='store_true', help='output debug info') + argparser.add_argument('-q', '--quiet', dest='quiet', action='store_true', help=argparse.SUPPRESS) + argparser.add_argument('--allow', dest='CLASS', action='append', help='ignore non-"allow-CLASS" interfaces') + argparser.add_argument('-w', '--with-depends', dest='withdepends', action='store_true', + help="run with all dependent interfaces. " + "This option is redundant when '-a' is specified. " + "With '-a' interfaces are always executed in dependency order") + argparser.add_argument('--perfmode', dest='perfmode', action='store_true', help=argparse.SUPPRESS) + argparser.add_argument('--nocache', dest='nocache', action='store_true', help=argparse.SUPPRESS) + argparser.add_argument('-X', '--exclude', dest='excludepats', action='append', + help='Exclude interfaces from the list of interfaces to operate on. ' + 'Can be specified multiple times.') + argparser.add_argument('-i', '--interfaces', dest='interfacesfile', default=None, + help='Specify interfaces file instead of file defined in ifupdown2.conf file') + argparser.add_argument('-t', '--interfaces-format', dest='interfacesfileformat', default='native', + choices=['native', 'json'], help='interfaces file format') + argparser.add_argument('-T', '--type', dest='type', default=None, choices=['iface', 'vlan'], + help='type of interface entry (iface or vlan). ' + 'This option can be used in case of ambiguity between ' + 'a vlan interface and an iface interface of the same name') + + def update_ifupdown_argparser(self, argparser): + """ common arg parser for ifup and ifdown """ + argparser.add_argument('-f', '--force', dest='force', action='store_true', help='force run all operations') + argparser.add_argument('-l', '--syslog', dest='syslog', action='store_true', help=argparse.SUPPRESS) + group = argparser.add_mutually_exclusive_group(required=False) + group.add_argument('-n', '--no-act', dest='noact', action='store_true', + help="print out what would happen, but don't do it") + group.add_argument('-p', '--print-dependency', dest='printdependency', + choices=['list', 'dot'], help='print iface dependency') + group.add_argument('--no-scripts', '--admin-state', dest='noaddons', action='store_true', + help='dont run any addon modules/scripts. Only bring the interface administratively up/down') + + def update_ifup_argparser(self, argparser): + argparser.add_argument('-s', '--syntax-check', dest='syntaxcheck', + action='store_true', help='Only run the interfaces file parser') + argparser.add_argument('-k', '--skip-upperifaces', dest='skipupperifaces', action='store_true', + help='ifup by default tries to add newly created interfaces into its upper/parent ' + 'interfaces. Eg. if a bridge port is created as a result of ifup on the port, ' + 'ifup automatically adds the port to the bridge. This option can be used to ' + 'disable this default behaviour') + self.update_ifupdown_argparser(argparser) + + def update_ifdown_argparser(self, argparser): + self.update_ifupdown_argparser(argparser) + argparser.add_argument('-u', '--use-current-config', + dest='usecurrentconfig', action='store_true', + help='By default ifdown looks at the saved state for interfaces to bring down. ' + 'This option allows ifdown to look at the current interfaces file. ' + 'Useful when your state file is corrupted or you want down to use ' + 'the latest from the interfaces file') + + def update_ifquery_argparser(self, argparser): + """ arg parser for ifquery options """ + + # -l is same as '-a', only here for backward compatibility + argparser.add_argument('-l', '--list', action='store_true', dest='list', + help='list all matching known interfaces') + group = argparser.add_mutually_exclusive_group(required=False) + group.add_argument('-r', '--running', dest='running', action='store_true', + help='query running state of an interface') + group.add_argument('-c', '--check', dest='checkcurr', action='store_true', + help='check interface file contents against running state of an interface') + group.add_argument('-x', '--raw', action='store_true', dest='raw', help='print raw config file entries') + group.add_argument('--print-savedstate', action='store_true', dest='printsavedstate', help=argparse.SUPPRESS) + argparser.add_argument('-o', '--format', dest='format', default='native', + choices=['native', 'json'], help='interface display format') + argparser.add_argument('-p', '--print-dependency', dest='printdependency', + choices=['list', 'dot'], help='print interface dependency') + argparser.add_argument('-s', '--syntax-help', action='store_true', dest='syntaxhelp', + help='print supported interface config syntax') + argparser.add_argument('--with-defaults', action='store_true', dest='withdefaults', + help='check policy default file contents, for unconfigured attributes, ' + 'against running state of an interface') + + def update_ifreload_argparser(self, argparser): + """ parser for ifreload """ + group = argparser.add_mutually_exclusive_group(required=True) + group.add_argument('-a', '--all', action='store_true', help='process all interfaces marked "auto"') + group.add_argument('-c', '--currently-up', dest='currentlyup', action='store_true', + help='Reload the configuration for all interfaces which are ' + 'currently up regardless of whether an interface has ' + '"auto " configuration within the /etc/network/interfaces file.') + group.add_argument('--allow', dest='CLASS', action='append', help='ignore non-"allow-CLASS" interfaces') + argparser.add_argument('iflist', metavar='IFACE', nargs='*', help=argparse.SUPPRESS) + argparser.add_argument('-n', '--no-act', dest='noact', action='store_true', + help='print out what would happen, but don\'t do it') + argparser.add_argument('-v', '--verbose', dest='verbose', action='store_true', help='verbose') + argparser.add_argument('-d', '--debug', dest='debug', action='store_true', help='output debug info') + argparser.add_argument('-w', '--with-depends', dest='withdepends', action='store_true', help=argparse.SUPPRESS) + argparser.add_argument('--perfmode', dest='perfmode', action='store_true', help=argparse.SUPPRESS) + argparser.add_argument('--nocache', dest='nocache', action='store_true', help=argparse.SUPPRESS) + argparser.add_argument('-X', '--exclude', dest='excludepats', action='append', help=argparse.SUPPRESS) + # argparser.add_argument('-j', '--jobs', dest='jobs', type=int, + # default=-1, choices=range(1,12), help=argparse.SUPPRESS) + # argparser.add_argument('-i', '--interfaces', dest='interfacesfile', + # default='/etc/network/interfaces', + # help='use interfaces file instead of default ' + + # '/etc/network/interfaces') + argparser.add_argument('-u', '--use-current-config', dest='usecurrentconfig', action='store_true', + help='By default ifreload looks at saved state for interfaces to bring down. ' + 'With this option ifreload will only look at the current interfaces file. ' + 'Useful when your state file is corrupted or you want down to use the latest ' + 'from the interfaces file') + argparser.add_argument('-l', '--syslog', dest='syslog', action='store_true', help=argparse.SUPPRESS) + argparser.add_argument('-f', '--force', dest='force', action='store_true', help='force run all operations') + argparser.add_argument('-s', '--syntax-check', dest='syntaxcheck', action='store_true', + help='Only run the interfaces file parser') + + def update_common_argparser(self, argparser): + ''' general parsing rules ''' + + argparser.add_argument('-V', '--version', action=VersionAction, nargs=0) diff --git a/ifupdown2/ifupdown/config.py b/ifupdown2/ifupdown/config.py new file mode 100644 index 0000000..5e2696f --- /dev/null +++ b/ifupdown2/ifupdown/config.py @@ -0,0 +1,57 @@ +#!/usr/bin/python +# +# Copyright 2017 Cumulus Networks, Inc. All rights reserved. +# Authors: +# Julien Fortin, julien@cumulusnetworks.com +# +# ifupdown2 -- +# tool to configure network interfaces +# + +import os +import resource + +IFUPDOWN2_ADDON_DROPIN_FOLDER = '/usr/share/ifupdown2/addons' + +# ifupdown2/core/config.py -> we need to use dirname twice. +_ = { + IFUPDOWN2_ADDON_DROPIN_FOLDER, + '%s/addons' % (os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) +} + +try: + addon_module_dir_list = list(_) + if addon_module_dir_list[0] is not IFUPDOWN2_ADDON_DROPIN_FOLDER: + addon_module_dir_list.remove(IFUPDOWN2_ADDON_DROPIN_FOLDER) + ADDON_MODULES_DIR = [IFUPDOWN2_ADDON_DROPIN_FOLDER] + addon_module_dir_list + else: + ADDON_MODULES_DIR = addon_module_dir_list +except: + ADDON_MODULES_DIR = [IFUPDOWN2_ADDON_DROPIN_FOLDER] + +__version__ = '' + + +def get_configuration_file_real_path(path_to_file): + """ + When install via pypi or `pip install .` ifupdown2 is install in a virtualenv + config file that should be installed in /etc/network/ifupdown2 end-up being + installed in /usr/local/lib/python2.7/dist-packages/etc/network/ifupdown2/ + """ + if not os.path.exists(path_to_file): + # we will try to resolve the location of our conf file + # otherwise default to the input argument + package_dir = os.path.dirname(os.path.realpath(__file__)) + parent_dir = os.path.dirname(package_dir) + resolved_path = '%s%s' % (parent_dir, path_to_file) + + if os.path.exists(resolved_path): + return resolved_path + + return path_to_file + + +IFUPDOWN2_CONF_PATH = get_configuration_file_real_path('/etc/network/ifupdown2/ifupdown2.conf') +ADDONS_CONF_PATH = get_configuration_file_real_path('/etc/network/ifupdown2/addons.conf') + +resource.setrlimit(resource.RLIMIT_CORE, (0, 0)) diff --git a/ifupdown2/ifupdown/exceptions.py b/ifupdown2/ifupdown/exceptions.py new file mode 100644 index 0000000..9537f21 --- /dev/null +++ b/ifupdown2/ifupdown/exceptions.py @@ -0,0 +1,58 @@ +#!/usr/bin/python +# +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. +# Authors: +# Roopa Prabhu, roopa@cumulusnetworks.com +# Julien Fortin, julien@cumulusnetworks.com +# +# ifupdown -- +# exceptions +# + +try: + from ifupdown2.ifupdown.log import log +except: + from ifupdown.log import log + + +class Error(Exception): + """Base class for exceptions in ifupdown""" + + def log_error(self): + log.error(self.message) + + def log_warning(self): + log.warning(self.message) + + def log_info(self): + log.info(self.message) + + def log_debug(self): + log.debug(self.message) + + +class ArgvParseError(Error): + """ + Exception coming from argv parsing + """ + pass + + +class ifaceNotFoundError(Error): + pass + + +class invalidValueError(Error): + pass + + +class errorReadingStateError(Error): + pass + + +class moduleNotSupported(Error): + pass + + +class ReservedVlanException(Error): + pass diff --git a/ifupdown/graph.py b/ifupdown2/ifupdown/graph.py similarity index 92% rename from ifupdown/graph.py rename to ifupdown2/ifupdown/graph.py index e1729d0..22101ec 100644 --- a/ifupdown/graph.py +++ b/ifupdown2/ifupdown/graph.py @@ -1,20 +1,23 @@ #!/usr/bin/python # -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. # Author: Roopa Prabhu, roopa@cumulusnetworks.com # # graph -- # graph helper module for ifupdown # -import logging import copy +import logging + from collections import deque + try: from gvgen import * except ImportError, e: pass + class graph(): """ graph functions to sort and print interface graph """ @@ -26,7 +29,7 @@ class graph(): Args: **dependency_graphs** (dict): dependency graph with dependency - lists for interfaces + lists for interfaces **indegrees_arg** (dict): indegrees array for all interfaces """ @@ -73,7 +76,7 @@ class graph(): Args: **dependency_graphs** (dict): dependency graph with dependency - lists for interfaces + lists for interfaces **indegrees_arg** (dict): indegrees array for all interfaces """ diff --git a/ifupdown/iface.py b/ifupdown2/ifupdown/iface.py similarity index 95% rename from ifupdown/iface.py rename to ifupdown2/ifupdown/iface.py index 8dce8a6..75cc37a 100644 --- a/ifupdown/iface.py +++ b/ifupdown2/ifupdown/iface.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. # Author: Roopa Prabhu, roopa@cumulusnetworks.com # # iface -- @@ -13,10 +13,11 @@ It is modeled based on the 'iface' section in /etc/network/interfaces file. But can be extended to include any other network interface format """ -from collections import OrderedDict -import logging import json +from collections import OrderedDict + + class ifaceStatusUserStrs(): """ This class declares strings user can see during an ifquery --check for example. These strings can be overridden by user defined strings from @@ -46,13 +47,28 @@ class ifaceLinkKind(): bond have an ifaceobj.role attribute of SLAVE and the bridge or bond itself has ifaceobj.role of MASTER. """ - UNKNOWN = 0x000000 - BRIDGE = 0x000001 - BOND = 0x000010 - VLAN = 0x000100 - VXLAN = 0x001000 - VRF = 0x010000 - BATMAN_ADV = 0x100000 + UNKNOWN = 0x000000 + BRIDGE = 0x000001 + BOND = 0x000010 + VLAN = 0x000100 + VXLAN = 0x001000 + VRF = 0x010000 + # to indicate logical interface created by an external entity. + # the 'kind' of which ifupdown2 does not really understand + OTHER = 0x100000 + + @classmethod + def to_str(cls, kind): + if kind == cls.BRIDGE: + return "bridge" + elif kind == cls.BOND: + return "bond" + elif kind == cls.VLAN: + return "vlan" + elif kind == cls.VXLAN: + return "vxlan" + elif kind == cls.VRF: + return "vrf" class ifaceLinkPrivFlags(): """ This corresponds to kernel netdev->priv_flags @@ -103,6 +119,18 @@ class ifaceLinkType(): LINK_MASTER = 0x2 LINK_NA = 0x3 +class VlanProtocols(): + # Picked ID values from + # http://www.microhowto.info/howto/configure_an_ethernet_interface_as_a_qinq_vlan_trunk.html + ETHERTYPES_TO_ID = { + '802.1Q' : '0x8100', + '802.1AD' : '0x88a8', + } + ID_TO_ETHERTYPES = { + '0x8100' : '802.1Q', + '0x88a8' : '802.1AD', + } + class ifaceDependencyType(): """ Indicates type of dependency. @@ -119,7 +147,7 @@ class ifaceDependencyType(): swp1.200 can both have 'link' swp1. swp1 is also a dependency of swp1.100 and swp1.200. As you can see dependency swp1 is shared between swp1.100 and swp1.200. - + In a master/slave relationship like bridge and its ports: eg: bridge br0 and its ports swp1 and swp2. dependency swp1 and swp2 cannot be shared with any other @@ -152,7 +180,7 @@ class ifaceStatus(): return 'error' elif state == cls.NOTFOUND: return 'notfound' - + @classmethod def from_str(cls, state_str): if state_str == 'unknown': @@ -228,7 +256,7 @@ class ifaceJsonEncoder(json.JSONEncoder): def default(self, o): retconfig = {} retifacedict = OrderedDict([]) - if o.config: + if o.config: retconfig = dict((k, (v[0] if len(v) == 1 else v)) for k,v in o.config.items()) retifacedict['name'] = o.name @@ -258,12 +286,12 @@ class ifaceJsonEncoderWithStatus(json.JSONEncoder): vitem_status = [] for vitem in v: s = o.get_config_attr_status(k, idx) - if s == -1: - status_str = ifaceStatusUserStrs.UNKNOWN - elif s == 1: + if s == 1: status_str = ifaceStatusUserStrs.ERROR elif s == 0: status_str = ifaceStatusUserStrs.SUCCESS + else: + status_str = ifaceStatusUserStrs.UNKNOWN vitem_status.append('%s' %status_str) idx += 1 retconfig[k] = v[0] if len(v) == 1 else v @@ -302,7 +330,7 @@ class ifaceJsonDecoder(): class iface(): """ ifupdown2 iface object class Attributes: - **name** Name of the interface + **name** Name of the interface **addr_family** Address family eg, inet, inet6. Can be None to indicate both address families @@ -331,7 +359,7 @@ class iface(): this interface depends on **upperifaces** list of interface names upper to this interface or - the interfaces that depend on this interface + the interfaces that depend on this interface **auto** True if interface belongs to the auto class @@ -374,7 +402,7 @@ class iface(): """iface priv flags. can be used by the external object manager """ self.refcnt = 0 """iface refcnt (incremented for each dependent this interface has) """ - self.lowerifaces = None + self.lowerifaces = None """lower iface list (in other words: slaves of this interface """ self.upperifaces = None """upper iface list (in other words: master of this interface """ @@ -471,7 +499,7 @@ class iface(): def get_attr_value(self, attr_name): """ add to the list of upperifaces """ return self.config.get(attr_name) - + def get_attr_value_first(self, attr_name): """ get first value of the specified attr name """ attr_value_list = self.config.get(attr_name) @@ -562,7 +590,7 @@ class iface(): def get_config_attr_status(self, attr_name, idx=0): """ get status of a attribute config on this interface. - + Looks at the iface _config_status dict""" return self._config_status.get(attr_name, [])[idx] @@ -653,7 +681,7 @@ class iface(): print (self.raw_config[0]) for i in range(1, len(self.raw_config)): print(indent + self.raw_config[i]) - + def dump(self, logger): indent = '\t' logger.info(self.name + ' : {') diff --git a/ifupdown/iff.py b/ifupdown2/ifupdown/iff.py similarity index 94% rename from ifupdown/iff.py rename to ifupdown2/ifupdown/iff.py index f191883..c7c5d65 100644 --- a/ifupdown/iff.py +++ b/ifupdown2/ifupdown/iff.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. # # Author: Scott Feldman, sfeldma@cumulusnetworks.com # diff --git a/ifupdown/ifupdownbase.py b/ifupdown2/ifupdown/ifupdownbase.py similarity index 76% rename from ifupdown/ifupdownbase.py rename to ifupdown2/ifupdown/ifupdownbase.py index dbac0ad..fd7c730 100644 --- a/ifupdown/ifupdownbase.py +++ b/ifupdown2/ifupdown/ifupdownbase.py @@ -1,20 +1,26 @@ #!/usr/bin/python # -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. # Author: Roopa Prabhu, roopa@cumulusnetworks.com # # ifupdownBase -- # base object for various ifupdown objects # -import logging import re import os +import logging import traceback -from ifupdown.netlink import netlink -from iface import * -import ifupdownflags as ifupdownflags +try: + from ifupdown2.ifupdown.netlink import netlink + + import ifupdown2.ifupdown.ifupdownflags as ifupdownflags +except ImportError: + from ifupdown.netlink import netlink + + import ifupdown.ifupdownflags as ifupdownflags + class ifupdownBase(object): @@ -32,13 +38,13 @@ class ifupdownBase(object): if self.ignore_error(str) == False: if self.logger.getEffectiveLevel() == logging.DEBUG: traceback.print_stack() + traceback.print_exc() self.logger.warn(str) pass def log_error(self, str): if self.ignore_error(str) == False: - raise - #raise Exception(str) + raise Exception(str) else: pass diff --git a/packages/ifupdown2/ifupdown/ifupdownconfig.py b/ifupdown2/ifupdown/ifupdownconfig.py similarity index 58% rename from packages/ifupdown2/ifupdown/ifupdownconfig.py rename to ifupdown2/ifupdown/ifupdownconfig.py index b156b6a..f4f6bf2 100644 --- a/packages/ifupdown2/ifupdown/ifupdownconfig.py +++ b/ifupdown2/ifupdown/ifupdownconfig.py @@ -1,10 +1,9 @@ #!/usr/bin/env python # -# Copyright 2015 Cumulus Networks, Inc. All rights reserved. +# Copyright 2015-2017 Cumulus Networks, Inc. All rights reserved. # # Author: Roopa Prabhu, roopa@cumulusnetworks.com # -# class ifupdownConfig(): @@ -12,3 +11,7 @@ class ifupdownConfig(): self.conf = {} config = ifupdownConfig() + +def reset(): + global config + config = ifupdownConfig() diff --git a/ifupdown/ifupdownflags.py b/ifupdown2/ifupdown/ifupdownflags.py similarity index 81% rename from ifupdown/ifupdownflags.py rename to ifupdown2/ifupdown/ifupdownflags.py index d76e733..e5efbe1 100644 --- a/ifupdown/ifupdownflags.py +++ b/ifupdown2/ifupdown/ifupdownflags.py @@ -1,10 +1,9 @@ #!/usr/bin/env python # -# Copyright 2015 Cumulus Networks, Inc. All rights reserved. +# Copyright 2015-2017 Cumulus Networks, Inc. All rights reserved. # # Author: Roopa Prabhu, roopa@cumulusnetworks.com # -# class ifupdownFlags(): @@ -23,3 +22,8 @@ class ifupdownFlags(): self.CACHE_FLAGS = 0x0 flags = ifupdownFlags() + + +def reset(): + global flags + flags = ifupdownFlags() diff --git a/ifupdown/ifupdownmain.py b/ifupdown2/ifupdown/ifupdownmain.py similarity index 84% rename from ifupdown/ifupdownmain.py rename to ifupdown2/ifupdown/ifupdownmain.py index 951cf68..45391ee 100644 --- a/ifupdown/ifupdownmain.py +++ b/ifupdown2/ifupdown/ifupdownmain.py @@ -1,34 +1,54 @@ #!/usr/bin/python # -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. # Author: Roopa Prabhu, roopa@cumulusnetworks.com # # ifupdownMain -- # ifupdown main module # -import os -import re -import imp import pprint -import logging -import sys, traceback -import copy -import json -import ifupdown.statemanager as statemanager -import ifupdown.ifupdownconfig as ifupdownConfig -import ifupdown.ifupdownflags as ifupdownflags -from networkinterfaces import * -from iface import * -from scheduler import * -from collections import deque + from collections import OrderedDict -from graph import * -from exceptions import * -from sets import Set from ipaddr import IPNetwork, IPv4Network, IPv6Network, IPAddress, IPv4Address, IPv6Address +try: + import ifupdown2.ifupdownaddons.cache + import ifupdown2.ifupdownaddons.LinkUtils + import ifupdown2.ifupdownaddons.mstpctlutil + + import ifupdown2.ifupdown.policymanager + import ifupdown2.ifupdown.ifupdownflags + import ifupdown2.ifupdown.statemanager as statemanager + import ifupdown2.ifupdown.ifupdownflags as ifupdownflags + import ifupdown2.ifupdown.ifupdownconfig as ifupdownConfig + + from ifupdown2.ifupdown.graph import * + from ifupdown2.ifupdown.iface import * + from ifupdown2.ifupdown.scheduler import * + from ifupdown2.ifupdown.exceptions import * + from ifupdown2.ifupdown.networkinterfaces import * + from ifupdown2.ifupdown.config import ADDON_MODULES_DIR, ADDONS_CONF_PATH, IFUPDOWN2_ADDON_DROPIN_FOLDER +except ImportError: + import ifupdownaddons.cache + import ifupdownaddons.LinkUtils + import ifupdownaddons.mstpctlutil + + import ifupdown.ifupdownflags + import ifupdown.policymanager + import ifupdown.statemanager as statemanager + import ifupdown.ifupdownflags as ifupdownflags + import ifupdown.ifupdownconfig as ifupdownConfig + + from ifupdown.graph import * + from ifupdown.iface import * + from ifupdown.scheduler import * + from ifupdown.exceptions import * + from ifupdown.networkinterfaces import * + from ifupdown.config import ADDON_MODULES_DIR, ADDONS_CONF_PATH, IFUPDOWN2_ADDON_DROPIN_FOLDER + + """ .. module:: ifupdownmain :synopsis: main module for ifupdown package @@ -59,52 +79,13 @@ class ifacePrivFlags(): def __init__(self, builtin=False, noconfig=False): self.BUILTIN = builtin self.NOCONFIG = noconfig - + class ifupdownMain(ifupdownBase): """ ifupdown2 main class """ - scripts_dir='/etc/network' - addon_modules_dir='/usr/share/ifupdown2/addons' - addon_modules_configfile='/etc/network/ifupdown2/addons.conf' - - # iface dictionary in the below format: - # { '' : [, ..] } - # eg: - # { 'swp1' : [, ..] } - # - # Each ifaceobject corresponds to a configuration block for - # that interface - # The value in the dictionary is a list because the network - # interface configuration file supports more than one iface section - # in the interfaces file - ifaceobjdict = OrderedDict() - - # iface dictionary representing the curr running state of an iface - # in the below format: - # {'' : } - ifaceobjcurrdict = OrderedDict() - - # Dictionary representing operation and modules - # for every operation - module_ops = OrderedDict([('pre-up', []), - ('up' , []), - ('post-up' , []), - ('query-checkcurr', []), - ('query-running', []), - ('query-dependency', []), - ('query', []), - ('query-raw', []), - ('pre-down', []), - ('down' , []), - ('post-down' , [])]) - - # For old style /etc/network/ bash scripts - script_ops = OrderedDict([('pre-up', []), - ('up' , []), - ('post-up' , []), - ('pre-down', []), - ('down' , []), - ('post-down' , [])]) + scripts_dir = '/etc/network' + addon_modules_dir = ADDON_MODULES_DIR + addon_modules_configfile = ADDONS_CONF_PATH # Handlers for ops that ifupdown2 owns def run_up(self, ifaceobj): @@ -114,6 +95,7 @@ class ifupdownMain(ifupdownBase): return if ((ifaceobj.link_kind & ifaceLinkKind.VRF) or (ifaceobj.link_privflags & ifaceLinkPrivFlags.VRF_SLAVE)): + self._keep_link_down(ifaceobj) return # if not a logical interface and addr method is manual, # ignore link admin state changes @@ -130,14 +112,19 @@ class ifupdownMain(ifupdownBase): if ifaceobj.link_type == ifaceLinkType.LINK_SLAVE: return if not self.link_exists(ifaceobj.name): - return + return + if self._keep_link_down(ifaceobj): + return + self.link_up(ifaceobj.name) + + def _keep_link_down(self, ifaceobj): if ifaceobj.link_privflags & ifaceLinkPrivFlags.KEEP_LINK_DOWN: # user has asked to explicitly keep the link down, # so, force link down self.logger.info('%s: keeping link down due to user config' %ifaceobj.name) self.link_down(ifaceobj.name) - return - self.link_up(ifaceobj.name) + return True + return False def run_down(self, ifaceobj): if ((ifaceobj.link_kind & ifaceLinkKind.VRF) or @@ -179,8 +166,21 @@ class ifupdownMain(ifupdownBase): # ifupdown object interface scheduler pre and posthooks sched_hooks = {'posthook' : run_sched_ifaceobj_posthook} + def reset_ifupdown2(self): + ifaceScheduler.reset() + + ifupdown2.ifupdown.statemanager.reset() + ifupdown2.ifupdown.policymanager.reset() + ifupdown2.ifupdown.ifupdownflags.reset() + ifupdownConfig.reset() + ifupdown2.ifupdownaddons.mstpctlutil.mstpctlutil.reset() + ifupdown2.ifupdownaddons.LinkUtils.LinkUtils.reset() + + ifupdown2.ifupdownaddons.cache.linkCache.reset() + ifupdown2.ifupdownaddons.cache.MSTPAttrsCache.invalidate() + def __init__(self, config={}, - force=False, dryrun=False, nowait=False, + daemon=False, force=False, dryrun=False, nowait=False, perfmode=False, withdepends=False, njobs=1, cache=False, addons_enable=True, statemanager_enable=True, interfacesfile='/etc/network/interfaces', @@ -200,6 +200,49 @@ class ifupdownMain(ifupdownBase): Raises: AttributeError, KeyError """ + if daemon: + self.reset_ifupdown2() + + # iface dictionary in the below format: + # { '' : [, ..] } + # eg: + # { 'swp1' : [, ..] } + # + # Each ifaceobject corresponds to a configuration block for + # that interface + # The value in the dictionary is a list because the network + # interface configuration file supports more than one iface section + # in the interfaces file + self.ifaceobjdict = OrderedDict() + + # iface dictionary representing the curr running state of an iface + # in the below format: + # {'' : } + self.ifaceobjcurrdict = OrderedDict() + + # Dictionary representing operation and modules + # for every operation + self.module_ops = OrderedDict([('pre-up', []), + ('up', []), + ('post-up', []), + ('query-checkcurr', []), + ('query-running', []), + ('query-dependency', []), + ('query', []), + ('query-raw', []), + ('pre-down', []), + ('down', []), + ('post-down', [])]) + + # For old style /etc/network/ bash scripts + self.script_ops = OrderedDict([('pre-up', []), + ('up', []), + ('post-up', []), + ('pre-down', []), + ('down', []), + ('post-down', [])]) + + self.logger = logging.getLogger('ifupdown') ifupdownflags.flags.FORCE = force ifupdownflags.flags.DRYRUN = dryrun @@ -243,13 +286,13 @@ class ifupdownMain(ifupdownBase): self._cache_no_repeats = {} if self.flags.STATEMANAGER_ENABLE: + self.statemanager = statemanager.statemanager_api try: - self.statemanager = statemanager.statemanager_api self.statemanager.read_saved_state() except Exception, e: - # XXX Maybe we should continue by ignoring old state + # if read_saved_state fails, state file might be corrupt. + # Ignore old state and continue self.logger.warning('error reading state (%s)' %str(e)) - raise else: self.flags.STATEMANAGER_UPDATE = False self._delay_admin_state = True if self.config.get( @@ -297,16 +340,21 @@ class ifupdownMain(ifupdownBase): '': self._keyword_ipv6_prefixlen, '': self._keyword_ip_prefixlen, '': self._keyword_number_range_list, + '': self._keyword_number_comma_range_list, '': self._keyword_interface_range_list, + '': self._keyword_interface_range_list_multiple_of_16, '': self._keyword_mac_ip_prefixlen_list, '': self._keyword_number_interface_list, '': self._keyword_interface_yes_no_list, + '': self._keyword_interface_on_off_list, '': self._keyword_interface_yes_no_0_1_list, + '': self._keyword_interface_disabled_automatic_enabled_list, '': self._keyword_interface_yes_no_auto_list, + '': self._keyword_interface_l2protocol_tunnel_list } def link_master_slave_ignore_error(self, errorstr): - # If link master slave flag is set, + # If link master slave flag is set, # there may be cases where the lowerdev may not be # up resulting in 'Network is down' error # This can happen if the lowerdev is a LINK_SLAVE @@ -372,7 +420,7 @@ class ifupdownMain(ifupdownBase): def create_n_save_ifaceobjcurr(self, ifaceobj): """ creates a copy of iface object and adds it to the iface - dict containing current iface objects + dict containing current iface objects """ ifaceobjcurr = iface() ifaceobjcurr.name = ifaceobj.name @@ -409,7 +457,7 @@ class ifupdownMain(ifupdownBase): def is_iface_builtin_byname(self, ifacename): """ Returns true if iface name is a builtin interface. - + A builtin interface is an interface which ifupdown understands. The following are currently considered builtin ifaces: - vlan interfaces in the format . @@ -418,7 +466,7 @@ class ifupdownMain(ifupdownBase): def is_ifaceobj_builtin(self, ifaceobj): """ Returns true if iface name is a builtin interface. - + A builtin interface is an interface which ifupdown understands. The following are currently considered builtin ifaces: - vlan interfaces in the format . @@ -429,7 +477,7 @@ class ifupdownMain(ifupdownBase): def is_ifaceobj_noconfig(self, ifaceobj): """ Returns true if iface object did not have a user defined config. - + These interfaces appear only when they are dependents of interfaces which have user defined config """ @@ -468,7 +516,7 @@ class ifupdownMain(ifupdownBase): def _set_iface_role(self, ifaceobj, role, upperifaceobj): if (self.flags.CHECK_SHARED_DEPENDENTS and (ifaceobj.role & ifaceRole.SLAVE) and - (role == ifaceRole.SLAVE) and (upperifaceobj.role == ifaceRole.MASTER)): + (role == ifaceRole.SLAVE) and (upperifaceobj.role & ifaceRole.MASTER)): self.logger.error("misconfig..? %s %s is enslaved to multiple interfaces %s" %(ifaceobj.name, ifaceLinkPrivFlags.get_all_str(ifaceobj.link_privflags), str(ifaceobj.upperifaces))) @@ -510,7 +558,7 @@ class ifupdownMain(ifupdownBase): ifaceobj.link_type = ifaceLinkType.LINK_NA def dump_iface_dependency_info(self): - """ debug funtion to print raw dependency + """ debug funtion to print raw dependency info - lower and upper devices""" for ifacename, ifaceobjs in self.ifaceobjdict.iteritems(): @@ -892,6 +940,13 @@ class ifupdownMain(ifupdownBase): self.logger.debug('keyword: interface list with value: %s' % str(e)) return False + def _keyword_interface_on_off_list(self, value, validrange=None): + """ + | ( = [= ...] ) + ex: bridge-learning swp1=on swp2=off + """ + return self._keyword_interface_list_with_value(value, ['on', 'off']) + def _keyword_interface_yes_no_list(self, value, validrange=None): """ | ( = [= ...] ) @@ -908,6 +963,30 @@ class ifupdownMain(ifupdownBase): return self._keyword_interface_list_with_value(value, ['yes', 'no', 'auto']) + def _keyword_interface_l2protocol_tunnel_list(self, value, validrange=None): + """ + bridge-l2protocol-tunnel swpX=lacp,stp swpY=cdp swpZ=all + bridge-l2protocol-tunnel lacp stp,lldp,cdp + bridge-l2protocol-tunnel stp lacp cdp + bridge-l2protocol-tunnel lldp pvst + bridge-l2protocol-tunnel stp + bridge-l2protocol-tunnel all + """ + try: + if '=' in value: + for intf_arg in value.split(): + intf_arg_split = intf_arg.split('=') + for arg in re.split(',|\s*', intf_arg_split[1]): + if arg not in ['all', 'stp', 'lldp', 'lacp', 'cdp', 'pvst']: + return False + else: + for arg in re.split(',|\s*', value): + if arg not in ['all', 'stp', 'lldp', 'lacp', 'cdp', 'pvst']: + return False + except: + return False + return True + def _keyword_interface_yes_no_0_1_list(self, value, validrange=None): """ | @@ -915,16 +994,25 @@ class ifupdownMain(ifupdownBase): ex: bridge-portmcrouter swp1=yes swp2=yes swp3=1 """ return self._keyword_interface_list_with_value(value, - ['yes', 'no', '1', '0']) + ['yes', 'no', '1', '0', '2']) + + def _keyword_interface_disabled_automatic_enabled_list(self, value, validrange=None): + return self._keyword_interface_list_with_value(value, [ + '0', 'disabled', 'no', + '1', 'automatic', 'yes', + '2', 'enabled']) - def _keyword_interface_range_list(self, value, validrange): + def _keyword_interface_range_list_multiple_of_16(self, value, validrange): + return self._keyword_interface_range_list(value, validrange, multiple=16) + + def _keyword_interface_range_list(self, value, validrange, multiple=None): """ | ( = [ =number> ...] ) ex: mstpctl-portpathcost swp1=0 swp2=1 """ values = value.split() try: - if len(values) == 1: + if len(values) == 1 and '=' not in values[0]: try: n = int(values[0]) if n < int(validrange[0]) or n > int( @@ -933,6 +1021,11 @@ class ifupdownMain(ifupdownBase): ' valid attribute range: %s' % (values[0], '-'.join(validrange))) + + if multiple is not None: + if not (n % multiple == 0): + raise invalidValueError('invalid value %s: must be a multiple of %s' % (n, multiple)) + return True except invalidValueError as e: raise e @@ -954,6 +1047,11 @@ class ifupdownMain(ifupdownBase): % (iface_value[1], iface_value[0], '-'.join(validrange))) + + if multiple is not None: + if not (number % multiple == 0): + raise invalidValueError('invalid value %s: must be a multiple of %s' % (number, multiple)) + return True except invalidValueError as e: raise e @@ -1059,6 +1157,8 @@ class ifupdownMain(ifupdownBase): 'message': 'invalid value "%s": valid attribute values: %s' % (value, validvals) } + elif validvals and value in validvals: + pass elif validrange: if len(validrange) != 2: raise Exception('%s: invalid range in addon configuration' @@ -1202,45 +1302,51 @@ class ifupdownMain(ifupdownBase): self.logger.warn('error reading line \'%s\' %s:' %(l, str(e))) continue - def load_addon_modules(self, modules_dir): + def load_addon_modules(self, modules_dir_list): """ load python modules from modules_dir Default modules_dir is /usr/share/ifupdownmodules """ - self.logger.info('loading builtin modules from %s' %modules_dir) + failed_import = list() + + self.logger.info('loading builtin modules from %s' % str(modules_dir_list)) self._load_addon_modules_config() - if not modules_dir in sys.path: - sys.path.append(modules_dir) - try: - for op, mlist in self.module_ops.items(): - for mname in mlist: - if self.modules.get(mname): - continue - mpath = modules_dir + '/' + mname + '.py' - if os.path.exists(mpath): - try: - m = __import__(mname) - mclass = getattr(m, mname) - except: - raise - try: - minstance = mclass() - script_override = minstance.get_overrides_ifupdown_scripts() - self.overridden_ifupdown_scripts.extend(script_override) - except moduleNotSupported, e: - self.logger.info('module %s not loaded (%s)\n' - %(mname, str(e))) + + for modules_dir in modules_dir_list: + if not modules_dir in sys.path: + sys.path.insert(1, modules_dir) + try: + for op, mlist in self.module_ops.items(): + for mname in mlist: + if self.modules.get(mname): continue - except: - raise - self.modules[mname] = minstance - try: - self.module_attrs[mname] = minstance.get_modinfo() - except: - pass - except: - raise + mpath = modules_dir + '/' + mname + '.py' + if os.path.exists(mpath) and mpath not in failed_import: + try: + m = __import__(mname) + mclass = getattr(m, mname) + except Exception as e: + self.logger.warning('cannot load "%s" module: %s' % (mname, str(e))) + failed_import.append(mpath) + continue + try: + minstance = mclass() + script_override = minstance.get_overrides_ifupdown_scripts() + self.overridden_ifupdown_scripts.extend(script_override) + except moduleNotSupported, e: + self.logger.info('module %s not loaded (%s)\n' + %(mname, str(e))) + continue + except: + raise + self.modules[mname] = minstance + try: + self.module_attrs[mname] = minstance.get_modinfo() + except: + pass + except: + raise # Assign all modules to query operations self.module_ops['query-checkcurr'] = self.modules.keys() @@ -1249,52 +1355,65 @@ class ifupdownMain(ifupdownBase): self.module_ops['query'] = self.modules.keys() self.module_ops['query-raw'] = self.modules.keys() + def _keyword_number_comma_range_list(self, value, validrange=None): + return self._keyword_number_range_list(value.replace(',', ' '), validrange=validrange) - def _modules_help(self): - """ Prints addon modules supported syntax """ - indent = ' ' - for m, mdict in self.module_attrs.items(): - if not mdict: - continue - print('%s: %s' %(m, mdict.get('mhelp'))) - attrdict = mdict.get('attrs') - if not attrdict: - continue - try: - for attrname, attrvaldict in attrdict.items(): - if attrvaldict.get('compat', False): - continue - print('%s%s' %(indent, attrname)) - print('%shelp: %s' %(indent + ' ', - attrvaldict.get('help', ''))) - print ('%srequired: %s' %(indent + ' ', - attrvaldict.get('required', False))) - default = attrvaldict.get('default') - if default: - print('%sdefault: %s' %(indent + ' ', default)) - - validrange = attrvaldict.get('validrange') - if validrange: - print('%svalidrange: %s-%s' - %(indent + ' ', validrange[0], validrange[1])) + def _modules_help(self, fmt): + """ Prints addon modules supported syntax """ - validvals = attrvaldict.get('validvals') - if validvals: - print('%svalidvals: %s' - %(indent + ' ', ','.join(validvals))) + if fmt == 'json': + modinfos = {} + for key, value in self.modules.items(): + if hasattr(value, '_modinfo'): + modinfos[key] = { + 'mhelp': value._modinfo['mhelp'], + 'attrs': value.merge_modinfo_with_policy_files() + } + print json.dumps(modinfos) + else: + indent = ' ' + for m, mdict in self.module_attrs.items(): + if not mdict: + continue + print('%s: %s' %(m, mdict.get('mhelp'))) + attrdict = self.modules[m].merge_modinfo_with_policy_files() + if not attrdict: + continue + try: + for attrname, attrvaldict in attrdict.items(): + if attrvaldict.get('compat', False): + continue + print('%s%s' %(indent, attrname)) + print('%shelp: %s' %(indent + ' ', + attrvaldict.get('help', ''))) + print ('%srequired: %s' %(indent + ' ', + attrvaldict.get('required', False))) + default = attrvaldict.get('default') + if default: + print('%sdefault: %s' %(indent + ' ', default)) + + validrange = attrvaldict.get('validrange') + if validrange: + print('%svalidrange: %s-%s' + %(indent + ' ', validrange[0], validrange[1])) + + validvals = attrvaldict.get('validvals') + if validvals: + print('%svalidvals: %s' + %(indent + ' ', ','.join(validvals))) + + examples = attrvaldict.get('example') + if not examples: + continue - examples = attrvaldict.get('example') - if not examples: - continue + print '%sexample:' %(indent + ' ') + for e in examples: + print '%s%s' %(indent + ' ', e) + except: + pass + print '' - print '%sexample:' %(indent + ' ') - for e in examples: - print '%s%s' %(indent + ' ', e) - except: - pass - print '' - def load_scripts(self, modules_dir): """ loading user modules from /etc/network/. @@ -1313,7 +1432,7 @@ class ifupdownMain(ifupdownBase): if self.modules.get(module) or module in self.overridden_ifupdown_scripts: continue self.script_ops[op].append(msubdir + '/' + module) - except: + except: # continue reading pass @@ -1324,13 +1443,13 @@ class ifupdownMain(ifupdownBase): self._pretty_print_ordered_dict('dependency graph', self.dependency_graph) ifaceScheduler.sched_ifaces(self, ifacenames, ops, - dependency_graph=self.dependency_graph, - order=ifaceSchedulerFlags.INORDER + dependency_graph=self.dependency_graph, + order=ifaceSchedulerFlags.INORDER if 'down' in ops[0] else ifaceSchedulerFlags.POSTORDER, - followdependents=followdependents, - skipupperifaces=skipupperifaces, - sort=True if (sort or ifupdownflags.flags.CLASS) else False) + followdependents=followdependents, + skipupperifaces=skipupperifaces, + sort=True if (sort or ifupdownflags.flags.CLASS) else False) return ifaceScheduler.get_sched_status() def _render_ifacename(self, ifacename): @@ -1346,7 +1465,7 @@ class ifupdownMain(ifupdownBase): def _preprocess_ifacenames(self, ifacenames): """ validates interface list for config existance. - + returns -1 if one or more interface not found. else, returns 0 """ @@ -1370,7 +1489,7 @@ class ifupdownMain(ifupdownBase): new_ifacenames.append(i) if err_iface: raise Exception('cannot find interfaces:%s' %err_iface) - return new_ifacenames + return new_ifacenames def _iface_whitelisted(self, auto, allow_classes, excludepats, ifacename): """ Checks if interface is whitelisted depending on set of parameters. @@ -1403,7 +1522,7 @@ class ifupdownMain(ifupdownBase): ret = False for i in ifaceobjs: if i.classes: - common = Set([allow_classes]).intersection( + common = Set(allow_classes).intersection( Set(i.classes)) if common: ret = True @@ -1500,7 +1619,7 @@ class ifupdownMain(ifupdownBase): excludepats=None, printdependency=None, syntaxcheck=False, type=None, skipupperifaces=False): """This brings the interface(s) up - + Args: ops (list): list of ops to perform on the interface(s). Eg: ['pre-up', 'up', 'post-up' @@ -1527,16 +1646,22 @@ class ifupdownMain(ifupdownBase): except Exception: raise + filtered_ifacenames = None if ifacenames: ifacenames = self._preprocess_ifacenames(ifacenames) + if allow_classes: + filtered_ifacenames = self._get_filtered_ifacenames_with_classes(auto, allow_classes, excludepats, ifacenames) + # if iface list not given by user, assume all from config file if not ifacenames: ifacenames = self.ifaceobjdict.keys() - # filter interfaces based on auto and allow classes - filtered_ifacenames = [i for i in ifacenames - if self._iface_whitelisted(auto, allow_classes, + if not filtered_ifacenames: + # filter interfaces based on auto and allow classes + filtered_ifacenames = [i for i in ifacenames + if self._iface_whitelisted(auto, allow_classes, excludepats, i)] + if not filtered_ifacenames: raise Exception('no ifaces found matching given allow lists') @@ -1574,6 +1699,21 @@ class ifupdownMain(ifupdownBase): if not iface_read_ret or not ret: raise Exception() + def _get_filtered_ifacenames_with_classes(self, auto, allow_classes, excludepats, ifacenames): + # if user has specified ifacelist and allow_classes + # append the allow_classes interfaces to user + # ifacelist + filtered_ifacenames = [i for i in self.ifaceobjdict.keys() + if self._iface_whitelisted(auto, allow_classes, + excludepats, i)] + filtered_ifacenames += ifacenames + + for intf in ifacenames: + for obj in self.get_ifaceobjs(intf) or []: + obj.blacklisted = False + + return filtered_ifacenames + def down(self, ops, auto=False, allow_classes=None, ifacenames=None, excludepats=None, printdependency=None, usecurrentconfig=False, type=None): @@ -1597,27 +1737,35 @@ class ifupdownMain(ifupdownBase): self.logger.debug('Looking at old state ..') self.read_old_iface_config() else: - # If no old state available + # If no old state available try: self.read_iface_config() except Exception, e: raise Exception('error reading iface config (%s)' %str(e)) + filtered_ifacenames = None if ifacenames: # If iface list is given by the caller, always check if iface # is present try: ifacenames = self._preprocess_ifacenames(ifacenames) + + if allow_classes: + filtered_ifacenames = self._get_filtered_ifacenames_with_classes(auto, allow_classes, excludepats, ifacenames) + except Exception, e: raise Exception('%s' %str(e) + ' (interface was probably never up ?)') + # if iface list not given by user, assume all from config file if not ifacenames: ifacenames = self.ifaceobjdict.keys() - # filter interfaces based on auto and allow classes - filtered_ifacenames = [i for i in ifacenames - if self._iface_whitelisted(auto, allow_classes, - excludepats, i)] + if not filtered_ifacenames: + # filter interfaces based on auto and allow classes + filtered_ifacenames = [i for i in ifacenames + if self._iface_whitelisted(auto, allow_classes, + excludepats, i)] + if not filtered_ifacenames: raise Exception('no ifaces found matching given allow lists ' + '(or interfaces were probably never up ?)') @@ -1646,7 +1794,7 @@ class ifupdownMain(ifupdownBase): self.set_type(type) - # Let us forget internal squashing when it comes to + # Let us forget internal squashing when it comes to # ifquery. It can surprise people relying of ifquery # output self._ifaceobj_squash_internal = False @@ -1662,7 +1810,7 @@ class ifupdownMain(ifupdownBase): ifupdownflags.flags.WITH_DEPENDS = True if ops[0] == 'query-syntax': - self._modules_help() + self._modules_help(format) return elif ops[0] == 'query-running': # create fake devices to all dependents that dont have config @@ -1675,8 +1823,11 @@ class ifupdownMain(ifupdownBase): raise if ifacenames and ops[0] != 'query-running': - # If iface list is given, always check if iface is present - ifacenames = self._preprocess_ifacenames(ifacenames) + # If iface list is given, always check if iface is present + ifacenames = self._preprocess_ifacenames(ifacenames) + + if allow_classes: + filtered_ifacenames = self._get_filtered_ifacenames_with_classes(auto, allow_classes, excludepats, ifacenames) # if iface list not given by user, assume all from config file if not ifacenames: ifacenames = self.ifaceobjdict.keys() @@ -1684,10 +1835,16 @@ class ifupdownMain(ifupdownBase): # filter interfaces based on auto and allow classes if ops[0] == 'query-running': filtered_ifacenames = ifacenames - else: - filtered_ifacenames = [i for i in ifacenames - if self._iface_whitelisted(auto, allow_classes, - excludepats, i)] + elif not allow_classes: + filtered_ifacenames = [ + i for i in ifacenames + if self._iface_whitelisted( + auto, + allow_classes, + excludepats, i + ) + ] + if not filtered_ifacenames: raise Exception('no ifaces found matching ' + 'given allow lists') @@ -1706,7 +1863,7 @@ class ifupdownMain(ifupdownBase): return self.print_ifaceobjs_raw(filtered_ifacenames) ret = self._sched_ifaces(filtered_ifacenames, ops, - followdependents=True + followdependents=True if ifupdownflags.flags.WITH_DEPENDS else False) if ops[0] == 'query' and ifupdownflags.flags.WITHDEFAULTS: @@ -1778,7 +1935,7 @@ class ifupdownMain(ifupdownBase): # old interface config is read into self.ifaceobjdict self.read_old_iface_config() - # reinitialize dependency graph + # reinitialize dependency graph self.dependency_graph = OrderedDict({}) falready_up_ifacenames_not_present = [i for i in already_up_ifacenames_not_present @@ -1882,14 +2039,14 @@ class ifupdownMain(ifupdownBase): # these are saved interfaces and dependency for these # have been checked before they became part of saved state. try: - self.flags.CHECK_SHARED_DEPENDENTS = False + self.flags.CHECK_SHARED_DEPENDENTS = False self.populate_dependency_info(upops) self.flags.CHECK_SHARED_DEPENDENTS = True except Exception, e: self.logger.info("error generating dependency graph for " "saved interfaces (%s)" %str(e)) pass - + # make sure we pick up built-in interfaces # if config file had 'ifreload_down_changed' variable # set, also look for interfaces that changed to down them @@ -1923,7 +2080,7 @@ class ifupdownMain(ifupdownBase): # for example: remove a bond section from the interfaces # file, but leave it around as a bridge port # XXX: Ideally its better to just add it to the - # ifacedownlist. But we will be cautious here + # ifacedownlist. But we will be cautious here # and just print a warning if (self.is_ifaceobj_noconfig(newifaceobjlist[0]) and not self.is_ifaceobj_builtin(newifaceobjlist[0]) and @@ -1936,6 +2093,16 @@ class ifupdownMain(ifupdownBase): % (newifaceobjlist[objidx].name, str(newifaceobjlist[objidx].upperifaces), newifaceobjlist[objidx].name)) + if (lastifaceobjlist[0].link_kind and + not newifaceobjlist[0].link_kind): + self.logger.warn('%s: moved from being a %s to a' + ' physical interface (non-logical interface).' + 'This interface will be downed.\n' + ' If this was not intentional, please restore the' + ' original interface definition and execute ifreload' + % (newifaceobjlist[objidx].name, + ifaceLinkKind.to_str(lastifaceobjlist[0].link_kind))) + ifacedownlist.append(newifaceobjlist[objidx].name) if not down_changed: continue if len(newifaceobjlist) != len(lastifaceobjlist): @@ -1955,7 +2122,7 @@ class ifupdownMain(ifupdownBase): if ifacedownlist: self.logger.info('reload: scheduling down on interfaces: %s' %str(ifacedownlist)) - # reinitialize dependency graph + # reinitialize dependency graph self.dependency_graph = OrderedDict({}) # Generate dependency info for old config @@ -1970,7 +2137,7 @@ class ifupdownMain(ifupdownBase): # Hence during reload, set this to true. # This is being added to avoid a failure in # scheduler._check_upperifaces when we are dowing - # a builtin bridge port + # a builtin bridge port self.flags.SCHED_SKIP_CHECK_UPPERIFACES = True self._sched_ifaces(ifacedownlist, downops, followdependents=False, @@ -2065,11 +2232,9 @@ class ifupdownMain(ifupdownBase): for i in ifacenames: for ifaceobj in self.get_ifaceobjs(i): - if (self.is_ifaceobj_builtin(ifaceobj) or - not ifaceobj.is_config_present()): + if self.is_ifaceobj_builtin(ifaceobj): continue ifaceobj.dump_raw(self.logger) - print '\n' if (ifupdownflags.flags.WITH_DEPENDS and not ifupdownflags.flags.ALL): dlist = ifaceobj.lowerifaces diff --git a/ifupdown2/ifupdown/log.py b/ifupdown2/ifupdown/log.py new file mode 100644 index 0000000..732c68a --- /dev/null +++ b/ifupdown2/ifupdown/log.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python +# +# Copyright 2016-2017 Cumulus Networks, Inc. All rights reserved. +# Author: Julien Fortin, julien@cumulusnetworks.com +# + +import sys +import json +import struct +import select +import logging +import logging.handlers + +from cStringIO import StringIO + + +class Log: + LOGGER_NAME = sys.argv[0].split('/')[-1] + + def __init__(self): + """ + - On start the daemon will log on syslog. + - For each client commands we might need to adjust the target + (stderr/stdout): + if -v --verbose or -d --debug are provided we override + sys.stdout and sys.stderr with string buffers to be able to send + back the content of these buffer on the UNIX socket back to the + client. + if -l or --syslog we make sure to use syslog. + """ + + self.stdout_buffer = None + self.stderr_buffer = None + + self.root = logging.getLogger() + self.root.name = Log.LOGGER_NAME + self.root.setLevel(logging.INFO) + + self.root_info = self.root.info + self.root_debug = self.root.debug + self.root_error = self.root.error + self.root_warning = self.root.warning + self.root_critical = self.root.critical + + self.root.info = self.info + self.root.debug = self.debug + self.root.error = self.error + self.root.warning = self.warning + self.root.critical = self.critical + + logging.addLevelName(logging.CRITICAL, 'critical') + logging.addLevelName(logging.WARNING, 'warning') + logging.addLevelName(logging.ERROR, 'error') + logging.addLevelName(logging.DEBUG, 'debug') + logging.addLevelName(logging.INFO, 'info') + + self.syslog = True + self.socket = None + + # syslog + facility = logging.handlers.SysLogHandler.LOG_DAEMON + address = '/dev/log' + format = '%(name)s: %(levelname)s: %(message)s' + + self.syslog_handler = logging.handlers.SysLogHandler(address=address, facility=facility) + self.syslog_handler.setFormatter(logging.Formatter(format)) + + # console + format = '%(levelname)s: %(message)s' + self.console_handler = logging.StreamHandler(sys.stderr) + self.console_handler.setFormatter(logging.Formatter(format)) + + if self.LOGGER_NAME[-1] == 'd': + self.update_current_logger(syslog=True, verbose=True, debug=False) + else: + self.update_current_logger(syslog=False, verbose=False, debug=False) + + def update_current_logger(self, syslog, verbose, debug): + self.syslog = syslog + self.root.setLevel(self.get_log_level(verbose=verbose, debug=debug)) + self.root.handlers = [self.syslog_handler if self.syslog else self.console_handler] + self.flush() + + def flush(self): + if self.socket: + result = dict() + stdout = self._flush_buffer('stdout', self.stdout_buffer, result) + stderr = self._flush_buffer('stderr', self.stderr_buffer, result) + if stdout or stderr: + try: + self.tx_data(json.dumps(result)) + self.redirect_stdouput() + except select.error as e: + # haven't seen the case yet + self.socket = None + self.update_current_logger(syslog=True, verbose=True) + self.critical(str(e)) + exit(84) + self.console_handler.flush() + self.syslog_handler.flush() + + def tx_data(self, data, socket=None): + socket_obj = socket if socket else self.socket + ready = select.select([], [socket_obj], []) + if ready and ready[1] and ready[1][0] == socket_obj: + frmt = "=%ds" % len(data) + packed_msg = struct.pack(frmt, data) + packed_hdr = struct.pack('=I', len(packed_msg)) + socket_obj.sendall(packed_hdr) + socket_obj.sendall(packed_msg) + + def set_socket(self, socket): + self.socket = socket + self.redirect_stdouput() + + def redirect_stdouput(self): + self.stdout_buffer = sys.stdout = StringIO() + self.stderr_buffer = self.console_handler.stream = sys.stderr = StringIO() + + def error(self, msg, *args, **kwargs): + self.root_error(msg, *args, **kwargs) + self.flush() + + def critical(self, msg, *args, **kwargs): + self.root_critical(msg, *args, **kwargs) + self.flush() + + def warning(self, msg, *args, **kwargs): + self.root_warning(msg, *args, **kwargs) + self.flush() + + def info(self, msg, *args, **kwargs): + self.root_info(msg, *args, **kwargs) + self.flush() + + def debug(self, msg, *args, **kwargs): + self.root_debug(msg, *args, **kwargs) + self.flush() + + def get_current_log_level(self): + return self.root.level + + def is_syslog(self): return self.syslog + + @staticmethod + def get_log_level(verbose=False, debug=False): + log_level = logging.WARNING + if debug: + log_level = logging.DEBUG + elif verbose: + log_level = logging.INFO + return log_level + + @staticmethod + def _flush_buffer(stream, buff, dictionary): + if buff: + data = buff.getvalue() + if data: + dictionary[stream] = data + return True + + +log = Log() + + +""" + +#logging.basicConfig( format="%(filename)s: %(username)s says '%(message)s' in %(funcname)s" ) + +Logger.debug(msg, *args, **kwargs) +Logs a message with level DEBUG on this logger. The msg is the message format string, and the args are the arguments which are merged into msg using the string formatting operator. (Note that this means that you can use keywords in the format string, together with a single dictionary argument.) + +There are two keyword arguments in kwargs which are inspected: exc_info which, if it does not evaluate as false, causes exception information to be added to the logging message. If an exception tuple (in the format returned by sys.exc_info()) is provided, it is used; otherwise, sys.exc_info() is called to get the exception information. + +""" + +""" +USE FILTER TO IGNORE "EXITS" MESSAGES +Now that you know the basic plot, let me introduce one more character - the Filter. +Filter as the name suggests, allows you to filter a message before you log it. Yes, messages are filtered based on the level setting, but adding a Filter gives you more fine grained control of messages you log. +Both Loggers and Handlers can have multiple Filters. You can add Filters using addFilter and removeFilter methods. +When a Logger/Handler receives a message, it consults all of its filters. If the filter(record) method on any of the Filters attached returns False (or 0) the message is dropped. +The official documentation, though detailed, is actually pretty confusing about the role of Filters. This is a pity; because Filters can be handy when you want to drop a message based on a regular expression, error code, contextual information and pretty much anything else. The default Filter is pretty much useless (and the doc string is very confusing too). Just inherit from the default filter and override the filter method according to what you want to filter out. (Be sure to download the source for logging module and check out the unit tests which have some good examples. See the references at the end of this post.)""" diff --git a/ifupdown2/ifupdown/main.py b/ifupdown2/ifupdown/main.py new file mode 100644 index 0000000..e54ace8 --- /dev/null +++ b/ifupdown2/ifupdown/main.py @@ -0,0 +1,277 @@ +#!/usr/bin/python +# +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. +# Authors: +# Roopa Prabhu, roopa@cumulusnetworks.com +# Julien Fortin, julien@cumulusnetworks.com +# ifupdown2 -- +# tool to configure network interfaces +# + +import os +import sys +import signal +import StringIO +import ConfigParser + +try: + from ifupdown2.ifupdown.log import log + from ifupdown2.ifupdown.argv import Parse + from ifupdown2.ifupdown.config import IFUPDOWN2_CONF_PATH + from ifupdown2.ifupdown.ifupdownmain import ifupdownMain +except ImportError: + from ifupdown.log import log + from ifupdown.argv import Parse + from ifupdown.config import IFUPDOWN2_CONF_PATH + from ifupdown.ifupdownmain import ifupdownMain + + +_SIGINT = signal.getsignal(signal.SIGINT) +_SIGTERM = signal.getsignal(signal.SIGTERM) +_SIGQUIT = signal.getsignal(signal.SIGQUIT) + +configmap_g = None + + +class Ifupdown2: + def __init__(self, daemon, uid): + self.daemon = daemon + self.uid = uid + self.args = None + self.op = None + + self.interfaces_filename = None + self.interfaces_file_iobuf = None + + self.handlers = { + 'up': self.run_up, + 'down': self.run_down, + 'query': self.run_query, + 'reload': self.run_reload + } + + def parse_argv(self, argv): + args_parse = Parse(argv) + args_parse.validate() + + self.args = args_parse.get_args() + self.op = args_parse.get_op() + + def update_logger(self, socket=None): + syslog = self.args.syslog if hasattr(self.args, 'syslog') else False + log.update_current_logger(syslog=syslog, + verbose=self.args.verbose, + debug=self.args.debug) + if socket: + log.set_socket(socket) + + def main(self, stdin_buffer=None): + if self.op != 'query' and self.uid != 0: + raise Exception('must be root to run this command') + + try: + self.read_config() + self.init(stdin_buffer) + self.handlers.get(self.op)(self.args) + except Exception, e: + if not str(e): + return 1 + # if args and args.debug: + raise + # else: + if log: + log.error(str(e)) + else: + print str(e) + # if args and not args.debug: + # print '\nrerun the command with \'-d\' for a detailed errormsg' + return 1 + return 0 + + def init(self, stdin_buffer): + if hasattr(self.args, 'interfacesfile') and self.args.interfacesfile != None: + # Check to see if -i option is allowed by config file + # But for ifquery, we will not check this + if (not self.op == 'query' and + configmap_g.get('disable_cli_interfacesfile', '0') == '1'): + log.error('disable_cli_interfacesfile is set so users ' + 'not allowed to specify interfaces file on cli.') + raise Exception("") + if self.args.interfacesfile == '-': + # If interfaces file is stdin, read + if self.daemon: + self.interfaces_file_iobuf = stdin_buffer + else: + self.interfaces_file_iobuf = sys.stdin.read() + else: + self.interfaces_filename = self.args.interfacesfile + else: + # if the ifupdown2 config file does not have it, default to standard + self.interfaces_filename = configmap_g.get('default_interfaces_configfile', + '/etc/network/interfaces') + + def read_config(self): + global configmap_g + + with open(IFUPDOWN2_CONF_PATH, 'r') as f: + config = f.read() + configStr = '[ifupdown2]\n' + config + configFP = StringIO.StringIO(configStr) + parser = ConfigParser.RawConfigParser() + parser.readfp(configFP) + configmap_g = dict(parser.items('ifupdown2')) + + # Preprocess config map + configval = configmap_g.get('multiple_vlan_aware_bridge_support', '0') + if configval == '0': + # if multiple bridges not allowed, set the bridge-vlan-aware + # attribute in the 'no_repeats' config, so that the ifupdownmain + # module can catch it appropriately + configmap_g['no_repeats'] = {'bridge-vlan-aware': 'yes'} + + configval = configmap_g.get('link_master_slave', '0') + if configval == '1': + # link_master_slave is only valid when all is set + if hasattr(self.args, 'all') and not self.args.all: + configmap_g['link_master_slave'] = '0' + + configval = configmap_g.get('delay_admin_state_change', '0') + if configval == '1': + # reset link_master_slave if delay_admin_state_change is on + configmap_g['link_master_slave'] = '0' + + def run_up(self, args): + log.debug('args = %s' % str(args)) + + try: + iflist = args.iflist + if len(args.iflist) == 0: + iflist = None + log.debug('creating ifupdown object ..') + cachearg = (False if (iflist or args.nocache or args.noact) + else True) + ifupdown_handle = ifupdownMain(daemon=self.daemon, + config=configmap_g, + force=args.force, + withdepends=args.withdepends, + perfmode=args.perfmode, + dryrun=args.noact, + cache=cachearg, + addons_enable=not args.noaddons, + statemanager_enable=not args.noaddons, + interfacesfile=self.interfaces_filename, + interfacesfileiobuf=self.interfaces_file_iobuf, + interfacesfileformat=args.interfacesfileformat) + if args.noaddons: + ifupdown_handle.up(['up'], args.all, args.CLASS, iflist, + excludepats=args.excludepats, + printdependency=args.printdependency, + syntaxcheck=args.syntaxcheck, type=args.type, + skipupperifaces=args.skipupperifaces) + else: + ifupdown_handle.up(['pre-up', 'up', 'post-up'], + args.all, args.CLASS, iflist, + excludepats=args.excludepats, + printdependency=args.printdependency, + syntaxcheck=args.syntaxcheck, type=args.type, + skipupperifaces=args.skipupperifaces) + except: + raise + + def run_down(self, args): + log.debug('args = %s' % str(args)) + + try: + iflist = args.iflist + log.debug('creating ifupdown object ..') + ifupdown_handle = ifupdownMain(daemon=self.daemon, + config=configmap_g, force=args.force, + withdepends=args.withdepends, + perfmode=args.perfmode, + dryrun=args.noact, + addons_enable=not args.noaddons, + statemanager_enable=not args.noaddons, + interfacesfile=self.interfaces_filename, + interfacesfileiobuf=self.interfaces_file_iobuf, + interfacesfileformat=args.interfacesfileformat) + + ifupdown_handle.down(['pre-down', 'down', 'post-down'], + args.all, args.CLASS, iflist, + excludepats=args.excludepats, + printdependency=args.printdependency, + usecurrentconfig=args.usecurrentconfig, + type=args.type) + except: + raise + + def run_query(self, args): + log.debug('args = %s' % str(args)) + + try: + iflist = args.iflist + if args.checkcurr: + qop = 'query-checkcurr' + elif args.running: + qop = 'query-running' + elif args.raw: + qop = 'query-raw' + elif args.syntaxhelp: + qop = 'query-syntax' + elif args.printdependency: + qop = 'query-dependency' + elif args.printsavedstate: + qop = 'query-savedstate' + else: + qop = 'query' + cachearg = (False if (iflist or args.nocache or args.syntaxhelp or + (qop != 'query-checkcurr' and + qop != 'query-running')) else True) + if not iflist and qop == 'query-running': + iflist = [i for i in os.listdir('/sys/class/net/') + if os.path.isdir('/sys/class/net/%s' % i)] + log.debug('creating ifupdown object ..') + ifupdown_handle = ifupdownMain(daemon=self.daemon, + config=configmap_g, + withdepends=args.withdepends, + perfmode=args.perfmode, + cache=cachearg, + interfacesfile=self.interfaces_filename, + interfacesfileiobuf=self.interfaces_file_iobuf, + interfacesfileformat=args.interfacesfileformat, + withdefaults=args.withdefaults) + # list implies all auto interfaces (this is how ifupdown behaves) + if args.list: + args.all = True + ifupdown_handle.query([qop], args.all, args.list, args.CLASS, iflist, + excludepats=args.excludepats, + printdependency=args.printdependency, + format=args.format, type=args.type) + except: + raise + + def run_reload(self, args): + log.debug('args = %s' % str(args)) + + try: + log.debug('creating ifupdown object ..') + ifupdown_handle = ifupdownMain(daemon=self.daemon, + config=configmap_g, + interfacesfile=self.interfaces_filename, + withdepends=args.withdepends, + perfmode=args.perfmode, + dryrun=args.noact) + ifupdown_handle.reload(['pre-up', 'up', 'post-up'], + ['pre-down', 'down', 'post-down'], + auto=args.all, allow=args.CLASS, ifacenames=None, + excludepats=args.excludepats, + usecurrentconfig=args.usecurrentconfig, + syntaxcheck=args.syntaxcheck, + currentlyup=args.currentlyup) + except: + raise + + @staticmethod + def set_signal_handlers(): + signal.signal(signal.SIGQUIT, _SIGQUIT) + signal.signal(signal.SIGTERM, _SIGTERM) + signal.signal(signal.SIGINT, _SIGINT) diff --git a/ifupdown2/ifupdown/netlink.py b/ifupdown2/ifupdown/netlink.py new file mode 100644 index 0000000..6a29902 --- /dev/null +++ b/ifupdown2/ifupdown/netlink.py @@ -0,0 +1,627 @@ +#!/usr/bin/python +# +# Copyright 2016-2017 Cumulus Networks, Inc. All rights reserved. +# Author: Julien Fortin, julien@cumulusnetworks.com +# + +import sys +import socket +import logging + +from collections import OrderedDict + +try: + import ifupdown2.nlmanager.nlpacket + import ifupdown2.ifupdown.ifupdownflags as ifupdownflags + + from ifupdown2.ifupdownaddons.cache import * + from ifupdown2.ifupdownaddons.utilsbase import utilsBase + + from ifupdown2.nlmanager.nlmanager import Link, Address, Route, NetlinkPacket +except ImportError: + import nlmanager.nlpacket + import ifupdown.ifupdownflags as ifupdownflags + + from ifupdownaddons.cache import * + from ifupdownaddons.utilsbase import utilsBase + + from nlmanager.nlmanager import Link, Address, Route, NetlinkPacket + + +class Netlink(utilsBase): + VXLAN_UDP_PORT = 4789 + + def __init__(self, *args, **kargs): + utilsBase.__init__(self, *args, **kargs) + try: + sys.path.insert(0, '/usr/share/ifupdown2/') + try: + from ifupdown2.nlmanager.nlmanager import NetlinkManager + # Override the nlmanager's mac_int_to_str function to print the MACs + # like xx:xx:xx:xx:xx:xx instead of xxxx.xxxx.xxxx + ifupdown2.nlmanager.nlpacket.mac_int_to_str = self.mac_int_to_str + except ImportError: + from nlmanager.nlmanager import NetlinkManager + # Override the nlmanager's mac_int_to_str function to print the MACs + # like xx:xx:xx:xx:xx:xx instead of xxxx.xxxx.xxxx + nlmanager.nlpacket.mac_int_to_str = self.mac_int_to_str + + # this should force the use of the local nlmanager + self._nlmanager_api = NetlinkManager(log_level=logging.WARNING) + + self.link_kind_handlers = { + 'vlan': self._link_dump_info_data_vlan, + 'vrf': self._link_dump_info_data_vrf, + 'vxlan': self._link_dump_info_data_vxlan, + 'bond': self._link_dump_info_data_bond, + 'bridge': self._link_dump_info_data_bridge + } + + except Exception as e: + self.logger.error('cannot initialize ifupdown2\'s ' + 'netlink manager: %s' % str(e)) + raise + + @staticmethod + def IN_MULTICAST(a): + """ + /include/uapi/linux/in.h + + #define IN_CLASSD(a) ((((long int) (a)) & 0xf0000000) == 0xe0000000) + #define IN_MULTICAST(a) IN_CLASSD(a) + """ + return (int(a) & 0xf0000000) == 0xe0000000 + + @staticmethod + def mac_int_to_str(mac_int): + """ + Return an integer in MAC string format: xx:xx:xx:xx:xx:xx + """ + return ':'.join(("%012x" % mac_int)[i:i + 2] for i in range(0, 12, 2)) + + def get_iface_index(self, ifacename): + if ifupdownflags.flags.DRYRUN: return + try: + return self._nlmanager_api.get_iface_index(ifacename) + except Exception as e: + raise Exception('%s: netlink: %s: cannot get ifindex: %s' + % (ifacename, ifacename, str(e))) + + def get_iface_name(self, ifindex): + if ifupdownflags.flags.DRYRUN: return + try: + return self._nlmanager_api.get_iface_name(ifindex) + except Exception as e: + raise Exception('netlink: cannot get ifname for index %s: %s' % (ifindex, str(e))) + + def get_bridge_vlan(self, ifname): + self.logger.info('%s: netlink: /sbin/bridge -d -c -json vlan show' % ifname) + if ifupdownflags.flags.DRYRUN: return + try: + return self._nlmanager_api.vlan_get() + except Exception as e: + raise Exception('netlink: get bridge vlan: %s' % str(e)) + + def bridge_set_vlan_filtering(self, ifname, vlan_filtering): + self.logger.info('%s: netlink: ip link set dev %s type bridge vlan_filtering %s' + % (ifname, ifname, vlan_filtering)) + if ifupdownflags.flags.DRYRUN: return + try: + ifla_info_data = {Link.IFLA_BR_VLAN_FILTERING: int(vlan_filtering)} + return self._nlmanager_api.link_set_attrs(ifname, 'bridge', ifla_info_data=ifla_info_data) + except Exception as e: + raise Exception('%s: cannot set %s vlan_filtering %s' % (ifname, ifname, vlan_filtering)) + + def link_add_set(self, + ifname=None, ifindex=0, + kind=None, slave_kind=None, + ifla={}, + ifla_info_data={}, + ifla_info_slave_data={}, + link_exists=False): + action = 'set' if ifindex or link_exists else 'add' + + if slave_kind: + self.logger.info('%s: netlink: ip link set dev %s: %s slave attributes' % (ifname, ifname, slave_kind)) + else: + self.logger.info('%s: netlink: ip link %s %s type %s with attributes' % (ifname, action, ifname, kind)) + if ifla: + self.logger.debug('%s: ifla attributes a %s' % (ifname, ifla)) + if ifla_info_data: + self.logger.debug('%s: ifla_info_data %s' % (ifname, ifla_info_data)) + if ifla_info_slave_data: + self.logger.debug('%s: ifla_info_slave_data %s' % (ifname, ifla_info_slave_data)) + + if ifupdownflags.flags.DRYRUN: return + try: + self._nlmanager_api.link_add_set(ifname=ifname, + ifindex=ifindex, + kind=kind, + slave_kind=slave_kind, + ifla=ifla, + ifla_info_data=ifla_info_data, + ifla_info_slave_data=ifla_info_slave_data) + except Exception as e: + if kind and not slave_kind: + kind_str = kind + elif kind and slave_kind: + kind_str = '%s (%s slave)' % (kind, slave_kind) + else: + kind_str = '(%s slave)' % slave_kind + + raise Exception('netlink: cannot %s %s %s with options: %s' % (action, kind_str, ifname, str(e))) + + def link_del(self, ifname): + self.logger.info('%s: netlink: ip link del %s' % (ifname, ifname)) + if ifupdownflags.flags.DRYRUN: return + try: + self._nlmanager_api.link_del(ifname=ifname) + except Exception as e: + raise Exception('netlink: cannot delete link %s: %s' % (ifname, str(e))) + + def link_set_master(self, ifacename, master_dev, state=None): + self.logger.info('%s: netlink: ip link set dev %s master %s %s' + % (ifacename, ifacename, master_dev, + state if state else '')) + if ifupdownflags.flags.DRYRUN: return + try: + master = 0 if not master_dev else self.get_iface_index(master_dev) + return self._nlmanager_api.link_set_master(ifacename, + master_ifindex=master, + state=state) + except Exception as e: + raise Exception('netlink: %s: cannot set %s master %s: %s' + % (ifacename, ifacename, master_dev, str(e))) + + def link_set_nomaster(self, ifacename, state=None): + self.logger.info('%s: netlink: ip link set dev %s nomaster %s' + % (ifacename, ifacename, state if state else '')) + if ifupdownflags.flags.DRYRUN: return + try: + return self._nlmanager_api.link_set_master(ifacename, + master_ifindex=0, + state=state) + except Exception as e: + raise Exception('netlink: %s: cannot set %s nomaster: %s' + % (ifacename, ifacename, str(e))) + + def link_add_vlan(self, vlanrawdevice, ifacename, vlanid, vlan_protocol): + if vlan_protocol: + self.logger.info('%s: netlink: ip link add link %s name %s type vlan id %s protocol %s' + % (ifacename, vlanrawdevice, ifacename, vlanid, vlan_protocol)) + + else: + self.logger.info('%s: netlink: ip link add link %s name %s type vlan id %s' + % (ifacename, vlanrawdevice, ifacename, vlanid)) + if ifupdownflags.flags.DRYRUN: return + ifindex = self.get_iface_index(vlanrawdevice) + try: + return self._nlmanager_api.link_add_vlan(ifindex, ifacename, vlanid, vlan_protocol) + except Exception as e: + raise Exception('netlink: %s: cannot create vlan %s: %s' + % (vlanrawdevice, vlanid, str(e))) + + def link_add_macvlan(self, ifacename, macvlan_ifacename): + self.logger.info('%s: netlink: ip link add link %s name %s type macvlan mode private' + % (ifacename, ifacename, macvlan_ifacename)) + if ifupdownflags.flags.DRYRUN: return + ifindex = self.get_iface_index(ifacename) + try: + return self._nlmanager_api.link_add_macvlan(ifindex, macvlan_ifacename) + except Exception as e: + raise Exception('netlink: %s: cannot create macvlan %s: %s' + % (ifacename, macvlan_ifacename, str(e))) + + def link_set_updown(self, ifacename, state): + self.logger.info('%s: netlink: ip link set dev %s %s' + % (ifacename, ifacename, state)) + if ifupdownflags.flags.DRYRUN: return + try: + return self._nlmanager_api.link_set_updown(ifacename, state) + except Exception as e: + raise Exception('netlink: cannot set link %s %s: %s' + % (ifacename, state, str(e))) + + def link_set_protodown(self, ifacename, state): + self.logger.info('%s: netlink: set link %s protodown %s' + % (ifacename, ifacename, state)) + if ifupdownflags.flags.DRYRUN: return + try: + return self._nlmanager_api.link_set_protodown(ifacename, state) + except Exception as e: + raise Exception('netlink: cannot set link %s protodown %s: %s' + % (ifacename, state, str(e))) + + def link_add_bridge(self, ifname): + self.logger.info('%s: netlink: ip link add %s type bridge' % (ifname, ifname)) + if ifupdownflags.flags.DRYRUN: return + try: + return self._nlmanager_api.link_add_bridge(ifname) + except Exception as e: + raise Exception('netlink: cannot create bridge %s: %s' % (ifname, str(e))) + + def link_add_bridge_vlan(self, ifacename, vlanid): + self.logger.info('%s: netlink: bridge vlan add vid %s dev %s' + % (ifacename, vlanid, ifacename)) + if ifupdownflags.flags.DRYRUN: return + ifindex = self.get_iface_index(ifacename) + try: + return self._nlmanager_api.link_add_bridge_vlan(ifindex, vlanid) + except Exception as e: + raise Exception('netlink: %s: cannot create bridge vlan %s: %s' + % (ifacename, vlanid, str(e))) + + def link_del_bridge_vlan(self, ifacename, vlanid): + self.logger.info('%s: netlink: bridge vlan del vid %s dev %s' + % (ifacename, vlanid, ifacename)) + if ifupdownflags.flags.DRYRUN: return + ifindex = self.get_iface_index(ifacename) + try: + return self._nlmanager_api.link_del_bridge_vlan(ifindex, vlanid) + except Exception as e: + raise Exception('netlink: %s: cannot remove bridge vlan %s: %s' + % (ifacename, vlanid, str(e))) + + def link_add_vxlan(self, ifacename, vxlanid, local=None, dstport=VXLAN_UDP_PORT, + group=None, learning=True, ageing=None): + cmd = 'ip link add %s type vxlan id %s dstport %s' % (ifacename, + vxlanid, + dstport) + cmd += ' local %s' % local if local else '' + cmd += ' ageing %s' % ageing if ageing else '' + cmd += ' remote %s' % group if group else ' noremote' + cmd += ' nolearning' if not learning else '' + self.logger.info('%s: netlink: %s' % (ifacename, cmd)) + if ifupdownflags.flags.DRYRUN: return + try: + return self._nlmanager_api.link_add_vxlan(ifacename, + vxlanid, + dstport=dstport, + local=local, + group=group, + learning=learning, + ageing=ageing) + except Exception as e: + raise Exception('netlink: %s: cannot create vxlan %s: %s' + % (ifacename, vxlanid, str(e))) + + @staticmethod + def _link_dump_attr(link, ifla_attributes, dump): + for obj in ifla_attributes: + attr = link.attributes.get(obj['attr']) + if attr: + dump[obj['name']] = attr.get_pretty_value(obj=obj.get('func')) + + @staticmethod + def _link_dump_linkdata_attr(linkdata, ifla_linkdata_attr, dump): + for obj in ifla_linkdata_attr: + attr = obj['attr'] + if attr in linkdata: + func = obj.get('func') + value = linkdata.get(attr) + + if func: + value = func(value) + + if value or obj['accept_none']: + dump[obj['name']] = value + + ifla_attributes = [ + { + 'attr': Link.IFLA_LINK, + 'name': 'link', + 'func': lambda x: netlink.get_iface_name(x) if x > 0 else None + }, + { + 'attr': Link.IFLA_MASTER, + 'name': 'master', + 'func': lambda x: netlink.get_iface_name(x) if x > 0 else None + }, + { + 'attr': Link.IFLA_IFNAME, + 'name': 'ifname', + 'func': str, + }, + { + 'attr': Link.IFLA_MTU, + 'name': 'mtu', + 'func': str + }, + { + 'attr': Link.IFLA_OPERSTATE, + 'name': 'state', + 'func': lambda x: '0%x' % int(x) if x > len(Link.oper_to_string) else Link.oper_to_string[x][8:] + } + ] + + ifla_address = {'attr': Link.IFLA_ADDRESS, 'name': 'hwaddress', 'func': str} + + ifla_vxlan_attributes = [ + { + 'attr': Link.IFLA_VXLAN_LOCAL, + 'name': 'local', + 'func': str, + 'accept_none': True + }, + { + 'attr': Link.IFLA_VXLAN_LOCAL6, + 'name': 'local', + 'func': str, + 'accept_none': True + }, + { + 'attr': Link.IFLA_VXLAN_GROUP, + 'name': 'svcnode', + 'func': lambda x: str(x) if not Netlink.IN_MULTICAST(x) else None, + 'accept_none': False + }, + { + 'attr': Link.IFLA_VXLAN_GROUP6, + 'name': 'svcnode', + 'func': lambda x: str(x) if not Netlink.IN_MULTICAST(x) else None, + 'accept_none': False + }, + { + 'attr': Link.IFLA_VXLAN_LEARNING, + 'name': 'learning', + 'func': lambda x: 'on' if x else 'off', + 'accept_none': True + } + ] + + def _link_dump_info_data_vlan(self, ifname, linkdata): + return { + 'vlanid': str(linkdata.get(Link.IFLA_VLAN_ID, '')), + 'vlan_protocol': linkdata.get(Link.IFLA_VLAN_PROTOCOL) + } + + def _link_dump_info_data_vrf(self, ifname, linkdata): + vrf_info = {'table': str(linkdata.get(Link.IFLA_VRF_TABLE, ''))} + + # to remove later when moved to a true netlink cache + linkCache.vrfs[ifname] = vrf_info + return vrf_info + + def _link_dump_info_data_vxlan(self, ifname, linkdata): + for attr, value in ( + ('learning', 'on'), + ('svcnode', None), + ('vxlanid', str(linkdata.get(Link.IFLA_VXLAN_ID, ''))), + ('ageing', str(linkdata.get(Link.IFLA_VXLAN_AGEING, ''))), + (Link.IFLA_VXLAN_PORT, linkdata.get(Link.IFLA_VXLAN_PORT)) + ): + linkdata[attr] = value + self._link_dump_linkdata_attr(linkdata, self.ifla_vxlan_attributes, linkdata) + return linkdata + + ifla_bond_attributes = ( + Link.IFLA_BOND_MODE, + Link.IFLA_BOND_MIIMON, + Link.IFLA_BOND_USE_CARRIER, + Link.IFLA_BOND_AD_LACP_RATE, + Link.IFLA_BOND_XMIT_HASH_POLICY, + Link.IFLA_BOND_MIN_LINKS, + Link.IFLA_BOND_NUM_PEER_NOTIF, + Link.IFLA_BOND_AD_ACTOR_SYSTEM, + Link.IFLA_BOND_AD_ACTOR_SYS_PRIO, + Link.IFLA_BOND_AD_LACP_BYPASS, + Link.IFLA_BOND_UPDELAY, + Link.IFLA_BOND_DOWNDELAY, + ) + + def _link_dump_info_data_bond(self, ifname, linkdata): + linkinfo = {} + for nl_attr in self.ifla_bond_attributes: + try: + linkinfo[nl_attr] = linkdata.get(nl_attr) + except Exception as e: + self.logger.debug('%s: parsing bond IFLA_INFO_DATA (%s): %s' + % (ifname, nl_attr, str(e))) + return linkinfo + + # this dict contains the netlink attribute, cache key, + # and a callable to translate the netlink value into + # whatever value we need to store in the old cache to + # make sure we don't break anything + ifla_bridge_attributes = ( + (Link.IFLA_BR_UNSPEC, Link.IFLA_BR_UNSPEC, None), + (Link.IFLA_BR_FORWARD_DELAY, "fd", lambda x: str(x / 100)), + (Link.IFLA_BR_HELLO_TIME, "hello", lambda x: str(x / 100)), + (Link.IFLA_BR_MAX_AGE, "maxage", lambda x: str(x / 100)), + (Link.IFLA_BR_AGEING_TIME, "ageing", lambda x: str(x / 100)), + (Link.IFLA_BR_STP_STATE, "stp", lambda x: 'yes' if x else 'no'), + (Link.IFLA_BR_PRIORITY, "bridgeprio", str), + (Link.IFLA_BR_VLAN_FILTERING, 'vlan_filtering', str), + (Link.IFLA_BR_VLAN_PROTOCOL, "vlan-protocol", str), + (Link.IFLA_BR_GROUP_FWD_MASK, Link.IFLA_BR_GROUP_FWD_MASK, None), + (Link.IFLA_BR_ROOT_ID, Link.IFLA_BR_ROOT_ID, None), + (Link.IFLA_BR_BRIDGE_ID, Link.IFLA_BR_BRIDGE_ID, None), + (Link.IFLA_BR_ROOT_PORT, Link.IFLA_BR_ROOT_PORT, None), + (Link.IFLA_BR_ROOT_PATH_COST, Link.IFLA_BR_ROOT_PATH_COST, None), + (Link.IFLA_BR_TOPOLOGY_CHANGE, Link.IFLA_BR_TOPOLOGY_CHANGE, None), + (Link.IFLA_BR_TOPOLOGY_CHANGE_DETECTED, Link.IFLA_BR_TOPOLOGY_CHANGE_DETECTED, None), + (Link.IFLA_BR_HELLO_TIMER, Link.IFLA_BR_HELLO_TIMER, None), + (Link.IFLA_BR_TCN_TIMER, Link.IFLA_BR_TCN_TIMER, None), + (Link.IFLA_BR_TOPOLOGY_CHANGE_TIMER, Link.IFLA_BR_TOPOLOGY_CHANGE_TIMER, None), + (Link.IFLA_BR_GC_TIMER, Link.IFLA_BR_GC_TIMER, None), + (Link.IFLA_BR_GROUP_ADDR, Link.IFLA_BR_GROUP_ADDR, None), + (Link.IFLA_BR_FDB_FLUSH, Link.IFLA_BR_FDB_FLUSH, None), + (Link.IFLA_BR_MCAST_ROUTER, "mcrouter", str), + (Link.IFLA_BR_MCAST_SNOOPING, "mcsnoop", str), + (Link.IFLA_BR_MCAST_QUERY_USE_IFADDR, "mcqifaddr", str), + (Link.IFLA_BR_MCAST_QUERIER, "mcquerier", str), + (Link.IFLA_BR_MCAST_HASH_ELASTICITY, "hashel", str), + (Link.IFLA_BR_MCAST_HASH_MAX, "hashmax", str), + (Link.IFLA_BR_MCAST_LAST_MEMBER_CNT, "mclmc", str), + (Link.IFLA_BR_MCAST_STARTUP_QUERY_CNT, "mcsqc", str), + (Link.IFLA_BR_MCAST_LAST_MEMBER_INTVL, "mclmi", lambda x: str(x / 100)), + (Link.IFLA_BR_MCAST_MEMBERSHIP_INTVL, "mcmi", lambda x: str(x / 100)), + (Link.IFLA_BR_MCAST_QUERIER_INTVL, "mcqpi", lambda x: str(x / 100)), + (Link.IFLA_BR_MCAST_QUERY_INTVL, "mcqi", lambda x: str(x / 100)), + (Link.IFLA_BR_MCAST_QUERY_RESPONSE_INTVL, "mcqri", lambda x: str(x / 100)), + (Link.IFLA_BR_MCAST_STARTUP_QUERY_INTVL, "mcsqi", lambda x: str(x / 100)), + (Link.IFLA_BR_NF_CALL_IPTABLES, Link.IFLA_BR_NF_CALL_IPTABLES, None), + (Link.IFLA_BR_NF_CALL_IP6TABLES, Link.IFLA_BR_NF_CALL_IP6TABLES, None), + (Link.IFLA_BR_NF_CALL_ARPTABLES, Link.IFLA_BR_NF_CALL_ARPTABLES, None), + (Link.IFLA_BR_VLAN_DEFAULT_PVID, Link.IFLA_BR_VLAN_DEFAULT_PVID, None), + (Link.IFLA_BR_PAD, Link.IFLA_BR_PAD, None), + (Link.IFLA_BR_VLAN_STATS_ENABLED, "vlan-stats", str), + (Link.IFLA_BR_MCAST_STATS_ENABLED, "mcstats", str), + (Link.IFLA_BR_MCAST_IGMP_VERSION, "igmp-version", str), + (Link.IFLA_BR_MCAST_MLD_VERSION, "mld-version", str) + ) + + def _link_dump_info_data_bridge(self, ifname, linkdata): + linkinfo = {} + for nl_attr, cache_key, func in self.ifla_bridge_attributes: + try: + if func: + linkinfo[cache_key] = func(linkdata.get(nl_attr)) + else: + linkinfo[cache_key] = linkdata.get(nl_attr) + + # we also store the value in pure netlink, + # to make the transition easier in the future + linkinfo[nl_attr] = linkdata.get(nl_attr) + except Exception as e: + self.logger.error('%s: parsing birdge IFLA_INFO_DATA %s: %s' + % (ifname, nl_attr, str(e))) + return linkinfo + + def _link_dump_info_slave_data_bridge(self, ifname, info_slave_data): + return info_slave_data + + def _link_dump_linkinfo(self, link, dump): + linkinfo = link.attributes[Link.IFLA_LINKINFO].get_pretty_value(dict) + + if linkinfo: + info_kind = linkinfo.get(Link.IFLA_INFO_KIND) + info_data = linkinfo.get(Link.IFLA_INFO_DATA) + + info_slave_kind = linkinfo.get(Link.IFLA_INFO_SLAVE_KIND) + info_slave_data = linkinfo.get(Link.IFLA_INFO_SLAVE_DATA) + + dump['kind'] = info_kind + dump['slave_kind'] = info_slave_kind + + if info_data: + link_kind_handler = self.link_kind_handlers.get(info_kind) + if callable(link_kind_handler): + dump['linkinfo'] = link_kind_handler(dump['ifname'], info_data) + + if info_slave_data: + dump['info_slave_data'] = info_slave_data + + def link_dump(self, ifname=None): + if ifname: + self.logger.info('netlink: ip link show dev %s' % ifname) + else: + self.logger.info('netlink: ip link show') + + if ifupdownflags.flags.DRYRUN: return {} + + links = dict() + + try: + links_dump = self._nlmanager_api.link_dump(ifname) + except Exception as e: + raise Exception('netlink: link dump failed: %s' % str(e)) + + for link in links_dump: + try: + dump = dict() + + flags = [] + for flag, string in Link.flag_to_string.items(): + if link.flags & flag: + flags.append(string[4:]) + + dump['flags'] = flags + dump['ifflag'] = 'UP' if 'UP' in flags else 'DOWN' + dump['ifindex'] = str(link.ifindex) + + if link.device_type == Link.ARPHRD_ETHER: + self._link_dump_attr(link, [self.ifla_address], dump) + + self._link_dump_attr(link, self.ifla_attributes, dump) + + if Link.IFLA_LINKINFO in link.attributes: + self._link_dump_linkinfo(link, dump) + + links[dump['ifname']] = dump + except Exception as e: + self.logger.warning('netlink: ip link show: %s' % str(e)) + return links + + def _addr_dump_extract_ifname(self, addr_packet): + addr_ifname_attr = addr_packet.attributes.get(Address.IFA_LABEL) + + if addr_ifname_attr: + return addr_ifname_attr.get_pretty_value(str) + else: + return self.get_iface_name(addr_packet.ifindex) + + @staticmethod + def _addr_filter(addr_ifname, addr): + return addr_ifname == 'lo' and addr in ['127.0.0.1/8', '::1/128', '0.0.0.0'] + + def _addr_dump_entry(self, ifaces, addr_packet, addr_ifname, ifa_attr): + attribute = addr_packet.attributes.get(ifa_attr) + + if attribute: + address = attribute.get_pretty_value(str) + + if hasattr(addr_packet, 'prefixlen'): + address = '%s/%d' % (address, addr_packet.prefixlen) + + if self._addr_filter(addr_ifname, address): + return + + addr_family = NetlinkPacket.af_family_to_string.get(addr_packet.family) + if not addr_family: + return + + ifaces[addr_ifname]['addrs'][address] = { + 'type': addr_family, + 'scope': addr_packet.scope + } + + ifa_address_attributes = [ + Address.IFA_ADDRESS, + Address.IFA_LOCAL, + Address.IFA_BROADCAST, + Address.IFA_ANYCAST, + Address.IFA_MULTICAST + ] + + def addr_dump(self, ifname=None): + if ifname: + self.logger.info('netlink: ip addr show dev %s' % ifname) + else: + self.logger.info('netlink: ip addr show') + + ifaces = dict() + addr_dump = self._nlmanager_api.addr_dump() + + for addr_packet in addr_dump: + addr_ifname = self._addr_dump_extract_ifname(addr_packet) + + if addr_packet.family not in [socket.AF_INET, socket.AF_INET6]: + continue + + if ifname and ifname != addr_ifname: + continue + + if addr_ifname not in ifaces: + ifaces[addr_ifname] = {'addrs': OrderedDict({})} + + for ifa_attr in self.ifa_address_attributes: + self._addr_dump_entry(ifaces, addr_packet, addr_ifname, ifa_attr) + + if ifname: + return {ifname: ifaces.get(ifname, {})} + + return ifaces + + +netlink = Netlink() diff --git a/ifupdown/networkinterfaces.py b/ifupdown2/ifupdown/networkinterfaces.py similarity index 97% rename from ifupdown/networkinterfaces.py rename to ifupdown2/ifupdown/networkinterfaces.py index 22058d7..c79d2b8 100644 --- a/ifupdown/networkinterfaces.py +++ b/ifupdown2/ifupdown/networkinterfaces.py @@ -1,34 +1,35 @@ #!/usr/bin/python # -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. # Author: Roopa Prabhu, roopa@cumulusnetworks.com # # networkInterfaces -- # ifupdown network interfaces file parser # -import collections -import logging -import glob import re -import os import copy -from utils import utils -from iface import * -from template import templateEngine +import glob +import logging +import collections + +try: + from ifupdown2.ifupdown.iface import * + from ifupdown2.ifupdown.utils import utils + from ifupdown2.ifupdown.template import templateEngine +except ImportError: + from ifupdown.iface import * + from ifupdown.utils import utils + from ifupdown.template import templateEngine + whitespaces = '\n\t\r ' class networkInterfaces(): """ debian ifupdown /etc/network/interfaces file parser """ - hotplugs = {} - auto_ifaces = [] - callbacks = {} - auto_all = False - - _addrfams = {'inet' : ['static', 'manual', 'loopback', 'dhcp', 'dhcp6', 'tunnel', 'ppp'], - 'inet6' : ['static', 'manual', 'loopback', 'dhcp', 'dhcp6', 'tunnel', 'ppp']} + _addrfams = {'inet' : ['static', 'manual', 'loopback', 'dhcp', 'dhcp6'], + 'inet6' : ['static', 'manual', 'loopback', 'dhcp', 'dhcp6']} def __init__(self, interfacesfile='/etc/network/interfaces', interfacesfileiobuf=None, interfacesfileformat='native', @@ -50,6 +51,10 @@ class networkInterfaces(): Raises: AttributeError, KeyError """ + self.auto_ifaces = [] + self.callbacks = {} + self.auto_all = False + self.logger = logging.getLogger('ifupdown.' + self.__class__.__name__) self.callbacks = {'iface_found' : None, @@ -210,7 +215,7 @@ class networkInterfaces(): # For attributes that are related and that can have multiple # entries, store them at the same index as their parent attribute. # The example of such attributes is 'address' and its related - # attributes. since the related attributes can be optional, + # attributes. since the related attributes can be optional, # we add null string '' in places where they are optional. # XXX: this introduces awareness of attribute names in # this class which is a violation. @@ -257,6 +262,7 @@ class networkInterfaces(): for line_idx in range(cur_idx + 1, len(lines)): l = lines[line_idx].strip(whitespaces) if self.ignore_line(l) == 1: + ifaceobj.raw_config.append(l) continue attrs = re.split(self._ws_split_regex, l, 1) if self._is_keyword(attrs[0]): @@ -299,7 +305,7 @@ class networkInterfaces(): classes = self.get_allow_classes_for_iface(ifaceobj.name) if classes: [ifaceobj.set_class(c) for c in classes] - + return lines_consumed # Return next index def _create_ifaceobj_clone(self, ifaceobj, newifaceobjname, @@ -494,7 +500,7 @@ class networkInterfaces(): errors += 1 self.callbacks.get('iface_found')(ifaceobj) self.errors += errors - + def load(self): """ This member function loads the networkinterfaces file. diff --git a/ifupdown/policymanager.py b/ifupdown2/ifupdown/policymanager.py similarity index 82% rename from ifupdown/policymanager.py rename to ifupdown2/ifupdown/policymanager.py index c624c18..d6de8c2 100644 --- a/ifupdown/policymanager.py +++ b/ifupdown2/ifupdown/policymanager.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -# Copyright 2015 Cumulus Networks, Inc. All rights reserved. +# Copyright 2015-2017 Cumulus Networks, Inc. All rights reserved. # # ''' @@ -12,7 +12,7 @@ Initialize: This module defines a list of config file location based on module. There are defined in the __init__(): All the addon modules need to do is import the policymanager module. - import ifupdown.policymanager as policymanager + import ifupdown2.ifupdown.policymanager as policymanager Provides: an API to retrieve link attributes based on addon module name, @@ -29,8 +29,9 @@ Provides: an API to retrieve link attributes based on addon module name, ''' import json -import logging import glob +import logging + class policymanager(): def __init__(self): @@ -54,9 +55,9 @@ class policymanager(): self.logger.debug('reading %s system policy defaults config' \ % filename) except Exception, e: - self.logger.info('could not read %s system policy defaults config' \ + self.logger.warning('could not read %s system policy defaults config' \ % filename) - self.logger.info(' exception is %s' % str(e)) + self.logger.warning(' exception is %s' % str(e)) for module in system_array.keys(): if self.system_policy_array.has_key(module): @@ -74,9 +75,9 @@ class policymanager(): self.logger.debug('reading %s policy user defaults config' \ % filename) except Exception, e: - self.logger.debug('could not read %s user policy defaults config' \ + self.logger.warning('could not read %s user policy defaults config' \ % filename) - self.logger.debug(' exception is %s' % str(e)) + self.logger.warning(' exception is %s' % str(e)) # customer added module attributes for module in user_array.keys(): if self.system_policy_array.has_key(module): @@ -136,7 +137,7 @@ class policymanager(): get_attr_default: Addon modules must use one of two types of access methods to the default configs. In this method, we expect the default to be in - [module]['defaults'][attr] + [module]['defaults'][attr] We first check the user_policy_array and return that value. But if the user did not specify an override, we use the system_policy_array. @@ -166,7 +167,7 @@ class policymanager(): get_module_globals: Addon modules must use one of two types of access methods to the default configs. In this method, we expect the default to be in - [module]['module_globals'][attr] + [module]['module_globals'][attr] We first check the user_policy_array and return that value. But if the user did not specify an override, we use the system_policy_array. @@ -192,23 +193,34 @@ class policymanager(): return val - def get_module_default(self,module_name=None): - ''' - get_module_default: Addon modules can also access the entire config - This method returns indexed by "system" and "user": these are the - system-wide and user-defined policy arrays for a specific module. - ''' + def get_module_defaults(self, module_name): + """ + get_module_defaults: returns a merged dictionary of default values + specified in policy files. Users provided values override system + values. + """ + if not module_name: - return None - if self.system_policy_array.get(module_name) and \ - self.user_policy_array.get(module_name): - mod_array = {"system":self.system_policy_array[module_name], - "user":self.user_policy_array[module_name]} - else: - # the module must not have these defined, return None - mod_array = None + raise NotImplementedError('get_module_defaults: module name can\'t be None') + + defaults = dict() + defaults.update(self.system_policy_array.get(module_name, {}).get('defaults', {})) + defaults.update(self.user_policy_array.get(module_name, {}).get('defaults', {})) + return defaults + + def get_iface_defaults(self, module_name): + defaults = dict() - return mod_array + if not module_name: + self.logger.info('get_iface_defaults: module name can\'t be None') + else: + defaults.update(self.system_policy_array.get(module_name, {}).get('iface_defaults', {})) + defaults.update(self.user_policy_array.get(module_name, {}).get('iface_defaults', {})) + return defaults policymanager_api = policymanager() + +def reset(): + global policymanager_api + policymanager_api = policymanager() diff --git a/ifupdown/scheduler.py b/ifupdown2/ifupdown/scheduler.py similarity index 96% rename from ifupdown/scheduler.py rename to ifupdown2/ifupdown/scheduler.py index cb81287..6cbe785 100644 --- a/ifupdown/scheduler.py +++ b/ifupdown2/ifupdown/scheduler.py @@ -1,28 +1,36 @@ #!/usr/bin/python # -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. # Author: Roopa Prabhu, roopa@cumulusnetworks.com # # ifaceScheduler -- # interface scheduler # -from statemanager import * -import ifupdown.ifupdownflags as ifupdownflags -from iface import * -from graph import * -from collections import deque -from collections import OrderedDict -import logging -import traceback import sys -from graph import * -from collections import deque -from threading import * -from ifupdownbase import * -from ifupdown.utils import utils + from sets import Set +try: + from ifupdown2.ifupdown.graph import * + from ifupdown2.ifupdown.ifupdownbase import * + + from ifupdown2.ifupdown.iface import * + from ifupdown2.ifupdown.utils import utils + from ifupdown2.ifupdown.statemanager import * + + import ifupdown2.ifupdown.ifupdownflags as ifupdownflags +except ImportError: + from ifupdown.graph import * + from ifupdown.ifupdownbase import * + + from ifupdown.iface import * + from ifupdown.utils import utils + from ifupdown.statemanager import * + + import ifupdown.ifupdownflags as ifupdownflags + + class ifaceSchedulerFlags(): """ Enumerates scheduler flags """ @@ -41,6 +49,11 @@ class ifaceScheduler(): _SCHED_STATUS = True + @classmethod + def reset(cls): + cls._STATE_CHECK = True + cls._SCHED_STATUS = True + @classmethod def get_sched_status(cls): return cls._SCHED_STATUS @@ -74,7 +87,7 @@ class ifaceScheduler(): if hasattr(m, 'run'): msg = ('%s: %s : running module %s' %(ifacename, op, mname)) if op == 'query-checkcurr': - # Dont check curr if the interface object was + # Dont check curr if the interface object was # auto generated if (ifaceobj.priv_flags and ifaceobj.priv_flags.NOCONFIG): @@ -108,7 +121,7 @@ class ifaceScheduler(): status) if ifupdownobj.config.get('addon_scripts_support', '0') == '1': - # execute /etc/network/ scripts + # execute /etc/network/ scripts os.environ['IFACE'] = ifaceobj.name if ifaceobj.name else '' os.environ['LOGICAL'] = ifaceobj.name if ifaceobj.name else '' os.environ['METHOD'] = ifaceobj.addr_method if ifaceobj.addr_method else '' @@ -142,7 +155,7 @@ class ifaceScheduler(): for ifaceobj in ifaceobjs: ifaceobj.status = ifaceStatus.SUCCESS posthookfunc(ifupdownobj, ifaceobj, 'down') - return + return for op in ops: # first run ifupdownobj handlers. This is good enough # for the first object in the list @@ -397,7 +410,7 @@ class ifaceScheduler(): @classmethod def run_upperifaces(cls, ifupdownobj, ifacenames, ops, continueonfailure=True): - """ Run through valid upperifaces """ + """ Run through valid upperifaces """ upperifaces = [] cls._get_valid_upperifaces(ifupdownobj, ifacenames, upperifaces) @@ -468,7 +481,7 @@ class ifaceScheduler(): argument. Runs topological sort on interface dependency graph. Args: - **ifupdownobj** (object): ifupdownMain object + **ifupdownobj** (object): ifupdownMain object **ifacenames** (list): list of interface names @@ -564,7 +577,7 @@ class ifaceScheduler(): if (not skipupperifaces and ifupdownobj.config.get('skip_upperifaces', '0') == '0' and ((not ifupdownflags.flags.ALL and followdependents) or - followupperifaces) and + followupperifaces) and 'up' in ops[0]): # If user had given a set of interfaces to bring up # try and execute 'up' on the upperifaces diff --git a/ifupdown/statemanager.py b/ifupdown2/ifupdown/statemanager.py similarity index 93% rename from ifupdown/statemanager.py rename to ifupdown2/ifupdown/statemanager.py index 9eee24f..0615456 100644 --- a/ifupdown/statemanager.py +++ b/ifupdown2/ifupdown/statemanager.py @@ -1,17 +1,25 @@ #!/usr/bin/python # -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. # Author: Roopa Prabhu, roopa@cumulusnetworks.com # # stateManager -- # interface state manager # + +import os import cPickle -from collections import OrderedDict import logging -import exceptions -import os -from iface import * + +try: + from ifupdown2.ifupdown.iface import * + + import ifupdown2.ifupdown.exceptions as exceptions +except ImportError: + from ifupdown.iface import * + + import ifupdown.exceptions as exceptions + class pickling(): """ class with helper methods for pickling/unpickling iface objects """ @@ -179,10 +187,14 @@ class stateManager(): ifaceobj = self.ifaces.get(i) if ifaceobj is None: raise exceptions.ifaceNotFoundError('ifname %s' - %i + ' not found') + % i + ' not found') ifaceobj.dump(self.logger) else: for ifacename, ifaceobjs in self.ifaceobjdict.items(): [i.dump(self.logger) for i in ifaceobjs] statemanager_api = stateManager() + +def reset(): + global statemanager_api + statemanager_api = stateManager() diff --git a/ifupdown/template.py b/ifupdown2/ifupdown/template.py similarity index 92% rename from ifupdown/template.py rename to ifupdown2/ifupdown/template.py index 4397834..6560180 100644 --- a/ifupdown/template.py +++ b/ifupdown2/ifupdown/template.py @@ -1,15 +1,17 @@ #!/usr/bin/python # -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. # Author: Roopa Prabhu, roopa@cumulusnetworks.com # # template -- # helper class to render templates # -import logging -import traceback -from utils import * +try: + from ifupdown2.ifupdown.utils import * +except ImportError: + from ifupdown.utils import * + class templateEngine(): """ provides template rendering methods """ diff --git a/ifupdown/utils.py b/ifupdown2/ifupdown/utils.py similarity index 73% rename from ifupdown/utils.py rename to ifupdown2/ifupdown/utils.py index 82eafa1..f7a89a7 100644 --- a/ifupdown/utils.py +++ b/ifupdown2/ifupdown/utils.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. # Author: Roopa Prabhu, roopa@cumulusnetworks.com # # utils -- @@ -14,12 +14,19 @@ import fcntl import signal import logging import subprocess -import ifupdownflags from functools import partial from ipaddr import IPNetwork, IPAddress -from ifupdown.iface import * +try: + from ifupdown2.ifupdown.iface import * + + import ifupdown2.ifupdown.ifupdownflags as ifupdownflags +except ImportError: + from ifupdown.iface import * + + import ifupdown.ifupdownflags as ifupdownflags + def signal_handler_f(ps, sig, frame): if ps: @@ -35,9 +42,11 @@ class utils(): "on": True, "yes": True, "1": True, + "fast": True, "off": False, "no": False, "0": False, + "slow": False } _binary_bool = { @@ -55,6 +64,81 @@ class utils(): 'no': 'off' } + _onoff_onezero = { + '1' : 'on', + '0' : 'off' + } + + _yesno_onezero = { + '1' : 'yes', + '0' : 'no' + } + + """ + Set debian path as default path for all the commands. + If command not present in debian path, search for the + commands in the other system directories. + This search is carried out to handle different locations + on different distros. + If the command is not found in any of the system + directories, command execution will fail because we have + set default path same as debian path. + """ + bridge_cmd = '/sbin/bridge' + ip_cmd = '/bin/ip' + brctl_cmd = '/sbin/brctl' + pidof_cmd = '/bin/pidof' + service_cmd = '/usr/sbin/service' + sysctl_cmd = '/sbin/sysctl' + modprobe_cmd = '/sbin/modprobe' + pstree_cmd = '/usr/bin/pstree' + ss_cmd = '/bin/ss' + vrrpd_cmd = '/usr/sbin/vrrpd' + ifplugd_cmd = '/usr/sbin/ifplugd' + mstpctl_cmd = '/sbin/mstpctl' + ethtool_cmd = '/sbin/ethtool' + systemctl_cmd = '/bin/systemctl' + dpkg_cmd = '/usr/bin/dpkg' + + for cmd in ['bridge', + 'ip', + 'brctl', + 'pidof', + 'service', + 'sysctl', + 'modprobe', + 'pstree', + 'ss', + 'vrrpd', + 'ifplugd', + 'mstpctl', + 'ethtool', + 'systemctl', + 'dpkg' + ]: + if os.path.exists(vars()[cmd + '_cmd']): + continue + for path in ['/bin/', + '/sbin/', + '/usr/bin/', + '/usr/sbin/',]: + if os.path.exists(path + cmd): + vars()[cmd + '_cmd'] = path + cmd + else: + logger.debug('warning: path %s not found: %s won\'t be usable' % (path + cmd, cmd)) + + @staticmethod + def get_onff_from_onezero(value): + if value in utils._onoff_onezero: + return utils._onoff_onezero[value] + return value + + @staticmethod + def get_yesno_from_onezero(value): + if value in utils._yesno_onezero: + return utils._yesno_onezero[value] + return value + @staticmethod def get_onoff_bool(value): if value in utils._onoff_bool: @@ -63,9 +147,7 @@ class utils(): @staticmethod def get_boolean_from_string(value): - if value in utils._string_values: - return utils._string_values[value] - return False + return utils._string_values.get(value, False) @staticmethod def get_yesno_boolean(bool): @@ -93,6 +175,22 @@ class utils(): if attr in attrsdict: attrsdict[attr] = utils.boolean_support_binary(attrsdict[attr]) + @staticmethod + def get_int_from_boolean_and_string(value): + try: + return int(value) + except: + return int(utils.get_boolean_from_string(value)) + + @staticmethod + def strip_hwaddress(hwaddress): + if hwaddress and hwaddress.startswith("ether"): + hwaddress = hwaddress[5:].strip() + return hwaddress.lower() if hwaddress else hwaddress + # we need to "normalize" the user provided MAC so it can match with + # what we have in the cache (data retrieved via a netlink dump by + # nlmanager). nlmanager return all macs in lower-case + @classmethod def importName(cls, modulename, name): """ Import a named object """ @@ -212,6 +310,25 @@ class utils(): cls.logger.warning('%s: %s' % (ifacename, e)) return ipaddrs + @classmethod + def get_ip_objs(cls, module_name, ifname, addrs_list): + addrs_obj_list = [] + for a in addrs_list or []: + try: + addrs_obj_list.append(IPNetwork(a) if '/' in a else IPAddress(a)) + except Exception as e: + cls.logger.warning('%s: %s: %s' % (module_name, ifname, str(e))) + return addrs_obj_list + + @classmethod + def get_ip_obj(cls, module_name, ifname, addr): + if addr: + try: + return IPNetwork(addr) if '/' in addr else IPAddress(addr) + except Exception as e: + cls.logger.warning('%s: %s: %s' % (module_name, ifname, str(e))) + return None + @classmethod def is_addr_ip_allowed_on(cls, ifaceobj, syntax_check=False): msg = ('%s: ignoring ip address. Assigning an IP ' diff --git a/ifupdown2/ifupdownaddons/LinkUtils.py b/ifupdown2/ifupdownaddons/LinkUtils.py new file mode 100644 index 0000000..ab93f77 --- /dev/null +++ b/ifupdown2/ifupdownaddons/LinkUtils.py @@ -0,0 +1,2542 @@ +#!/usr/bin/python +# +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# Julien Fortin, julien@cumulusnetworks.com +# + +import os +import re +import glob +import shlex +import signal +import subprocess + +from ipaddr import IPNetwork, IPv6Network + +try: + import ifupdown2.ifupdown.statemanager as statemanager + import ifupdown2.ifupdown.ifupdownflags as ifupdownflags + + from ifupdown2.nlmanager.nlmanager import Link, Route + + from ifupdown2.ifupdown.iface import * + from ifupdown2.ifupdown.utils import utils + from ifupdown2.ifupdown.netlink import netlink + + from ifupdown2.ifupdownaddons.utilsbase import utilsBase + from ifupdown2.ifupdownaddons.cache import linkCache, MSTPAttrsCache +except ImportError: + import ifupdown.ifupdownflags as ifupdownflags + import ifupdown.statemanager as statemanager + + from nlmanager.nlmanager import Link, Route + + from ifupdown.iface import * + from ifupdown.utils import utils + from ifupdown.netlink import netlink + + from ifupdownaddons.utilsbase import utilsBase + from ifupdownaddons.cache import linkCache, MSTPAttrsCache + + +class LinkUtils(utilsBase): + """ + This class contains helper methods to cache and manipulate interfaces through + non-netlink APIs (sysfs, iproute2, brctl...) + """ + _CACHE_FILL_DONE = False + VXLAN_UDP_PORT = 4789 + + ipbatchbuf = '' + ipbatch = False + ipbatch_pause = False + + bridge_utils_is_installed = os.path.exists(utils.brctl_cmd) + bridge_utils_missing_warning = True + + def __init__(self, *args, **kargs): + utilsBase.__init__(self, *args, **kargs) + + self.supported_command = { + '%s -c -json vlan show' % utils.bridge_cmd: True, + 'showmcqv4src': True + } + self.bridge_vlan_cache = {} + self.bridge_vlan_cache_fill_done = False + + if not ifupdownflags.flags.PERFMODE and not LinkUtils._CACHE_FILL_DONE: + self._fill_cache() + + @classmethod + def reset(cls): + LinkUtils._CACHE_FILL_DONE = False + LinkUtils.ipbatchbuf = '' + LinkUtils.ipbatch = False + LinkUtils.ipbatch_pause = False + + def _fill_cache(self): + if not LinkUtils._CACHE_FILL_DONE: + self._link_fill() + self._addr_fill() + LinkUtils._CACHE_FILL_DONE = True + return True + return False + + @staticmethod + def _get_vland_id(citems, i, warn): + try: + sub = citems[i:] + index = sub.index('id') + int(sub[index + 1]) + return sub[index + 1] + except: + if warn: + raise Exception('invalid use of \'vlan\' keyword') + return None + + def _link_fill(self, ifacename=None, refresh=False): + """ fills cache with link information + + if ifacename argument given, fill cache for ifacename, else + fill cache for all interfaces in the system + """ + + if LinkUtils._CACHE_FILL_DONE and not refresh: + return + try: + # if ifacename already present, return + if (ifacename and not refresh and + linkCache.get_attr([ifacename, 'ifflag'])): + return + except: + pass + + if True: + try: + [linkCache.update_attrdict([ifname], linkattrs) + for ifname, linkattrs in netlink.link_dump(ifacename).items()] + except Exception as e: + self.logger.info('%s' % str(e)) + # this netlink call replaces the call to _link_fill_iproute2_cmd() + # We shouldn't have netlink calls in the iproute2 module, this will + # be removed in the future. We plan to release, a flexible backend + # (netlink+iproute2) by default we will use netlink backend but with + # a CLI arg we can switch to iproute2 backend. + # Until we decide to create this "backend" switch capability, + # we have to put the netlink call inside the iproute2 module. + else: + self._link_fill_iproute2_cmd(ifacename, refresh) + + self._fill_bond_info(ifacename) + self._fill_bridge_info(ifacename) + + def _fill_bridge_info(self, ifacename): + + if True: # netlink + brports = {} + + if ifacename: + cache_dict = {ifacename: linkCache.links.get(ifacename, {})} + else: + cache_dict = linkCache.links + + for ifname, obj in cache_dict.items(): + slave_kind = obj.get('slave_kind') + if not slave_kind and slave_kind != 'bridge': + continue + + info_slave_data = obj.get('info_slave_data') + if not info_slave_data: + continue + + ifla_master = obj.get('master') + if not ifla_master: + raise Exception('No master associated with bridge port %s' % ifname) + + for nl_attr in [ + Link.IFLA_BRPORT_STATE, + Link.IFLA_BRPORT_COST, + Link.IFLA_BRPORT_PRIORITY, + ]: + if nl_attr not in info_slave_data and LinkUtils.bridge_utils_is_installed: + self._fill_bridge_info_brctl() + return + + brport_attrs = { + 'pathcost': str(info_slave_data.get(Link.IFLA_BRPORT_COST, 0)), + 'fdelay': format(float(info_slave_data.get(Link.IFLA_BRPORT_FORWARD_DELAY_TIMER, 0) / 100), '.2f'), + 'portmcrouter': str(info_slave_data.get(Link.IFLA_BRPORT_MULTICAST_ROUTER, 0)), + 'portmcfl': str(info_slave_data.get(Link.IFLA_BRPORT_FAST_LEAVE, 0)), + 'portprio': str(info_slave_data.get(Link.IFLA_BRPORT_PRIORITY, 0)), + 'unicast-flood': str(info_slave_data.get(Link.IFLA_BRPORT_UNICAST_FLOOD, 0)), + 'multicast-flood': str(info_slave_data.get(Link.IFLA_BRPORT_MCAST_FLOOD, 0)), + 'learning': str(info_slave_data.get(Link.IFLA_BRPORT_LEARNING, 0)), + 'arp-nd-suppress': str(info_slave_data.get(Link.IFLA_BRPORT_ARP_SUPPRESS, 0)) + } + + if ifla_master in brports: + brports[ifla_master][ifname] = brport_attrs + else: + brports[ifla_master] = {ifname: brport_attrs} + + linkCache.update_attrdict([ifla_master, 'linkinfo', 'ports'], brports[ifla_master]) + else: + if LinkUtils.bridge_utils_is_installed: + self._fill_bridge_info_brctl() + + def _fill_bridge_info_brctl(self): + brctlout = utils.exec_command('%s show' % utils.brctl_cmd) + if not brctlout: + return + + for bline in brctlout.splitlines()[1:]: + bitems = bline.split() + if len(bitems) < 2: + continue + try: + linkCache.update_attrdict([bitems[0], 'linkinfo'], + {'stp': bitems[2]}) + except KeyError: + linkCache.update_attrdict([bitems[0]], + {'linkinfo': {'stp': bitems[2]}}) + self._bridge_attrs_fill(bitems[0]) + + def _bridge_attrs_fill(self, bridgename): + battrs = {} + bports = {} + + brout = utils.exec_command('%s showstp %s' % (utils.brctl_cmd, bridgename)) + chunks = re.split(r'\n\n', brout, maxsplit=0, flags=re.MULTILINE) + + try: + # Get all bridge attributes + broutlines = chunks[0].splitlines() + # battrs['pathcost'] = broutlines[3].split('path cost')[1].strip() + + try: + battrs['maxage'] = broutlines[4].split('bridge max age')[ + 1].strip().replace('.00', '') + except: + pass + + try: + battrs['hello'] = broutlines[5].split('bridge hello time')[ + 1].strip().replace('.00', '') + except: + pass + + try: + battrs['fd'] = broutlines[6].split('bridge forward delay')[ + 1].strip().replace('.00', '') + except: + pass + + try: + battrs['ageing'] = broutlines[7].split('ageing time')[ + 1].strip().replace('.00', '') + except: + pass + + try: + battrs['mcrouter'] = broutlines[12].split('mc router')[ + 1].strip().split('\t\t\t')[0] + except: + pass + + try: + battrs['bridgeprio'] = self.read_file_oneline( + '/sys/class/net/%s/bridge/priority' % bridgename) + except: + pass + + try: + battrs['vlan-protocol'] = VlanProtocols.ID_TO_ETHERTYPES[ + self.read_file_oneline( + '/sys/class/net/%s/bridge/vlan_protocol' % bridgename)] + except: + pass + + try: + battrs.update(self._bridge_get_mcattrs_from_sysfs(bridgename)) + except: + pass + + # XXX: comment this out until mc attributes become available + # with brctl again + # battrs['hashel'] = broutlines[10].split('hash elasticity')[1].split()[0].strip() + # battrs['hashmax'] = broutlines[10].split('hash max')[1].strip() + # battrs['mclmc'] = broutlines[11].split('mc last member count')[1].split()[0].strip() + # battrs['mciqc'] = broutlines[11].split('mc init query count')[1].strip() + # battrs['mcrouter'] = broutlines[12].split('mc router')[1].split()[0].strip() + ##battrs['mcsnoop'] = broutlines[12].split('mc snooping')[1].strip() + # battrs['mclmt'] = broutlines[13].split('mc last member timer')[1].split()[0].strip() + except Exception, e: + self.logger.warn('%s: error while processing bridge attributes: %s' % (bridgename, str(e))) + pass + + linkCache.update_attrdict([bridgename, 'linkinfo'], battrs) + for cidx in range(1, len(chunks)): + bpout = chunks[cidx].lstrip('\n') + if not bpout or bpout[0] == ' ': + continue + bplines = bpout.splitlines() + pname = bplines[0].split()[0] + bportattrs = {} + try: + bportattrs['pathcost'] = bplines[2].split( + 'path cost')[1].strip() + bportattrs['fdelay'] = bplines[4].split( + 'forward delay timer')[1].strip() + bportattrs['portmcrouter'] = self.read_file_oneline( + '/sys/class/net/%s/brport/multicast_router' % pname) + bportattrs['portmcfl'] = self.read_file_oneline( + '/sys/class/net/%s/brport/multicast_fast_leave' % pname) + bportattrs['portprio'] = self.read_file_oneline( + '/sys/class/net/%s/brport/priority' % pname) + bportattrs['unicast-flood'] = self.read_file_oneline( + '/sys/class/net/%s/brport/unicast_flood' % pname) + bportattrs['multicast-flood'] = self.read_file_oneline( + '/sys/class/net/%s/brport/multicast_flood' % pname) + bportattrs['learning'] = self.read_file_oneline( + '/sys/class/net/%s/brport/learning' % pname) + bportattrs['arp-nd-suppress'] = self.read_file_oneline( + '/sys/class/net/%s/brport/neigh_suppress' % pname) + # bportattrs['mcrouters'] = bplines[6].split('mc router')[1].split()[0].strip() + # bportattrs['mc fast leave'] = bplines[6].split('mc fast leave')[1].strip() + except Exception, e: + self.logger.warn('%s: error while processing bridge attributes: %s' % (bridgename, str(e))) + bports[pname] = bportattrs + linkCache.update_attrdict([bridgename, 'linkinfo', 'ports'], bports) + + _bridge_sysfs_mcattrs = { + 'mclmc': 'multicast_last_member_count', + 'mcrouter': 'multicast_router', + 'mcsnoop': 'multicast_snooping', + 'mcsqc': 'multicast_startup_query_count', + 'mcqifaddr': 'multicast_query_use_ifaddr', + 'mcquerier': 'multicast_querier', + 'hashel': 'hash_elasticity', + 'hashmax': 'hash_max', + 'mclmi': 'multicast_last_member_interval', + 'mcmi': 'multicast_membership_interval', + 'mcqpi': 'multicast_querier_interval', + 'mcqi': 'multicast_query_interval', + 'mcqri': 'multicast_query_response_interval', + 'mcsqi': 'multicast_startup_query_interval', + 'igmp-version': 'multicast_igmp_version', + 'mld-version': 'multicast_mld_version', + 'vlan-stats': 'vlan_stats_enabled', + 'mcstats': 'multicast_stats_enabled', + } + + def _bridge_get_mcattrs_from_sysfs(self, bridgename): + mcattrsdivby100 = ['mclmi', 'mcmi', 'mcqpi', 'mcqi', 'mcqri', 'mcsqi'] + mcattrs = {} + + for m, s in self._bridge_sysfs_mcattrs.items(): + n = self.read_file_oneline('/sys/class/net/%s/bridge/%s' % (bridgename, s)) + if m in mcattrsdivby100: + try: + v = int(n) / 100 + mcattrs[m] = str(v) + except Exception, e: + self.logger.warn('error getting mc attr %s (%s)' % (m, str(e))) + pass + else: + mcattrs[m] = n + return mcattrs + + def _fill_bond_info(self, ifacename): + bonding_masters = self.read_file_oneline('/sys/class/net/bonding_masters') + if not bonding_masters: + return + + bond_masters_list = bonding_masters.split() + + if ifacename: + if ifacename in bond_masters_list: + bond_masters_list = [ifacename] + else: + # we want to refresh this interface only if it's a bond master + return + + for bondname in bond_masters_list: + try: + if bondname not in linkCache.links: + linkCache.set_attr([bondname], {'linkinfo': {}}) + linkCache.set_attr([bondname, 'linkinfo', 'slaves'], + self.read_file_oneline('/sys/class/net/%s/bonding/slaves' + % bondname).split()) + try: + # if some attribute are missing we try to get the bond attributes via sysfs + bond_linkinfo = linkCache.links[bondname]['linkinfo'] + for attr in [Link.IFLA_BOND_MODE, Link.IFLA_BOND_XMIT_HASH_POLICY, Link.IFLA_BOND_MIN_LINKS]: + if attr not in bond_linkinfo: + self._fill_bond_info_sysfs(bondname) + # after we fill in the cache we can continue to the next bond + break + except: + self._fill_bond_info_sysfs(bondname) + + except Exception as e: + self.logger.debug('LinkUtils: bond cache error: %s' % str(e)) + + def _fill_bond_info_sysfs(self, bondname): + try: + linkCache.set_attr([bondname, 'linkinfo', Link.IFLA_BOND_MIN_LINKS], + self.read_file_oneline( + '/sys/class/net/%s/bonding/min_links' + % bondname)) + except Exception as e: + self.logger.debug(str(e)) + + try: + linkCache.set_attr([bondname, 'linkinfo', Link.IFLA_BOND_MODE], + self.read_file_oneline('/sys/class/net/%s/bonding/mode' + % bondname).split()[0]) + except Exception as e: + self.logger.debug(str(e)) + try: + linkCache.set_attr([bondname, 'linkinfo', Link.IFLA_BOND_XMIT_HASH_POLICY], + self.read_file_oneline( + '/sys/class/net/%s/bonding/xmit_hash_policy' + % bondname).split()[0]) + except Exception as e: + self.logger.debug(str(e)) + try: + linkCache.set_attr([bondname, 'linkinfo', Link.IFLA_BOND_AD_LACP_RATE], + self.read_file_oneline('/sys/class/net/%s/bonding/lacp_rate' + % bondname).split()[1]) + except Exception as e: + self.logger.debug(str(e)) + try: + linkCache.set_attr([bondname, 'linkinfo', Link.IFLA_BOND_AD_ACTOR_SYS_PRIO], + self.read_file_oneline('/sys/class/net/%s/bonding/ad_actor_sys_prio' + % bondname)) + except Exception as e: + self.logger.debug(str(e)) + try: + linkCache.set_attr([bondname, 'linkinfo', Link.IFLA_BOND_AD_ACTOR_SYSTEM], + self.read_file_oneline('/sys/class/net/%s/bonding/ad_actor_system' + % bondname)) + except Exception as e: + self.logger.debug(str(e)) + try: + linkCache.set_attr([bondname, 'linkinfo', Link.IFLA_BOND_AD_LACP_BYPASS], + self.read_file_oneline('/sys/class/net/%s/bonding/lacp_bypass' + % bondname).split()[1]) + except Exception as e: + self.logger.debug(str(e)) + try: + linkCache.set_attr([bondname, 'linkinfo', Link.IFLA_BOND_UPDELAY], + self.read_file_oneline('/sys/class/net/%s/bonding/updelay' + % bondname)) + except Exception as e: + self.logger.debug(str(e)) + try: + linkCache.set_attr([bondname, 'linkinfo', Link.IFLA_BOND_DOWNDELAY], + self.read_file_oneline('/sys/class/net/%s/bonding/downdelay' + % bondname)) + except Exception as e: + self.logger.debug(str(e)) + + try: + linkCache.set_attr([bondname, 'linkinfo', Link.IFLA_BOND_USE_CARRIER], + self.read_file_oneline('/sys/class/net/%s/bonding/use_carrier' % bondname)) + except Exception as e: + self.logger.debug(str(e)) + + try: + linkCache.set_attr([bondname, 'linkinfo', Link.IFLA_BOND_MIIMON], + self.read_file_oneline('/sys/class/net/%s/bonding/miimon' % bondname)) + except Exception as e: + self.logger.debug(str(e)) + + try: + linkCache.set_attr([bondname, 'linkinfo', Link.IFLA_BOND_NUM_PEER_NOTIF], + self.read_file_oneline('/sys/class/net/%s/bonding/num_unsol_na' % bondname)) + except Exception as e: + self.logger.debug(str(e)) + + try: + linkCache.set_attr([bondname, 'linkinfo', Link.IFLA_BOND_NUM_PEER_NOTIF], + self.read_file_oneline('/sys/class/net/%s/bonding/num_grat_arp' % bondname)) + except Exception as e: + self.logger.debug(str(e)) + + + def _link_fill_iproute2_cmd(self, ifacename=None, refresh=False): + warn = True + linkout = {} + if LinkUtils._CACHE_FILL_DONE and not refresh: + return + try: + # if ifacename already present, return + if (ifacename and not refresh and + linkCache.get_attr([ifacename, 'ifflag'])): + return + except: + pass + cmdout = self.link_show(ifacename=ifacename) + if not cmdout: + return + for c in cmdout.splitlines(): + citems = c.split() + ifnamenlink = citems[1].split('@') + if len(ifnamenlink) > 1: + ifname = ifnamenlink[0] + iflink = ifnamenlink[1].strip(':') + else: + ifname = ifnamenlink[0].strip(':') + iflink = None + linkattrs = dict() + linkattrs['link'] = iflink + linkattrs['ifindex'] = citems[0].strip(':') + flags = citems[2].strip('<>').split(',') + linkattrs['flags'] = flags + linkattrs['ifflag'] = 'UP' if 'UP' in flags else 'DOWN' + for i in range(0, len(citems)): + try: + if citems[i] == 'mtu': + linkattrs['mtu'] = citems[i + 1] + elif citems[i] == 'state': + linkattrs['state'] = citems[i + 1] + elif citems[i] == 'link/ether': + linkattrs['hwaddress'] = citems[i + 1] + elif citems[i] == 'vlan': + vlanid = self._get_vland_id(citems, i, warn) + if vlanid: + linkattrs['linkinfo'] = {'vlanid': vlanid} + linkattrs['kind'] = 'vlan' + elif citems[i] == 'dummy': + linkattrs['kind'] = 'dummy' + elif citems[i] == 'vxlan' and citems[i + 1] == 'id': + linkattrs['kind'] = 'vxlan' + vattrs = {'vxlanid': citems[i + 2], + 'svcnode': None, + 'remote': [], + 'ageing': citems[i + 2], + 'learning': 'on'} + for j in range(i + 2, len(citems)): + if citems[j] == 'local': + vattrs['local'] = citems[j + 1] + elif citems[j] == 'remote': + vattrs['svcnode'] = citems[j + 1] + elif citems[j] == 'ageing': + vattrs['ageing'] = citems[j + 1] + elif citems[j] == 'nolearning': + vattrs['learning'] = 'off' + linkattrs['linkinfo'] = vattrs + break + elif citems[i] == 'vrf' and citems[i + 1] == 'table': + vattrs = {'table': citems[i + 2]} + linkattrs['linkinfo'] = vattrs + linkattrs['kind'] = 'vrf' + linkCache.vrfs[ifname] = vattrs + break + elif citems[i] == 'veth': + linkattrs['kind'] = 'veth' + elif citems[i] == 'vrf_slave': + linkattrs['slave_kind'] = 'vrf_slave' + break + elif citems[i] == 'macvlan' and citems[i + 1] == 'mode': + linkattrs['kind'] = 'macvlan' + except Exception as e: + if warn: + self.logger.debug('%s: parsing error: id, mtu, state, ' + 'link/ether, vlan, dummy, vxlan, local, ' + 'remote, ageing, nolearning, vrf, table, ' + 'vrf_slave are reserved keywords: %s' % + (ifname, str(e))) + warn = False + # linkattrs['alias'] = self.read_file_oneline( + # '/sys/class/net/%s/ifalias' %ifname) + linkout[ifname] = linkattrs + [linkCache.update_attrdict([ifname], linkattrs) + for ifname, linkattrs in linkout.items()] + + @staticmethod + def _addr_filter(ifname, addr, scope=None): + default_addrs = ['127.0.0.1/8', '::1/128', '0.0.0.0'] + if ifname == 'lo' and addr in default_addrs: + return True + if scope and scope == 'link': + return True + return False + + def _addr_fill(self, ifacename=None, refresh=False): + """ fills cache with address information + + if ifacename argument given, fill cache for ifacename, else + fill cache for all interfaces in the system + """ + if LinkUtils._CACHE_FILL_DONE and not refresh: + return + try: + # Check if ifacename is already full, in which case, return + if ifacename and not refresh: + linkCache.get_attr([ifacename, 'addrs']) + return + except: + pass + + if True: + try: + [linkCache.update_attrdict([ifname], linkattrs) + for ifname, linkattrs in netlink.addr_dump(ifname=ifacename).items()] + except Exception as e: + self.logger.info(str(e)) + + # this netlink call replaces the call to _addr_fill_iproute2_cmd() + # We shouldn't have netlink calls in the iproute2 module, this will + # be removed in the future. We plan to release, a flexible backend + # (netlink+iproute2) by default we will use netlink backend but with + # a CLI arg we can switch to iproute2 backend. + # Until we decide to create this "backend" switch capability, + # we have to put the netlink call inside the iproute2 module. + + else: + self._addr_fill_iproute2_cmd(ifacename, refresh) + + def _addr_fill_iproute2_cmd(self, ifacename=None, refresh=False): + """ fills cache with address information + + if ifacename argument given, fill cache for ifacename, else + fill cache for all interfaces in the system + """ + linkout = {} + if LinkUtils._CACHE_FILL_DONE and not refresh: + return + try: + # Check if ifacename is already full, in which case, return + if ifacename and not refresh: + linkCache.get_attr([ifacename, 'addrs']) + return + except: + pass + cmdout = self.addr_show(ifacename=ifacename) + if not cmdout: + return + for c in cmdout.splitlines(): + citems = c.split() + ifnamenlink = citems[1].split('@') + if len(ifnamenlink) > 1: + ifname = ifnamenlink[0] + else: + ifname = ifnamenlink[0].strip(':') + if not linkout.get(ifname): + linkattrs = dict() + linkattrs['addrs'] = OrderedDict({}) + try: + linkout[ifname].update(linkattrs) + except KeyError: + linkout[ifname] = linkattrs + if citems[2] == 'inet': + if self._addr_filter(ifname, citems[3], scope=citems[5]): + continue + addrattrs = dict() + addrattrs['scope'] = citems[5] + addrattrs['type'] = 'inet' + linkout[ifname]['addrs'][citems[3]] = addrattrs + elif citems[2] == 'inet6': + if self._addr_filter(ifname, citems[3], scope=citems[5]): + continue + if citems[5] == 'link': + continue # skip 'link' addresses + addrattrs = dict() + addrattrs['scope'] = citems[5] + addrattrs['type'] = 'inet6' + linkout[ifname]['addrs'][citems[3]] = addrattrs + [linkCache.update_attrdict([ifname], linkattrs) + for ifname, linkattrs in linkout.items()] + + def cache_get(self, t, attrlist, refresh=False): + return self._cache_get(t, attrlist, refresh) + + def _cache_get(self, t, attrlist, refresh=False): + try: + if ifupdownflags.flags.DRYRUN: + return False + if ifupdownflags.flags.CACHE: + if self._fill_cache(): + # if we filled the cache, return new data + return linkCache.get_attr(attrlist) + if not refresh: + return linkCache.get_attr(attrlist) + if t == 'link': + self._link_fill(attrlist[0], refresh) + elif t == 'addr': + self._addr_fill(attrlist[0], refresh) + else: + self._link_fill(attrlist[0], refresh) + self._addr_fill(attrlist[0], refresh) + return linkCache.get_attr(attrlist) + except Exception, e: + self.logger.debug('_cache_get(%s) : [%s]' % (str(attrlist), str(e))) + return None + + def cache_check(self, attrlist, value, refresh=False): + return self._cache_check('link', attrlist, value, refresh=refresh) + + def _cache_check(self, t, attrlist, value, refresh=False): + try: + return self._cache_get(t, attrlist, refresh) == value + except Exception, e: + self.logger.debug('_cache_check(%s) : [%s]' + % (str(attrlist), str(e))) + return False + + def cache_update(self, attrlist, value): + return self._cache_update(attrlist, value) + + @staticmethod + def _cache_update(attrlist, value): + if ifupdownflags.flags.DRYRUN: + return + try: + if attrlist[-1] == 'slaves': + linkCache.append_to_attrlist(attrlist, value) + return + linkCache.set_attr(attrlist, value) + except: + pass + + @staticmethod + def _cache_delete(attrlist, value=None): + if ifupdownflags.flags.DRYRUN: + return + try: + if value: + linkCache.remove_from_attrlist(attrlist, value) + else: + linkCache.del_attr(attrlist) + except: + pass + + @staticmethod + def _cache_invalidate(): + linkCache.invalidate() + LinkUtils._CACHE_FILL_DONE = False + + @staticmethod + def batch_start(): + LinkUtils.ipbatcbuf = '' + LinkUtils.ipbatch = True + LinkUtils.ipbatch_pause = False + + @staticmethod + def add_to_batch(cmd): + LinkUtils.ipbatchbuf += cmd + '\n' + + @staticmethod + def batch_pause(): + LinkUtils.ipbatch_pause = True + + @staticmethod + def batch_resume(): + LinkUtils.ipbatch_pause = False + + def batch_commit(self): + if not LinkUtils.ipbatchbuf: + LinkUtils.ipbatchbuf = '' + LinkUtils.ipbatch = False + LinkUtils.ipbatch_pause = False + return + try: + utils.exec_command('%s -force -batch -' % utils.ip_cmd, + stdin=self.ipbatchbuf) + except: + raise + finally: + LinkUtils.ipbatchbuf = '' + LinkUtils.ipbatch = False + LinkUtils.ipbatch_pause = False + + def bridge_batch_commit(self): + if not LinkUtils.ipbatchbuf: + LinkUtils.ipbatchbuf = '' + LinkUtils.ipbatch = False + LinkUtils.ipbatch_pause = False + return + try: + utils.exec_command('%s -force -batch -' + % utils.bridge_cmd, stdin=self.ipbatchbuf) + except: + raise + finally: + LinkUtils.ipbatchbuf = '' + LinkUtils.ipbatch = False + LinkUtils.ipbatch_pause = False + + def addr_show(self, ifacename=None): + if ifacename: + if not self.link_exists(ifacename): + return + return utils.exec_commandl([utils.ip_cmd, + '-o', 'addr', 'show', 'dev', ifacename]) + else: + return utils.exec_commandl([utils.ip_cmd, + '-o', 'addr', 'show']) + + @staticmethod + def link_show(ifacename=None): + if ifacename: + return utils.exec_commandl([utils.ip_cmd, + '-o', '-d', 'link', 'show', 'dev', ifacename]) + else: + return utils.exec_commandl([utils.ip_cmd, + '-o', '-d', 'link', 'show']) + + def addr_add(self, ifacename, address, broadcast=None, + peer=None, scope=None, preferred_lifetime=None): + if not address: + return + cmd = 'addr add %s' % address + if broadcast: + cmd += ' broadcast %s' % broadcast + if peer: + cmd += ' peer %s' % peer + if scope: + cmd += ' scope %s' % scope + if preferred_lifetime: + cmd += ' preferred_lft %s' % preferred_lifetime + cmd += ' dev %s' % ifacename + if LinkUtils.ipbatch and not LinkUtils.ipbatch_pause: + self.add_to_batch(cmd) + else: + utils.exec_command('%s %s' % (utils.ip_cmd, cmd)) + self._cache_update([ifacename, 'addrs', address], {}) + + def addr_del(self, ifacename, address, broadcast=None, + peer=None, scope=None): + """ Delete ipv4 address """ + if not address: + return + if not self._cache_get('addr', [ifacename, 'addrs', address]): + return + cmd = 'addr del %s' % address + if broadcast: + cmd += 'broadcast %s' % broadcast + if peer: + cmd += 'peer %s' % peer + if scope: + cmd += 'scope %s' % scope + cmd += ' dev %s' % ifacename + utils.exec_command('%s %s' % (utils.ip_cmd, cmd)) + self._cache_delete([ifacename, 'addrs', address]) + + def addr_flush(self, ifacename): + cmd = 'addr flush dev %s' % ifacename + if LinkUtils.ipbatch and not LinkUtils.ipbatch_pause: + self.add_to_batch(cmd) + else: + utils.exec_command('%s %s' % (utils.ip_cmd, cmd)) + self._cache_delete([ifacename, 'addrs']) + + def del_addr_all(self, ifacename, skip_addrs=[]): + if not skip_addrs: + skip_addrs = [] + runningaddrsdict = self.get_running_addrs(ifname=ifacename) + try: + # XXX: ignore errors. Fix this to delete secondary addresses + # first + [self.addr_del(ifacename, a) for a in + set(runningaddrsdict.keys()).difference(skip_addrs)] + except: + # ignore errors + pass + + def addr_get(self, ifacename, details=True, refresh=False): + addrs = self._cache_get('addr', [ifacename, 'addrs'], refresh=refresh) + if not addrs: + return None + if details: + return addrs + return addrs.keys() + + def get_running_addrs(self, ifaceobj=None, ifname=None, details=True, addr_virtual_ifaceobj=None): + """ + We now support addr with link scope. Since the kernel may add it's + own link address to some interfaces we need to filter them out and + make sure we only deal with the addresses set by ifupdown2. + + To do so we look at the previous configuration made by ifupdown2 + (with the help of the statemanager) together with the addresses + specified by the user in /etc/network/interfaces, these addresses + are then compared to the running state of the intf (ip addr show) + made via a netlink addr dump. + For each configured addresses of scope link, we check if it was + previously configured by ifupdown2 to create a final set of the + addresses watched by ifupdown2 + """ + if not ifaceobj and not ifname: + return None + + config_addrs = set() + + if ifaceobj: + interface_name = ifaceobj.name + else: + interface_name = ifname + + if addr_virtual_ifaceobj: + for virtual in addr_virtual_ifaceobj.get_attr_value('address-virtual') or []: + for ip in virtual.split(): + try: + IPNetwork(ip) + config_addrs.add(ip) + except: + pass + + saved_ifaceobjs = statemanager.statemanager_api.get_ifaceobjs(addr_virtual_ifaceobj.name) + for saved_ifaceobj in saved_ifaceobjs or []: + for virtual in saved_ifaceobj.get_attr_value('address-virtual') or []: + for ip in virtual.split(): + try: + IPNetwork(ip) + config_addrs.add(ip) + except: + pass + else: + if ifaceobj: + for addr in ifaceobj.get_attr_value('address') or []: + config_addrs.add(addr) + + saved_ifaceobjs = statemanager.statemanager_api.get_ifaceobjs(interface_name) + for saved_ifaceobj in saved_ifaceobjs or []: + for addr in saved_ifaceobj.get_attr_value('address') or []: + config_addrs.add(addr) + + running_addrs = OrderedDict() + cached_addrs = self.addr_get(interface_name) + if cached_addrs: + for addr, addr_details in cached_addrs.items(): + try: + scope = int(addr_details['scope']) + except Exception: + try: + d = {} + addr_obj = IPNetwork(addr) + if isinstance(addr_obj, IPv6Network): + d['family'] = 'inet6' + else: + d['family'] = 'inet' + running_addrs[addr] = d + except: + running_addrs[addr] = {} + continue + if (scope & Route.RT_SCOPE_LINK and addr in config_addrs) or not scope & Route.RT_SCOPE_LINK: + running_addrs[addr] = addr_details + else: + return None + + if details: + return running_addrs + return running_addrs.keys() + + @staticmethod + def compare_user_config_vs_running_state(running_addrs, user_addrs): + ip4 = [] + ip6 = [] + + for ip in user_addrs or []: + obj = IPNetwork(ip) + + if type(obj) == IPv6Network: + ip6.append(obj) + else: + ip4.append(obj) + + running_ipobj = [] + for ip in running_addrs or []: + running_ipobj.append(IPNetwork(ip)) + + return running_ipobj == (ip4 + ip6) + + def addr_add_multiple(self, ifaceobj, ifacename, addrs, purge_existing=False): + # purges address + if purge_existing: + # if perfmode is not set and also if iface has no sibling + # objects, purge addresses that are not present in the new + # config + runningaddrs = self.get_running_addrs( + ifname=ifacename, + details=False, + addr_virtual_ifaceobj=ifaceobj + ) + addrs = utils.get_normalized_ip_addr(ifacename, addrs) + + if self.compare_user_config_vs_running_state(runningaddrs, addrs): + return + try: + # if primary address is not same, there is no need to keep any. + # reset all addresses + if (addrs and runningaddrs and + (addrs[0] != runningaddrs[0])): + self.del_addr_all(ifacename) + else: + self.del_addr_all(ifacename, addrs) + except Exception, e: + self.logger.warning('%s: %s' % (ifacename, str(e))) + for a in addrs: + try: + self.addr_add(ifacename, a) + except Exception, e: + self.logger.error(str(e)) + + def _link_set_ifflag(self, ifacename, value): + # Dont look at the cache, the cache may have stale value + # because link status can be changed by external + # entity (One such entity is ifupdown main program) + cmd = 'link set dev %s %s' % (ifacename, value.lower()) + if LinkUtils.ipbatch: + self.add_to_batch(cmd) + else: + utils.exec_command('%s %s' % (utils.ip_cmd, cmd)) + + def link_up(self, ifacename): + self._link_set_ifflag(ifacename, 'UP') + + def link_down(self, ifacename): + self._link_set_ifflag(ifacename, 'DOWN') + + def link_set(self, ifacename, key, value=None, + force=False, t=None, state=None): + if not force: + if (key not in ['master', 'nomaster'] and + self._cache_check('link', [ifacename, key], value)): + return + cmd = 'link set dev %s' % ifacename + if t: + cmd += ' type %s' % t + cmd += ' %s' % key + if value: + cmd += ' %s' % value + if state: + cmd += ' %s' % state + if LinkUtils.ipbatch: + self.add_to_batch(cmd) + else: + utils.exec_command('%s %s' % (utils.ip_cmd, cmd)) + if key not in ['master', 'nomaster']: + self._cache_update([ifacename, key], value) + + def link_set_hwaddress(self, ifacename, hwaddress, force=False): + if not force: + if self._cache_check('link', [ifacename, 'hwaddress'], hwaddress): + return + self.link_down(ifacename) + cmd = 'link set dev %s address %s' % (ifacename, hwaddress) + if LinkUtils.ipbatch: + self.add_to_batch(cmd) + else: + utils.exec_command('%s %s' % (utils.ip_cmd, cmd)) + self.link_up(ifacename) + self._cache_update([ifacename, 'hwaddress'], hwaddress) + + def link_set_mtu(self, ifacename, mtu): + if ifupdownflags.flags.DRYRUN: + return True + if not mtu or not ifacename: return + self.write_file('/sys/class/net/%s/mtu' % ifacename, mtu) + self._cache_update([ifacename, 'mtu'], mtu) + + def link_set_alias(self, ifacename, alias): + self.write_file('/sys/class/net/%s/ifalias' % ifacename, + '\n' if not alias else alias) + + def link_get_alias(self, ifacename): + return self.read_file_oneline('/sys/class/net/%s/ifalias' + % ifacename) + + def link_isloopback(self, ifacename): + flags = self._cache_get('link', [ifacename, 'flags']) + if not flags: + return + if 'LOOPBACK' in flags: + return True + return False + + def link_get_status(self, ifacename): + return self._cache_get('link', [ifacename, 'ifflag'], refresh=True) + + @staticmethod + def route_add_gateway(ifacename, gateway, vrf=None, metric=None): + if not gateway: + return + if not vrf: + cmd = '%s route add default via %s proto kernel' % (utils.ip_cmd, + gateway) + else: + cmd = ('%s route add table %s default via %s proto kernel' % + (utils.ip_cmd, vrf, gateway)) + # Add metric + if metric: + cmd += 'metric %s' % metric + cmd += ' dev %s' % ifacename + utils.exec_command(cmd) + + @staticmethod + def route_del_gateway(ifacename, gateway, vrf=None, metric=None): + # delete default gw + if not gateway: + return + if not vrf: + cmd = ('%s route del default via %s proto kernel' % + (utils.ip_cmd, gateway)) + else: + cmd = ('%s route del table %s default via %s proto kernel' % + (utils.ip_cmd, vrf, gateway)) + if metric: + cmd += ' metric %s' % metric + cmd += ' dev %s' % ifacename + utils.exec_command(cmd) + + @staticmethod + def _get_vrf_id(ifacename): + try: + return linkCache.vrfs[ifacename]['table'] + except KeyError: + dump = netlink.link_dump(ifacename) + + [linkCache.update_attrdict([ifname], linkattrs) + for ifname, linkattrs in dump.items()] + + if dump and dump.get(ifacename, {}).get('kind') == 'vrf': + vrf_table = dump.get(ifacename, {}).get('linkinfo', {}).get('table') + linkCache.vrfs[ifacename] = {'table': vrf_table} + return vrf_table + + return None + + def fix_ipv6_route_metric(self, ifaceobj, macvlan_ifacename, ips): + vrf_table = None + + if ifaceobj.link_privflags & ifaceLinkPrivFlags.VRF_SLAVE: + try: + for upper_iface in ifaceobj.upperifaces: + vrf_table = self._get_vrf_id(upper_iface) + if vrf_table: + break + except: + pass + + ip_route_del = [] + for ip in ips: + ip_network_obj = IPNetwork(ip) + + if type(ip_network_obj) == IPv6Network: + route_prefix = '%s/%d' % (ip_network_obj.network, ip_network_obj.prefixlen) + + if vrf_table: + if LinkUtils.ipbatch and not LinkUtils.ipbatch_pause: + LinkUtils.add_to_batch('route del %s table %s dev %s' % (route_prefix, vrf_table, macvlan_ifacename)) + else: + utils.exec_commandl([utils.ip_cmd, 'route', 'del', route_prefix, 'table', vrf_table, 'dev', macvlan_ifacename]) + else: + if LinkUtils.ipbatch and not LinkUtils.ipbatch_pause: + LinkUtils.add_to_batch('route del %s dev %s' % (route_prefix, macvlan_ifacename)) + else: + utils.exec_commandl([utils.ip_cmd, 'route', 'del', route_prefix, 'dev', macvlan_ifacename]) + ip_route_del.append((route_prefix, vrf_table)) + + for ip, vrf_table in ip_route_del: + if vrf_table: + if LinkUtils.ipbatch and not LinkUtils.ipbatch_pause: + LinkUtils.add_to_batch('route add %s table %s dev %s proto kernel metric 9999' % (ip, vrf_table, macvlan_ifacename)) + else: + utils.exec_commandl([utils.ip_cmd, 'route', 'add', ip, 'table', vrf_table, 'dev', macvlan_ifacename, 'proto', 'kernel' 'metric', '9999']) + else: + if LinkUtils.ipbatch and not LinkUtils.ipbatch_pause: + LinkUtils.add_to_batch('route add %s dev %s proto kernel metric 9999' % (ip, macvlan_ifacename)) + else: + utils.exec_commandl([utils.ip_cmd, 'route', 'add', ip, 'dev', macvlan_ifacename, 'proto', 'kernel' 'metric', '9999']) + + def link_create_vlan(self, vlan_device_name, vlan_raw_device, vlanid): + if self.link_exists(vlan_device_name): + return + utils.exec_command('%s link add link %s name %s type vlan id %d' % + (utils.ip_cmd, + vlan_raw_device, vlan_device_name, vlanid)) + self._cache_update([vlan_device_name], {}) + + def link_create_vlan_from_name(self, vlan_device_name): + v = vlan_device_name.split('.') + if len(v) != 2: + self.logger.warn('invalid vlan device name %s' % vlan_device_name) + return + self.link_create_vlan(vlan_device_name, v[0], v[1]) + + def link_create_macvlan(self, name, linkdev, mode='private'): + if self.link_exists(name): + return + cmd = ('link add link %s' % linkdev + + ' name %s' % name + + ' type macvlan mode %s' % mode) + if LinkUtils.ipbatch and not LinkUtils.ipbatch_pause: + self.add_to_batch(cmd) + else: + utils.exec_command('%s %s' % (utils.ip_cmd, cmd)) + self._cache_update([name], {}) + + def get_vxlan_peers(self, dev, svcnodeip): + cmd = '%s fdb show brport %s' % (utils.bridge_cmd, + dev) + cur_peers = [] + try: + ps = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, close_fds=False) + utils.enable_subprocess_signal_forwarding(ps, signal.SIGINT) + output = subprocess.check_output(('grep', '00:00:00:00:00:00'), stdin=ps.stdout) + ps.wait() + utils.disable_subprocess_signal_forwarding(signal.SIGINT) + try: + ppat = re.compile('\s+dst\s+(\d+.\d+.\d+.\d+)\s+') + for l in output.split('\n'): + m = ppat.search(l) + if m and m.group(1) != svcnodeip: + cur_peers.append(m.group(1)) + except: + self.logger.warn('error parsing ip link output') + except subprocess.CalledProcessError as e: + if e.returncode != 1: + self.logger.error(str(e)) + finally: + utils.disable_subprocess_signal_forwarding(signal.SIGINT) + + return cur_peers + + def link_create_vxlan(self, name, vxlanid, + localtunnelip=None, + svcnodeip=None, + remoteips=None, + learning='on', + ageing=None, + anycastip=None): + if svcnodeip and remoteips: + raise Exception("svcnodeip and remoteip is mutually exclusive") + args = '' + if svcnodeip: + args += ' remote %s' % svcnodeip + if ageing: + args += ' ageing %s' % ageing + if learning == 'off': + args += ' nolearning' + + if self.link_exists(name): + cmd = 'link set dev %s type vxlan dstport %d' % (name, LinkUtils.VXLAN_UDP_PORT) + vxlanattrs = self.get_vxlandev_attrs(name) + # on ifreload do not overwrite anycast_ip to individual ip if clagd + # has modified + if vxlanattrs: + running_localtunnelip = vxlanattrs.get('local') + if anycastip and running_localtunnelip and anycastip == running_localtunnelip: + localtunnelip = running_localtunnelip + running_svcnode = vxlanattrs.get('svcnode') + if running_svcnode and not svcnodeip: + args += ' noremote' + else: + cmd = 'link add dev %s type vxlan id %s dstport %d' % (name, vxlanid, LinkUtils.VXLAN_UDP_PORT) + + if localtunnelip: + args += ' local %s' % localtunnelip + cmd += args + + if LinkUtils.ipbatch and not LinkUtils.ipbatch_pause: + self.add_to_batch(cmd) + else: + utils.exec_command('%s %s' % (utils.ip_cmd, cmd)) + + # XXX: update linkinfo correctly + #self._cache_update([name], {}) + + @staticmethod + def link_exists(ifacename): + if ifupdownflags.flags.DRYRUN: + return True + return os.path.exists('/sys/class/net/%s' % ifacename) + + def link_get_ifindex(self, ifacename): + if ifupdownflags.flags.DRYRUN: + return True + return self.read_file_oneline('/sys/class/net/%s/ifindex' % ifacename) + + def is_vlan_device_by_name(self, ifacename): + if re.search(r'\.', ifacename): + return True + return False + + @staticmethod + def link_add_macvlan(ifname, macvlan_ifacename): + utils.exec_commandl(['ip', 'link', 'add', 'link', ifname, 'name', macvlan_ifacename, 'type', 'macvlan', 'mode', 'private']) + + @staticmethod + def route_add(route): + utils.exec_command('%s route add %s' % (utils.ip_cmd, + route)) + + @staticmethod + def route6_add(route): + utils.exec_command('%s -6 route add %s' % (utils.ip_cmd, + route)) + + def get_vlandev_attrs(self, ifacename): + return (self._cache_get('link', [ifacename, 'link']), + self._cache_get('link', [ifacename, 'linkinfo', 'vlanid']), + self._cache_get('link', [ifacename, 'linkinfo', 'vlan_protocol'])) + + def get_vlan_protocol(self, ifacename): + return self._cache_get('link', [ifacename, 'linkinfo', 'vlan_protocol']) + + def get_vxlandev_attrs(self, ifacename): + return self._cache_get('link', [ifacename, 'linkinfo']) + + def get_vxlandev_learning(self, ifacename): + return self._cache_get('link', [ifacename, 'linkinfo', Link.IFLA_VXLAN_LEARNING]) + + def set_vxlandev_learning(self, ifacename, learn): + if learn == 'on': + utils.exec_command('%s link set dev %s type vxlan learning' % + (utils.ip_cmd, ifacename)) + self._cache_update([ifacename, 'linkinfo', 'learning'], 'on') + else: + utils.exec_command('%s link set dev %s type vxlan nolearning' % + (utils.ip_cmd, ifacename)) + self._cache_update([ifacename, 'linkinfo', 'learning'], 'off') + + def link_get_linkinfo_attrs(self, ifacename): + return self._cache_get('link', [ifacename, 'linkinfo']) + + def link_get_mtu(self, ifacename, refresh=False): + return self._cache_get('link', [ifacename, 'mtu'], refresh=refresh) + + def link_get_mtu_sysfs(self, ifacename): + return self.read_file_oneline('/sys/class/net/%s/mtu' + % ifacename) + + def link_get_kind(self, ifacename): + return self._cache_get('link', [ifacename, 'kind']) + + def link_get_slave_kind(self, ifacename): + return self._cache_get('link', [ifacename, 'slave_kind']) + + def link_get_hwaddress(self, ifacename): + address = self._cache_get('link', [ifacename, 'hwaddress']) + # newly created logical interface addresses dont end up in the cache + # read hwaddress from sysfs file for these interfaces + if not address: + address = self.read_file_oneline('/sys/class/net/%s/address' + % ifacename) + return address + + def link_create(self, ifacename, t, attrs={}): + """ generic link_create function """ + if self.link_exists(ifacename): + return + cmd = 'link add' + cmd += ' name %s type %s' % (ifacename, t) + if attrs: + for k, v in attrs.iteritems(): + cmd += ' %s' % k + if v: + cmd += ' %s' % v + if LinkUtils.ipbatch and not LinkUtils.ipbatch_pause: + self.add_to_batch(cmd) + else: + utils.exec_command('%s %s' % (utils.ip_cmd, cmd)) + self._cache_update([ifacename], {}) + + def link_delete(self, ifacename): + if not self.link_exists(ifacename): + return + cmd = 'link del %s' % ifacename + if LinkUtils.ipbatch and not LinkUtils.ipbatch_pause: + self.add_to_batch(cmd) + else: + utils.exec_command('%s %s' % (utils.ip_cmd, cmd)) + self._cache_invalidate() + + def link_get_master(self, ifacename): + sysfs_master_path = '/sys/class/net/%s/master' % ifacename + if os.path.exists(sysfs_master_path): + link_path = os.readlink(sysfs_master_path) + if link_path: + return os.path.basename(link_path) + else: + return None + else: + return self._cache_get('link', [ifacename, 'master']) + + def get_brport_peer_link(self, bridgename): + try: + return self._cache_get('link', [bridgename, 'info_slave_data', Link.IFLA_BRPORT_PEER_LINK]) + except: + return None + + @staticmethod + def bridge_port_vids_add(bridgeportname, vids): + [utils.exec_command('%s vlan add vid %s dev %s' % + (utils.bridge_cmd, + v, bridgeportname)) for v in vids] + + @staticmethod + def bridge_port_vids_del(bridgeportname, vids): + if not vids: + return + [utils.exec_command('%s vlan del vid %s dev %s' % + (utils.bridge_cmd, + v, bridgeportname)) for v in vids] + + @staticmethod + def bridge_port_vids_flush(bridgeportname, vid): + utils.exec_command('%s vlan del vid %s dev %s' % + (utils.bridge_cmd, + vid, bridgeportname)) + + @staticmethod + def bridge_port_vids_get(bridgeportname): + bridgeout = utils.exec_command('%s vlan show dev %s' % + (utils.bridge_cmd, + bridgeportname)) + if not bridgeout: + return [] + brvlanlines = bridgeout.readlines()[2:] + vids = [l.strip() for l in brvlanlines] + return [v for v in vids if v] + + @staticmethod + def bridge_port_vids_get_all(): + brvlaninfo = {} + bridgeout = utils.exec_command('%s -c vlan show' + % utils.bridge_cmd) + if not bridgeout: + return brvlaninfo + brvlanlines = bridgeout.splitlines() + brportname = None + for l in brvlanlines[1:]: + if l and not l.startswith(' ') and not l.startswith('\t'): + attrs = l.split() + brportname = attrs[0].strip() + brvlaninfo[brportname] = {'pvid': None, 'vlan': []} + l = ' '.join(attrs[1:]) + if not brportname or not l: + continue + l = l.strip() + if 'PVID' in l: + brvlaninfo[brportname]['pvid'] = l.split()[0] + elif 'Egress Untagged' not in l: + brvlaninfo[brportname]['vlan'].append(l) + return brvlaninfo + + def bridge_port_vids_get_all_json(self): + if not self.supported_command['%s -c -json vlan show' + % utils.bridge_cmd]: + return {} + brvlaninfo = {} + try: + bridgeout = utils.exec_command('%s -c -json vlan show' + % utils.bridge_cmd) + except: + self.supported_command['%s -c -json vlan show' + % utils.bridge_cmd] = False + self.logger.info('%s -c -json vlan show: skipping unsupported command' + % utils.bridge_cmd) + try: + return self.get_bridge_vlan_nojson() + except Exception as e: + self.logger.info('bridge: get_bridge_vlan_nojson: %s' % str(e)) + return {} + + if not bridgeout: return brvlaninfo + try: + vlan_json_dict = json.loads(bridgeout, encoding="utf-8") + except Exception, e: + self.logger.info('json loads failed with (%s)' % str(e)) + return {} + return vlan_json_dict + + @staticmethod + def get_bridge_vlan_nojson(): + vlan_json = {} + bridgeout = utils.exec_commandl([utils.bridge_cmd, '-c', 'vlan', 'show']) + if bridgeout: + output = [line.split('\n') for line in bridgeout.split('\n\n')] + output[0] = output[0][1:] + for line in output: + current_swp = None + if not line: + continue + for entry in line: + if not entry: + continue + prefix, vlan = entry.split('\t') + if prefix: + current_swp = prefix + vlan_json[prefix] = [] + v = {} + vlan = vlan[1:] + try: + v['vlan'] = int(vlan) + except: + try: + if '-' in vlan: + start, end = vlan.split('-') + if ' ' in end: + end = end[0:end.index(' ')] + v['vlan'] = int(start) + v['vlanEnd'] = int(end) + else: + v['vlan'] = int(vlan[0:vlan.index(' ')]) + flags = [] + if 'PVID' in vlan: + flags.append('PVID') + if 'Egress Untagged' in vlan: + flags.append('Egress Untagged') + v['flags'] = flags + except: + continue + vlan_json[current_swp].append(v) + return vlan_json + + def bridge_vlan_cache_get(self, ifacename, refresh=False): + if not self.bridge_vlan_cache_fill_done or refresh: + self.bridge_vlan_cache = self.bridge_port_vids_get_all_json() + self.bridge_vlan_cache_fill_done = True + return self.bridge_vlan_cache.get(ifacename, {}) + + def bridge_vlan_get_pvid(self, ifacename, refresh=False): + pvid = 0 + + for vinfo in self.bridge_vlan_cache_get(ifacename, refresh): + v = vinfo.get('vlan') + pvid = v if 'PVID' in vinfo.get('flags', []) else 0 + if pvid: + return pvid + return pvid + + def bridge_vlan_get_vids(self, ifacename, refresh=False): + vids = [] + + for vinfo in self.bridge_vlan_cache_get(ifacename, refresh): + v = vinfo.get('vlan') + ispvid = True if 'PVID' in vinfo.get('flags', []) else False + if ispvid: + pvid = v if 'PVID' in vinfo.get('flags', []) else 0 + if pvid == 1: + continue + vEnd = vinfo.get('vlanEnd') + if vEnd: + vids.extend(range(v, vEnd + 1)) + else: + vids.append(v) + return vids + + def bridge_vlan_get_vids_n_pvid(self, ifacename, refresh=False): + vids = [] + pvid = 0 + + for vinfo in self.bridge_vlan_cache_get(ifacename, refresh): + v = vinfo.get('vlan') + ispvid = True if 'PVID' in vinfo.get('flags', []) else False + if ispvid: + pvid = v if 'PVID' in vinfo.get('flags', []) else 0 + vEnd = vinfo.get('vlanEnd') + if vEnd: + vids.extend(range(v, vEnd + 1)) + else: + vids.append(v) + return vids, pvid + + def bridge_port_pvid_add(self, bridgeportname, pvid): + if LinkUtils.ipbatch and not LinkUtils.ipbatch_pause: + self.add_to_batch('vlan add vid %s untagged pvid dev %s' % + (pvid, bridgeportname)) + else: + utils.exec_command('%s vlan add vid %s untagged pvid dev %s' % + (utils.bridge_cmd, + pvid, bridgeportname)) + + def bridge_port_pvid_del(self, bridgeportname, pvid): + if LinkUtils.ipbatch and not LinkUtils.ipbatch_pause: + self.add_to_batch('vlan del vid %s untagged pvid dev %s' % + (pvid, bridgeportname)) + else: + utils.exec_command('%s vlan del vid %s untagged pvid dev %s' % + (utils.bridge_cmd, + pvid, bridgeportname)) + + def bridge_port_pvids_get(self, bridgeportname): + return self.read_file_oneline('/sys/class/net/%s/brport/pvid' + % bridgeportname) + + def bridge_vids_add(self, bridgeportname, vids, bridge=True): + target = 'self' if bridge else '' + if LinkUtils.ipbatch and not LinkUtils.ipbatch_pause: + [self.add_to_batch('vlan add vid %s dev %s %s' % + (v, bridgeportname, target)) for v in vids] + else: + [utils.exec_command('%s vlan add vid %s dev %s %s' % + (utils.bridge_cmd, + v, bridgeportname, target)) for v in vids] + + def bridge_vids_del(self, bridgeportname, vids, bridge=True): + target = 'self' if bridge else '' + if LinkUtils.ipbatch and not LinkUtils.ipbatch_pause: + [self.add_to_batch('vlan del vid %s dev %s %s' % + (v, bridgeportname, target)) for v in vids] + else: + [utils.exec_command('%s vlan del vid %s dev %s %s' % + (utils.bridge_cmd, + v, bridgeportname, target)) for v in vids] + + @staticmethod + def bridge_fdb_add(dev, address, vlan=None, bridge=True, remote=None): + target = 'self' if bridge else '' + vlan_str = '' + if vlan: + vlan_str = 'vlan %s ' % vlan + + dst_str = '' + if remote: + dst_str = 'dst %s ' % remote + + utils.exec_command('%s fdb replace %s dev %s %s %s %s' % + (utils.bridge_cmd, + address, dev, vlan_str, target, dst_str)) + + @staticmethod + def bridge_fdb_append(dev, address, vlan=None, bridge=True, remote=None): + target = 'self' if bridge else '' + vlan_str = '' + if vlan: + vlan_str = 'vlan %s ' % vlan + + dst_str = '' + if remote: + dst_str = 'dst %s ' % remote + + utils.exec_command('%s fdb append %s dev %s %s %s %s' % + (utils.bridge_cmd, + address, dev, vlan_str, target, dst_str)) + + @staticmethod + def bridge_fdb_del(dev, address, vlan=None, bridge=True, remote=None): + target = 'self' if bridge else '' + vlan_str = '' + if vlan: + vlan_str = 'vlan %s ' % vlan + + dst_str = '' + if remote: + dst_str = 'dst %s ' % remote + utils.exec_command('%s fdb del %s dev %s %s %s %s' % + (utils.bridge_cmd, + address, dev, vlan_str, target, dst_str)) + + def bridge_is_vlan_aware(self, bridgename): + filename = '/sys/class/net/%s/bridge/vlan_filtering' % bridgename + if os.path.exists(filename) and self.read_file_oneline(filename) == '1': + return True + return False + + @staticmethod + def bridge_port_get_bridge_name(bridgeport): + filename = '/sys/class/net/%s/brport/bridge' % bridgeport + try: + return os.path.basename(os.readlink(filename)) + except: + return None + + @staticmethod + def bridge_port_exists(bridge, bridgeportname): + try: + return os.path.exists('/sys/class/net/%s/brif/%s' + % (bridge, bridgeportname)) + except Exception: + return False + + def bridge_fdb_show_dev(self, dev): + try: + fdbs = {} + output = utils.exec_command('%s fdb show dev %s' + % (utils.bridge_cmd, dev)) + if output: + for fdb_entry in output.splitlines(): + try: + entries = fdb_entry.split() + fdbs.setdefault(entries[2], []).append(entries[0]) + except: + self.logger.debug('%s: invalid fdb line \'%s\'' + % (dev, fdb_entry)) + return fdbs + except Exception: + return None + + @staticmethod + def is_bridge(bridge): + return os.path.exists('/sys/class/net/%s/bridge' % bridge) + + def is_link_up(self, ifacename): + ret = False + try: + flags = self.read_file_oneline('/sys/class/net/%s/flags' % ifacename) + iflags = int(flags, 16) + if iflags & 0x0001: + ret = True + except: + ret = False + return ret + + def ip_route_get_dev(self, prefix): + try: + output = utils.exec_command('%s route get %s' % + (utils.ip_cmd, prefix)) + if output: + rline = output.splitlines()[0] + if rline: + rattrs = rline.split() + return rattrs[rattrs.index('dev') + 1] + except Exception, e: + self.logger.debug('ip_route_get_dev: failed .. %s' % str(e)) + return None + + @staticmethod + def link_get_lowers(ifacename): + try: + lowers = glob.glob("/sys/class/net/%s/lower_*" % ifacename) + if not lowers: + return [] + return [os.path.basename(l)[6:] for l in lowers] + except: + return [] + + @staticmethod + def link_get_uppers(ifacename): + try: + uppers = glob.glob("/sys/class/net/%s/upper_*" % ifacename) + if not uppers: + return None + return [os.path.basename(u)[6:] for u in uppers] + except Exception: + return None + + def link_get_vrfs(self): + if not LinkUtils._CACHE_FILL_DONE: + self._fill_cache() + return linkCache.vrfs + + @staticmethod + def cache_get_info_slave(attrlist): + try: + return linkCache.get_attr(attrlist) + except: + return None + + def get_brport_learning(self, ifacename): + learn = self.read_file_oneline('/sys/class/net/%s/brport/learning' + % ifacename) + if learn and learn == '1': + return 'on' + else: + return 'off' + + def get_brport_learning_bool(self, ifacename): + return utils.get_boolean_from_string(self.read_file_oneline('/sys/class/net/%s/brport/learning' % ifacename)) + + def set_brport_learning(self, ifacename, learn): + if learn == 'off': + return self.write_file('/sys/class/net/%s/brport/learning' + % ifacename, '0') + else: + return self.write_file('/sys/class/net/%s/brport/learning' + % ifacename, '1') + + ################################################################################# + ################################### BOND UTILS ################################## + ################################################################################# + + def _link_cache_get(self, attrlist, refresh=False): + return self._cache_get('link', attrlist, refresh) + + def cache_delete(self, attrlist, value=None): + return self._cache_delete(attrlist, value) + + def link_cache_get(self, attrlist, refresh=False): + return self._link_cache_get(attrlist, refresh) + + def link_cache_check(self, attrlist, value, refresh=False): + return self._link_cache_check(attrlist, value, refresh) + + def _link_cache_check(self, attrlist, value, refresh=False): + try: + return self._link_cache_get(attrlist, refresh) == value + except Exception, e: + self.logger.debug('_cache_check(%s) : [%s]' + % (str(attrlist), str(e))) + pass + return False + + bondcmd_attrmap = { + Link.IFLA_BOND_MODE: 'mode', + Link.IFLA_BOND_MIIMON: 'miimon', + Link.IFLA_BOND_USE_CARRIER: 'use_carrier', + Link.IFLA_BOND_AD_LACP_RATE: 'lacp_rate', + Link.IFLA_BOND_XMIT_HASH_POLICY: 'xmit_hash_policy', + Link.IFLA_BOND_MIN_LINKS: 'min_links', + Link.IFLA_BOND_NUM_PEER_NOTIF: 'num_grat_arp', + Link.IFLA_BOND_AD_ACTOR_SYSTEM: 'ad_actor_system', + Link.IFLA_BOND_AD_ACTOR_SYS_PRIO: 'ad_actor_sys_prio', + Link.IFLA_BOND_AD_LACP_BYPASS: 'lacp_bypass', + Link.IFLA_BOND_UPDELAY: 'updelay', + Link.IFLA_BOND_DOWNDELAY: 'downdelay', + } + + def bond_set_attrs_nl(self, bondname, ifla_info_data): + bond_attr_name = 'None' # for log purpose (in case an exception raised) + for nl_attr, value in ifla_info_data.items(): + try: + bond_attr_name = self.bondcmd_attrmap[nl_attr] + file_path = '/sys/class/net/%s/bonding/%s' % (bondname, bond_attr_name) + if os.path.exists(file_path): + self.write_file(file_path, str(value)) + except Exception as e: + exception_str = '%s: %s %s: %s' % (bondname, bond_attr_name, value, str(e)) + if ifupdownflags.flags.FORCE: + self.logger.warning(exception_str) + else: + self.logger.debug(exception_str) + + def bond_set_attrs(self, bondname, attrdict, prehook): + for attrname, attrval in attrdict.items(): + if (self._link_cache_check([bondname, 'linkinfo', + attrname], attrval)): + continue + if (attrname == 'mode' + or attrname == 'xmit_hash_policy' + or attrname == 'lacp_rate' or attrname == 'min_links'): + if prehook: + prehook(bondname) + try: + if ((attrname not in ['lacp_rate', + 'lacp_bypass']) or + self._link_cache_check([bondname, 'linkinfo', 'mode'], '802.3ad', + True)): + self.write_file('/sys/class/net/%s/bonding/%s' + % (bondname, attrname), attrval) + except Exception, e: + if ifupdownflags.flags.FORCE: + self.logger.warn(str(e)) + pass + else: + raise + + def bond_set_use_carrier(self, bondname, use_carrier): + if not use_carrier or (use_carrier != '0' and use_carrier != '1'): + return + if (self._link_cache_check([bondname, 'linkinfo', 'use_carrier'], + use_carrier)): + return + self.write_file('/sys/class/net/%s' % bondname + + '/bonding/use_carrier', use_carrier) + self._cache_update([bondname, 'linkinfo', + 'use_carrier'], use_carrier) + + def bond_get_use_carrier(self, bondname): + return self._link_cache_get([bondname, 'linkinfo', 'use_carrier']) + + def bond_get_use_carrier_nl(self, bondname): + return self._link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_USE_CARRIER]) + + def bond_set_xmit_hash_policy(self, bondname, hash_policy, prehook=None): + valid_values = ['layer2', 'layer3+4', 'layer2+3'] + if not hash_policy: + return + if hash_policy not in valid_values: + raise Exception('invalid hash policy value %s' % hash_policy) + if (self._link_cache_check([bondname, 'linkinfo', 'xmit_hash_policy'], + hash_policy)): + return + if prehook: + prehook(bondname) + self.write_file('/sys/class/net/%s' % bondname + + '/bonding/xmit_hash_policy', hash_policy) + self._cache_update([bondname, 'linkinfo', 'xmit_hash_policy'], + hash_policy) + + def bond_get_xmit_hash_policy(self, bondname): + return self._link_cache_get([bondname, 'linkinfo', 'xmit_hash_policy']) + + def bond_get_xmit_hash_policy_nl(self, bondname): + return self._link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_XMIT_HASH_POLICY]) + + def bond_set_miimon(self, bondname, miimon): + if (self._link_cache_check([bondname, 'linkinfo', 'miimon'], + miimon)): + return + self.write_file('/sys/class/net/%s' % bondname + + '/bonding/miimon', miimon) + self._cache_update([bondname, 'linkinfo', 'miimon'], miimon) + + def bond_get_miimon(self, bondname): + return self._link_cache_get([bondname, 'linkinfo', 'miimon']) + + def bond_get_miimon_nl(self, bondname): + return self._link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_MIIMON]) + + def bond_set_mode(self, bondname, mode, prehook=None): + valid_modes = ['balance-rr', 'active-backup', 'balance-xor', + 'broadcast', '802.3ad', 'balance-tlb', 'balance-alb'] + if not mode: + return + if mode not in valid_modes: + raise Exception('invalid mode %s' % mode) + if (self._link_cache_check([bondname, 'linkinfo', 'mode'], + mode)): + return + if prehook: + prehook(bondname) + self.write_file('/sys/class/net/%s' % bondname + '/bonding/mode', mode) + self._cache_update([bondname, 'linkinfo', 'mode'], mode) + + def bond_get_mode(self, bondname): + return self._link_cache_get([bondname, 'linkinfo', 'mode']) + + def bond_get_mode_nl(self, bondname): + return self._link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_MODE]) + + def bond_set_lacp_rate(self, bondname, lacp_rate, prehook=None, posthook=None): + if not lacp_rate or (lacp_rate != '0' and lacp_rate != '1'): + return + if (self._link_cache_check([bondname, 'linkinfo', 'lacp_rate'], + lacp_rate)): + return + if prehook: + prehook(bondname) + try: + self.write_file('/sys/class/net/%s' % bondname + + '/bonding/lacp_rate', lacp_rate) + except: + raise + finally: + if posthook: + prehook(bondname) + self._cache_update([bondname, 'linkinfo', + 'lacp_rate'], lacp_rate) + + def bond_get_lacp_rate(self, bondname): + return self._link_cache_get([bondname, 'linkinfo', 'lacp_rate']) + + def bond_get_lacp_rate_nl(self, bondname): + return self._link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_AD_LACP_RATE]) + + def bond_set_lacp_bypass_allow(self, bondname, allow, prehook=None, posthook=None): + if self._link_cache_check([bondname, 'linkinfo', 'lacp_bypass'], allow): + return + if prehook: + prehook(bondname) + try: + self.write_file('/sys/class/net/%s' % bondname + + '/bonding/lacp_bypass', allow) + except: + raise + finally: + if posthook: + posthook(bondname) + self._cache_update([bondname, 'linkinfo', + 'lacp_bypass'], allow) + + def bond_get_lacp_bypass_allow(self, bondname): + return self._link_cache_get([bondname, 'linkinfo', 'lacp_bypass']) + + def bond_get_lacp_bypass_allow_nl(self, bondname): + return self._link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_AD_LACP_BYPASS]) + + def bond_set_min_links(self, bondname, min_links, prehook=None): + if (self._link_cache_check([bondname, 'linkinfo', 'min_links'], + min_links)): + return + if prehook: + prehook(bondname) + self.write_file('/sys/class/net/%s/bonding/min_links' % bondname, + min_links) + self._cache_update([bondname, 'linkinfo', 'min_links'], min_links) + + def bond_get_min_links(self, bondname): + return self._link_cache_get([bondname, 'linkinfo', 'min_links']) + + def get_min_links_nl(self, bondname): + return self._link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_MIN_LINKS]) + + def bond_get_ad_actor_system(self, bondname): + return self._link_cache_get([bondname, 'linkinfo', 'ad_actor_system']) + + def bond_get_ad_actor_system_nl(self, bondname): + return self._link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_AD_ACTOR_SYSTEM]) + + def bond_get_ad_actor_sys_prio(self, bondname): + return self._link_cache_get([bondname, 'linkinfo', 'ad_actor_sys_prio']) + + def bond_get_ad_actor_sys_prio_nl(self, bondname): + return self._link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_AD_ACTOR_SYS_PRIO]) + + def bond_get_num_unsol_na(self, bondname): + return self._link_cache_get([bondname, 'linkinfo', 'num_unsol_na']) + + def bond_get_num_unsol_na_nl(self, bondname): + return self._link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_NUM_PEER_NOTIF]) + + def bond_get_num_grat_arp(self, bondname): + return self._link_cache_get([bondname, 'linkinfo', 'num_grat_arp']) + + def bond_get_num_grat_arp_nl(self, bondname): + return self._link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_NUM_PEER_NOTIF]) + + def bond_get_updelay(self, bondname): + return self._link_cache_get([bondname, 'linkinfo', 'updelay']) + + def bond_get_updelay_nl(self, bondname): + return self._link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_UPDELAY]) + + def bond_get_downdelay(self, bondname): + return self._link_cache_get([bondname, 'linkinfo', 'downdelay']) + + def bond_get_downdelay_nl(self, bondname): + return self._link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_DOWNDELAY]) + + def bond_enslave_slave(self, bondname, slave, prehook=None, posthook=None): + slaves = self._link_cache_get([bondname, 'linkinfo', 'slaves']) + if slaves and slave in slaves: + return + if prehook: + prehook(slave) + self.write_file('/sys/class/net/%s' % bondname + + '/bonding/slaves', '+' + slave) + if posthook: + posthook(slave) + self._cache_update([bondname, 'linkinfo', 'slaves'], slave) + + def bond_remove_slave(self, bondname, slave): + slaves = self._link_cache_get([bondname, 'linkinfo', 'slaves']) + if not slaves or slave not in slaves: + return + sysfs_bond_path = ('/sys/class/net/%s' % bondname + + '/bonding/slaves') + if not os.path.exists(sysfs_bond_path): + return + self.write_file(sysfs_bond_path, '-' + slave) + self._cache_delete([bondname, 'linkinfo', 'slaves'], slave) + + def bond_remove_slaves_all(self, bondname): + if not self._link_cache_get([bondname, 'linkinfo', 'slaves']): + return + slaves = None + sysfs_bond_path = ('/sys/class/net/%s' % bondname + + '/bonding/slaves') + try: + with open(sysfs_bond_path, 'r') as f: + slaves = f.readline().strip().split() + except IOError, e: + raise Exception('error reading slaves of bond %s (%s)' % (bondname, str(e))) + for slave in slaves: + self.link_down(slave) + try: + self.bond_remove_slave(bondname, slave) + except Exception, e: + if not ifupdownflags.flags.FORCE: + raise Exception('error removing slave %s from bond %s (%s)' % (slave, bondname, str(e))) + else: + pass + self._cache_delete([bondname, 'linkinfo', 'slaves']) + + @staticmethod + def bond_load_bonding_module(): + return utils.exec_command('%s -q bonding' % utils.modprobe_cmd) + + def create_bond(self, bondname): + if self.bond_exists(bondname): + return + # load_bonding_module() has already been run + self.write_file('/sys/class/net/bonding_masters', '+' + bondname) + self._cache_update([bondname], {}) + + def delete_bond(self, bondname): + if not os.path.exists('/sys/class/net/%s' % bondname): + return + self.write_file('/sys/class/net/bonding_masters', '-' + bondname) + self._cache_delete([bondname]) + + def bond_get_slaves(self, bondname): + slaves = self._link_cache_get([bondname, 'linkinfo', 'slaves']) + if slaves: + return list(slaves) + slavefile = '/sys/class/net/%s/bonding/slaves' % bondname + if os.path.exists(slavefile): + buf = self.read_file_oneline(slavefile) + if buf: + slaves = buf.split() + if not slaves: + return [] + self._cache_update([bondname, 'linkinfo', 'slaves'], slaves) + return list(slaves) + + def bond_slave_exists(self, bond, slave): + slaves = self.bond_get_slaves(bond) + if not slaves: + return False + return slave in slaves + + @staticmethod + def bond_exists(bondname): + return os.path.exists('/sys/class/net/%s/bonding' % bondname) + + ################################################################################# + ################################## BRIDGE UTILS ################################# + ################################################################################# + + def create_bridge(self, bridgename): + if not LinkUtils.bridge_utils_is_installed: + return + if self.bridge_exists(bridgename): + return + utils.exec_command('%s addbr %s' % (utils.brctl_cmd, bridgename)) + self._cache_update([bridgename], {}) + + def delete_bridge(self, bridgename): + if not LinkUtils.bridge_utils_is_installed: + return + if not self.bridge_exists(bridgename): + return + utils.exec_command('%s delbr %s' % (utils.brctl_cmd, bridgename)) + self._cache_invalidate() + + def add_bridge_port(self, bridgename, bridgeportname): + """ Add port to bridge """ + if not LinkUtils.bridge_utils_is_installed: + return + ports = self._link_cache_get([bridgename, 'linkinfo', 'ports']) + if ports and ports.get(bridgeportname): + return + utils.exec_command('%s addif %s %s' % (utils.brctl_cmd, bridgename, bridgeportname)) + self._cache_update([bridgename, 'linkinfo', 'ports', bridgeportname], {}) + + def delete_bridge_port(self, bridgename, bridgeportname): + """ Delete port from bridge """ + if not LinkUtils.bridge_utils_is_installed: + return + ports = self._link_cache_get([bridgename, 'linkinfo', 'ports']) + if not ports or not ports.get(bridgeportname): + return + utils.exec_command('%s delif %s %s' % (utils.brctl_cmd, bridgename, bridgeportname)) + self._cache_delete([bridgename, 'linkinfo', 'ports', 'bridgeportname']) + + def set_bridgeport_attrs(self, bridgename, bridgeportname, attrdict): + portattrs = self._link_cache_get([bridgename, 'linkinfo', 'ports', bridgeportname]) + if portattrs == None: + portattrs = {} + for k, v in attrdict.iteritems(): + if ifupdownflags.flags.CACHE: + curval = portattrs.get(k) + if curval and curval == v: + continue + if k == 'unicast-flood': + self.write_file('/sys/class/net/%s/brport/unicast_flood' % bridgeportname, v) + elif k == 'multicast-flood': + self.write_file('/sys/class/net/%s/brport/multicast_flood' % bridgeportname, v) + elif k == 'learning': + self.write_file('/sys/class/net/%s/brport/learning' % bridgeportname, v) + elif k == 'arp-nd-suppress': + self.write_file('/sys/class/net/%s/brport/neigh_suppress' % bridgeportname, v) + else: + if not LinkUtils.bridge_utils_is_installed: + continue + utils.exec_command('%s set%s %s %s %s' % (utils.brctl_cmd, k, bridgename, bridgeportname, v)) + + def set_bridgeport_attr(self, bridgename, bridgeportname, + attrname, attrval): + if not LinkUtils.bridge_utils_is_installed: + return + if self._link_cache_check([bridgename, 'linkinfo', 'ports', bridgeportname, attrname], attrval): + return + utils.exec_command('%s set%s %s %s %s' % + (utils.brctl_cmd, + attrname, + bridgename, + bridgeportname, + attrval)) + + def set_bridge_attrs(self, bridgename, attrdict): + for k, v in attrdict.iteritems(): + if not v: + continue + if self._link_cache_check([bridgename, 'linkinfo', k], v): + continue + try: + if k == 'igmp-version': + self.write_file('/sys/class/net/%s/bridge/' + 'multicast_igmp_version' % bridgename, v) + elif k == 'mld-version': + self.write_file('/sys/class/net/%s/bridge/' + 'multicast_mld_version' % bridgename, v) + elif k == 'vlan-protocol': + self.write_file('/sys/class/net/%s/bridge/' + 'vlan_protocol' % bridgename, + VlanProtocols.ETHERTYPES_TO_ID.get(v.upper(), + None)) + elif k == 'vlan-stats': + self.write_file('/sys/class/net/%s/bridge/' + 'vlan_stats_enabled' % bridgename, v) + elif k == 'mcstats': + self.write_file('/sys/class/net/%s/bridge/' + 'multicast_stats_enabled' % bridgename, v) + else: + if not LinkUtils.bridge_utils_is_installed: + continue + cmd = ('%s set%s %s %s' % + (utils.brctl_cmd, k, bridgename, v)) + utils.exec_command(cmd) + except Exception, e: + self.logger.warn('%s: %s' % (bridgename, str(e))) + pass + + def set_bridge_attr(self, bridgename, attrname, attrval): + if self._link_cache_check([bridgename, 'linkinfo', attrname], attrval): + return + if attrname == 'igmp-version': + self.write_file('/sys/class/net/%s/bridge/multicast_igmp_version' + % bridgename, attrval) + elif attrname == 'mld-version': + self.write_file('/sys/class/net/%s/bridge/multicast_mld_version' + % bridgename, attrval) + elif attrname == 'vlan-protocol': + self.write_file('/sys/class/net/%s/bridge/vlan_protocol' + % bridgename, VlanProtocols.ETHERTYPES_TO_ID[attrval.upper()]) + elif attrname == 'vlan-stats': + self.write_file('/sys/class/net/%s/bridge/vlan_stats_enabled' + % bridgename, attrval) + elif attrname == 'mcstats': + self.write_file('/sys/class/net/%s/bridge/multicast_stats_enabled' + % bridgename, attrval) + else: + if not LinkUtils.bridge_utils_is_installed: + return + cmd = '%s set%s %s %s' % (utils.brctl_cmd, + attrname, bridgename, attrval) + utils.exec_command(cmd) + + def get_bridge_attrs(self, bridgename): + attrs = self._link_cache_get([bridgename, 'linkinfo']) + no_ints_attrs = {} + for key, value in attrs.items(): + if type(key) == str: + no_ints_attrs[key] = value + return no_ints_attrs + + def get_bridgeport_attrs(self, bridgename, bridgeportname): + return self._link_cache_get([bridgename, 'linkinfo', 'ports', + bridgeportname]) + + def get_bridgeport_attr(self, bridgename, bridgeportname, attrname): + return self._link_cache_get([bridgename, 'linkinfo', 'ports', + bridgeportname, attrname]) + + @staticmethod + def bridge_set_stp(bridge, stp_state): + if not LinkUtils.bridge_utils_is_installed: + return + utils.exec_command('%s stp %s %s' % (utils.brctl_cmd, bridge, stp_state)) + + def bridge_get_stp(self, bridge): + sysfs_stpstate = '/sys/class/net/%s/bridge/stp_state' % bridge + if not os.path.exists(sysfs_stpstate): + return 'error' + stpstate = self.read_file_oneline(sysfs_stpstate) + if not stpstate: + return 'error' + try: + if int(stpstate) > 0: + return 'yes' + elif int(stpstate) == 0: + return 'no' + except: + return 'unknown' + + @staticmethod + def _conv_value_to_user(s): + try: + ret = int(s) / 100 + return '%d' % ret + except: + return None + + def read_value_from_sysfs(self, filename, preprocess_func): + value = self.read_file_oneline(filename) + if not value: + return None + return preprocess_func(value) + + @staticmethod + def bridge_set_ageing(bridge, ageing): + if not LinkUtils.bridge_utils_is_installed: + return + utils.exec_command('%s setageing %s %s' % (utils.brctl_cmd, bridge, ageing)) + + def bridge_get_ageing(self, bridge): + return self.read_value_from_sysfs('/sys/class/net/%s/bridge/ageing_time' + % bridge, self._conv_value_to_user) + + @staticmethod + def set_bridgeprio(bridge, prio): + if not LinkUtils.bridge_utils_is_installed: + return + utils.exec_command('%s setbridgeprio %s %s' % (utils.brctl_cmd, bridge, prio)) + + def get_bridgeprio(self, bridge): + return self.read_file_oneline( + '/sys/class/net/%s/bridge/priority' % bridge) + + @staticmethod + def bridge_set_fd(bridge, fd): + if not LinkUtils.bridge_utils_is_installed: + return + utils.exec_command('%s setfd %s %s' % (utils.brctl_cmd, bridge, fd)) + + def bridge_get_fd(self, bridge): + return self.read_value_from_sysfs( + '/sys/class/net/%s/bridge/forward_delay' + % bridge, self._conv_value_to_user) + + def bridge_set_gcint(self, bridge, gcint): + raise Exception('set_gcint not implemented') + + @staticmethod + def bridge_set_hello(bridge, hello): + if not LinkUtils.bridge_utils_is_installed: + return + utils.exec_command('%s sethello %s %s' % (utils.brctl_cmd, bridge, hello)) + + def bridge_get_hello(self, bridge): + return self.read_value_from_sysfs('/sys/class/net/%s/bridge/hello_time' + % bridge, self._conv_value_to_user) + + @staticmethod + def bridge_set_maxage(bridge, maxage): + if not LinkUtils.bridge_utils_is_installed: + return + utils.exec_command('%s setmaxage %s %s' % (utils.brctl_cmd, bridge, maxage)) + + def bridge_get_maxage(self, bridge): + return self.read_value_from_sysfs('/sys/class/net/%s/bridge/max_age' + % bridge, self._conv_value_to_user) + + @staticmethod + def bridge_set_pathcost(bridge, port, pathcost): + if not LinkUtils.bridge_utils_is_installed: + return + utils.exec_command('%s setpathcost %s %s %s' % (utils.brctl_cmd, bridge, port, pathcost)) + + def bridge_get_pathcost(self, bridge, port): + return self.read_file_oneline('/sys/class/net/%s/brport/path_cost' + % port) + + @staticmethod + def bridge_set_portprio(bridge, port, prio): + if not LinkUtils.bridge_utils_is_installed: + return + utils.exec_command('%s setportprio %s %s %s' % (utils.brctl_cmd, bridge, port, prio)) + + def bridge_get_portprio(self, bridge, port): + return self.read_file_oneline('/sys/class/net/%s/brport/priority' + % port) + + @staticmethod + def bridge_set_hashmax(bridge, hashmax): + if not LinkUtils.bridge_utils_is_installed: + return + utils.exec_command('%s sethashmax %s %s' % (utils.brctl_cmd, bridge, hashmax)) + + def bridge_get_hashmax(self, bridge): + return self.read_file_oneline('/sys/class/net/%s/bridge/hash_max' + % bridge) + + @staticmethod + def bridge_set_hashel(bridge, hashel): + if not LinkUtils.bridge_utils_is_installed: + return + utils.exec_command('%s sethashel %s %s' % (utils.brctl_cmd, bridge, hashel)) + + def bridge_get_hashel(self, bridge): + return self.read_file_oneline('/sys/class/net/%s/bridge/hash_elasticity' + % bridge) + + @staticmethod + def bridge_set_mclmc(bridge, mclmc): + if not LinkUtils.bridge_utils_is_installed: + return + utils.exec_command('%s setmclmc %s %s' % (utils.brctl_cmd, bridge, mclmc)) + + def bridge_get_mclmc(self, bridge): + return self.read_file_oneline( + '/sys/class/net/%s/bridge/multicast_last_member_count' + % bridge) + + @staticmethod + def bridge_set_mcrouter(bridge, mcrouter): + if not LinkUtils.bridge_utils_is_installed: + return + utils.exec_command('%s setmcrouter %s %s' % (utils.brctl_cmd, bridge, mcrouter)) + + def bridge_get_mcrouter(self, bridge): + return self.read_file_oneline( + '/sys/class/net/%s/bridge/multicast_router' % bridge) + + @staticmethod + def bridge_set_mcsnoop(bridge, mcsnoop): + if not LinkUtils.bridge_utils_is_installed: + return + utils.exec_command('%s setmcsnoop %s %s' % (utils.brctl_cmd, bridge, mcsnoop)) + + def bridge_get_mcsnoop(self, bridge): + return self.read_file_oneline( + '/sys/class/net/%s/bridge/multicast_snooping' % bridge) + + @staticmethod + def bridge_set_mcsqc(bridge, mcsqc): + if not LinkUtils.bridge_utils_is_installed: + return + utils.exec_command('%s setmcsqc %s %s' % (utils.brctl_cmd, bridge, mcsqc)) + + def bridge_get_mcsqc(self, bridge): + return self.read_file_oneline( + '/sys/class/net/%s/bridge/multicast_startup_query_count' + % bridge) + + @staticmethod + def bridge_set_mcqifaddr(bridge, mcqifaddr): + if not LinkUtils.bridge_utils_is_installed: + return + utils.exec_command('%s setmcqifaddr %s %s' % (utils.brctl_cmd, bridge, mcqifaddr)) + + def bridge_get_mcqifaddr(self, bridge): + return self.read_file_oneline( + '/sys/class/net/%s/bridge/multicast_startup_query_use_ifaddr' + % bridge) + + @staticmethod + def bridge_set_mcquerier(bridge, mcquerier): + if not LinkUtils.bridge_utils_is_installed: + return + utils.exec_command('%s setmcquerier %s %s' % (utils.brctl_cmd, bridge, mcquerier)) + + def bridge_get_mcquerier(self, bridge): + return self.read_file_oneline( + '/sys/class/net/%s/bridge/multicast_querier' % bridge) + + def bridge_set_mcqv4src(self, bridge, vlan, mcquerier): + try: + vlan = int(vlan) + except: + self.logger.info('%s: set mcqv4src vlan: invalid parameter %s: %s' %(bridge, vlan, str(e))) + return + if vlan == 0 or vlan > 4095: + self.logger.warn('mcqv4src vlan \'%d\' invalid range' % vlan) + return + + ip = mcquerier.split('.') + if len(ip) != 4: + self.logger.warn('mcqv4src \'%s\' invalid IPv4 address' % mcquerier) + return + for k in ip: + if not k.isdigit() or int(k, 10) < 0 or int(k, 10) > 255: + self.logger.warn('mcqv4src \'%s\' invalid IPv4 address' % mcquerier) + return + + if not LinkUtils.bridge_utils_is_installed: + return + + utils.exec_command('%s setmcqv4src %s %d %s' % + (utils.brctl_cmd, bridge, vlan, mcquerier)) + + def bridge_del_mcqv4src(self, bridge, vlan): + if not LinkUtils.bridge_utils_is_installed: + return + try: + vlan = int(vlan) + except: + self.logger.info('%s: del mcqv4src vlan: invalid parameter %s: %s' %(bridge, vlan, str(e))) + return + utils.exec_command('%s delmcqv4src %s %d' % (utils.brctl_cmd, bridge, vlan)) + + def bridge_get_mcqv4src(self, bridge, vlan=None): + if not LinkUtils.bridge_utils_is_installed: + return {} + if not self.supported_command['showmcqv4src']: + return {} + mcqv4src = {} + try: + mcqout = utils.exec_command('%s showmcqv4src %s' % + (utils.brctl_cmd, bridge)) + except Exception as e: + s = str(e).lower() + if 'never heard' in s: + msg = ('%s showmcqv4src: skipping unsupported command' + % utils.brctl_cmd) + self.logger.info(msg) + self.supported_command['showmcqv4src'] = False + return {} + raise + if not mcqout: + return {} + mcqlines = mcqout.splitlines() + for l in mcqlines[1:]: + l = l.strip() + k, d, v = l.split('\t') + if not k or not v: + continue + mcqv4src[k] = v + if vlan: + return mcqv4src.get(vlan) + return mcqv4src + + @staticmethod + def bridge_set_mclmi(bridge, mclmi): + if not LinkUtils.bridge_utils_is_installed: + return + utils.exec_command('%s setmclmi %s %s' % (utils.brctl_cmd, bridge, mclmi)) + + def bridge_get_mclmi(self, bridge): + return self.read_file_oneline( + '/sys/class/net/%s/bridge/multicast_last_member_interval' + % bridge) + + @staticmethod + def bridge_set_mcmi(bridge, mcmi): + if not LinkUtils.bridge_utils_is_installed: + return + utils.exec_command('%s setmcmi %s %s' % (utils.brctl_cmd, bridge, mcmi)) + + def bridge_get_mcmi(self, bridge): + return self.read_file_oneline( + '/sys/class/net/%s/bridge/multicast_membership_interval' + % bridge) + + @staticmethod + def bridge_exists(bridge): + return os.path.exists('/sys/class/net/%s/bridge' % bridge) + + @staticmethod + def is_bridge_port(ifacename): + return os.path.exists('/sys/class/net/%s/brport' % ifacename) + + @staticmethod + def bridge_port_exists(bridge, bridgeportname): + try: + return os.path.exists('/sys/class/net/%s/brif/%s' % (bridge, bridgeportname)) + except: + return False + + @staticmethod + def get_bridge_ports(bridgename): + try: + return os.listdir('/sys/class/net/%s/brif/' % bridgename) + except: + return [] diff --git a/ifupdown2/ifupdownaddons/__init__.py b/ifupdown2/ifupdownaddons/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ifupdownaddons/cache.py b/ifupdown2/ifupdownaddons/cache.py similarity index 95% rename from ifupdownaddons/cache.py rename to ifupdown2/ifupdownaddons/cache.py index 35da9f6..b74f327 100644 --- a/ifupdownaddons/cache.py +++ b/ifupdown2/ifupdownaddons/cache.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. # Author: Roopa Prabhu, roopa@cumulusnetworks.com # @@ -30,8 +30,6 @@ class linkCache(): """ This class contains methods and instance variables to cache link info """ - _shared_state = {} - """ { : { 'ifindex': , 'mtu': , 'state' : ', @@ -98,6 +96,11 @@ class linkCache(): def invalidate(cls): cls.links = {} + @classmethod + def reset(cls): + cls.invalidate() + cls.vrfs = {} + @classmethod def dump(cls): print 'Dumping link cache' diff --git a/ifupdownaddons/dhclient.py b/ifupdown2/ifupdownaddons/dhclient.py similarity index 93% rename from ifupdownaddons/dhclient.py rename to ifupdown2/ifupdownaddons/dhclient.py index b982d85..d8f8924 100644 --- a/ifupdownaddons/dhclient.py +++ b/ifupdown2/ifupdownaddons/dhclient.py @@ -1,13 +1,18 @@ #!/usr/bin/python # -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. # Author: Roopa Prabhu, roopa@cumulusnetworks.com # -from ifupdown.utils import utils -from utilsbase import * import os +try: + from ifupdown2.ifupdown.utils import utils + from ifupdown2.ifupdownaddons.utilsbase import * +except ImportError: + from ifupdown.utils import utils + from ifupdownaddons.utilsbase import * + class dhclient(utilsBase): """ This class contains helper methods to interact with the dhclient diff --git a/ifupdownaddons/modulebase.py b/ifupdown2/ifupdownaddons/modulebase.py similarity index 72% rename from ifupdownaddons/modulebase.py rename to ifupdown2/ifupdownaddons/modulebase.py index bcbb865..d0169f3 100644 --- a/ifupdownaddons/modulebase.py +++ b/ifupdown2/ifupdownaddons/modulebase.py @@ -1,19 +1,29 @@ #!/usr/bin/python # -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. # Author: Roopa Prabhu, roopa@cumulusnetworks.com # import os import re -import io import logging import traceback -from ifupdown.utils import utils -from ifupdown.iface import * -import ifupdown.policymanager as policymanager -import ifupdown.ifupdownflags as ifupdownflags +try: + from ifupdown2.ifupdown.iface import * + from ifupdown2.ifupdown.utils import utils + + import ifupdown2.ifupdown.exceptions as exceptions + import ifupdown2.ifupdown.policymanager as policymanager + import ifupdown2.ifupdown.ifupdownflags as ifupdownflags +except ImportError: + from ifupdown.iface import * + from ifupdown.utils import utils + + import ifupdown.exceptions as exceptions + import ifupdown.policymanager as policymanager + import ifupdown.ifupdownflags as ifupdownflags + class NotSupported(Exception): pass @@ -24,8 +34,8 @@ class moduleBase(object): Provides common infrastructure methods for all addon modules """ def __init__(self, *args, **kargs): - modulename = self.__class__.__name__ - self.logger = logging.getLogger('ifupdown.' + modulename) + self.modulename = self.__class__.__name__ + self.logger = logging.getLogger('ifupdown.' + self.modulename) # vrfs are a global concept and a vrf context can be applicable # to all global vrf commands. Get the default vrf-exec-cmd-prefix @@ -39,12 +49,52 @@ class moduleBase(object): self._bridge_stp_user_space = None + self.merge_modinfo_with_policy_files() + + def merge_modinfo_with_policy_files(self): + """ + update addons modinfo dictionary with system/user defined values in policy files + Any value can be updated except the module help "mhelp" + + We also check if the policy attributes really exist to make sure someone is not + trying to "inject" new attributes to prevent breakages and security issue + """ + attrs = dict(self.get_modinfo().get('attrs', {})) + + if not attrs: + return + + error_msg = 'this attribute doesn\'t exist or isn\'t supported' + + # first check module_defaults + for key, value in policymanager.policymanager_api.get_module_defaults(self.modulename).items(): + if not key in attrs: + self.logger.warning('%s: %s: %s' % (self.modulename, key, error_msg)) + continue + attrs[key]['default'] = value + + # then check module_globals (overrides module_defaults) + policy_modinfo = policymanager.policymanager_api.get_module_globals(self.modulename, '_modinfo') + if policy_modinfo: + policy_attrs = policy_modinfo.get('attrs', {}) + update_attrs = dict() + + for attr_name, attr_description in policy_attrs.items(): + if attr_name not in attrs: + self.logger.warning('%s: %s: %s' % (self.modulename, attr_name, error_msg)) + else: + update_attrs[attr_name] = attr_description + + attrs.update(update_attrs) + + return attrs def log_warn(self, str, ifaceobj=None): """ log a warning if err str is not one of which we should ignore """ if not self.ignore_error(str) and not ifupdownflags.flags.IGNORE_ERRORS: if self.logger.getEffectiveLevel() == logging.DEBUG: traceback.print_stack() + traceback.print_exc() self.logger.warn(str) if ifaceobj: ifaceobj.set_status(ifaceStatus.WARNING) @@ -55,6 +105,7 @@ class moduleBase(object): if not self.ignore_error(str) and not ifupdownflags.flags.IGNORE_ERRORS: if self.logger.getEffectiveLevel() == logging.DEBUG: traceback.print_stack() + traceback.print_exc() if raise_error: if ifaceobj: ifaceobj.set_status(ifaceStatus.ERROR) @@ -66,7 +117,8 @@ class moduleBase(object): def is_process_running(self, procName): try: - utils.exec_command('/bin/pidof -x %s' % procName) + utils.exec_command('%s -x %s' % + (utils.pidof_cmd, procName)) except: return False else: @@ -120,6 +172,12 @@ class moduleBase(object): errmsg = ('error parsing glob expression \'%s\'' %expr + ' (supported glob syntax: swp1-10.300 or swp[1-10].300' + ' or swp[1-10]sub[0-4].300') + + if ',' in expr: + self.logger.warn('%s: comma are not supported in glob: %s' % (ifacename, errmsg)) + yield expr + return + regexs = self.glob_regexs if regexs[0].match(expr): @@ -249,11 +307,13 @@ class moduleBase(object): def sysctl_set(self, variable, value): """ set sysctl variable to value passed as argument """ - utils.exec_command('sysctl %s=%s' % (variable, value)) + utils.exec_command('%s %s=%s' % + (utils.sysctl_cmd, variable, value)) def sysctl_get(self, variable): """ get value of sysctl variable """ - output = utils.exec_command('sysctl %s' % variable) + output = utils.exec_command('%s %s' % + (utils.sysctl_cmd, variable)) split = output.split('=') if len(split) > 1: return split[1].strip() @@ -296,7 +356,7 @@ class moduleBase(object): ifaceobjcurr.update_config_with_status(attr_name, runningattrvalue, 0) - def dict_key_subset(self, a, b): + def dict_key_subset(self, a, b): """ returns a list of differing keys """ return [x for x in a if x in b] @@ -337,7 +397,10 @@ class moduleBase(object): try: return self._modinfo except: - return None + return {} + + def get_attr_default_value(self, attrname): + return self.get_modinfo().get('attrs', {}).get(attrname, {}).get('default') def get_overrides_ifupdown_scripts(self): """ return the ifupdown scripts replaced by the current module """ @@ -361,19 +424,66 @@ class moduleBase(object): pass return (start, end) - def _handle_reserved_vlan(self, vlanid, logprefix=''): + def _handle_reserved_vlan(self, vlanid, logprefix='', end=-1): """ Helper function to check and warn if the vlanid falls in the reserved vlan range """ - if vlanid in range(self._resv_vlan_range[0], - self._resv_vlan_range[1]): - self.logger.error('%s: reserved vlan %d being used' - %(logprefix, vlanid) + ' (reserved vlan range %d-%d)' - %(self._resv_vlan_range[0], self._resv_vlan_range[1])) - return True - return False + error = False + invalid_vlan = vlanid + + if self._resv_vlan_range[0] <= vlanid <= self._resv_vlan_range[1]: + error = True + elif end > 0: + if self._resv_vlan_range[0] <= end <= self._resv_vlan_range[1]: + error = True + invalid_vlan = end + elif vlanid < self._resv_vlan_range[0] and end > self._resv_vlan_range[1]: + error = True + invalid_vlan = self._resv_vlan_range[0] + + if error: + raise exceptions.ReservedVlanException('%s: reserved vlan %d being used (reserved vlan range %d-%d)' + % (logprefix, invalid_vlan, self._resv_vlan_range[0], self._resv_vlan_range[1])) + + return error def _valid_ethaddr(self, ethaddr): """ Check if address is 00:00:00:00:00:00 """ if not ethaddr or re.match('00:00:00:00:00:00', ethaddr): return False return True + + def _get_vlan_id_from_ifacename(self, ifacename): + if '.' in ifacename: + vid_str = ifacename.split('.', 2) + vlen = len(vid_str) + if vlen == 2: + vid_str = vid_str[1] + elif vlen == 3: + vid_str = vid_str[2] + elif ifacename.startswith('vlan'): + vid_str = ifacename[4:] + else: + return -1 + try: + vid = int(vid_str) + except: + return -1 + return vid + + def _get_vlan_id(self, ifaceobj): + """ Derives vlanid from iface name + + Example: + Returns 1 for ifname vlan0001 returns 1 + Returns 1 for ifname vlan1 + Returns 1 for ifname eth0.1 + Returns 100 for ifname eth0.1.100 + Returns -1 if vlan id cannot be determined + """ + vid_str = ifaceobj.get_attr_value_first('vlan-id') + try: + if vid_str: return int(vid_str) + except: + return -1 + + return self._get_vlan_id_from_ifacename(ifaceobj.name) diff --git a/ifupdownaddons/mstpctlutil.py b/ifupdown2/ifupdownaddons/mstpctlutil.py similarity index 83% rename from ifupdownaddons/mstpctlutil.py rename to ifupdown2/ifupdownaddons/mstpctlutil.py index 1452bd4..e601e12 100644 --- a/ifupdownaddons/mstpctlutil.py +++ b/ifupdown2/ifupdownaddons/mstpctlutil.py @@ -1,15 +1,22 @@ #!/usr/bin/python # -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. # Author: Roopa Prabhu, roopa@cumulusnetworks.com # -from cache import MSTPAttrsCache -from utilsbase import * -from ifupdown.iface import * -from ifupdown.utils import utils -from cache import * -import json +try: + from ifupdown2.ifupdown.iface import * + from ifupdown2.ifupdown.utils import utils + + from ifupdown2.ifupdownaddons.cache import * + from ifupdown2.ifupdownaddons.utilsbase import * +except ImportError: + from ifupdown.iface import * + from ifupdown.utils import utils + + from ifupdownaddons.cache import * + from ifupdownaddons.utilsbase import * + class mstpctlutil(utilsBase): """ This class contains helper methods to interact with mstpd using @@ -53,9 +60,13 @@ class mstpctlutil(utilsBase): def __init__(self, *args, **kargs): utilsBase.__init__(self, *args, **kargs) + @classmethod + def reset(cls): + cls._cache_fill_done = False + def is_mstpd_running(self): try: - utils.exec_command('/bin/pidof mstpd') + utils.exec_command('%s mstpd'%utils.pidof_cmd) except: return False else: @@ -73,7 +84,8 @@ class mstpctlutil(utilsBase): return attrs mstpctl_bridgeport_attrs_dict = {} try: - cmd = ['/sbin/mstpctl', 'showportdetail', bridgename, 'json'] + cmd = [utils.mstpctl_cmd, + 'showportdetail', bridgename, 'json'] output = utils.exec_commandl(cmd) if not output: return mstpctl_bridgeport_attrs_dict @@ -95,7 +107,8 @@ class mstpctlutil(utilsBase): mstpctl_bridge_attrs_dict = {} try: - cmd = ['/sbin/mstpctl', 'showbridge', 'json', bridgename] + cmd = [utils.mstpctl_cmd, + 'showbridge', 'json', bridgename] output = utils.exec_commandl(cmd) if not output: return mstpctl_bridge_attrs_dict @@ -147,10 +160,10 @@ class mstpctlutil(utilsBase): if cache_value and cache_value == value: return if attrname == 'treeportcost' or attrname == 'treeportprio': - utils.exec_commandl(['/sbin/mstpctl', 'set%s' % attrname, + utils.exec_commandl([utils.mstpctl_cmd, 'set%s' % attrname, bridgename, portname, '0', value]) else: - utils.exec_commandl(['/sbin/mstpctl', 'set%s' % attrname, + utils.exec_commandl([utils.mstpctl_cmd, 'set%s' % attrname, bridgename, portname, value]) if json_attr: self.update_bridge_port_cache(bridgename, portname, json_attr, value) @@ -182,14 +195,12 @@ class mstpctlutil(utilsBase): if attrvalue_curr and attrvalue_curr == attrvalue: return if attrname == 'treeprio': - utils.exec_commandl(['/sbin/mstpctl', 'set%s' % attrname, - '%s' % bridgename, '0', '%s' % attrvalue], - stdout=False, stderr=None) + utils.exec_commandl([utils.mstpctl_cmd, 'set%s' % attrname, + '%s' % bridgename, '0', '%s' % attrvalue], stderr=None) self.update_bridge_cache(bridgename, attrname, str(attrvalue)) else: - utils.exec_commandl(['/sbin/mstpctl', 'set%s' % attrname, - '%s' % bridgename, '%s' % attrvalue], - stdout=False, stderr=None) + utils.exec_commandl([utils.mstpctl_cmd, 'set%s' % attrname, + '%s' % bridgename, '%s' % attrvalue], stderr=None) self.update_bridge_cache(bridgename, self._bridge_jsonAttr_map[attrname], str(attrvalue)) @@ -211,22 +222,26 @@ class mstpctlutil(utilsBase): attrvalue_curr = self.get_bridge_treeprio(bridgename) if attrvalue_curr and attrvalue_curr == attrvalue: return - utils.exec_commandl(['/sbin/mstpctl', 'settreeprio', bridgename, '0', + utils.exec_commandl([utils.mstpctl_cmd, + 'settreeprio', bridgename, '0', str(attrvalue)]) self.update_bridge_cache(bridgename, 'treeprio', str(attrvalue)) def showbridge(self, bridgename=None): if bridgename: - return utils.exec_command('/sbin/mstpctl showbridge %s' % bridgename) + return utils.exec_command('%s showbridge %s' % + (utils.mstpctl_cmd, bridgename)) else: - return utils.exec_command('/sbin/mstpctl showbridge') + return utils.exec_command('%s showbridge' %utils.mstpctl_cmd) def showportdetail(self, bridgename): - return utils.exec_command('/sbin/mstpctl showportdetail %s' % bridgename) + return utils.exec_command('%s showportdetail %s' % + (utils.mstpctl_cmd, bridgename)) def mstpbridge_exists(self, bridgename): try: - utils.exec_command('mstpctl showbridge %s' % bridgename, stdout=False) + utils.exec_command('%s showbridge %s' % + (utils.mstpctl_cmd, bridgename)) return True except: return False diff --git a/ifupdownaddons/systemutils.py b/ifupdown2/ifupdownaddons/systemutils.py similarity index 65% rename from ifupdownaddons/systemutils.py rename to ifupdown2/ifupdownaddons/systemutils.py index a6aad67..5e2812a 100644 --- a/ifupdownaddons/systemutils.py +++ b/ifupdown2/ifupdownaddons/systemutils.py @@ -1,12 +1,18 @@ #!/usr/bin/python # -# Copyright 2015 Cumulus Networks, Inc. All rights reserved. +# Copyright 2015-2017 Cumulus Networks, Inc. All rights reserved. # Author: Roopa Prabhu, roopa@cumulusnetworks.com # import os -from utilsbase import * -from ifupdown.utils import utils + +try: + from ifupdown2.ifupdown.utils import utils + from ifupdown2.ifupdownaddons.utilsbase import * +except ImportError: + from ifupdown.utils import utils + from ifupdownaddons.utilsbase import * + class systemUtils(): @classmethod @@ -23,7 +29,8 @@ class systemUtils(): if procname: try: - utils.exec_command('/bin/pidof %s' % procname, stdout=False) + utils.exec_command('%s %s' % + (utils.pidof_cmd, procname)) except: return False else: @@ -36,8 +43,8 @@ class systemUtils(): if not servicename: return False try: - utils.exec_commandl(['/usr/sbin/service', servicename, 'status'], - stdout=False) + utils.exec_commandl([utils.service_cmd, + servicename, 'status']) except Exception: # XXX: check for subprocess errors vs os error return False @@ -48,7 +55,8 @@ class systemUtils(): if not processname: return False try: - utils.exec_command('/bin/pidof %s' % processname, stdout=False) + utils.exec_command('%s %s' % + (utils.pidof_cmd, processname)) except: return False else: diff --git a/ifupdownaddons/utilsbase.py b/ifupdown2/ifupdownaddons/utilsbase.py similarity index 69% rename from ifupdownaddons/utilsbase.py rename to ifupdown2/ifupdownaddons/utilsbase.py index 477fcc8..f467087 100644 --- a/ifupdownaddons/utilsbase.py +++ b/ifupdown2/ifupdownaddons/utilsbase.py @@ -1,20 +1,26 @@ #!/usr/bin/python # -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. # Author: Roopa Prabhu, roopa@cumulusnetworks.com # -import logging -import re -import io - -from ifupdown.utils import utils -import ifupdown.ifupdownflags as ifupdownflags -from ifupdown.iface import * -from cache import * import time import logging +try: + from ifupdown2.ifupdown.iface import * + from ifupdown2.ifupdown.utils import utils + from ifupdown2.ifupdownaddons.cache import * + + import ifupdown2.ifupdown.ifupdownflags as ifupdownflags +except ImportError: + from ifupdown.iface import * + from ifupdown.utils import utils + from ifupdownaddons.cache import * + + import ifupdown.ifupdownflags as ifupdownflags + + def profile(func): def wrap(*args, **kwargs): started_at = time.time() @@ -64,7 +70,10 @@ class utilsBase(object): return None def sysctl_set(self, variable, value): - utils.exec_command('sysctl %s=%s' % (variable, value)) + utils.exec_command('%s %s=%s' % + (utils.sysctl_cmd, variable, value)) def sysctl_get(self, variable): - return utils.exec_command('sysctl %s' % variable).split('=')[1].strip() + return utils.exec_command('%s %s' % + (utils.sysctl_cmd, + variable)).split('=')[1].strip() diff --git a/scripts/genmanpages.sh b/ifupdown2/man/genmanpages.sh similarity index 100% rename from scripts/genmanpages.sh rename to ifupdown2/man/genmanpages.sh diff --git a/man.rst/ifup.8.rst b/ifupdown2/man/ifdown.8.rst similarity index 100% rename from man.rst/ifup.8.rst rename to ifupdown2/man/ifdown.8.rst diff --git a/man.rst/ifquery.8.rst b/ifupdown2/man/ifquery.8.rst similarity index 100% rename from man.rst/ifquery.8.rst rename to ifupdown2/man/ifquery.8.rst diff --git a/man.rst/ifreload.8.rst b/ifupdown2/man/ifreload.8.rst similarity index 91% rename from man.rst/ifreload.8.rst rename to ifupdown2/man/ifreload.8.rst index e5c2f9d..ccb1806 100644 --- a/man.rst/ifreload.8.rst +++ b/ifupdown2/man/ifreload.8.rst @@ -54,10 +54,10 @@ OPTIONS -f, --force force run all operations - -c, --currently-up only reload auto and other interfaces that are - currently up. This can be used as a non-disruptive - alternative to -a because it will not down any - interfaces + -c, --currently-up Reload the configuration for all interfaces which + are currently up regardless of whether an interface + has "auto " configuration within the + /etc/network/interfaces file. -X EXCLUDEPATS, --exclude EXCLUDEPATS Exclude interfaces from the list of interfaces to diff --git a/ifupdown2/man/ifup.8.rst b/ifupdown2/man/ifup.8.rst new file mode 100644 index 0000000..6fc202c --- /dev/null +++ b/ifupdown2/man/ifup.8.rst @@ -0,0 +1,183 @@ +==== +ifup +==== + +------------------------------------- +network interface management commands +------------------------------------- + +:Author: Roopa Prabhu +:Date: 2014-02-05 +:Copyright: Copyright 2014 Cumulus Networks, Inc. All rights reserved. +:Version: 0.1 +:Manual section: 8 + +NAME +==== + **ifup** - bring a network interface up + + **ifdown** - take a network interface down + +SYNOPSIS +======== + + ifup [-h] [-a] [-v] [-d] [--allow CLASS] [--with-depends] + **[-X EXCLUDEPATS] [-f] [-n] [-s] [--print-dependency {list,dot}]** + **[IFACE [IFACE ...]]** + + ifdown [-h] [-a] [-v] [-d] [--allow CLASS] [--with-depends] + **[-X EXCLUDEPATS] [-f] [-n] [--print-dependency {list,dot}]** + **[IFACE [IFACE ...]]** + +DESCRIPTION +=========== + **ifup** and **ifdown** commands can be used to configure (or, respectively, + deconfigure) network interfaces based on interface definitions in the + config file ifupdown2.conf (defaults to **/etc/network/interfaces/** file). + + **ifquery(8)** maybe used in conjunction with **ifup** and **ifdown** + commands to query and validate applied/running configuration. + + **ifup** always works on the current **interfaces(5)** file defined in ifupdown2.conf + (default **/etc/network/interfaces**). **ifdown** works on the last applied interface + configuration. + + **ifup** on an already ifup'ed interface will re-apply the configuration, + skipping already applied configuration wherever possible. In many cases + where config commands are idempotent, you will see that ifup/ifdown will + reapply the config even if the interface already has that config. + + **ifup** and **ifdown** understands interface dependency order. + + For logical interfaces like vlans, bridges, bonds, **ifup** creates the + interface and **ifdown** deletes the interface. Use **--admin-state** + option if you only want to administratively bring the interface up/down. + + When **ifup** and **ifdown** are used with interfaces on command line, + they must be have a **iface** section in the **interfaces(5)** file. + +OPTIONS +======= + positional arguments: + + **IFACE** interface list separated by spaces. **IFACE** list and **'-a'** + argument are mutually exclusive. + + optional arguments: + + -h, --help show this help message and exit + + -a, --all process all interfaces marked "auto" + + -v, --verbose verbose + + -d, --debug output debug info + --allow CLASS ignore non-"allow-CLASS" interfaces + + -w, --with-depends run with all dependent interfaces. This option + is redundant when -a is specified. When '-a' is + specified, interfaces are always executed in + dependency order. + + -X EXCLUDEPATS, --exclude EXCLUDEPATS + Exclude interfaces from the list of interfaces to + operate on. Can be specified multiple times + If the excluded interface has dependent interfaces, + (e.g. a bridge or a bond with multiple enslaved interfaces) + then each dependent interface must be specified in order + to be excluded. + + -i INTERFACESFILE, --interfaces INTERFACESFILE + Uses interfaces file instead of default defined in + ifupdown2.conf (default /etc/network/interfaces). + Also in ifupdown2.conf, users are not allowed to specify their own + interfaces file unless disable_cli_interfacesfile is set to 0 + (default is 1). + + -t {native,json}, --interfaces-format {native,json} + interfaces file format + + -f, --force force run all operations + + -n, --no-act print out what would happen, but don't do it + + -p, --print-dependency {list,dot} + print iface dependency in list or dot format + + -m, --admin-state, --no-scripts + don't run any addon modules/scripts. Only bring + the interface administratively up/down + + -u, --use-current-config + By default ifdown looks at the saved state for + interfaces to bring down. This option allows ifdown + to look at the current interfaces file. Useful when + your state file is corrupted or you want down to use + the latest from the interfaces file + + -s, --syntax-check Only run the interfaces file parser + +EXAMPLES +======== + # bringing up all interfaces + + **ifup -a** + + # bringing up interface list + + **ifup swp1 swp2** + + # bringing up interface with its dependents + + **ifup br0 --with-depends** + + # bringing down all interfaces + + **ifdown -a** + + # bringing down a single interface + + **ifdown swp1** + + # excluding interfaces using -X option + + **ifdown -X eth0 -a** + + **ifup -X eth0 -a** + + **ifdown -X eth0 -X lo -a** + + # using verbose -v option to see what is going on + + **ifup -v -a** + + # using debug -d option to see more of what is going on + + **ifup -d -a** + + # ignore errors + + **ifup -a -f** + + **ifdown -a -f** + + # run ifdown and ifup on all interfaces using service command/init script + + **service networking restart** + + # run ifup on all interfaces using service command/init script + + **service networking start** + + # ifdown on all interfaces using service command/init script + + **service networking stop** + + # To run ifup/ifdown on only interfaces that changed see **ifreload(8)** + +SEE ALSO +======== + ifquery(8), + ifreload(8), + interfaces(5), + ifupdown-addons-interfaces(5) diff --git a/man.rst/ifupdown-addons-interfaces.5.rst b/ifupdown2/man/ifupdown-addons-interfaces.5.rst similarity index 96% rename from man.rst/ifupdown-addons-interfaces.5.rst rename to ifupdown2/man/ifupdown-addons-interfaces.5.rst index 9f61a8a..1858dac 100644 --- a/man.rst/ifupdown-addons-interfaces.5.rst +++ b/ifupdown2/man/ifupdown-addons-interfaces.5.rst @@ -58,12 +58,12 @@ EXAMPLES **required**: False - **default**: off + **default**: no - **validvals**: on,off + **validvals**: yes,no **example**: - link-autoneg on + link-autoneg yes **link-speed** @@ -182,10 +182,12 @@ EXAMPLES **required**: False - **default**: 0 + **default**: no + + **validvals**: yes,no **example**: - bridge-mcquerier 0 + bridge-mcquerier no **bridge-mclmc** @@ -221,10 +223,12 @@ EXAMPLES **required**: False - **default**: 1 + **default**: yes + + **validvals**: yes,no **example**: - bridge-mcrouter 1 + bridge-mcrouter yes **bridge-stp** @@ -333,10 +337,12 @@ EXAMPLES **required**: False - **default**: 0 + **default**: no + + **validvals**: yes,no **example**: - bridge-mcqifaddr 0 + bridge-mcqifaddr no **bridge-waitport** @@ -441,10 +447,12 @@ EXAMPLES **required**: False - **default**: 1 + **default**: yes + + **validvals**: yes,no **example**: - bridge-mcsnoop 1 + bridge-mcsnoop yes **bridge-access** @@ -978,12 +986,12 @@ EXAMPLES **required**: False - **default**: 1 + **default**: yes - **validvals**: 0,1 + **validvals**: yes,no **example**: - bond-use-carrier 1 + bond-use-carrier yes **bond-lacp-bypass-period** @@ -1077,27 +1085,12 @@ EXAMPLES **required**: False - **default**: 0 - - **validvals**: 0,1 - - **example**: - bond-lacp-bypass-allow 0 - - - **bond-lacp-bypass-allow-all-active** - - **help**: allow all slaves to be active in lacp bypass irrespective of priority - - - **required**: False - - **default**: 0 + **default**: no - **validvals**: 0,1 + **validvals**: yes,no **example**: - bond-lacp-bypass-all-active 1 + bond-lacp-bypass-allow no **bond-mode** @@ -1323,15 +1316,17 @@ EXAMPLES **vxlan-learning** - **help**: vxlan learning on/off + **help**: vxlan learning yes/no **required**: False - **default**: on + **default**: yes + + **validvals**: yes,no **example**: - vxlan-learning off + vxlan-learning no **vxlan-id** diff --git a/man.rst/interfaces.5.rst b/ifupdown2/man/interfaces.5.rst similarity index 84% rename from man.rst/interfaces.5.rst rename to ifupdown2/man/interfaces.5.rst index 9247c98..262d726 100644 --- a/man.rst/interfaces.5.rst +++ b/ifupdown2/man/interfaces.5.rst @@ -10,7 +10,7 @@ network interface configuration for ifupdown :Date: 2014-02-05 :Copyright: Copyright 2014 Cumulus Networks, Inc. All rights reserved. :Version: 0.1 -:Manual section: 5 +:Manual section: 5 DESCRIPTION =========== @@ -26,7 +26,7 @@ DESCRIPTION A line may be extended across multiple lines by making the last character a backslash. - The file consists of zero or more "iface", "auto", "allow-" + The file consists of zero or more "iface", "auto", "allow-" and "source" stanzas. Here is an example:: auto lo eth0 @@ -45,23 +45,22 @@ DESCRIPTION Lines beginning with the word "auto" are used to identify the physical interfaces to be brought up when ifup is run with the -a option. (This option is used by the system boot scripts.) Physical interface names - should follow the word "auto" on the same line. There can be multiple - "auto" stanzas. + should follow the word "auto" on the same line. There can be multiple + "auto" stanzas. - Lines beginning with "allow-" are used to identify interfaces that - should be brought up automatically by various subsystems. This may be - done using a command such as "ifup --allow=hotplug eth0 eth1", which - will only bring up eth0 or eth1 if it is listed in an "allow-hotplug" + Lines beginning with "allow-" are used to identify interfaces that + should be brought up automatically by various subsystems. This may be + done using a command such as "ifup --allow=hotplug eth0 eth1", which + will only bring up eth0 or eth1 if it is listed in an "allow-hotplug" line. Note that "allow-auto" and "auto" are synonyms. - Lines beginning with "source" are used to include stanzas from other + Lines beginning with "source" are used to include stanzas from other files, so configuration can be split into many files. The word "source" - is followed by the path of file to be sourced. Shell wildcards can be - used. Currently only supports absolute - path names. + is followed by the path of the file to be sourced. Shell wildcards can + be used. Currently only supports absolute path names. iface is normally given a interface name as its first non-option - argument. + argument. The interface name is followed by the name of the address family that the interface uses. This will be "inet" for TCP/IP networking and inet6 for @@ -89,7 +88,7 @@ DESCRIPTION ifupdown supports python-mako style templates in the interfaces file. See examples section for details. - See **/usr/share/doc/python-ifupdown2/examples/** for **interfaces(5)** + See **/usr/share/doc/ifupdown2/examples/** for **interfaces(5)** file examples and interfaces file generation scripts. METHODS diff --git a/nlmanager/README b/ifupdown2/nlmanager/README similarity index 100% rename from nlmanager/README rename to ifupdown2/nlmanager/README diff --git a/nlmanager/__init__.py b/ifupdown2/nlmanager/__init__.py similarity index 100% rename from nlmanager/__init__.py rename to ifupdown2/nlmanager/__init__.py diff --git a/nlmanager/nllistener.py b/ifupdown2/nlmanager/nllistener.py similarity index 82% rename from nlmanager/nllistener.py rename to ifupdown2/nlmanager/nllistener.py index 46ffe7c..e9c877e 100644 --- a/nlmanager/nllistener.py +++ b/ifupdown2/nlmanager/nllistener.py @@ -1,4 +1,29 @@ #!/usr/bin/env python +# +# Copyright (C) 2015, 2017 Cumulus Networks, Inc. all rights reserved +# +# This program 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; version 2. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. +# +# https://www.gnu.org/licenses/gpl-2.0-standalone.html +# +# Authors: +# Daniel Walton, dwalton@cumulusnetworks.com +# Julien Fortin, julien@cumulusnetworks.com +# +# Netlink Listener -- +# from nlpacket import * from nlmanager import NetlinkManager @@ -8,13 +33,14 @@ from threading import Thread, Event, Lock from Queue import Queue import logging import socket +import errno log = logging.getLogger(__name__) class NetlinkListener(Thread): - def __init__(self, manager, groups, pid_offset=1): + def __init__(self, manager, groups, pid_offset=1, error_notification=False, rcvbuf_sz=10000000): """ groups controls what types of messages we are interested in hearing To get everything pass: @@ -29,10 +55,37 @@ class NetlinkListener(Thread): self.shutdown_event = Event() self.groups = groups self.pid_offset = pid_offset + self.rcvbuf_sz = rcvbuf_sz + + # if the app has requested for error notification socket errors will + # be sent via the SERVICE_ERROR event + self.error_notification = error_notification + + self.supported_messages = [RTM_NEWLINK, RTM_DELLINK, RTM_NEWADDR, + RTM_DELADDR, RTM_NEWNEIGH, RTM_DELNEIGH, + RTM_NEWROUTE, RTM_DELROUTE] + self.ignore_messages = [RTM_GETLINK, RTM_GETADDR, RTM_GETNEIGH, + RTM_GETROUTE, RTM_GETQDISC, NLMSG_ERROR, NLMSG_DONE] def __str__(self): return 'NetlinkListener' + def supported_messages_add(self, msgtype): + + if msgtype not in self.supported_messages: + self.supported_messages.append(msgtype) + + if msgtype in self.ignore_messages: + self.ignore_messages.remove(msgtype) + + def supported_messages_del(self, msgtype): + + if msgtype in self.supported_messages: + self.supported_messages.remove(msgtype) + + if msgtype not in self.ignore_messages: + self.ignore_messages.append(msgtype) + def run(self): manager = self.manager header_PACK = 'IHHII' @@ -45,12 +98,15 @@ class NetlinkListener(Thread): # use 2 in the upper space (top 10 bits) instead of 0 to avoid conflicts # with the netlink manager which always attempts to bind with the pid. self.rx_socket = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, 0) - self.rx_socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 10000000) + _SO_RCVBUFFORCE = socket.SO_RCVBUFFORCE if hasattr(socket, 'SO_RCVBUFFORCE') else 33 + self.rx_socket.setsockopt(socket.SOL_SOCKET, _SO_RCVBUFFORCE, self.rcvbuf_sz) self.rx_socket.bind((manager.pid | (self.pid_offset << 22), self.groups)) self.rx_socket_prev_seq = {} + manager.target_lock.acquire() if not manager.tx_socket: manager.tx_socket_allocate() + manager.target_lock.release() my_sockets = (manager.tx_socket, self.rx_socket) @@ -59,18 +115,11 @@ class NetlinkListener(Thread): self.rx_socket: "RX" } - supported_messages = (RTM_NEWLINK, RTM_DELLINK, RTM_NEWADDR, - RTM_DELADDR, RTM_NEWNEIGH, RTM_DELNEIGH, - RTM_NEWROUTE, RTM_DELROUTE) - - ignore_messages = (RTM_GETLINK, RTM_GETADDR, RTM_GETNEIGH, - RTM_GETROUTE, RTM_GETQDISC, NLMSG_ERROR, NLMSG_DONE) - while True: if self.shutdown_event.is_set(): log.info("%s: shutting down" % self) - return + break # Only block for 1 second so we can wake up to see if shutdown_event is set try: @@ -83,6 +132,7 @@ class NetlinkListener(Thread): continue set_alarm = False + set_overrun = False set_tx_socket_rxed_ack_alarm = False for s in readable: @@ -90,9 +140,14 @@ class NetlinkListener(Thread): try: data = s.recv(4096) + except socket.error, e: + log.error('recv() error: ' + str(e)) + data = [] + if e.errno is errno.ENOBUFS and self.error_notification: + set_overrun = True except Exception as e: log.error('recv() error: ' + str(e)) - continue + data = [] total_length = len(data) while data: @@ -125,13 +180,13 @@ class NetlinkListener(Thread): set_tx_socket_rxed_ack_alarm = True # Put the message on the manager's netlinkq - if msgtype in supported_messages: + if msgtype in self.supported_messages: set_alarm = True manager.netlinkq.append((msgtype, length, flags, seq, pid, data[0:length])) # There are certain message types we do not care about # (RTM_GETs for example) - elif msgtype in ignore_messages: + elif msgtype in self.ignore_messages: pass # And there are certain message types we have not added @@ -166,6 +221,11 @@ class NetlinkListener(Thread): if set_alarm: manager.workq.put(('SERVICE_NETLINK_QUEUE', None)) + + if set_overrun: + manager.workq.put(('SERVICE_ERROR', "OVERFLOW")) + + if set_alarm or set_overrun: manager.alarm.set() self.rx_socket.close() @@ -173,8 +233,8 @@ class NetlinkListener(Thread): class NetlinkManagerWithListener(NetlinkManager): - def __init__(self, groups, start_listener=True, use_color=True): - NetlinkManager.__init__(self, use_color=use_color) + def __init__(self, groups, start_listener=True, use_color=True, pid_offset=0, error_notification=False, rcvbuf_sz=10000000): + NetlinkManager.__init__(self, use_color=use_color, pid_offset=pid_offset) self.groups = groups self.workq = Queue() self.netlinkq = [] @@ -192,17 +252,23 @@ class NetlinkManagerWithListener(NetlinkManager): self.ifname_by_index = {} self.blacklist_filter = {} self.whitelist_filter = {} + self.rcvbuf_sz = rcvbuf_sz + self.error_notification = error_notification + self.pid_offset = pid_offset # Listen to netlink messages if start_listener: - self.listener = NetlinkListener(self, self.groups) - self.listener.start() + self.restart_listener() else: self.listener = None def __str__(self): return 'NetlinkManagerWithListener' + def restart_listener(self): + self.listener = NetlinkListener(self, self.groups, self.pid_offset+1, self.error_notification, self.rcvbuf_sz) + self.listener.start() + def signal_term_handler(self, signal, frame): log.info("NetlinkManagerWithListener: Caught SIGTERM") @@ -233,10 +299,10 @@ class NetlinkManagerWithListener(NetlinkManager): self.target_lock.acquire() self.target_seq = nlpacket.seq self.target_pid = nlpacket.pid - self.target_lock.release() if not self.tx_socket: self.tx_socket_allocate() + self.target_lock.release() log.debug('%s TX: TXed %s seq %d, pid %d, %d bytes' % (self, NetlinkPacket.type_to_string[nlpacket.msgtype], @@ -283,6 +349,9 @@ class NetlinkManagerWithListener(NetlinkManager): log.debug("RXed RTM_DELROUTE seq %d, pid %d, %d bytes, for %s%s" % (msg.seq, msg.pid, msg.length, msg.get_prefix_string(), msg.get_nexthops_string(self.ifname_by_index))) + def rx_nlmsg_done(self, msg): + log.debug("RXed NLMSG_DONE seq %d, pid %d, %d bytes" % (msg.seq, msg.pid, msg.length)) + # Note that tx_nlpacket_get_response will block until NetlinkListener has RXed # an Ack/DONE for the message we TXed def get_all_addresses(self): @@ -313,6 +382,24 @@ class NetlinkManagerWithListener(NetlinkManager): self.tx_nlpacket_get_response(link) + def get_all_br_links(self, compress_vlans=True): + family = socket.AF_BRIDGE + debug = RTM_GETLINK in self.debug + + link = Link(RTM_GETLINK, debug, use_color=self.use_color) + link.flags = NLM_F_REQUEST | NLM_F_DUMP + link.body = pack('Bxxxiii', family, 0, 0, 0) + if compress_vlans: + link.add_attribute(Link.IFLA_EXT_MASK, Link.RTEXT_FILTER_BRVLAN_COMPRESSED) + else: + link.add_attribute(Link.IFLA_EXT_MASK, Link.RTEXT_FILTER_BRVLAN) + link.build_message(self.sequence.next(), self.pid) + + if debug: + self.debug_seq_pid[(link.seq, link.pid)] = True + + self.tx_nlpacket_get_response(link) + def get_all_neighbors(self): family = socket.AF_UNSPEC debug = RTM_GETNEIGH in self.debug @@ -490,6 +577,9 @@ class NetlinkManagerWithListener(NetlinkManager): elif msgtype == RTM_NEWROUTE or msgtype == RTM_DELROUTE: msg = Route(msgtype, debug, use_color=self.use_color) + elif msgtype == NLMSG_DONE: + msg = Done(msgtype, debug, use_color=self.use_color) + else: log.warning('RXed unknown netlink message type %s' % msgtype) continue @@ -540,6 +630,9 @@ class NetlinkManagerWithListener(NetlinkManager): elif msg.msgtype == RTM_DELROUTE: self.rx_rtm_delroute(msg) + elif msg.msgtype == NLMSG_DONE: + self.rx_nlmsg_done(msg) + else: log.warning('RXed unknown netlink message type %s' % msgtype) diff --git a/nlmanager/nlmanager.py b/ifupdown2/nlmanager/nlmanager.py similarity index 83% rename from nlmanager/nlmanager.py rename to ifupdown2/nlmanager/nlmanager.py index 8299a46..e4a5892 100644 --- a/nlmanager/nlmanager.py +++ b/ifupdown2/nlmanager/nlmanager.py @@ -1,4 +1,29 @@ #!/usr/bin/env python +# +# Copyright (C) 2015, 2017 Cumulus Networks, Inc. all rights reserved +# +# This program 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; version 2. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. +# +# https://www.gnu.org/licenses/gpl-2.0-standalone.html +# +# Authors: +# Daniel Walton, dwalton@cumulusnetworks.com +# Julien Fortin, julien@cumulusnetworks.com +# +# Netlink Manager -- +# from collections import OrderedDict from ipaddr import IPv4Address, IPv6Address @@ -40,7 +65,7 @@ class Sequence(object): class NetlinkManager(object): - def __init__(self, pid_offset=0, use_color=True, extra_debug=False): + def __init__(self, pid_offset=0, use_color=True, log_level=None): # PID_MAX_LIMIT is 2^22 allowing 1024 sockets per-pid. We default to 0 # in the upper space (top 10 bits), which will simply be the PID. Other # NetlinkManager instantiations in the same process can choose other @@ -58,7 +83,10 @@ class NetlinkManager(object): self.debug_address(False) self.debug_neighbor(False) self.debug_route(False) - set_extra_debug(extra_debug) + + if log_level: + log.setLevel(log_level) + set_log_level(log_level) def __str__(self): return 'NetlinkManager' @@ -493,6 +521,22 @@ class NetlinkManager(object): log.info("Netlink did not find interface %s" % ifname) return None + def _get_iface_by_index(self, ifindex): + """ + Return a Link object for ifindex + """ + debug = RTM_GETLINK in self.debug + + link = Link(RTM_GETLINK, debug, use_color=self.use_color) + link.flags = NLM_F_REQUEST | NLM_F_ACK + link.body = pack('=Bxxxiii', socket.AF_UNSPEC, ifindex, 0, 0) + link.build_message(self.sequence.next(), self.pid) + try: + return self.tx_nlpacket_get_response(link)[0] + except NetlinkNoAddressError: + log.info("Netlink did not find interface %s" % ifindex) + return None + def get_iface_index(self, ifname): """ Return the interface index for ifname @@ -503,6 +547,101 @@ class NetlinkManager(object): return iface.ifindex return None + def get_iface_name(self, ifindex): + iface = self._get_iface_by_index(ifindex) + + if iface: + return iface.attributes[Link.IFLA_IFNAME].get_pretty_value(str) + return None + + def link_dump(self, ifname=None): + msg = Link(RTM_GETLINK, False, use_color=self.use_color) + msg.body = pack('Bxxxiii', socket.AF_UNSPEC, 0, 0, 0) + msg.flags = NLM_F_REQUEST | NLM_F_ACK + + if ifname: + msg.add_attribute(Link.IFLA_IFNAME, ifname) + else: + msg.flags |= NLM_F_DUMP + + msg.build_message(self.sequence.next(), self.pid) + return self.tx_nlpacket_get_response(msg) + + def link_set_attrs(self, ifname, kind=None, slave_kind=None, ifindex=0, ifla={}, ifla_info_data={}, ifla_info_slave_data={}): + debug = RTM_NEWLINK in self.debug + + link = Link(RTM_NEWLINK, debug, use_color=self.use_color) + link.flags = NLM_F_REQUEST | NLM_F_ACK + link.body = pack('Bxxxiii', socket.AF_UNSPEC, ifindex, 0, 0) + + for nl_attr, value in ifla.items(): + link.add_attribute(nl_attr, value) + + if ifname: + link.add_attribute(Link.IFLA_IFNAME, ifname) + + linkinfo = dict() + + if kind: + linkinfo[Link.IFLA_INFO_KIND] = kind + linkinfo[Link.IFLA_INFO_DATA] = ifla_info_data + elif slave_kind: + linkinfo[Link.IFLA_INFO_SLAVE_KIND] = slave_kind, + linkinfo[Link.IFLA_INFO_SLAVE_DATA] = ifla_info_slave_data + + link.add_attribute(Link.IFLA_LINKINFO, linkinfo) + link.build_message(self.sequence.next(), self.pid) + return self.tx_nlpacket_get_response(link) + + def link_add_set(self, kind, + ifname=None, + ifindex=0, + slave_kind=None, + ifla={}, + ifla_info_data={}, + ifla_info_slave_data={}): + """ + Build and TX a RTM_NEWLINK message to add the desired interface + """ + debug = RTM_NEWLINK in self.debug + + link = Link(RTM_NEWLINK, debug, use_color=self.use_color) + link.flags = NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK + link.body = pack('Bxxxiii', socket.AF_UNSPEC, ifindex, 0, 0) + + for nl_attr, value in ifla.items(): + link.add_attribute(nl_attr, value) + + if ifname: + link.add_attribute(Link.IFLA_IFNAME, ifname) + + linkinfo = dict() + if kind: + linkinfo[Link.IFLA_INFO_KIND] = kind + linkinfo[Link.IFLA_INFO_DATA] = ifla_info_data + if slave_kind: + linkinfo[Link.IFLA_INFO_SLAVE_KIND] = slave_kind + linkinfo[Link.IFLA_INFO_SLAVE_DATA] = ifla_info_slave_data + link.add_attribute(Link.IFLA_LINKINFO, linkinfo) + + link.build_message(self.sequence.next(), self.pid) + return self.tx_nlpacket_get_response(link) + + def link_del(self, ifindex=None, ifname=None): + if not ifindex and not ifname: + raise ValueError('invalid ifindex and/or ifname') + + if not ifindex: + ifindex = self.get_iface_index(ifname) + + debug = RTM_DELLINK in self.debug + + link = Link(RTM_DELLINK, debug, use_color=self.use_color) + link.flags = NLM_F_REQUEST | NLM_F_ACK + link.body = pack('Bxxxiii', socket.AF_UNSPEC, ifindex, 0, 0) + link.build_message(self.sequence.next(), self.pid) + return self.tx_nlpacket_get_response(link) + def _link_add(self, ifindex, ifname, kind, ifla_info_data): """ Build and TX a RTM_NEWLINK message to add the desired interface @@ -513,7 +652,10 @@ class NetlinkManager(object): link.flags = NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK link.body = pack('Bxxxiii', socket.AF_UNSPEC, 0, 0, 0) link.add_attribute(Link.IFLA_IFNAME, ifname) - link.add_attribute(Link.IFLA_LINK, ifindex) + + if ifindex: + link.add_attribute(Link.IFLA_LINK, ifindex) + link.add_attribute(Link.IFLA_LINKINFO, { Link.IFLA_INFO_KIND: kind, Link.IFLA_INFO_DATA: ifla_info_data @@ -521,7 +663,10 @@ class NetlinkManager(object): link.build_message(self.sequence.next(), self.pid) return self.tx_nlpacket_get_response(link) - def link_add_vlan(self, ifindex, ifname, vlanid): + def link_add_bridge(self, ifname, ifla_info_data={}): + return self._link_add(ifindex=None, ifname=ifname, kind='bridge', ifla_info_data=ifla_info_data) + + def link_add_vlan(self, ifindex, ifname, vlanid, vlan_protocol=None): """ ifindex is the index of the parent interface that this sub-interface is being added to @@ -542,7 +687,12 @@ class NetlinkManager(object): "to VLAN %d (VLAN %d was requested)" % (ifname, ifname_vlanid, vlanid)) - return self._link_add(ifindex, ifname, 'vlan', {Link.IFLA_VLAN_ID: vlanid}) + ifla_info_data = {Link.IFLA_VLAN_ID: vlanid} + + if vlan_protocol: + ifla_info_data[Link.IFLA_VLAN_PROTOCOL] = vlan_protocol + + return self._link_add(ifindex, ifname, 'vlan', ifla_info_data) def link_add_macvlan(self, ifindex, ifname): """ @@ -551,7 +701,7 @@ class NetlinkManager(object): """ return self._link_add(ifindex, ifname, 'macvlan', {Link.IFLA_MACVLAN_MODE: Link.MACVLAN_MODE_PRIVATE}) - def vlan_get(self, filter_ifindex=(), filter_vlanid=(), compress_vlans=True): + def vlan_get(self, filter_ifindex=None, filter_vlanid=None, compress_vlans=True): """ filter_ifindex should be a tuple if interface indexes, this is a whitelist filter filter_vlandid should be a tuple if VLAN IDs, this is a whitelist filter @@ -819,7 +969,7 @@ class NetlinkManager(object): return self.tx_nlpacket_get_response(nbr) def link_add_vxlan(self, ifname, vxlanid, dstport=None, local=None, - group=None, learning='on', ageing=None, physdev=None): + group=None, learning=True, ageing=None): debug = RTM_NEWLINK in self.debug @@ -830,11 +980,8 @@ class NetlinkManager(object): info_data[Link.IFLA_VXLAN_LOCAL] = local if group: info_data[Link.IFLA_VXLAN_GROUP] = group - if physdev: - info_data[Link.IFLA_VXLAN_LINK] = int (physdev) - learning = 0 if learning == 'off' else 1 - info_data[Link.IFLA_VXLAN_LEARNING] = learning + info_data[Link.IFLA_VXLAN_LEARNING] = int(learning) if ageing: info_data[Link.IFLA_VXLAN_AGEING] = int(ageing) @@ -850,3 +997,21 @@ class NetlinkManager(object): link.build_message(self.sequence.next(), self.pid) return self.tx_nlpacket_get_response(link) + + # ========= + # Addresses + # ========= + def addr_dump(self): + """ + TODO: add ifname/ifindex filtering: + - via the RTM_GETADDR request packet + - or in python if kernel doesn't support per intf dump + """ + debug = RTM_GETADDR in self.debug + + msg = Address(RTM_GETADDR, debug, use_color=self.use_color) + msg.body = pack('=Bxxxi', socket.AF_UNSPEC, 0) + msg.flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP + + msg.build_message(self.sequence.next(), self.pid) + return self.tx_nlpacket_get_response(msg) diff --git a/nlmanager/nlpacket.py b/ifupdown2/nlmanager/nlpacket.py similarity index 62% rename from nlmanager/nlpacket.py rename to ifupdown2/nlmanager/nlpacket.py index f2d7b45..d6f93fe 100644 --- a/nlmanager/nlpacket.py +++ b/ifupdown2/nlmanager/nlpacket.py @@ -1,6 +1,6 @@ # Copyright (c) 2009-2013, Exa Networks Limited # Copyright (c) 2009-2013, Thomas Mangin -# Copyright (c) 2015 Cumulus Networks, Inc. +# Copyright (c) 2015-2017 Cumulus Networks, Inc. # # All rights reserved. # Redistribution and use in source and binary forms, with or without @@ -33,11 +33,13 @@ import struct from ipaddr import IPv4Address, IPv6Address, IPAddress from binascii import hexlify from pprint import pformat -from socket import AF_INET, AF_INET6, AF_BRIDGE +from socket import AF_UNSPEC, AF_INET, AF_INET6, AF_BRIDGE, htons from string import printable from struct import pack, unpack, calcsize log = logging.getLogger(__name__) +SYSLOG_EXTRA_DEBUG = 5 + # Interface name buffer size #define IFNAMSIZ 16 (kernel source) IF_NAME_SIZE = 15 # 15 because python doesn't have \0 @@ -114,17 +116,34 @@ RTMGRP_ALL = (RTMGRP_LINK | RTMGRP_NOTIFY | RTMGRP_NEIGH | RTMGRP_TC | RTMGRP_DECnet_IFADDR | RTMGRP_DECnet_ROUTE | RTMGRP_IPV6_PREFIX) +AF_MPLS = 28 + # Colors for logging red = 91 green = 92 yellow = 93 blue = 94 -EXTRA_DEBUG = False +value_to_bool_dict = { + False: False, + None: False, + 0: False, + '0': False, + 'no': False, + 'off': False, + 'slow': False, + 'None': False, + True: True, + 1: True, + '1': True, + 'on': True, + 'yes': True, + 'fast': True +} -def set_extra_debug(debug): - global EXTRA_DEBUG - EXTRA_DEBUG = debug + +def set_log_level(level): + log.setLevel(level) def zfilled_hex(value, digits): @@ -293,7 +312,9 @@ class Attribute(object): return line_number - def get_pretty_value(self): + def get_pretty_value(self, obj=None): + if obj and callable(obj): + return obj(self.value) return self.value @@ -545,6 +566,44 @@ class AttributeMACAddress(Attribute): return line_number +class AttributeMplsLabel(Attribute): + + def __init__(self, atype, string, family, logger): + Attribute.__init__(self, atype, string, logger) + self.value_int = None + self.value_int_str = None + self.family = family + self.PACK = '>HBB' + + def decode(self, parent_msg, data): + self.decode_length_type(data) + + try: + (label_high, label_low_tc_s, self.ttl) = unpack(self.PACK, self.data[4:]) + self.s_bit = label_low_tc_s & 0x1 + self.traffic_class = ((label_low_tc_s & 0xf) >> 1) + self.label = (label_high << 4) | (label_low_tc_s >> 4) + self.value = self.label + self.value_int = self.value + self.value_int_str = str(self.value_int) + + except struct.error: + self.value = None + self.value_int = None + self.value_int_str = None + self.log.error("%s unpack of %s failed, data 0x%s" % (self, self.PACK, hexlify(self.data[4:]))) + raise + + def dump_lines(self, dump_buffer, line_number, color): + line_number = self.dump_first_line(dump_buffer, line_number, color) + dump_buffer.append(data_to_color_text(line_number, color, self.data[4:8], + 'label %s, TC %s, bottom-of-stack %s, TTL %d' % + (self.label, self.traffic_class, self.s_bit, self.ttl))) + line_number += 1 + + return line_number + + class AttributeGeneric(Attribute): def __init__(self, atype, string, family, logger): @@ -569,9 +628,30 @@ class AttributeOneByteValue(AttributeGeneric): def __init__(self, atype, string, family, logger): AttributeGeneric.__init__(self, atype, string, family, logger) - self.PACK = '=B' + self.PACK = '=Bxxx' self.LEN = calcsize(self.PACK) + def decode(self, parent_msg, data): + self.decode_length_type(data) + assert self.attr_end == 8, "Attribute length for %s must be 8, it is %d" % (self, self.attr_end) + + try: + self.value = int(unpack(self.PACK, self.data[4:8])[0]) + except struct.error: + self.log.error("%s unpack of %s failed, data 0x%s" % (self, self.PACK, hexlify(self.data[4:5]))) + raise + + def encode(self): + length = self.HEADER_LEN + self.LEN + raw = pack(self.HEADER_PACK, length-3, self.atype) + pack(self.PACK, self.value) + raw = self.pad(length, raw) + return raw + + def dump_lines(self, dump_buffer, line_number, color): + line_number = self.dump_first_line(dump_buffer, line_number, color) + dump_buffer.append(data_to_color_text(line_number, color, self.data[4:8], self.value)) + return line_number + 1 + class AttributeIFLA_AF_SPEC(Attribute): """ @@ -583,10 +663,11 @@ class AttributeIFLA_AF_SPEC(Attribute): """ def __init__(self, atype, string, family, logger): Attribute.__init__(self, atype, string, logger) + self.family = family def encode(self): pack_layout = [self.HEADER_PACK] - payload = [0, self.atype] + payload = [0, self.atype | NLA_F_NESTED] attr_length_index = 0 # For now this assumes that all data will be packed in the native endian @@ -608,7 +689,7 @@ class AttributeIFLA_AF_SPEC(Attribute): sub_attr_to_add.append((sub_attr_type, (vlan_flag, vlan_id))) else: - self.log.debug('Add support for encoding IFLA_AF_SPEC sub-attribute type %d' % sub_attr_type) + self.log.log(SYSLOG_EXTRA_DEBUG, 'Add support for encoding IFLA_AF_SPEC sub-attribute type %d' % sub_attr_type) continue for (sub_attr_type, sub_attr_value) in sub_attr_to_add: @@ -658,10 +739,57 @@ class AttributeIFLA_AF_SPEC(Attribute): Link.IFLA_BRIDGE_FLAGS: flags, Link.IFLA_BRIDGE_VLAN_INFO: (vflags, vlanid) } + + FROM: David Ahern + The encoding of the IFLA_AF_SPEC attribute varies depending on the family + used for the request (RTM_GETLINK) message. For AF_UNSPEC the encoding + has another level of nesting for each address family with the type encoded + first. i.e., + af_spec = nla_nest_start(skb, IFLA_AF_SPEC) + for each family: + af = nla_nest_start(skb, af_ops->family) + af_ops->fill_link_af(skb, dev, ext_filter_mask) + nest_end + nest_end + + This allows the parser to find the address family by looking at the first + type. + + Whereas AF_BRIDGE encoding is just: + af_spec = nla_nest_start(skb, IFLA_AF_SPEC) + br_fill_ifvlaninfo{_compressed}(skb, vg) + nest_end + + which means the parser can not use the attribute itself to know the family + to which the attribute belongs. + + /include/uapi/linux/if_link.h + /* + * IFLA_AF_SPEC + * Contains nested attributes for address family specific attributes. + * Each address family may create a attribute with the address family + * number as type and create its own attribute structure in it. + * + * Example: + * [IFLA_AF_SPEC] = { + * [AF_INET] = { + * [IFLA_INET_CONF] = ..., + * }, + * [AF_INET6] = { + * [IFLA_INET6_FLAGS] = ..., + * [IFLA_INET6_CONF] = ..., + * } + * } + */ + """ self.decode_length_type(data) self.value = {} + # opti for now, since we only support AF_BRIDGE + if self.family != AF_BRIDGE: + return + data = self.data[4:] while data: @@ -674,17 +802,30 @@ class AttributeIFLA_AF_SPEC(Attribute): sub_attr_data = data[4:sub_attr_end] - if sub_attr_type == Link.IFLA_BRIDGE_FLAGS: - self.value[Link.IFLA_BRIDGE_FLAGS] = unpack("=H", sub_attr_data[0:2])[0] + if self.family == AF_BRIDGE: + if sub_attr_type == Link.IFLA_BRIDGE_FLAGS: + self.value[Link.IFLA_BRIDGE_FLAGS] = unpack("=H", sub_attr_data[0:2])[0] - elif sub_attr_type == Link.IFLA_BRIDGE_VLAN_INFO: - if Link.IFLA_BRIDGE_VLAN_INFO not in self.value: - self.value[Link.IFLA_BRIDGE_VLAN_INFO] = [] - self.value[Link.IFLA_BRIDGE_VLAN_INFO].append(tuple(unpack("=HH", sub_attr_data[0:4]))) + elif sub_attr_type == Link.IFLA_BRIDGE_VLAN_INFO: + if Link.IFLA_BRIDGE_VLAN_INFO not in self.value: + self.value[Link.IFLA_BRIDGE_VLAN_INFO] = [] + self.value[Link.IFLA_BRIDGE_VLAN_INFO].append(tuple(unpack("=HH", sub_attr_data[0:4]))) + + else: + self.log.log(SYSLOG_EXTRA_DEBUG, 'Add support for decoding IFLA_AF_SPEC sub-attribute ' + 'type %s (%d), length %d, padded to %d' % + (parent_msg.get_ifla_bridge_af_spec_to_string(sub_attr_type), + sub_attr_type, sub_attr_length, sub_attr_end)) - elif EXTRA_DEBUG: - self.log.debug('Add support for decoding IFLA_AF_SPEC sub-attribute type %s (%d), length %d, padded to %d' % - (parent_msg.get_ifla_bridge_af_spec_to_string(sub_attr_type), sub_attr_type, sub_attr_length, sub_attr_end)) + elif self.family == AF_UNSPEC: + self.log.log(SYSLOG_EXTRA_DEBUG, 'Add support for decoding IFLA_AF_SPEC sub-attribute ' + 'type AF_UNSPEC (0), length %d, padded to %d' + % (sub_attr_length, sub_attr_end)) + + else: + self.log.log(SYSLOG_EXTRA_DEBUG, 'Add support for decoding IFLA_AF_SPEC sub-attribute ' + 'family %d, length %d, padded to %d' + % (self.family, sub_attr_length, sub_attr_end)) data = data[sub_attr_end:] @@ -715,11 +856,12 @@ class AttributeIFLA_AF_SPEC(Attribute): else: padded_to = ' padded to %d, ' % sub_attr_end - extra = 'Nested Attribute - Length %s (%d)%s Type %s (%d) %s' % \ - (zfilled_hex(sub_attr_length, 4), sub_attr_length, - padded_to, - zfilled_hex(sub_attr_type, 4), sub_attr_type, - Link.ifla_bridge_af_spec_to_string.get(sub_attr_type)) + if self.family == AF_BRIDGE: + extra = 'Nested Attribute - Length %s (%d)%s Type %s (%d) %s' % \ + (zfilled_hex(sub_attr_length, 4), sub_attr_length, + padded_to, + zfilled_hex(sub_attr_type, 4), sub_attr_type, + Link.ifla_bridge_af_spec_to_string.get(sub_attr_type)) else: extra = '' @@ -728,13 +870,17 @@ class AttributeIFLA_AF_SPEC(Attribute): return line_number - def get_pretty_value(self): + def get_pretty_value(self, obj=None): + + if obj and callable(obj): + return obj(self.value) + # We do this so we can print a more human readable dictionary # with the names of the nested keys instead of their numbers value_pretty = {} for (sub_key, sub_value) in self.value.iteritems(): - sub_key_pretty = "(%2d) % s" % (sub_key, Link.ifla_bridge_af_spec_to_string.get(sub_key)) + sub_key_pretty = "(%2d) %s" % (sub_key, Link.ifla_bridge_af_spec_to_string.get(sub_key)) value_pretty[sub_key_pretty] = sub_value return value_pretty @@ -816,15 +962,19 @@ struct rtnexthop { data = data[self.HEADER_LEN:] if self.family == AF_INET: + if len(data) < self.IPV4_LEN: + break nexthop = IPv4Address(unpack('>L', data[:self.IPV4_LEN])[0]) self.value.append((nexthop, rtnh_ifindex, rtnh_flags, rtnh_hops)) - data = data[self.IPV4_LEN:] elif self.family == AF_INET6: + if len(data) < self.IPV6_LEN: + break (data1, data2) = unpack('>QQ', data[:self.IPV6_LEN]) nexthop = IPv6Address(data1 << 64 | data2) self.value.append((nexthop, rtnh_ifindex, rtnh_flags, rtnh_hops)) - data = data[self.IPV6_LEN:] + + data = data[(rtnh_len-self.RTNH_LEN-self.HEADER_LEN):] self.value = tuple(self.value) @@ -845,13 +995,17 @@ class AttributeIFLA_LINKINFO(Attribute): def encode(self): pack_layout = [self.HEADER_PACK] - payload = [0, self.atype] + payload = [0, self.atype | NLA_F_NESTED] attr_length_index = 0 - kind = self.value[Link.IFLA_INFO_KIND] + kind = self.value.get(Link.IFLA_INFO_KIND) + slave_kind = self.value.get(Link.IFLA_INFO_SLAVE_KIND) - if kind not in ('vlan', 'macvlan', 'vxlan'): + if not slave_kind and kind not in ('vlan', 'macvlan', 'vxlan', 'bond', 'bridge'): raise Exception('Unsupported IFLA_INFO_KIND %s' % kind) + elif not kind and slave_kind != 'bridge': + # only support brport for now. + raise Exception('Unsupported IFLA_INFO_SLAVE_KIND %s' % slave_kind) # For now this assumes that all data will be packed in the native endian # order (=). If a field is added that needs to be packed via network @@ -871,6 +1025,8 @@ class AttributeIFLA_LINKINFO(Attribute): elif sub_attr_type == Link.IFLA_INFO_DATA: + sub_attr_payload = [0, sub_attr_type | NLA_F_NESTED] + for (info_data_type, info_data_value) in sub_attr_value.iteritems(): if kind == 'vlan': @@ -886,8 +1042,23 @@ class AttributeIFLA_LINKINFO(Attribute): # pad 2 bytes sub_attr_pack_layout.extend('xx') + elif info_data_type == Link.IFLA_VLAN_PROTOCOL: + sub_attr_pack_layout.append('HH') + sub_attr_payload.append(6) # length + sub_attr_payload.append(info_data_type) + + # vlan protocol + vlan_protocol = Link.ifla_vlan_protocol_dict.get(info_data_value) + if not vlan_protocol: + raise NotImplementedError('vlan protocol %s not implemented' % info_data_value) + + sub_attr_pack_layout.append('H') + sub_attr_payload.append(htons(vlan_protocol)) + + # pad 2 bytes + sub_attr_pack_layout.extend('xx') else: - self.log.debug('Add support for encoding IFLA_INFO_DATA vlan sub-attribute type %d' % info_data_type) + self.log.log(SYSLOG_EXTRA_DEBUG, 'Add support for encoding IFLA_INFO_DATA vlan sub-attribute type %d' % info_data_type) elif kind == 'macvlan': if info_data_type == Link.IFLA_MACVLAN_MODE: @@ -900,7 +1071,7 @@ class AttributeIFLA_LINKINFO(Attribute): sub_attr_payload.append(info_data_value) else: - self.log.debug('Add support for encoding IFLA_INFO_DATA macvlan sub-attribute type %d' % info_data_type) + self.log.log(SYSLOG_EXTRA_DEBUG, 'Add support for encoding IFLA_INFO_DATA macvlan sub-attribute type %d' % info_data_type) elif kind == 'vxlan': if info_data_type in (Link.IFLA_VXLAN_ID, @@ -961,10 +1132,279 @@ class AttributeIFLA_LINKINFO(Attribute): sub_attr_pack_layout.extend('xxx') else: - self.log.debug('Add support for encoding IFLA_INFO_DATA vxlan sub-attribute type %d' % info_data_type) + self.log.log(SYSLOG_EXTRA_DEBUG, 'Add support for encoding IFLA_INFO_DATA vxlan sub-attribute type %d' % info_data_type) + + elif kind == 'bond': + if info_data_type in (Link.IFLA_BOND_AD_ACTOR_SYSTEM, ): + sub_attr_pack_layout.append('HH') + sub_attr_payload.append(10) # length + sub_attr_payload.append(info_data_type) + + sub_attr_pack_layout.append('6B') + for mbyte in info_data_value.replace('.', ' ').replace(':', ' ').split(): + sub_attr_payload.append(int(mbyte, 16)) + sub_attr_pack_layout.extend('xx') + + elif info_data_type == Link.IFLA_BOND_AD_ACTOR_SYS_PRIO: + sub_attr_pack_layout.append('HH') + sub_attr_payload.append(6) # length + sub_attr_payload.append(info_data_type) + + # 2 bytes + sub_attr_pack_layout.append('H') + sub_attr_payload.append(int(info_data_value)) + + # pad 2 bytes + sub_attr_pack_layout.extend('xx') + + elif info_data_type == Link.IFLA_BOND_NUM_PEER_NOTIF: + sub_attr_pack_layout.append('HH') + sub_attr_payload.append(5) # length + sub_attr_payload.append(info_data_type) + + # 1 byte + sub_attr_pack_layout.append('B') + sub_attr_payload.append(int(info_data_value)) + + # pad 3 bytes + sub_attr_pack_layout.extend('xxx') + + + elif info_data_type in (Link.IFLA_BOND_AD_LACP_RATE, + Link.IFLA_BOND_AD_LACP_BYPASS, + Link.IFLA_BOND_USE_CARRIER): + # converts yes/no/on/off/0/1 strings to boolean value + bool_value = self.get_bool_value(info_data_value) + + sub_attr_pack_layout.append('HH') + sub_attr_payload.append(5) # length + sub_attr_payload.append(info_data_type) + + # 1 byte + sub_attr_pack_layout.append('B') + sub_attr_payload.append(bool_value) + + # pad 3 bytes + sub_attr_pack_layout.extend('xxx') + + elif info_data_type == Link.IFLA_BOND_XMIT_HASH_POLICY: + index = self.get_index(Link.ifla_bond_xmit_hash_policy_tbl, + 'bond xmit hash policy', + info_data_value) + + sub_attr_pack_layout.append('HH') + sub_attr_payload.append(5) # length + sub_attr_payload.append(info_data_type) + + # 1 byte + sub_attr_pack_layout.append('B') + sub_attr_payload.append(index) + + # pad 3 bytes + sub_attr_pack_layout.extend('xxx') + + elif info_data_type == Link.IFLA_BOND_MODE: + index = self.get_index(Link.ifla_bond_mode_tbl, + 'bond mode', + info_data_value) + + sub_attr_pack_layout.append('HH') + sub_attr_payload.append(5) # length + sub_attr_payload.append(info_data_type) + + # 1 byte + sub_attr_pack_layout.append('B') + sub_attr_payload.append(index) + + # pad 3 bytes + sub_attr_pack_layout.extend('xxx') + + elif info_data_type in (Link.IFLA_BOND_MIIMON, + Link.IFLA_BOND_UPDELAY, + Link.IFLA_BOND_DOWNDELAY, + Link.IFLA_BOND_MIN_LINKS): + sub_attr_pack_layout.append('HH') + sub_attr_payload.append(8) # length + sub_attr_payload.append(info_data_type) + + sub_attr_pack_layout.append('L') + sub_attr_payload.append(int(info_data_value)) + + else: + self.log.log(SYSLOG_EXTRA_DEBUG, 'Add support for encoding IFLA_INFO_DATA bond sub-attribute type %d' % info_data_type) + + elif kind == 'bridge': + if info_data_type == Link.IFLA_BR_VLAN_PROTOCOL: + sub_attr_pack_layout.append('HH') + sub_attr_payload.append(6) # length + sub_attr_payload.append(info_data_type) + + # vlan protocol + vlan_protocol = Link.ifla_vlan_protocol_dict.get(info_data_value) + if not vlan_protocol: + raise NotImplementedError('vlan protocol %s not implemented' % info_data_value) + + sub_attr_pack_layout.append('H') + sub_attr_payload.append(htons(vlan_protocol)) + + # pad 2 bytes + sub_attr_pack_layout.extend('xx') + + # 1 byte + elif info_data_type in (Link.IFLA_BR_VLAN_FILTERING, + Link.IFLA_BR_TOPOLOGY_CHANGE, + Link.IFLA_BR_TOPOLOGY_CHANGE_DETECTED, + Link.IFLA_BR_MCAST_ROUTER, + Link.IFLA_BR_MCAST_SNOOPING, + Link.IFLA_BR_MCAST_QUERY_USE_IFADDR, + Link.IFLA_BR_MCAST_QUERIER, + Link.IFLA_BR_NF_CALL_IPTABLES, + Link.IFLA_BR_NF_CALL_IP6TABLES, + Link.IFLA_BR_NF_CALL_ARPTABLES, + Link.IFLA_BR_VLAN_STATS_ENABLED, + Link.IFLA_BR_MCAST_STATS_ENABLED, + Link.IFLA_BR_MCAST_IGMP_VERSION, + Link.IFLA_BR_MCAST_MLD_VERSION): + sub_attr_pack_layout.append('HH') + sub_attr_payload.append(5) # length + sub_attr_payload.append(info_data_type) + + # 1 byte + sub_attr_pack_layout.append('B') + sub_attr_payload.append(int(info_data_value)) + + # pad 3 bytes + sub_attr_pack_layout.extend('xxx') + + # 2 bytes + elif info_data_type in (Link.IFLA_BR_PRIORITY, + Link.IFLA_BR_GROUP_FWD_MASK, + Link.IFLA_BR_ROOT_PORT, + Link.IFLA_BR_VLAN_DEFAULT_PVID): + sub_attr_pack_layout.append('HH') + sub_attr_payload.append(6) # length + sub_attr_payload.append(info_data_type) + + # 2 bytes + sub_attr_pack_layout.append('H') + sub_attr_payload.append(int(info_data_value)) + + # pad 2 bytes + sub_attr_pack_layout.extend('xx') + + # 4 bytes + elif info_data_type in (Link.IFLA_BR_FORWARD_DELAY, + Link.IFLA_BR_HELLO_TIME, + Link.IFLA_BR_MAX_AGE, + Link.IFLA_BR_AGEING_TIME, + Link.IFLA_BR_STP_STATE, + Link.IFLA_BR_ROOT_PATH_COST, + Link.IFLA_BR_MCAST_QUERIER, + Link.IFLA_BR_MCAST_HASH_ELASTICITY, + Link.IFLA_BR_MCAST_HASH_MAX, + Link.IFLA_BR_MCAST_LAST_MEMBER_CNT, + Link.IFLA_BR_MCAST_STARTUP_QUERY_CNT): + sub_attr_pack_layout.append('HH') + sub_attr_payload.append(8) # length + sub_attr_payload.append(info_data_type) + + sub_attr_pack_layout.append('L') + sub_attr_payload.append(int(info_data_value)) + + # 8 bytes + elif info_data_type in (Link.IFLA_BR_MCAST_LAST_MEMBER_INTVL, + Link.IFLA_BR_MCAST_MEMBERSHIP_INTVL, + Link.IFLA_BR_MCAST_QUERIER_INTVL, + Link.IFLA_BR_MCAST_QUERY_INTVL, + Link.IFLA_BR_MCAST_QUERY_RESPONSE_INTVL, + Link.IFLA_BR_MCAST_STARTUP_QUERY_INTVL): + sub_attr_pack_layout.append('HH') + sub_attr_payload.append(12) # length + sub_attr_payload.append(info_data_type) + + sub_attr_pack_layout.append('Q') + sub_attr_payload.append(int(info_data_value)) + + else: + self.log.log(SYSLOG_EXTRA_DEBUG, 'Add support for encoding IFLA_INFO_DATA bridge sub-attribute type %d' % info_data_type) + + elif sub_attr_type == Link.IFLA_INFO_SLAVE_DATA: + + sub_attr_payload = [0, sub_attr_type | NLA_F_NESTED] + + for (info_slave_data_type, info_slave_data_value) in sub_attr_value.iteritems(): + + if slave_kind == 'bridge': + + # 1 byte + if info_slave_data_type in (Link.IFLA_BRPORT_STATE, + Link.IFLA_BRPORT_MODE, + Link.IFLA_BRPORT_GUARD, + Link.IFLA_BRPORT_PROTECT, + Link.IFLA_BRPORT_FAST_LEAVE, + Link.IFLA_BRPORT_LEARNING, + Link.IFLA_BRPORT_UNICAST_FLOOD, + Link.IFLA_BRPORT_PROXYARP, + Link.IFLA_BRPORT_LEARNING_SYNC, + Link.IFLA_BRPORT_PROXYARP_WIFI, + Link.IFLA_BRPORT_TOPOLOGY_CHANGE_ACK, + Link.IFLA_BRPORT_CONFIG_PENDING, + Link.IFLA_BRPORT_MULTICAST_ROUTER, + Link.IFLA_BRPORT_MCAST_FLOOD, + Link.IFLA_BRPORT_MCAST_TO_UCAST, + Link.IFLA_BRPORT_VLAN_TUNNEL, + Link.IFLA_BRPORT_BCAST_FLOOD, + Link.IFLA_BRPORT_PEER_LINK, + Link.IFLA_BRPORT_DUAL_LINK, + Link.IFLA_BRPORT_ARP_SUPPRESS, + Link.IFLA_BRPORT_DOWN_PEERLINK_REDIRECT): + sub_attr_pack_layout.append('HH') + sub_attr_payload.append(5) # length + sub_attr_payload.append(info_slave_data_type) + + # 1 byte + sub_attr_pack_layout.append('B') + sub_attr_payload.append(int(info_slave_data_value)) + + # pad 3 bytes + sub_attr_pack_layout.extend('xxx') + + # 2 bytes + elif info_slave_data_type in (Link.IFLA_BRPORT_PRIORITY, + Link.IFLA_BRPORT_DESIGNATED_PORT, + Link.IFLA_BRPORT_DESIGNATED_COST, + Link.IFLA_BRPORT_ID, + Link.IFLA_BRPORT_NO, + Link.IFLA_BRPORT_GROUP_FWD_MASK, + Link.IFLA_BRPORT_GROUP_FWD_MASKHI): + sub_attr_pack_layout.append('HH') + sub_attr_payload.append(6) # length + sub_attr_payload.append(info_slave_data_type) + + # 2 bytes + sub_attr_pack_layout.append('H') + sub_attr_payload.append(int(info_slave_data_value)) + + # pad 2 bytes + sub_attr_pack_layout.extend('xx') + + # 4 bytes + elif info_slave_data_type == Link.IFLA_BRPORT_COST: + sub_attr_pack_layout.append('HH') + sub_attr_payload.append(8) # length + sub_attr_payload.append(info_slave_data_type) + + sub_attr_pack_layout.append('L') + sub_attr_payload.append(int(info_slave_data_value)) + + else: + self.log.log(SYSLOG_EXTRA_DEBUG, 'Add support for encoding IFLA_INFO_SLAVE_DATA bond sub-attribute type %d' % info_slave_data_type) + + else: + self.log.log(SYSLOG_EXTRA_DEBUG, 'Add support for encoding IFLA_LINKINFO kind %s' % slave_kind) else: - self.log.debug('Add support for encoding IFLA_LINKINFO sub-attribute type %d' % sub_attr_type) + self.log.log(SYSLOG_EXTRA_DEBUG, 'Add support for encoding IFLA_LINKINFO sub-attribute type %d' % sub_attr_type) continue sub_attr_length = calcsize(''.join(sub_attr_pack_layout)) @@ -993,6 +1433,20 @@ class AttributeIFLA_LINKINFO(Attribute): raw = self.pad(length, raw) return raw + def get_bool_value(self, value, default=None): + try: + return value_to_bool_dict[value] + except KeyError: + self.log.debug('%s: unsupported boolean value' % value) + return default + + def get_index(self, tbl, attr, value, default=None): + try: + return tbl[value] + except KeyError: + self.log.debug('unsupported %s value %s (%s)' % (attr, value, tbl.keys())) + return default + def decode(self, parent_msg, data): """ value is a dictionary such as: @@ -1012,6 +1466,7 @@ class AttributeIFLA_LINKINFO(Attribute): # IFLA_MACVLAN_MODE and IFLA_VLAN_ID both have a value of 1 and both are # valid IFLA_INFO_DATA entries :( The sender must TX IFLA_INFO_KIND # first in order for us to know if "1" is IFLA_MACVLAN_MODE vs IFLA_VLAN_ID. + while data: (sub_attr_length, sub_attr_type) = unpack('=HH', data[:4]) sub_attr_end = padded_length(sub_attr_length) @@ -1020,116 +1475,298 @@ class AttributeIFLA_LINKINFO(Attribute): self.log.error('parsed a zero length sub-attr') return - if sub_attr_type == Link.IFLA_INFO_KIND: - self.value[Link.IFLA_INFO_KIND] = remove_trailing_null(unpack('%ds' % (sub_attr_length - 4), data[4:sub_attr_length])[0]) - - elif sub_attr_type == Link.IFLA_INFO_DATA: + if sub_attr_type in (Link.IFLA_INFO_KIND, Link.IFLA_INFO_SLAVE_KIND): + self.value[sub_attr_type] = remove_trailing_null(unpack('%ds' % (sub_attr_length - 4), data[4:sub_attr_length])[0]) + elif sub_attr_type == Link.IFLA_INFO_SLAVE_DATA: sub_attr_data = data[4:sub_attr_end] - self.value[Link.IFLA_INFO_DATA] = {} - - while sub_attr_data: - (info_data_length, info_data_type) = unpack('=HH', sub_attr_data[:4]) - info_data_end = padded_length(info_data_length) - # self.log.info('sub attr length %d, end %d, type %d' % (info_data_length, info_data_end, info_data_type)) - - if not sub_attr_data: - self.log.error('RXed zero length sub-attribute') - break - - if Link.IFLA_INFO_KIND not in self.value: - self.log.warning('IFLA_INFO_KIND is not known...we cannot parse IFLA_INFO_DATA') - - elif self.value[Link.IFLA_INFO_KIND] == 'vlan': - if info_data_type == Link.IFLA_VLAN_ID: - self.value[Link.IFLA_INFO_DATA][info_data_type] = unpack('=H', sub_attr_data[4:6])[0] - elif EXTRA_DEBUG: - self.log.debug('Add support for decoding IFLA_INFO_KIND vlan type %s (%d), length %d, padded to %d' % - (parent_msg.get_ifla_vlan_string(info_data_type), info_data_type, info_data_length, info_data_end)) - - elif self.value[Link.IFLA_INFO_KIND] == 'macvlan': - if info_data_type == Link.IFLA_MACVLAN_MODE: - self.value[Link.IFLA_INFO_DATA][info_data_type] = unpack('=L', sub_attr_data[4:8])[0] - elif EXTRA_DEBUG: - self.log.debug('Add support for decoding IFLA_INFO_KIND macvlan type %s (%d), length %d, padded to %d' % - (parent_msg.get_ifla_macvlan_string(info_data_type), info_data_type, info_data_length, info_data_end)) - - elif self.value[Link.IFLA_INFO_KIND] == 'vxlan': - - # IPv4Address - if info_data_type in (Link.IFLA_VXLAN_GROUP, - Link.IFLA_VXLAN_LOCAL): - self.value[Link.IFLA_INFO_DATA][info_data_type] = IPv4Address(unpack('>L', sub_attr_data[4:8])[0]) - - # 4-byte int - elif info_data_type in (Link.IFLA_VXLAN_ID, - Link.IFLA_VXLAN_LINK, - Link.IFLA_VXLAN_AGEING, - Link.IFLA_VXLAN_LIMIT, - Link.IFLA_VXLAN_PORT_RANGE): - self.value[Link.IFLA_INFO_DATA][info_data_type] = unpack('=L', sub_attr_data[4:8])[0] - - # 2-byte int - elif info_data_type in (Link.IFLA_VXLAN_PORT, ): - self.value[Link.IFLA_INFO_DATA][info_data_type] = unpack('=H', sub_attr_data[4:6])[0] - - # 1-byte int - elif info_data_type in (Link.IFLA_VXLAN_TTL, - Link.IFLA_VXLAN_TOS, - Link.IFLA_VXLAN_LEARNING, - Link.IFLA_VXLAN_PROXY, - Link.IFLA_VXLAN_RSC, - Link.IFLA_VXLAN_L2MISS, - Link.IFLA_VXLAN_L3MISS, - Link.IFLA_VXLAN_UDP_CSUM, - Link.IFLA_VXLAN_UDP_ZERO_CSUM6_TX, - Link.IFLA_VXLAN_UDP_ZERO_CSUM6_RX, - Link.IFLA_VXLAN_REMCSUM_TX, - Link.IFLA_VXLAN_REMCSUM_RX, - Link.IFLA_VXLAN_REPLICATION_TYPE): - self.value[Link.IFLA_INFO_DATA][info_data_type] = unpack('=B', sub_attr_data[4])[0] - - elif EXTRA_DEBUG: - # sub_attr_end = padded_length(sub_attr_length) - self.log.debug('Add support for decoding IFLA_INFO_KIND vxlan type %s (%d), length %d, padded to %d' % - (parent_msg.get_ifla_vxlan_string(info_data_type), info_data_type, info_data_length, info_data_end)) - elif self.value[Link.IFLA_INFO_KIND] == 'bond': + ifla_info_slave_data = dict() + ifla_info_slave_kind = self.value.get(Link.IFLA_INFO_SLAVE_KIND) - if info_data_type in (Link.IFLA_BOND_AD_INFO, ): - bond_value = {} - - self.value[Link.IFLA_INFO_DATA][info_data_type] = bond_value - elif EXTRA_DEBUG: - self.log.debug('Add support for decoding IFLA_INFO_KIND bond type %s (%d), length %d, padded to %d' % - (parent_msg.get_ifla_bond_string(info_data_type), info_data_type, info_data_length, info_data_end)) - - elif self.value[Link.IFLA_INFO_KIND] == 'bridge': - - if info_data_type in (Link.IFLA_BRPORT_STATE, - Link.IFLA_BRPORT_PRIORITY, - Link.IFLA_BRPORT_COST): - self.value[Link.IFLA_INFO_DATA][info_data_type] = unpack('=L', sub_attr_data[4:8])[0] - - elif info_data_type in (Link.IFLA_BRPORT_FAST_LEAVE, ): - self.value[Link.IFLA_INFO_DATA][info_data_type] = unpack('=B', sub_attr_data[4])[0] - - elif EXTRA_DEBUG: - self.log.debug('Add support for decoding IFLA_INFO_KIND bridge type %s (%d), length %d, padded to %d' % - (parent_msg.get_ifla_bridge_string(info_data_type), info_data_type, info_data_length, info_data_end)) - - elif EXTRA_DEBUG: - self.log.debug("Add support for decoding IFLA_INFO_KIND %s (%d), length %d, padded to %d" % - (self.value[Link.IFLA_INFO_KIND], info_data_type, info_data_length, info_data_end)) + if not ifla_info_slave_kind: + self.log.warning('IFLA_INFO_SLAVE_KIND is not known...we cannot parse IFLA_INFO_SLAVE_DATA') + else: + while sub_attr_data: + (info_data_length, info_data_type) = unpack('=HH', sub_attr_data[:4]) + info_data_end = padded_length(info_data_length) + try: + if ifla_info_slave_kind == 'bridge': + # 1 byte + if info_data_type in (Link.IFLA_BRPORT_STATE, + Link.IFLA_BRPORT_MODE, + Link.IFLA_BRPORT_GUARD, + Link.IFLA_BRPORT_PROTECT, + Link.IFLA_BRPORT_FAST_LEAVE, + Link.IFLA_BRPORT_LEARNING, + Link.IFLA_BRPORT_UNICAST_FLOOD, + Link.IFLA_BRPORT_PROXYARP, + Link.IFLA_BRPORT_LEARNING_SYNC, + Link.IFLA_BRPORT_PROXYARP_WIFI, + Link.IFLA_BRPORT_TOPOLOGY_CHANGE_ACK, + Link.IFLA_BRPORT_CONFIG_PENDING, + Link.IFLA_BRPORT_MULTICAST_ROUTER, + Link.IFLA_BRPORT_MCAST_FLOOD, + Link.IFLA_BRPORT_MCAST_TO_UCAST, + Link.IFLA_BRPORT_VLAN_TUNNEL, + Link.IFLA_BRPORT_PEER_LINK, + Link.IFLA_BRPORT_DUAL_LINK, + Link.IFLA_BRPORT_ARP_SUPPRESS, + Link.IFLA_BRPORT_DOWN_PEERLINK_REDIRECT): + ifla_info_slave_data[info_data_type] = unpack('=B', sub_attr_data[4])[0] + + # 2 bytes + elif info_data_type in (Link.IFLA_BRPORT_PRIORITY, + Link.IFLA_BRPORT_DESIGNATED_PORT, + Link.IFLA_BRPORT_DESIGNATED_COST, + Link.IFLA_BRPORT_ID, + Link.IFLA_BRPORT_NO, + Link.IFLA_BRPORT_GROUP_FWD_MASK, + Link.IFLA_BRPORT_GROUP_FWD_MASKHI): + ifla_info_slave_data[info_data_type] = unpack('=H', sub_attr_data[4:6])[0] + + # 4 bytes + elif info_data_type == Link.IFLA_BRPORT_COST: + ifla_info_slave_data[info_data_type] = unpack('=L', sub_attr_data[4:8])[0] + + elif ifla_info_slave_kind == 'bond': + + # 1 byte + if info_data_type in ( + Link.IFLA_BOND_SLAVE_STATE, + Link.IFLA_BOND_SLAVE_MII_STATUS, + Link.IFLA_BOND_SLAVE_AD_ACTOR_OPER_PORT_STATE, + Link.IFLA_BOND_SLAVE_AD_RX_BYPASS, + ): + ifla_info_slave_data[info_data_type] = unpack('=B', sub_attr_data[4])[0] + + # 2 bytes + elif info_data_type in ( + Link.IFLA_BOND_SLAVE_QUEUE_ID, + Link.IFLA_BOND_SLAVE_AD_AGGREGATOR_ID + ): + ifla_info_slave_data[info_data_type] = unpack('=H', sub_attr_data[4:6])[0] + + # 4 bytes + elif info_data_type == ( + Link.IFLA_BOND_SLAVE_PERM_HWADDR, + Link.IFLA_BOND_SLAVE_LINK_FAILURE_COUNT + ): + ifla_info_slave_data[info_data_type] = unpack('=L', sub_attr_data[4:8])[0] + + except Exception as e: + self.log.debug('%s: attribute %s: %s' + % (self.value[Link.IFLA_INFO_SLAVE_KIND], + info_data_type, + str(e))) + sub_attr_data = sub_attr_data[info_data_end:] + + self.value[Link.IFLA_INFO_SLAVE_DATA] = ifla_info_slave_data - sub_attr_data = sub_attr_data[info_data_end:] + elif sub_attr_type == Link.IFLA_INFO_DATA: + sub_attr_data = data[4:sub_attr_end] + self.value[Link.IFLA_INFO_DATA] = {} - elif sub_attr_type == Link.IFLA_INFO_SLAVE_KIND: - self.value[Link.IFLA_INFO_SLAVE_KIND] = remove_trailing_null(unpack('%ds' % (sub_attr_length - 4), data[4:sub_attr_length])[0]) + ifla_info_kind = self.value.get(Link.IFLA_INFO_KIND) + if not ifla_info_kind: + self.log.warning('IFLA_INFO_KIND is not known...we cannot parse IFLA_INFO_DATA') + else: + while sub_attr_data: + (info_data_length, info_data_type) = unpack('=HH', sub_attr_data[:4]) + info_data_end = padded_length(info_data_length) + try: + if ifla_info_kind == 'vlan': + if info_data_type == Link.IFLA_VLAN_ID: + self.value[Link.IFLA_INFO_DATA][info_data_type] = unpack('=H', sub_attr_data[4:6])[0] + + elif info_data_type == Link.IFLA_VLAN_PROTOCOL: + hex_value = '0x%s' % sub_attr_data[4:6].encode('hex') + vlan_protocol = Link.ifla_vlan_protocol_dict.get(int(hex_value, base=16)) + + if vlan_protocol: + self.value[Link.IFLA_INFO_DATA][info_data_type] = vlan_protocol + else: + self.log.warning('IFLA_VLAN_PROTOCOL: cannot decode vlan protocol %s' % hex_value) + + else: + self.log.log(SYSLOG_EXTRA_DEBUG, 'Add support for decoding IFLA_INFO_KIND vlan type %s (%d), length %d, padded to %d' % + (parent_msg.get_ifla_vlan_string(info_data_type), info_data_type, info_data_length, info_data_end)) + + elif ifla_info_kind == 'macvlan': + if info_data_type == Link.IFLA_MACVLAN_MODE: + self.value[Link.IFLA_INFO_DATA][info_data_type] = unpack('=L', sub_attr_data[4:8])[0] + else: + self.log.log(SYSLOG_EXTRA_DEBUG, 'Add support for decoding IFLA_INFO_KIND macvlan type %s (%d), length %d, padded to %d' % + (parent_msg.get_ifla_macvlan_string(info_data_type), info_data_type, info_data_length, info_data_end)) + + elif ifla_info_kind == 'vxlan': + + # IPv4Address + if info_data_type in (Link.IFLA_VXLAN_GROUP, + Link.IFLA_VXLAN_LOCAL): + self.value[Link.IFLA_INFO_DATA][info_data_type] = IPv4Address(unpack('>L', sub_attr_data[4:8])[0]) + + # 4-byte int + elif info_data_type in (Link.IFLA_VXLAN_ID, + Link.IFLA_VXLAN_LINK, + Link.IFLA_VXLAN_AGEING, + Link.IFLA_VXLAN_LIMIT, + Link.IFLA_VXLAN_PORT_RANGE): + self.value[Link.IFLA_INFO_DATA][info_data_type] = unpack('=L', sub_attr_data[4:8])[0] + + # 2-byte int + elif info_data_type in (Link.IFLA_VXLAN_PORT, ): + self.value[Link.IFLA_INFO_DATA][info_data_type] = unpack('!H', sub_attr_data[4:6])[0] + # The form '!' is available for those poor souls who claim they can't + # remember whether network byte order is big-endian or little-endian. + + # 1-byte int + elif info_data_type in (Link.IFLA_VXLAN_TTL, + Link.IFLA_VXLAN_TOS, + Link.IFLA_VXLAN_LEARNING, + Link.IFLA_VXLAN_PROXY, + Link.IFLA_VXLAN_RSC, + Link.IFLA_VXLAN_L2MISS, + Link.IFLA_VXLAN_L3MISS, + Link.IFLA_VXLAN_UDP_CSUM, + Link.IFLA_VXLAN_UDP_ZERO_CSUM6_TX, + Link.IFLA_VXLAN_UDP_ZERO_CSUM6_RX, + Link.IFLA_VXLAN_REMCSUM_TX, + Link.IFLA_VXLAN_REMCSUM_RX, + Link.IFLA_VXLAN_REPLICATION_TYPE): + self.value[Link.IFLA_INFO_DATA][info_data_type] = unpack('=B', sub_attr_data[4])[0] + + else: + # sub_attr_end = padded_length(sub_attr_length) + self.log.log(SYSLOG_EXTRA_DEBUG, 'Add support for decoding IFLA_INFO_KIND vxlan type %s (%d), length %d, padded to %d' % + (parent_msg.get_ifla_vxlan_string(info_data_type), info_data_type, info_data_length, info_data_end)) + + elif ifla_info_kind == 'bond': + + if info_data_type in (Link.IFLA_BOND_AD_INFO, ): + ad_attr_data = sub_attr_data[4:info_data_end] + self.value[Link.IFLA_INFO_DATA][Link.IFLA_BOND_AD_INFO] = {} + + while ad_attr_data: + (ad_data_length, ad_data_type) = unpack('=HH', ad_attr_data[:4]) + ad_data_end = padded_length(ad_data_length) + + if ad_data_type in (Link.IFLA_BOND_AD_INFO_PARTNER_MAC,): + (data1, data2) = unpack('>LHxx', ad_attr_data[4:12]) + self.value[Link.IFLA_INFO_DATA][Link.IFLA_BOND_AD_INFO][ad_data_type] = mac_int_to_str(data1 << 16 | data2) + + ad_attr_data = ad_attr_data[ad_data_end:] + + # 1-byte int + elif info_data_type in (Link.IFLA_BOND_MODE, + Link.IFLA_BOND_USE_CARRIER, + Link.IFLA_BOND_AD_LACP_RATE, + Link.IFLA_BOND_AD_LACP_BYPASS, + Link.IFLA_BOND_XMIT_HASH_POLICY, + Link.IFLA_BOND_NUM_PEER_NOTIF): + self.value[Link.IFLA_INFO_DATA][info_data_type] = unpack('=B', sub_attr_data[4])[0] + + # 2-bytes int + elif info_data_type == Link.IFLA_BOND_AD_ACTOR_SYS_PRIO: + self.value[Link.IFLA_INFO_DATA][info_data_type] = unpack('=H', sub_attr_data[4:6])[0] + + # 4-bytes int + elif info_data_type in (Link.IFLA_BOND_MIIMON, + Link.IFLA_BOND_UPDELAY, + Link.IFLA_BOND_DOWNDELAY, + Link.IFLA_BOND_MIN_LINKS): + self.value[Link.IFLA_INFO_DATA][info_data_type] = unpack('=L', sub_attr_data[4:8])[0] + + # mac address + elif info_data_type in (Link.IFLA_BOND_AD_ACTOR_SYSTEM, ): + (data1, data2) = unpack('>LHxx', sub_attr_data[4:12]) + self.value[Link.IFLA_INFO_DATA][info_data_type] = mac_int_to_str(data1 << 16 | data2) + + else: + self.log.log(SYSLOG_EXTRA_DEBUG, 'Add support for decoding IFLA_INFO_KIND bond type %s (%d), length %d, padded to %d' % + (parent_msg.get_ifla_bond_string(info_data_type), info_data_type, info_data_length, info_data_end)) + + elif ifla_info_kind == 'bridge': + # 4 bytes + if info_data_type in (Link.IFLA_BR_AGEING_TIME, + Link.IFLA_BR_FORWARD_DELAY, + Link.IFLA_BR_HELLO_TIME, + Link.IFLA_BR_MAX_AGE, + Link.IFLA_BR_STP_STATE, + Link.IFLA_BR_ROOT_PATH_COST, + Link.IFLA_BR_MCAST_HASH_ELASTICITY, + Link.IFLA_BR_MCAST_HASH_MAX, + Link.IFLA_BR_MCAST_LAST_MEMBER_CNT, + Link.IFLA_BR_MCAST_STARTUP_QUERY_CNT): + self.value[Link.IFLA_INFO_DATA][info_data_type] = unpack('=L', sub_attr_data[4:8])[0] + + # 2 bytes + elif info_data_type in (Link.IFLA_BR_PRIORITY, + Link.IFLA_BR_GROUP_FWD_MASK, + Link.IFLA_BR_ROOT_PORT, + Link.IFLA_BR_VLAN_DEFAULT_PVID): + self.value[Link.IFLA_INFO_DATA][info_data_type] = unpack('=H', sub_attr_data[4:6])[0] + + elif info_data_type == Link.IFLA_BR_VLAN_PROTOCOL: + hex_value = '0x%s' % sub_attr_data[4:6].encode('hex') + vlan_protocol = Link.ifla_vlan_protocol_dict.get(int(hex_value, base=16)) + + if vlan_protocol: + self.value[Link.IFLA_INFO_DATA][info_data_type] = vlan_protocol + else: + self.log.warning('IFLA_VLAN_PROTOCOL: cannot decode vlan protocol %s' % hex_value) + + # 8 bytes + elif info_data_type in (Link.IFLA_BR_MCAST_MEMBERSHIP_INTVL, + Link.IFLA_BR_MCAST_QUERIER_INTVL, + Link.IFLA_BR_MCAST_LAST_MEMBER_INTVL, + Link.IFLA_BR_MCAST_MEMBERSHIP_INTVL, + Link.IFLA_BR_MCAST_QUERIER_INTVL, + Link.IFLA_BR_MCAST_QUERY_INTVL, + Link.IFLA_BR_MCAST_QUERY_RESPONSE_INTVL, + Link.IFLA_BR_MCAST_STARTUP_QUERY_INTVL): + self.value[Link.IFLA_INFO_DATA][info_data_type] = unpack('=Q', sub_attr_data[4:12])[0] + + # 1 bytes + elif info_data_type in (Link.IFLA_BR_VLAN_FILTERING, + Link.IFLA_BR_TOPOLOGY_CHANGE, + Link.IFLA_BR_TOPOLOGY_CHANGE_DETECTED, + Link.IFLA_BR_MCAST_ROUTER, + Link.IFLA_BR_MCAST_SNOOPING, + Link.IFLA_BR_MCAST_QUERY_USE_IFADDR, + Link.IFLA_BR_MCAST_QUERIER, + Link.IFLA_BR_NF_CALL_IPTABLES, + Link.IFLA_BR_NF_CALL_IP6TABLES, + Link.IFLA_BR_NF_CALL_ARPTABLES, + Link.IFLA_BR_VLAN_STATS_ENABLED, + Link.IFLA_BR_MCAST_STATS_ENABLED, + Link.IFLA_BR_MCAST_IGMP_VERSION, + Link.IFLA_BR_MCAST_MLD_VERSION): + self.value[Link.IFLA_INFO_DATA][info_data_type] = unpack('=B', sub_attr_data[4])[0] + else: + self.log.log(SYSLOG_EXTRA_DEBUG, 'Add support for decoding IFLA_INFO_KIND bridge type %s (%d), length %d, padded to %d' % + (parent_msg.get_ifla_br_string(info_data_type), info_data_type, info_data_length, info_data_end)) + + elif ifla_info_kind == 'vrf': + + if info_data_type in (Link.IFLA_VRF_TABLE,): + self.value[Link.IFLA_INFO_DATA][info_data_type] = unpack('=L', sub_attr_data[4:8])[0] + + + else: + self.log.log(SYSLOG_EXTRA_DEBUG, "Add support for decoding IFLA_INFO_KIND %s (%d), length %d, padded to %d" % + (ifla_info_kind, info_data_type, info_data_length, info_data_end)) + + except Exception as e: + self.log.debug('%s: attribute %s: %s' + % (self.value[Link.IFLA_INFO_KIND], + info_data_type, + str(e))) + sub_attr_data = sub_attr_data[info_data_end:] - elif EXTRA_DEBUG: - self.log.debug('Add support for decoding IFLA_LINKINFO sub-attribute type %s (%d), length %d, padded to %d' % - (parent_msg.get_ifla_info_string(sub_attr_type), sub_attr_type, sub_attr_length, sub_attr_end)) + else: + self.log.log(SYSLOG_EXTRA_DEBUG, 'Add support for decoding IFLA_LINKINFO sub-attribute type %s (%d), length %d, padded to %d' % + (parent_msg.get_ifla_info_string(sub_attr_type), sub_attr_type, sub_attr_length, sub_attr_end)) data = data[sub_attr_end:] @@ -1175,39 +1812,48 @@ class AttributeIFLA_LINKINFO(Attribute): return line_number - def get_pretty_value(self): - value_pretty = self.value - ifla_info_kind = self.value.get(Link.IFLA_INFO_KIND) + def get_pretty_value(self, obj=None): + + if obj and callable(obj): + return obj(self.value) + + value_pretty = self.value + ifla_info_kind = self.value.get(Link.IFLA_INFO_KIND) + ifla_info_slave_kind = self.value.get(Link.IFLA_INFO_SLAVE_KIND) + + kind_dict = dict() # We do this so we can print a more human readable dictionary # with the names of the nested keys instead of their numbers # Most of these are placeholders...we need to add support # for more human readable dictionaries for bond, bridge, etc - if ifla_info_kind == 'bond': - pass - - elif ifla_info_kind == 'bridge': - pass - - elif ifla_info_kind == 'macvlan': - pass - - elif ifla_info_kind == 'vlan': - pass - - elif ifla_info_kind == 'vxlan': + kind_dict[Link.IFLA_INFO_DATA] = { + 'bond': Link.ifla_bond_to_string, + 'vlan': Link.ifla_vlan_to_string, + 'vxlan': Link.ifla_vxlan_to_string, + 'bridge': Link.ifla_br_to_string, + 'macvlan': Link.ifla_macvlan_to_string + }.get(ifla_info_kind, {}) + + kind_dict[Link.IFLA_INFO_SLAVE_DATA] = { + 'bridge': Link.ifla_brport_to_string, + 'bond': Link.ifla_bond_slave_to_string + }.get(ifla_info_slave_kind, {}) + + if ifla_info_kind or ifla_info_slave_kind: value_pretty = {} for (sub_key, sub_value) in self.value.iteritems(): - sub_key_pretty = "(%2d) % s" % (sub_key, Link.ifla_info_to_string[sub_key]) + sub_key_pretty = "(%2d) %s" % (sub_key, Link.ifla_info_to_string.get(sub_key, 'UNKNOWN')) sub_value_pretty = sub_value - if sub_key == Link.IFLA_INFO_DATA: + if sub_key in (Link.IFLA_INFO_DATA, Link.IFLA_INFO_SLAVE_DATA): + kind_to_string_dict = kind_dict.get(sub_key, {}) sub_value_pretty = {} for (sub_sub_key, sub_sub_value) in sub_value.iteritems(): - sub_sub_key_pretty = "(%2d) %s" % (sub_sub_key, Link.ifla_vxlan_to_string[sub_sub_key]) + sub_sub_key_pretty = "(%2d) %s" % (sub_sub_key, kind_to_string_dict.get(sub_sub_key, 'UNKNOWN')) sub_value_pretty[sub_sub_key_pretty] = sub_sub_value value_pretty[sub_key_pretty] = sub_value_pretty @@ -1215,6 +1861,235 @@ class AttributeIFLA_LINKINFO(Attribute): return value_pretty +class AttributeIFLA_PROTINFO(Attribute): + """ + IFLA_PROTINFO nested attributes. + """ + def __init__(self, atype, string, family, logger): + Attribute.__init__(self, atype, string, logger) + self.family = family + + def encode(self): + pack_layout = [self.HEADER_PACK] + payload = [0, self.atype | NLA_F_NESTED] + attr_length_index = 0 + + if self.family not in (AF_BRIDGE,): + raise Exception('Unsupported IFLA_PROTINFO family %d' % self.family) + + # For now this assumes that all data will be packed in the native endian + # order (=). If a field is added that needs to be packed via network + # order (>) then some smarts will need to be added to split the pack_layout + # string at the >, split the payload and make the needed pack() calls. + # + # Until we cross that bridge though we will keep things nice and simple and + # pack everything via a single pack() call. + for (sub_attr_type, sub_attr_value) in self.value.iteritems(): + sub_attr_pack_layout = ['=', 'HH'] + sub_attr_payload = [0, sub_attr_type] + sub_attr_length_index = 0 + + if self.family == AF_BRIDGE: + # 1 Byte attributes + if sub_attr_type in (Link.IFLA_BRPORT_STATE, + Link.IFLA_BRPORT_MODE, + Link.IFLA_BRPORT_GUARD, + Link.IFLA_BRPORT_PROTECT, + Link.IFLA_BRPORT_FAST_LEAVE, + Link.IFLA_BRPORT_LEARNING, + Link.IFLA_BRPORT_UNICAST_FLOOD, + Link.IFLA_BRPORT_PROXYARP, + Link.IFLA_BRPORT_LEARNING_SYNC, + Link.IFLA_BRPORT_PROXYARP_WIFI, + Link.IFLA_BRPORT_TOPOLOGY_CHANGE_ACK, + Link.IFLA_BRPORT_CONFIG_PENDING, + Link.IFLA_BRPORT_FLUSH, + Link.IFLA_BRPORT_MULTICAST_ROUTER, + Link.IFLA_BRPORT_PEER_LINK, + Link.IFLA_BRPORT_DUAL_LINK, + Link.IFLA_BRPORT_ARP_SUPPRESS, + Link.IFLA_BRPORT_DOWN_PEERLINK_REDIRECT): + sub_attr_pack_layout.append('B') + sub_attr_payload.append(sub_attr_value) + sub_attr_pack_layout.extend('xxx') + + # 2 Byte attributes + elif sub_attr_type in (Link.IFLA_BRPORT_PRIORITY, + Link.IFLA_BRPORT_DESIGNATED_PORT, + Link.IFLA_BRPORT_DESIGNATED_COST, + Link.IFLA_BRPORT_ID, + Link.IFLA_BRPORT_NO): + sub_attr_pack_layout.append('H') + sub_attr_payload.append(sub_attr_value) + sub_attr_pack_layout.extend('xx') + + # 4 Byte attributes + elif sub_attr_type in (Link.IFLA_BRPORT_COST,): + sub_attr_pack_layout.append('L') + sub_attr_payload.append(sub_attr_value) + + # 8 Byte attributes + elif sub_attr_type in (Link.IFLA_BRPORT_MESSAGE_AGE_TIMER, + Link.IFLA_BRPORT_FORWARD_DELAY_TIMER, + Link.IFLA_BRPORT_HOLD_TIMER): + sub_attr_pack_layout.append('Q') + sub_attr_payload.append(sub_attr_value) + + else: + self.log.log(SYSLOG_EXTRA_DEBUG, 'Add support for encoding IFLA_PROTINFO sub-attribute type %d' % sub_attr_type) + + sub_attr_length = calcsize(''.join(sub_attr_pack_layout)) + sub_attr_payload[sub_attr_length_index] = sub_attr_length + + # add padding + for x in xrange(self.pad_bytes_needed(sub_attr_length)): + sub_attr_pack_layout.append('x') + + # The [1:] is to remove the leading = so that when we do the ''.join() later + # we do not end up with an = in the middle of the pack layout string. There + # will be an = at the beginning via self.HEADER_PACK + sub_attr_pack_layout = sub_attr_pack_layout[1:] + + # Now extend the ovarall attribute pack_layout/payload to include this sub-attribute + pack_layout.extend(sub_attr_pack_layout) + payload.extend(sub_attr_payload) + + pack_layout = ''.join(pack_layout) + + # Fill in the length field + length = calcsize(pack_layout) + payload[attr_length_index] = length + + raw = pack(pack_layout, *payload) + raw = self.pad(length, raw) + return raw + + def decode(self, parent_msg, data): + """ + value is a dictionary such as: + { + Link.IFLA_BRPORT_STATE : 3, + Link.IFLA_BRPORT_PRIORITY : 8 + Link.IFLA_BRPORT_COST : 2 + ... + } + """ + self.decode_length_type(data) + self.value = {} + + data = self.data[4:] + + while data: + (sub_attr_length, sub_attr_type) = unpack('=HH', data[:4]) + sub_attr_end = padded_length(sub_attr_length) + + if not sub_attr_length: + self.log.error('parsed a zero length sub-attr') + return + + if self.family == AF_BRIDGE: + + # 1 Byte attributes + if sub_attr_type in (Link.IFLA_BRPORT_STATE, + Link.IFLA_BRPORT_MODE, + Link.IFLA_BRPORT_GUARD, + Link.IFLA_BRPORT_PROTECT, + Link.IFLA_BRPORT_FAST_LEAVE, + Link.IFLA_BRPORT_LEARNING, + Link.IFLA_BRPORT_UNICAST_FLOOD, + Link.IFLA_BRPORT_PROXYARP, + Link.IFLA_BRPORT_LEARNING_SYNC, + Link.IFLA_BRPORT_PROXYARP_WIFI, + Link.IFLA_BRPORT_TOPOLOGY_CHANGE_ACK, + Link.IFLA_BRPORT_CONFIG_PENDING, + Link.IFLA_BRPORT_FLUSH, + Link.IFLA_BRPORT_MULTICAST_ROUTER, + Link.IFLA_BRPORT_PEER_LINK, + Link.IFLA_BRPORT_DUAL_LINK, + Link.IFLA_BRPORT_ARP_SUPPRESS, + Link.IFLA_BRPORT_DOWN_PEERLINK_REDIRECT): + self.value[sub_attr_type] = unpack('=B', data[4])[0] + + # 2 Byte attributes + elif sub_attr_type in (Link.IFLA_BRPORT_PRIORITY, + Link.IFLA_BRPORT_DESIGNATED_PORT, + Link.IFLA_BRPORT_DESIGNATED_COST, + Link.IFLA_BRPORT_ID, + Link.IFLA_BRPORT_NO): + self.value[sub_attr_type] = unpack('=H', data[4:6])[0] + + # 4 Byte attributes + elif sub_attr_type in (Link.IFLA_BRPORT_COST,): + self.value[sub_attr_type] = unpack('=L', data[4:8])[0] + + # 8 Byte attributes + elif sub_attr_type in (Link.IFLA_BRPORT_MESSAGE_AGE_TIMER, + Link.IFLA_BRPORT_FORWARD_DELAY_TIMER, + Link.IFLA_BRPORT_HOLD_TIMER): + self.value[sub_attr_type] = unpack('=Q', data[4:12])[0] + + else: + self.log.log(SYSLOG_EXTRA_DEBUG, 'Add support for decoding IFLA_PROTINFO sub-attribute type %s (%d), length %d, padded to %d' % + (parent_msg.get_ifla_brport_string(sub_attr_type), sub_attr_type, sub_attr_length, sub_attr_end)) + + data = data[sub_attr_end:] + + def dump_lines(self, dump_buffer, line_number, color): + line_number = self.dump_first_line(dump_buffer, line_number, color) + extra = '' + + next_sub_attr_line = 0 + sub_attr_line = True + + for x in xrange(1, self.attr_end/4): + start = x * 4 + end = start + 4 + + if line_number == next_sub_attr_line: + sub_attr_line = True + + if sub_attr_line: + sub_attr_line = False + + (sub_attr_length, sub_attr_type) = unpack('=HH', self.data[start:start+4]) + sub_attr_end = padded_length(sub_attr_length) + + next_sub_attr_line = line_number + (sub_attr_end/4) + + if sub_attr_end == sub_attr_length: + padded_to = ', ' + else: + padded_to = ' padded to %d, ' % sub_attr_end + + extra = 'Nested Attribute - Length %s (%d)%s Type %s (%d) %s' % \ + (zfilled_hex(sub_attr_length, 4), sub_attr_length, + padded_to, + zfilled_hex(sub_attr_type, 4), sub_attr_type, + Link.ifla_brport_to_string.get(sub_attr_type)) + else: + extra = '' + + dump_buffer.append(data_to_color_text(line_number, color, self.data[start:end], extra)) + line_number += 1 + + return line_number + + def get_pretty_value(self, obj=None): + + if obj and callable(obj): + return obj(self.value) + + value_pretty = {} + + for (sub_key, sub_value) in self.value.iteritems(): + sub_key_pretty = "(%2d) %s" % (sub_key, Link.ifla_brport_to_string.get(sub_key, 'UNKNOWN')) + sub_value_pretty = sub_value + value_pretty[sub_key_pretty] = sub_value_pretty + + return value_pretty + + + class NetlinkPacket(object): """ Netlink Header @@ -1260,6 +2135,11 @@ class NetlinkPacket(object): RTM_GETQDISC : 'RTM_GETQDISC' } + af_family_to_string = { + AF_INET : 'inet', + AF_INET6 : 'inet6' + } + def __init__(self, msgtype, debug, owner_logger=None, use_color=True): self.msgtype = msgtype self.attributes = {} @@ -1460,6 +2340,16 @@ class NetlinkPacket(object): # case of RTA_DST. if attr_type in self.attribute_to_class: (attr_string, attr_class) = self.attribute_to_class[attr_type] + + ''' + attribute_to_class is a dictionary where the key is the attr_type, it doesn't + take the family into account. For now we'll handle this as a special case for + MPLS but long term we may need to make key a tuple of the attr_type and family. + ''' + if attr_type == Route.RTA_DST and self.family == AF_MPLS: + attr_string = 'RTA_DST' + attr_class = AttributeMplsLabel + else: attr_string = "UNKNOWN_ATTRIBUTE_%d" % attr_type attr_class = AttributeGeneric @@ -1478,9 +2368,9 @@ class NetlinkPacket(object): return attr - def get_attribute_value(self, attr_type): + def get_attribute_value(self, attr_type, default=None): if attr_type not in self.attributes: - return None + return default return self.attributes[attr_type].value @@ -1518,7 +2408,7 @@ class NetlinkPacket(object): for k,v in dic.iteritems(): if isinstance(v, dict): self.log.debug(' '*level + str(k) + ':') - self.pretty_display_dict(v, level+1) + self.pretty_display_dict(v, level+5) else: self.log.debug(' '*level + str(k) + ': ' + str(v)) @@ -1829,7 +2719,8 @@ class Link(NetlinkPacket): IFLA_LINK_NETNSID = 37 IFLA_PHYS_PORT_NAME = 38 IFLA_PROTO_DOWN = 39 - IFLA_LINKPROTODOWN = 200 + IFLA_GSO_MAX_SEGS = 40 + IFLA_GSO_MAX_SIZE = 41 attribute_to_class = { IFLA_UNSPEC : ('IFLA_UNSPEC', AttributeGeneric), @@ -1844,12 +2735,12 @@ class Link(NetlinkPacket): IFLA_PRIORITY : ('IFLA_PRIORITY', AttributeGeneric), IFLA_MASTER : ('IFLA_MASTER', AttributeFourByteValue), IFLA_WIRELESS : ('IFLA_WIRELESS', AttributeGeneric), - IFLA_PROTINFO : ('IFLA_PROTINFO', AttributeGeneric), # Create an AttributeProtinfo class for this + IFLA_PROTINFO : ('IFLA_PROTINFO', AttributeIFLA_PROTINFO), IFLA_TXQLEN : ('IFLA_TXQLEN', AttributeFourByteValue), IFLA_MAP : ('IFLA_MAP', AttributeGeneric), IFLA_WEIGHT : ('IFLA_WEIGHT', AttributeGeneric), - IFLA_OPERSTATE : ('IFLA_OPERSTATE', AttributeFourByteValue), - IFLA_LINKMODE : ('IFLA_LINKMODE', AttributeFourByteValue), + IFLA_OPERSTATE : ('IFLA_OPERSTATE', AttributeOneByteValue), + IFLA_LINKMODE : ('IFLA_LINKMODE', AttributeOneByteValue), IFLA_LINKINFO : ('IFLA_LINKINFO', AttributeIFLA_LINKINFO), IFLA_NET_NS_PID : ('IFLA_NET_NS_PID', AttributeGeneric), IFLA_IFALIAS : ('IFLA_IFALIAS', AttributeGeneric), @@ -1872,7 +2763,8 @@ class Link(NetlinkPacket): IFLA_LINK_NETNSID : ('IFLA_LINK_NETNSID', AttributeGeneric), IFLA_PHYS_PORT_NAME : ('IFLA_PHYS_PORT_NAME', AttributeGeneric), IFLA_PROTO_DOWN : ('IFLA_PROTO_DOWN', AttributeOneByteValue), - IFLA_LINKPROTODOWN : ('IFLA_LINKPROTODOWN', AttributeGeneric) + IFLA_GSO_MAX_SEGS : ('IFLA_GSO_MAX_SEGS', AttributeFourByteValue), + IFLA_GSO_MAX_SIZE : ('IFLA_GSO_MAX_SIZE', AttributeFourByteValue) } # Link flags @@ -2111,6 +3003,19 @@ class Link(NetlinkPacket): IFLA_VLAN_PROTOCOL : 'IFLA_VLAN_PROTOCOL' } + ifla_vlan_protocol_dict = { + '802.1Q': 0x8100, + '802.1q': 0x8100, + + '802.1ad': 0x88A8, + '802.1AD': 0x88A8, + '802.1Ad': 0x88A8, + '802.1aD': 0x88A8, + + 0x8100: '802.1Q', + 0x88A8: '802.1ad' + } + # ========================================= # IFLA_INFO_DATA attributes for macvlan # ========================================= @@ -2228,11 +3133,7 @@ class Link(NetlinkPacket): IFLA_BOND_AD_ACTOR_SYS_PRIO = 24 IFLA_BOND_AD_USER_PORT_KEY = 25 IFLA_BOND_AD_ACTOR_SYSTEM = 26 - IFLA_BOND_CL_LACP_BYPASS_ALLOW = 100 - IFLA_BOND_CL_LACP_BYPASS_ACTIVE = 101 - IFLA_BOND_CL_LACP_BYPASS_PERIOD = 102 - IFLA_BOND_CL_CLAG_ENABLE = 103 - IFLA_BOND_CL_LACP_BYPASS_ALL_ACTIVE = 104 + IFLA_BOND_AD_LACP_BYPASS = 100 ifla_bond_to_string = { IFLA_BOND_UNSPEC : 'IFLA_BOND_UNSPEC', @@ -2262,15 +3163,116 @@ class Link(NetlinkPacket): IFLA_BOND_AD_ACTOR_SYS_PRIO : 'IFLA_BOND_AD_ACTOR_SYS_PRIO', IFLA_BOND_AD_USER_PORT_KEY : 'IFLA_BOND_AD_USER_PORT_KEY', IFLA_BOND_AD_ACTOR_SYSTEM : 'IFLA_BOND_AD_ACTOR_SYSTEM', - IFLA_BOND_CL_LACP_BYPASS_ALLOW : 'IFLA_BOND_CL_LACP_BYPASS_ALLOW', - IFLA_BOND_CL_LACP_BYPASS_ACTIVE : 'IFLA_BOND_CL_LACP_BYPASS_ACTIVE', - IFLA_BOND_CL_LACP_BYPASS_PERIOD : 'IFLA_BOND_CL_LACP_BYPASS_PERIOD', - IFLA_BOND_CL_CLAG_ENABLE : 'IFLA_BOND_CL_CLAG_ENABLE', - IFLA_BOND_CL_LACP_BYPASS_ALL_ACTIVE : 'IFLA_BOND_CL_LACP_BYPASS_ALL_ACTIVE' + IFLA_BOND_AD_LACP_BYPASS : 'IFLA_BOND_AD_LACP_BYPASS' + } + + IFLA_BOND_AD_INFO_UNSPEC = 0 + IFLA_BOND_AD_INFO_AGGREGATOR = 1 + IFLA_BOND_AD_INFO_NUM_PORTS = 2 + IFLA_BOND_AD_INFO_ACTOR_KEY = 3 + IFLA_BOND_AD_INFO_PARTNER_KEY = 4 + IFLA_BOND_AD_INFO_PARTNER_MAC = 5 + + ifla_bond_ad_to_string = { + IFLA_BOND_AD_INFO_UNSPEC : 'IFLA_BOND_AD_INFO_UNSPEC', + IFLA_BOND_AD_INFO_AGGREGATOR : 'IFLA_BOND_AD_INFO_AGGREGATOR', + IFLA_BOND_AD_INFO_NUM_PORTS : 'IFLA_BOND_AD_INFO_NUM_PORTS', + IFLA_BOND_AD_INFO_ACTOR_KEY : 'IFLA_BOND_AD_INFO_ACTOR_KEY', + IFLA_BOND_AD_INFO_PARTNER_KEY : 'IFLA_BOND_AD_INFO_PARTNER_KEY', + IFLA_BOND_AD_INFO_PARTNER_MAC : 'IFLA_BOND_AD_INFO_PARTNER_MAC' + } + + ifla_bond_mode_tbl = { + 'balance-rr': 0, + 'active-backup': 1, + 'balance-xor': 2, + 'broadcast': 3, + '802.3ad': 4, + 'balance-tlb': 5, + 'balance-alb': 6, + '0': 0, + '1': 1, + '2': 2, + '3': 3, + '4': 4, + '5': 5, + '6': 6, + 0: 0, + 1: 1, + 2: 2, + 3: 3, + 4: 4, + 5: 5, + 6: 6 + } + + ifla_bond_mode_pretty_tbl = { + 0: 'balance-rr', + 1: 'active-backup', + 2: 'balance-xor', + 3: 'broadcast', + 4: '802.3ad', + 5: 'balance-tlb', + 6: 'balance-alb' + } + + ifla_bond_xmit_hash_policy_tbl = { + 'layer2': 0, + 'layer3+4': 1, + 'layer2+3': 2, + 'encap2+3': 3, + 'encap3+4': 4, + '0': 0, + '1': 1, + '2': 2, + '3': 3, + '4': 4, + 0: 0, + 1: 1, + 2: 2, + 3: 3, + 4: 4 + } + + ifla_bond_xmit_hash_policy_pretty_tbl = { + 0: 'layer2', + 1: 'layer3+4', + 2: 'layer2+3', + 3: 'encap2+3', + 4: 'encap3+4', } # ========================================= - # IFLA_INFO_DATA attributes for bridges + # IFLA_INFO_SLAVE_DATA attributes for bonds + # ========================================= + IFLA_BOND_SLAVE_UNSPEC = 0 + IFLA_BOND_SLAVE_STATE = 1 + IFLA_BOND_SLAVE_MII_STATUS = 2 + IFLA_BOND_SLAVE_LINK_FAILURE_COUNT = 3 + IFLA_BOND_SLAVE_PERM_HWADDR = 4 + IFLA_BOND_SLAVE_QUEUE_ID = 5 + IFLA_BOND_SLAVE_AD_AGGREGATOR_ID = 6 + IFLA_BOND_SLAVE_AD_ACTOR_OPER_PORT_STATE = 7 + IFLA_BOND_SLAVE_AD_PARTNER_OPER_PORT_STATE = 8 + IFLA_BOND_SLAVE_CL_START = 50 + IFLA_BOND_SLAVE_AD_RX_BYPASS = IFLA_BOND_SLAVE_CL_START + + ifla_bond_slave_to_string = { + IFLA_BOND_SLAVE_UNSPEC : 'IFLA_BOND_SLAVE_UNSPEC', + IFLA_BOND_SLAVE_STATE : 'IFLA_BOND_SLAVE_STATE', + IFLA_BOND_SLAVE_MII_STATUS : 'IFLA_BOND_SLAVE_MII_STATUS', + IFLA_BOND_SLAVE_LINK_FAILURE_COUNT : 'IFLA_BOND_SLAVE_LINK_FAILURE_COUNT', + IFLA_BOND_SLAVE_PERM_HWADDR : 'IFLA_BOND_SLAVE_PERM_HWADDR', + IFLA_BOND_SLAVE_QUEUE_ID : 'IFLA_BOND_SLAVE_QUEUE_ID', + IFLA_BOND_SLAVE_AD_AGGREGATOR_ID : 'IFLA_BOND_SLAVE_AD_AGGREGATOR_ID', + IFLA_BOND_SLAVE_AD_ACTOR_OPER_PORT_STATE : 'IFLA_BOND_SLAVE_AD_ACTOR_OPER_PORT_STATE', + IFLA_BOND_SLAVE_AD_PARTNER_OPER_PORT_STATE : 'IFLA_BOND_SLAVE_AD_PARTNER_OPER_PORT_STATE', + IFLA_BOND_SLAVE_CL_START : 'IFLA_BOND_SLAVE_CL_START', + IFLA_BOND_SLAVE_AD_RX_BYPASS : 'IFLA_BOND_SLAVE_AD_RX_BYPASS' + } + + # ========================================= + # IFLA_PROTINFO attributes for bridge ports # ========================================= IFLA_BRPORT_UNSPEC = 0 IFLA_BRPORT_STATE = 1 @@ -2298,10 +3300,19 @@ class Link(NetlinkPacket): IFLA_BRPORT_HOLD_TIMER = 23 IFLA_BRPORT_FLUSH = 24 IFLA_BRPORT_MULTICAST_ROUTER = 25 + IFLA_BRPORT_PAD = 26 + IFLA_BRPORT_MCAST_FLOOD = 27 + IFLA_BRPORT_MCAST_TO_UCAST = 28 + IFLA_BRPORT_VLAN_TUNNEL = 29 + IFLA_BRPORT_BCAST_FLOOD = 30 + IFLA_BRPORT_GROUP_FWD_MASK = 31 IFLA_BRPORT_PEER_LINK = 150 IFLA_BRPORT_DUAL_LINK = 151 + IFLA_BRPORT_ARP_SUPPRESS = 152 + IFLA_BRPORT_GROUP_FWD_MASKHI = 153 + IFLA_BRPORT_DOWN_PEERLINK_REDIRECT = 154 - ifla_bridge_to_string = { + ifla_brport_to_string = { IFLA_BRPORT_UNSPEC : 'IFLA_BRPORT_UNSPEC', IFLA_BRPORT_STATE : 'IFLA_BRPORT_STATE', IFLA_BRPORT_PRIORITY : 'IFLA_BRPORT_PRIORITY', @@ -2328,8 +3339,17 @@ class Link(NetlinkPacket): IFLA_BRPORT_HOLD_TIMER : 'IFLA_BRPORT_HOLD_TIMER', IFLA_BRPORT_FLUSH : 'IFLA_BRPORT_FLUSH', IFLA_BRPORT_MULTICAST_ROUTER : 'IFLA_BRPORT_MULTICAST_ROUTER', + IFLA_BRPORT_PAD : 'IFLA_BRPORT_PAD', + IFLA_BRPORT_MCAST_FLOOD : 'IFLA_BRPORT_MCAST_FLOOD', + IFLA_BRPORT_MCAST_TO_UCAST : 'IFLA_BRPORT_MCAST_TO_UCAST', + IFLA_BRPORT_VLAN_TUNNEL : 'IFLA_BRPORT_VLAN_TUNNEL', + IFLA_BRPORT_BCAST_FLOOD : 'IFLA_BRPORT_BCAST_FLOOD', + IFLA_BRPORT_GROUP_FWD_MASK : 'IFLA_BRPORT_GROUP_FWD_MASK', IFLA_BRPORT_PEER_LINK : 'IFLA_BRPORT_PEER_LINK', - IFLA_BRPORT_DUAL_LINK : 'IFLA_BRPORT_DUAL_LINK' + IFLA_BRPORT_DUAL_LINK : 'IFLA_BRPORT_DUAL_LINK', + IFLA_BRPORT_ARP_SUPPRESS : 'IFLA_BRPORT_ARP_SUPPRESS', + IFLA_BRPORT_GROUP_FWD_MASKHI : 'IFLA_BRPORT_GROUP_FWD_MASKHI', + IFLA_BRPORT_DOWN_PEERLINK_REDIRECT : 'IFLA_BRPORT_DOWN_PEERLINK_REDIRECT' } # BRIDGE IFLA_AF_SPEC attributes @@ -2344,18 +3364,20 @@ class Link(NetlinkPacket): } # BRIDGE_VLAN_INFO flags - BRIDGE_VLAN_INFO_MASTER = 1 << 0 - BRIDGE_VLAN_INFO_PVID = 1 << 1 - BRIDGE_VLAN_INFO_UNTAGGED = 1 << 2 - BRIDGE_VLAN_INFO_RANGE_BEGIN = 1 << 3 - BRIDGE_VLAN_INFO_RANGE_END = 1 << 4 + BRIDGE_VLAN_INFO_MASTER = 1 << 0 # Operate on Bridge device as well + BRIDGE_VLAN_INFO_PVID = 1 << 1 # VLAN is PVID, ingress untagged + BRIDGE_VLAN_INFO_UNTAGGED = 1 << 2 # VLAN egresses untagged + BRIDGE_VLAN_INFO_RANGE_BEGIN = 1 << 3 # VLAN is start of vlan range + BRIDGE_VLAN_INFO_RANGE_END = 1 << 4 # VLAN is end of vlan range + BRIDGE_VLAN_INFO_BRENTRY = 1 << 5 # Global bridge VLAN entry bridge_vlan_to_string = { BRIDGE_VLAN_INFO_MASTER : 'BRIDGE_VLAN_INFO_MASTER', BRIDGE_VLAN_INFO_PVID : 'BRIDGE_VLAN_INFO_PVID', BRIDGE_VLAN_INFO_UNTAGGED : 'BRIDGE_VLAN_INFO_UNTAGGED', BRIDGE_VLAN_INFO_RANGE_BEGIN : 'BRIDGE_VLAN_INFO_RANGE_BEGIN', - BRIDGE_VLAN_INFO_RANGE_END : 'BRIDGE_VLAN_INFO_RANGE_END' + BRIDGE_VLAN_INFO_RANGE_END : 'BRIDGE_VLAN_INFO_RANGE_END', + BRIDGE_VLAN_INFO_BRENTRY : 'BRIDGE_VLAN_INFO_BRENTRY' } # Bridge flags @@ -2420,6 +3442,11 @@ class Link(NetlinkPacket): IFLA_BR_NF_CALL_IP6TABLES = 37 IFLA_BR_NF_CALL_ARPTABLES = 38 IFLA_BR_VLAN_DEFAULT_PVID = 39 + IFLA_BR_PAD = 40 + IFLA_BR_VLAN_STATS_ENABLED = 41 + IFLA_BR_MCAST_STATS_ENABLED = 42 + IFLA_BR_MCAST_IGMP_VERSION = 43 + IFLA_BR_MCAST_MLD_VERSION = 44 ifla_br_to_string = { IFLA_BR_UNSPEC : 'IFLA_BR_UNSPEC', @@ -2462,6 +3489,22 @@ class Link(NetlinkPacket): IFLA_BR_NF_CALL_IP6TABLES : 'IFLA_BR_NF_CALL_IP6TABLES', IFLA_BR_NF_CALL_ARPTABLES : 'IFLA_BR_NF_CALL_ARPTABLES', IFLA_BR_VLAN_DEFAULT_PVID : 'IFLA_BR_VLAN_DEFAULT_PVID', + IFLA_BR_PAD : 'IFLA_BR_PAD', + IFLA_BR_VLAN_STATS_ENABLED : 'IFLA_BR_VLAN_STATS_ENABLED', + IFLA_BR_MCAST_STATS_ENABLED : 'IFLA_BR_MCAST_STATS_ENABLED', + IFLA_BR_MCAST_IGMP_VERSION : 'IFLA_BR_MCAST_IGMP_VERSION', + IFLA_BR_MCAST_MLD_VERSION : 'IFLA_BR_MCAST_MLD_VERSION' + } + + # ========================================= + # IFLA_INFO_DATA attributes for vrfs + # ========================================= + IFLA_VRF_UNSPEC = 0 + IFLA_VRF_TABLE = 1 + + ifla_vrf_to_string = { + IFLA_VRF_UNSPEC : 'IFLA_VRF_UNSPEC', + IFLA_VRF_TABLE : 'IFLA_VRF_TABLE' } def __init__(self, msgtype, debug=False, logger=None, use_color=True): @@ -2493,8 +3536,11 @@ class Link(NetlinkPacket): def get_ifla_bond_string(self, index): return self.get_string(self.ifla_bond_to_string, index) - def get_ifla_bridge_string(self, index): - return self.get_string(self.ifla_bridge_to_string, index) + def get_ifla_bond_ad_string(self, index): + return self.get_string(self.ifla_bond_ad_to_string, index) + + def get_ifla_brport_string(self, index): + return self.get_string(self.ifla_brport_to_string, index) def get_ifla_br_string(self, index): return self.get_string(self.ifla_br_to_string, index) @@ -2593,18 +3639,20 @@ class Neighbor(NetlinkPacket): # Neighbor flags # /usr/include/linux/neighbour.h - NTF_USE = 0x01 - NTF_SELF = 0x02 - NTF_MASTER = 0x04 - NTF_PROXY = 0x08 # A proxy ARP entry - NTF_ROUTER = 0x80 # An IPv6 router + NTF_USE = 0x01 + NTF_SELF = 0x02 + NTF_MASTER = 0x04 + NTF_PROXY = 0x08 # A proxy ARP entry + NTF_EXT_LEARNED = 0x10 # neigh entry installed by an external APP + NTF_ROUTER = 0x80 # An IPv6 router flag_to_string = { - NTF_USE : 'NTF_USE', - NTF_SELF : 'NTF_SELF', - NTF_MASTER : 'NTF_MASTER', - NTF_PROXY : 'NTF_PROXY', - NTF_ROUTER : 'NTF_ROUTER' + NTF_USE : 'NTF_USE', + NTF_SELF : 'NTF_SELF', + NTF_MASTER : 'NTF_MASTER', + NTF_PROXY : 'NTF_PROXY', + NTF_EXT_LEARNED : 'NTF_EXT_LEARNED', + NTF_ROUTER : 'NTF_ROUTER' } # Neighbor states @@ -2639,6 +3687,55 @@ class Neighbor(NetlinkPacket): def get_state_string(self, index): return self.get_string(self.state_to_string, index) + def get_states_string(self, states): + for_string = [] + + if states & Neighbor.NUD_INCOMPLETE: + for_string.append('NUD_INCOMPLETE') + + if states & Neighbor.NUD_REACHABLE: + for_string.append('NUD_REACHABLE') + + if states & Neighbor.NUD_STALE: + for_string.append('NUD_STALE') + + if states & Neighbor.NUD_DELAY: + for_string.append('NUD_DELAY') + + if states & Neighbor.NUD_PROBE: + for_string.append('NUD_PROBE') + + if states & Neighbor.NUD_FAILED: + for_string.append('NUD_FAILED') + + if states & Neighbor.NUD_NOARP: + for_string.append('NUD_NOARP') + + if states & Neighbor.NUD_PERMANENT: + for_string.append('NUD_PERMANENT') + + return ', '.join(for_string) + + def get_flags_string(self, flags): + for_string = [] + + if flags & Neighbor.NTF_USE: + for_string.append('NTF_USE') + + if flags & Neighbor.NTF_SELF: + for_string.append('NTF_SELF') + + if flags & Neighbor.NTF_MASTER: + for_string.append('NTF_MASTER') + + if flags & Neighbor.NTF_PROXY: + for_string.append('NTF_PROXY') + + if flags & Neighbor.NTF_ROUTER: + for_string.append('NTF_ROUTER') + + return ', '.join(for_string) + def decode_service_header(self): # Nothing to do if the message did not contain a service header @@ -2662,9 +3759,9 @@ class Neighbor(NetlinkPacket): elif self.line_number == 6: extra = "Interface Index %s (%d)" % (zfilled_hex(self.ifindex, 8), self.ifindex) elif self.line_number == 7: - extra = "State %s (%d), Flags %s, Type %s (%d)" % \ - (zfilled_hex(self.state, 4), self.state, - zfilled_hex(self.flags, 2), + extra = "State %s (%d) %s, Flags %s (%s) %s, Type %s (%d)" % \ + (zfilled_hex(self.state, 4), self.state, self.get_states_string(self.state), + zfilled_hex(self.flags, 2), self.flags, self.get_flags_string(self.flags), zfilled_hex(self.neighbor_type, 4), self.neighbor_type) else: extra = "Unexpected line number %d" % self.line_number @@ -2774,6 +3871,16 @@ class Route(NetlinkPacket): RT_SCOPE_NOWHERE : 'RT_SCOPE_NOWHERE' } + # Route scope to string + # iproute2/lib/rt_names.c + rtnl_rtscope_tab = { + RT_SCOPE_UNIVERSE: 'global', + RT_SCOPE_NOWHERE: 'nowhere', + RT_SCOPE_HOST: 'host', + RT_SCOPE_LINK: 'link', + RT_SCOPE_SITE: 'site' + } + # Routing stack # /usr/include/linux/rtnetlink.h RT_PROT_UNSPEC = 0x00 # Identifies what/who added the route @@ -2969,3 +4076,36 @@ class Route(NetlinkPacket): end = start + 4 self.dump_buffer.append(data_to_color_text(self.line_number, color, self.msg_data[start:end], extra)) self.line_number += 1 + +class Done(NetlinkPacket): + """ + NLMSG_DONE + + Service Header + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | TBD | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + """ + + def __init__(self, msgtype, debug=False, logger=None, use_color=True): + NetlinkPacket.__init__(self, msgtype, debug, logger, use_color) + self.PACK = 'i' + self.LEN = calcsize(self.PACK) + + def decode_service_header(self): + foo = unpack(self.PACK, self.msg_data[:self.LEN]) + + if self.debug: + color = yellow if self.use_color else None + color_start = "\033[%dm" % color if color else "" + color_end = "\033[0m" if color else "" + self.dump_buffer.append(" %sService Header%s" % (color_start, color_end)) + + for x in range(0, self.LEN/4): + extra = '' + start = x * 4 + end = start + 4 + self.dump_buffer.append(data_to_color_text(self.line_number, color, self.msg_data[start:end], extra)) + self.line_number += 1 diff --git a/sbin/ifaddon b/ifupdown2/sbin/ifaddon similarity index 98% rename from sbin/ifaddon rename to ifupdown2/sbin/ifaddon index 193331e..4a2f748 100755 --- a/sbin/ifaddon +++ b/ifupdown2/sbin/ifaddon @@ -1,4 +1,8 @@ #!/usr/bin/python +# +# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# import sys import os diff --git a/ifupdown2/sbin/ifupdown2d b/ifupdown2/sbin/ifupdown2d new file mode 100755 index 0000000..d223b80 --- /dev/null +++ b/ifupdown2/sbin/ifupdown2d @@ -0,0 +1,288 @@ +#!/usr/bin/python +# +# Copyright 2017 Cumulus Networks, Inc. All rights reserved. +# Authors: +# Roopa Prabhu, roopa@cumulusnetworks.com +# Julien Fortin, julien@cumulusnetworks.com +# +# ifupdown2 -- +# tool to configure network interfaces +# + +import os +import re +import json +import fcntl +import struct +import signal +import socket +import daemon +import select +import datetime +import threading + +try: + import ifupdown2.ifupdown.argv + + from ifupdown2.ifupdown.log import log + from ifupdown2.ifupdown.main import Ifupdown2 +except ImportError: + import ifupdown.argv + + from ifupdown.log import log + from ifupdown.main import Ifupdown2 + + +class Daemon: + shutdown_event = threading.Event() + + def __init__(self): + self.uds = None + self.context = None + self.working_directory = '/var/run/ifupdown2d/' + self.server_address = '/var/run/ifupdown2d/uds' + + if not os.path.exists(self.working_directory): + log.info('creating %s' % self.working_directory) + os.makedirs(self.working_directory, mode=0755) + + if os.path.exists(self.server_address): + log.info('removing uds %s' % self.server_address) + os.remove(self.server_address) + + self.context = daemon.DaemonContext( + working_directory=self.working_directory, + signal_map={ + signal.SIGINT: self.signal_handler, + signal.SIGTERM: self.signal_handler, + signal.SIGQUIT: self.signal_handler, + }, + umask=0o22 + ) + + try: + self.SO_PEERCRED = socket.SO_PEERCRED + except AttributeError: + # powerpc is the only non-generic we care about. alpha, mips, + # sparc, and parisc also have non-generic values. + machine = os.uname()[4] + if re.search(r'^(ppc|powerpc)', machine): + self.SO_PASSCRED = 20 + self.SO_PEERCRED = 21 + else: + self.SO_PASSCRED = 16 + self.SO_PEERCRED = 17 + + log.info('daemonizing ifupdown2d...') + self.context.open() + + log.info('preloading all necessary modules') + self.preload_imports() + + try: + log.info('opening UNIX socket') + self.uds = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + fcntl.fcntl(self.uds.fileno(), fcntl.F_SETFD, fcntl.FD_CLOEXEC) + except Exception as e: + raise Exception('socket: %s' % str(e)) + try: + self.uds.bind(self.server_address) + except Exception as e: + raise Exception('bind: %s' % str(e)) + try: + self.uds.setsockopt(socket.SOL_SOCKET, self.SO_PASSCRED, 1) + except Exception as e: + raise Exception('setsockopt: %s' % str(e)) + try: + self.uds.listen(1) + except Exception as e: + raise Exception('listen: %s' % str(e)) + os.chmod(self.server_address, 0777) + + def __del__(self): + if self.context: + self.context.close() + if self.uds: + self.uds.close() + + @staticmethod + def preload_imports(): + """ + preloading all the necessary modules + at first will increase performances + """ + try: + import io + import pdb + import imp + import sets + import json + import glob + import time + import copy + import errno + import pprint + import atexit + import ipaddr + import cPickle + import logging + import argparse + import StringIO + import datetime + import traceback + import itertools + import subprocess + import argcomplete + import collections + import ConfigParser + import pkg_resources + + import ifupdown2.ifupdown.exceptions + import ifupdown2.ifupdown.graph + import ifupdown2.ifupdown.iface + import ifupdown2.ifupdown.iff + import ifupdown2.ifupdown.ifupdownbase + import ifupdown2.ifupdown.ifupdownbase + import ifupdown2.ifupdown.ifupdownconfig + import ifupdown2.ifupdown.ifupdownflags + import ifupdown2.ifupdown.ifupdownmain + import ifupdown2.ifupdown.netlink + import ifupdown2.ifupdown.networkinterfaces + import ifupdown2.ifupdown.policymanager + import ifupdown2.ifupdown.scheduler + import ifupdown2.ifupdown.statemanager + import ifupdown2.ifupdown.template + import ifupdown2.ifupdown.utils + + import ifupdown2.ifupdownaddons.cache + import ifupdown2.ifupdownaddons.dhclient + import ifupdown2.ifupdownaddons.mstpctlutil + import ifupdown2.ifupdownaddons.LinkUtils + import ifupdown2.ifupdownaddons.modulebase + import ifupdown2.ifupdownaddons.systemutils + import ifupdown2.ifupdownaddons.utilsbase + except ImportError, e: + raise ImportError('%s - required module not found' % str(e)) + + @staticmethod + def signal_handler(sig, frame): + log.info('received %s' % 'SIGINT' if sig == signal.SIGINT else 'SIGTERM') + Daemon.shutdown_event.set() + + @staticmethod + def user_waiting_for_reply(): + return not log.is_syslog() + + def run(self): + try: + while True: + if Daemon.shutdown_event.is_set(): + log.info("shutdown signal RXed, breaking out loop") + break + + try: + (client_socket, client_address) = self.uds.accept() + except socket.error as e: + log.error(str(e)) + break + + pid = os.fork() + if pid == 0: + exit(self.ifupdown2(client_socket)) + else: + log.tx_data(json.dumps({'pid': pid}), socket=client_socket) + + start = datetime.datetime.now() + status = os.WEXITSTATUS(os.waitpid(pid, 0)[1]) + end = datetime.datetime.now() + + log.tx_data(json.dumps({'status': status}), socket=client_socket) + client_socket.close() + + log.info('exit status %d - in %ssecs' + % (status, (end - start).total_seconds())) + + except Exception as e: + log.error(e) + self.uds.close() + + def get_client_uid(self, client_socket): + creds = client_socket.getsockopt(socket.SOL_SOCKET, self.SO_PEERCRED, struct.calcsize('3i')) + (pid, uid, gid) = struct.unpack('3i', creds) + log.debug('client uid %d' % uid) + return uid + + @staticmethod + def get_client_request(client_socket): + """ + This function handles requests of any length. + + if the received json is longer than 65k it will be truncated + several calls to recv will be needed, we store the data until + we can decode them with the json library. + """ + data = [] + while True: + log.debug('waiting for request on client socket') + ready = select.select([client_socket], [], []) + + if ready and ready[0] and ready[0][0] == client_socket: + # data available start reading + raw_data = client_socket.recv(65536) + + try: + return json.loads(raw_data) + except ValueError: + # the json is incomplete + data.append(raw_data) + + if len(data) > 1: + try: + return json.loads(''.join(data)) + except ValueError: + pass + + def ifupdown2(self, client_socket): + try: + fcntl.fcntl(client_socket.fileno(), fcntl.F_SETFD, fcntl.FD_CLOEXEC) + + ifupdown2 = Ifupdown2(daemon=True, uid=self.get_client_uid(client_socket)) + ifupdown2.set_signal_handlers() + + request = self.get_client_request(client_socket) + log.info('request: %s' % request['argv']) + + ifupdown2.parse_argv(request['argv']) + # adjust the logger with argv + ifupdown2.update_logger(socket=client_socket) + + try: + status = ifupdown2.main(request['stdin']) + except Exception as e: + log.error(str(e)) + status = 1 + + except ifupdown2.ifupdown.argv.ArgvParseError as e: + log.update_current_logger(syslog=False, verbose=True, debug=False) + log.set_socket(client_socket) + e.log_error() + status = 1 + except Exception as e: + log.error(e) + status = 1 + + log.flush() + log.set_socket(None) + client_socket.close() + return status + + +if __name__ == '__main__': + try: + Daemon().run() + except Exception as e: + print e + log.error(str(e)) + import traceback + log.error(traceback.format_exc()) + exit(1) diff --git a/sbin/start-networking b/ifupdown2/sbin/start-networking similarity index 97% rename from sbin/start-networking rename to ifupdown2/sbin/start-networking index 5232e88..da6f9d0 100755 --- a/sbin/start-networking +++ b/ifupdown2/sbin/start-networking @@ -4,7 +4,6 @@ # Only has start, stop, reload, because that's all systemd has. # restart is implemented in systemd by stop then start. -PATH="/sbin:/bin" RUN_DIR="/run/network" IFSTATE_LOCKFILE="${RUN_DIR}/ifstatelock" @@ -87,9 +86,7 @@ ifup_hotplug () { do link=${iface##:*} link=${link##.*} - - ip link set "$iface" up || true - if [ "$(cat /sys/class/net/$link/operstate)" = up ] + if [ -e "/sys/class/net/$link" ] then echo "$iface" fi diff --git a/ifupdownaddons/bondutil.py b/ifupdownaddons/bondutil.py deleted file mode 100644 index 859ff7b..0000000 --- a/ifupdownaddons/bondutil.py +++ /dev/null @@ -1,426 +0,0 @@ -#!/usr/bin/python -# -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. -# Author: Roopa Prabhu, roopa@cumulusnetworks.com -# - -import os -import re -import ifupdown.ifupdownflags as ifupdownflags -from ifupdown.utils import utils -from ifupdown.iface import * -from utilsbase import * -from iproute2 import * -from cache import * - -class bondutil(utilsBase): - """ This class contains methods to interact with linux kernel bond - related interfaces """ - - _cache_fill_done = False - - def __init__(self, *args, **kargs): - utilsBase.__init__(self, *args, **kargs) - if ifupdownflags.flags.CACHE and not self._cache_fill_done: - self._bond_linkinfo_fill_all() - self._cache_fill_done = True - - def _bond_linkinfo_fill_attrs(self, bondname): - try: - linkCache.links[bondname]['linkinfo'] = {} - except: - linkCache.links[bondname] = {'linkinfo': {}} - - try: - linkCache.set_attr([bondname, 'linkinfo', 'min_links'], - self.read_file_oneline( - '/sys/class/net/%s/bonding/min_links' - % bondname)) - except Exception as e: - self.logger.debug(str(e)) - try: - linkCache.set_attr([bondname, 'linkinfo', 'slaves'], - self.read_file_oneline('/sys/class/net/%s/bonding/slaves' - %bondname).split()) - except Exception as e: - self.logger.debug(str(e)) - try: - linkCache.set_attr([bondname, 'linkinfo', 'mode'], - self.read_file_oneline('/sys/class/net/%s/bonding/mode' - %bondname).split()[0]) - except Exception as e: - self.logger.debug(str(e)) - try: - linkCache.set_attr([bondname, 'linkinfo', 'xmit_hash_policy'], - self.read_file_oneline( - '/sys/class/net/%s/bonding/xmit_hash_policy' - %bondname).split()[0]) - except Exception as e: - self.logger.debug(str(e)) - try: - linkCache.set_attr([bondname, 'linkinfo', 'lacp_rate'], - self.read_file_oneline('/sys/class/net/%s/bonding/lacp_rate' - %bondname).split()[1]) - except Exception as e: - self.logger.debug(str(e)) - try: - linkCache.set_attr([bondname, 'linkinfo', 'ad_actor_sys_prio'], - self.read_file_oneline('/sys/class/net/%s/bonding/ad_actor_sys_prio' - %bondname)) - except Exception as e: - self.logger.debug(str(e)) - try: - linkCache.set_attr([bondname, 'linkinfo', 'ad_actor_system'], - self.read_file_oneline('/sys/class/net/%s/bonding/ad_actor_system' - %bondname)) - except Exception as e: - self.logger.debug(str(e)) - try: - linkCache.set_attr([bondname, 'linkinfo', 'lacp_bypass'], - self.read_file_oneline('/sys/class/net/%s/bonding/lacp_bypass' - %bondname).split()[1]) - except Exception as e: - self.logger.debug(str(e)) - try: - linkCache.set_attr([bondname, 'linkinfo', 'updelay'], - self.read_file_oneline('/sys/class/net/%s/bonding/updelay' - %bondname)) - except Exception as e: - self.logger.debug(str(e)) - try: - linkCache.set_attr([bondname, 'linkinfo', 'downdelay'], - self.read_file_oneline('/sys/class/net/%s/bonding/downdelay' - %bondname)) - except Exception as e: - self.logger.debug(str(e)) - try: - map(lambda x: linkCache.set_attr([bondname, 'linkinfo', x], - self.read_file_oneline('/sys/class/net/%s/bonding/%s' - %(bondname, x))), - ['use_carrier', 'miimon', 'min_links', 'num_unsol_na', - 'num_grat_arp']) - except Exception as e: - self.logger.debug(str(e)) - - def _bond_linkinfo_fill_all(self): - bondstr = self.read_file_oneline('/sys/class/net/bonding_masters') - if not bondstr: - return - [self._bond_linkinfo_fill_attrs(b) for b in bondstr.split()] - - def _bond_linkinfo_fill(self, bondname, refresh=False): - if not refresh: - try: - linkCache.get_attr([bondname, 'linkinfo', 'slaves']) - return - except: - pass - bondstr = self.read_file_oneline('/sys/class/net/bonding_masters') - if (not bondstr or bondname not in bondstr.split()): - raise Exception('bond %s not found' %bondname) - self._bond_linkinfo_fill_attrs(bondname) - - def _cache_get(self, attrlist, refresh=False): - try: - if ifupdownflags.flags.DRYRUN: - return None - if ifupdownflags.flags.CACHE: - if not bondutil._cache_fill_done: - self._bond_linkinfo_fill_all() - bondutil._cache_fill_done = True - return linkCache.get_attr(attrlist) - if not refresh: - return linkCache.get_attr(attrlist) - self._bond_linkinfo_fill(attrlist[0], refresh) - return linkCache.get_attr(attrlist) - except Exception, e: - self.logger.debug('_cache_get(%s) : [%s]' - %(str(attrlist), str(e))) - pass - return None - - def _cache_check(self, attrlist, value, refresh=False): - try: - attrvalue = self._cache_get(attrlist, refresh) - if attrvalue and attrvalue == value: - return True - except Exception, e: - self.logger.debug('_cache_check(%s) : [%s]' - %(str(attrlist), str(e))) - pass - return False - - def _cache_update(self, attrlist, value): - if ifupdownflags.flags.DRYRUN: return - try: - if attrlist[-1] == 'slaves': - linkCache.add_to_attrlist(attrlist, value) - return - linkCache.add_attr(attrlist, value) - except: - pass - - def _cache_delete(self, attrlist, value=None): - if ifupdownflags.flags.DRYRUN: return - try: - if attrlist[-1] == 'slaves': - linkCache.remove_from_attrlist(attrlist, value) - return - linkCache.del_attr(attrlist) - except: - pass - - def _cache_invalidate(self): - if ifupdownflags.flags.DRYRUN: return - linkCache.invalidate() - - def set_attrs(self, bondname, attrdict, prehook): - for attrname, attrval in attrdict.items(): - if (self._cache_check([bondname, 'linkinfo', - attrname], attrval)): - continue - if (attrname == 'mode' or attrname == 'xmit_hash_policy' or - attrname == 'lacp_rate' or attrname == 'min_links'): - if prehook: - prehook(bondname) - try: - if ((attrname not in ['lacp_rate', - 'lacp_bypass']) or - self._cache_check([bondname, 'linkinfo', 'mode'], '802.3ad', - True)): - self.write_file('/sys/class/net/%s/bonding/%s' - %(bondname, attrname), attrval) - except Exception, e: - if ifupdownflags.flags.FORCE: - self.logger.warn(str(e)) - pass - else: - raise - - def set_use_carrier(self, bondname, use_carrier): - if not use_carrier or (use_carrier != '0' and use_carrier != '1'): - return - if (self._cache_check([bondname, 'linkinfo', 'use_carrier'], - use_carrier)): - return - self.write_file('/sys/class/net/%s' %bondname + - '/bonding/use_carrier', use_carrier) - self._cache_update([bondname, 'linkinfo', - 'use_carrier'], use_carrier) - - def get_use_carrier(self, bondname): - return self._cache_get([bondname, 'linkinfo', 'use_carrier']) - - def set_xmit_hash_policy(self, bondname, hash_policy, prehook=None): - valid_values = ['layer2', 'layer3+4', 'layer2+3'] - if not hash_policy: - return - if hash_policy not in valid_values: - raise Exception('invalid hash policy value %s' %hash_policy) - if (self._cache_check([bondname, 'linkinfo', 'xmit_hash_policy'], - hash_policy)): - return - if prehook: - prehook(bondname) - self.write_file('/sys/class/net/%s' %bondname + - '/bonding/xmit_hash_policy', hash_policy) - self._cache_update([bondname, 'linkinfo', 'xmit_hash_policy'], - hash_policy) - - def get_xmit_hash_policy(self, bondname): - return self._cache_get([bondname, 'linkinfo', 'xmit_hash_policy']) - - def set_miimon(self, bondname, miimon): - if (self._cache_check([bondname, 'linkinfo', 'miimon'], - miimon)): - return - self.write_file('/sys/class/net/%s' %bondname + - '/bonding/miimon', miimon) - self._cache_update([bondname, 'linkinfo', 'miimon'], miimon) - - def get_miimon(self, bondname): - return self._cache_get([bondname, 'linkinfo', 'miimon']) - - def set_mode(self, bondname, mode, prehook=None): - valid_modes = ['balance-rr', 'active-backup', 'balance-xor', - 'broadcast', '802.3ad', 'balance-tlb', 'balance-alb'] - if not mode: - return - if mode not in valid_modes: - raise Exception('invalid mode %s' %mode) - if (self._cache_check([bondname, 'linkinfo', 'mode'], - mode)): - return - if prehook: - prehook(bondname) - self.write_file('/sys/class/net/%s' %bondname + '/bonding/mode', mode) - self._cache_update([bondname, 'linkinfo', 'mode'], mode) - - def get_mode(self, bondname): - return self._cache_get([bondname, 'linkinfo', 'mode']) - - def set_lacp_rate(self, bondname, lacp_rate, prehook=None, posthook=None): - if not lacp_rate or (lacp_rate != '0' and lacp_rate != '1'): - return - if (self._cache_check([bondname, 'linkinfo', 'lacp_rate'], - lacp_rate)): - return - if prehook: - prehook(bondname) - try: - self.write_file('/sys/class/net/%s' %bondname + - '/bonding/lacp_rate', lacp_rate) - except: - raise - finally: - if posthook: - prehook(bondname) - self._cache_update([bondname, 'linkinfo', - 'lacp_rate'], lacp_rate) - - def get_lacp_rate(self, bondname): - return self._cache_get([bondname, 'linkinfo', 'lacp_rate']) - - def set_lacp_bypass_allow(self, bondname, allow, prehook=None, posthook=None): - if (self._cache_check([bondname, 'linkinfo', 'lacp_bypass'], allow)): - return - if prehook: - prehook(bondname) - try: - self.write_file('/sys/class/net/%s' %bondname + - '/bonding/lacp_bypass', allow) - except: - raise - finally: - if posthook: - posthook(bondname) - self._cache_update([bondname, 'linkinfo', - 'lacp_bypass'], allow) - - def get_lacp_bypass_allow(self, bondname): - return self._cache_get([bondname, 'linkinfo', 'lacp_bypass']) - - def set_min_links(self, bondname, min_links, prehook=None): - if (self._cache_check([bondname, 'linkinfo', 'min_links'], - min_links)): - return - if prehook: - prehook(bondname) - self.write_file('/sys/class/net/%s/bonding/min_links' %bondname, - min_links) - self._cache_update([bondname, 'linkinfo', 'min_links'], min_links) - - def get_min_links(self, bondname): - return self._cache_get([bondname, 'linkinfo', 'min_links']) - - def get_ad_actor_system(self, bondname): - return self._cache_get([bondname, 'linkinfo', 'ad_actor_system']) - - def get_ad_actor_sys_prio(self, bondname): - return self._cache_get([bondname, 'linkinfo', 'ad_actor_sys_prio']) - - def get_num_unsol_na(self, bondname): - return self._cache_get([bondname, 'linkinfo', 'num_unsol_na']) - - def get_num_grat_arp(self, bondname): - return self._cache_get([bondname, 'linkinfo', 'num_grat_arp']) - - def get_updelay(self, bondname): - return self._cache_get([bondname, 'linkinfo', 'updelay']) - - def get_downdelay(self, bondname): - return self._cache_get([bondname, 'linkinfo', 'downdelay']) - - def enslave_slave(self, bondname, slave, prehook=None, posthook=None): - slaves = self._cache_get([bondname, 'linkinfo', 'slaves']) - if slaves and slave in slaves: return - if prehook: - prehook(slave) - self.write_file('/sys/class/net/%s' %bondname + - '/bonding/slaves', '+' + slave) - if posthook: - posthook(slave) - self._cache_update([bondname, 'linkinfo', 'slaves'], slave) - - def remove_slave(self, bondname, slave): - slaves = self._cache_get([bondname, 'linkinfo', 'slaves']) - if slave not in slaves: - return - sysfs_bond_path = ('/sys/class/net/%s' %bondname + - '/bonding/slaves') - if not os.path.exists(sysfs_bond_path): - return - self.write_file(sysfs_bond_path, '-' + slave) - self._cache_delete([bondname, 'linkinfo', 'slaves'], slave) - - def remove_slaves_all(self, bondname): - if not self._cache_get([bondname, 'linkinfo', 'slaves']): - return - slaves = None - sysfs_bond_path = ('/sys/class/net/%s' %bondname + - '/bonding/slaves') - ipcmd = iproute2() - try: - with open(sysfs_bond_path, 'r') as f: - slaves = f.readline().strip().split() - except IOError, e: - raise Exception('error reading slaves of bond %s' %bondname - + '(' + str(e) + ')') - for slave in slaves: - ipcmd.ip_link_down(slave) - try: - self.remove_slave(bondname, slave) - except Exception, e: - if not ifupdownflags.flags.FORCE: - raise Exception('error removing slave %s' - %slave + ' from bond %s' %bondname + - '(%s)' %str(e)) - else: - pass - self._cache_del([bondname, 'linkinfo', 'slaves']) - - def load_bonding_module(self): - return utils.exec_command('modprobe -q bonding') - - def create_bond(self, bondname): - if self.bond_exists(bondname): - return - sysfs_net = '/sys/class/net/' - sysfs_bonding_masters = sysfs_net + 'bonding_masters' - if not os.path.exists(sysfs_bonding_masters): - self.logger.debug('loading bonding driver') - self.load_bonding_module() - self.write_file(sysfs_bonding_masters, '+' + bondname) - self._cache_update([bondname], {}) - - def delete_bond(self, bondname): - if not os.path.exists('/sys/class/net/%s' %bondname): - return - self.write_file('/sys/class/net/bonding_masters', '-' + bondname) - self._cache_delete([bondname]) - - def unset_master(self, bondname): - print 'Do nothing yet' - return 0 - - def get_slaves(self, bondname): - slaves = self._cache_get([bondname, 'linkinfo', 'slaves']) - if slaves: - return list(slaves) - slavefile = '/sys/class/net/%s/bonding/slaves' %bondname - if os.path.exists(slavefile): - buf = self.read_file_oneline(slavefile) - if buf: - slaves = buf.split() - if not slaves: - return slaves - self._cache_update([bondname, 'linkinfo', 'slaves'], slaves) - return list(slaves) - - def bond_slave_exists(self, bond, slave): - slaves = self.get_slaves(bond) - if not slaves: return False - return slave in slaves - - def bond_exists(self, bondname): - return os.path.exists('/sys/class/net/%s/bonding' %bondname) diff --git a/ifupdownaddons/bridgeutils.py b/ifupdownaddons/bridgeutils.py deleted file mode 100644 index 159a87f..0000000 --- a/ifupdownaddons/bridgeutils.py +++ /dev/null @@ -1,544 +0,0 @@ -#!/usr/bin/python -# -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. -# Author: Roopa Prabhu, roopa@cumulusnetworks.com -# - -from ifupdown.iface import * -from utilsbase import * -import os -import re -import logging -from ifupdown.utils import utils -import ifupdown.ifupdownflags as ifupdownflags -from cache import * - -class brctl(utilsBase): - """ This class contains helper functions to interact with the bridgeutils - commands """ - - _cache_fill_done = False - - def __init__(self, *args, **kargs): - utilsBase.__init__(self, *args, **kargs) - if ifupdownflags.flags.CACHE and not brctl._cache_fill_done: - if os.path.exists('/sbin/brctl'): - self._bridge_fill() - brctl._cache_fill_done = True - self.supported_command = {'showmcqv4src': True} - - - def _bridge_get_mcattrs_from_sysfs(self, bridgename): - mcattrs = {} - mcattrmap = {'mclmc': 'multicast_last_member_count', - 'mcrouter': 'multicast_router', - 'mcsnoop' : 'multicast_snooping', - 'mcsqc' : 'multicast_startup_query_count', - 'mcqifaddr' : 'multicast_query_use_ifaddr', - 'mcquerier' : 'multicast_querier', - 'hashel' : 'hash_elasticity', - 'hashmax' : 'hash_max', - 'mclmi' : 'multicast_last_member_interval', - 'mcmi' : 'multicast_membership_interval', - 'mcqpi' : 'multicast_querier_interval', - 'mcqi' : 'multicast_query_interval', - 'mcqri' : 'multicast_query_response_interval', - 'mcsqi' : 'multicast_startup_query_interval'} - - mcattrsdivby100 = ['mclmi', 'mcmi', 'mcqpi', 'mcqi', 'mcqri', 'mcsqi'] - - for m, s in mcattrmap.items(): - n = self.read_file_oneline('/sys/class/net/%s/bridge/%s' - %(bridgename, s)) - if m in mcattrsdivby100: - try: - v = int(n) / 100 - mcattrs[m] = str(v) - except Exception, e: - self.logger.warn('error getting mc attr %s (%s)' - %(m, str(e))) - pass - else: - mcattrs[m] = n - return mcattrs - - def _bridge_attrs_fill(self, bridgename): - battrs = {} - bports = {} - - brout = utils.exec_command('/sbin/brctl showstp %s' % bridgename) - chunks = re.split(r'\n\n', brout, maxsplit=0, flags=re.MULTILINE) - - try: - # Get all bridge attributes - broutlines = chunks[0].splitlines() - #battrs['pathcost'] = broutlines[3].split('path cost')[1].strip() - - try: - battrs['maxage'] = broutlines[4].split('bridge max age')[ - 1].strip().replace('.00', '') - except: - pass - - try: - battrs['hello'] = broutlines[5].split('bridge hello time')[ - 1].strip().replace('.00', '') - except: - pass - - try: - battrs['fd'] = broutlines[6].split('bridge forward delay')[ - 1].strip().replace('.00', '') - except: - pass - - try: - battrs['ageing'] = broutlines[7].split('ageing time')[ - 1].strip().replace('.00', '') - except: - pass - - try: - battrs['mcrouter'] = broutlines[12].split('mc router')[ - 1].strip().split('\t\t\t')[0] - except: - pass - - try: - battrs['bridgeprio'] = self.read_file_oneline( - '/sys/class/net/%s/bridge/priority' % bridgename) - except: - pass - - try: - battrs.update(self._bridge_get_mcattrs_from_sysfs(bridgename)) - except: - pass - - # XXX: comment this out until mc attributes become available - # with brctl again - #battrs['hashel'] = broutlines[10].split('hash elasticity')[1].split()[0].strip() - #battrs['hashmax'] = broutlines[10].split('hash max')[1].strip() - #battrs['mclmc'] = broutlines[11].split('mc last member count')[1].split()[0].strip() - #battrs['mciqc'] = broutlines[11].split('mc init query count')[1].strip() - #battrs['mcrouter'] = broutlines[12].split('mc router')[1].split()[0].strip() - ##battrs['mcsnoop'] = broutlines[12].split('mc snooping')[1].strip() - #battrs['mclmt'] = broutlines[13].split('mc last member timer')[1].split()[0].strip() - except Exception, e: - self.logger.warn('%s: error while processing bridge attributes: %s' % (bridgename, str(e))) - pass - - linkCache.update_attrdict([bridgename, 'linkinfo'], battrs) - for cidx in range(1, len(chunks)): - bpout = chunks[cidx].lstrip('\n') - if not bpout or bpout[0] == ' ': - continue - bplines = bpout.splitlines() - pname = bplines[0].split()[0] - bportattrs = {} - try: - bportattrs['pathcost'] = bplines[2].split( - 'path cost')[1].strip() - bportattrs['fdelay'] = bplines[4].split( - 'forward delay timer')[1].strip() - bportattrs['portmcrouter'] = self.read_file_oneline( - '/sys/class/net/%s/brport/multicast_router' %pname) - bportattrs['portmcfl'] = self.read_file_oneline( - '/sys/class/net/%s/brport/multicast_fast_leave' %pname) - bportattrs['portprio'] = self.read_file_oneline( - '/sys/class/net/%s/brport/priority' %pname) - #bportattrs['mcrouters'] = bplines[6].split('mc router')[1].split()[0].strip() - #bportattrs['mc fast leave'] = bplines[6].split('mc fast leave')[1].strip() - except Exception, e: - self.logger.warn('%s: error while processing bridge attributes: %s' % (bridgename, str(e))) - bports[pname] = bportattrs - linkCache.update_attrdict([bridgename, 'linkinfo', 'ports'], bports) - - def _bridge_fill(self, bridgename=None, refresh=False): - try: - # if cache is already filled, return - linkCache.get_attr([bridgename, 'linkinfo', 'fd']) - return - except: - pass - if not bridgename: - brctlout = utils.exec_command('/sbin/brctl show') - else: - brctlout = utils.exec_command('/sbin/brctl show %s' % bridgename) - if not brctlout: - return - - for bline in brctlout.splitlines()[1:]: - bitems = bline.split() - if len(bitems) < 2: - continue - try: - linkCache.update_attrdict([bitems[0], 'linkinfo'], - {'stp' : bitems[2]}) - except KeyError: - linkCache.update_attrdict([bitems[0]], - {'linkinfo' : {'stp' : bitems[2]}}) - self._bridge_attrs_fill(bitems[0]) - - def _cache_get(self, attrlist, refresh=False): - try: - if ifupdownflags.flags.DRYRUN: - return None - if ifupdownflags.flags.CACHE: - if not self._cache_fill_done: - self._bridge_fill() - self._cache_fill_done = True - return linkCache.get_attr(attrlist) - if not refresh: - return linkCache.get_attr(attrlist) - self._bridge_fill(attrlist[0], refresh) - return linkCache.get_attr(attrlist) - except Exception, e: - self.logger.debug('_cache_get(%s) : [%s]' - %(str(attrlist), str(e))) - pass - return None - - def _cache_check(self, attrlist, value, refresh=False): - try: - attrvalue = self._cache_get(attrlist, refresh) - if attrvalue and attrvalue == value: - return True - except Exception, e: - self.logger.debug('_cache_check(%s) : [%s]' - %(str(attrlist), str(e))) - pass - return False - - def _cache_update(self, attrlist, value): - if ifupdownflags.flags.DRYRUN: return - try: - linkCache.add_attr(attrlist, value) - except: - pass - - def _cache_delete(self, attrlist): - if ifupdownflags.flags.DRYRUN: return - try: - linkCache.del_attr(attrlist) - except: - pass - - def _cache_invalidate(self): - if ifupdownflags.flags.DRYRUN: return - linkCache.invalidate() - - def create_bridge(self, bridgename): - if self.bridge_exists(bridgename): - return - utils.exec_command('/sbin/brctl addbr %s' % bridgename) - self._cache_update([bridgename], {}) - - def delete_bridge(self, bridgename): - if not self.bridge_exists(bridgename): - return - utils.exec_command('/sbin/brctl delbr %s' % bridgename) - self._cache_invalidate() - - def add_bridge_port(self, bridgename, bridgeportname): - """ Add port to bridge """ - ports = self._cache_get([bridgename, 'linkinfo', 'ports']) - if ports and ports.get(bridgeportname): - return - utils.exec_command('/sbin/brctl addif %s %s' % - (bridgename, bridgeportname)) - self._cache_update([bridgename, 'linkinfo', 'ports', - bridgeportname], {}) - - def delete_bridge_port(self, bridgename, bridgeportname): - """ Delete port from bridge """ - ports = self._cache_get([bridgename, 'linkinfo', 'ports']) - if not ports or not ports.get(bridgeportname): - return - utils.exec_command('/sbin/brctl delif %s %s' % - (bridgename, bridgeportname)) - self._cache_delete([bridgename, 'linkinfo', 'ports', - 'bridgeportname']) - - def set_bridgeport_attrs(self, bridgename, bridgeportname, attrdict): - portattrs = self._cache_get([bridgename, 'linkinfo', - 'ports', bridgeportname]) - if portattrs == None: portattrs = {} - for k, v in attrdict.iteritems(): - if ifupdownflags.flags.CACHE: - curval = portattrs.get(k) - if curval and curval == v: - continue - utils.exec_command('/sbin/brctl set%s %s %s %s' % - (k, bridgename, bridgeportname, v)) - - def set_bridgeport_attr(self, bridgename, bridgeportname, - attrname, attrval): - if self._cache_check([bridgename, 'linkinfo', 'ports', - bridgeportname, attrname], attrval): - return - utils.exec_command('/sbin/brctl set%s %s %s %s' % - (attrname, - bridgename, - bridgeportname, - attrval)) - - def set_bridge_attrs(self, bridgename, attrdict): - for k, v in attrdict.iteritems(): - if not v: - continue - if self._cache_check([bridgename, 'linkinfo', k], v): - continue - try: - cmd = '/sbin/brctl set%s %s %s' % (k, bridgename, v) - utils.exec_command(cmd) - except Exception, e: - self.logger.warn('%s: %s' %(bridgename, str(e))) - pass - - def set_bridge_attr(self, bridgename, attrname, attrval): - if self._cache_check([bridgename, 'linkinfo', attrname], attrval): - return - utils.exec_command('/sbin/brctl set%s %s %s' % - (attrname, bridgename, attrval)) - - def get_bridge_attrs(self, bridgename): - return self._cache_get([bridgename, 'linkinfo']) - - def get_bridgeport_attrs(self, bridgename, bridgeportname): - return self._cache_get([bridgename, 'linkinfo', 'ports', - bridgeportname]) - - def get_bridgeport_attr(self, bridgename, bridgeportname, attrname): - return self._cache_get([bridgename, 'linkinfo', 'ports', - bridgeportname, attrname]) - - def set_stp(self, bridge, stp_state): - utils.exec_command('/sbin/brctl stp %s %s' % (bridge, stp_state)) - - def get_stp(self, bridge): - sysfs_stpstate = '/sys/class/net/%s/bridge/stp_state' %bridge - if not os.path.exists(sysfs_stpstate): - return 'error' - stpstate = self.read_file_oneline(sysfs_stpstate) - if not stpstate: - return 'error' - try: - if int(stpstate) > 0: - return 'yes' - elif int(stpstate) == 0: - return 'no' - except: - return 'unknown' - - def conv_value_to_user(self, str): - try: - ret = int(str) / 100 - except: - return None - finally: - return '%d' %ret - - def read_value_from_sysfs(self, filename, preprocess_func): - value = self.read_file_oneline(filename) - if not value: - return None - return preprocess_func(value) - - def set_ageing(self, bridge, ageing): - utils.exec_command('/sbin/brctl setageing %s %s' % (bridge, ageing)) - - def get_ageing(self, bridge): - return self.read_value_from_sysfs('/sys/class/net/%s/bridge/ageing_time' - %bridge, self.conv_value_to_user) - - def set_bridgeprio(self, bridge, prio): - utils.exec_command('/sbin/brctl setbridgeprio %s %s' % (bridge, prio)) - - def get_bridgeprio(self, bridge): - return self.read_file_oneline( - '/sys/class/net/%s/bridge/priority' %bridge) - - def set_fd(self, bridge, fd): - utils.exec_command('/sbin/brctl setfd %s %s' % (bridge, fd)) - - def get_fd(self, bridge): - return self.read_value_from_sysfs( - '/sys/class/net/%s/bridge/forward_delay' - %bridge, self.conv_value_to_user) - - def set_gcint(self, bridge, gcint): - #cmd = '/sbin/brctl setgcint ' + bridge + ' ' + gcint - raise Exception('set_gcint not implemented') - - def set_hello(self, bridge, hello): - utils.exec_command('/sbin/brctl sethello %s %s' % (bridge, hello)) - - def get_hello(self, bridge): - return self.read_value_from_sysfs('/sys/class/net/%s/bridge/hello_time' - %bridge, self.conv_value_to_user) - - def set_maxage(self, bridge, maxage): - utils.exec_command('/sbin/brctl setmaxage %s %s' % (bridge, maxage)) - - def get_maxage(self, bridge): - return self.read_value_from_sysfs('/sys/class/net/%s/bridge/max_age' - %bridge, self.conv_value_to_user) - - def set_pathcost(self, bridge, port, pathcost): - utils.exec_command('/sbin/brctl setpathcost %s %s %s' % - (bridge, port, pathcost)) - - def get_pathcost(self, bridge, port): - return self.read_file_oneline('/sys/class/net/%s/brport/path_cost' - %port) - - def set_portprio(self, bridge, port, prio): - utils.exec_command('/sbin/brctl setportprio %s %s %s' % - (bridge, port, prio)) - - def get_portprio(self, bridge, port): - return self.read_file_oneline('/sys/class/net/%s/brport/priority' - %port) - - def set_hashmax(self, bridge, hashmax): - utils.exec_command('/sbin/brctl sethashmax %s %s' % (bridge, hashmax)) - - def get_hashmax(self, bridge): - return self.read_file_oneline('/sys/class/net/%s/bridge/hash_max' - %bridge) - - def set_hashel(self, bridge, hashel): - utils.exec_command('/sbin/brctl sethashel %s %s' % (bridge, hashel)) - - def get_hashel(self, bridge): - return self.read_file_oneline('/sys/class/net/%s/bridge/hash_elasticity' - %bridge) - - def set_mclmc(self, bridge, mclmc): - utils.exec_command('/sbin/brctl setmclmc %s %s' % (bridge, mclmc)) - - def get_mclmc(self, bridge): - return self.read_file_oneline( - '/sys/class/net/%s/bridge/multicast_last_member_count' - %bridge) - - def set_mcrouter(self, bridge, mcrouter): - utils.exec_command('/sbin/brctl setmcrouter %s %s' % (bridge, mcrouter)) - - def get_mcrouter(self, bridge): - return self.read_file_oneline( - '/sys/class/net/%s/bridge/multicast_router' %bridge) - - def set_mcsnoop(self, bridge, mcsnoop): - utils.exec_command('/sbin/brctl setmcsnoop %s %s' % (bridge, mcsnoop)) - - def get_mcsnoop(self, bridge): - return self.read_file_oneline( - '/sys/class/net/%s/bridge/multicast_snooping' %bridge) - - def set_mcsqc(self, bridge, mcsqc): - utils.exec_command('/sbin/brctl setmcsqc %s %s' % (bridge, mcsqc)) - - def get_mcsqc(self, bridge): - return self.read_file_oneline( - '/sys/class/net/%s/bridge/multicast_startup_query_count' - %bridge) - - def set_mcqifaddr(self, bridge, mcqifaddr): - utils.exec_command('/sbin/brctl setmcqifaddr %s %s' % - (bridge, mcqifaddr)) - - def get_mcqifaddr(self, bridge): - return self.read_file_oneline( - '/sys/class/net/%s/bridge/multicast_startup_query_use_ifaddr' - %bridge) - - def set_mcquerier(self, bridge, mcquerier): - utils.exec_command('/sbin/brctl setmcquerier %s %s' % - (bridge, mcquerier)) - - def get_mcquerier(self, bridge): - return self.read_file_oneline( - '/sys/class/net/%s/bridge/multicast_querier' %bridge) - - def set_mcqv4src(self, bridge, vlan, mcquerier): - if vlan == 0 or vlan > 4095: - self.logger.warn('mcqv4src vlan \'%d\' invalid range' %vlan) - return - - ip = mcquerier.split('.') - if len(ip) != 4: - self.logger.warn('mcqv4src \'%s\' invalid IPv4 address' %mcquerier) - return - for k in ip: - if not k.isdigit() or int(k, 10) < 0 or int(k, 10) > 255: - self.logger.warn('mcqv4src \'%s\' invalid IPv4 address' %mcquerier) - return - - utils.exec_command('/sbin/brctl setmcqv4src %s %d %s' % - (bridge, vlan, mcquerier)) - - def del_mcqv4src(self, bridge, vlan): - utils.exec_command('/sbin/brctl delmcqv4src %s %d' % (bridge, vlan)) - - def get_mcqv4src(self, bridge, vlan=None): - if not self.supported_command['showmcqv4src']: - return {} - mcqv4src = {} - try: - mcqout = utils.exec_command('/sbin/brctl showmcqv4src %s' % bridge) - except Exception as e: - s = str(e).lower() - if 'never heard' in s: - self.logger.info('/sbin/brctl showmcqv4src: ' - 'skipping unsupported command') - self.supported_command['showmcqv4src'] = False - return {} - raise - if not mcqout: return {} - mcqlines = mcqout.splitlines() - for l in mcqlines[1:]: - l=l.strip() - k, d, v = l.split('\t') - if not k or not v: - continue - mcqv4src[k] = v - if vlan: - return mcqv4src.get(vlan) - return mcqv4src - - def set_mclmi(self, bridge, mclmi): - utils.exec_command('/sbin/brctl setmclmi %s %s' % (bridge, mclmi)) - - def get_mclmi(self, bridge): - return self.read_file_oneline( - '/sys/class/net/%s/bridge/multicast_last_member_interval' - %bridge) - - def set_mcmi(self, bridge, mcmi): - utils.exec_command('/sbin/brctl setmcmi %s %s' % (bridge, mcmi)) - - def get_mcmi(self, bridge): - return self.read_file_oneline( - '/sys/class/net/%s/bridge/multicast_membership_interval' - %bridge) - - def bridge_exists(self, bridge): - return os.path.exists('/sys/class/net/%s/bridge' %bridge) - - def is_bridge_port(self, ifacename): - return os.path.exists('/sys/class/net/%s/brport' %ifacename) - - def bridge_port_exists(self, bridge, bridgeportname): - try: - return os.path.exists('/sys/class/net/%s/brif/%s' - %(bridge, bridgeportname)) - except Exception: - return False - - def get_bridge_ports(self, bridgename): - try: - return os.listdir('/sys/class/net/%s/brif/' %bridgename) - except: - return [] diff --git a/ifupdownaddons/ifenslaveutil.py b/ifupdownaddons/ifenslaveutil.py deleted file mode 100644 index c7e0d42..0000000 --- a/ifupdownaddons/ifenslaveutil.py +++ /dev/null @@ -1,421 +0,0 @@ -#!/usr/bin/python -# -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. -# Author: Roopa Prabhu, roopa@cumulusnetworks.com -# - -import os -import re -from ifupdown.iface import * -from utilsbase import * -from iproute2 import * -from cache import * - -class ifenslaveutil(utilsBase): - """ This class contains methods to interact with linux kernel bond - related interfaces """ - - _cache_fill_done = False - - def __init__(self, *args, **kargs): - utilsBase.__init__(self, *args, **kargs) - if self.CACHE and not self._cache_fill_done: - self._bond_linkinfo_fill_all() - self._cache_fill_done = True - - def _bond_linkinfo_fill_attrs(self, bondname): - try: - linkCache.links[bondname]['linkinfo'] = {} - except: - linkCache.links[bondname] = {'linkinfo': {}} - - try: - linkCache.set_attr([bondname, 'linkinfo', 'slaves'], - self.read_file_oneline('/sys/class/net/%s/bonding/slaves' - %bondname).split()) - linkCache.set_attr([bondname, 'linkinfo', 'mode'], - self.read_file_oneline('/sys/class/net/%s/bonding/mode' - %bondname).split()[0]) - linkCache.set_attr([bondname, 'linkinfo', 'xmit_hash_policy'], - self.read_file_oneline( - '/sys/class/net/%s/bonding/xmit_hash_policy' - %bondname).split()[0]) - linkCache.set_attr([bondname, 'linkinfo', 'lacp_rate'], - self.read_file_oneline('/sys/class/net/%s/bonding/lacp_rate' - %bondname).split()[1]) - linkCache.set_attr([bondname, 'linkinfo', 'ad_sys_priority'], - self.read_file_oneline('/sys/class/net/%s/bonding/ad_sys_priority' - %bondname)) - linkCache.set_attr([bondname, 'linkinfo', 'ad_sys_mac_addr'], - self.read_file_oneline('/sys/class/net/%s/bonding/ad_sys_mac_addr' - %bondname)) - map(lambda x: linkCache.set_attr([bondname, 'linkinfo', x], - self.read_file_oneline('/sys/class/net/%s/bonding/%s' - %(bondname, x))), - ['use_carrier', 'miimon', 'min_links', 'num_unsol_na', - 'num_grat_arp', 'lacp_bypass_allow', 'lacp_bypass_period', - 'clag_enable']) - except Exception, e: - pass - - def _bond_linkinfo_fill_all(self): - bondstr = self.read_file_oneline('/sys/class/net/bonding_masters') - if not bondstr: - return - [self._bond_linkinfo_fill_attrs(b) for b in bondstr.split()] - - def _bond_linkinfo_fill(self, bondname, refresh=False): - try: - linkCache.get_attr([bondname, 'linkinfo', 'slaves']) - return - except: - pass - bondstr = self.read_file_oneline('/sys/class/net/bonding_masters') - if (not bondstr or bondname not in bondstr.split()): - raise Exception('bond %s not found' %bondname) - self._bond_linkinfo_fill_attrs(bondname) - - def _cache_get(self, attrlist, refresh=False): - try: - if self.DRYRUN: - return None - if self.CACHE: - if not ifenslaveutil._cache_fill_done: - self._bond_linkinfo_fill_all() - ifenslaveutil._cache_fill_done = True - return linkCache.get_attr(attrlist) - if not refresh: - return linkCache.get_attr(attrlist) - self._bond_linkinfo_fill(attrlist[0], refresh) - return linkCache.get_attr(attrlist) - except Exception, e: - self.logger.debug('_cache_get(%s) : [%s]' - %(str(attrlist), str(e))) - pass - return None - - def _cache_check(self, attrlist, value, refresh=False): - try: - attrvalue = self._cache_get(attrlist, refresh) - if attrvalue and attrvalue == value: - return True - except Exception, e: - self.logger.debug('_cache_check(%s) : [%s]' - %(str(attrlist), str(e))) - pass - return False - - def _cache_update(self, attrlist, value): - if self.DRYRUN: return - try: - if attrlist[-1] == 'slaves': - linkCache.add_to_attrlist(attrlist, value) - return - linkCache.add_attr(attrlist, value) - except: - pass - - def _cache_delete(self, attrlist, value=None): - if self.DRYRUN: return - try: - if attrlist[-1] == 'slaves': - linkCache.remove_from_attrlist(attrlist, value) - return - linkCache.del_attr(attrlist) - except: - pass - - def _cache_invalidate(self): - if self.DRYRUN: return - linkCache.invalidate() - - def set_attrs(self, bondname, attrdict, prehook): - for attrname, attrval in attrdict.items(): - if (self._cache_check([bondname, 'linkinfo', - attrname], attrval)): - continue - if (attrname == 'mode' or attrname == 'xmit_hash_policy' or - attrname == 'lacp_rate' or attrname == 'min_links'): - if prehook: - prehook(bondname) - try: - self.write_file('/sys/class/net/%s/bonding/%s' - %(bondname, attrname), attrval) - except Exception, e: - if self.FORCE: - self.logger.warn(str(e)) - pass - else: - raise - - def set_use_carrier(self, bondname, use_carrier): - if not use_carrier or (use_carrier != '0' and use_carrier != '1'): - return - if (self._cache_check([bondname, 'linkinfo', 'use_carrier'], - use_carrier)): - return - self.write_file('/sys/class/net/%s' %bondname + - '/bonding/use_carrier', use_carrier) - self._cache_update([bondname, 'linkinfo', - 'use_carrier'], use_carrier) - - def get_use_carrier(self, bondname): - return self._cache_get([bondname, 'linkinfo', 'use_carrier']) - - def set_xmit_hash_policy(self, bondname, hash_policy, prehook=None): - valid_values = ['layer2', 'layer3+4', 'layer2+3'] - if not hash_policy: - return - if hash_policy not in valid_values: - raise Exception('invalid hash policy value %s' %hash_policy) - if (self._cache_check([bondname, 'linkinfo', 'xmit_hash_policy'], - hash_policy)): - return - if prehook: - prehook(bondname) - self.write_file('/sys/class/net/%s' %bondname + - '/bonding/xmit_hash_policy', hash_policy) - self._cache_update([bondname, 'linkinfo', 'xmit_hash_policy'], - hash_policy) - - def get_xmit_hash_policy(self, bondname): - return self._cache_get([bondname, 'linkinfo', 'xmit_hash_policy']) - - def set_miimon(self, bondname, miimon): - if (self._cache_check([bondname, 'linkinfo', 'miimon'], - miimon)): - return - self.write_file('/sys/class/net/%s' %bondname + - '/bonding/miimon', miimon) - self._cache_update([bondname, 'linkinfo', 'miimon'], miimon) - - def get_miimon(self, bondname): - return self._cache_get([bondname, 'linkinfo', 'miimon']) - - def set_clag_enable(self, bondname, clag_id): - clag_enable = '0' if clag_id == '0' else '1' - if self._cache_check([bondname, 'linkinfo', 'clag_enable'], - clag_enable) == False: - self.write_file('/sys/class/net/%s' %bondname + - '/bonding/clag_enable', clag_enable) - self._cache_update([bondname, 'linkinfo', 'clag_enable'], - clag_enable) - - def get_clag_enable(self, bondname): - return self._cache_get([bondname, 'linkinfo', 'clag_enable']) - - def set_mode(self, bondname, mode, prehook=None): - valid_modes = ['balance-rr', 'active-backup', 'balance-xor', - 'broadcast', '802.3ad', 'balance-tlb', 'balance-alb'] - if not mode: - return - if mode not in valid_modes: - raise Exception('invalid mode %s' %mode) - if (self._cache_check([bondname, 'linkinfo', 'mode'], - mode)): - return - if prehook: - prehook(bondname) - self.write_file('/sys/class/net/%s' %bondname + '/bonding/mode', mode) - self._cache_update([bondname, 'linkinfo', 'mode'], mode) - - def get_mode(self, bondname): - return self._cache_get([bondname, 'linkinfo', 'mode']) - - def set_lacp_rate(self, bondname, lacp_rate, prehook=None, posthook=None): - if not lacp_rate or (lacp_rate != '0' and lacp_rate != '1'): - return - if (self._cache_check([bondname, 'linkinfo', 'lacp_rate'], - lacp_rate)): - return - if prehook: - prehook(bondname) - try: - self.write_file('/sys/class/net/%s' %bondname + - '/bonding/lacp_rate', lacp_rate) - except: - raise - finally: - if posthook: - prehook(bondname) - self._cache_update([bondname, 'linkinfo', - 'lacp_rate'], lacp_rate) - - def get_lacp_rate(self, bondname): - return self._cache_get([bondname, 'linkinfo', 'lacp_rate']) - - def set_lacp_fallback_allow(self, bondname, allow, prehook=None, posthook=None): - if (self._cache_check([bondname, 'linkinfo', 'lacp_bypass_allow'], - lacp_bypass_allow)): - return - if prehook: - prehook(bondname) - try: - self.write_file('/sys/class/net/%s' %bondname + - '/bonding/lacp_bypass_allow', allow) - except: - raise - finally: - if posthook: - posthook(bondname) - self._cache_update([bondname, 'linkinfo', - 'lacp_bypass_allow'], allow) - - def get_lacp_fallback_allow(self, bondname): - return self._cache_get([bondname, 'linkinfo', 'lacp_bypass_allow']) - - def set_lacp_fallback_period(self, bondname, period, prehook=None, posthook=None): - if (self._cache_check([bondname, 'linkinfo', 'lacp_bypass_period'], - lacp_bypass_period)): - return - if prehook: - prehook(bondname) - try: - self.write_file('/sys/class/net/%s' %bondname + - '/bonding/lacp_bypass_period', period) - except: - raise - finally: - if posthook: - posthook(bondname) - self._cache_update([bondname, 'linkinfo', - 'lacp_bypass_period'], period) - - def get_lacp_fallback_period(self, bondname): - return self._cache_get([bondname, 'linkinfo', 'lacp_bypass_period']) - - def set_min_links(self, bondname, min_links, prehook=None): - if (self._cache_check([bondname, 'linkinfo', 'min_links'], - min_links)): - return - if prehook: - prehook(bondname) - self.write_file('/sys/class/net/%s/bonding/min_links' %bondname, - min_links) - self._cache_update([bondname, 'linkinfo', 'min_links'], min_links) - - def get_min_links(self, bondname): - return self._cache_get([bondname, 'linkinfo', 'min_links']) - - def set_lacp_fallback_priority(self, bondname, port, val): - slavefile = '/sys/class/net/%s/bonding_slave/lacp_bypass_priority' %port - if os.path.exists(slavefile): - self.write_file(slavefile, val) - - def get_lacp_fallback_priority(self, bondname): - slaves = self.get_slaves(bondname) - if not slaves: - return slaves - prios = [] - for slave in slaves: - priofile = '/sys/class/net/%s/bonding_slave/lacp_bypass_priority' %slave - if os.path.exists(priofile): - val = self.read_file_oneline(priofile) - if val and val != '0': - prio = slave + '=' + val - prios.append(prio) - prios.sort() - prio_str = ' '.join(prios) - return prio_str - - def get_ad_sys_mac_addr(self, bondname): - return self._cache_get([bondname, 'linkinfo', 'ad_sys_mac_addr']) - - def get_ad_sys_priority(self, bondname): - return self._cache_get([bondname, 'linkinfo', 'ad_sys_priority']) - - def enslave_slave(self, bondname, slave, prehook=None, posthook=None): - slaves = self._cache_get([bondname, 'linkinfo', 'slaves']) - if slaves and slave in slaves: return - if prehook: - prehook(slave) - self.write_file('/sys/class/net/%s' %bondname + - '/bonding/slaves', '+' + slave) - if posthook: - posthook(slave) - self._cache_update([bondname, 'linkinfo', 'slaves'], slave) - - def remove_slave(self, bondname, slave): - slaves = self._cache_get([bondname, 'linkinfo', 'slaves']) - if slave not in slaves: - return - sysfs_bond_path = ('/sys/class/net/%s' %bondname + - '/bonding/slaves') - if not os.path.exists(sysfs_bond_path): - return - self.write_file(sysfs_bond_path, '-' + slave) - self._cache_delete([bondname, 'linkinfo', 'slaves'], slave) - - def remove_slaves_all(self, bondname): - if not _self._cache_get([bondname, 'linkinfo', 'slaves']): - return - slaves = None - sysfs_bond_path = ('/sys/class/net/%s' %bondname + - '/bonding/slaves') - ipcmd = iproute2() - try: - f = open(sysfs_bond_path, 'r') - slaves = f.readline().strip().split() - f.close() - except IOError, e: - raise Exception('error reading slaves of bond %s' %bondname - + '(' + str(e) + ')') - for slave in slaves: - ipcmd.ip_link_down(slave) - try: - self.remove_slave(bondname, slave) - except Exception, e: - if not self.FORCE: - raise Exception('error removing slave %s' - %slave + ' from bond %s' %bondname + - '(%s)' %str(e)) - else: - pass - self._cache_del([bondname, 'linkinfo', 'slaves']) - - def load_bonding_module(self): - return self.exec_command('modprobe -q bonding') - - def create_bond(self, bondname): - if self.bond_exists(bondname): - return - sysfs_net = '/sys/class/net/' - sysfs_bonding_masters = sysfs_net + 'bonding_masters' - if not os.path.exists(sysfs_bonding_masters): - self.logger.debug('loading bonding driver') - self.load_bonding_module() - return True - self.write_file(sysfs_bonding_masters, '+' + bondname) - self._cache_update([bondname], {}) - - def delete_bond(self, bondname): - if not os.path.exists('/sys/class/net/%s' %bondname): - return - self.write_file('/sys/class/net/bonding_masters', '-' + bondname) - self._cache_delete([bondname]) - - def unset_master(self, bondname): - print 'Do nothing yet' - return 0 - - def get_slaves(self, bondname): - slaves = self._cache_get([bondname, 'linkinfo', 'slaves']) - if slaves: - return list(slaves) - slavefile = '/sys/class/net/%s/bonding/slaves' %bondname - if os.path.exists(slavefile): - buf = self.read_file_oneline(slavefile) - if buf: - slaves = buf.split() - if not slaves: - return slaves - self._cache_update([bondname, 'linkinfo', 'slaves'], slaves) - return list(slaves) - - def bond_slave_exists(self, bond, slave): - slaves = self.get_slaves(bond) - if not slaves: return False - return slave in slaves - - def bond_exists(self, bondname): - return os.path.exists('/sys/class/net/%s/bonding' %bondname) diff --git a/ifupdownaddons/iproute2.py b/ifupdownaddons/iproute2.py deleted file mode 100644 index e9476a9..0000000 --- a/ifupdownaddons/iproute2.py +++ /dev/null @@ -1,1028 +0,0 @@ -#!/usr/bin/python -# -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. -# Author: Roopa Prabhu, roopa@cumulusnetworks.com -# - -import os -import glob -import shlex -import signal -import subprocess - -from ifupdown.utils import utils -from collections import OrderedDict -from utilsbase import * -from systemutils import * -from cache import * -import ifupdown.ifupdownflags as ifupdownflags - -VXLAN_UDP_PORT = 4789 - -class iproute2(utilsBase): - """ This class contains helper methods to cache and interact with the - commands in the iproute2 package """ - - _cache_fill_done = False - ipbatchbuf = '' - ipbatch = False - ipbatch_pause = False - - def __init__(self, *args, **kargs): - utilsBase.__init__(self, *args, **kargs) - if ifupdownflags.flags.CACHE: - self._fill_cache() - self.supported_command = { - '/sbin/bridge -c -json vlan show': True - } - - def _fill_cache(self): - if not iproute2._cache_fill_done: - self._link_fill() - self._addr_fill() - iproute2._cache_fill_done = True - return True - return False - - def _get_vland_id(self, citems, i, warn): - try: - sub = citems[i:] - index = sub.index('id') - int(sub[index + 1]) - return sub[index + 1] - except: - if warn: - raise Exception('invalid use of \'vlan\' keyword') - return None - - def _link_fill(self, ifacename=None, refresh=False): - """ fills cache with link information - - if ifacename argument given, fill cache for ifacename, else - fill cache for all interfaces in the system - """ - - warn = True - linkout = {} - vxrd_running = False - if iproute2._cache_fill_done and not refresh: return - try: - # if ifacename already present, return - if (ifacename and not refresh and - linkCache.get_attr([ifacename, 'ifflag'])): - return - except: - pass - cmdout = self.link_show(ifacename=ifacename) - if not cmdout: - return - # read vxrd.pid and cache the running state before going through - # every interface in the system - if systemUtils.is_service_running(None, '/var/run/vxrd.pid'): - vxrd_running = True - for c in cmdout.splitlines(): - citems = c.split() - ifnamenlink = citems[1].split('@') - if len(ifnamenlink) > 1: - ifname = ifnamenlink[0] - iflink = ifnamenlink[1].strip(':') - else: - ifname = ifnamenlink[0].strip(':') - iflink = None - linkattrs = {} - linkattrs['link'] = iflink - linkattrs['ifindex'] = citems[0].strip(':') - flags = citems[2].strip('<>').split(',') - linkattrs['flags'] = flags - linkattrs['ifflag'] = 'UP' if 'UP' in flags else 'DOWN' - for i in range(0, len(citems)): - try: - if citems[i] == 'mtu': - linkattrs['mtu'] = citems[i + 1] - elif citems[i] == 'state': - linkattrs['state'] = citems[i + 1] - elif citems[i] == 'link/ether': - linkattrs['hwaddress'] = citems[i + 1] - elif citems[i] in [ 'link/gre', 'link/ipip', 'link/sit', 'link/gre6', 'link/tunnel6', 'gretap' ]: - linkattrs['kind'] = 'tunnel' - tunattrs = {'mode' : citems[i].split ('/')[-1], - 'endpoint' : None, - 'local' : None, - 'ttl' : None, - 'physdev' : None} - for j in range(i, len(citems)): - if citems[j] == 'local': - tunattrs['local'] = citems[j + 1] - elif citems[j] == 'remote': - tunattrs['endpoint'] = citems[j + 1] - elif citems[j] == 'ttl': - tunattrs['ttl'] = citems[j + 1] - elif citems[j] == 'dev': - tunattrs['physdev'] = citems[j + 1] - elif citems[j] in ['vti', 'vti6', 'ip6gre', 'ipip6', 'ip6ip6']: - tunattrs['mode'] = citems[j] - linkattrs['linkinfo'] = tunattrs - break - elif citems[i] == 'link/ppp': - linkattrs['kind'] = 'ppp' - elif citems[i] == 'vlan': - vlanid = self._get_vland_id(citems, i, warn) - if vlanid: - linkattrs['linkinfo'] = {'vlanid': vlanid} - linkattrs['kind'] = 'vlan' - elif citems[i] == 'dummy': - linkattrs['kind'] = 'dummy' - elif citems[i] == 'vxlan' and citems[i + 1] == 'id': - linkattrs['kind'] = 'vxlan' - vattrs = {'vxlanid': citems[i + 2], - 'svcnode': None, - 'physdev': None, - 'remote': [], - 'ageing': citems[i + 2], - 'learning': 'on'} - for j in range(i + 2, len(citems)): - if citems[j] == 'local': - vattrs['local'] = citems[j + 1] - elif citems[j] == 'group': - vattrs['svcnode'] = citems[j + 1] - elif citems[j] == 'ageing': - vattrs['ageing'] = citems[j + 1] - elif citems[j] == 'nolearning': - vattrs['learning'] = 'off' - elif citems[j] == 'dev': - vattrs['physdev'] = citems[j + 1] - # get vxlan peer nodes if provisioned by user and not by vxrd - if not vxrd_running: - peers = self.get_vxlan_peers(ifname, vattrs['svcnode']) - if peers: - vattrs['remote'] = peers - linkattrs['linkinfo'] = vattrs - break - elif citems[i] == 'vrf' and citems[i + 1] == 'table': - vattrs = {'table': citems[i + 2]} - linkattrs['linkinfo'] = vattrs - linkattrs['kind'] = 'vrf' - linkCache.vrfs[ifname] = vattrs - break - elif citems[i] == 'veth': - linkattrs['kind'] = 'veth' - elif citems[i] == 'vrf_slave': - linkattrs['slave_kind'] = 'vrf_slave' - break - elif citems[i] == 'macvlan' and citems[i + 1] == 'mode': - linkattrs['kind'] = 'macvlan' - except Exception as e: - if warn: - self.logger.debug('%s: parsing error: id, mtu, state, link/ether, vlan, dummy, vxlan, local, remote, ageing, nolearning, vrf, table, vrf_slave are reserved keywords: %s' % (ifname, str(e))) - warn = False - #linkattrs['alias'] = self.read_file_oneline( - # '/sys/class/net/%s/ifalias' %ifname) - linkout[ifname] = linkattrs - [linkCache.update_attrdict([ifname], linkattrs) - for ifname, linkattrs in linkout.items()] - - def _addr_filter(self, ifname, addr, scope=None): - default_addrs = ['127.0.0.1/8', '::1/128' , '0.0.0.0'] - if ifname == 'lo' and addr in default_addrs: - return True - if scope and scope == 'link': - return True - return False - - def _addr_fill(self, ifacename=None, refresh=False): - """ fills cache with address information - - if ifacename argument given, fill cache for ifacename, else - fill cache for all interfaces in the system - """ - linkout = {} - if iproute2._cache_fill_done and not refresh: return - - try: - # Check if ifacename is already full, in which case, return - if ifacename and not refresh: - linkCache.get_attr([ifacename, 'addrs']) - return - except: - pass - cmdout = self.addr_show(ifacename=ifacename) - if not cmdout: - return - for c in cmdout.splitlines(): - citems = c.split() - ifnamenlink = citems[1].split('@') - if len(ifnamenlink) > 1: - ifname = ifnamenlink[0] - else: - ifname = ifnamenlink[0].strip(':') - if not linkout.get(ifname): - linkattrs = {} - linkattrs['addrs'] = OrderedDict({}) - try: - linkout[ifname].update(linkattrs) - except KeyError: - linkout[ifname] = linkattrs - if citems[2] == 'inet': - if self._addr_filter(ifname, citems[3], scope=citems[5]): - continue - addrattrs = {} - addrattrs['scope'] = citems[5] - addrattrs['type'] = 'inet' - linkout[ifname]['addrs'][citems[3]] = addrattrs - elif citems[2] == 'inet6': - if self._addr_filter(ifname, citems[3], scope=citems[5]): - continue - if citems[5] == 'link': continue #skip 'link' addresses - addrattrs = {} - addrattrs['scope'] = citems[5] - addrattrs['type'] = 'inet6' - linkout[ifname]['addrs'][citems[3]] = addrattrs - [linkCache.update_attrdict([ifname], linkattrs) - for ifname, linkattrs in linkout.items()] - - def _cache_get(self, type, attrlist, refresh=False): - try: - if ifupdownflags.flags.DRYRUN: - return False - if ifupdownflags.flags.CACHE: - if self._fill_cache(): - # if we filled the cache, return new data - return linkCache.get_attr(attrlist) - if not refresh: - return linkCache.get_attr(attrlist) - if type == 'link': - self._link_fill(attrlist[0], refresh) - elif type == 'addr': - self._addr_fill(attrlist[0], refresh) - else: - self._link_fill(attrlist[0], refresh) - self._addr_fill(attrlist[0], refresh) - return linkCache.get_attr(attrlist) - except Exception, e: - self.logger.debug('_cache_get(%s) : [%s]' - %(str(attrlist), str(e))) - pass - return None - - def _cache_check(self, type, attrlist, value, refresh=False): - try: - attrvalue = self._cache_get(type, attrlist, refresh) - if attrvalue and attrvalue == value: - return True - except Exception, e: - self.logger.debug('_cache_check(%s) : [%s]' - %(str(attrlist), str(e))) - pass - return False - - def _cache_update(self, attrlist, value): - if ifupdownflags.flags.DRYRUN: return - try: - linkCache.set_attr(attrlist, value) - except: - pass - - def _cache_delete(self, attrlist): - if ifupdownflags.flags.DRYRUN: return - try: - linkCache.del_attr(attrlist) - except: - pass - - def _cache_invalidate(self): - linkCache.invalidate() - iproute2._cache_fill_done = False - - def batch_start(self): - self.ipbatcbuf = '' - self.ipbatch = True - self.ipbatch_pause = False - - def add_to_batch(self, cmd): - self.ipbatchbuf += cmd + '\n' - - def batch_pause(self): - self.ipbatch_pause = True - - def batch_resume(self): - self.ipbatch_pause = False - - def batch_commit(self): - if not self.ipbatchbuf: - self.ipbatchbuf = '' - self.ipbatch = False - self.ipbatch_pause = False - return - try: - utils.exec_command('ip -force -batch -', stdin=self.ipbatchbuf) - except: - raise - finally: - self.ipbatchbuf = '' - self.ipbatch = False - self.ipbatch_pause = False - - def bridge_batch_commit(self): - if not self.ipbatchbuf: - self.ipbatchbuf = '' - self.ipbatch = False - self.ipbatch_pause = False - return - try: - utils.exec_command('bridge -force -batch -', stdin=self.ipbatchbuf) - except: - raise - finally: - self.ipbatchbuf = '' - self.ipbatch = False - self.ipbatch_pause = False - - def addr_show(self, ifacename=None): - if ifacename: - if not self.link_exists(ifacename): - return - return utils.exec_commandl(['ip', '-o', 'addr', 'show', 'dev', - ifacename]) - else: - return utils.exec_commandl(['ip', '-o', 'addr', 'show']) - - def link_show(self, ifacename=None): - if ifacename: - return utils.exec_commandl(['ip', '-o', '-d', 'link', 'show', 'dev', - ifacename]) - else: - return utils.exec_commandl(['ip', '-o', '-d', 'link', 'show']) - - def addr_add(self, ifacename, address, broadcast=None, - peer=None, scope=None, preferred_lifetime=None): - if not address: - return - cmd = 'addr add %s' %address - if broadcast: - cmd += ' broadcast %s' %broadcast - if peer: - cmd += ' peer %s' %peer - if scope: - cmd += ' scope %s' %scope - if preferred_lifetime: - cmd += ' preferred_lft %s' %preferred_lifetime - cmd += ' dev %s' %ifacename - if self.ipbatch and not self.ipbatch_pause: - self.add_to_batch(cmd) - else: - utils.exec_command('ip %s' % cmd) - self._cache_update([ifacename, 'addrs', address], {}) - - def addr_del(self, ifacename, address, broadcast=None, - peer=None, scope=None): - """ Delete ipv4 address """ - if not address: - return - if not self._cache_get('addr', [ifacename, 'addrs', address]): - return - cmd = 'addr del %s' %address - if broadcast: - cmd += 'broadcast %s' %broadcast - if peer: - cmd += 'peer %s' %peer - if scope: - cmd += 'scope %s' %scope - cmd += ' dev %s' %ifacename - utils.exec_command('ip %s' % cmd) - self._cache_delete([ifacename, 'addrs', address]) - - def addr_flush(self, ifacename): - cmd = 'addr flush dev %s' %ifacename - if self.ipbatch and not self.ipbatch_pause: - self.add_to_batch(cmd) - else: - utils.exec_command('ip %s' % cmd) - self._cache_delete([ifacename, 'addrs']) - - def del_addr_all(self, ifacename, skip_addrs=[]): - if not skip_addrs: skip_addrs = [] - runningaddrsdict = self.addr_get(ifacename) - try: - # XXX: ignore errors. Fix this to delete secondary addresses - # first - [self.addr_del(ifacename, a) for a in - set(runningaddrsdict.keys()).difference(skip_addrs)] - except: - # ignore errors - pass - - def addr_get(self, ifacename, details=True, refresh=False): - addrs = self._cache_get('addr', [ifacename, 'addrs'], - refresh=refresh) - if not addrs: - return None - if details: - return addrs - return addrs.keys() - - def addr_add_multiple(self, ifacename, addrs, purge_existing=False): - # purges address - if purge_existing: - # if perfmode is not set and also if iface has no sibling - # objects, purge addresses that are not present in the new - # config - runningaddrs = self.addr_get(ifacename, details=False) - if addrs == runningaddrs: - return - try: - # if primary address is not same, there is no need to keep any. - # reset all addresses - if (addrs and runningaddrs and - (addrs[0] != runningaddrs[0])): - self.del_addr_all(ifacename) - else: - self.del_addr_all(ifacename, addrs) - except Exception, e: - self.log_warn(str(e)) - for a in addrs: - try: - self.addr_add(ifacename, a) - except Exception, e: - self.logger.error(str(e)) - - def _link_set_ifflag(self, ifacename, value): - # Dont look at the cache, the cache may have stale value - # because link status can be changed by external - # entity (One such entity is ifupdown main program) - cmd = 'link set dev %s %s' %(ifacename, value.lower()) - if self.ipbatch: - self.add_to_batch(cmd) - else: - utils.exec_command('ip %s' % cmd) - - def link_up(self, ifacename): - self._link_set_ifflag(ifacename, 'UP') - - def link_down(self, ifacename): - self._link_set_ifflag(ifacename, 'DOWN') - - def link_set(self, ifacename, key, value=None, - force=False, type=None, state=None): - if not force: - if (key not in ['master', 'nomaster'] and - self._cache_check('link', [ifacename, key], value)): - return - cmd = 'link set dev %s' %ifacename - if type: - cmd += ' type %s' %type - cmd += ' %s' %key - if value: - cmd += ' %s' %value - if state: - cmd += ' %s' %state - if self.ipbatch: - self.add_to_batch(cmd) - else: - utils.exec_command('ip %s' % cmd) - if key not in ['master', 'nomaster']: - self._cache_update([ifacename, key], value) - - def link_set_hwaddress(self, ifacename, hwaddress, force=False): - if not force: - if self._cache_check('link', [ifacename, 'hwaddress'], hwaddress): - return - self.link_down(ifacename) - cmd = 'link set dev %s address %s' %(ifacename, hwaddress) - if self.ipbatch: - self.add_to_batch(cmd) - else: - utils.exec_command('ip %s' % cmd) - self.link_up(ifacename) - self._cache_update([ifacename, 'hwaddress'], hwaddress) - - def link_set_mtu(self, ifacename, mtu): - if ifupdownflags.flags.DRYRUN: - return True - if not mtu or not ifacename: return - with open('/sys/class/net/%s/mtu' % ifacename, 'w') as f: - f.write(mtu) - self._cache_update([ifacename, 'mtu'], mtu) - - def link_set_alias(self, ifacename, alias): - if not alias: - utils.exec_user_command('echo "" > /sys/class/net/%s/ifalias' - % ifacename) - else: - self.write_file('/sys/class/net/%s/ifalias' % ifacename, alias) - - def link_get_alias(self, ifacename): - return self.read_file_oneline('/sys/class/net/%s/ifalias' - %ifacename) - - def link_isloopback(self, ifacename): - flags = self._cache_get('link', [ifacename, 'flags']) - if not flags: - return - if 'LOOPBACK' in flags: - return True - return False - - def link_get_status(self, ifacename): - return self._cache_get('link', [ifacename, 'ifflag'], refresh=True) - - def route_add_gateway(self, ifacename, gateway, vrf=None, metric=None): - if not gateway: - return - if not vrf: - cmd = 'ip route add default via %s' %gateway - else: - cmd = 'ip route add table %s default via %s' %(vrf, gateway) - # Add metric - if metric: - cmd += 'metric %s' %metric - cmd += ' dev %s' %ifacename - utils.exec_command(cmd) - - def route_del_gateway(self, ifacename, gateway, vrf=None, metric=None): - # delete default gw - if not gateway: - return - if not vrf: - cmd = 'ip route del default via %s' %gateway - else: - cmd = 'ip route del table %s default via %s' %(vrf, gateway) - if metric: - cmd += ' metric %s' %metric - cmd += ' dev %s' %ifacename - utils.exec_command(cmd) - - def route6_add_gateway(self, ifacename, gateway): - if not gateway: - return - return utils.exec_command('ip -6 route add default via %s dev %s' % - (gateway, ifacename)) - - def route6_del_gateway(self, ifacename, gateway): - if not gateway: - return - return utils.exec_command('ip -6 route del default via %s dev %s' % - (gateway, ifacename)) - - def link_create_vlan(self, vlan_device_name, vlan_raw_device, vlanid): - if self.link_exists(vlan_device_name): - return - utils.exec_command('ip link add link %s name %s type vlan id %d' % - (vlan_raw_device, vlan_device_name, vlanid)) - self._cache_update([vlan_device_name], {}) - - def link_create_vlan_from_name(self, vlan_device_name): - v = vlan_device_name.split('.') - if len(v) != 2: - self.logger.warn('invalid vlan device name %s' %vlan_device_name) - return - self.link_create_vlan(vlan_device_name, v[0], v[1]) - - def link_create_macvlan(self, name, linkdev, mode='private'): - if self.link_exists(name): - return - cmd = ('link add link %s' %linkdev + - ' name %s' %name + - ' type macvlan mode %s' %mode) - if self.ipbatch and not self.ipbatch_pause: - self.add_to_batch(cmd) - else: - utils.exec_command('ip %s' % cmd) - self._cache_update([name], {}) - - def get_vxlan_peers(self, dev, svcnodeip): - cmd = 'bridge fdb show brport %s' % dev - cur_peers = [] - try: - ps = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, close_fds=False) - utils.enable_subprocess_signal_forwarding(ps, signal.SIGINT) - output = subprocess.check_output(('grep', '00:00:00:00:00:00'), stdin=ps.stdout) - ps.wait() - utils.disable_subprocess_signal_forwarding(signal.SIGINT) - try: - ppat = re.compile('\s+dst\s+(\d+.\d+.\d+.\d+)\s+') - for l in output.split('\n'): - m = ppat.search(l) - if m and m.group(1) != svcnodeip: - cur_peers.append(m.group(1)) - except: - self.logger.warn('error parsing ip link output') - pass - except subprocess.CalledProcessError as e: - if e.returncode != 1: - self.logger.error(str(e)) - finally: - utils.disable_subprocess_signal_forwarding(signal.SIGINT) - - return cur_peers - - def link_create_vxlan(self, name, vxlanid, - localtunnelip=None, - svcnodeip=None, - remoteips=None, - learning='on', - ageing=None, - anycastip=None): - if svcnodeip and remoteips: - raise Exception("svcnodeip and remoteip is mutually exclusive") - args = '' - if svcnodeip: - args += ' remote %s' %svcnodeip - if ageing: - args += ' ageing %s' %ageing - if learning == 'off': - args += ' nolearning' - - if self.link_exists(name): - cmd = 'link set dev %s type vxlan dstport %d' %(name, VXLAN_UDP_PORT) - vxlanattrs = self.get_vxlandev_attrs(name) - # on ifreload do not overwrite anycast_ip to individual ip if clagd - # has modified - if vxlanattrs: - running_localtunnelip = vxlanattrs.get('local') - if anycastip and running_localtunnelip and anycastip == running_localtunnelip: - localtunnelip = running_localtunnelip - running_svcnode = vxlanattrs.get('svcnode') - if running_svcnode and not svcnodeip: - args += ' noremote' - else: - cmd = 'link add dev %s type vxlan id %s dstport %d' %(name, vxlanid, VXLAN_UDP_PORT) - - if localtunnelip: - args += ' local %s' %localtunnelip - cmd += args - - if self.ipbatch and not self.ipbatch_pause: - self.add_to_batch(cmd) - else: - utils.exec_command('ip %s' % cmd) - - # XXX: update linkinfo correctly - self._cache_update([name], {}) - - def link_exists(self, ifacename): - if ifupdownflags.flags.DRYRUN: - return True - return os.path.exists('/sys/class/net/%s' %ifacename) - - def link_get_ifindex(self, ifacename): - if ifupdownflags.flags.DRYRUN: - return True - return self.read_file_oneline('/sys/class/net/%s/ifindex' %ifacename) - - def is_vlan_device_by_name(self, ifacename): - if re.search(r'\.', ifacename): - return True - return False - - def route_add(self, route): - utils.exec_command('ip route add %s' % route) - - def route6_add(self, route): - utils.exec_command('ip -6 route add %s' % route) - - def get_vlandev_attrs(self, ifacename): - return (self._cache_get('link', [ifacename, 'link']), - self._cache_get('link', [ifacename, 'linkinfo', 'vlanid'])) - - def get_vxlandev_attrs(self, ifacename): - return self._cache_get('link', [ifacename, 'linkinfo']) - - def get_vxlandev_learning(self, ifacename): - return self._cache_get('link', [ifacename, 'linkinfo', 'learning']) - - def set_vxlandev_learning(self, ifacename, learn): - if learn == 'on': - utils.exec_command('ip link set dev %s type vxlan learning' %ifacename) - self._cache_update([ifacename, 'linkinfo', 'learning'], 'on') - else: - utils.exec_command('ip link set dev %s type vxlan nolearning' %ifacename) - self._cache_update([ifacename, 'linkinfo', 'learning'], 'off') - - def link_get_linkinfo_attrs(self, ifacename): - return self._cache_get('link', [ifacename, 'linkinfo']) - - def link_get_mtu(self, ifacename, refresh=False): - return self._cache_get('link', [ifacename, 'mtu'], refresh=refresh) - - def link_get_mtu_sysfs(self, ifacename): - return self.read_file_oneline('/sys/class/net/%s/mtu' - %ifacename) - - def link_get_kind(self, ifacename): - return self._cache_get('link', [ifacename, 'kind']) - - def link_get_slave_kind(self, ifacename): - return self._cache_get('link', [ifacename, 'slave_kind']) - - def link_get_hwaddress(self, ifacename): - address = self._cache_get('link', [ifacename, 'hwaddress']) - # newly created logical interface addresses dont end up in the cache - # read hwaddress from sysfs file for these interfaces - if not address: - address = self.read_file_oneline('/sys/class/net/%s/address' - %ifacename) - return address - - def link_create(self, ifacename, type, attrs={}): - """ generic link_create function """ - if self.link_exists(ifacename): - return - cmd = 'link add' - cmd += ' name %s type %s' %(ifacename, type) - if attrs: - for k, v in attrs.iteritems(): - cmd += ' %s' %k - if v: - cmd += ' %s' %v - if self.ipbatch and not self.ipbatch_pause: - self.add_to_batch(cmd) - else: - utils.exec_command('ip %s' % cmd) - self._cache_update([ifacename], {}) - - def link_delete(self, ifacename): - if not self.link_exists(ifacename): - return - cmd = 'link del %s' %ifacename - if self.ipbatch and not self.ipbatch_pause: - self.add_to_batch(cmd) - else: - utils.exec_command('ip %s' % cmd) - self._cache_invalidate() - - def link_get_master(self, ifacename): - sysfs_master_path = '/sys/class/net/%s/master' %ifacename - if os.path.exists(sysfs_master_path): - link_path = os.readlink(sysfs_master_path) - if link_path: - return os.path.basename(link_path) - else: - return None - else: - return self._cache_get('link', [ifacename, 'master']) - - def tunnel_create(self, tunnelname, mode, attrs={}): - """ generic link_create function """ - if self.link_exists(tunnelname): - return - - cmd = '' - if '6' in mode: - cmd = ' -6 ' - - if mode in ['gretap']: - cmd += 'link add %s type %s' % (tunnelname, mode) - else: - cmd += 'tunnel add %s mode %s' % (tunnelname, mode) - - if attrs: - for k, v in attrs.iteritems(): - cmd += ' %s' % k - if v: - cmd += ' %s' % v - if self.ipbatch and not self.ipbatch_pause: - self.add_to_batch(cmd) - else: - utils.exec_command('ip %s' % cmd) - self._cache_update([tunnelname], {}) - - def bridge_port_vids_add(self, bridgeportname, vids): - [utils.exec_command('bridge vlan add vid %s dev %s' % - (v, bridgeportname)) for v in vids] - - def bridge_port_vids_del(self, bridgeportname, vids): - if not vids: - return - [utils.exec_command('bridge vlan del vid %s dev %s' % - (v, bridgeportname)) for v in vids] - - def bridge_port_vids_flush(self, bridgeportname, vid): - utils.exec_command('bridge vlan del vid %s dev %s' % - (vid, bridgeportname)) - - def bridge_port_vids_get(self, bridgeportname): - utils.exec_command('/sbin/bridge vlan show %s' % bridgeportname) - bridgeout = utils.exec_command('/sbin/bridge vlan show dev %s' % - bridgeportname) - if not bridgeout: return [] - brvlanlines = bridgeout.readlines()[2:] - vids = [l.strip() for l in brvlanlines] - return [v for v in vids if v] - - def bridge_port_vids_get_all(self): - brvlaninfo = {} - bridgeout = utils.exec_command('/sbin/bridge -c vlan show') - if not bridgeout: return brvlaninfo - brvlanlines = bridgeout.splitlines() - brportname=None - for l in brvlanlines[1:]: - if l and not l.startswith(' ') and not l.startswith('\t'): - attrs = l.split() - brportname = attrs[0].strip() - brvlaninfo[brportname] = {'pvid' : None, 'vlan' : []} - l = ' '.join(attrs[1:]) - if not brportname or not l: - continue - l = l.strip() - if 'PVID' in l: - brvlaninfo[brportname]['pvid'] = l.split()[0] - elif 'Egress Untagged' not in l: - brvlaninfo[brportname]['vlan'].append(l) - return brvlaninfo - - def bridge_port_vids_get_all_json(self): - if not self.supported_command['/sbin/bridge -c -json vlan show']: - return {} - brvlaninfo = {} - try: - bridgeout = utils.exec_command('/sbin/bridge -c -json vlan show') - except: - self.supported_command['/sbin/bridge -c -json vlan show'] = False - self.logger.info('/sbin/bridge -c -json vlan show: skipping unsupported command') - return {} - if not bridgeout: return brvlaninfo - try: - vlan_json_dict = json.loads(bridgeout, encoding="utf-8") - except Exception, e: - self.logger.info('json loads failed with (%s)' %str(e)) - return {} - return vlan_json_dict - - def bridge_port_pvid_add(self, bridgeportname, pvid): - if self.ipbatch and not self.ipbatch_pause: - self.add_to_batch('vlan add vid %s untagged pvid dev %s' % - (pvid, bridgeportname)) - else: - utils.exec_command('bridge vlan add vid %s untagged pvid dev %s' % - (pvid, bridgeportname)) - - def bridge_port_pvid_del(self, bridgeportname, pvid): - if self.ipbatch and not self.ipbatch_pause: - self.add_to_batch('vlan del vid %s untagged pvid dev %s' % - (pvid, bridgeportname)) - else: - utils.exec_command('bridge vlan del vid %s untagged pvid dev %s' % - (pvid, bridgeportname)) - - def bridge_port_pvids_get(self, bridgeportname): - return self.read_file_oneline('/sys/class/net/%s/brport/pvid' - %bridgeportname) - - def bridge_vids_add(self, bridgeportname, vids, bridge=True): - target = 'self' if bridge else '' - if self.ipbatch and not self.ipbatch_pause: - [self.add_to_batch('vlan add vid %s dev %s %s' % - (v, bridgeportname, target)) for v in vids] - else: - [utils.exec_command('bridge vlan add vid %s dev %s %s' % - (v, bridgeportname, target)) for v in vids] - - def bridge_vids_del(self, bridgeportname, vids, bridge=True): - target = 'self' if bridge else '' - if self.ipbatch and not self.ipbatch_pause: - [self.add_to_batch('vlan del vid %s dev %s %s' % - (v, bridgeportname, target)) for v in vids] - else: - [utils.exec_command('bridge vlan del vid %s dev %s %s' % - (v, bridgeportname, target)) for v in vids] - - def bridge_fdb_add(self, dev, address, vlan=None, bridge=True, remote=None): - target = 'self' if bridge else '' - vlan_str = '' - if vlan: - vlan_str = 'vlan %s ' % vlan - - dst_str = '' - if remote: - dst_str = 'dst %s ' % remote - - utils.exec_command('bridge fdb replace %s dev %s %s %s %s' % - (address, dev, vlan_str, target, dst_str)) - - def bridge_fdb_append(self, dev, address, vlan=None, bridge=True, remote=None): - target = 'self' if bridge else '' - vlan_str = '' - if vlan: - vlan_str = 'vlan %s ' % vlan - - dst_str = '' - if remote: - dst_str = 'dst %s ' % remote - - utils.exec_command('bridge fdb append %s dev %s %s %s %s' % - (address, dev, vlan_str, target, dst_str)) - - def bridge_fdb_del(self, dev, address, vlan=None, bridge=True, remote=None): - target = 'self' if bridge else '' - vlan_str = '' - if vlan: - vlan_str = 'vlan %s ' % vlan - - dst_str = '' - if remote: - dst_str = 'dst %s ' % remote - utils.exec_command('bridge fdb del %s dev %s %s %s %s' % - (address, dev, vlan_str, target, dst_str)) - - def bridge_is_vlan_aware(self, bridgename): - filename = '/sys/class/net/%s/bridge/vlan_filtering' %bridgename - if os.path.exists(filename) and self.read_file_oneline(filename) == '1': - return True - return False - - def bridge_port_get_bridge_name(self, bridgeport): - filename = '/sys/class/net/%s/brport/bridge' %bridgeport - try: - return os.path.basename(os.readlink(filename)) - except: - return None - - def bridge_port_exists(self, bridge, bridgeportname): - try: - return os.path.exists('/sys/class/net/%s/brif/%s' - %(bridge, bridgeportname)) - except Exception: - return False - - def bridge_fdb_show_dev(self, dev): - try: - fdbs = {} - output = utils.exec_command('bridge fdb show dev %s' % dev) - if output: - for fdb_entry in output.splitlines(): - try: - entries = fdb_entry.split() - fdbs.setdefault(entries[2], []).append(entries[0]) - except: - self.logger.debug('%s: invalid fdb line \'%s\'' - %(dev, fdb_entry)) - pass - return fdbs - except Exception: - return None - - def is_bridge(self, bridge): - return os.path.exists('/sys/class/net/%s/bridge' %bridge) - - def is_link_up(self, ifacename): - ret = False - try: - flags = self.read_file_oneline('/sys/class/net/%s/flags' %ifacename) - iflags = int(flags, 16) - if (iflags & 0x0001): - ret = True - except: - ret = False - pass - return ret - - def ip_route_get_dev(self, prefix): - try: - output = utils.exec_command('ip route get %s' % prefix) - if output: - rline = output.splitlines()[0] - if rline: - rattrs = rline.split() - return rattrs[rattrs.index('dev') + 1] - except Exception, e: - self.logger.debug('ip_route_get_dev: failed .. %s' %str(e)) - pass - return None - - def link_get_lowers(self, ifacename): - try: - lowers = glob.glob("/sys/class/net/%s/lower_*" %ifacename) - if not lowers: - return [] - return [os.path.basename(l)[6:] for l in lowers] - except: - return [] - - def link_get_uppers(self, ifacename): - try: - uppers = glob.glob("/sys/class/net/%s/upper_*" %ifacename) - if not uppers: - return None - return [ os.path.basename(u)[6:] for u in uppers ] - except: - return None - - def link_get_vrfs(self): - self._fill_cache() - return linkCache.vrfs - - def get_brport_learning(self, ifacename): - learn = self.read_file_oneline('/sys/class/net/%s/brport/learning' - %ifacename) - if learn and learn == '1': - return 'on' - else: - return 'off' - - def set_brport_learning(self, ifacename, learn): - if learn == 'off': - return self.write_file('/sys/class/net/%s/brport/learning' - %ifacename, '0') - else: - return self.write_file('/sys/class/net/%s/brport/learning' - %ifacename, '1') diff --git a/man/interfaces.5 b/man/interfaces.5 deleted file mode 100644 index e5d15a4..0000000 --- a/man/interfaces.5 +++ /dev/null @@ -1,207 +0,0 @@ -.\" Man page generated from reStructeredText. -. -.TH INTERFACES 5 "2014-02-05" "0.1" "" -.SH NAME -interfaces \- network interface configuration for ifupdown -. -.nr rst2man-indent-level 0 -. -.de1 rstReportMargin -\\$1 \\n[an-margin] -level \\n[rst2man-indent-level] -level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] -- -\\n[rst2man-indent0] -\\n[rst2man-indent1] -\\n[rst2man-indent2] -.. -.de1 INDENT -.\" .rstReportMargin pre: -. RS \\$1 -. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] -. nr rst2man-indent-level +1 -.\" .rstReportMargin post: -.. -.de UNINDENT -. RE -.\" indent \\n[an-margin] -.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] -.nr rst2man-indent-level -1 -.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] -.in \\n[rst2man-indent\\n[rst2man-indent-level]]u -.. -.SH DESCRIPTION -.INDENT 0.0 -.INDENT 3.5 -\fB/etc/network/interfaces\fP contains network interface configuration -information for the \fBifup(8)\fP, \fBifdown(8)\fP and \fBifquery(8)\fP commands. -.sp -This is where you configure how your system is connected to the network. -.sp -Lines starting with # are ignored. Note that end\-of\-line comments are -NOT supported, comments must be on a line of their own. -.sp -A line may be extended across multiple lines by making the last character -a backslash. -.sp -The file consists of zero or more "iface", "auto", "allow\-" -and "source" stanzas. Here is an example: -.sp -.nf -.ft C -auto lo eth0 -allow\-hotplug eth1 - -iface lo inet loopback - -source /etc/network/interfaces.d/bridges - -iface eth0 inet static - address 192.168.1.1/24 - up flush\-mail - -iface eth1 inet dhcp -.ft P -.fi -.sp -Lines beginning with the word "auto" are used to identify the physical -interfaces to be brought up when ifup is run with the \-a option. -(This option is used by the system boot scripts.) Physical interface names -should follow the word "auto" on the same line. There can be multiple -"auto" stanzas. -.sp -Lines beginning with "allow\-" are used to identify interfaces that -should be brought up automatically by various subsystems. This may be -done using a command such as "ifup \-\-allow=hotplug eth0 eth1", which -will only bring up eth0 or eth1 if it is listed in an "allow\-hotplug" -line. Note that "allow\-auto" and "auto" are synonyms. -.sp -Lines beginning with "source" are used to include stanzas from other -files, so configuration can be split into many files. The word "source" -is followed by the path of file to be sourced. Shell wildcards can be -used. Currently only supports absolute -path names. -.sp -iface is normally given a interface name as its first non\-option -argument. -.sp -The interface name is followed by the name of the address family that the -interface uses. This will be "inet" for TCP/IP networking and inet6 for -ipv6. Following that is the name of the method used to configure the -interface. -.sp -ifupdown supports iface stanzas without a family or a method. This enables -using the same stanza for inet and inet6 family addresses. And the method -defaults to "static" -.sp -Additional interface options/attributes can be given on subsequent lines -in the iface stanza. These options come from addon modules. see -\fBifupdown\-addons\-interfaces(5)\fP for these options. -.sp -example bridge interface with additional attributes listed in the -\fBifupdown\-addons\-interfaces(5)\fP man page: -.sp -.nf -.ft C -auto br0 -iface br0 - address 12.0.0.4/24 - address 2000:1000:1000:1000:3::5/128 - bridge\-ports swp1 swp2 swp3 - bridge\-stp on -.ft P -.fi -.sp -ifupdown supports python\-mako style templates in the interfaces file. -See examples section for details. -.sp -See \fB/usr/share/doc/python\-ifupdown2/examples/\fP for \fBinterfaces(5)\fP -file examples and interfaces file generation scripts. -.UNINDENT -.UNINDENT -.SH METHODS -.INDENT 0.0 -.INDENT 3.5 -Both \fBinet\fP and \fBinet6\fP address family interfaces can use the following -methods (However they are not required): -.INDENT 0.0 -.TP -.B The loopback Method -This method may be used to define the loopback interface. -.TP -.B The static Method -This method may be used to define ethernet interfaces with -statically allocated addresses. -.TP -.B The dhcp Method -This method may be used to obtain an address via DHCP. -.UNINDENT -.UNINDENT -.UNINDENT -.SH BUILTIN INTERFACES -.INDENT 0.0 -.INDENT 3.5 -\fBiface\fP sections for some interfaces like physical interfaces or vlan -interfaces in dot notation (like eth1.100) are understood by ifupdown. -These interfaces do not need an entry in the interfaces file if -they are dependents of other interfaces and dont need any specific -configurations like addresses etc. -.UNINDENT -.UNINDENT -.SH EXAMPLES -.INDENT 0.0 -.INDENT 3.5 -Sample /etc/network/interfaces file: -.sp -.nf -.ft C -auto lo -iface lo - address 192.168.2.0/24 - address 2001:dee:eeee:1::4/128 - -auto eth0 -iface eth0 inet dhcp - -auto eth1 -iface eth1 inet manual - address 192.168.2.0/24 - address 2001:dee:eeee:1::4/128 - -# source files from a directory /etc/network/interfaces.d -source /etc/network/interfaces.d/* - -# Using mako style templates -% for v in [11,12]: - auto vlan${v} - iface vlan${v} inet static - address 10.20.${v}.3/24 -% endfor -.ft P -.fi -.sp -For additional syntax and examples see \fBifupdown\-addons\-interfaces(5)\fP -.UNINDENT -.UNINDENT -.SH FILES -.INDENT 0.0 -.INDENT 3.5 -/etc/network/interfaces -.UNINDENT -.UNINDENT -.SH SEE ALSO -.INDENT 0.0 -.INDENT 3.5 -ifupdown\-addons\-interfaces(5), -ifup(8), -ifquery(8), -ifreload(8) -.UNINDENT -.UNINDENT -.SH AUTHOR -Roopa Prabhu -.SH COPYRIGHT -Copyright 2014 Cumulus Networks, Inc. All rights reserved. -.\" Generated by docutils manpage writer. -.\" -. diff --git a/sbin/ifupdown b/sbin/ifupdown deleted file mode 100755 index d6bdeb2..0000000 --- a/sbin/ifupdown +++ /dev/null @@ -1,496 +0,0 @@ -#!/usr/bin/python -# -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. -# Author: Roopa Prabhu, roopa@cumulusnetworks.com -# -# ifupdown -- -# tool to configure network interfaces -# -import sys -import os -import argcomplete -import argparse -import ConfigParser -import StringIO -import logging -import logging.handlers -import resource -from ifupdown.ifupdownmain import * -from ifupdown.utils import * - -lockfile="/run/network/.lock" -configfile="/etc/network/ifupdown2/ifupdown2.conf" -configmap_g=None -logger = None -interfacesfileiobuf=None -ENVPATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" - -def run_up(args): - logger.debug('args = %s' %str(args)) - - try: - iflist = args.iflist - if len(args.iflist) == 0: - iflist = None - logger.debug('creating ifupdown object ..') - cachearg=(False if (iflist or args.nocache or - args.perfmode or args.noact) - else True) - ifupdown_handle = ifupdownMain(config=configmap_g, - force=args.force, - withdepends=args.withdepends, - perfmode=args.perfmode, - dryrun=args.noact, - cache=cachearg, - addons_enable=not args.noaddons, - statemanager_enable=not args.noaddons, - interfacesfile=args.interfacesfile, - interfacesfileiobuf=interfacesfileiobuf, - interfacesfileformat=args.interfacesfileformat) - if args.noaddons: - ifupdown_handle.up(['up'], args.all, args.CLASS, iflist, - excludepats=args.excludepats, - printdependency=args.printdependency, - syntaxcheck=args.syntaxcheck, type=args.type, - skipupperifaces=args.skipupperifaces) - else: - ifupdown_handle.up(['pre-up', 'up', 'post-up'], - args.all, args.CLASS, iflist, - excludepats=args.excludepats, - printdependency=args.printdependency, - syntaxcheck=args.syntaxcheck, type=args.type, - skipupperifaces=args.skipupperifaces) - except: - raise - -def run_down(args): - logger.debug('args = %s' %str(args)) - - try: - iflist = args.iflist - logger.debug('creating ifupdown object ..') - ifupdown_handle = ifupdownMain(config=configmap_g, force=args.force, - withdepends=args.withdepends, - perfmode=args.perfmode, - dryrun=args.noact, - addons_enable=not args.noaddons, - statemanager_enable=not args.noaddons, - interfacesfile=args.interfacesfile, - interfacesfileiobuf=interfacesfileiobuf, - interfacesfileformat=args.interfacesfileformat) - - ifupdown_handle.down(['pre-down', 'down', 'post-down'], - args.all, args.CLASS, iflist, - excludepats=args.excludepats, - printdependency=args.printdependency, - usecurrentconfig=args.usecurrentconfig, - type=args.type) - except: - raise - -def run_query(args): - logger.debug('args = %s' %str(args)) - - try: - iflist = args.iflist - if args.checkcurr: - qop='query-checkcurr' - elif args.running: - qop='query-running' - elif args.raw: - qop='query-raw' - elif args.syntaxhelp: - qop = 'query-syntax' - elif args.printdependency: - qop = 'query-dependency' - elif args.printsavedstate: - qop = 'query-savedstate' - else: - qop='query' - cachearg=(False if (iflist or args.nocache or - args.perfmode or args.syntaxhelp or - (qop != 'query-checkcurr' and - qop != 'query-running')) else True) - if not iflist and qop == 'query-running': - iflist = [i for i in os.listdir('/sys/class/net/') - if os.path.isdir('/sys/class/net/%s' %i)] - logger.debug('creating ifupdown object ..') - ifupdown_handle = ifupdownMain(config=configmap_g, - withdepends=args.withdepends, - perfmode=args.perfmode, - cache=cachearg, - interfacesfile=args.interfacesfile, - interfacesfileiobuf=interfacesfileiobuf, - interfacesfileformat=args.interfacesfileformat) - - ifupdown_handle.query([qop], args.all, args.CLASS, iflist, - excludepats=args.excludepats, - printdependency=args.printdependency, - format=args.format, type=args.type) - except: - raise - -def run_reload(args): - logger.debug('args = %s' %str(args)) - - try: - logger.debug('creating ifupdown object ..') - ifupdown_handle = ifupdownMain(config=configmap_g, - withdepends=args.withdepends, - perfmode=args.perfmode) - ifupdown_handle.reload(['pre-up', 'up', 'post-up'], - ['pre-down', 'down', 'post-down'], - auto=args.all, allow=None, ifacenames=None, - excludepats=args.excludepats, - usecurrentconfig=args.usecurrentconfig, - currentlyup=args.currentlyup) - except: - raise - -def init(args): - global logger - global interfacesfileiobuf - - log_level = logging.WARNING - if args.verbose: - log_level = logging.INFO - if args.debug: - log_level = logging.DEBUG - - try: - if hasattr(args, 'syslog') and args.syslog: - root_logger = logging.getLogger() - syslog_handler = logging.handlers.SysLogHandler(address='/dev/log', - facility=logging.handlers.SysLogHandler.LOG_DAEMON) - logging.addLevelName(logging.ERROR, 'error') - logging.addLevelName(logging.WARNING, 'warning') - logging.addLevelName(logging.DEBUG, 'debug') - logging.addLevelName(logging.INFO, 'info') - root_logger.setLevel(log_level) - syslog_handler.setFormatter(logging.Formatter( - '%(name)s: %(levelname)s: %(message)s')) - root_logger.addHandler(syslog_handler) - logger = logging.getLogger('ifupdown') - else: - logging.basicConfig(level=log_level, - format='%(levelname)s: %(message)s') - logging.addLevelName(logging.ERROR, 'error') - logging.addLevelName(logging.WARNING, 'warning') - logging.addLevelName(logging.DEBUG, 'debug') - logging.addLevelName(logging.INFO, 'info') - logger = logging.getLogger('ifupdown') - except: - raise - - # If interfaces file is stdin, read - if hasattr(args, 'interfacesfile') and args.interfacesfile == '-': - interfacesfileiobuf = sys.stdin.read() - -def deinit(): - {} - -def update_argparser(argparser): - """ base parser, common to all commands """ - - argparser.add_argument('-a', '--all', action='store_true', required=False, - help='process all interfaces marked \"auto\"') - argparser.add_argument('iflist', metavar='IFACE', - nargs='*', help='interface list separated by spaces. ' + - 'IFACE list is mutually exclusive with -a option.') - argparser.add_argument('-v', '--verbose', dest='verbose', - action='store_true', help='verbose') - argparser.add_argument('-d', '--debug', dest='debug', - action='store_true', - help='output debug info') - argparser.add_argument('-q', '--quiet', dest='quiet', - action='store_true', - help=argparse.SUPPRESS) - argparser.add_argument('--allow', dest='CLASS', - help='ignore non-\"allow-CLASS\" interfaces') - argparser.add_argument('-w', '--with-depends', dest='withdepends', - action='store_true', help='run with all dependent interfaces.'+ - ' This option is redundant when \'-a\' is specified. With ' + - '\'-a\' interfaces are always executed in dependency order') - argparser.add_argument('--perfmode', dest='perfmode', - action='store_true', help=argparse.SUPPRESS) - #argparser.add_argument('-j', '--jobs', dest='jobs', type=int, - # default=-1, choices=range(1,12), help=argparse.SUPPRESS) - argparser.add_argument('--nocache', dest='nocache', action='store_true', - help=argparse.SUPPRESS) - argparser.add_argument('-X', '--exclude', dest='excludepats', - action='append', - help='Exclude interfaces from the list of interfaces' + - ' to operate on. Can be specified multiple times.') - argparser.add_argument('-i', '--interfaces', dest='interfacesfile', - default='/etc/network/interfaces', - help='use interfaces file instead of default ' + - '/etc/network/interfaces') - argparser.add_argument('-t', '--interfaces-format', - dest='interfacesfileformat', - default='native', - choices=['native', 'json'], - help='interfaces file format') - argparser.add_argument('-T', '--type', - dest='type', - default=None, - choices=['iface', 'vlan'], - help='type of interface entry (iface or vlan).' + - 'This option can be used in case of ambiguity between ' + - 'a vlan interface and an iface interface of the same name') - -def update_ifupdown_argparser(argparser): - """ common arg parser for ifup and ifdown """ - argparser.add_argument('-f', '--force', dest='force', - action='store_true', - help='force run all operations') - argparser.add_argument('-l', '--syslog', dest='syslog', - action='store_true', - help=argparse.SUPPRESS) - group = argparser.add_mutually_exclusive_group(required=False) - group.add_argument('-n', '--no-act', dest='noact', - action='store_true', help='print out what would happen,' + - 'but don\'t do it') - group.add_argument('-p', '--print-dependency', - dest='printdependency', choices=['list', 'dot'], - help='print iface dependency') - group.add_argument('--no-scripts', '--admin-state', - dest='noaddons', action='store_true', - help='dont run any addon modules/scripts. Only bring the ' + - 'interface administratively up/down') - -def update_ifup_argparser(argparser): - argparser.add_argument('-s', '--syntax-check', dest='syntaxcheck', - action='store_true', - help='Only run the interfaces file parser') - argparser.add_argument('-k', '--skip-upperifaces', dest='skipupperifaces', - action='store_true', - help='ifup by default tries to add newly created interfaces' + - ' into its upper/parent interfaces. Eg. if a bridge port is' + - ' created as a result of ifup on the port, ifup automatically' + - ' adds the port to the bridge. This option can be used to ' + - 'disable this default behaviour') - update_ifupdown_argparser(argparser) - -def update_ifdown_argparser(argparser): - update_ifupdown_argparser(argparser) - argparser.add_argument('-u', '--use-current-config', - dest='usecurrentconfig', action='store_true', - help='By default ifdown looks at the saved state for ' + - 'interfaces to bring down. This option allows ifdown to ' + - 'look at the current interfaces file. Useful when your ' + - 'state file is corrupted or you want down to use the latest ' - 'from the interfaces file') - -def update_ifquery_argparser(argparser): - """ arg parser for ifquery options """ - - # -l is same as '-a', only here for backward compatibility - argparser.add_argument('-l', '--list', action='store_true', dest='all', - help=argparse.SUPPRESS) - group = argparser.add_mutually_exclusive_group(required=False) - group.add_argument('-r', '--running', dest='running', - action='store_true', - help='query running state of an interface') - group.add_argument('-c', '--check', dest='checkcurr', - action='store_true', - help='check interface file contents against ' + - 'running state of an interface') - group.add_argument('-x', '--raw', action='store_true', dest='raw', - help='print raw config file entries') - group.add_argument('--print-savedstate', action='store_true', - dest='printsavedstate', - help=argparse.SUPPRESS) - argparser.add_argument('-o', '--format', dest='format', default='native', - choices=['native', 'json'], - help='interface display format') - argparser.add_argument('-p', '--print-dependency', - dest='printdependency', choices=['list', 'dot'], - help='print interface dependency') - argparser.add_argument('-s', '--syntax-help', action='store_true', - dest='syntaxhelp', - help='print supported interface config syntax') - -def update_ifreload_argparser(argparser): - """ parser for ifreload """ - group = argparser.add_mutually_exclusive_group(required=True) - group.add_argument('-a', '--all', action='store_true', - help='process all interfaces marked \"auto\"') - group.add_argument('-c', '--currently-up', dest='currentlyup', - action='store_true', - help='only reload auto and other interfaces that are ' + - 'currently up') - argparser.add_argument('iflist', metavar='IFACE', - nargs='*', help=argparse.SUPPRESS) - argparser.add_argument('-n', '--no-act', dest='noact', - action='store_true', help=argparse.SUPPRESS) - argparser.add_argument('-v', '--verbose', dest='verbose', - action='store_true', help='verbose') - argparser.add_argument('-d', '--debug', dest='debug', - action='store_true', - help='output debug info') - argparser.add_argument('-w', '--with-depends', dest='withdepends', - action='store_true', help=argparse.SUPPRESS) - argparser.add_argument('--perfmode', dest='perfmode', - action='store_true', help=argparse.SUPPRESS) - argparser.add_argument('--nocache', dest='nocache', action='store_true', - help=argparse.SUPPRESS) - argparser.add_argument('-X', '--exclude', dest='excludepats', - action='append', - help=argparse.SUPPRESS) - #argparser.add_argument('-j', '--jobs', dest='jobs', type=int, - # default=-1, choices=range(1,12), help=argparse.SUPPRESS) - #argparser.add_argument('-i', '--interfaces', dest='interfacesfile', - # default='/etc/network/interfaces', - # help='use interfaces file instead of default ' + - # '/etc/network/interfaces') - argparser.add_argument('-u', '--use-current-config', - dest='usecurrentconfig', action='store_true', - help='By default ifreload looks at saved state for ' + - 'interfaces to bring down. With this option ifreload will' - ' only look at the current interfaces file. Useful when your ' + - 'state file is corrupted or you want down to use the latest ' - 'from the interfaces file') - argparser.add_argument('-l', '--syslog', dest='syslog', - action='store_true', - help=argparse.SUPPRESS) - argparser.add_argument('-f', '--force', dest='force', - action='store_true', - help='force run all operations') - -def parse_args(argsv, op): - if op == 'query': - descr = 'query interfaces (all or interface list)' - elif op == 'reload': - descr = 'reload interface configuration.' - else: - descr = 'interface management' - argparser = argparse.ArgumentParser(description=descr) - if op == 'reload': - update_ifreload_argparser(argparser) - else: - update_argparser(argparser) - if op == 'up': - update_ifup_argparser(argparser) - elif op == 'down': - update_ifdown_argparser(argparser) - elif op == 'query': - update_ifquery_argparser(argparser) - elif op == 'reload': - update_ifreload_argparser(argparser) - argcomplete.autocomplete(argparser) - return argparser.parse_args(argsv) - -handlers = {'up' : run_up, - 'down' : run_down, - 'query' : run_query, - 'reload' : run_reload } - -def validate_args(op, args): - #if op == 'up' and args.syntaxcheck: - # if args.iflist or args.all: - # print 'ignoring interface options ..' - # return True - if op == 'query' and args.syntaxhelp: - return True - if op == 'reload': - if not args.all and not args.currentlyup: - print '\'-a\' or \'-c\' option is required' - return False - elif (not args.iflist and - not args.all and not args.CLASS): - print '\'-a\' option or interface list are required' - return False - if args.iflist and args.all: - print '\'-a\' option and interface list are mutually exclusive' - return False - if op != 'reload' and args.CLASS and (args.all or args.iflist): - print ('\'--allow\' option is mutually exclusive ' + - 'with interface list and \'-a\'') - return False - return True - -def read_config(args): - global configmap_g - - config = open(configfile, 'r').read() - configStr = '[ifupdown2]\n' + config - configFP = StringIO.StringIO(configStr) - parser = ConfigParser.RawConfigParser() - parser.readfp(configFP) - configmap_g = dict(parser.items('ifupdown2')) - - # Preprocess config map - configval = configmap_g.get('multiple_vlan_aware_bridge_support', '0') - if configval == '0': - # if multiple bridges not allowed, set the bridge-vlan-aware - # attribute in the 'no_repeats' config, so that the ifupdownmain - # module can catch it appropriately - configmap_g['no_repeats'] = {'bridge-vlan-aware' : 'yes'} - - - configval = configmap_g.get('link_master_slave', '0') - if configval == '1': - # link_master_slave is only valid when all is set - if hasattr(args, 'all') and not args.all: - configmap_g['link_master_slave'] = '0' - - configval = configmap_g.get('delay_admin_state_change', '0') - if configval == '1': - # reset link_master_slave if delay_admin_state_change is on - configmap_g['link_master_slave'] = '0' - -def main(argv): - """ main function """ - args = None - try: - op = None - if argv[0].endswith('ifup'): - op = 'up' - elif argv[0].endswith('ifdown'): - op = 'down' - elif argv[0].endswith('ifquery'): - op = 'query' - elif argv[0].endswith('ifreload'): - op = 'reload' - else: - print ('Unexpected executable.' + - ' Should be \'ifup\' or \'ifdown\' or \'ifquery\'') - exit(1) - # Command line arg parser - args = parse_args(argv[1:], op) - if not validate_args(op, args): - exit(1) - - if not sys.argv[0].endswith('ifquery') and not os.geteuid() == 0: - print 'error: must be root to run this command' - exit(1) - - if not sys.argv[0].endswith('ifquery') and not utils.lockFile(lockfile): - print 'Another instance of this program is already running.' - exit(0) - - read_config(args) - init(args) - handlers.get(op)(args) - except Exception, e: - if not str(e): - exit(1) - if args and args.debug: - raise - else: - if logger: - logger.error(str(e)) - else: - print str(e) - #if args and not args.debug: - # print '\nrerun the command with \'-d\' for a detailed errormsg' - exit(1) - finally: - deinit() - -if __name__ == "__main__": - - # required during boot - os.putenv('PATH', ENVPATH) - resource.setrlimit(resource.RLIMIT_CORE, (0,0)) - main(sys.argv) diff --git a/sbin/ifupdown2 b/sbin/ifupdown2 deleted file mode 100755 index a7f7e2a..0000000 --- a/sbin/ifupdown2 +++ /dev/null @@ -1,549 +0,0 @@ -#!/usr/bin/python -# -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. -# Author: Roopa Prabhu, roopa@cumulusnetworks.com -# -# ifupdown -- -# tool to configure network interfaces -# -import sys -import os -import argcomplete -import argparse -import ConfigParser -import StringIO -import logging -import logging.handlers -import resource -from ifupdown.ifupdownmain import * -from ifupdown.utils import * - -IFUPDOWN2_VERSION = '20170223-1' - -lockfile="/run/network/.lock" -configfile="/etc/network/ifupdown2/ifupdown2.conf" -configmap_g=None -logger = None -interfacesfileiobuf=None -interfacesfilename=None -ENVPATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" - -def run_up(args): - logger.debug('args = %s' %str(args)) - - try: - iflist = args.iflist - if len(args.iflist) == 0: - iflist = None - logger.debug('creating ifupdown object ..') - cachearg=(False if (iflist or args.nocache or - args.perfmode or args.noact) - else True) - ifupdown_handle = ifupdownMain(config=configmap_g, - force=args.force, - withdepends=args.withdepends, - perfmode=args.perfmode, - dryrun=args.noact, - cache=cachearg, - addons_enable=not args.noaddons, - statemanager_enable=not args.noaddons, - interfacesfile=interfacesfilename, - interfacesfileiobuf=interfacesfileiobuf, - interfacesfileformat=args.interfacesfileformat) - if args.noaddons: - ifupdown_handle.up(['up'], args.all, args.CLASS, iflist, - excludepats=args.excludepats, - printdependency=args.printdependency, - syntaxcheck=args.syntaxcheck, type=args.type, - skipupperifaces=args.skipupperifaces) - else: - ifupdown_handle.up(['pre-up', 'up', 'post-up'], - args.all, args.CLASS, iflist, - excludepats=args.excludepats, - printdependency=args.printdependency, - syntaxcheck=args.syntaxcheck, type=args.type, - skipupperifaces=args.skipupperifaces) - except: - raise - -def run_down(args): - logger.debug('args = %s' %str(args)) - - try: - iflist = args.iflist - logger.debug('creating ifupdown object ..') - ifupdown_handle = ifupdownMain(config=configmap_g, force=args.force, - withdepends=args.withdepends, - perfmode=args.perfmode, - dryrun=args.noact, - addons_enable=not args.noaddons, - statemanager_enable=not args.noaddons, - interfacesfile=interfacesfilename, - interfacesfileiobuf=interfacesfileiobuf, - interfacesfileformat=args.interfacesfileformat) - - ifupdown_handle.down(['pre-down', 'down', 'post-down'], - args.all, args.CLASS, iflist, - excludepats=args.excludepats, - printdependency=args.printdependency, - usecurrentconfig=args.usecurrentconfig, - type=args.type) - except: - raise - -def run_query(args): - logger.debug('args = %s' %str(args)) - - try: - iflist = args.iflist - if args.checkcurr: - qop='query-checkcurr' - elif args.running: - qop='query-running' - elif args.raw: - qop='query-raw' - elif args.syntaxhelp: - qop = 'query-syntax' - elif args.printdependency: - qop = 'query-dependency' - elif args.printsavedstate: - qop = 'query-savedstate' - else: - qop='query' - cachearg=(False if (iflist or args.nocache or - args.perfmode or args.syntaxhelp or - (qop != 'query-checkcurr' and - qop != 'query-running')) else True) - if not iflist and qop == 'query-running': - iflist = [i for i in os.listdir('/sys/class/net/') - if os.path.isdir('/sys/class/net/%s' %i)] - logger.debug('creating ifupdown object ..') - ifupdown_handle = ifupdownMain(config=configmap_g, - withdepends=args.withdepends, - perfmode=args.perfmode, - cache=cachearg, - interfacesfile=interfacesfilename, - interfacesfileiobuf=interfacesfileiobuf, - interfacesfileformat=args.interfacesfileformat, - withdefaults=args.withdefaults) - # list implies all auto interfaces (this is how ifupdown behaves) - if args.list: - args.all = True - ifupdown_handle.query([qop], args.all, args.list, args.CLASS, iflist, - excludepats=args.excludepats, - printdependency=args.printdependency, - format=args.format, type=args.type) - except: - raise - -def run_reload(args): - logger.debug('args = %s' %str(args)) - - try: - logger.debug('creating ifupdown object ..') - ifupdown_handle = ifupdownMain(config=configmap_g, - interfacesfile=interfacesfilename, - withdepends=args.withdepends, - perfmode=args.perfmode, - dryrun=args.noact) - ifupdown_handle.reload(['pre-up', 'up', 'post-up'], - ['pre-down', 'down', 'post-down'], - auto=args.all, allow=args.CLASS, ifacenames=None, - excludepats=args.excludepats, - usecurrentconfig=args.usecurrentconfig, - syntaxcheck=args.syntaxcheck, - currentlyup=args.currentlyup) - except: - raise - -def init(args): - global logger - global interfacesfileiobuf - global interfacesfilename - - log_level = logging.WARNING - if args.verbose: - log_level = logging.INFO - if args.debug: - log_level = logging.DEBUG - - try: - if hasattr(args, 'syslog') and args.syslog: - root_logger = logging.getLogger() - syslog_handler = logging.handlers.SysLogHandler(address='/dev/log', - facility=logging.handlers.SysLogHandler.LOG_DAEMON) - logging.addLevelName(logging.ERROR, 'error') - logging.addLevelName(logging.WARNING, 'warning') - logging.addLevelName(logging.DEBUG, 'debug') - logging.addLevelName(logging.INFO, 'info') - root_logger.setLevel(log_level) - syslog_handler.setFormatter(logging.Formatter( - '%(name)s: %(levelname)s: %(message)s')) - root_logger.addHandler(syslog_handler) - logger = logging.getLogger('ifupdown') - else: - logging.basicConfig(level=log_level, - format='%(levelname)s: %(message)s') - logging.addLevelName(logging.ERROR, 'error') - logging.addLevelName(logging.WARNING, 'warning') - logging.addLevelName(logging.DEBUG, 'debug') - logging.addLevelName(logging.INFO, 'info') - logger = logging.getLogger('ifupdown') - except: - raise - - if hasattr(args, 'interfacesfile') and args.interfacesfile != None: - # Check to see if -i option is allowed by config file - # But for ifquery, we will not check this - if (not sys.argv[0].endswith('ifquery') and - configmap_g.get('disable_cli_interfacesfile','0') == '1'): - logger.error('disable_cli_interfacesfile is set so users ' - 'not allowed to specify interfaces file on cli.') - exit(1) - if args.interfacesfile == '-': - # If interfaces file is stdin, read - interfacesfileiobuf = sys.stdin.read() - else: - interfacesfilename = args.interfacesfile - else: - # if the ifupdown2 config file does not have it, default to standard - interfacesfilename = configmap_g.get('default_interfaces_configfile', - '/etc/network/interfaces') - - - - -def deinit(): - {} - -def update_argparser(argparser): - """ base parser, common to all commands """ - - argparser.add_argument('-a', '--all', action='store_true', required=False, - help='process all interfaces marked \"auto\"') - argparser.add_argument('iflist', metavar='IFACE', - nargs='*', help='interface list separated by spaces. ' + - 'IFACE list is mutually exclusive with -a option.') - argparser.add_argument('-v', '--verbose', dest='verbose', - action='store_true', help='verbose') - argparser.add_argument('-d', '--debug', dest='debug', - action='store_true', - help='output debug info') - argparser.add_argument('-q', '--quiet', dest='quiet', - action='store_true', - help=argparse.SUPPRESS) - argparser.add_argument('--allow', dest='CLASS', - help='ignore non-\"allow-CLASS\" interfaces') - argparser.add_argument('-w', '--with-depends', dest='withdepends', - action='store_true', help='run with all dependent interfaces.'+ - ' This option is redundant when \'-a\' is specified. With ' + - '\'-a\' interfaces are always executed in dependency order') - argparser.add_argument('--perfmode', dest='perfmode', - action='store_true', help=argparse.SUPPRESS) - #argparser.add_argument('-j', '--jobs', dest='jobs', type=int, - # default=-1, choices=range(1,12), help=argparse.SUPPRESS) - argparser.add_argument('--nocache', dest='nocache', action='store_true', - help=argparse.SUPPRESS) - argparser.add_argument('-X', '--exclude', dest='excludepats', - action='append', - help='Exclude interfaces from the list of interfaces' + - ' to operate on. Can be specified multiple times.') - argparser.add_argument('-i', '--interfaces', dest='interfacesfile', - default=None, - help='Specify interfaces file instead of file defined ' + - 'in ifupdown2.conf file') - argparser.add_argument('-t', '--interfaces-format', - dest='interfacesfileformat', - default='native', - choices=['native', 'json'], - help='interfaces file format') - argparser.add_argument('-T', '--type', - dest='type', - default=None, - choices=['iface', 'vlan'], - help='type of interface entry (iface or vlan).' + - 'This option can be used in case of ambiguity between ' + - 'a vlan interface and an iface interface of the same name') - -def update_ifupdown_argparser(argparser): - """ common arg parser for ifup and ifdown """ - argparser.add_argument('-f', '--force', dest='force', - action='store_true', - help='force run all operations') - argparser.add_argument('-l', '--syslog', dest='syslog', - action='store_true', - help=argparse.SUPPRESS) - group = argparser.add_mutually_exclusive_group(required=False) - group.add_argument('-n', '--no-act', dest='noact', - action='store_true', help='print out what would happen,' + - 'but don\'t do it') - group.add_argument('-p', '--print-dependency', - dest='printdependency', choices=['list', 'dot'], - help='print iface dependency') - group.add_argument('--no-scripts', '--admin-state', - dest='noaddons', action='store_true', - help='dont run any addon modules/scripts. Only bring the ' + - 'interface administratively up/down') - -def update_ifup_argparser(argparser): - argparser.add_argument('-s', '--syntax-check', dest='syntaxcheck', - action='store_true', - help='Only run the interfaces file parser') - argparser.add_argument('-k', '--skip-upperifaces', dest='skipupperifaces', - action='store_true', - help='ifup by default tries to add newly created interfaces' + - ' into its upper/parent interfaces. Eg. if a bridge port is' + - ' created as a result of ifup on the port, ifup automatically' + - ' adds the port to the bridge. This option can be used to ' + - 'disable this default behaviour') - update_ifupdown_argparser(argparser) - -def update_ifdown_argparser(argparser): - update_ifupdown_argparser(argparser) - argparser.add_argument('-u', '--use-current-config', - dest='usecurrentconfig', action='store_true', - help='By default ifdown looks at the saved state for ' + - 'interfaces to bring down. This option allows ifdown to ' + - 'look at the current interfaces file. Useful when your ' + - 'state file is corrupted or you want down to use the latest ' - 'from the interfaces file') - -def update_ifquery_argparser(argparser): - """ arg parser for ifquery options """ - - # -l is same as '-a', only here for backward compatibility - argparser.add_argument('-l', '--list', action='store_true', dest='list', - help='list all matching known interfaces') - group = argparser.add_mutually_exclusive_group(required=False) - group.add_argument('-r', '--running', dest='running', - action='store_true', - help='query running state of an interface') - group.add_argument('-c', '--check', dest='checkcurr', - action='store_true', - help='check interface file contents against ' + - 'running state of an interface') - group.add_argument('-x', '--raw', action='store_true', dest='raw', - help='print raw config file entries') - group.add_argument('--print-savedstate', action='store_true', - dest='printsavedstate', - help=argparse.SUPPRESS) - argparser.add_argument('-o', '--format', dest='format', default='native', - choices=['native', 'json'], - help='interface display format') - argparser.add_argument('-p', '--print-dependency', - dest='printdependency', choices=['list', 'dot'], - help='print interface dependency') - argparser.add_argument('-s', '--syntax-help', action='store_true', - dest='syntaxhelp', - help='print supported interface config syntax') - argparser.add_argument('--with-defaults', action='store_true', - dest='withdefaults', - help='check policy default file contents, ' + - 'for unconfigured attributes, against ' + - 'running state of an interface') - -def update_ifreload_argparser(argparser): - """ parser for ifreload """ - group = argparser.add_mutually_exclusive_group(required=True) - group.add_argument('-a', '--all', action='store_true', - help='process all interfaces marked \"auto\"') - group.add_argument('-c', '--currently-up', dest='currentlyup', - action='store_true', - help='only reload auto and other interfaces that are ' + - 'currently up') - group.add_argument('--allow', dest='CLASS', - help='ignore non-\"allow-CLASS\" interfaces') - argparser.add_argument('iflist', metavar='IFACE', - nargs='*', help=argparse.SUPPRESS) - argparser.add_argument('-n', '--no-act', dest='noact', - action='store_true', help='print out what would happen,' + - 'but don\'t do it') - argparser.add_argument('-v', '--verbose', dest='verbose', - action='store_true', help='verbose') - argparser.add_argument('-d', '--debug', dest='debug', - action='store_true', - help='output debug info') - argparser.add_argument('-w', '--with-depends', dest='withdepends', - action='store_true', help=argparse.SUPPRESS) - argparser.add_argument('--perfmode', dest='perfmode', - action='store_true', help=argparse.SUPPRESS) - argparser.add_argument('--nocache', dest='nocache', action='store_true', - help=argparse.SUPPRESS) - argparser.add_argument('-X', '--exclude', dest='excludepats', - action='append', - help=argparse.SUPPRESS) - #argparser.add_argument('-j', '--jobs', dest='jobs', type=int, - # default=-1, choices=range(1,12), help=argparse.SUPPRESS) - #argparser.add_argument('-i', '--interfaces', dest='interfacesfile', - # default='/etc/network/interfaces', - # help='use interfaces file instead of default ' + - # '/etc/network/interfaces') - argparser.add_argument('-u', '--use-current-config', - dest='usecurrentconfig', action='store_true', - help='By default ifreload looks at saved state for ' + - 'interfaces to bring down. With this option ifreload will' - ' only look at the current interfaces file. Useful when your ' + - 'state file is corrupted or you want down to use the latest ' - 'from the interfaces file') - argparser.add_argument('-l', '--syslog', dest='syslog', - action='store_true', - help=argparse.SUPPRESS) - argparser.add_argument('-f', '--force', dest='force', - action='store_true', - help='force run all operations') - argparser.add_argument('-s', '--syntax-check', dest='syntaxcheck', - action='store_true', - help='Only run the interfaces file parser') - -def update_common_argparser(argparser): - ''' general parsing rules ''' - - argparser.add_argument('-V', '--version', - action='version', - version='ifupdown2:%(prog)s ' + IFUPDOWN2_VERSION, - help='display current ifupdown2 version') - -def parse_args(argsv, op): - if op == 'query': - descr = 'query interfaces (all or interface list)' - elif op == 'reload': - descr = 'reload interface configuration.\n\n \ - ifreload downs interfaces that were removed\n \ - from the interfaces file and subsequently runs ifup\n \ - on all interfaces. ifreload is non-disruptive. It will fix\n \ - running config to match what is configured in the interfaces\n \ - file without bringing the interface down. It may flap an interface\n\ - for some attribute changes. Please see man ifreload\n \ - for details.' - else: - descr = 'interface management' - argparser = argparse.ArgumentParser(description=descr) - if op == 'reload': - update_ifreload_argparser(argparser) - else: - update_argparser(argparser) - if op == 'up': - update_ifup_argparser(argparser) - elif op == 'down': - update_ifdown_argparser(argparser) - elif op == 'query': - update_ifquery_argparser(argparser) - update_common_argparser(argparser) - argcomplete.autocomplete(argparser) - return argparser.parse_args(argsv) - -handlers = {'up' : run_up, - 'down' : run_down, - 'query' : run_query, - 'reload' : run_reload } - -def validate_args(op, args): - #if op == 'up' and args.syntaxcheck: - # if args.iflist or args.all: - # print 'ignoring interface options ..' - # return True - if op == 'query' and (args.syntaxhelp or args.list): - return True - if op == 'reload': - if not args.all and not args.currentlyup and not args.CLASS: - print '\'-a\' or \'-c\' or \'-allow\' option is required' - return False - elif (not args.iflist and - not args.all and not args.CLASS): - print '\'-a\' option or interface list are required' - return False - if args.iflist and args.all: - print '\'-a\' option and interface list are mutually exclusive' - return False - if op != 'reload' and args.CLASS and (args.all or args.iflist): - print ('\'--allow\' option is mutually exclusive ' + - 'with interface list and \'-a\'') - return False - return True - -def read_config(args): - global configmap_g - - with open(configfile, 'r') as f: - config = f.read() - configStr = '[ifupdown2]\n' + config - configFP = StringIO.StringIO(configStr) - parser = ConfigParser.RawConfigParser() - parser.readfp(configFP) - configmap_g = dict(parser.items('ifupdown2')) - - # Preprocess config map - configval = configmap_g.get('multiple_vlan_aware_bridge_support', '0') - if configval == '0': - # if multiple bridges not allowed, set the bridge-vlan-aware - # attribute in the 'no_repeats' config, so that the ifupdownmain - # module can catch it appropriately - configmap_g['no_repeats'] = {'bridge-vlan-aware' : 'yes'} - - - configval = configmap_g.get('link_master_slave', '0') - if configval == '1': - # link_master_slave is only valid when all is set - if hasattr(args, 'all') and not args.all: - configmap_g['link_master_slave'] = '0' - - configval = configmap_g.get('delay_admin_state_change', '0') - if configval == '1': - # reset link_master_slave if delay_admin_state_change is on - configmap_g['link_master_slave'] = '0' - -def main(argv): - """ main function """ - args = None - try: - op = None - if argv[0].endswith('ifup'): - op = 'up' - elif argv[0].endswith('ifdown'): - op = 'down' - elif argv[0].endswith('ifquery'): - op = 'query' - elif argv[0].endswith('ifreload'): - op = 'reload' - else: - print ('Unexpected executable.' + - ' Should be \'ifup\' or \'ifdown\' or \'ifquery\'') - exit(1) - # Command line arg parser - args = parse_args(argv[1:], op) - if not validate_args(op, args): - exit(1) - - if not sys.argv[0].endswith('ifquery') and not os.geteuid() == 0: - print 'error: must be root to run this command' - exit(1) - - if not sys.argv[0].endswith('ifquery') and not utils.lockFile(lockfile): - print 'Another instance of this program is already running.' - exit(0) - - read_config(args) - init(args) - handlers.get(op)(args) - except Exception, e: - if not str(e): - exit(1) - if args and args.debug: - raise - else: - if logger: - logger.error(str(e)) - else: - print str(e) - #if args and not args.debug: - # print '\nrerun the command with \'-d\' for a detailed errormsg' - exit(1) - finally: - deinit() - -if __name__ == "__main__": - - # required during boot - os.putenv('PATH', ENVPATH) - resource.setrlimit(resource.RLIMIT_CORE, (0,0)) - main(sys.argv) diff --git a/setup.py b/setup.py index 5d4979d..a799c3d 100755 --- a/setup.py +++ b/setup.py @@ -1,33 +1,73 @@ -from distutils.core import setup - -setup(name='ifupdown2', - version='1.1', - description = "ifupdown 2", - author='Roopa Prabhu', - author_email='roopa@cumulusnetworks.com', - url='cumulusnetworks.com', - packages=['ifupdown', 'ifupdownaddons'], - data_files=[ ('/etc/network/ifupdown2/', - ['config/ifupdown2.conf']), - ('/usr/share/bash-completion/completions/', ['completion/ifup']), - ('/usr/share/ifupdown2/addons/', ['addons/bridge.py', - 'addons/bond.py', 'addons/vlan.py', - 'addons/mstpctl.py', 'addons/address.py', - 'addons/dhcp.py', 'addons/usercmds.py', - 'addons/ethtool.py', - 'addons/addressvirtual.py', 'addons/vxlan.py', - 'addons/link.py', 'addons/tunnel.py', - 'addons/ppp.py', 'addons/vrf.py', - 'addons/bridgevlan.py', 'addons/batman_adv.py']), - ('/usr/share/ifupdown2/nlmanager/', - ['nlmanager/nllistener.py', - 'nlmanager/nlmanager.py', - 'nlmanager/nlpacket.py', - 'nlmanager/__init__.py', - 'nlmanager/README']), - ('/etc/network/ifupdown2/', ['config/addons.conf']), - ('/etc/network/ifupdown2/', ['config/addons.conf']), - ('/var/lib/ifupdown2/policy.d/', []), - ('/etc/network/ifupdown2/policy.d/', []) - ] - ) +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sys + +from setuptools import setup +from setuptools import find_packages + +INSTALL_REQUIRES = [ + 'argcomplete', + 'ipaddr', +] + +DATA_FILES = [ + ('/etc/default/', ['etc/default/networking']), + ('/etc/network/ifupdown2/', ['etc/network/ifupdown2/addons.conf']), + ('/etc/network/ifupdown2/', ['etc/network/ifupdown2/ifupdown2.conf']), +] + +SCRIPTS = [] + +ENTRY_POINTS = {} + + +def build_deb_package(): + try: + return sys.argv[sys.argv.index('--root') + 1].endswith('/debian/ifupdown2') + except: + return False + + +if build_deb_package(): + DATA_FILES.append(('/usr/share/ifupdown2/sbin/', ['ifupdown2/sbin/start-networking'])) +else: + ENTRY_POINTS = { + 'console_scripts': [ + 'ifup = ifupdown2.__main__:main', + 'ifdown = ifupdown2.__main__:main', + 'ifquery = ifupdown2.__main__:main', + 'ifreload = ifupdown2.__main__:main', + ], + } + +setup( + author='Roopa Prabhu', + author_email='roopa@cumulusnetworks.com', + maintainer='Julien Fortin', + maintainer_email='julien@cumulusnetworks.com', + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Environment :: Console', + 'Intended Audience :: System Administrators', + 'License :: OSI Approved :: GNU General Public License v2 (GPLv2)', + 'Natural Language :: English', + 'Operating System :: POSIX :: Linux', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Topic :: System :: Networking', + 'Topic :: System :: Systems Administration' + ], + description='interface network manager', + install_requires=INSTALL_REQUIRES, + license='GNU General Public License v2', + keywords='ifupdown2', + name='ifupdown2', + packages=find_packages(), + url='https://github.com/CumulusNetworks/ifupdown2', + version='2.0.0', + data_files=DATA_FILES, + setup_requires=['setuptools'], + scripts=SCRIPTS, + entry_points=ENTRY_POINTS +) diff --git a/stdeb.cfg b/stdeb.cfg deleted file mode 100644 index db6414a..0000000 --- a/stdeb.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[DEFAULT] -Conflicts: ifupdown diff --git a/tests/ifstatetest b/tests/ifstatetest deleted file mode 100755 index a761e75..0000000 --- a/tests/ifstatetest +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/python - -""" test for testing and profiling state manager """ - -import cProfile - -from ifupdown.networkinterfaces import * -from ifupdown.iface import * -from ifupdown.statemanager import pickling -import os - -ifaceobjdict = {} -state_file = '/tmp/ifstatetest' - -def save_iface(ifaceobj): - ifaceobjdict[ifaceobj.get_name()] = ifaceobj - -def read_default_iface_config(): - """ Reads default network interface config /etc/network/interfaces. """ - nifaces = networkInterfaces() - nifaces.subscribe('iface_found', save_iface) - nifaces.load() - -def save_state(): - try: - with open(state_file, 'w') as f: - for ifaceobj in ifaceobjdict.values(): - pickling.save_obj(f, ifaceobj) - except: - raise - -def load_state(): - global ifaceobjdict - - if not os.path.exists(state_file): - return - - del ifaceobjdict - ifaceobjdict = {} - - # Read all ifaces from file - for ifaceobj in pickling.load(state_file): - save_iface(ifaceobj) - - -print 'Reading iface config files ..' -cProfile.run('read_default_iface_config()') -print 'number of objects: %d' %len(ifaceobjdict) - -print 'saving iface state ..' -cProfile.run('save_state()') - -print 'loading iface state ..' -cProfile.run('load_state()') -print 'number of objects: %d' %len(ifaceobjdict) - diff --git a/tests/test_ifupdown2.py b/tests/test_ifupdown2.py new file mode 100644 index 0000000..643dc54 --- /dev/null +++ b/tests/test_ifupdown2.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""Tests for `ifupdown2` package.""" + +import pytest + + +@pytest.fixture +def response(): + """Sample pytest fixture. + + See more at: http://doc.pytest.org/en/latest/fixture.html + """ + # import requests + # return requests.get('https://github.com/audreyr/cookiecutter-pypackage') + + +def test_content(response): + """Sample pytest test function with the pytest fixture as an argument.""" + # from bs4 import BeautifulSoup + # assert 'GitHub' in BeautifulSoup(response.content).title.string -- 2.39.2