IPRoute module

iproute module provides low-level API to RTNetlink protocol via IPRoute and IPRSocket classes as well as all required constants.

iproute quickstart

pyroute2.IPRoute in two words:

$ sudo pip install pyroute2
$ cat >example.py <<EOF
> from pyroute2 import IPRoute
> ip = IPRoute()
> print([x.get_attr('IFLA_IFNAME') for x in ip.get_links()])
$ python example.py
['lo', 'p6p1', 'wlan0', 'virbr0', 'virbr0-nic']

threaded vs. threadless architecture

Please note, that objects of IPRoute class implicitly starts several threads:

  • I/O Loop – main thread that performs all Netlink I/O and clusterization
  • Main thread – thread that reassembles messages and parses them into dict-like structures
  • Cache thread – IPRoute objects can be connected together, and in this case header masquerading should be performed on netlink packets; the thread performs masquerade cache expiration

In most cases it should be ok, IPRoute uses no daemonic threads and explicit release() call is provided to stop all the threads. Beside of that, the architecture provides packet buffering.

But if you do not like implicit threads, you can use simplest threadless RTNetlink interface, IPRSocket.


class pyroute2.netlink.iproute.IPRSocket

The simplest class, that connects together the netlink parser and a generic Python socket implementation. Provides method get() to receive the next message from netlink socket and parse it. It is just simple socket-like class, it implements no buffering or like that. It spawns no additional threads, leaving this up to developers.

Please note, that netlink is an asynchronous protocol with non-guaranteed delivery. You should be fast enough to get all the messages in time. If the message flow rate is higher than the speed you parse them with, exceeding messages will be dropped.


Threadless RT netlink monitoring with blocking I/O calls:

>>> from pyroute2.netlink.iproute import IPRSocket
>>> from pprint import pprint
>>> s = IPRSocket()
>>> s.bind()
>>> pprint(s.get())
[{'attrs': [('RTA_TABLE', 254),
            ('RTA_DST', '2a00:1450:4009:808::1002'),
            ('RTA_GATEWAY', 'fe80:52:0:2282::1fe'),
            ('RTA_OIF', 2),
            ('RTA_PRIORITY', 0),
            ('RTA_CACHEINFO', {'rta_clntref': 0,
                               'rta_error': 0,
                               'rta_expires': 0,
                               'rta_id': 0,
                               'rta_lastuse': 5926,
                               'rta_ts': 0,
                               'rta_tsage': 0,
                               'rta_used': 1})],
  'dst_len': 128,
  'event': 'RTM_DELROUTE',
  'family': 10,
  'flags': 512,
  'header': {'error': None,
             'flags': 0,
             'length': 128,
             'pid': 0,
             'sequence_number': 0,
             'type': 25},
  'proto': 9,
  'scope': 0,
  'src_len': 0,
  'table': 254,
  'tos': 0,
  'type': 1}]

It is required to call IPRSocket.bind() after creation. The call subscribes the NetlinkSocket to default RTNL groups (RTNL_GROUPS) or to a requested group set.

class pyroute2.netlink.iproute.IPRoute(debug=False, timeout=3, do_connect=True, host=None, key=None, cert=None, ca=None, addr=None, fork=False)

You can think of this class in some way as of plain old iproute2 utility.

It is an old-style library, that provides access to rtnetlink as is. It helps you to retrieve and change almost all the data, available through rtnetlink:

from pyroute2 import IPRoute
ipr = IPRoute()
    # lookup interface by name
dev = ipr.link_lookup(ifname='tap0')[0]
    # bring it down
ipr.link('set', dev, state='down')
    # change interface MAC address and rename it
ipr.link('set', dev, address='00:11:22:33:44:55', ifname='vpn')
    # add primary IP address
ipr.addr('add', dev, address='', mask=24)
    # add secondary IP address
ipr.addr('add', dev, address='', mask=24)
    # bring it up
ipr.link('set', dev, state='up')


IPRoute objects allows not only simple monitoring or querying of RT netlink, but also clusterization of IPRoute instances. Simple local sample:

>>> from pyroute2 import IPRoute
>>> from pprint import pprint
>>> ip = IPRoute()
>>> ip.monitor()
>>> pprint(ip.get())
[{'attrs': [('RTA_TABLE', 255),
            ('RTA_DST', 'ff02::1:2'),
            ('RTA_OIF', 3),
            ('RTA_PRIORITY', 0),
            ('RTA_CACHEINFO', {'rta_clntref': 1,
                               'rta_error': 0,
                               'rta_expires': 0,
                               'rta_id': 0,
                               'rta_lastuse': 0,
                               'rta_ts': 0,
                               'rta_tsage': 0,
                               'rta_used': 0})],
  'dst_len': 128,
  'event': 'RTM_NEWROUTE',
  'family': 10,
  'flags': 512,
  'header': {'error': None,
             'flags': 0,
             'host': 'netlink://16',
             'length': 108,
             'pid': 0,
             'sequence_number': 0,
             'type': 24},
  'proto': 0,
  'scope': 0,
  'src_len': 0,
  'table': 255,
  'tos': 0,
  'type': 1}]

IPRoute objects have many methods to get the information about Linux network objects:

>>> pprint(ip.get_routes()[0])
{'attrs': [('RTA_TABLE', 254),
           ('RTA_GATEWAY', ''),
           ('RTA_OIF', 2)],
 'dst_len': 0,
 'event': 'RTM_NEWROUTE',
 'family': 2,
 'flags': 0,
 'proto': 4,
 'scope': 0,
 'src_len': 0,
 'table': 254,
 'tos': 0,
 'type': 1}
>>> pprint(ip.get_neighbors()[0])
{'attrs': [('NDA_DST', 'ff02::2'),
           ('NDA_LLADDR', '33:33:00:00:00:02'),
           ('NDA_PROBES', 0),
           ('NDA_CACHEINFO', {'ndm_confirmed': 309550224,
                              'ndm_refcnt': 0,
                              'ndm_updated': 309544224,
                              'ndm_used': 309544224})],
 'event': 'RTM_NEWNEIGH',
 'family': 10,
 'flags': 0,
 'ifindex': 33554432,
 'ndm_type': 64,
 'state': 0}

But IPRoute objects start additional threads to implement transparent authentication, message reassembling and so on. Sometimes it can become an overkill for simple projects, in these cases consider usage of IPRSocket.

addr(command, index, address, mask=24, family=None, scope=0)

Address operations

  • command – add, delete
  • index – device index
  • address – IPv4 or IPv6 address
  • mask – address mask
  • family – socket.AF_INET for IPv4 or socket.AF_INET6 for IPv6
  • scope – the address scope, see /etc/iproute2/rt_scopes


index = 62
ip.addr("add", index, address="", mask=24)
ip.addr("add", index, address="", mask=24)
flush_routes(*argv, **kwarg)

Flush routes – purge route records from a table. Arguments are the same as for get_routes() routine. Actually, this routine implements a pipe from get_routes() to nlm_request().


Get all addresses.


Get classes for specified interface.

get_default_routes(family=0, table=254)

Get default routes

get_filters(index=0, handle=0, parent=0)

Get filters for specified interface, handle and parent.

Get network interfaces.

By default returns all interfaces. Arguments vector can contain interface indices or a special keyword ‘all’:

ip.get_links(1, 2, 3)

interfaces = [1, 2, 3]

Retrieve ARP cache records.


Get all queue disciplines for all interfaces or for specified one.

get_routes(family=0, **kwarg)

Get all routes. You can specify the table. There are 255 routing classes (tables), and the kernel returns all the routes on each request. So the routine filters routes from full output.


ip.get_routes()  # get all the routes for all families
ip.get_routes(family=AF_INET6)  # get only IPv6 routes
ip.get_routes(table=254)  # get routes from 254 table

Get all rules. You can specify inet family, by default return rules for all families.

ip.get_rules() # get all the rules for all families ip.get_routes(family=AF_INET6) # get only IPv6 rules

Link operations.

  • command – set, add or delete
  • index – device index
  • **kwarg – keywords, NLA


x = 62  # interface index
ip.link("set", index=x, state="down")
ip.link("set", index=x, address="00:11:22:33:44:55", name="bala")
ip.link("set", index=x, mtu=1000, txqlen=2000)
ip.link("set", index=x, state="up")

Keywords “state”, “flags” and “mask” are reserved. State can be “up” or “down”, it is a shortcut:

state="up":   flags=1, mask=1
state="down": flags=0, mask=0

For more flags grep IFF in the kernel code, until we write human-readable flag resolver.

Other keywords are from ifinfmsg.nla_map, look into the corresponding module. You can use the form “ifname” as well as “IFLA_IFNAME” and so on, so that’s equal:

ip.link("set", index=x, mtu=1000)
ip.link("set", index=x, IFLA_MTU=1000)

You can also delete interface with:

ip.link("delete", index=x)

Switch an interface down unconditilnally.

Lookup interface index (indeces) by first level NLA value.



Please note, that link_lookup() returns list, not one value.

Remove an interface

Rename an interface. Please note, that the interface must be in the DOWN state in order to be renamed, otherwise you will get an error.

Switch an interface up unconditionally.

route(command, rtype='RTN_UNICAST', rtproto='RTPROT_STATIC', rtscope='RT_SCOPE_UNIVERSE', **kwarg)

