.. parser:
.. raw:: html
Netlink parser data flow
========================
NetlinkSocketBase: receive the data
-----------------------------------
When `NetlinkSocketBase` receives the data from a netlink socket, it can do it
in two ways:
1. get data directly with `socket.recv()` or `socket.recv_into()`
2. run a buffer thread that receives the data asap and leaves in the
`buffer_queue` to be consumed later by `recv()` or `recv_into()`
`NetlinkSocketBase` implements these two receive methods, that choose
the data source -- directly from the socket or from `buffer_queue` --
depending on the `buffer_thread` property:
**pyroute2.netlink.nlsocket.NetlinkSocketBase**
.. code-include:: :func:`pyroute2.netlink.nlsocket.NetlinkSocketBase.recv`
:language: python
.. code-include:: :func:`pyroute2.netlink.nlsocket.NetlinkSocketBase.recv_into`
:language: python
.. code-include:: :func:`pyroute2.netlink.nlsocket.NetlinkSocketBase.buffer_thread_routine`
:language: python
.. aafig::
:scale: 80
:textual:
` `
data flow struct marshal
+--------+ +--------+ +------------+
| |--->| bits | | |
| | | 32 |--->| length | 4 bytes, offset 0
| | +--------+ +------------+
| | | 16 |--->| type-> key | 2 bytes, offset 4
| | +--------+ +------------+
| | | 16 | | flags | 2 bytes, offset 6
| | +--------+ +------------+
| | | | | `sequence` |
| | | 32 |--->| `number` | 4 bytes, offset 8
| | +--------+ +------------+
| | | |
| | | 32 | pid (ignored by marshal)
| | +--------+
| | | |
| | | | payload (ignored by marshal)
| | | | \ /
+--------+ +--------+ ---+--------------------
|
|
| / `marshal.msg_map = {`
| |
| | key-> parser,
| |
+--------+ key-> parser,
| |
| | key-> parser,
| |
| \ `}`
|
v
Marshal: get and run parsers
----------------------------
Marshal should choose a proper parser depending on the `key`, `flags` and
`sequence_number`. By default it uses only `nlmsg->type` as the `key` and
`nlmsg->flags`, and there are several ways to customize getting parsers.
1. Use custom `key_format`, `key_offset` and `key_mask`. The latter is used
to partially match the key, while `key_format` and `key_offset` are used
to `struct.unpack()` the key from the raw netlink data.
2. You can overload `Marshal.get_parser()` and implement your own way to
get parsers. A parser should be a simple function that gets only
`data`, `offset` and `length` as arguments, and returns one dict compatible
message.
.. aafig::
:scale: 80
:textual:
` `
|
|
|
|
|
v
`if marshal.key_format is not None:`
`marshal.key_format`\
|
`marshal.key_offset` +-- custom key
|
`marshal.key_mask` /
`parser = marshal.get_parser(key, flags, sequence_number)`
`msg = parser(data, offset, length)`
|
|
|
|
|
v
**pyroute2.netlink.nlsocket.Marshal**
.. code-include:: :func:`pyroute2.netlink.nlsocket.Marshal.parse`
:language: python
The message parser routine must accept `data, offset, length` as the
arguments, and must return a valid `nlmsg` or `dict`, with the mandatory
fields, see the spec below. The parser can also return `None` which tells
the marshal to skip this message. The parser must parse data for one
message.
Mandatory message fields, expected by NetlinkSocketBase methods:
.. code-block:: python
{
'header': {
'type': int,
'flags': int,
'error': None or NetlinkError(),
'sequence_number': int,
}
}
.. aafig::
:scale: 80
:textual:
` `
|
|
|
v
parsed msg
+-------------------------------------------+
| header |
| `{` |
| `uint32 length,` |
| `uint16 type,` |
| `uint16 flags,` |
| `uint32 sequence_number,` |
| `uint32 pid,` |
| `}` |
+- - - - - - - - - - - - - - - - - - - - - -+
| data fields (optional) |
| `{` |
| `int field,` |
| `int field,` |
| `}` |
| or |
| `string field` |
| |
+- - - - - - - - - - - - - - - - - - - - - -+
| nla chain |
| |
| +-------------------------------+ |
| | header | |
| | `{` | |
| | `uint16 length,` | |
| | `uint16 type,` | |
| | `}` | |
| +- - - - - - - - - - - - - - - -+ |
| | data fields (optional) | |
| | | |
| | ... | |
| | | |
| +- - - - - - - - - - - - - - - -+ |
| | nla chain | |
| | | |
| | recursive | |
| | | |
| +-------------------------------+ |
| |
+-------------------------------------------+
Per-request parsers
-------------------
Sometimes it may be reasonable to handle a particular response with a
spcific parser, rather than a generic one. An example is
`IPRoute.get_default_routes()`, which could be slow on systems with
huge amounts of routes.
Instead of parsing every route record as `rtmsg`, this method assigns
a specific parser to its request. The custom parser doesn't parse records
blindly, but looks up only for default route records in the dump, and
then parses only matched records with the standard routine:
**pyroute2.iproute.linux.IPRoute**
.. code-include:: :func:`pyroute2.iproute.linux.RTNL_API.get_default_routes`
:language: python
**pyroute2.iproute.parsers**
.. code-include:: :func:`pyroute2.iproute.parsers.default_routes`
:language: python
To assign a custom parser to a request/response communication, you should
know first `sequence_number`, be it allocated dynamically with
`NetlinkSocketBase.addr_pool.alloc()` or assigned statically. Then you
can create a record in `NetlinkSocketBase.seq_map`:
.. code-block:: python
#
def my_parser(data, offset, length):
...
return parsed_message
msg_seq = nlsocket.addr_pool.alloc()
msg = nlmsg()
msg['header'] = {
'type': my_type,
'flags': NLM_F_REQUEST | NLM_F_ACK,
'sequence_number': msg_seq,
}
msg['data'] = my_data
msg.encode()
nlsocket.seq_map[msg_seq] = my_parser
nlsocket.sendto(msg.data, (0, 0))
for reponse_message in nlsocket.get(msg_seq=msg_seq):
handle(response_message)
NetlinkSocketBase: pick correct messages
----------------------------------------
The netlink protocol is asynchronous, so responses to several requests may
come simultaneously. Also the kernel may send broadcast messages that are
not responses, and have `sequence_number == 0`. As the response *may* contain
multiple messages, and *may* or *may not* be terminated by some specific type
of message, the task of returning relevant messages from the flow is a bit
complicated.
Let's look at an example:
.. aafig::
:scale: 80
:textual:
+-----------+ +-----------+
| program | | kernel |
+-----+-----+ +-----+-----+
| |
| |
| | random broadcast
|<---------------|
| |
| |
request seq 1 X |
X--------------->X
X X
X X
X X random broadcast
X<---------------X
X X
X X
X X `response seq 1`
X<---------------X `flags: NLM_F_MULTI`
X X
X X
X X random broadcast
X<---------------X
X X
X X
X X `response seq 1`
X<---------------X `type: NLMSG_DONE`
X |
| |
v v
The message flow on the diagram features `sequence_number == 0` broadcasts and
`sequence_number == 1` request and response packets. To complicate it even
further you can run a request with `sequence_number == 2` before the final
response with `sequence_number == 1` comes.
To handle that, `NetlinkSocketBase.get()` buffers all the irrelevant messages,
returns ones with only the requested `sequence_number`, and uses locks to wait
on the resource.
The current implementation is relatively complicated and will be changed in
the future.
**pyroute2.netlink.nlsocket.NetlinkSocketBase**
.. code-include:: :func:`pyroute2.netlink.nlsocket.NetlinkSocketBase.get`
:language: python