Modules in progress

There are several modules in the very initial development state, and the help with them will be particularly valuable. You are more than just welcome to help with:

IPSet module

ipset support.

This module is tested with hash:ip, hash:net, list:set and several other ipset structures (like hash:net,iface). There is no guarantee that this module is working with all available ipset modules.

It supports almost all kernel commands (create, destroy, flush, rename, swap, test…)

class pyroute2.ipset.PortRange(begin, end, protocol=None)

A simple container for port range with optional protocol

Note that optional protocol parameter is not supported by all kernel ipset modules using ports. On the other hand, it’s sometimes mandatory to set it (like for hash:net,port ipsets)

Example:

udp_proto = socket.getprotobyname("udp")
port_range = PortRange(1000, 2000, protocol=udp_proto)
ipset.create("foo", stype="hash:net,port")
ipset.add("foo", ("192.0.2.0/24", port_range), etype="net,port")
ipset.test("foo", ("192.0.2.0/24", port_range), etype="net,port")
class pyroute2.ipset.PortEntry(port, protocol=None)

A simple container for port entry with optional protocol

class pyroute2.ipset.IPSet(version=None, attr_revision=None, nfgen_family=2)

NFNetlink socket (family=NETLINK_NETFILTER).

Implements API to the ipset functionality.

headers(name, **kwargs)

Get headers of the named ipset. It can be used to test if one ipset exists, since it returns a no such file or directory.

get_proto_version(version=6)

Get supported protocol version by kernel.

version parameter allow to set mandatory (but unused?) IPSET_ATTR_PROTOCOL netlink attribute in the request.

list(*argv, **kwargs)

List installed ipsets. If name is provided, list the named ipset or return an empty list.

Be warned: netlink does not return an error if given name does not exit, you will receive an empty list.

destroy(name=None)

Destroy one (when name is set) or all ipset (when name is None)

create(name, stype='hash:ip', family=<AddressFamily.AF_INET: 2>, exclusive=True, counters=False, comment=False, maxelem=65536, forceadd=False, hashsize=None, timeout=None, bitmap_ports_range=None, size=None, skbinfo=False)

Create an ipset name of type stype, by default hash:ip.

Common ipset options are supported:

  • exclusive – if set, raise an error if the ipset exists

  • counters – enable data/packets counters

  • comment – enable comments capability

  • maxelem – max size of the ipset

  • forceadd – you should refer to the ipset manpage

  • hashsize – size of the hashtable (if any)

  • timeout – enable and set a default value for entries (if not None)

  • bitmap_ports_range – set the specified inclusive portrange for

    the bitmap ipset structure (0, 65536)

  • size – Size of the list:set, the default is 8

  • skbinfo – enable skbinfo capability

add(name, entry, family=<AddressFamily.AF_INET: 2>, exclusive=True, comment=None, timeout=None, etype='ip', skbmark=None, skbprio=None, skbqueue=None, wildcard=False, **kwargs)

Add a member to the ipset.

etype is the entry type that you add to the ipset. It’s related to the ipset type. For example, use “ip” for one hash:ip or bitmap:ip ipset.

When your ipset store a tuple, like “hash:net,iface”, you must use a comma a separator (etype=”net,iface”)

entry is a string for “ip” and “net” objects. For ipset with several dimensions, you must use a tuple (or a list) of objects.

“port” type is specific, since you can use integer of specialized containers like PortEntry and PortRange

Examples:

ipset = IPSet()
ipset.create("foo", stype="hash:ip")
ipset.add("foo", "198.51.100.1", etype="ip")

ipset = IPSet()
ipset.create("bar", stype="bitmap:port",
             bitmap_ports_range=(1000, 2000))
ipset.add("bar", 1001, etype="port")
ipset.add("bar", PortRange(1500, 2000), etype="port")

ipset = IPSet()
import socket
protocol = socket.getprotobyname("tcp")
ipset.create("foobar", stype="hash:net,port")
port_entry = PortEntry(80, protocol=protocol)
ipset.add("foobar", ("198.51.100.0/24", port_entry),
          etype="net,port")

wildcard option enable kernel wildcard matching on interface name for net,iface entries.