Route operations

  • command – add, delete
  • prefix – route prefix
  • mask – route prefix mask
  • rtype – route type (default: “RTN_UNICAST”)
  • rtproto – routing protocol (default: “RTPROT_STATIC”)
  • rtscope – routing scope (default: “RT_SCOPE_UNIVERSE”)
  • index – via device index
  • family – socket.AF_INET (default) or socket.AF_INET6

pyroute2/netlink/rtnl/rtmsg.py rtmsg.nla_map:

  • table – routing table to use (default: 254)
  • gateway – via address
  • prefsrc – preferred source IP address



ip.route("add", dst="", mask=24, gateway="")
rule(command, table, priority=32000, rtype='RTN_UNICAST', rtscope='RT_SCOPE_UNIVERSE', family=2, src=None, src_len=None, dst=None, dst_len=None, fwmark=None)

Rule operations

  • command - add, delete

  • table - 0 < table id < 253

  • priority - 0 < rule’s priority < 32766

  • rtype - type of rule, default ‘RTN_UNICAST’

  • rtscope - routing scope, default RT_SCOPE_UNIVERSE


  • family - rule’s family (socket.AF_INET (default) or


  • src - IP source for Source Based (Policy Based) routing’s rule

  • dst - IP for Destination Based (Policy Based) routing’s rule

  • src_len - Mask for Source Based (Policy Based) routing’s rule

  • dst_len - Mask for Destination Based (Policy Based) routing’s rule

ip.rule(‘add’, 10, 32000)
Will create::
#ip ru sh ... 32000: from all lookup 10 ....
iproute.rule(‘add’, 11, 32001, ‘RTN_UNREACHABLE’)
Will create::
#ip ru sh ... 32001: from all lookup 11 unreachable ....
iproute.rule(‘add’, 14, 32004, src=‘’)
Will create::
#ip ru sh ... 32004: from lookup 14 ...
iproute.rule(‘add’, 15, 32005, dst=‘’, dst_len=24)
Will create::
#ip ru sh ... 32005: from lookup 15 ...
iproute.rule(‘add’, 15, 32006, dst=‘’, fwmark=10)
Will create::
#ip ru sh ... 32006: from fwmark 0xa lookup 15 ...
tc(command, kind, index, handle=0, **kwarg)

“Swiss knife” for traffic control. With the method you can add, delete or modify qdiscs, classes and filters.

  • command – add or delete qdisc, class, filter.
  • kind – a string identifier – “sfq”, “htb”, “u32” and so on.
  • handle – integer or string

Command can be one of (“add”, “del”, “add-class”, “del-class”, “add-filter”, “del-filter”) (see commands dict in the code).

Handle notice: traditional iproute2 notation, like “1:0”, actually represents two parts in one four-bytes integer:

1:0    ->    0x10000
1:1    ->    0x10001
ff:0   ->   0xff0000
ffff:1 -> 0xffff0001

For pyroute2 tc() you can use both forms: integer like 0xffff0000 or string like ‘ffff:0000’. By default, handle is 0, so you can add simple classless queues w/o need to specify handle. Ingress queue causes handle to be 0xffff0000.

So, to set up sfq queue on interface 1, the function call will be like that:

ip = IPRoute()
ip.tc("add", "sfq", 1)

Instead of string commands (“add”, “del”...), you can use also module constants, RTM_NEWQDISC, RTM_DELQDISC and so on:

ip = IPRoute()
ip.tc(RTM_NEWQDISC, "sfq", 1)

More complex example with htb qdisc, lets assume eth0 == 2:

#          u32 -->    +--> htb 1:10 --> sfq 10:0
#          |          |
#          |          |
# eth0 -- htb 1:0 -- htb 1:1
#          |          |
#          |          |
#          u32 -->    +--> htb 1:20 --> sfq 20:0

eth0 = 2
# add root queue 1:0
ip.tc("add", "htb", eth0, 0x10000, default=0x200000)

# root class 1:1
ip.tc("add-class", "htb", eth0, 0x10001,
      burst=1024 * 6)

# two branches: 1:10 and 1:20
ip.tc("add-class", "htb", eth0, 0x10010,
      burst=1024 * 6,
ip.tc("add-class", "htb", eht0, 0x10020,
      burst=1024 * 6,

# two leaves: 10:0 and 20:0
ip.tc("add", "sfq", eth0, 0x100000,
ip.tc("add", "sfq", eth0, 0x200000,

# two filters: one to load packets into 1:10 and the
# second to 1:20
ip.tc("add-filter", "u32", eth0,
      keys=["0x0006/0x00ff+8", "0x0000/0xffc0+2"])
ip.tc("add-filter", "u32", eth0,
      keys=["0x5/0xf+0", "0x10/0xff+33"])

Table Of Contents

Previous topic

Generic netlink protocol

Next topic

IPDB module

This Page