I want to use python pyroute2 to discover a USB WiFi dongle on Linux and move it into a network namespace.
I can do this with an ethernet interface but I understand that I have to use the PHY to move it with pyroute2 if it is a wifi interface. I checked the docs and do not see an example for how to do this.
Below is what I have so far. How can I use pyroute2 to move my USB WiFi dongle into a network namespace?
# Inspect networking interfaces
import netifaces
# Setup netowrking namespaces
from pyroute2 import NetNS, NSPopen, netns, IPDB
from pyroute2 import IW
from pyroute2 import IPRoute
from pyroute2.netlink import NetlinkError
# Get a list of all the network interfaces
interfaces = netifaces.interfaces()
# Manage interfaces
ipdb = IPDB()
# Loop over all the interfaces and print their details
for iface in interfaces:
iface_details = netifaces.ifaddresses(iface)
if iface.find("wlxc") >= 0:
print(f"Found USB Wi-Fi interface: {iface} with details: {iface_details}")
# Truncate the long name
truncated_name = iface[-8:-1]
# Create a networking namespace
ipdb_netns = IPDB(nl=NetNS(truncated_name))
# Command to run
program_command = ["ifconfig"]
# Get the interface
print(f"Truncated name is: {truncated_name}")
link_iface = ipdb.interfaces[iface]
print(f"Link interface: {link_iface} ...")
# Look for the wireless index
ip = IPRoute()
iw = IW()
index = ip.link_lookup(ifname=iface)[0]
try:
wifi_if = iw.get_interface_by_ifindex(index)
print(f"Wireless interface: {wifi_if}")
with ipdb.interfaces[iface] as if_to_move:
print(f"Moving interface: {if_to_move} to: {truncated_name}")
if_to_move.net_ns_fd = truncated_name
print(f"Done moving to: {truncated_name} ...")
except NetlinkError as e:
if e.code == 19: # 19 'No such device'
print("not a wireless interface")
finally:
iw.close()
ip.close()
Error:
Found USB Wi-Fi interface: wlxc87f548d9633 with details: {17: [{'addr': 'c8:7f:54:8d:96:33', 'broadcast': 'ff:ff:ff:ff:ff:ff'}]}
Truncated name is: 548d963
Link interface: {'address': 'c8:7f:54:8d:96:33', 'broadcast': 'ff:ff:ff:ff:ff:ff', 'ifname': 'wlxc87f548d9633', 'mtu': 1500, 'qdisc': 'noqueue', 'txqlen': 1000, 'operstate': 'DOWN', 'linkmode': 1, 'group': 0, 'promiscuity': 0, 'num_tx_queues': 1, 'num_rx_queues': 1, 'carrier': 0, 'carrier_changes': 1, 'proto_down': 0, 'gso_max_segs': 65535, 'gso_max_size': 65536, 'xdp': {'attrs': [('IFLA_XDP_ATTACHED', None)]}, 'carrier_up_count': 0, 'carrier_down_count': 1, 'min_mtu': 256, 'max_mtu': 2304, 'perm_address': 'c8:7f:54:8d:96:33', 'parent_dev_name': '3-3:1.0', 'parent_dev_bus_name': 'usb', 'index': 10, 'flags': 4099, 'ipdb_scope': 'system', 'ipdb_priority': 0, 'vlans': (), 'ipaddr': (), 'ports': (), 'family': 0, 'ifi_type': 1, 'state': 'up', 'neighbours': ()} ...
Wireless interface: ({'cmd': 7, 'version': 1, 'reserved': 0, 'attrs': [('NL80211_ATTR_IFINDEX', 10), ('NL80211_ATTR_IFNAME', 'wlxc87f548d9633'), ('NL80211_ATTR_WIPHY', 1), ('NL80211_ATTR_IFTYPE', 2), ('NL80211_ATTR_WDEV', 4294967297), ('NL80211_ATTR_MAC', 'c8:7f:54:8d:96:33'), ('NL80211_ATTR_GENERATION', 17), ('NL80211_ATTR_4ADDR', '00'), ('NL80211_ATTR_WIPHY_TX_POWER_LEVEL', 1600), ('NL80211_ATTR_TXQ_STATS', '08:00:01:00:00:00:00:00:08:00:02:00:00:00:00:00:08:00:03:00:00:00:00:00:08:00:04:00:00:00:00:00:08:00:05:00:00:00:00:00:08:00:06:00:00:00:00:00:08:00:08:00:00:00:00:00:08:00:09:00:00:00:00:00:08:00:0a:00:00:00:00:00')], 'header': {'length': 188, 'type': 34, 'flags': 0, 'sequence_number': 255, 'pid': 14840, 'error': None, 'target': 'localhost', 'stats': Stats(qsize=0, delta=0, delay=0)}, 'event': 'NL80211_CMD_NEW_INTERFACE'},)
Moving interface: {'address': 'c8:7f:54:8d:96:33', 'broadcast': 'ff:ff:ff:ff:ff:ff', 'ifname': 'wlxc87f548d9633', 'mtu': 1500, 'qdisc': 'noqueue', 'txqlen': 1000, 'operstate': 'DOWN', 'linkmode': 1, 'group': 0, 'promiscuity': 0, 'num_tx_queues': 1, 'num_rx_queues': 1, 'carrier': 0, 'carrier_changes': 1, 'proto_down': 0, 'gso_max_segs': 65535, 'gso_max_size': 65536, 'xdp': {'attrs': [('IFLA_XDP_ATTACHED', None)]}, 'carrier_up_count': 0, 'carrier_down_count': 1, 'min_mtu': 256, 'max_mtu': 2304, 'perm_address': 'c8:7f:54:8d:96:33', 'parent_dev_name': '3-3:1.0', 'parent_dev_bus_name': 'usb', 'index': 10, 'flags': 4099, 'ipdb_scope': 'system', 'ipdb_priority': 0, 'vlans': (), 'ipaddr': (), 'ports': (), 'family': 0, 'ifi_type': 1, 'state': 'up', 'neighbours': ()} to: 548d963
fail <class 'NoneType'>
fail <class 'NoneType'>
Updated code with suggested answer:
#!/usr/bin/python3
# Configure logging
import logging
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO, format="%(asctime)s:%(levelname)s:%(message)s")
# Inspect networking interfaces
import netifaces
# Setup netowrking namespaces
from pyroute2 import NetNS, NSPopen, netns, IPDB, IPRoute
# Get a list of all the network interfaces
interfaces = netifaces.interfaces()
# Managing network interfaces
with IPDB() as ipdb:
# Loop over all the interfaces and logger.info their details
for iface in interfaces:
# Determine interface information
iface_details = netifaces.ifaddresses(iface)
if iface.find("wlxc") >= 0:
logger.info(f"Found USB Wi-Fi interface: {iface} with details: {iface_details}")
# Attempt to remove a networking namespace by the same name
iface_truncate = iface[-4:]
net_namespace = f"{iface_truncate}-ns"
try:
netns.remove(net_namespace)
except Exception as err:
logger.warning(f"Namespace does not exist and we could not remove it: {net_namespace} ...")
# Create a new network namespace or identify an existing one
logger.info(f"Trying to create network namespace: {net_namespace} ...")
netns.create(net_namespace)
# Get the interface index
with IPRoute() as ip:
index = ip.link_lookup(ifname=iface)[0]
logger.info(f"Index of USB WiFi is: {index} ...")
logger.info(f"Interfaces: {ipdb.interfaces[iface]} ...")
# Move the interface to the new network namespace
with ipdb.interfaces[index] as interf:
interf.net_ns_fd = net_namespace
logger.info(f"Moved {iface} to {net_namespace}")
logger.info("Done moving interface ...")
# Close IPDB
ipdb.release()
Traceback with latest code:
2023-11-13 21:05:36,676:WARNING:Deprecation warning https://docs.pyroute2.org/ipdb_toc.html
2023-11-13 21:05:36,676:WARNING:To remove this DeprecationWarning exception, start IPDB(deprecation_warning=False, ...)
2023-11-13 21:05:36,678:INFO:Found USB Wi-Fi interface: wlxc87f548d9633 with details: {17: [{'addr': 'c8:7f:54:8d:96:33', 'broadcast': 'ff:ff:ff:ff:ff:ff'}]}
2023-11-13 21:05:36,680:INFO:Trying to create network namespace: 9633-ns ...
2023-11-13 21:05:36,686:INFO:Index of USB WiFi is: 10 ...
2023-11-13 21:05:36,700:INFO:Interfaces: {'address': 'c8:7f:54:8d:96:33', 'broadcast': 'ff:ff:ff:ff:ff:ff', 'ifname': 'wlxc87f548d9633', 'mtu': 1500, 'qdisc': 'noqueue', 'txqlen': 1000, 'operstate': 'DOWN', 'linkmode': 1, 'group': 0, 'promiscuity': 0, 'num_tx_queues': 1, 'num_rx_queues': 1, 'carrier': 0, 'carrier_changes': 1, 'proto_down': 0, 'gso_max_segs': 65535, 'gso_max_size': 65536, 'xdp': {'attrs': [('IFLA_XDP_ATTACHED', None)]}, 'carrier_up_count': 0, 'carrier_down_count': 1, 'min_mtu': 256, 'max_mtu': 2304, 'perm_address': 'c8:7f:54:8d:96:33', 'parent_dev_name': '3-3:1.0', 'parent_dev_bus_name': 'usb', 'index': 10, 'flags': 4099, 'ipdb_scope': 'system', 'ipdb_priority': 0, 'vlans': (), 'ipaddr': (), 'ports': (), 'family': 0, 'ifi_type': 1, 'state': 'up', 'neighbours': ()} ...
2023-11-13 21:05:36,700:INFO:Moved wlxc87f548d9633 to 9633-ns
fail <class 'NoneType'>
fail <class 'NoneType'>
Traceback (most recent call last):
File "/home/user/Desktop/network-stress-test/network-stress-test.py", line 52, in <module>
with ipdb.interfaces[index] as interf:
File "/usr/local/lib/python3.10/dist-packages/pyroute2/ipdb/transactional.py", line 206, in __exit__
self.commit()
File "/usr/local/lib/python3.10/dist-packages/pyroute2/ipdb/interfaces.py", line 1136, in commit
raise error
File "/usr/local/lib/python3.10/dist-packages/pyroute2/ipdb/interfaces.py", line 1036, in commit
run(nl.link, 'update', **request)
File "/usr/local/lib/python3.10/dist-packages/pyroute2/ipdb/interfaces.py", line 515, in _run
raise error
File "/usr/local/lib/python3.10/dist-packages/pyroute2/ipdb/interfaces.py", line 510, in _run
return cmd(*argv, **kwarg)
File "/usr/local/lib/python3.10/dist-packages/pyroute2/iproute/linux.py", line 1672, in link
ret = self.nlm_request(msg, msg_type=msg_type, msg_flags=msg_flags)
File "/usr/local/lib/python3.10/dist-packages/pyroute2/netlink/nlsocket.py", line 870, in nlm_request
return tuple(self._genlm_request(*argv, **kwarg))
File "/usr/local/lib/python3.10/dist-packages/pyroute2/netlink/nlsocket.py", line 1214, in nlm_request
for msg in self.get(
File "/usr/local/lib/python3.10/dist-packages/pyroute2/netlink/nlsocket.py", line 873, in get
return tuple(self._genlm_get(*argv, **kwarg))
File "/usr/local/lib/python3.10/dist-packages/pyroute2/netlink/nlsocket.py", line 550, in get
raise msg['header']['error']
pyroute2.netlink.exceptions.NetlinkError: (22, 'Invalid argument')
You are creating a new network namespace using
IPDB(nl=NetNS(truncated_name)), and then try to move the interface to the new namespace using thewith ipdb.interfaces[iface] as if_to_move:context manager, which does not seem to work.Using
if_to_move.net_ns_fd = truncated_name, assuming this would move the interface to the new namespace, might work for Ethernet interfaces. But WiFi interfaces require a different handling due to their association with a wireless PHYs (physical layer entities).You should first identify the WiFi interface using its
ifnameor index, which you did (see, for instance, "How to get interface names from kernel using pyroute2 IPDB function?", but also the IPRoute module).Then create a new network namespace or identify an existing one. And move the WiFi interface to the new network namespace.
That should move your USB WiFi dongle into the specified network namespace.
Instead of creating a new namespace as part of the IPDB instance, I use
netns.create(net_namespace)to explicitly create or identify the namespace. That approach is clearer and separates the concerns of namespace creation and interface manipulation.Then, I use
with IPDB() as ipdb:to manage the interfaces andwith ipdb.interfaces[iface] as i:to handle the interface to be moved (from "Move an interface to a namespace").The
NetlinkError: (22, 'Invalid argument')error in your updated code indicates that an invalid argument is being passed during the operation of moving the interface to the new network namespace.In your code, you are trying to move the interface using its index
with ipdb.interfaces[index] as interf:. However, Pyroute2 typically expects the interface name rather than the index for such operations. Try and use the interface name instead of the index when moving it to the namespace:That would be:
Also, make sure the namespace you are trying to move the interface to exists and is valid: after creating the namespace, do verify its existence or handle potential exceptions related to namespace creation more precisely.
Before attempting to create a new namespace, the code checks if it already exists using
netns.listnetns(). This prevents errors related to trying to create a namespace that's already there.This includes try-except blocks around namespace operations to handle any exceptions that might occur during the creation or verification of the namespace. And some more logging statements, for better tracking of the process, especially regarding namespace operations.