API classes¶
AsyncIPRoute¶
Warning
The project core is currently undergoing refactoring, so some methods may still use the old synchronous API. This will be addressed in future updates.
The main RTNL API class is built on an asyncio core. All methods that send netlink requests are asynchronous and return awaitables. Dump requests return asynchronous generators, while other requests return iterables, such as tuples or lists.
This design choice addresses the fact that RTNL dumps, such as routes or neighbors, can return an extremely large number of objects. Buffering the entire response in memory could lead to performance issues.
import asyncio
from pyroute2 import AsyncIPRoute
async def main():
async with AsyncIPRoute() as ipr:
# create a link: immediate evaluation
await ipr.link("add", ifname="test0", kind="dummy")
# dump links: lazy evaluation
async for link in await ipr.link("dump"):
print(link.get("ifname"))
asyncio.run(main())
lo
eth0
test0
IPRoute¶
This API is designed to be compatible with the old synchronous IPRoute from version 0.8.x and earlier:
from pyroute2 import IPRoute
with IPRoute() as ipr:
for msg in ipr.addr("dump"):
addr = msg.get("address")
mask = msg.get("prefixlen")
print(f"{addr}/{mask}")
127.0.0.1/8
192.168.122.28/24
from pyroute2 import IPRoute
with IPRoute() as ipr:
# this request returns one match, one interface index
eth0 = ipr.link_lookup(ifname="eth0")
assert len(eth0) == 1 # 1 if exists else 0
# this requests uses a lambda to filter interfaces
# and returns all interfaces that are up
nics_up = set(ipr.link_lookup(lambda x: x.get("flags") & 1))
assert len(nics_up) == 2
assert nics_up == {1, 2}
NetNS¶
The NetNS class, prior to version 0.9.1, was used to run the RTNL API in a network namespace. Starting with pyroute2 version 0.9.1, the network namespace functionality has been integrated into the library core. To run an IPRoute or AsyncIPRoute instance in a network namespace, simply use the netns argument:
from pyroute2 import IPRoute
with IPRoute(netns="test") as ipr:
assert ipr.status["netns"] == "test"
After initialization, the netns name is available as .status["netns"].
The old synchronous NetNS class is still available for compatibility but now serves as a wrapper around IPRoute.
from pyroute2 import NetNS
with NetNS("test") as ns:
assert ns.status["netns"] == "test"
NLMSG_ERROR responses¶
Some kernel subsystems return NLMSG_ERROR in response to any request. This is acceptable as long as nlmsg["header"]["error"] is None. If it is not None, an exception will be raised by the parser.
If you receive an NLMSG_ERROR message instead of an exception, it means error == 0, which is equivalent to $? == 0 in bash.
How to work with messages¶
Every netlink message contains a header, fields, and NLAs (netlink attributes). Each NLA is itself a netlink message (see "recursion").
The library parses messages according to this structure. Each RTNL message includes the following:
nlmsg['header'] -- parsed header
nlmsg['attrs'] -- NLA chain (parsed on demand)
data fields, e.g. nlmsg['flags'] etc.
nlmsg.header -- the header fields spec
nlmsg.fields -- the data fields spec
nlmsg.nla_map -- NLA spec
One key feature of the parser is that NLAs are parsed only on demand, i.e., when accessed. This prevents unnecessary CPU usage.
The NLA chain is a list-like structure rather than a dictionary because the netlink standard does not require NLAs to be unique within a single message:
{'attrs': [('IFLA_IFNAME', 'lo'), # [1]
('IFLA_TXQLEN', 1),
('IFLA_OPERSTATE', 'UNKNOWN'),
('IFLA_LINKMODE', 0),
('IFLA_MTU', 65536),
('IFLA_GROUP', 0),
('IFLA_PROMISCUITY', 0),
('IFLA_NUM_TX_QUEUES', 1),
('IFLA_NUM_RX_QUEUES', 1),
('IFLA_CARRIER', 1),
...],
'change': 0,
'event': 'RTM_NEWLINK', # [2]
'family': 0,
'flags': 65609,
'header': {'error': None, # [3]
'flags': 2,
'length': 1180,
'pid': 28233,
'sequence_number': 257, # [4]
'type': 16}, # [5]
'ifi_type': 772,
'index': 1}
# [1] every NLA is parsed upon access
# [2] this field is injected by the RTNL parser
# [3] if not None, an exception will be raised
# [4] more details in the netlink description
# [5] 16 == RTM_NEWLINK
To access fields or NLAs, use the .get() method. To retrieve nested NLAs, pass a tuple of NLA names to the .get() call to navigate through the hierarchy:
from pyroute2 import IPRoute
with IPRoute() as ipr:
lo = tuple(ipr.link("get", index=1))[0]
# get a field
assert lo.get("index") == 1
# get an NLA
assert lo.get("ifname") == "lo"
# get a nested NLA
assert lo.get(("stats64", "rx_bytes")) == 43309665
If an NLA with the specified name is not present in the chain, .get() returns None. To retrieve a list of all NLAs with the specified name, use .get_attrs().
Below is an example demonstrating the usage of .get() and .get_attrs() with an NLA hierarchy:
# for macvlan interfaces there may be several
# IFLA_MACVLAN_MACADDR NLA provided, so use
# get_attrs() to get all the list, not only
# the first one
(msg
.get('IFLA_LINKINFO') # one NLA
.get('IFLA_INFO_DATA') # one NLA
.get_attrs('IFLA_MACVLAN_MACADDR')) # a list of
The protocol itself does not impose a limit on the number of NLAs of the same type within a single message. This is why we cannot represent them as a dictionary, unlike with PF_ROUTE messages.