delete(name, entry, family=<AddressFamily.AF_INET: 2>, exclusive=True, etype='ip')

Delete a member from the ipset.

See add() method for more information on etype.

test(name, entry, family=<AddressFamily.AF_INET: 2>, etype='ip')

Test if entry is part of an ipset

See add() method for more information on etype.

swap(set_a, set_b)

Swap two ipsets. They must have compatible content type.

flush(name=None)

Flush all ipsets. When name is set, flush only this ipset.

rename(name_src, name_dst)

Rename the ipset.

get_set_byname(name)

Get a set by its name

get_set_byindex(index)

Get a set by its index

get_supported_revisions(stype, family=<AddressFamily.AF_INET: 2>)

Return minimum and maximum of revisions supported by the kernel.

Each ipset module (like hash:net, hash:ip, etc) has several revisions. Newer revisions often have more features or more performances. Thanks to this call, you can ask the kernel the list of supported revisions.

You can manually set/force revisions used in IPSet constructor.

Example:

ipset = IPSet()
ipset.get_supported_revisions("hash:net")

ipset.get_supported_revisions("hash:net,port,net")

IW module

Experimental wireless module — nl80211 support.

Disclaimer

Unlike IPRoute, which is mostly usable, though is far from complete yet, the IW module is in the very initial state. Neither the module itself, nor the message class cover the nl80211 functionality reasonably enough. So if you’re going to use it, brace yourself — debug is coming.

Messages

nl80211 messages are defined here:

pyroute2/netlink/nl80211/__init__.py

Pls notice NLAs of type hex. On the early development stage hex allows to inspect incoming data as a hex dump and, occasionally, even make requests with such NLAs. But it’s not a production way.

The type hex in the NLA definitions means that this particular NLA is not handled yet properly. If you want to use some NLA which is defined as hex yet, pls find out a specific type, patch the message class and submit your pull request on github.

If you’re not familiar with NLA types, take a look at RTNL definitions:

pyroute2/netlink/rtnl/ndmsg.py

and so on.

Communication with the kernel

There are several methods of the communication with the kernel.

  • sendto() — lowest possible, send a raw binary data

  • put() — send a netlink message

  • nlm_request() — send a message, return the response

  • get() — get a netlink message

  • recv() — get a raw binary data from the kernel

There are no errors on put() usually. Any permission denied, any invalid value errors are returned from the kernel with netlink also. So if you do put(), but don’t do get(), be prepared to miss errors.

The preferred method for the communication is nlm_request(). It tracks the message ID, returns the corresponding response. In the case of errors nlm_request() raises an exception. To get the response on any operation with nl80211, use flag NLM_F_ACK.

Reverse it

If you’re too lazy to read the kernel sources, but still need something not implemented here, you can use reverse engineering on a reference implementation. E.g.:

# strace -e trace=network -f -x -s 4096 \
        iw phy phy0 interface add test type monitor

Will dump all the netlink traffic between the program iw and the kernel. Three first packets are the generic netlink protocol discovery, you can ignore them. All that follows, is the nl80211 traffic:

sendmsg(3, {msg_name(12)={sa_family=AF_NETLINK, ... },
    msg_iov(1)=[{"\x30\x00\x00\x00\x1b\x00\x05 ...", 48}],
    msg_controllen=0, msg_flags=0}, 0) = 48
recvmsg(3, {msg_name(12)={sa_family=AF_NETLINK, ... },
    msg_iov(1)=[{"\x58\x00\x00\x00\x1b\x00\x00 ...", 16384}],
    msg_controllen=0, msg_flags=0}, 0) = 88
...

With -s 4096 you will get the full dump. Then copy the strings from msg_iov to a file, let’s say data, and run the decoder:

$ pwd
/home/user/Projects/pyroute2
$ export PYTHONPATH=`pwd`
$ python scripts/decoder.py pyroute2.netlink.nl80211.nl80211cmd data

You will get the session decoded:

{'attrs': [['NL80211_ATTR_WIPHY', 0],
           ['NL80211_ATTR_IFNAME', 'test'],
           ['NL80211_ATTR_IFTYPE', 6]],
 'cmd': 7,
 'header': {'flags': 5,
            'length': 48,
            'pid': 3292542647,
            'sequence_number': 1430426434,
            'type': 27},
 'reserved': 0,
 'version': 0}
{'attrs': [['NL80211_ATTR_IFINDEX', 23811],
           ['NL80211_ATTR_IFNAME', 'test'],
           ['NL80211_ATTR_WIPHY', 0],
           ['NL80211_ATTR_IFTYPE', 6],
           ['NL80211_ATTR_WDEV', 4],
           ['NL80211_ATTR_MAC', 'a4:4e:31:43:1c:7c'],
           ['NL80211_ATTR_GENERATION', '02:00:00:00']],
 'cmd': 7,
 'header': {'flags': 0,
            'length': 88,
            'pid': 3292542647,
            'sequence_number': 1430426434,
            'type': 27},
 'reserved': 0,
 'version': 1}

Now you know, how to do a request and what you will get as a response. Sample collected data is in the scripts directory.

Submit changes

Please do not hesitate to submit the changes on github. Without your patches this module will not evolve.

class pyroute2.iwutil.IW(*argv, **kwarg)
del_interface(dev)

Delete a virtual interface

  • dev — device index

add_interface(ifname, iftype, dev=None, phy=0)

Create a virtual interface

  • ifname — name of the interface to create

  • iftype — interface type to create

  • dev — device index

  • phy — phy index

One should specify dev (device index) or phy (phy index). If no one specified, phy == 0.

iftype can be integer or string:

  1. adhoc

  2. station

  3. ap

  4. ap_vlan

  5. wds

  6. monitor

  7. mesh_point

  8. p2p_client

  9. p2p_go

  10. p2p_device

  11. ocb

list_dev()

Get list of all wifi network interfaces

list_wiphy()

Get list of all phy devices

get_interfaces_dict()

Get interfaces dictionary

get_interfaces_dump()

Get interfaces dump

get_interface_by_phy(attr)

Get interface by phy ( use x.get_attr(‘NL80211_ATTR_WIPHY’) )

get_interface_by_ifindex(ifindex)

Get interface by ifindex ( use x.get_attr(‘NL80211_ATTR_IFINDEX’)

get_stations(ifindex)

Get stations by ifindex

join_ibss(ifindex, ssid, freq, bssid=None, channel_fixed=False, width=None, center=None, center2=None)
Connect to network by ssid
  • ifindex - IFINDEX of the interface to perform the connection

  • ssid - Service set identification

  • freq - Frequency in MHz

  • bssid - The MAC address of target interface

  • channel_fixed: Boolean flag

  • width - Channel width

  • center - Central frequency of the 40/80/160 MHz channel

  • center2 - Center frequency of second segment if 80P80

If the flag of channel_fixed is True, one should specify both the width and center of the channel

width can be integer of string:

  1. 20_noht

  2. 20

  3. 40

  4. 80

  5. 80p80

  6. 160

  7. 5

  8. 10

leave_ibss(ifindex)

Leave the IBSS – the IBSS is determined by the network interface

authenticate(ifindex, bssid, ssid, freq, auth_type=0)

Send an Authentication management frame.

deauthenticate(ifindex, bssid, reason_code=1)

Send a Deauthentication management frame.

associate(ifindex, bssid, ssid, freq, info_elements=None)

Send an Association request frame.

disassociate(ifindex, bssid, reason_code=3)

Send a Disassociation management frame.

connect(ifindex, ssid, bssid=None)

Connect to the ap with ssid and bssid

disconnect(ifindex)

Disconnect the device

scan(ifindex, ssids=None, flush_cache=False)

Trigger scan and get results.

Triggering scan usually requires root, and can take a couple of seconds.

get_associated_bss(ifindex)

Returns the same info like scan() does, but only about the currently associated BSS.

Unlike scan(), it returns immediately and doesn’t require root.

Network settings daemon – pyrouted

Pyrouted is a standalone project of a system service, that utilizes the pyroute2 library. It consists of a daemon controlled by systemd and a CLI utility that communicates with the daemon via UNIX socket.

It is an extremely simple and basic network interface setup tool.