IPDB module

Basically, IPDB is a transactional database, containing records, that represent network stack objects. Any change in the database is not reflected immediately in OS, but waits until commit() is called. One failed operation during commit() rolls back all the changes, has been made so far. Moreover, IPDB has commit hooks API, that allows you to roll back changes depending on your own function calls, e.g. when a host or a network becomes unreachable.

IPDB vs. IPRoute

These two modules, IPRoute and IPDB, use completely different approaches. The first one, IPRoute, just forward requests to the kernel, and doesn’t wait for the system state. So it’s up to developer to check, whether the requested object is really set up, or not yet.

The latter, IPDB, is an asynchronously updated database, that starts several additional threads by default. If your project’s policy doesn’t allow implicit threads, keep it in mind. But unlike IPRoute, the IPDB ensures the changes to be reflected in the system:

with IPDB() as ipdb:
    with ipdb.interfaces['eth0'] as i:
    # ---> <--- here you can expect `eth0` is up
    #           and has these two addresses, so
    #           the following code can rely on that

So IPDB is updated asynchronously, but the commit() operation is synchronous. At least, it is planned to be such.

NB: In the example above `commit()` is implied with the `__exit__()` of the `with` statement.

The choice between IPDB and IPRoute depends on your project’s workflow. If you plan to retrieve the system info not too often (or even once), or you are sure there will be not too many network object, it is better to use IPRoute. If you plan to lookup the network info on the regular basis and there can be loads of network objects, it is better to use IPDB. Why?

IPRoute just loads what you ask – and loads all the information you ask to. While IPDB loads all the info upon startup, and later is just updated by asynchronous broadcast netlink messages. Assume you want to lookup ARP cache that contains hundreds or even thousands of objects. Using IPRoute, you have to load all the ARP cache every time you want to make a lookup. While IPDB will load all the cache once, and then maintain it up-to-date just inserting new records or removing them by one.

So, IPRoute is much simpler when you need to make a call and then exit, while IPDB is cheaper in terms of CPU performance if you implement a long-running program like a daemon.


Simple tutorial:

from pyroute2 import IPDB
# several IPDB instances are supported within on process
ipdb = IPDB()

# commit is called automatically upon the exit from `with`
# statement
with ipdb.interfaces.eth0 as i:
    i.address = '00:11:22:33:44:55'
    i.ifname = 'bala'
    i.txqlen = 2000

# basic routing support
ipdb.routes.add({'dst': 'default',
                 'gateway': ''}).commit()

# do not forget to shutdown IPDB

Please, notice ip.release() call in the end. Though it is not forced in an interactive python session for the better user experience, it is required in the scripts to sync the IPDB state before exit.

IPDB supports functional-like syntax also:

from pyroute2 import IPDB
with IPDB() as ipdb:
    intf = (ipdb.interfaces['eth0']
    # ---> <--- here you have the interface reference with
    #           all the changes applied: renamed, added ipaddr,
    #           changed macaddr and mtu.
    ...  # some code

# pls notice, that the interface reference will not work
# outside of `with IPDB() ...`

Transaction modes

IPDB has several operating modes:

  • ‘implicit’ (default) – the first change starts an implicit

    transaction, that have to be committed

  • ‘explicit’ – you have to begin() a transaction prior to

    make any change

The default is to use implicit transaction. This behaviour can be changed in the future, so use ‘mode’ argument when creating IPDB instances.

The sample session with explicit transactions:

In [1]: from pyroute2 import IPDB
In [2]: ip = IPDB(mode='explicit')
In [3]: ifdb = ip.interfaces
In [4]: ifdb.tap0.begin()
    Out[3]: UUID('7a637a44-8935-4395-b5e7-0ce40d31d937')
In [5]: ifdb.tap0.up()
In [6]: ifdb.tap0.address = '00:11:22:33:44:55'
In [7]: ifdb.tap0.add_ip('', 24)
In [8]: ifdb.tap0.add_ip('', 24)
In [9]: ifdb.tap0.review()
    {'+ipaddr': set([('', 24), ('', 24)]),
     '-ipaddr': set([]),
     'address': '00:11:22:33:44:55',
     'flags': 4099}
In [10]: ifdb.tap0.commit()

Note, that you can review() the current_tx transaction, and commit() or drop() it. Also, multiple transactions are supported, use uuid returned by begin() to identify them.

Actually, the form like ‘ip.tap0.address’ is an eye-candy. The IPDB objects are dictionaries, so you can write the code above as that:

ipdb.interfaces['tap0']['address'] = '00:11:22:33:44:55'

Context managers

Transactional objects (interfaces, routes) can act as context managers in the same way as IPDB does itself:

with ipdb.interfaces.tap0 as i:
    i.address = '00:11:22:33:44:55'
    i.ifname = 'vpn'
    i.add_ip('', 24)
    i.add_ip('', 24)

On exit, the context manager will authomatically commit() the transaction.

Create interfaces

IPDB can also create virtual interfaces:

with ipdb.create(kind='bridge', ifname='control') as i:

The IPDB.create() call has the same syntax as IPRoute.link(‘add’, ...), except you shouldn’t specify the ‘add’ command. Refer to IPRoute docs for details.

Routes management

IPDB has a simple yet useful routing management interface.

Create a route

To add a route, there is an easy to use syntax:

# spec as a dictionary
spec = {'dst': '',
        'oif': 4,
        'gateway': '',
        'metrics': {'mtu': 1400,
                    'advmss': 500}}

# pass spec as is

# pass spec as kwargs

# use keyword arguments explicitly
ipdb.routes.add(dst='', oif=4, ...).commit()

Please notice, that the device can be specified with oif (output interface) or iif (input interface), the device keyword is not supported anymore.

Get a route

To access and change the routes, one can use notations as follows:

# default table (254)
# change the route gateway and mtu
with ipdb.routes[''] as route:
    route.gateway = ''
    route.metrics.mtu = 1500

# access the default route

# change the default gateway
with ipdb.routes['default'] as route:
    route.gateway = ''

By default, the path ipdb.routes reflects only the main routing table (254). But Linux supports much more routing tables, so does IPDB:

In [1]: ipdb.routes.tables.keys()
Out[1]: [0, 254, 255]

In [2]: len(ipdb.routes.tables[255])
Out[2]: 11  # => 11 automatic routes in the table local

It is important to understand, that routing tables keys in IPDB are not only the destination prefix. The key consists of ‘prefix/mask’ string and the route priority (if any):

In [1]: ipdb.routes.tables[254].idx.keys()
[RouteKey(dst='default', priority=600),
 RouteKey(dst='', priority=600),
 RouteKey(dst='', priority=None)]

But a routing table in IPDB allows several variants of the route spec. The simplest case is to retrieve a route by prefix, if there is only one match:

# get route by prefix

# get route by a special name

If there are more than one route that matches the spec, only the first one will be retrieved. One should iterate all the records and filter by a key to retrieve all matches:

# only one route will be retrieved

# get all routes by this prefix
[ x for x in ipdb.routes if x['dst'] == 'fe80::/64' ]

It is also possible to use dicts as specs:

ipdb.routes[{'dst': '',
             'oif': 2}]

Route metrics

A special object is dedicated to route metrics, one can access it via route.metrics or route[‘metrics’]:

# these two statements are equal:
with ipdb.routes[''] as route:
    route['metrics']['mtu'] = 1400

with ipdb.routes[''] as route:
    route.metrics.mtu = 1400

Possible metrics are defined in rtmsg.py:rtmsg.metrics, e.g. RTAX_HOPLIMIT means hoplimit metric etc.

Multipath routing

Multipath nexthops are managed via route.add_nh() and route.del_nh() methods. They are available to review via route.multipath, but one should not directly add/remove/modify nexthops in route.multipath, as the changes will not be committed correctly.

To create a multipath route:

ipdb.routes.add({'dst': '',
                 'multipath': [{'gateway': '',
                                'hops': 2},
                               {'gateway': '',
                                'hops': 1},
                               {'gateway': ''}]}).commit()

To change a multipath route:

with ipdb.routes[''] as r:
    r.add_nh({'gateway': ''})
    r.del_nh({'gateway': ''})

Another possible way is to create a normal route and turn it into multipath by add_nh():

# create a non-MP route with one gateway:
 .add({'dst': '',
       'gateway': ''})

# turn it to become a MP route:
 .add_nh({'gateway': ''})

# here the route will contain two NH records, with
# gateways and

# remove one NH and turn the route to be a normal one
 .del_nh({'gateway': ''})

# thereafter the traffic to will go only
# via

Differences from the iproute2 syntax

By historical reasons, iproute2 uses names that differs from what the kernel uses. E.g., iproute2 uses weight for multipath route hops instead of hops, where weight == (hops + 1). Thus, a route created with hops == 2 will be listed by iproute2 as weight 3.

Another significant difference is metrics. The pyroute2 library uses the kernel naming scheme, where metrics means mtu, rtt, window etc. The iproute2 utility uses metric (not metrics) as a name for the priority field.

In examples:

# -------------------------------------------------------
# iproute2 command:
$ ip route add default \
    nexthop via weight 2 \
    nexthop via weight 9

# pyroute2 code:
 .add({'dst': 'default',
       'multipath': [{'gateway': '', 'hops': 1},
                     {'gateway': '', 'hops': 8}])

# -------------------------------------------------------
# iproute2 command:
$ ip route add default via metric 200

# pyroute2 code:
 .add({'dst': 'default',
       'gateway': '',
       'priority': 200})

# -------------------------------------------------------
# iproute2 command:
$ ip route add default via mtu 1460

# pyroute2 code:
 .add({'dst': 'default',
       'gateway': '',
       'metrics': {'mtu': 1460}})

Multipath default routes


As of the merge of kill_rtcache into the kernel, and it’s release in ~3.6, weighted default routes no longer work in Linux.

Please refer to https://github.com/svinota/pyroute2/issues/171#issuecomment-149297244 for details.

Rules management

IPDB provides a basic IP rules management system.

Create a rule

Syntax is almost the same as for routes:

# rule spec
spec = {'src': '',
        'table': 200,
        'priority': 15000}


Get a rule

The way IPDB handles IP rules is almost the same as routes, but rule keys are more complicated – the Linux kernel doesn’t use keys for rules, but instead iterates all the records until the first one w/o any attribute mismatch.

The fields that the kernel uses to compare rules, IPDB uses as the key fields (see pyroute2/ipdb/rule.py:RuleKey)

There are also more ways to find a record, as with routes:

# 1. iterate all the records
for record in ipdb.rules:

# 2. an integer as the key matches the first
#    rule with that priority

# 3. a dict as the key returns the first match
#    for all the specified attrs
ipdb.rules[{'dst': '', 'table': 200}]


Thus, the rule priority is not a key, neither in the kernel, nor in IPDB. One should not rely on priorities as on keys, there may be several rules with the same priority, and it often happens, e.g. on Android systems.


There is no change operation for the rule records in the kernel, so only add/del work. When IPDB changes a record, it effectively deletes the old one and creates the new with new parameters, but the object, referring the record, stays the same. Also that means, that IPDB can not recognize the situation, when someone else does the same. So if there is another program changing records by del/add operations, even another IPDB instance, referring objects in the IPDB will be recreated.

Performance issues

In the case of bursts of Netlink broadcast messages, all the activity of the pyroute2-based code in the async mode becomes suppressed to leave more CPU resources to the packet reader thread. So please be ready to cope with delays in the case of Netlink broadcast storms. It means also, that IPDB state will be synchronized with OS also after some delay.

The class API

class pyroute2.ipdb.main.IPDB(nl=None, mode='implicit', restart_on_error=None, nl_async=None, debug=False, ignore_rtables=None)

The class that maintains information about network setup of the host. Monitoring netlink events allows it to react immediately. It uses no polling.

register_callback(callback, mode='post')

IPDB callbacks are routines executed on a RT netlink message arrival. There are two types of callbacks: “post” and “pre” callbacks.


“Post” callbacks are executed after the message is processed by IPDB and all corresponding objects are created or deleted. Using ipdb reference in “post” callbacks you will access the most up-to-date state of the IP database.

“Post” callbacks are executed asynchronously in separate threads. These threads can work as long as you want them to. Callback threads are joined occasionally, so for a short time there can exist stopped threads.


“Pre” callbacks are synchronous routines, executed before the message gets processed by IPDB. It gives you the way to patch arriving messages, but also places a restriction: until the callback exits, the main event IPDB loop is blocked.

Normally, only “post” callbacks are required. But in some specific cases “pre” also can be useful.


The routine, register_callback(), takes two arguments:
  • callback function
  • mode (optional, default=”post”)

The callback should be a routine, that accepts three arguments:

cb(ipdb, msg, action)

Arguments are:

  • ipdb is a reference to IPDB instance, that invokes

    the callback.

  • msg is a message arrived

  • action is just a msg[‘event’] field

E.g., to work on a new interface, you should catch action == ‘RTM_NEWLINK’ and with the interface index (arrived in msg[‘index’]) get it from IPDB:

index = msg['index']
interface = ipdb.interfaces[index]

Shutdown IPDB instance and sync the state. Since IPDB is asyncronous, some operations continue in the background, e.g. callbacks. So, prior to exit the script, it is required to properly shutdown IPDB.

The shutdown sequence is not forced in an interactive python session, since it is easier for users and there is enough time to sync the state. But for the scripts the release() call is required.