diff --git a/configure.in b/configure.in index 43150a7..00ad3ee 100644 --- a/configure.in +++ b/configure.in @@ -27,6 +27,11 @@ AM_PROG_LIBTOOL AM_PROG_LEX AC_PROG_YACC +AM_PATH_PYTHON(2.6) +AX_PKG_SWIG +AX_PYTHON_DEVEL +AX_SWIG_PYTHON + AC_C_CONST AC_C_INLINE diff --git a/m4/ax_pkg_swig.m4 b/m4/ax_pkg_swig.m4 new file mode 100644 index 0000000..e112f3d --- /dev/null +++ b/m4/ax_pkg_swig.m4 @@ -0,0 +1,135 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_pkg_swig.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PKG_SWIG([major.minor.micro], [action-if-found], [action-if-not-found]) +# +# DESCRIPTION +# +# This macro searches for a SWIG installation on your system. If found, +# then SWIG is AC_SUBST'd; if not found, then $SWIG is empty. If SWIG is +# found, then SWIG_LIB is set to the SWIG library path, and AC_SUBST'd. +# +# You can use the optional first argument to check if the version of the +# available SWIG is greater than or equal to the value of the argument. It +# should have the format: N[.N[.N]] (N is a number between 0 and 999. Only +# the first N is mandatory.) If the version argument is given (e.g. +# 1.3.17), AX_PKG_SWIG checks that the swig package is this version number +# or higher. +# +# As usual, action-if-found is executed if SWIG is found, otherwise +# action-if-not-found is executed. +# +# In configure.in, use as: +# +# AX_PKG_SWIG(1.3.17, [], [ AC_MSG_ERROR([SWIG is required to build..]) ]) +# AX_SWIG_ENABLE_CXX +# AX_SWIG_MULTI_MODULE_SUPPORT +# AX_SWIG_PYTHON +# +# LICENSE +# +# Copyright (c) 2008 Sebastian Huber +# Copyright (c) 2008 Alan W. Irwin +# Copyright (c) 2008 Rafael Laboissiere +# Copyright (c) 2008 Andrew Collier +# Copyright (c) 2011 Murray Cumming +# +# 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; either version 2 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 8 + +AC_DEFUN([AX_PKG_SWIG],[ + # Ubuntu has swig 2.0 as /usr/bin/swig2.0 + AC_PATH_PROGS([SWIG],[swig swig2.0]) + if test -z "$SWIG" ; then + m4_ifval([$3],[$3],[:]) + elif test -n "$1" ; then + AC_MSG_CHECKING([SWIG version]) + [swig_version=`$SWIG -version 2>&1 | grep 'SWIG Version' | sed 's/.*\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*/\1/g'`] + AC_MSG_RESULT([$swig_version]) + if test -n "$swig_version" ; then + # Calculate the required version number components + [required=$1] + [required_major=`echo $required | sed 's/[^0-9].*//'`] + if test -z "$required_major" ; then + [required_major=0] + fi + [required=`echo $required | sed 's/[0-9]*[^0-9]//'`] + [required_minor=`echo $required | sed 's/[^0-9].*//'`] + if test -z "$required_minor" ; then + [required_minor=0] + fi + [required=`echo $required | sed 's/[0-9]*[^0-9]//'`] + [required_patch=`echo $required | sed 's/[^0-9].*//'`] + if test -z "$required_patch" ; then + [required_patch=0] + fi + # Calculate the available version number components + [available=$swig_version] + [available_major=`echo $available | sed 's/[^0-9].*//'`] + if test -z "$available_major" ; then + [available_major=0] + fi + [available=`echo $available | sed 's/[0-9]*[^0-9]//'`] + [available_minor=`echo $available | sed 's/[^0-9].*//'`] + if test -z "$available_minor" ; then + [available_minor=0] + fi + [available=`echo $available | sed 's/[0-9]*[^0-9]//'`] + [available_patch=`echo $available | sed 's/[^0-9].*//'`] + if test -z "$available_patch" ; then + [available_patch=0] + fi + # Convert the version tuple into a single number for easier comparison. + # Using base 100 should be safe since SWIG internally uses BCD values + # to encode its version number. + required_swig_vernum=`expr $required_major \* 10000 \ + \+ $required_minor \* 100 \+ $required_patch` + available_swig_vernum=`expr $available_major \* 10000 \ + \+ $available_minor \* 100 \+ $available_patch` + + if test $available_swig_vernum -lt $required_swig_vernum; then + AC_MSG_WARN([SWIG version >= $1 is required. You have $swig_version.]) + SWIG='' + m4_ifval([$3],[$3],[]) + else + AC_MSG_CHECKING([for SWIG library]) + SWIG_LIB=`$SWIG -swiglib` + AC_MSG_RESULT([$SWIG_LIB]) + m4_ifval([$2],[$2],[]) + fi + else + AC_MSG_WARN([cannot determine SWIG version]) + SWIG='' + m4_ifval([$3],[$3],[]) + fi + fi + AC_SUBST([SWIG_LIB]) +]) diff --git a/m4/ax_python_devel.m4 b/m4/ax_python_devel.m4 new file mode 100644 index 0000000..a62b860 --- /dev/null +++ b/m4/ax_python_devel.m4 @@ -0,0 +1,325 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_python_devel.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PYTHON_DEVEL([version]) +# +# DESCRIPTION +# +# Note: Defines as a precious variable "PYTHON_VERSION". Don't override it +# in your configure.ac. +# +# This macro checks for Python and tries to get the include path to +# 'Python.h'. It provides the $(PYTHON_CPPFLAGS) and $(PYTHON_LDFLAGS) +# output variables. It also exports $(PYTHON_EXTRA_LIBS) and +# $(PYTHON_EXTRA_LDFLAGS) for embedding Python in your code. +# +# You can search for some particular version of Python by passing a +# parameter to this macro, for example ">= '2.3.1'", or "== '2.4'". Please +# note that you *have* to pass also an operator along with the version to +# match, and pay special attention to the single quotes surrounding the +# version number. Don't use "PYTHON_VERSION" for this: that environment +# variable is declared as precious and thus reserved for the end-user. +# +# This macro should work for all versions of Python >= 2.1.0. As an end +# user, you can disable the check for the python version by setting the +# PYTHON_NOVERSIONCHECK environment variable to something else than the +# empty string. +# +# If you need to use this macro for an older Python version, please +# contact the authors. We're always open for feedback. +# +# LICENSE +# +# Copyright (c) 2009 Sebastian Huber +# Copyright (c) 2009 Alan W. Irwin +# Copyright (c) 2009 Rafael Laboissiere +# Copyright (c) 2009 Andrew Collier +# Copyright (c) 2009 Matteo Settenvini +# Copyright (c) 2009 Horst Knorr +# +# 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, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 8 + +AU_ALIAS([AC_PYTHON_DEVEL], [AX_PYTHON_DEVEL]) +AC_DEFUN([AX_PYTHON_DEVEL],[ + # + # Allow the use of a (user set) custom python version + # + AC_ARG_VAR([PYTHON_VERSION],[The installed Python + version to use, for example '2.3'. This string + will be appended to the Python interpreter + canonical name.]) + + AC_PATH_PROG([PYTHON],[python[$PYTHON_VERSION]]) + if test -z "$PYTHON"; then + AC_MSG_ERROR([Cannot find python$PYTHON_VERSION in your system path]) + PYTHON_VERSION="" + fi + + # + # Check for a version of Python >= 2.1.0 + # + AC_MSG_CHECKING([for a version of Python >= '2.1.0']) + ac_supports_python_ver=`$PYTHON -c "import sys; \ + ver = sys.version.split ()[[0]]; \ + print (ver >= '2.1.0')"` + if test "$ac_supports_python_ver" != "True"; then + if test -z "$PYTHON_NOVERSIONCHECK"; then + AC_MSG_RESULT([no]) + AC_MSG_FAILURE([ +This version of the AC@&t@_PYTHON_DEVEL macro +doesn't work properly with versions of Python before +2.1.0. You may need to re-run configure, setting the +variables PYTHON_CPPFLAGS, PYTHON_LDFLAGS, PYTHON_SITE_PKG, +PYTHON_EXTRA_LIBS and PYTHON_EXTRA_LDFLAGS by hand. +Moreover, to disable this check, set PYTHON_NOVERSIONCHECK +to something else than an empty string. +]) + else + AC_MSG_RESULT([skip at user request]) + fi + else + AC_MSG_RESULT([yes]) + fi + + # + # if the macro parameter ``version'' is set, honour it + # + if test -n "$1"; then + AC_MSG_CHECKING([for a version of Python $1]) + ac_supports_python_ver=`$PYTHON -c "import sys; \ + ver = sys.version.split ()[[0]]; \ + print (ver $1)"` + if test "$ac_supports_python_ver" = "True"; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + AC_MSG_ERROR([this package requires Python $1. +If you have it installed, but it isn't the default Python +interpreter in your system path, please pass the PYTHON_VERSION +variable to configure. See ``configure --help'' for reference. +]) + PYTHON_VERSION="" + fi + fi + + # + # Check if you have distutils, else fail + # + AC_MSG_CHECKING([for the distutils Python package]) + ac_distutils_result=`$PYTHON -c "import distutils" 2>&1` + if test -z "$ac_distutils_result"; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + AC_MSG_ERROR([cannot import Python module "distutils". +Please check your Python installation. The error was: +$ac_distutils_result]) + PYTHON_VERSION="" + fi + + # + # Check for Python include path + # + AC_MSG_CHECKING([for Python include path]) + if test -z "$PYTHON_CPPFLAGS"; then + python_path=`$PYTHON -c "import distutils.sysconfig; \ + print (distutils.sysconfig.get_python_inc ());"` + if test -n "${python_path}"; then + python_path="-I$python_path" + fi + PYTHON_CPPFLAGS=$python_path + fi + AC_MSG_RESULT([$PYTHON_CPPFLAGS]) + AC_SUBST([PYTHON_CPPFLAGS]) + + # + # Check for Python library path + # + AC_MSG_CHECKING([for Python library path]) + if test -z "$PYTHON_LDFLAGS"; then + # (makes two attempts to ensure we've got a version number + # from the interpreter) + ac_python_version=`cat<]], + [[Py_Initialize();]]) + ],[pythonexists=yes],[pythonexists=no]) + AC_LANG_POP([C]) + # turn back to default flags + CPPFLAGS="$ac_save_CPPFLAGS" + LIBS="$ac_save_LIBS" + + AC_MSG_RESULT([$pythonexists]) + + if test ! "x$pythonexists" = "xyes"; then + AC_MSG_FAILURE([ + Could not link test program to Python. Maybe the main Python library has been + installed in some non-standard library path. If so, pass it to configure, + via the LDFLAGS environment variable. + Example: ./configure LDFLAGS="-L/usr/non-standard-path/python/lib" + ============================================================================ + ERROR! + You probably have to install the development version of the Python package + for your distribution. The exact name of this package varies among them. + ============================================================================ + ]) + PYTHON_VERSION="" + fi + + # + # all done! + # +]) diff --git a/m4/ax_swig_python.m4 b/m4/ax_swig_python.m4 new file mode 100644 index 0000000..8fd3df5 --- /dev/null +++ b/m4/ax_swig_python.m4 @@ -0,0 +1,64 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_swig_python.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_SWIG_PYTHON([use-shadow-classes = {no, yes}]) +# +# DESCRIPTION +# +# Checks for Python and provides the $(AX_SWIG_PYTHON_CPPFLAGS), and +# $(AX_SWIG_PYTHON_OPT) output variables. +# +# $(AX_SWIG_PYTHON_OPT) contains all necessary SWIG options to generate +# code for Python. Shadow classes are enabled unless the value of the +# optional first argument is exactly 'no'. If you need multi module +# support (provided by the AX_SWIG_MULTI_MODULE_SUPPORT macro) use +# $(AX_SWIG_PYTHON_LIBS) to link against the appropriate library. It +# contains the SWIG Python runtime library that is needed by the type +# check system for example. +# +# LICENSE +# +# Copyright (c) 2008 Sebastian Huber +# Copyright (c) 2008 Alan W. Irwin +# Copyright (c) 2008 Rafael Laboissiere +# Copyright (c) 2008 Andrew Collier +# +# 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; either version 2 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 7 + +AU_ALIAS([SWIG_PYTHON], [AX_SWIG_PYTHON]) +AC_DEFUN([AX_SWIG_PYTHON],[ + AC_REQUIRE([AX_PKG_SWIG]) + AC_REQUIRE([AX_PYTHON_DEVEL]) + test "x$1" != "xno" || swig_shadow=" -noproxy" + AC_SUBST([AX_SWIG_PYTHON_OPT],[-python$swig_shadow]) + AC_SUBST([AX_SWIG_PYTHON_CPPFLAGS],[$PYTHON_CPPFLAGS]) +]) diff --git a/python/doc/conf.py b/python/doc/conf.py new file mode 100644 index 0000000..74041f0 --- /dev/null +++ b/python/doc/conf.py @@ -0,0 +1,216 @@ +# -*- coding: utf-8 -*- +# +# libnl-python documentation build configuration file, created by +# sphinx-quickstart on Mon May 9 10:58:58 2011. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.todo', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'libnl-python' +copyright = u'2011, Thomas Graf ' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '1.0' +# The full version, including alpha/beta/rc tags. +release = '1.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'libnl-pythondoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'libnl-python.tex', u'libnl-python Documentation', + u'Thomas Graf \\textless{}tgraf@suug.ch\\textgreater{}', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'libnl-python', u'libnl-python Documentation', + [u'Thomas Graf '], 1) +] diff --git a/python/doc/core.rst b/python/doc/core.rst new file mode 100644 index 0000000..012e14c --- /dev/null +++ b/python/doc/core.rst @@ -0,0 +1,215 @@ +******************* +Netlink Core Module +******************* + +.. py:module:: netlink.core + +Examples:: + + import netlink.core as netlink + +=============== +Object +=============== + +.. py:class:: Object + + Base class for all classes representing a cacheable object + + Example:: + obj = netlink.Object("route/link", "link") + + .. py:method:: clone + + Clone the object and return a duplicate (used for COW) + + .. py:method:: dump([params=None]) + + Call the libnl internal dump mechanism to dump the object + according to the parameters specified. + + .. py:method:: apply(attr, val) + + Applies a attribute=value pair and modifies the object accordingly. + Example:: + obj.apply("mtu", 1200) # Sets attribute mtu to 1200 (link obj) + + :raises: KeyError if attribute is unknown + :raises: ImmutableError if attribute is not mutable + + .. py:attribute:: mark + + True if the object is marked, otherwise False. + + .. py:attribute:: shared + + True if the object is used by multiple parties, otherwise False. + + .. py:attribute:: refcnt + + Number of users sharing a reference to the object + :rtype: int + + .. py:attribute:: attrs + + List of attributes + + :rtype: list of strings + +=============== +Cache +=============== + +.. py:class:: Cache + + Base class for all cache implementations. + + A cache is a collection of cacheable objects which is typically used + by netlink protocols which handle any kind of object, e.g. network + links, network addresses, neighbours, ... + + .. py:method:: subset(filter) + + Returns a new cache containing the subset which matches the + provided filter. + + :raises: ValueError if no filter is specified + :rtype: :py:class:`Cache` + + .. py:method:: dump([params=None, filter=None]) + + Calls the libnl internal dump mechanism to dump the cache according + to the parameters and filter specified. + + .. py:method:: clear() + + Remove and possibly destroy all objects in the cache + + .. py:method:: refill([socket=None]) -> :py:class:`Cache` + + Clears and refills the cache with the content which is provided by + the kernel, e.g. for a link cache this would mean refilling the + cache with all configured network links. + + .. py:method:: provide() + + Caches which have been "provided" are made available to other users + (of the same application context) which "require" it. F.e. a link + cache is generally provided to allow others to translate interface + indexes to link names + + + .. py:method:: unprovide() + + No longer make the cache available to others. If the cache has been + handed out already, that reference will still be valid. + +=============== +AbstractAddress +=============== + +.. py:class:: AbstractAddress + + Abstract representation of an address. This class is not to be mistaken + with :py:class:`route.Address` which represents a configured network + address. This class represents the actual address in a family independent + way:: + + addr = netlink.AbstractAddress('127.0.0.1/8') + print addr # => '127.0.0.1/8' + print addr.prefixlen # => '8' + print addr.family # => 'inet' + print len(addr) # => '4' (32bit ipv4 address) + + a = netlink.AbstractAddress('10.0.0.1/24') + b = netlink.AbstractAddress('10.0.0.2/24') + print a == b # => False + + .. py:attribute:: prefixlen + + Length of prefix in number of bits. + + :rtype: int + + .. py:attribute:: family + + The family type of the address. Setting the address family can be + done with a string or a :py:class:`AddressFamily` object. + + :rtype: :py:class:`AddressFamily` + + .. py:attribute:: shared + + True if address is in use by multiple callers, otherwise False + + :rtype: bool + +=============== +AddressFamily +=============== + +.. py:class:: AddressFamily + + Address family representation:: + + af = netlink.AddressFamily('inet6') + # raises: + # - ValueError if family name is not known + # - TypeError if invalid type is specified for family + + print af # => 'inet6' (string representation) + print int(af) # => 10 (numeric representation) + print repr(af) # => AddressFamily('inet6') + +=============== +Exceptions +=============== + +.. py:exception:: NetlinkError + + Generic exception raised by netlink modules. + +.. py:exception:: KernelError + + Raised if an error occured while communicating with the kernel. Contains + the error code returning which is automatically included in the error + message. + +.. py:exception:: ImmutableError + + Raised if an attribute is modified which is marked immutable. + +=============== +Socket +=============== + +.. py:class:: Socket + + Netlink socket. + + Note: It is not required to manually create and connect netlink sockets + when using caches. The caches will automatically lookup or create a + socket as needed. + + .. py:attribute:: local_port + + Local port (address) of netlink socket + + .. py:attribute:: peer_port + + Peer port (remote address) of netlink socket. If set, all messages + will be sent to that peer. + + .. py:method:: connect(proto) + + Connect the netlink socket using the specified netlink protocol:: + sock.connect(netlink.NETLINK_ROUTE) + + .. py:method:: disconnect() + + Disconnect the socket + + .. py:method:: set_bufsize(rx, tx) + + Sets the size of the socket buffer + diff --git a/python/doc/index.rst b/python/doc/index.rst new file mode 100644 index 0000000..8de8ae3 --- /dev/null +++ b/python/doc/index.rst @@ -0,0 +1,24 @@ +.. libnl-python documentation master file, created by + sphinx-quickstart on Mon May 9 10:58:58 2011. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to libnl-python's documentation! +======================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + core + route + route_addr + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/python/doc/route.rst b/python/doc/route.rst new file mode 100644 index 0000000..0b8f3f9 --- /dev/null +++ b/python/doc/route.rst @@ -0,0 +1,3 @@ +********************** +Routing +********************** diff --git a/python/doc/route_addr.rst b/python/doc/route_addr.rst new file mode 100644 index 0000000..2cfe139 --- /dev/null +++ b/python/doc/route_addr.rst @@ -0,0 +1,47 @@ +================= +Network Addresses +================= + +The **Address** module provides access to the network address configuration +of the kernel. It provides an interface to fetch all configured addresses, +add new addresses and to delete existing addresses. + +Fetching the list of network addresses is achieved by creating a new +address cache:: + + import netlink.route.address as Address + + addr_cache = Address.AddressCache() + addr_cache.refill() + + for addr in addr_cache: + print addr + +.. py:module:: netlink.route.addr + + +AddressCache +------------ + +.. py:class:: AddressCache + + Represents a cache containing all or a subset of network addresses. + + .. py:method:: lookup(ifindex, local) + + Lookup the address which matches ifindex and local address + + :raises: KeyError if address is not found. + +Address +------- + +.. py:class:: Address + + Representation of a configured network address. + + .. py:attribute:: ifindex + + Interface index + + :rtype: int diff --git a/python/netlink/__init__.py b/python/netlink/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python/netlink/capi.i b/python/netlink/capi.i new file mode 100644 index 0000000..d80d87b --- /dev/null +++ b/python/netlink/capi.i @@ -0,0 +1,457 @@ +%module capi +%{ +#include +#include +#include +#include +#include +#include +%} + +%include +%include + +%inline %{ + struct nl_dump_params *alloc_dump_params(void) + { + struct nl_dump_params *dp; + if (!(dp = calloc(1, sizeof(*dp)))) + return NULL; + dp->dp_fd = stdout; + return dp; + } + + void free_dump_params(struct nl_dump_params *dp) + { + free(dp); + } +%}; + +/* */ + +enum nl_dump_type { + NL_DUMP_LINE, /**< Dump object briefly on one line */ + NL_DUMP_DETAILS, /**< Dump all attributes but no statistics */ + NL_DUMP_STATS, /**< Dump all attributes including statistics */ + __NL_DUMP_MAX, +}; + +struct nl_dump_params +{ + /** + * Specifies the type of dump that is requested. + */ + enum nl_dump_type dp_type; + + /** + * Specifies the number of whitespaces to be put in front + * of every new line (indentation). + */ + int dp_prefix; + + /** + * Causes the cache index to be printed for each element. + */ + int dp_print_index; + + /** + * Causes each element to be prefixed with the message type. + */ + int dp_dump_msgtype; + + /** + * A callback invoked for output + * + * Passed arguments are: + * - dumping parameters + * - string to append to the output + */ + void (*dp_cb)(struct nl_dump_params *, char *); + + /** + * A callback invoked for every new line, can be used to + * customize the indentation. + * + * Passed arguments are: + * - dumping parameters + * - line number starting from 0 + */ + void (*dp_nl_cb)(struct nl_dump_params *, int); + + /** + * User data pointer, can be used to pass data to callbacks. + */ + void *dp_data; + + /** + * File descriptor the dumping output should go to + */ + FILE * dp_fd; + + /** + * Alternatively the output may be redirected into a buffer + */ + char * dp_buf; + + /** + * Length of the buffer dp_buf + */ + size_t dp_buflen; + + /** + * PRIVATE + * Set if a dump was performed prior to the actual dump handler. + */ + int dp_pre_dump; + + /** + * PRIVATE + * Owned by the current caller + */ + int dp_ivar; + + unsigned int dp_line; +}; + +/* */ +extern const char *nl_geterror(int); + +/* */ + +extern double nl_cancel_down_bytes(unsigned long long, char **); +extern double nl_cancel_down_bits(unsigned long long, char **); +extern double nl_cancel_down_us(uint32_t, char **); + +extern long nl_size2int(const char *); +%cstring_output_maxsize(char *buf, const size_t len) +extern char *nl_size2str(const size_t, char *buf, const size_t len); +extern long nl_prob2int(const char *); + +extern int nl_get_user_hz(void); +extern uint32_t nl_us2ticks(uint32_t); +extern uint32_t nl_ticks2us(uint32_t); +extern int nl_str2msec(const char *, uint64_t *); + +%cstring_output_maxsize(char *buf, size_t len) +extern char *nl_msec2str(uint64_t, char *buf, size_t len); + +%cstring_output_maxsize(char *buf, size_t len) +extern char *nl_llproto2str(int, char *buf, size_t len); +extern int nl_str2llproto(const char *); + +%cstring_output_maxsize(char *buf, size_t len) +extern char *nl_ether_proto2str(int, char *buf, size_t len); +extern int nl_str2ether_proto(const char *); + +%cstring_output_maxsize(char *buf, size_t len) +extern char *nl_ip_proto2str(int, char *buf, size_t len); +extern int nl_str2ip_proto(const char *); + +extern void nl_new_line(struct nl_dump_params *); +extern void nl_dump(struct nl_dump_params *, const char *, ...); +extern void nl_dump_line(struct nl_dump_params *, const char *, ...); + +/* */ +extern struct nl_dump_params *alloc_dump_params(void); +extern void free_dump_params(struct nl_dump_params *); + +extern int nl_connect(struct nl_sock *, int); +extern void nl_close(struct nl_sock *); +extern int nl_pickup(struct nl_sock *, int (*parser)(struct nl_cache_ops *, + struct sockaddr_nl *, + struct nlmsghdr *, + struct nl_parser_param *), + struct nl_object **); + +/* */ +extern struct nl_sock *nl_socket_alloc(void); +extern struct nl_sock *nl_socket_alloc_cb(struct nl_cb *); +extern void nl_socket_free(struct nl_sock *); + +extern uint32_t nl_socket_get_local_port(const struct nl_sock *); +extern void nl_socket_set_local_port(struct nl_sock *, uint32_t); + +extern uint32_t nl_socket_get_peer_port(const struct nl_sock *); +extern void nl_socket_set_peer_port(struct nl_sock *, uint32_t); + +extern uint32_t nl_socket_get_peer_groups(const struct nl_sock *sk); +extern void nl_socket_set_peer_groups(struct nl_sock *sk, uint32_t groups); + +extern int nl_socket_set_buffer_size(struct nl_sock *, int, int); + +/* */ +extern int nlmsg_size(int); +extern int nlmsg_total_size(int); +extern int nlmsg_padlen(int); + +extern void * nlmsg_data(const struct nlmsghdr *); +extern int nlmsg_datalen(const struct nlmsghdr *); +extern void * nlmsg_tail(const struct nlmsghdr *); + +/* attribute access */ +extern struct nlattr * nlmsg_attrdata(const struct nlmsghdr *, int); +extern int nlmsg_attrlen(const struct nlmsghdr *, int); + +/* message parsing */ +extern int nlmsg_valid_hdr(const struct nlmsghdr *, int); +extern int nlmsg_ok(const struct nlmsghdr *, int); +extern struct nlmsghdr * nlmsg_next(struct nlmsghdr *, int *); +extern int nlmsg_parse(struct nlmsghdr *, int, struct nlattr **, + int, struct nla_policy *); +extern struct nlattr * nlmsg_find_attr(struct nlmsghdr *, int, int); +extern int nlmsg_validate(struct nlmsghdr *, int, int, + struct nla_policy *); + +extern struct nl_msg * nlmsg_alloc(void); +extern struct nl_msg * nlmsg_alloc_size(size_t); +extern struct nl_msg * nlmsg_alloc_simple(int, int); +extern void nlmsg_set_default_size(size_t); +extern struct nl_msg * nlmsg_inherit(struct nlmsghdr *); +extern struct nl_msg * nlmsg_convert(struct nlmsghdr *); +extern void * nlmsg_reserve(struct nl_msg *, size_t, int); +extern int nlmsg_append(struct nl_msg *, void *, size_t, int); +extern int nlmsg_expand(struct nl_msg *, size_t); + +extern struct nlmsghdr * nlmsg_put(struct nl_msg *, uint32_t, uint32_t, + int, int, int); +extern struct nlmsghdr * nlmsg_hdr(struct nl_msg *); +extern void nlmsg_get(struct nl_msg *); +extern void nlmsg_free(struct nl_msg *); + +/* attribute modification */ +extern void nlmsg_set_proto(struct nl_msg *, int); +extern int nlmsg_get_proto(struct nl_msg *); +extern size_t nlmsg_get_max_size(struct nl_msg *); +extern void nlmsg_set_src(struct nl_msg *, struct sockaddr_nl *); +extern struct sockaddr_nl *nlmsg_get_src(struct nl_msg *); +extern void nlmsg_set_dst(struct nl_msg *, struct sockaddr_nl *); +extern struct sockaddr_nl *nlmsg_get_dst(struct nl_msg *); +extern void nlmsg_set_creds(struct nl_msg *, struct ucred *); +extern struct ucred * nlmsg_get_creds(struct nl_msg *); + +extern char * nl_nlmsgtype2str(int, char *, size_t); +extern int nl_str2nlmsgtype(const char *); + +extern char * nl_nlmsg_flags2str(int, char *, size_t); + +extern int nl_msg_parse(struct nl_msg *, + void (*cb)(struct nl_object *, void *), + void *); + +extern void nl_msg_dump(struct nl_msg *, FILE *); + +%inline %{ + struct nl_object *cast_obj(void *obj) + { + return (struct nl_object *) obj; + } + + struct nl_object *object_alloc_name(const char *name) + { + struct nl_object *obj; + + if (nl_object_alloc_name(name, &obj) < 0) + return NULL; + + return obj; + } +%}; + +extern struct nl_object *nl_object_alloc(struct nl_object_ops *); +extern void nl_object_free(struct nl_object *); +extern struct nl_object *nl_object_clone(struct nl_object *); +extern void nl_object_get(struct nl_object *); +extern void nl_object_put(struct nl_object *); +extern int nl_object_shared(struct nl_object *); + +%cstring_output_maxsize(char *buf, size_t len) +extern void nl_object_dump_buf(struct nl_object *, char *buf, size_t len); + +extern void nl_object_dump(struct nl_object *, struct nl_dump_params *); + +extern int nl_object_identical(struct nl_object *, struct nl_object *); +extern uint32_t nl_object_diff(struct nl_object *, struct nl_object *); +extern int nl_object_match_filter(struct nl_object *, struct nl_object *); + +%cstring_output_maxsize(char *buf, size_t len) +extern char *nl_object_attrs2str(struct nl_object *, uint32_t, char *buf, size_t len); + +%cstring_output_maxsize(char *buf, size_t len) +extern char *nl_object_attr_list(struct nl_object *, char *buf, size_t len); + +extern void nl_object_mark(struct nl_object *); +extern void nl_object_unmark(struct nl_object *); +extern int nl_object_is_marked(struct nl_object *); + +extern int nl_object_get_refcnt(struct nl_object *); + +/* */ + +typedef void (*change_func_t)(struct nl_cache *, struct nl_object *, int, void *); + +%inline %{ + struct nl_cache *alloc_cache_name(const char *name) + { + struct nl_cache *c; + if (nl_cache_alloc_name(name, &c) < 0) + return NULL; + return c; + } + + struct nl_cache_mngr *alloc_cache_mngr(struct nl_sock *sock, + int protocol, int flags) + { + struct nl_cache_mngr *mngr; + + if (nl_cache_mngr_alloc(sock, protocol, flags, &mngr) < 0) + return NULL; + + return mngr; + } + + struct nl_cache *cache_mngr_add(struct nl_cache_mngr *mngr, + const char *name, change_func_t func, + void *arg) + { + struct nl_cache *cache; + + if (nl_cache_mngr_add(mngr, name, func, arg, &cache) < 0) + return NULL; + + return cache; + } +%} + +/* Access Functions */ +extern int nl_cache_nitems(struct nl_cache *); +extern int nl_cache_nitems_filter(struct nl_cache *, + struct nl_object *); +extern struct nl_cache_ops * nl_cache_get_ops(struct nl_cache *); +extern struct nl_object * nl_cache_get_first(struct nl_cache *); +extern struct nl_object * nl_cache_get_last(struct nl_cache *); +extern struct nl_object * nl_cache_get_next(struct nl_object *); +extern struct nl_object * nl_cache_get_prev(struct nl_object *); + +extern struct nl_cache * nl_cache_alloc(struct nl_cache_ops *); +extern struct nl_cache * nl_cache_subset(struct nl_cache *, + struct nl_object *); +extern void nl_cache_clear(struct nl_cache *); +extern void nl_cache_free(struct nl_cache *); + +/* Cache modification */ +extern int nl_cache_add(struct nl_cache *, + struct nl_object *); +extern int nl_cache_parse_and_add(struct nl_cache *, + struct nl_msg *); +extern void nl_cache_remove(struct nl_object *); +extern int nl_cache_refill(struct nl_sock *, + struct nl_cache *); +extern int nl_cache_pickup(struct nl_sock *, + struct nl_cache *); +extern int nl_cache_resync(struct nl_sock *, + struct nl_cache *, + change_func_t, + void *); +extern int nl_cache_include(struct nl_cache *, + struct nl_object *, + change_func_t, + void *); +extern void nl_cache_set_arg1(struct nl_cache *, int); +extern void nl_cache_set_arg2(struct nl_cache *, int); + +/* General */ +extern int nl_cache_is_empty(struct nl_cache *); +extern struct nl_object * nl_cache_search(struct nl_cache *, + struct nl_object *); +extern void nl_cache_mark_all(struct nl_cache *); + +/* Dumping */ +extern void nl_cache_dump(struct nl_cache *, + struct nl_dump_params *); +extern void nl_cache_dump_filter(struct nl_cache *, + struct nl_dump_params *, + struct nl_object *); + +/* Iterators */ +extern void nl_cache_foreach(struct nl_cache *, + void (*cb)(struct nl_object *, + void *), + void *arg); +extern void nl_cache_foreach_filter(struct nl_cache *, + struct nl_object *, + void (*cb)(struct + nl_object *, + void *), + void *arg); + +/* --- cache management --- */ + +/* Cache type management */ +extern struct nl_cache_ops * nl_cache_ops_lookup(const char *); +extern struct nl_cache_ops * nl_cache_ops_associate(int, int); +extern struct nl_msgtype * nl_msgtype_lookup(struct nl_cache_ops *, int); +extern void nl_cache_ops_foreach(void (*cb)(struct nl_cache_ops *, void *), void *); +extern int nl_cache_mngt_register(struct nl_cache_ops *); +extern int nl_cache_mngt_unregister(struct nl_cache_ops *); + +/* Global cache provisioning/requiring */ +extern void nl_cache_mngt_provide(struct nl_cache *); +extern void nl_cache_mngt_unprovide(struct nl_cache *); +extern struct nl_cache * nl_cache_mngt_require(const char *); + +struct nl_cache_mngr; + +#define NL_AUTO_PROVIDE 1 + +extern int nl_cache_mngr_get_fd(struct nl_cache_mngr *); +extern int nl_cache_mngr_poll(struct nl_cache_mngr *, + int); +extern int nl_cache_mngr_data_ready(struct nl_cache_mngr *); +extern void nl_cache_mngr_free(struct nl_cache_mngr *); + +/* */ +%inline %{ + struct nl_addr *addr_parse(const char *addr, int guess) + { + struct nl_addr *result; + + if (nl_addr_parse(addr, guess, &result) < 0) + return NULL; + + return result; + } +%}; + +extern struct nl_addr *nl_addr_alloc(size_t); +extern struct nl_addr *nl_addr_alloc_attr(struct nlattr *, int); +extern struct nl_addr *nl_addr_build(int, void *, size_t); +extern struct nl_addr *nl_addr_clone(struct nl_addr *); + +extern struct nl_addr *nl_addr_get(struct nl_addr *); +extern void nl_addr_put(struct nl_addr *); +extern int nl_addr_shared(struct nl_addr *); + +extern int nl_addr_cmp(struct nl_addr *, struct nl_addr *); +extern int nl_addr_cmp_prefix(struct nl_addr *, struct nl_addr *); +extern int nl_addr_iszero(struct nl_addr *); +extern int nl_addr_valid(char *, int); +extern int nl_addr_guess_family(struct nl_addr *); +extern int nl_addr_fill_sockaddr(struct nl_addr *, struct sockaddr *, socklen_t *); +extern int nl_addr_info(struct nl_addr *, struct addrinfo **); +extern int nl_addr_resolve(struct nl_addr *addr, char *host, size_t hostlen); + +extern void nl_addr_set_family(struct nl_addr *, int); +extern int nl_addr_get_family(struct nl_addr *); +extern int nl_addr_set_binary_addr(struct nl_addr *, void *, size_t); + +extern void *nl_addr_get_binary_addr(struct nl_addr *); +extern unsigned int nl_addr_get_len(struct nl_addr *); +extern void nl_addr_set_prefixlen(struct nl_addr *, int); +extern unsigned int nl_addr_get_prefixlen(struct nl_addr *); + +%cstring_output_maxsize(char *buf, size_t len) +extern char *nl_af2str(int, char *buf, size_t len); +extern int nl_str2af(const char *); + +%cstring_output_maxsize(char *buf, size_t len) +extern char *nl_addr2str(struct nl_addr *, char *buf, size_t len); diff --git a/python/netlink/core.py b/python/netlink/core.py new file mode 100644 index 0000000..f97528d --- /dev/null +++ b/python/netlink/core.py @@ -0,0 +1,737 @@ +# +# Netlink interface based on libnl +# +# Copyright (c) 2011 Thomas Graf +# + +"""netlink library based on libnl + +This module provides an interface to netlink sockets + +The module contains the following public classes: + - Socket -- The netlink socket + - Object -- Abstract object (based on struct nl_obect in libnl) used as + base class for all object types which can be put into a Cache + - Cache -- A collection of objects which are derived from the base + class Object. Used for netlink protocols which maintain a list + or tree of objects. + - DumpParams -- + +The following exceptions are defined: + - NetlinkError -- Base exception for all general purpose exceptions raised. + - KernelError -- Raised when the kernel returns an error as response to a + request. + +All other classes or functions in this module are considered implementation +details. +""" + +import capi +import sys +import socket +import struct + +__all__ = ['Message', 'Socket', 'DumpParams', 'Object', 'Cache', 'KernelError', + 'NetlinkError'] +__version__ = "0.1" + +# netlink protocols +NETLINK_ROUTE = 0 +# NETLINK_UNUSED = 1 +NETLINK_USERSOCK = 2 +NETLINK_FIREWALL = 3 +NETLINK_INET_DIAG = 4 +NETLINK_NFLOG = 5 +NETLINK_XFRM = 6 +NETLINK_SELINUX = 7 +NETLINK_ISCSI = 8 +NETLINK_AUDIT = 9 +NETLINK_FIB_LOOKUP = 10 +NETLINK_CONNECTOR = 11 +NETLINK_NETFILTER = 12 +NETLINK_IP6_FW = 13 +NETLINK_DNRTMSG = 14 +NETLINK_KOBJECT_UEVENT = 15 +NETLINK_GENERIC = 16 +NETLINK_SCSITRANSPORT = 18 +NETLINK_ECRYPTFS = 19 + +NL_DONTPAD = 0 +NL_AUTO_PORT = 0 +NL_AUTO_SEQ = 0 + +NL_DUMP_LINE = 0 +NL_DUMP_DETAILS = 1 +NL_DUMP_STATS = 2 + +NLM_F_REQUEST = 1 +NLM_F_MULTI = 2 +NLM_F_ACK = 4 +NLM_F_ECHO = 8 + +NLM_F_ROOT = 0x100 +NLM_F_MATCH = 0x200 +NLM_F_ATOMIC = 0x400 +NLM_F_DUMP = NLM_F_ROOT | NLM_F_MATCH + +NLM_F_REPLACE = 0x100 +NLM_F_EXCL = 0x200 +NLM_F_CREATE = 0x400 +NLM_F_APPEND = 0x800 + +class NetlinkError(Exception): + def __init__(self, error): + self._error = error + self._msg = capi.nl_geterror(error) + + def __str__(self): + return self._msg + +class KernelError(NetlinkError): + def __str__(self): + return "Kernel returned: " + self._msg + +class ImmutableError(NetlinkError): + def __init__(self, msg): + self._msg = msg + + def __str__(self): + return "Immutable attribute: " + self._msg + +class Message(object): + """Netlink message""" + + def __init__(self, size=0): + if size == 0: + self._msg = capi.nlmsg_alloc() + else: + self._msg = capi.nlmsg_alloc_size(size) + + if self._msg is None: + raise Exception("Message allocation returned NULL") + + def __del__(self): + capi.nlmsg_free(self._msg) + + def __len__(self): + return capi.nlmsg_len(nlmsg_hdr(self._msg)) + + @property + def protocol(self): + return capi.nlmsg_get_proto(self._msg) + + @protocol.setter + def protocol(self, value): + capi.nlmsg_set_proto(self._msg, value) + + @property + def maxSize(self): + return capi.nlmsg_get_max_size(self._msg) + + @property + def hdr(self): + return capi.nlmsg_hdr(self._msg) + + @property + def data(self): + return capi.nlmsg_data(self._msg) + + @property + def attrs(self): + return capi.nlmsg_attrdata(self._msg) + + def send(self, socket): + socket.send(self) + +class Socket(object): + """Netlink socket""" + + def __init__(self, cb=None): + if cb is None: + self._sock = capi.nl_socket_alloc() + else: + self._sock = capi.nl_socket_alloc_cb(cb) + + if self._sock is None: + raise Exception("NULL pointer returned while allocating socket") + + def __del__(self): + capi.nl_socket_free(self._sock) + + def __str__(self): + return "nlsock<" + str(self.localPort) + ">" + + @property + def local_port(self): + return capi.nl_socket_get_local_port(self._sock) + + @local_port.setter + def local_port(self, value): + capi.nl_socket_set_local_port(self._sock, int(value)) + + @property + def peer_port(self): + return capi.nl_socket_get_peer_port(self._sock) + + @peer_port.setter + def peer_port(self, value): + capi.nl_socket_set_peer_port(self._sock, int(value)) + + @property + def peer_groups(self): + return capi.nl_socket_get_peer_groups(self._sock) + + @peer_groups.setter + def peer_groups(self, value): + capi.nl_socket_set_peer_groups(self._sock, value) + + def set_bufsize(self, rx, tx): + capi.nl_socket_set_buffer_size(self._sock, rx, tx) + + def connect(self, proto): + capi.nl_connect(self._sock, proto) + return self + + def disconnect(self): + capi.nl_close(self._sock) + + def sendto(self, buf): + ret = capi.nl_sendto(self._sock, buf, len(buf)) + if ret < 0: + raise Exception("Failed to send") + else: + return ret + +_sockets = {} + +def lookup_socket(protocol): + try: + sock = _sockets[protocol] + except KeyError: + sock = Socket() + sock.connect(protocol) + _sockets[protocol] = sock + + return sock + +class DumpParams(object): + """Dumping parameters""" + + def __init__(self, type=NL_DUMP_LINE): + self._dp = capi.alloc_dump_params() + if not self._dp: + raise Exception("Unable to allocate struct nl_dump_params") + + self._dp.dp_type = type + + def __del__(self): + capi.free_dump_params(self._dp) + + @property + def type(self): + return self._dp.dp_type + + @type.setter + def type(self, value): + self._dp.dp_type = value + + @property + def prefix(self): + return self._dp.dp_prefix + + @prefix.setter + def prefix(self, value): + self._dp.dp_prefix = value + +# underscore this to make sure it is deleted first upon module deletion +_defaultDumpParams = DumpParams(type=NL_DUMP_LINE) + +########################################################################### +# Cacheable Object (Base Class) +class Object(object): + """Cacheable object (base class)""" + + def __init__(self, obj_name, name, obj=None): + self._obj_name = obj_name + self._name = name + + if not obj: + obj = capi.object_alloc_name(self._obj_name) + + self._nl_object = obj + + # Create a clone which stores the original state to notice + # modifications + clone_obj = capi.nl_object_clone(self._nl_object) + self._orig = self._obj2type(clone_obj) + + def __del__(self): + if not self._nl_object: + raise ValueError() + + capi.nl_object_put(self._nl_object) + + def __str__(self): + if hasattr(self, 'format'): + return self.format() + else: + return capi.nl_object_dump_buf(self._nl_object, 4096).rstrip() + + def _new_instance(self): + raise NotImplementedError() + + def clone(self): + """Clone object""" + return self._new_instance(capi.nl_object_clone(self._nl_object)) + + def dump(self, params=None): + """Dump object as human readable text""" + if params is None: + params = _defaultDumpParams + + capi.nl_object_dump(self._nl_object, params._dp) + + ##################################################################### + # mark + @property + def mark(self): + if capi.nl_object_is_marked(self.obj): + return True + else: + return False + + @mark.setter + def mark(self, value): + if value: + capi.nl_object_mark(self._nl_object) + else: + capi.nl_object_unmark(self._nl_object) + + ##################################################################### + # shared + @property + def shared(self): + return capi.nl_object_shared(self._nl_object) != 0 + + ##################################################################### + # attrs + @property + def attrs(self): + attr_list = capi.nl_object_attr_list(self._nl_object, 1024) + return re.split('\W+', attr_list[0]) + + ##################################################################### + # refcnt + @property + def refcnt(self): + return capi.nl_object_get_refcnt(self._nl_object) + + # this method resolves multiple levels of sub types to allow + # accessing properties of subclass/subtypes (e.g. link.vlan.id) + def _resolve(self, attr): + obj = self + l = attr.split('.') + while len(l) > 1: + obj = getattr(obj, l.pop(0)) + return (obj, l.pop(0)) + + def _setattr(self, attr, val): + obj, attr = self._resolve(attr) + return setattr(obj, attr, val) + + def _hasattr(self, attr): + obj, attr = self._resolve(attr) + return hasattr(obj, attr) + + def apply(self, attr, val): + try: + d = attrs[self._name + "." + attr] + except KeyError: + raise KeyError("Unknown " + self._name + + " attribute: " + attr) + + if 'immutable' in d: + raise ImmutableError(attr) + + if not self._hasattr(attr): + raise KeyError("Invalid " + self._name + + " attribute: " + attr) + self._setattr(attr, val) + +class ObjIterator(object): + def __init__(self, cache, obj): + self._cache = cache + + capi.nl_object_get(obj) + self._nl_object = obj + + self._first = 1 + self._end = 0 + + def __del__(self): + if self._nl_object: + capi.nl_object_put(self._nl_object) + + def __iter__(self): + return self + + def get_next(self): + return capi.nl_cache_get_next(self._nl_object) + + def next(self): + if self._end: + raise StopIteration() + + if self._first: + ret = self._nl_object + self._first = 0 + else: + ret = self.get_next() + if not ret: + self._end = 1 + raise StopIteration() + + # return ref of previous element and acquire ref of current + # element to have object stay around until we fetched the + # next ptr + capi.nl_object_put(self._nl_object) + capi.nl_object_get(ret) + self._nl_object = ret + + # reference used inside object + capi.nl_object_get(ret) + return self._cache._new_object(ret) + + +class ReverseObjIterator(ObjIterator): + def get_next(self): + return capi.nl_cache_get_prev(self._nl_object) + +########################################################################### +# Cache +class Cache(object): + """Collection of netlink objects""" + def __init__(self): + raise NotImplementedError() + + def __del(self): + capi.nl_cache_free(self._c_cache) + + def __len__(self): + return capi.nl_cache_nitems(self._c_cache) + + def __iter__(self): + obj = capi.nl_cache_get_first(self._c_cache) + return ObjIterator(self, obj) + + def __reversed__(self): + obj = capi.nl_cache_get_last(self._c_cache) + return ReverseObjIterator(self, obj) + + def __contains__(self, item): + obj = capi.nl_cache_search(self._c_cache, item._nl_object) + if obj is None: + return False + else: + capi.nl_object_put(obj) + return True + + # called by sub classes to allocate type specific caches by name + def _alloc_cache_name(self, name): + return capi.alloc_cache_name(name) + + # implemented by sub classes, must return new instasnce of cacheable + # object + def _new_object(self, obj): + raise NotImplementedError() + + # implemented by sub classes, must return instance of sub class + def _new_cache(self, cache): + raise NotImplementedError() + + def subset(self, filter): + """Return new cache containing subset of cache + + Cretes a new cache containing all objects which match the + specified filter. + """ + if not filter: + raise ValueError() + + c = capi.nl_cache_subset(self._c_cache, filter._nl_object) + return self._new_cache(cache=c) + + def dump(self, params=None, filter=None): + """Dump (print) cache as human readable text""" + if not params: + params = _defaultDumpParams + + if filter: + filter = filter._nl_object + + capi.nl_cache_dump_filter(self._c_cache, params._dp, filter) + + def clear(self): + """Remove all cache entries""" + capi.nl_cache_clear(self._c_cache) + + # Called by sub classes to set first cache argument + def _set_arg1(self, arg): + self.arg1 = arg + capi.nl_cache_set_arg1(self._c_cache, arg) + + # Called by sub classes to set second cache argument + def _set_arg2(self, arg): + self.arg2 = arg + capi.nl_cache_set_arg2(self._c_cache, arg) + + def refill(self, socket=None): + """Clear cache and refill it""" + if socket is None: + socket = lookup_socket(self._protocol) + + capi.nl_cache_refill(socket._sock, self._c_cache) + return self + + def resync(self, socket=None, cb=None): + """Synchronize cache with content in kernel""" + if socket is None: + socket = lookup_socket(self._protocol) + + capi.nl_cache_resync(socket._sock, self._c_cache, cb) + + def provide(self): + """Provide this cache to others + + Caches which have been "provided" are made available + to other users (of the same application context) which + "require" it. F.e. a link cache is generally provided + to allow others to translate interface indexes to + link names + """ + + capi.nl_cache_mngt_provide(self._c_cache) + + def unprovide(self): + """Unprovide this cache + + No longer make the cache available to others. If the cache + has been handed out already, that reference will still + be valid. + """ + capi.nl_cache_mngt_unprovide(self._c_cache) + +########################################################################### +# Cache Manager (Work in Progress) +NL_AUTO_PROVIDE = 1 +class CacheManager(object): + def __init__(self, protocol, flags=None): + + self._sock = Socket() + self._sock.connect(protocol) + + if not flags: + flags = NL_AUTO_PROVIDE + + self._mngr = cache_mngr_alloc(self._sock._sock, protocol, flags) + + def __del__(self): + if self._sock: + self._sock.disconnect() + + if self._mngr: + capi.nl_cache_mngr_free(self._mngr) + + def add(self, name): + capi.cache_mngr_add(self._mngr, name, None, None) + +########################################################################### +# Address Family +class AddressFamily(object): + """Address family representation + + af = AddressFamily('inet6') + # raises: + # - ValueError if family name is not known + # - TypeError if invalid type is specified for family + + print af # => 'inet6' (string representation) + print int(af) # => 10 (numeric representation) + print repr(af) # => AddressFamily('inet6') + """ + def __init__(self, family=socket.AF_UNSPEC): + if isinstance(family, str): + family = capi.nl_str2af(family) + if family < 0: + raise ValueError('Unknown family name') + elif not isinstance(family, int): + raise TypeError() + + self._family = family + + def __str__(self): + return capi.nl_af2str(self._family, 32)[0] + + def __len__(self): + return len(str(self)) + + def __int__(self): + return self._family + + def __repr__(self): + return 'AddressFamily(\'' + str(self) + '\')' + + +########################################################################### +# Abstract Address +class AbstractAddress(object): + """Abstract address object + + addr = AbstractAddress('127.0.0.1/8') + print addr # => '127.0.0.1/8' + print addr.prefixlen # => '8' + print addr.family # => 'inet' + print len(addr) # => '4' (32bit ipv4 address) + + a = AbstractAddress('10.0.0.1/24') + b = AbstractAddress('10.0.0.2/24') + print a == b # => False + + + """ + def __init__(self, addr): + self._nl_addr = None + + if isinstance(addr, str): + addr = capi.addr_parse(addr, socket.AF_UNSPEC) + if addr is None: + raise ValueError('Invalid address format') + elif addr: + capi.nl_addr_get(addr) + + self._nl_addr = addr + + def __del__(self): + if self._nl_addr: + capi.nl_addr_put(self._nl_addr) + + def __cmp__(self, other): + if isinstance(other, str): + other = AbstractAddress(other) + + diff = self.prefixlen - other.prefixlen + if diff == 0: + diff = capi.nl_addr_cmp(self._nl_addr, other._nl_addr) + + return diff + + def contains(self, item): + diff = int(self.family) - int(item.family) + if diff: + return False + + if item.prefixlen < self.prefixlen: + return False + + diff = capi.nl_addr_cmp_prefix(self._nl_addr, item._nl_addr) + return diff == 0 + + def __nonzero__(self): + if self._nl_addr: + return not capi.nl_addr_iszero(self._nl_addr) + else: + return False + + def __len__(self): + if self._nl_addr: + return capi.nl_addr_get_len(self._nl_addr) + else: + return 0 + + def __str__(self): + if self._nl_addr: + return capi.nl_addr2str(self._nl_addr, 64)[0] + else: + return "none" + + @property + def shared(self): + """True if address is shared (multiple users)""" + if self._nl_addr: + return capi.nl_addr_shared(self._nl_addr) != 0 + else: + return False + + @property + def prefixlen(self): + """Length of prefix (number of bits)""" + if self._nl_addr: + return capi.nl_addr_get_prefixlen(self._nl_addr) + else: + return 0 + + @prefixlen.setter + def prefixlen(self, value): + if not self._nl_addr: + raise TypeError() + + capi.nl_addr_set_prefixlen(self._nl_addr, int(value)) + + @property + def family(self): + """Address family""" + f = 0 + if self._nl_addr: + f = capi.nl_addr_get_family(self._nl_addr) + + return AddressFamily(f) + + @family.setter + def family(self, value): + if not self._nl_addr: + raise TypeError() + + if not isinstance(value, AddressFamily): + value = AddressFamily(value) + + capi.nl_addr_set_family(self._nl_addr, int(value)) + + +# global dictionay for all object attributes +# +# attrs[type][keyword] : value +# +# keyword: +# type = { int | str } +# immutable = { True | False } +# fmt = func (formatting function) +# +attrs = {} + +def attr(name, **kwds): + attrs[name] = {} + for k in kwds: + attrs[name][k] = kwds[k] + +def nlattr(name, **kwds): + """netlink object attribute decorator + + decorator used to mark mutable and immutable properties + of netlink objects. All properties marked as such are + regarded to be accessable. + + @netlink.nlattr('my_type.my_attr', type=int) + @property + def my_attr(self): + return self._my_attr + + """ + attrs[name] = {} + for k in kwds: + attrs[name][k] = kwds[k] + + def wrap_fn(func): + return func + + return wrap_fn + diff --git a/python/netlink/fixes.h b/python/netlink/fixes.h new file mode 100644 index 0000000..9a6118b --- /dev/null +++ b/python/netlink/fixes.h @@ -0,0 +1 @@ +#include diff --git a/python/netlink/route/__init__.py b/python/netlink/route/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python/netlink/route/address.py b/python/netlink/route/address.py new file mode 100644 index 0000000..c0ce54f --- /dev/null +++ b/python/netlink/route/address.py @@ -0,0 +1,398 @@ +# +# Copyright (c) 2011 Thomas Graf +# + +"""Module providing access to network addresses +""" + +__version__ = "1.0" +__all__ = [ + 'AddressCache', + 'Address'] + +import datetime +import netlink.core as netlink +import netlink.capi as core_capi +import netlink.route.capi as capi +import netlink.route.link as Link +import netlink.util as util + +########################################################################### +# Address Cache +class AddressCache(netlink.Cache): + """Cache containing network addresses""" + + def __init__(self, cache=None): + if not cache: + cache = self._alloc_cache_name("route/addr") + + self._protocol = netlink.NETLINK_ROUTE + self._c_cache = cache + + def __getitem__(self, key): + # Using ifindex=0 here implies that the local address itself + # is unique, otherwise the first occurence is returned. + return self.lookup(0, key) + + def lookup(self, ifindex, local): + if type(local) is str: + local = netlink.AbstractAddress(local) + + addr = capi.rtnl_addr_get(self._c_cache, ifindex, + local._nl_addr) + if addr is None: + raise KeyError() + + return Address._from_capi(addr) + + def _new_object(self, obj): + return Address(obj) + + def _new_cache(self, cache): + return AddressCache(cache=cache) + +########################################################################### +# Address Object +class Address(netlink.Object): + """Network address""" + + def __init__(self, obj=None): + netlink.Object.__init__(self, "route/addr", "address", obj) + self._rtnl_addr = self._obj2type(self._nl_object) + + @classmethod + def _from_capi(cls, obj): + return cls(capi.addr2obj(obj)) + + def _obj2type(self, obj): + return capi.obj2addr(obj) + + def __cmp__(self, other): + # sort by: + # 1. network link + # 2. address family + # 3. local address (including prefixlen) + diff = self.ifindex - other.ifindex + + if diff == 0: + diff = self.family - other.family + if diff == 0: + diff = capi.nl_addr_cmp(self.local, other.local) + + return diff + + def _new_instance(self, obj): + return Address(obj) + + ##################################################################### + # ifindex + @netlink.nlattr('address.ifindex', type=int, immutable=True, + fmt=util.num) + @property + def ifindex(self): + """interface index""" + return capi.rtnl_addr_get_ifindex(self._rtnl_addr) + + @ifindex.setter + def ifindex(self, value): + link = Link.resolve(value) + if not link: + raise ValueError() + + self.link = link + + ##################################################################### + # link + @netlink.nlattr('address.link', type=str, fmt=util.string) + @property + def link(self): + link = capi.rtnl_addr_get_link(self._rtnl_addr) + if not link: + return None + + return Link.Link.from_capi(link) + + @link.setter + def link(self, value): + if type(value) is str: + try: + value = Link.resolve(value) + except KeyError: + raise ValueError() + + capi.rtnl_addr_set_link(self._rtnl_addr, value._rtnl_link) + + # ifindex is immutable but we assume that if _orig does not + # have an ifindex specified, it was meant to be given here + if capi.rtnl_addr_get_ifindex(self._orig) == 0: + capi.rtnl_addr_set_ifindex(self._orig, value.ifindex) + + ##################################################################### + # label + @netlink.nlattr('address.label', type=str, fmt=util.string) + @property + def label(self): + """address label""" + return capi.rtnl_addr_get_label(self._rtnl_addr) + + @label.setter + def label(self, value): + capi.rtnl_addr_set_label(self._rtnl_addr, value) + + ##################################################################### + # flags + @netlink.nlattr('address.flags', type=str, fmt=util.string) + @property + def flags(self): + """Flags""" + flags = capi.rtnl_addr_get_flags(self._rtnl_addr) + return capi.rtnl_addr_flags2str(flags, 256)[0].split(',') + + def _set_flag(self, flag): + if flag[0] == '-': + i = capi.rtnl_addr_str2flags(flag[1:]) + capi.rtnl_addr_unset_flags(self._rtnl_addr, i) + else: + i = capi.rtnl_addr_str2flags(flag[1:]) + capi.rtnl_addr_set_flags(self._rtnl_addr, i) + + @flags.setter + def flags(self, value): + if type(value) is list: + for flag in value: + self._set_flag(flag) + else: + self._set_flag(value) + + ##################################################################### + # family + @netlink.nlattr('address.family', type=int, immutable=True, + fmt=util.num) + @property + def family(self): + """Address family""" + fam = capi.rtnl_addr_get_family(self._rtnl_addr) + return netlink.AddressFamily(fam) + + @family.setter + def family(self, value): + if not isinstance(value, AddressFamily): + value = AddressFamily(value) + + capi.rtnl_addr_set_family(self._rtnl_addr, int(value)) + + ##################################################################### + # scope + @netlink.nlattr('address.scope', type=int, fmt=util.num) + @property + def scope(self): + """Address scope""" + scope = capi.rtnl_addr_get_scope(self._rtnl_addr) + return capi.rtnl_scope2str(scope, 32)[0] + + @scope.setter + def scope(self, value): + if type(value) is str: + value = capi.rtnl_str2scope(value) + capi.rtnl_addr_set_scope(self._rtnl_addr, value) + + ##################################################################### + # local address + @netlink.nlattr('address.local', type=str, immutable=True, + fmt=util.addr) + @property + def local(self): + """Local address""" + a = capi.rtnl_addr_get_local(self._rtnl_addr) + return netlink.AbstractAddress(a) + + @local.setter + def local(self, value): + a = netlink.AbstractAddress(value) + capi.rtnl_addr_set_local(self._rtnl_addr, a._nl_addr) + + # local is immutable but we assume that if _orig does not + # have a local address specified, it was meant to be given here + if capi.rtnl_addr_get_local(self._orig) is None: + capi.rtnl_addr_set_local(self._orig, a._nl_addr) + + ##################################################################### + # Peer address + @netlink.nlattr('address.peer', type=str, fmt=util.addr) + @property + def peer(self): + """Peer address""" + a = capi.rtnl_addr_get_peer(self._rtnl_addr) + return netlink.AbstractAddress(a) + + @peer.setter + def peer(self, value): + a = netlink.AbstractAddress(value) + capi.rtnl_addr_set_peer(self._rtnl_addr, a._nl_addr) + + ##################################################################### + # Broadcast address + @netlink.nlattr('address.broadcast', type=str, fmt=util.addr) + @property + def broadcast(self): + """Broadcast address""" + a = capi.rtnl_addr_get_broadcast(self._rtnl_addr) + return netlink.AbstractAddress(a) + + @broadcast.setter + def broadcast(self, value): + a = netlink.AbstractAddress(value) + capi.rtnl_addr_set_broadcast(self._rtnl_addr, a._nl_addr) + + ##################################################################### + # Multicast address + @netlink.nlattr('address.multicast', type=str, fmt=util.addr) + @property + def multicast(self): + """multicast address""" + a = capi.rtnl_addr_get_multicast(self._rtnl_addr) + return netlink.AbstractAddress(a) + + @multicast.setter + def multicast(self, value): + try: + a = netlink.AbstractAddress(value) + except ValueError as err: + raise AttributeError('multicast', err) + + capi.rtnl_addr_set_multicast(self._rtnl_addr, a._nl_addr) + + ##################################################################### + # Anycast address + @netlink.nlattr('address.anycast', type=str, fmt=util.addr) + @property + def anycast(self): + """anycast address""" + a = capi.rtnl_addr_get_anycast(self._rtnl_addr) + return netlink.AbstractAddress(a) + + @anycast.setter + def anycast(self, value): + a = netlink.AbstractAddress(value) + capi.rtnl_addr_set_anycast(self._rtnl_addr, a._nl_addr) + + ##################################################################### + # Valid lifetime + @netlink.nlattr('address.valid_lifetime', type=int, immutable=True, + fmt=util.num) + @property + def valid_lifetime(self): + """Valid lifetime""" + msecs = capi.rtnl_addr_get_valid_lifetime(self._rtnl_addr) + if msecs == 0xFFFFFFFF: + return None + else: + return datetime.timedelta(seconds=msecs) + + @valid_lifetime.setter + def valid_lifetime(self, value): + capi.rtnl_addr_set_valid_lifetime(self._rtnl_addr, int(value)) + + ##################################################################### + # Preferred lifetime + @netlink.nlattr('address.preferred_lifetime', type=int, + immutable=True, fmt=util.num) + @property + def preferred_lifetime(self): + """Preferred lifetime""" + msecs = capi.rtnl_addr_get_preferred_lifetime(self._rtnl_addr) + if msecs == 0xFFFFFFFF: + return None + else: + return datetime.timedelta(seconds=msecs) + + @preferred_lifetime.setter + def preferred_lifetime(self, value): + capi.rtnl_addr_set_preferred_lifetime(self._rtnl_addr, int(value)) + + ##################################################################### + # Creation Time + @netlink.nlattr('address.create_time', type=int, immutable=True, + fmt=util.num) + @property + def create_time(self): + """Creation time""" + hsec = capi.rtnl_addr_get_create_time(self._rtnl_addr) + return datetime.timedelta(milliseconds=10*hsec) + + ##################################################################### + # Last Update + @netlink.nlattr('address.last_update', type=int, immutable=True, + fmt=util.num) + @property + def last_update(self): + """Last update""" + hsec = capi.rtnl_addr_get_last_update_time(self._rtnl_addr) + return datetime.timedelta(milliseconds=10*hsec) + + ##################################################################### + # add() + def add(self, socket=None, flags=None): + if not socket: + socket = netlink.lookup_socket(netlink.NETLINK_ROUTE) + + if not flags: + flags = netlink.NLM_F_CREATE + + ret = capi.rtnl_addr_add(socket._sock, self._rtnl_addr, flags) + if ret < 0: + raise netlink.KernelError(ret) + + ##################################################################### + # delete() + def delete(self, socket): + """Attempt to delete this link in the kernel""" + ret = capi.rtnl_addr_delete(socket._sock, self._addr) + if ret < 0: + raise netlink.KernelError(ret) + + ################################################################### + # private properties + # + # Used for formatting output. USE AT OWN RISK + @property + def _flags(self): + return ','.join(self.flags) + + ################################################################### + # + # format(details=False, stats=False) + # + def format(self, details=False, stats=False, nodev=False, indent=''): + """Return address as formatted text""" + fmt = util.MyFormatter(self, indent) + + buf = fmt.format('{a|local!b}') + + if not nodev: + buf += fmt.format(' {a|ifindex}') + + buf += fmt.format(' {a|scope}') + + if self.label: + buf += fmt.format(' "{a|label}"') + + buf += fmt.format(' <{a|_flags}>') + + if details: + buf += fmt.nl('\t{t|broadcast} {t|multicast}') \ + + fmt.nl('\t{t|peer} {t|anycast}') + + if self.valid_lifetime: + buf += fmt.nl('\t{s|valid-lifetime!k} '\ + '{a|valid_lifetime}') + + if self.preferred_lifetime: + buf += fmt.nl('\t{s|preferred-lifetime!k} '\ + '{a|preferred_lifetime}') + + if stats and (self.create_time or self.last_update): + buf += self.nl('\t{s|created!k} {a|create_time}'\ + ' {s|last-updated!k} {a|last_update}') + + return buf diff --git a/python/netlink/route/capi.i b/python/netlink/route/capi.i new file mode 100644 index 0000000..abe3cc9 --- /dev/null +++ b/python/netlink/route/capi.i @@ -0,0 +1,338 @@ +%module capi +%{ +#include +#include +#include +#include + +#include +#include + +#include +%} + +%include +%include + +%inline %{ + struct nl_object *link2obj(struct rtnl_link *link) + { + return OBJ_CAST(link); + } + + struct rtnl_link *obj2link(struct nl_object *obj) + { + return (struct rtnl_link *) obj; + } + + struct rtnl_link *get_from_kernel(struct nl_sock *sk, int ifindex, const char *name) + { + struct rtnl_link *link; + if (rtnl_link_get_kernel(sk, ifindex, name, &link) < 0) + return NULL; + return link; + } + + uint32_t inet_get_conf(struct rtnl_link *link, const unsigned int id) + { + uint32_t result; + + if (rtnl_link_inet_get_conf(link, id, &result) < 0) + return 0; + + return result; + } +%}; + +extern struct nl_object *link2obj(struct rtnl_link *); +extern struct rtnl_link *obj2link(struct nl_object *); + +/* */ + +%cstring_output_maxsize(char *buf, size_t len) +extern char * rtnl_scope2str(int, char *buf, size_t len); +extern int rtnl_str2scope(const char *); + +/* */ + +extern struct rtnl_link *rtnl_link_alloc(void); + +extern struct rtnl_link *rtnl_link_get(struct nl_cache *, int); +extern struct rtnl_link *rtnl_link_get_by_name(struct nl_cache *, const char *); + +extern int rtnl_link_build_add_request(struct rtnl_link *, int, struct nl_msg **); +extern int rtnl_link_add(struct nl_sock *, struct rtnl_link *, int); +extern int rtnl_link_build_change_request(struct rtnl_link *, struct rtnl_link *, int, struct nl_msg **); +extern int rtnl_link_change(struct nl_sock *, struct rtnl_link *, struct rtnl_link *, int); + +extern int rtnl_link_build_delete_request(const struct rtnl_link *, struct nl_msg **); +extern int rtnl_link_delete(struct nl_sock *, const struct rtnl_link *); +extern int rtnl_link_build_get_request(int, const char *, struct nl_msg **); + +extern char *rtnl_link_stat2str(int, char *, size_t); +extern int rtnl_link_str2stat(const char *); + +%cstring_output_maxsize(char *buf, size_t len) +extern char *rtnl_link_flags2str(int, char *buf, size_t len); +extern int rtnl_link_str2flags(const char *); + +%cstring_output_maxsize(char *buf, size_t len) +extern char *rtnl_link_operstate2str(uint8_t, char *buf, size_t len); +extern int rtnl_link_str2operstate(const char *); + +%cstring_output_maxsize(char *buf, size_t len) +extern char *rtnl_link_mode2str(uint8_t, char *buf, size_t len); +extern int rtnl_link_str2mode(const char *); + +extern void rtnl_link_set_qdisc(struct rtnl_link *, const char *); +extern char *rtnl_link_get_qdisc(struct rtnl_link *); + +extern void rtnl_link_set_name(struct rtnl_link *, const char *); +extern char *rtnl_link_get_name(struct rtnl_link *); + +extern void rtnl_link_set_flags(struct rtnl_link *, unsigned int); +extern void rtnl_link_unset_flags(struct rtnl_link *, unsigned int); +extern unsigned int rtnl_link_get_flags(struct rtnl_link *); + +extern void rtnl_link_set_mtu(struct rtnl_link *, unsigned int); +extern unsigned int rtnl_link_get_mtu(struct rtnl_link *); + +extern void rtnl_link_set_txqlen(struct rtnl_link *, unsigned int); +extern unsigned int rtnl_link_get_txqlen(struct rtnl_link *); + +extern void rtnl_link_set_weight(struct rtnl_link *, unsigned int); +extern unsigned int rtnl_link_get_weight(struct rtnl_link *); + +extern void rtnl_link_set_ifindex(struct rtnl_link *, int); +extern int rtnl_link_get_ifindex(struct rtnl_link *); + +extern void rtnl_link_set_family(struct rtnl_link *, int); +extern int rtnl_link_get_family(struct rtnl_link *); + +extern void rtnl_link_set_arptype(struct rtnl_link *, unsigned int); +extern unsigned int rtnl_link_get_arptype(struct rtnl_link *); + +extern void rtnl_link_set_addr(struct rtnl_link *, struct nl_addr *); +extern struct nl_addr *rtnl_link_get_addr(struct rtnl_link *); + +extern void rtnl_link_set_broadcast(struct rtnl_link *, struct nl_addr *); +extern struct nl_addr *rtnl_link_get_broadcast(struct rtnl_link *); + +extern void rtnl_link_set_link(struct rtnl_link *, int); +extern int rtnl_link_get_link(struct rtnl_link *); + +extern void rtnl_link_set_master(struct rtnl_link *, int); +extern int rtnl_link_get_master(struct rtnl_link *); + +extern void rtnl_link_set_operstate(struct rtnl_link *, uint8_t); +extern uint8_t rtnl_link_get_operstate(struct rtnl_link *); + +extern void rtnl_link_set_linkmode(struct rtnl_link *, uint8_t); +extern uint8_t rtnl_link_get_linkmode(struct rtnl_link *); + +extern const char *rtnl_link_get_ifalias(struct rtnl_link *); +extern void rtnl_link_set_ifalias(struct rtnl_link *, const char *); + +extern int rtnl_link_get_num_vf(struct rtnl_link *, uint32_t *); + +extern uint64_t rtnl_link_get_stat(struct rtnl_link *, int); +extern int rtnl_link_set_stat(struct rtnl_link *, const unsigned int, const uint64_t); + +extern int rtnl_link_set_info_type(struct rtnl_link *, const char *); +extern char *rtnl_link_get_info_type(struct rtnl_link *); + +/* */ + +struct vlan_map +{ + uint32_t vm_from; + uint32_t vm_to; +}; + +#define VLAN_PRIO_MAX 7 + +%cstring_output_maxsize(char *buf, size_t len) +extern char *rtnl_link_vlan_flags2str(int, char *buf, size_t len); +extern int rtnl_link_vlan_str2flags(const char *); + +extern int rtnl_link_vlan_set_id(struct rtnl_link *, int); +extern int rtnl_link_vlan_get_id(struct rtnl_link *); + +extern int rtnl_link_vlan_set_flags(struct rtnl_link *, unsigned int); +extern int rtnl_link_vlan_unset_flags(struct rtnl_link *, unsigned int); +extern unsigned int rtnl_link_vlan_get_flags(struct rtnl_link *); + +extern int rtnl_link_vlan_set_ingress_map(struct rtnl_link *, int, uint32_t); +extern uint32_t *rtnl_link_vlan_get_ingress_map(struct rtnl_link *); + +extern int rtnl_link_vlan_set_egress_map(struct rtnl_link *, uint32_t, int); +extern struct vlan_map *rtnl_link_vlan_get_egress_map(struct rtnl_link *, int *); + +/* */ +%cstring_output_maxsize(char *buf, size_t len) +extern const char *rtnl_link_inet_devconf2str(int, char *buf, size_t len); +extern unsigned int rtnl_link_inet_str2devconf(const char *); + +extern int rtnl_link_inet_set_conf(struct rtnl_link *, const unsigned int, uint32_t); + +/* */ + +%inline %{ + uint32_t tc_str2handle(const char *name) + { + uint32_t result; + + if (rtnl_tc_str2handle(name, &result) < 0) + return 0; + + return result; + } +%}; + +extern void rtnl_tc_set_ifindex(struct rtnl_tc *, int); +extern int rtnl_tc_get_ifindex(struct rtnl_tc *); +extern void rtnl_tc_set_link(struct rtnl_tc *, struct rtnl_link *); +extern struct rtnl_link *rtnl_tc_get_link(struct rtnl_tc *); +extern void rtnl_tc_set_mtu(struct rtnl_tc *, uint32_t); +extern uint32_t rtnl_tc_get_mtu(struct rtnl_tc *); +extern void rtnl_tc_set_mpu(struct rtnl_tc *, uint32_t); +extern uint32_t rtnl_tc_get_mpu(struct rtnl_tc *); +extern void rtnl_tc_set_overhead(struct rtnl_tc *, uint32_t); +extern uint32_t rtnl_tc_get_overhead(struct rtnl_tc *); +extern void rtnl_tc_set_linktype(struct rtnl_tc *, uint32_t); +extern uint32_t rtnl_tc_get_linktype(struct rtnl_tc *); +extern void rtnl_tc_set_handle(struct rtnl_tc *, uint32_t); +extern uint32_t rtnl_tc_get_handle(struct rtnl_tc *); +extern void rtnl_tc_set_parent(struct rtnl_tc *, uint32_t); +extern uint32_t rtnl_tc_get_parent(struct rtnl_tc *); +extern int rtnl_tc_set_kind(struct rtnl_tc *, const char *); +extern char * rtnl_tc_get_kind(struct rtnl_tc *); +extern uint64_t rtnl_tc_get_stat(struct rtnl_tc *, enum rtnl_tc_stat); + +extern int rtnl_tc_calc_txtime(int, int); +extern int rtnl_tc_calc_bufsize(int, int); +extern int rtnl_tc_calc_cell_log(int); + +extern int rtnl_tc_read_classid_file(void); +%cstring_output_maxsize(char *buf, size_t len) +extern char * rtnl_tc_handle2str(uint32_t, char *buf, size_t len); +extern int rtnl_classid_generate(const char *, uint32_t *, uint32_t); + +/* */ + +%inline %{ + struct nl_object *qdisc2obj(struct rtnl_qdisc *qdisc) + { + return OBJ_CAST(qdisc); + } + + struct rtnl_qdisc *obj2qdisc(struct nl_object *obj) + { + return (struct rtnl_qdisc *) obj; + } + + struct rtnl_tc *obj2tc(struct nl_object *obj) + { + return TC_CAST(obj); + } +%}; +extern struct rtnl_qdisc * + rtnl_qdisc_alloc(void); + +extern struct rtnl_qdisc * + rtnl_qdisc_get(struct nl_cache *, int, uint32_t); + +extern struct rtnl_qdisc * + rtnl_qdisc_get_by_parent(struct nl_cache *, int, uint32_t); + +extern int rtnl_qdisc_build_add_request(struct rtnl_qdisc *, int, + struct nl_msg **); +extern int rtnl_qdisc_add(struct nl_sock *, struct rtnl_qdisc *, int); + +extern int rtnl_qdisc_build_update_request(struct rtnl_qdisc *, + struct rtnl_qdisc *, + int, struct nl_msg **); + +extern int rtnl_qdisc_update(struct nl_sock *, struct rtnl_qdisc *, + struct rtnl_qdisc *, int); + +extern int rtnl_qdisc_build_delete_request(struct rtnl_qdisc *, + struct nl_msg **); +extern int rtnl_qdisc_delete(struct nl_sock *, struct rtnl_qdisc *); + +/* */ + +%inline %{ + struct nl_object *addr2obj(struct rtnl_addr *addr) + { + return OBJ_CAST(addr); + } + + struct rtnl_addr *obj2addr(struct nl_object *obj) + { + return (struct rtnl_addr *) obj; + } +%}; + +extern struct rtnl_addr *rtnl_addr_alloc(void); + +extern struct rtnl_addr * + rtnl_addr_get(struct nl_cache *, int, struct nl_addr *); + +extern int rtnl_addr_build_add_request(struct rtnl_addr *, int, + struct nl_msg **); +extern int rtnl_addr_add(struct nl_sock *, struct rtnl_addr *, int); + +extern int rtnl_addr_build_delete_request(struct rtnl_addr *, int, + struct nl_msg **); +extern int rtnl_addr_delete(struct nl_sock *, + struct rtnl_addr *, int); + +%cstring_output_maxsize(char *buf, size_t len) +extern char * rtnl_addr_flags2str(int, char *buf, size_t len); +extern int rtnl_addr_str2flags(const char *); + +extern int rtnl_addr_set_label(struct rtnl_addr *, const char *); +extern char * rtnl_addr_get_label(struct rtnl_addr *); + +extern void rtnl_addr_set_ifindex(struct rtnl_addr *, int); +extern int rtnl_addr_get_ifindex(struct rtnl_addr *); + +extern void rtnl_addr_set_link(struct rtnl_addr *, struct rtnl_link *); +extern struct rtnl_link * + rtnl_addr_get_link(struct rtnl_addr *); +extern void rtnl_addr_set_family(struct rtnl_addr *, int); +extern int rtnl_addr_get_family(struct rtnl_addr *); + +extern void rtnl_addr_set_prefixlen(struct rtnl_addr *, int); +extern int rtnl_addr_get_prefixlen(struct rtnl_addr *); + +extern void rtnl_addr_set_scope(struct rtnl_addr *, int); +extern int rtnl_addr_get_scope(struct rtnl_addr *); + +extern void rtnl_addr_set_flags(struct rtnl_addr *, unsigned int); +extern void rtnl_addr_unset_flags(struct rtnl_addr *, unsigned int); +extern unsigned int rtnl_addr_get_flags(struct rtnl_addr *); + +extern int rtnl_addr_set_local(struct rtnl_addr *, + struct nl_addr *); +extern struct nl_addr *rtnl_addr_get_local(struct rtnl_addr *); + +extern int rtnl_addr_set_peer(struct rtnl_addr *, struct nl_addr *); +extern struct nl_addr *rtnl_addr_get_peer(struct rtnl_addr *); + +extern int rtnl_addr_set_broadcast(struct rtnl_addr *, struct nl_addr *); +extern struct nl_addr *rtnl_addr_get_broadcast(struct rtnl_addr *); + +extern int rtnl_addr_set_multicast(struct rtnl_addr *, struct nl_addr *); +extern struct nl_addr *rtnl_addr_get_multicast(struct rtnl_addr *); + +extern int rtnl_addr_set_anycast(struct rtnl_addr *, struct nl_addr *); +extern struct nl_addr *rtnl_addr_get_anycast(struct rtnl_addr *); + +extern uint32_t rtnl_addr_get_valid_lifetime(struct rtnl_addr *); +extern void rtnl_addr_set_valid_lifetime(struct rtnl_addr *, uint32_t); +extern uint32_t rtnl_addr_get_preferred_lifetime(struct rtnl_addr *); +extern void rtnl_addr_set_preferred_lifetime(struct rtnl_addr *, uint32_t); +extern uint32_t rtnl_addr_get_create_time(struct rtnl_addr *); +extern uint32_t rtnl_addr_get_last_update_time(struct rtnl_addr *); diff --git a/python/netlink/route/link.py b/python/netlink/route/link.py new file mode 100644 index 0000000..b8e19fa --- /dev/null +++ b/python/netlink/route/link.py @@ -0,0 +1,596 @@ +# +# Copyright (c) 2011 Thomas Graf +# + +"""Module providing access to network links + +This module provides an interface to view configured network links, +modify them and to add and delete virtual network links. + +The following is a basic example: + import netlink.core as netlink + import netlink.route.link as link + + sock = netlink.Socket() + sock.connect(netlink.NETLINK_ROUTE) + + cache = link.LinkCache() # create new empty link cache + cache.refill(sock) # fill cache with all configured links + eth0 = cache['eth0'] # lookup link "eth0" + print eth0 # print basic configuration + +The module contains the following public classes: + + - Link -- Represents a network link. Instances can be created directly + via the constructor (empty link objects) or via the refill() + method of a LinkCache. + - LinkCache -- Derived from netlink.Cache, holds any number of + network links (Link instances). Main purpose is to keep + a local list of all network links configured in the + kernel. + +The following public functions exist: + - get_from_kernel(socket, name) + +""" + +__version__ = "0.1" +__all__ = [ + 'LinkCache', + 'Link', + 'get_from_kernel'] + +import socket +import sys +import netlink.core as netlink +import netlink.capi as core_capi +import netlink.route.capi as capi +import netlink.route.links.inet as inet +import netlink.util as util + +########################################################################### +# Link statistics definitions +RX_PACKETS = 0 +TX_PACKETS = 1 +RX_BYTES = 2 +TX_BYTES = 3 +RX_ERRORS = 4 +TX_ERRORS = 5 +RX_DROPPED = 6 +TX_DROPPED = 7 +RX_COMPRESSED = 8 +TX_COMPRESSED = 9 +RX_FIFO_ERR = 10 +TX_FIFO_ERR = 11 +RX_LEN_ERR = 12 +RX_OVER_ERR = 13 +RX_CRC_ERR = 14 +RX_FRAME_ERR = 15 +RX_MISSED_ERR = 16 +TX_ABORT_ERR = 17 +TX_CARRIER_ERR = 18 +TX_HBEAT_ERR = 19 +TX_WIN_ERR = 20 +COLLISIONS = 21 +MULTICAST = 22 +IP6_INPKTS = 23 +IP6_INHDRERRORS = 24 +IP6_INTOOBIGERRORS = 25 +IP6_INNOROUTES = 26 +IP6_INADDRERRORS = 27 +IP6_INUNKNOWNPROTOS = 28 +IP6_INTRUNCATEDPKTS = 29 +IP6_INDISCARDS = 30 +IP6_INDELIVERS = 31 +IP6_OUTFORWDATAGRAMS = 32 +IP6_OUTPKTS = 33 +IP6_OUTDISCARDS = 34 +IP6_OUTNOROUTES = 35 +IP6_REASMTIMEOUT = 36 +IP6_REASMREQDS = 37 +IP6_REASMOKS = 38 +IP6_REASMFAILS = 39 +IP6_FRAGOKS = 40 +IP6_FRAGFAILS = 41 +IP6_FRAGCREATES = 42 +IP6_INMCASTPKTS = 43 +IP6_OUTMCASTPKTS = 44 +IP6_INBCASTPKTS = 45 +IP6_OUTBCASTPKTS = 46 +IP6_INOCTETS = 47 +IP6_OUTOCTETS = 48 +IP6_INMCASTOCTETS = 49 +IP6_OUTMCASTOCTETS = 50 +IP6_INBCASTOCTETS = 51 +IP6_OUTBCASTOCTETS = 52 +ICMP6_INMSGS = 53 +ICMP6_INERRORS = 54 +ICMP6_OUTMSGS = 55 +ICMP6_OUTERRORS = 56 + +########################################################################### +# Link Cache +class LinkCache(netlink.Cache): + """Cache of network links""" + + def __init__(self, family=socket.AF_UNSPEC, cache=None): + if not cache: + cache = self._alloc_cache_name("route/link") + + self._protocol = netlink.NETLINK_ROUTE + self._c_cache = cache + self._set_arg1(family) + + def __getitem__(self, key): + if type(key) is int: + link = capi.rtnl_link_get(self._c_cache, key) + elif type(key) is str: + link = capi.rtnl_link_get_by_name(self._c_cache, key) + + if link is None: + raise KeyError() + else: + return Link.from_capi(link) + + def _new_object(self, obj): + return Link(obj) + + def _new_cache(self, cache): + return LinkCache(family=self.arg1, cache=cache) + +########################################################################### +# Link Object +class Link(netlink.Object): + """Network link""" + + def __init__(self, obj=None): + netlink.Object.__init__(self, "route/link", "link", obj) + self._rtnl_link = self._obj2type(self._nl_object) + + self._type = None + + if self.type: + self._type_lookup(self.type) + + self.inet = inet.InetLink(self) + self.af = {'inet' : self.inet } + + @classmethod + def from_capi(cls, obj): + return cls(capi.link2obj(obj)) + + def _obj2type(self, obj): + return capi.obj2link(obj) + + def __cmp__(self, other): + return self.ifindex - other.ifindex + + def _new_instance(self, obj): + if not obj: + raise ValueError() + + return Link(obj) + + def _type_lookup(self, name): + rname = 'netlink.route.links.' + name + tmp = __import__(rname) + mod = sys.modules[rname] + + # this will create a type instance and assign it to + # link., e.g. link.vlan.id + self._type = mod.assign_type(self) + + ##################################################################### + # ifindex + @netlink.nlattr('link.ifindex', type=int, immutable=True, fmt=util.num) + @property + def ifindex(self): + """interface index""" + return capi.rtnl_link_get_ifindex(self._rtnl_link) + + @ifindex.setter + def ifindex(self, value): + capi.rtnl_link_set_ifindex(self._rtnl_link, int(value)) + + # ifindex is immutable but we assume that if _orig does not + # have an ifindex specified, it was meant to be given here + if capi.rtnl_link_get_ifindex(self._orig) == 0: + capi.rtnl_link_set_ifindex(self._orig, int(value)) + + ##################################################################### + # name + @netlink.nlattr('link.name', type=str, fmt=util.bold) + @property + def name(self): + """Name of link""" + return capi.rtnl_link_get_name(self._rtnl_link) + + @name.setter + def name(self, value): + capi.rtnl_link_set_name(self._rtnl_link, value) + + # name is the secondary identifier, if _orig does not have + # the name specified yet, assume it was meant to be specified + # here. ifindex will always take priority, therefore if ifindex + # is specified as well, this will be ignored automatically. + if capi.rtnl_link_get_name(self._orig) is None: + capi.rtnl_link_set_name(self._orig, value) + + ##################################################################### + # flags + @netlink.nlattr('link.flags', type=str, fmt=util.string) + @property + def flags(self): + """Flags""" + flags = capi.rtnl_link_get_flags(self._rtnl_link) + return capi.rtnl_link_flags2str(flags, 256)[0].split(',') + + def _set_flag(self, flag): + if flag[0] == '-': + i = capi.rtnl_link_str2flags(flag[1:]) + capi.rtnl_link_unset_flags(self._rtnl_link, i) + else: + i = capi.rtnl_link_str2flags(flag[1:]) + capi.rtnl_link_set_flags(self._rtnl_link, i) + + @flags.setter + def flags(self, value): + if type(value) is list: + for flag in value: + self._set_flag(flag) + else: + self._set_flag(value) + + ##################################################################### + # mtu + @netlink.nlattr('link.mtu', type=int, fmt=util.num) + @property + def mtu(self): + """Maximum Transmission Unit""" + return capi.rtnl_link_get_mtu(self._rtnl_link) + + @mtu.setter + def mtu(self, value): + capi.rtnl_link_set_mtu(self._rtnl_link, int(value)) + + ##################################################################### + # family + @netlink.nlattr('link.family', type=int, immutable=True, fmt=util.num) + @property + def family(self): + """Address family""" + return capi.rtnl_link_get_family(self._rtnl_link) + + @family.setter + def family(self, value): + capi.rtnl_link_set_family(self._rtnl_link, value) + + ##################################################################### + # address + @netlink.nlattr('link.address', type=str, fmt=util.addr) + @property + def address(self): + """Hardware address (MAC address)""" + a = capi.rtnl_link_get_addr(self._rtnl_link) + return netlink.AbstractAddress(a) + + @address.setter + def address(self, value): + capi.rtnl_link_set_addr(self._rtnl_link, value._addr) + + ##################################################################### + # broadcast + @netlink.nlattr('link.broadcast', type=str, fmt=util.addr) + @property + def broadcast(self): + """Hardware broadcast address""" + a = capi.rtnl_link_get_broadcast(self._rtnl_link) + return netlink.AbstractAddress(a) + + @broadcast.setter + def broadcast(self, value): + capi.rtnl_link_set_broadcast(self._rtnl_link, value._addr) + + ##################################################################### + # qdisc + @netlink.nlattr('link.qdisc', type=str, immutable=True, fmt=util.string) + @property + def qdisc(self): + """Name of qdisc (cannot be changed)""" + return capi.rtnl_link_get_qdisc(self._rtnl_link) + + @qdisc.setter + def qdisc(self, value): + capi.rtnl_link_set_qdisc(self._rtnl_link, value) + + ##################################################################### + # txqlen + @netlink.nlattr('link.txqlen', type=int, fmt=util.num) + @property + def txqlen(self): + """"Length of transmit queue""" + return capi.rtnl_link_get_txqlen(self._rtnl_link) + + @txqlen.setter + def txqlen(self, value): + capi.rtnl_link_set_txqlen(self._rtnl_link, int(value)) + + ##################################################################### + # weight + @netlink.nlattr('link.weight', type=str, fmt=util.string) + @property + def weight(self): + """Weight""" + v = capi.rtnl_link_get_weight(self._rtnl_link) + if v == 4294967295: + return 'max' + else: + return str(v) + + @weight.setter + def weight(self, value): + if value == 'max': + v = 4294967295 + else: + v = int(value) + capi.rtnl_link_set_weight(self._rtnl_link, v) + + ##################################################################### + # arptype + @netlink.nlattr('link.arptype', type=str, immutable=True, fmt=util.string) + @property + def arptype(self): + """Type of link (cannot be changed)""" + type = capi.rtnl_link_get_arptype(self._rtnl_link) + return core_capi.nl_llproto2str(type, 64)[0] + + @arptype.setter + def arptype(self, value): + i = core_capi.nl_str2llproto(value) + capi.rtnl_link_set_arptype(self._rtnl_link, i) + + ##################################################################### + # operstate + @netlink.nlattr('link.operstate', type=str, immutable=True, + fmt=util.string, title='state') + @property + def operstate(self): + """Operational status""" + operstate = capi.rtnl_link_get_operstate(self._rtnl_link) + return capi.rtnl_link_operstate2str(operstate, 32)[0] + + @operstate.setter + def operstate(self, value): + i = capi.rtnl_link_str2operstate(flag) + capi.rtnl_link_set_operstate(self._rtnl_link, i) + + ##################################################################### + # mode + @netlink.nlattr('link.mode', type=str, immutable=True, fmt=util.string) + @property + def mode(self): + """Link mode""" + mode = capi.rtnl_link_get_linkmode(self._rtnl_link) + return capi.rtnl_link_mode2str(mode, 32)[0] + + @mode.setter + def mode(self, value): + i = capi.rtnl_link_str2mode(flag) + capi.rtnl_link_set_linkmode(self._rtnl_link, i) + + ##################################################################### + # alias + @netlink.nlattr('link.alias', type=str, fmt=util.string) + @property + def alias(self): + """Interface alias (SNMP)""" + return capi.rtnl_link_get_ifalias(self._rtnl_link) + + @alias.setter + def alias(self, value): + capi.rtnl_link_set_ifalias(self._rtnl_link, value) + + ##################################################################### + # type + @netlink.nlattr('link.type', type=str, fmt=util.string) + @property + def type(self): + """Link type""" + return capi.rtnl_link_get_info_type(self._rtnl_link) + + @type.setter + def type(self, value): + if capi.rtnl_link_set_info_type(self._rtnl_link, value) < 0: + raise NameError("unknown info type") + + self._type_lookup(value) + + ##################################################################### + # get_stat() + def get_stat(self, stat): + """Retrieve statistical information""" + if type(stat) is str: + stat = capi.rtnl_link_str2stat(stat) + if stat < 0: + raise NameError("unknown name of statistic") + + return capi.rtnl_link_get_stat(self._rtnl_link, stat) + + ##################################################################### + # add() + def add(self, socket=None, flags=None): + if not socket: + socket = netlink.lookup_socket(netlink.NETLINK_ROUTE) + + if not flags: + flags = netlink.NLM_F_CREATE + + ret = capi.rtnl_link_add(socket._sock, self._rtnl_link, flags) + if ret < 0: + raise netlink.KernelError(ret) + + ##################################################################### + # change() + def change(self, socket=None, flags=0): + """Commit changes made to the link object""" + if not socket: + socket = netlink.lookup_socket(netlink.NETLINK_ROUTE) + + if not self._orig: + raise NetlinkError("Original link not available") + ret = capi.rtnl_link_change(socket._sock, self._orig, self._rtnl_link, flags) + if ret < 0: + raise netlink.KernelError(ret) + + ##################################################################### + # delete() + def delete(self, socket=None): + """Attempt to delete this link in the kernel""" + if not socket: + socket = netlink.lookup_socket(netlink.NETLINK_ROUTE) + + ret = capi.rtnl_link_delete(socket._sock, self._rtnl_link) + if ret < 0: + raise netlink.KernelError(ret) + + ################################################################### + # private properties + # + # Used for formatting output. USE AT OWN RISK + @property + def _state(self): + if 'up' in self.flags: + buf = util.good('up') + if 'lowerup' not in self.flags: + buf += ' ' + util.bad('no-carrier') + else: + buf = util.bad('down') + return buf + + @property + def _brief(self): + buf = '' + if self.type: + if self._type and hasattr(self._type, 'brief'): + buf += self._type.brief() + else: + buf += util.yellow(self.type) + + return buf + self._foreach_af('brief') + + @property + def _flags(self): + ignore = ['up', 'running', 'lowerup'] + return ','.join([flag for flag in self.flags if flag not in ignore]) + + def _foreach_af(self, name, args=None): + buf = '' + for af in self.af: + try: + func = getattr(self.af[af], name) + s = str(func(args)) + if len(s) > 0: + buf += ' ' + s + except AttributeError: + pass + return buf + + ################################################################### + # + # format(details=False, stats=False) + # + def format(self, details=False, stats=False, indent=''): + """Return link as formatted text""" + fmt = util.MyFormatter(self, indent) + + buf = fmt.format('{a|ifindex} {a|name} {a|arptype} {a|address} '\ + '{a|_state} <{a|_flags}> {a|_brief}') + + if details: + buf += fmt.nl('\t{t|mtu} {t|txqlen} {t|weight} '\ + '{t|qdisc} {t|operstate}') + buf += fmt.nl('\t{t|broadcast} {t|alias}') + + buf += self._foreach_af('details', fmt) + + if stats: + l = [['Packets', RX_PACKETS, TX_PACKETS], + ['Bytes', RX_BYTES, TX_BYTES], + ['Errors', RX_ERRORS, TX_ERRORS], + ['Dropped', RX_DROPPED, TX_DROPPED], + ['Compressed', RX_COMPRESSED, TX_COMPRESSED], + ['FIFO Errors', RX_FIFO_ERR, TX_FIFO_ERR], + ['Length Errors', RX_LEN_ERR, None], + ['Over Errors', RX_OVER_ERR, None], + ['CRC Errors', RX_CRC_ERR, None], + ['Frame Errors', RX_FRAME_ERR, None], + ['Missed Errors', RX_MISSED_ERR, None], + ['Abort Errors', None, TX_ABORT_ERR], + ['Carrier Errors', None, TX_CARRIER_ERR], + ['Heartbeat Errors', None, TX_HBEAT_ERR], + ['Window Errors', None, TX_WIN_ERR], + ['Collisions', None, COLLISIONS], + ['Multicast', None, MULTICAST], + ['', None, None], + ['Ipv6:', None, None], + ['Packets', IP6_INPKTS, IP6_OUTPKTS], + ['Bytes', IP6_INOCTETS, IP6_OUTOCTETS], + ['Discards', IP6_INDISCARDS, IP6_OUTDISCARDS], + ['Multicast Packets', IP6_INMCASTPKTS, IP6_OUTMCASTPKTS], + ['Multicast Bytes', IP6_INMCASTOCTETS, IP6_OUTMCASTOCTETS], + ['Broadcast Packets', IP6_INBCASTPKTS, IP6_OUTBCASTPKTS], + ['Broadcast Bytes', IP6_INBCASTOCTETS, IP6_OUTBCASTOCTETS], + ['Delivers', IP6_INDELIVERS, None], + ['Forwarded', None, IP6_OUTFORWDATAGRAMS], + ['No Routes', IP6_INNOROUTES, IP6_OUTNOROUTES], + ['Header Errors', IP6_INHDRERRORS, None], + ['Too Big Errors', IP6_INTOOBIGERRORS, None], + ['Address Errors', IP6_INADDRERRORS, None], + ['Unknown Protocol', IP6_INUNKNOWNPROTOS, None], + ['Truncated Packets', IP6_INTRUNCATEDPKTS, None], + ['Reasm Timeouts', IP6_REASMTIMEOUT, None], + ['Reasm Requests', IP6_REASMREQDS, None], + ['Reasm Failures', IP6_REASMFAILS, None], + ['Reasm OK', IP6_REASMOKS, None], + ['Frag Created', None, IP6_FRAGCREATES], + ['Frag Failures', None, IP6_FRAGFAILS], + ['Frag OK', None, IP6_FRAGOKS], + ['', None, None], + ['ICMPv6:', None, None], + ['Messages', ICMP6_INMSGS, ICMP6_OUTMSGS], + ['Errors', ICMP6_INERRORS, ICMP6_OUTERRORS]] + + buf += '\n\t%s%s%s%s\n' % (33 * ' ', util.title('RX'), + 15 * ' ', util.title('TX')) + + for row in l: + row[0] = util.kw(row[0]) + row[1] = self.get_stat(row[1]) if row[1] else '' + row[2] = self.get_stat(row[2]) if row[2] else '' + buf += '\t{0:27} {1:>16} {2:>16}\n'.format(*row) + + buf += self._foreach_af('stats') + + return buf + +def get(name, socket=None): + """Lookup Link object directly from kernel""" + if not name: + raise ValueError() + + if not socket: + socket = netlink.lookup_socket(netlink.NETLINK_ROUTE) + + link = capi.get_from_kernel(socket._sock, 0, name) + if not link: + return None + + return Link.from_capi(link) + +link_cache = LinkCache() + +def resolve(name): + socket = netlink.lookup_socket(netlink.NETLINK_ROUTE) + link_cache.refill() + + return link_cache[name] diff --git a/python/netlink/route/links/__init__.py b/python/netlink/route/links/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python/netlink/route/links/dummy.py b/python/netlink/route/links/dummy.py new file mode 100644 index 0000000..495be94 --- /dev/null +++ b/python/netlink/route/links/dummy.py @@ -0,0 +1,21 @@ +# +# Copyright (c) 2011 Thomas Graf +# + +"""Dummy + +""" + +__version__ = "1.0" +__all__ = ['assign_type'] + +import netlink.core as netlink +import netlink.route.capi as capi + +class DummyLink(object): + def __init__(self, link): + self._rtnl_link = link + +def assign_type(link): + link.dummy = DummyLink(link._rtnl_link) + return link.dummy diff --git a/python/netlink/route/links/inet.py b/python/netlink/route/links/inet.py new file mode 100644 index 0000000..63c234f --- /dev/null +++ b/python/netlink/route/links/inet.py @@ -0,0 +1,153 @@ +# +# Copyright (c) 2011 Thomas Graf +# + +"""IPv4 + +""" + +__all__ = [''] + +import netlink.core as netlink +import netlink.route.capi as capi +import netlink.util as util + +DEVCONF_FORWARDING = 1 +DEVCONF_MC_FORWARDING = 2 +DEVCONF_PROXY_ARP = 3 +DEVCONF_ACCEPT_REDIRECTS = 4 +DEVCONF_SECURE_REDIRECTS = 5 +DEVCONF_SEND_REDIRECTS = 6 +DEVCONF_SHARED_MEDIA = 7 +DEVCONF_RP_FILTER = 8 +DEVCONF_ACCEPT_SOURCE_ROUTE = 9 +DEVCONF_BOOTP_RELAY = 10 +DEVCONF_LOG_MARTIANS = 11 +DEVCONF_TAG = 12 +DEVCONF_ARPFILTER = 13 +DEVCONF_MEDIUM_ID = 14 +DEVCONF_NOXFRM = 15 +DEVCONF_NOPOLICY = 16 +DEVCONF_FORCE_IGMP_VERSION = 17 +DEVCONF_ARP_ANNOUNCE = 18 +DEVCONF_ARP_IGNORE = 19 +DEVCONF_PROMOTE_SECONDARIES = 20 +DEVCONF_ARP_ACCEPT = 21 +DEVCONF_ARP_NOTIFY = 22 +DEVCONF_ACCEPT_LOCAL = 23 +DEVCONF_SRC_VMARK = 24 +DEVCONF_PROXY_ARP_PVLAN = 25 +DEVCONF_MAX = DEVCONF_PROXY_ARP_PVLAN + +def _resolve(id): + if type(id) is str: + id = capi.rtnl_link_inet_str2devconf(id)[0] + if id < 0: + raise NameError("unknown configuration id") + return id + +class InetLink(object): + def __init__(self, link): + self._link = link + + def details(self, fmt): + buf = '\n' + fmt.nl('\t%s\n\t' % util.title('Configuration:')) + + for i in range(DEVCONF_FORWARDING,DEVCONF_MAX+1): + if i & 1 and i > 1: + buf += fmt.nl('\t') + txt = util.kw(capi.rtnl_link_inet_devconf2str(i, 32)[0]) + buf += fmt.format('{0:28s} {1:12} ', txt, + self.get_conf(i)) + + + return buf + + def get_conf(self, id): + return capi.inet_get_conf(self._link._rtnl_link, _resolve(id)) + + def set_conf(self, id, value): + return capi.rtnl_link_inet_set_conf(self._link._rtnl_link, + _resolve(id), int(value)) + + @netlink.nlattr('link.inet.forwarding', type=bool, fmt=util.bool) + @property + def forwarding(self): + return bool(self.get_conf(DEVCONF_FORWARDING)) + + @forwarding.setter + def forwarding(self, value): + self.set_conf(DEVCONF_FORWARDING, int(value)) + + @netlink.nlattr('link.inet.mc_forwarding', type=bool, fmt=util.bool) + @property + def mc_forwarding(self): + return bool(self.get_conf(DEVCONF_MC_FORWARDING)) + + @mc_forwarding.setter + def mc_forwarding(self, value): + self.set_conf(DEVCONF_MC_FORWARDING, int(value)) + + @netlink.nlattr('link.inet.proxy_arp', type=bool, fmt=util.bool) + @property + def proxy_arp(self): + return bool(self.get_conf(DEVCONF_PROXY_ARP)) + + @proxy_arp.setter + def proxy_arp(self, value): + self.set_conf(DEVCONF_PROXY_ARP, int(value)) + + @netlink.nlattr('link.inet.accept_redirects', type=bool, fmt=util.bool) + @property + def accept_redirects(self): + return bool(self.get_conf(DEVCONF_ACCEPT_REDIRECTS)) + + @accept_redirects.setter + def accept_redirects(self, value): + self.set_conf(DEVCONF_ACCEPT_REDIRECTS, int(value)) + + @netlink.nlattr('link.inet.secure_redirects', type=bool, fmt=util.bool) + @property + def secure_redirects(self): + return bool(self.get_conf(DEVCONF_SECURE_REDIRECTS)) + + @secure_redirects.setter + def secure_redirects(self, value): + self.set_conf(DEVCONF_SECURE_REDIRECTS, int(value)) + + @netlink.nlattr('link.inet.send_redirects', type=bool, fmt=util.bool) + @property + def send_redirects(self): + return bool(self.get_conf(DEVCONF_SEND_REDIRECTS)) + + @send_redirects.setter + def send_redirects(self, value): + self.set_conf(DEVCONF_SEND_REDIRECTS, int(value)) + + @netlink.nlattr('link.inet.shared_media', type=bool, fmt=util.bool) + @property + def shared_media(self): + return bool(self.get_conf(DEVCONF_SHARED_MEDIA)) + + @shared_media.setter + def shared_media(self, value): + self.set_conf(DEVCONF_SHARED_MEDIA, int(value)) + +# IPV4_DEVCONF_RP_FILTER, +# IPV4_DEVCONF_ACCEPT_SOURCE_ROUTE, +# IPV4_DEVCONF_BOOTP_RELAY, +# IPV4_DEVCONF_LOG_MARTIANS, +# IPV4_DEVCONF_TAG, +# IPV4_DEVCONF_ARPFILTER, +# IPV4_DEVCONF_MEDIUM_ID, +# IPV4_DEVCONF_NOXFRM, +# IPV4_DEVCONF_NOPOLICY, +# IPV4_DEVCONF_FORCE_IGMP_VERSION, +# IPV4_DEVCONF_ARP_ANNOUNCE, +# IPV4_DEVCONF_ARP_IGNORE, +# IPV4_DEVCONF_PROMOTE_SECONDARIES, +# IPV4_DEVCONF_ARP_ACCEPT, +# IPV4_DEVCONF_ARP_NOTIFY, +# IPV4_DEVCONF_ACCEPT_LOCAL, +# IPV4_DEVCONF_SRC_VMARK, +# IPV4_DEVCONF_PROXY_ARP_PVLAN, diff --git a/python/netlink/route/links/vlan.py b/python/netlink/route/links/vlan.py new file mode 100644 index 0000000..1d4e03d --- /dev/null +++ b/python/netlink/route/links/vlan.py @@ -0,0 +1,62 @@ +# +# Copyright (c) 2011 Thomas Graf +# + +"""VLAN network link + +""" + +import netlink.core as netlink +import netlink.route.capi as capi + +class VLANLink(object): + def __init__(self, link): + self._link = link + + ################################################################### + # id + @netlink.nlattr('link.vlan.id', type=int) + @property + def id(self): + """vlan identifier""" + return capi.rtnl_link_vlan_get_id(self._link) + + @id.setter + def id(self, value): + capi.rtnl_link_vlan_set_id(self._link, int(value)) + + ################################################################### + # flags + @netlink.nlattr('link.vlan.flags', type=str) + @property + def flags(self): + """vlan flags""" + flags = capi.rtnl_link_vlan_get_flags(self._link) + return capi.rtnl_link_vlan_flags2str(flags, 256)[0].split(',') + + def _set_flag(self, flag): + i = capi.rtnl_link_vlan_str2flags(flag[1:]) + if flag[0] == '-': + capi.rtnl_link_vlan_unset_flags(self._link, i) + else: + capi.rtnl_link_vlan_set_flags(self._link, i) + + @flags.setter + def flags(self, value): + if type(value) is list: + for flag in value: + self._set_flag(flag) + else: + self._set_flag(value) + + ################################################################### + # TODO: + # - ingress map + # - egress map + + def brief(self): + return 'vlan-id ' + self.id + +def assign_type(link): + link.vlan = VLANLink(link._link) + return link.vlan diff --git a/python/netlink/route/qdisc.py b/python/netlink/route/qdisc.py new file mode 100644 index 0000000..c8a4c6f --- /dev/null +++ b/python/netlink/route/qdisc.py @@ -0,0 +1,185 @@ +# +# Copyright (c) 2011 Thomas Graf +# + +__all__ = [ + 'QdiscCache', + 'Qdisc'] + +import netlink.core as netlink +import netlink.capi as core_capi +import netlink.route.capi as capi +import netlink.util as util +import netlink.route.tc as tc + +########################################################################### +# Link Cache +class QdiscCache(netlink.Cache): + """Cache of qdiscs""" + + def __init__(self, cache=None): + if not cache: + cache = self._alloc_cache_name("route/qdisc") + + self._protocol = netlink.NETLINK_ROUTE + self._c_cache = cache + +# def __getitem__(self, key): +# if type(key) is int: +# link = capi.rtnl_link_get(self._this, key) +# elif type(key) is str: +# link = capi.rtnl_link_get_by_name(self._this, key) +# +# if qdisc is None: +# raise KeyError() +# else: +# return Qdisc._from_capi(capi.qdisc2obj(qdisc)) + + def _new_object(self, obj): + return Qdisc(obj) + + def _new_cache(self, cache): + return QdiscCache(cache=cache) + +########################################################################### +# Qdisc Object +class Qdisc(tc.Tc): + """Network link""" + + def __init__(self, obj=None): + self._name = "qdisc" + self._abbr = "qdisc" + + netlink.Object.__init__(self, "route/qdisc", "qdisc", obj) + self._tc = capi.obj2tc(self._nl_object) + self._rtnl_qdisc = self._obj2type(self._nl_object) + + netlink.attr('qdisc.handle', fmt=util.handle) + netlink.attr('qdisc.parent', fmt=util.handle) + netlink.attr('qdisc.kind', fmt=util.bold) + + def _obj2type(self, obj): + return capi.obj2qdisc(obj) + + def __cmp__(self, other): + return self.handle - other.handle + + def _new_instance(self, obj): + if not obj: + raise ValueError() + + return Qdisc(obj) + +# ##################################################################### +# # add() +# def add(self, socket, flags=None): +# if not flags: +# flags = netlink.NLM_F_CREATE +# +# ret = capi.rtnl_link_add(socket._sock, self._link, flags) +# if ret < 0: +# raise netlink.KernelError(ret) +# +# ##################################################################### +# # change() +# def change(self, socket, flags=0): +# """Commit changes made to the link object""" +# if not self._orig: +# raise NetlinkError("Original link not available") +# ret = capi.rtnl_link_change(socket._sock, self._orig, self._link, flags) +# if ret < 0: +# raise netlink.KernelError(ret) +# +# ##################################################################### +# # delete() +# def delete(self, socket): +# """Attempt to delete this link in the kernel""" +# ret = capi.rtnl_link_delete(socket._sock, self._link) +# if ret < 0: +# raise netlink.KernelError(ret) + + @property + def _dev(self): + buf = util.kw('dev') + ' ' + + if self.link: + return buf + util.string(self.link.name) + else: + return buf + util.num(self.ifindex) + + @property + def _parent(self): + return util.kw('parent') + ' ' + str(self.parent) + + ################################################################### + # + # format(details=False, stats=False) + # + def format(self, details=False, stats=False): + """Return qdisc as formatted text""" + fmt = util.MyFormatter(self) + + buf = fmt.format('qdisc {kind} {handle} {_dev} {_parent}') + + if details: + fmt = util.MyFormatter(self) + buf += fmt.format('\n'\ + '\t{mtu} {mpu} {overhead}\n') + +# if stats: +# l = [['Packets', RX_PACKETS, TX_PACKETS], +# ['Bytes', RX_BYTES, TX_BYTES], +# ['Errors', RX_ERRORS, TX_ERRORS], +# ['Dropped', RX_DROPPED, TX_DROPPED], +# ['Compressed', RX_COMPRESSED, TX_COMPRESSED], +# ['FIFO Errors', RX_FIFO_ERR, TX_FIFO_ERR], +# ['Length Errors', RX_LEN_ERR, None], +# ['Over Errors', RX_OVER_ERR, None], +# ['CRC Errors', RX_CRC_ERR, None], +# ['Frame Errors', RX_FRAME_ERR, None], +# ['Missed Errors', RX_MISSED_ERR, None], +# ['Abort Errors', None, TX_ABORT_ERR], +# ['Carrier Errors', None, TX_CARRIER_ERR], +# ['Heartbeat Errors', None, TX_HBEAT_ERR], +# ['Window Errors', None, TX_WIN_ERR], +# ['Collisions', None, COLLISIONS], +# ['Multicast', None, MULTICAST], +# ['', None, None], +# ['Ipv6:', None, None], +# ['Packets', IP6_INPKTS, IP6_OUTPKTS], +# ['Bytes', IP6_INOCTETS, IP6_OUTOCTETS], +# ['Discards', IP6_INDISCARDS, IP6_OUTDISCARDS], +# ['Multicast Packets', IP6_INMCASTPKTS, IP6_OUTMCASTPKTS], +# ['Multicast Bytes', IP6_INMCASTOCTETS, IP6_OUTMCASTOCTETS], +# ['Broadcast Packets', IP6_INBCASTPKTS, IP6_OUTBCASTPKTS], +# ['Broadcast Bytes', IP6_INBCASTOCTETS, IP6_OUTBCASTOCTETS], +# ['Delivers', IP6_INDELIVERS, None], +# ['Forwarded', None, IP6_OUTFORWDATAGRAMS], +# ['No Routes', IP6_INNOROUTES, IP6_OUTNOROUTES], +# ['Header Errors', IP6_INHDRERRORS, None], +# ['Too Big Errors', IP6_INTOOBIGERRORS, None], +# ['Address Errors', IP6_INADDRERRORS, None], +# ['Unknown Protocol', IP6_INUNKNOWNPROTOS, None], +# ['Truncated Packets', IP6_INTRUNCATEDPKTS, None], +# ['Reasm Timeouts', IP6_REASMTIMEOUT, None], +# ['Reasm Requests', IP6_REASMREQDS, None], +# ['Reasm Failures', IP6_REASMFAILS, None], +# ['Reasm OK', IP6_REASMOKS, None], +# ['Frag Created', None, IP6_FRAGCREATES], +# ['Frag Failures', None, IP6_FRAGFAILS], +# ['Frag OK', None, IP6_FRAGOKS], +# ['', None, None], +# ['ICMPv6:', None, None], +# ['Messages', ICMP6_INMSGS, ICMP6_OUTMSGS], +# ['Errors', ICMP6_INERRORS, ICMP6_OUTERRORS]] +# +# buf += '\n\t%s%s%s%s\n' % (33 * ' ', util.title('RX'), +# 15 * ' ', util.title('TX')) +# +# for row in l: +# row[0] = util.kw(row[0]) +# row[1] = self.get_stat(row[1]) if row[1] else '' +# row[2] = self.get_stat(row[2]) if row[2] else '' +# buf += '\t{0:27} {1:>16} {2:>16}\n'.format(*row) + + return buf diff --git a/python/netlink/route/tc.py b/python/netlink/route/tc.py new file mode 100644 index 0000000..ebe25e1 --- /dev/null +++ b/python/netlink/route/tc.py @@ -0,0 +1,357 @@ + + +# +# Copyright (c) 2011 Thomas Graf +# + +__all__ = [ + 'TcCache', + 'Tc', + 'QdiscCache', + 'Qdisc'] + +import socket +import sys +import netlink.core as netlink +import netlink.capi as core_capi +import netlink.route.capi as capi +import netlink.util as util + +from netlink.route.link import Link + +TC_PACKETS = 0 +TC_BYTES = 1 +TC_RATE_BPS = 2 +TC_RATE_PPS = 3 +TC_QLEN = 4 +TC_BACKLOG = 5 +TC_DROPS = 6 +TC_REQUEUES = 7 +TC_OVERLIMITS = 9 + +TC_H_ROOT = 0xFFFFFFFF +TC_H_INGRESS = 0xFFFFFFF1 + +class Handle(object): + def __init__(self, val=None): + if type(val) is str: + val = capi.tc_str2handle(val) + elif not val: + val = 0 + + self._val = int(val) + + def __int__(self): + return self._val + + def __str__(self): + return capi.rtnl_tc_handle2str(self._val, 64)[0] + + def isroot(self): + return self._val == TC_H_ROOT or self._val == TC_H_INGRESS + +########################################################################### +# TC Cache +class TcCache(netlink.Cache): + """Cache of traffic control object""" + + def __getitem__(self, key): + raise NotImplementedError() + +########################################################################### +# Tc Object +class Tc(netlink.Object): + def __cmp__(self, other): + return self.ifindex - other.ifindex + + def isroot(self): + return self.parent.isroot() + + ##################################################################### + # ifindex + @property + def ifindex(self): + """interface index""" + return capi.rtnl_tc_get_ifindex(self._tc) + + @ifindex.setter + def ifindex(self, value): + capi.rtnl_tc_set_ifindex(self._tc, int(value)) + + ##################################################################### + # link + @property + def link(self): + link = capi.rtnl_tc_get_link(self._tc) + if not link: + return None + else: + return Link._from_capi(link) + + @link.setter + def link(self, value): + capi.rtnl_tc_set_link(self._tc, value._link) + + ##################################################################### + # mtu + @property + def mtu(self): + return capi.rtnl_tc_get_mtu(self._tc) + + @mtu.setter + def mtu(self, value): + capi.rtnl_tc_set_mtu(self._tc, int(value)) + + ##################################################################### + # mpu + @property + def mpu(self): + return capi.rtnl_tc_get_mpu(self._tc) + + @mpu.setter + def mpu(self, value): + capi.rtnl_tc_set_mpu(self._tc, int(value)) + + ##################################################################### + # overhead + @property + def overhead(self): + return capi.rtnl_tc_get_overhead(self._tc) + + @overhead.setter + def overhead(self, value): + capi.rtnl_tc_set_overhead(self._tc, int(value)) + + ##################################################################### + # linktype + @property + def linktype(self): + return capi.rtnl_tc_get_linktype(self._tc) + + @linktype.setter + def linktype(self, value): + capi.rtnl_tc_set_linktype(self._tc, int(value)) + + ##################################################################### + # handle + @property + def handle(self): + return Handle(capi.rtnl_tc_get_handle(self._tc)) + + @handle.setter + def handle(self, value): + capi.rtnl_tc_set_handle(self._tc, int(value)) + + ##################################################################### + # parent + @property + def parent(self): + return Handle(capi.rtnl_tc_get_parent(self._tc)) + + @parent.setter + def parent(self, value): + capi.rtnl_tc_set_parent(self._tc, int(value)) + + ##################################################################### + # kind + @property + def kind(self): + return capi.rtnl_tc_get_kind(self._tc) + + @kind.setter + def kind(self, value): + capi.rtnl_tc_set_kind(self._tc, value) + + def get_stat(self, id): + return capi.rtnl_tc_get_stat(self._tc, id) + +class TcTree(object): + def __init__(self, link, sock): + self._qdisc_cache = QdiscCache().refill(sock) + + def __getitem__(self, key): + pass +# if type(key) is int: +# link = capi.rtnl_link_get(self._this, key) +# elif type(key) is str: +# link = capi.rtnl_link_get_by_name(self._this, key) +# +# if qdisc is None: +# raise KeyError() +# else: +# return Qdisc._from_capi(capi.qdisc2obj(qdisc)) + + + + +########################################################################### +# Link Cache +class QdiscCache(netlink.Cache): + """Cache of qdiscs""" + + def __init__(self, cache=None): + if not cache: + cache = self._alloc_cache_name("route/qdisc") + + self._c_cache = cache + +# def __getitem__(self, key): +# if type(key) is int: +# link = capi.rtnl_link_get(self._this, key) +# elif type(key) is str: +# link = capi.rtnl_link_get_by_name(self._this, key) +# +# if qdisc is None: +# raise KeyError() +# else: +# return Qdisc._from_capi(capi.qdisc2obj(qdisc)) + + def _new_object(self, obj): + return Qdisc(obj) + + def _new_cache(self, cache): + return QdiscCache(cache=cache) + +########################################################################### +# Qdisc Object +class Qdisc(Tc): + """Network link""" + + def __init__(self, obj=None): + self._name = "qdisc" + self._abbr = "qdisc" + + if not obj: + self._qdisc = capi.rtnl_qdisc_alloc() + else: + self._qdisc = capi.obj2qdisc(obj) + + self._obj = capi.qdisc2obj(self._qdisc) + self._orig = capi.obj2qdisc(core_capi.nl_object_clone(self._obj)) + + Tc.__init__(self) + + netlink.attr('qdisc.handle', fmt=util.handle) + netlink.attr('qdisc.parent', fmt=util.handle) + netlink.attr('qdisc.kind', fmt=util.bold) + + def __cmp__(self, other): + return self.handle - other.handle + + def _new_instance(self, obj): + if not obj: raise ValueError() + return Qdisc(obj) + +# ##################################################################### +# # add() +# def add(self, socket, flags=None): +# if not flags: +# flags = netlink.NLM_F_CREATE +# +# ret = capi.rtnl_link_add(socket._sock, self._link, flags) +# if ret < 0: +# raise netlink.KernelError(ret) +# +# ##################################################################### +# # change() +# def change(self, socket, flags=0): +# """Commit changes made to the link object""" +# if not self._orig: +# raise NetlinkError("Original link not available") +# ret = capi.rtnl_link_change(socket._sock, self._orig, self._link, flags) +# if ret < 0: +# raise netlink.KernelError(ret) +# +# ##################################################################### +# # delete() +# def delete(self, socket): +# """Attempt to delete this link in the kernel""" +# ret = capi.rtnl_link_delete(socket._sock, self._link) +# if ret < 0: +# raise netlink.KernelError(ret) + + @property + def _dev(self): + buf = util.kw('dev') + ' ' + + if self.link: + return buf + util.string(self.link.name) + else: + return buf + util.num(self.ifindex) + + @property + def _parent(self): + return util.kw('parent') + ' ' + str(self.parent) + + ################################################################### + # + # format(details=False, stats=False) + # + def format(self, details=False, stats=False): + """Return qdisc as formatted text""" + fmt = util.BriefFormatter(self) + + buf = fmt.format('qdisc {kind} {handle} {_dev} {_parent}') + + if details: + fmt = util.DetailFormatter(self) + buf += fmt.format('\n'\ + '\t{mtu} {mpu} {overhead}\n') + +# if stats: +# l = [['Packets', RX_PACKETS, TX_PACKETS], +# ['Bytes', RX_BYTES, TX_BYTES], +# ['Errors', RX_ERRORS, TX_ERRORS], +# ['Dropped', RX_DROPPED, TX_DROPPED], +# ['Compressed', RX_COMPRESSED, TX_COMPRESSED], +# ['FIFO Errors', RX_FIFO_ERR, TX_FIFO_ERR], +# ['Length Errors', RX_LEN_ERR, None], +# ['Over Errors', RX_OVER_ERR, None], +# ['CRC Errors', RX_CRC_ERR, None], +# ['Frame Errors', RX_FRAME_ERR, None], +# ['Missed Errors', RX_MISSED_ERR, None], +# ['Abort Errors', None, TX_ABORT_ERR], +# ['Carrier Errors', None, TX_CARRIER_ERR], +# ['Heartbeat Errors', None, TX_HBEAT_ERR], +# ['Window Errors', None, TX_WIN_ERR], +# ['Collisions', None, COLLISIONS], +# ['Multicast', None, MULTICAST], +# ['', None, None], +# ['Ipv6:', None, None], +# ['Packets', IP6_INPKTS, IP6_OUTPKTS], +# ['Bytes', IP6_INOCTETS, IP6_OUTOCTETS], +# ['Discards', IP6_INDISCARDS, IP6_OUTDISCARDS], +# ['Multicast Packets', IP6_INMCASTPKTS, IP6_OUTMCASTPKTS], +# ['Multicast Bytes', IP6_INMCASTOCTETS, IP6_OUTMCASTOCTETS], +# ['Broadcast Packets', IP6_INBCASTPKTS, IP6_OUTBCASTPKTS], +# ['Broadcast Bytes', IP6_INBCASTOCTETS, IP6_OUTBCASTOCTETS], +# ['Delivers', IP6_INDELIVERS, None], +# ['Forwarded', None, IP6_OUTFORWDATAGRAMS], +# ['No Routes', IP6_INNOROUTES, IP6_OUTNOROUTES], +# ['Header Errors', IP6_INHDRERRORS, None], +# ['Too Big Errors', IP6_INTOOBIGERRORS, None], +# ['Address Errors', IP6_INADDRERRORS, None], +# ['Unknown Protocol', IP6_INUNKNOWNPROTOS, None], +# ['Truncated Packets', IP6_INTRUNCATEDPKTS, None], +# ['Reasm Timeouts', IP6_REASMTIMEOUT, None], +# ['Reasm Requests', IP6_REASMREQDS, None], +# ['Reasm Failures', IP6_REASMFAILS, None], +# ['Reasm OK', IP6_REASMOKS, None], +# ['Frag Created', None, IP6_FRAGCREATES], +# ['Frag Failures', None, IP6_FRAGFAILS], +# ['Frag OK', None, IP6_FRAGOKS], +# ['', None, None], +# ['ICMPv6:', None, None], +# ['Messages', ICMP6_INMSGS, ICMP6_OUTMSGS], +# ['Errors', ICMP6_INERRORS, ICMP6_OUTERRORS]] +# +# buf += '\n\t%s%s%s%s\n' % (33 * ' ', util.title('RX'), +# 15 * ' ', util.title('TX')) +# +# for row in l: +# row[0] = util.kw(row[0]) +# row[1] = self.get_stat(row[1]) if row[1] else '' +# row[2] = self.get_stat(row[2]) if row[2] else '' +# buf += '\t{0:27} {1:>16} {2:>16}\n'.format(*row) + + return buf diff --git a/python/netlink/util.py b/python/netlink/util.py new file mode 100644 index 0000000..d3d0167 --- /dev/null +++ b/python/netlink/util.py @@ -0,0 +1,146 @@ +# +# Utilities +# +# Copyright (c) 2011 Thomas Graf +# + +"""utility module for netlink + +""" + +import netlink.core as netlink +from string import Formatter + +__version__ = "1.0" + +def _color(t, c): + return chr(0x1b)+"["+str(c)+"m"+str(t)+chr(0x1b)+"[0m" + +def black(t): + return _color(t, 30) + +def red(t): + return _color(t, 31) + +def green(t): + return _color(t, 32) + +def yellow(t): + return _color(t, 33) + +def blue(t): + return _color(t, 34) + +def mangenta(t): + return _color(t, 35) + +def cyan(t): + return _color(t, 36) + +def white(t): + return _color(t, 37) + +def bold(t): + return _color(t, 1) + +def kw(t): + return yellow(t) + +def num(t): + return str(t) + +def string(t): + return t + +def addr(t): + return str(t) + +def bad(t): + return red(t) + +def good(t): + return green(t) + +def title(t): + return t + +def bool(t): + return str(t) + +def handle(t): + return str(t) + +class MyFormatter(Formatter): + def __init__(self, obj, indent=''): + self._obj = obj + self._indent = indent + + def _nlattr(self, key): + value = getattr(self._obj, key) + title = None + + if type(value) == 'instancemethod': + value = value() + + try: + d = netlink.attrs[self._obj._name + '.' + key] + + if 'fmt' in d: + value = d['fmt'](value) + + if 'title' in d: + title = d['title'] + except KeyError: + pass + + return title, str(value) + + def get_value(self, key, args, kwds): + # Let default get_value() handle ints + if not isinstance(key, str): + return Formatter.get_value(self, key, args, kwds) + + # HACK, we allow defining strings via fields to allow + # conversions + if key[:2] == 's|': + return key[2:] + + if key[:2] == 't|': + # title mode ("TITLE ATTR") + include_title = True + elif key[:2] == 'a|': + # plain attribute mode ("ATTR") + include_title = False + else: + # No special field, have default get_value() get it + return Formatter.get_value(self, key, args, kwds) + + key = key[2:] + (title, value) = self._nlattr(key) + + if include_title: + if not title: + title = key # fall back to key as title + value = kw(title) + ' ' + value + + return value + + def convert_field(self, value, conversion): + if conversion == 'r': + return repr(value) + elif conversion == 's': + return str(value) + elif conversion == 'k': + return kw(value) + elif conversion == 'b': + return bold(value) + elif conversion is None: + return value + + raise ValueError("Unknown converion specifier {0!s}".format(conversion)) + + def nl(self): + return '\n' + self._indent + + def nl(self, format_string=''): + return '\n' + self._indent + self.format(format_string) diff --git a/python/setup.py b/python/setup.py new file mode 100644 index 0000000..9ea7c15 --- /dev/null +++ b/python/setup.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python + +from distutils.core import setup, Extension + +opts = ['-O', '-nodefaultctor'] +include = ['../include'] + +netlink_capi = Extension('netlink/_capi', + sources = ['netlink/capi.i'], + include_dirs = include, + swig_opts = opts, + libraries = ['nl'], + ) + +route_capi = Extension('netlink/route/_capi', + sources = ['netlink/route/capi.i'], + include_dirs = include, + swig_opts = opts, + libraries = ['nl', 'nl-route'], + ) + +setup(name = 'netlink', + version = '1.0', + description = 'Python wrapper for netlink protocols', + author = 'Thomas Graf', + author_email = 'tgraf@suug.ch', + ext_modules = [netlink_capi, route_capi], + packages = ['netlink', 'netlink.route', 'netlink.route.links'], + )