blob: 268c03f71f760852d68b3d3e50005676882929a6 [file] [log] [blame]
#!/usr/bin/python
"""Partial Python implementation of iproute functionality."""
import os
import socket
import struct
### Base netlink constants. See include/uapi/linux/netlink.h.
NETLINK_ROUTE = 0
# Request constants.
NLM_F_REQUEST = 1
NLM_F_ACK = 4
NLM_F_EXCL = 0x200
NLM_F_CREATE = 0x400
# Message types.
NLMSG_ERROR = 2
# Data structure formats.
STRUCT_NLMSGHDR = "=LHHLL"
STRUCT_NLMSGERR = "=i"
STRUCT_NLATTR = "=HH"
### rtnetlink constants. See include/uapi/linux/rtnetlink.h.
# Message types.
RTM_NEWRULE = 32
RTM_DELRULE = 33
# Routing message type values (rtm_type).
RTN_UNSPEC = 0
RTN_UNICAST = 1
# Routing protocol values (rtm_protocol).
RTPROT_STATIC = 4
# Route scope values (rtm_scope).
RT_SCOPE_UNIVERSE = 0
# Data structure formats.
STRUCT_RTMSG = "=BBBBBBBBI"
### FIB rule constants. See include/uapi/linux/fib_rules.h.
FRA_PRIORITY = 6
FRA_FWMARK = 10
FRA_TABLE = 15
def Unpack(fmt, data):
"""Unpacks a data structure with variable-size contents at the end."""
size = struct.calcsize(fmt)
data, remainder = data[:size], data[size:]
return struct.unpack(fmt, data), remainder
class IPRoute(object):
"""Provides a tiny subset of iproute functionality."""
BUFSIZE = 1024
def _NlAttrU32(self, nla_type, value):
data = struct.pack("=I", value)
nla_len = struct.calcsize(STRUCT_NLATTR) + len(data)
return struct.pack(STRUCT_NLATTR, nla_len, nla_type) + data
def __init__(self):
# Global sequence number.
self.seq = 0
self.sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW,
socket.NETLINK_ROUTE)
self.sock.connect((0, 0)) # The kernel.
self.pid = self.sock.getsockname()[1]
def _Send(self, msg):
self.seq += 1
self.sock.send(msg)
def _Recv(self):
return self.sock.recv(self.BUFSIZE)
def _Rule(self, version, is_add, table, match_nlattr, priority):
"""Python equivalent of "ip rule <add|del> <match_cond> lookup <table>".
Args:
version: An integer, 4 or 6.
is_add: True to add a rule, False to delete it.
table: The table to add/delete the rule from.
match_nlattr: A blob of struct nlattrs that express the match condition.
priority: An integer, the priority.
Raises:
IOError: If the netlink request returns an error.
ValueError: If the kernel's response could not be parsed.
"""
self.seq += 1
# Create a struct rtmsg specifying the table and the given match attributes.
family = {4: socket.AF_INET, 6: socket.AF_INET6}[version]
rtmsg = struct.pack(STRUCT_RTMSG, family, 0, 0, 0, 0,
RTPROT_STATIC, RT_SCOPE_UNIVERSE, RTN_UNICAST, 0)
rtmsg += self._NlAttrU32(FRA_PRIORITY, priority)
rtmsg += match_nlattr
rtmsg += self._NlAttrU32(FRA_TABLE, table)
# Create a netlink request containing the rtmsg.
command = RTM_NEWRULE if is_add else RTM_DELRULE
flags = NLM_F_REQUEST | NLM_F_ACK
if is_add:
flags |= (NLM_F_EXCL | NLM_F_CREATE)
# Fill in the length field.
length = struct.calcsize(STRUCT_NLMSGHDR) + len(rtmsg)
nlmsg = struct.pack(STRUCT_NLMSGHDR, length, command, flags,
self.seq, self.pid) + rtmsg
# Send the message and block forever until we receive a response.
self._Send(nlmsg)
response = self._Recv()
# Find the error code.
(_, msgtype, _, _, _), msg = Unpack(STRUCT_NLMSGHDR, response)
if msgtype == NLMSG_ERROR:
((error,), _) = Unpack(STRUCT_NLMSGERR, msg)
if error:
raise IOError(error, os.strerror(-error))
else:
raise ValueError("Unexpected netlink ACK type %d" % msgtype)
def FwmarkRule(self, version, is_add, fwmark, table, priority=16383):
nlattr = self._NlAttrU32(FRA_FWMARK, fwmark)
return self._Rule(version, is_add, table, nlattr, priority)