Replace shelling out to ip with netlink code.
This reduces test setup time by about 50%.
Change-Id: I079bd29e5366eb7b23e63b2f4f3dbd9267376cca
diff --git a/net/test/iproute.py b/net/test/iproute.py
index 1b544d6..a899cb7 100644
--- a/net/test/iproute.py
+++ b/net/test/iproute.py
@@ -37,6 +37,12 @@
### rtnetlink constants. See include/uapi/linux/rtnetlink.h.
# Message types.
+RTM_NEWADDR = 20
+RTM_DELADDR = 21
+RTM_NEWROUTE = 24
+RTM_DELROUTE = 25
+RTM_NEWNEIGH = 28
+RTM_DELNEIGH = 29
RTM_NEWRULE = 32
RTM_DELRULE = 33
RTM_GETRULE = 34
@@ -55,12 +61,45 @@
# Named routing tables.
RT_TABLE_UNSPEC = 0
+# Routing attributes.
+RTA_DST = 1
+RTA_OIF = 4
+RTA_GATEWAY = 5
+
# Data structure formats.
RTMsg = cstruct.Struct(
"RTMsg", "=BBBBBBBBI",
"family dst_len src_len tos table protocol scope type flags")
+### Interface address constants. See include/uapi/linux/if_addr.h.
+# Interface address attributes.
+IFA_ADDRESS = 1
+IFA_LOCAL = 2
+
+# Address flags.
+IFA_F_PERMANENT = 0x80
+
+# Data structure formats.
+IfAddrMsg = cstruct.Struct(
+ "IfAddrMsg", "=BBBBI",
+ "family prefixlen flags scope index")
+
+
+### Neighbour table entry constants. See include/uapi/linux/neighbour.h.
+# Neighbour cache entry attributes.
+NDA_DST = 1
+NDA_LLADDR = 2
+
+# Neighbour cache entry states.
+NUD_PERMANENT = 0x80
+
+# Data structure formats.
+NdMsg = cstruct.Struct(
+ "NdMsg", "=BxxxiHBB",
+ "family ifindex state flags type")
+
+
### FIB rule constants. See include/uapi/linux/fib_rules.h.
FRA_PRIORITY = 6
FRA_FWMARK = 10
@@ -96,6 +135,9 @@
def _NlAttrU32(self, nla_type, value):
return self._NlAttr(nla_type, struct.pack("=I", value))
+ def _NlAttrIPAddress(self, nla_type, family, address):
+ return self._NlAttr(nla_type, socket.inet_pton(family, address))
+
def __init__(self):
# Global sequence number.
self.seq = 0
@@ -105,6 +147,7 @@
self.pid = self.sock.getsockname()[1]
def _Send(self, msg):
+ self._Debug(msg.encode("hex"))
self.seq += 1
self.sock.send(msg)
@@ -129,6 +172,24 @@
else:
raise ValueError("Unexpected netlink ACK type %d" % hdr.type)
+ def _AddressFamily(self, version):
+ return {4: socket.AF_INET, 6: socket.AF_INET6}[version]
+
+ def _SendNlRequest(self, command, is_add, data):
+ """Sends a netlink request and expects an ack."""
+ flags = NLM_F_REQUEST | NLM_F_ACK
+ if is_add:
+ flags |= (NLM_F_EXCL | NLM_F_CREATE)
+
+ length = len(NLMsgHdr) + len(data)
+ nlmsg = NLMsgHdr((length, command, flags, self.seq, self.pid)).Pack()
+
+ # Send the message and block forever until we receive a response.
+ self._Send(nlmsg + data)
+
+ # Expect a successful ACK.
+ self._ExpectAck()
+
def _Rule(self, version, is_add, table, match_nlattr, priority):
"""Python equivalent of "ip rule <add|del> <match_cond> lookup <table>".
@@ -143,10 +204,8 @@
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]
+ family = self._AddressFamily(version)
rtmsg = RTMsg((family, 0, 0, 0, RT_TABLE_UNSPEC,
RTPROT_STATIC, RT_SCOPE_UNIVERSE, RTN_UNICAST, 0)).Pack()
rtmsg += self._NlAttrU32(FRA_PRIORITY, priority)
@@ -155,18 +214,7 @@
# 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)
-
- length = len(NLMsgHdr) + len(rtmsg)
- nlmsg = NLMsgHdr((length, command, flags, self.seq, self.pid)).Pack()
-
- # Send the message and block forever until we receive a response.
- self._Send(nlmsg + rtmsg)
-
- # Expect a successful ACK.
- self._ExpectAck()
+ self._SendNlRequest(command, is_add, rtmsg)
def FwmarkRule(self, version, is_add, fwmark, table, priority=16383):
nlattr = self._NlAttrU32(FRA_FWMARK, fwmark)
@@ -184,7 +232,7 @@
def DumpRules(self, version):
"""Returns the IP rules for the specified IP version."""
# Create a struct rtmsg specifying the table and the given match attributes.
- family = {4: socket.AF_INET, 6: socket.AF_INET6}[version]
+ family = self._AddressFamily(version)
rtmsg = RTMsg((family, 0, 0, 0, 0, 0, 0, 0, 0))
# Create a netlink dump request containing the rtmsg.
@@ -226,6 +274,67 @@
self._ExpectDone()
return rules
+ def _Address(self, version, is_add, addr, prefixlen, flags, scope, ifindex):
+ """Adds or deletes an IP address."""
+ family = self._AddressFamily(version)
+ ifaddrmsg = IfAddrMsg((family, prefixlen, flags, scope, ifindex)).Pack()
+ ifaddrmsg += self._NlAttrIPAddress(IFA_ADDRESS, family, addr)
+ if version == 4:
+ ifaddrmsg += self._NlAttrIPAddress(IFA_LOCAL, family, addr)
+ command = RTM_NEWADDR if is_add else RTM_DELADDR
+ self._SendNlRequest(command, is_add, ifaddrmsg)
+
+ def AddAddress(self, address, prefixlen, ifindex):
+ version = 6 if ":" in address else 4
+ return self._Address(version, True, address, prefixlen, IFA_F_PERMANENT,
+ RT_SCOPE_UNIVERSE, ifindex)
+
+ def DelAddress(self, address, prefixlen, ifindex):
+ version = 6 if ":" in address else 4
+ return self._Address(version, False, address, prefixlen, 0, 0, ifindex)
+
+ def _Route(self, version, is_add, table, dest, prefixlen, nexthop, dev):
+ """Adds or deletes a route."""
+ family = self._AddressFamily(version)
+ rtmsg = RTMsg((family, prefixlen, 0, 0, RT_TABLE_UNSPEC,
+ RTPROT_STATIC, RT_SCOPE_UNIVERSE, RTN_UNICAST, 0)).Pack()
+ rtmsg += self._NlAttrU32(FRA_TABLE, table)
+ if dest != "default": # The default is the default route.
+ rtmsg += self._NlAttrIPAddress(RTA_DST, family, dest)
+ rtmsg += self._NlAttrIPAddress(RTA_GATEWAY, family, nexthop)
+ rtmsg += self._NlAttrU32(RTA_OIF, dev)
+ command = RTM_NEWROUTE if is_add else RTM_DELROUTE
+ self._SendNlRequest(command, is_add, rtmsg)
+
+ def AddRoute(self, version, table, dest, prefixlen, nexthop, dev):
+ self._Route(version, True, table, dest, prefixlen, nexthop, dev)
+
+ def DelRoute(self, version, table, dest, prefixlen, nexthop, dev):
+ self._Route(version, False, table, dest, prefixlen, nexthop, dev)
+
+ def _Neighbour(self, version, is_add, addr, lladdr, dev, state):
+ """Adds or deletes a neighbour cache entry."""
+ family = self._AddressFamily(version)
+
+ # Convert the link-layer address to a raw byte string.
+ if is_add:
+ lladdr = lladdr.split(":")
+ if len(lladdr) != 6:
+ raise ValueError("Invalid lladdr %s" % ":".join(lladdr))
+ lladdr = "".join(chr(int(hexbyte, 16)) for hexbyte in lladdr)
+
+ ndmsg = NdMsg((family, dev, state, 0, RTN_UNICAST)).Pack()
+ ndmsg += self._NlAttrIPAddress(NDA_DST, family, addr)
+ ndmsg += self._NlAttr(NDA_LLADDR, lladdr)
+ command = RTM_NEWNEIGH if is_add else RTM_DELNEIGH
+ self._SendNlRequest(command, is_add, ndmsg)
+
+ def AddNeighbour(self, version, addr, lladdr, dev):
+ self._Neighbour(version, True, addr, lladdr, dev, NUD_PERMANENT)
+
+ def DelNeighbour(self, version, addr, lladdr, dev):
+ self._Neighbour(version, False, addr, lladdr, dev, 0)
+
if __name__ == "__main__":
iproute = IPRoute()
diff --git a/net/test/mark_test.py b/net/test/mark_test.py
index 432f4d1..3411dd6 100755
--- a/net/test/mark_test.py
+++ b/net/test/mark_test.py
@@ -363,23 +363,25 @@
@classmethod
def _RunSetupCommands(cls, netid, is_add):
- iptables_commands = [
- "/sbin/%(iptables)s %(append_delete)s INPUT -t mangle -i %(iface)s"
- " -j MARK --set-mark %(mark)d",
- ]
- route_commands = [
- "ip -%(version)d route %(add_del)s table %(table)s"
- " default dev %(iface)s via %(router)s",
- ]
- ipv4_commands = [
- "ip -4 nei %(add_del)s %(router)s dev %(iface)s"
- " lladdr %(macaddr)s nud permanent",
- "ip -4 addr %(add_del)s %(ipv4addr)s/24 dev %(iface)s",
- ]
-
- for version, iptables in zip([4, 6], ["iptables", "ip6tables"]):
- table = cls._TableForNetid(netid)
+ for version in [4, 6]:
+ # Find out how to configure things.
iface = cls.GetInterfaceName(netid)
+ ifindex = cls.ifindices[netid]
+ macaddr = cls.RouterMacAddress(netid)
+ router = cls._RouterAddress(netid, version)
+ table = cls._TableForNetid(netid)
+
+ # Run iptables to set up incoming packet marking.
+ add_del = "-A" if is_add else "-D"
+ iptables = {4: "iptables", 6: "ip6tables"}[version]
+ args = "%s %s INPUT -t mangle -i %s -j MARK --set-mark %d" % (
+ iptables, add_del, iface, netid)
+ iptables = "/sbin/" + iptables
+ ret = os.spawnvp(os.P_WAIT, iptables, args.split(" "))
+ if ret:
+ raise ConfigurationError("Setup command failed: %s" % args)
+
+ # Set up routing rules.
if HAVE_EXPERIMENTAL_UID_ROUTING:
start, end = cls.UidRangeForNetid(netid)
cls.iproute.UidRangeRule(version, is_add, start, end, table,
@@ -387,42 +389,28 @@
cls.iproute.OifRule(version, is_add, iface, table, priority=200)
cls.iproute.FwmarkRule(version, is_add, netid, table, priority=300)
- if cls.DEBUG:
- os.spawnvp(os.P_WAIT, "/sbin/ip", ["ip", "-6", "rule", "list"])
-
- if version == 6:
- if cls.AUTOCONF_TABLE_OFFSET is None:
- # Set up routing manually.
- cmds = iptables_commands + route_commands
- else:
- cmds = iptables_commands
-
- if version == 4:
- # Deleting addresses also causes routes to be deleted, so watch the
- # order or the test will output lots of ENOENT errors.
- if is_add:
- cmds = iptables_commands + ipv4_commands + route_commands
- else:
- cmds = iptables_commands + route_commands + ipv4_commands
-
- cmds = str("\n".join(cmds) % {
- "add_del": "add" if is_add else "del",
- "append_delete": "-A" if is_add else "-D",
- "iface": iface,
- "iptables": iptables,
- "ipv4addr": cls._MyIPv4Address(netid),
- "macaddr": cls.RouterMacAddress(netid),
- "mark": netid,
- "router": cls._RouterAddress(netid, version),
- "table": table,
- "version": version,
- }).split("\n")
- for cmd in cmds:
- cmd = cmd.split(" ")
- if cls.DEBUG: print " ".join(cmd)
- ret = os.spawnvp(os.P_WAIT, cmd[0], cmd)
- if ret:
- raise ConfigurationError("Setup command failed: %s" % " ".join(cmd))
+ # Configure routing and addressing.
+ #
+ # IPv6 uses autoconf for everything, except if per-device autoconf routing
+ # tables are not supported, in which case the default route (only) is
+ # configured manually. For IPv4 we have to manualy configure addresses,
+ # routes, and neighbour cache entries (since we don't reply to ARP or ND).
+ #
+ # Since deleting addresses also causes routes to be deleted, we need to
+ # be careful with ordering or the delete commands will fail with ENOENT.
+ do_routing = (version == 4 or cls.AUTOCONF_TABLE_OFFSET is None)
+ if is_add:
+ if version == 4:
+ cls.iproute.AddAddress(cls._MyIPv4Address(netid), 24, ifindex)
+ cls.iproute.AddNeighbour(version, router, macaddr, ifindex)
+ if do_routing:
+ cls.iproute.AddRoute(version, table, "default", 0, router, ifindex)
+ else:
+ if do_routing:
+ cls.iproute.DelRoute(version, table, "default", 0, router, ifindex)
+ if version == 4:
+ cls.iproute.DelNeighbour(version, router, macaddr, ifindex)
+ cls.iproute.DelAddress(cls._MyIPv4Address(netid), 24, ifindex)
@classmethod
def GetSysctl(cls, sysctl):
@@ -1030,6 +1018,7 @@
self.SendRA(netid)
CheckIPv6Connectivity(True)
+ @unittest.skipUnless(HAVE_AUTOCONF_TABLE, "our manual routing doesn't do PIO")
def testOnlinkCommunication(self):
"""Checks that on-link communication goes direct and not through routers."""
for netid in self.tuns: