OSDN Git Service

am 105bda9c: Move all setup code to a new multinetwork_base.py
[android-x86/system-extras.git] / tests / net_test / multinetwork_test.py
1 #!/usr/bin/python
2
3 import errno
4 import os
5 import random
6 from socket import *  # pylint: disable=wildcard-import
7 import struct
8 import time           # pylint: disable=unused-import
9 import unittest
10
11 from scapy import all as scapy
12
13 import iproute
14 import multinetwork_base
15 import net_test
16
17 PING_IDENT = 0xff19
18 PING_PAYLOAD = "foobarbaz"
19 PING_SEQ = 3
20 PING_TOS = 0x83
21
22 IPV6_FLOWINFO = 11
23
24
25 UDP_PAYLOAD = str(scapy.DNS(rd=1,
26                             id=random.randint(0, 65535),
27                             qd=scapy.DNSQR(qname="wWW.GoOGle.CoM",
28                                            qtype="AAAA")))
29
30
31 IPV4_MARK_REFLECT_SYSCTL = "/proc/sys/net/ipv4/fwmark_reflect"
32 IPV6_MARK_REFLECT_SYSCTL = "/proc/sys/net/ipv6/fwmark_reflect"
33 SYNCOOKIES_SYSCTL = "/proc/sys/net/ipv4/tcp_syncookies"
34 TCP_MARK_ACCEPT_SYSCTL = "/proc/sys/net/ipv4/tcp_fwmark_accept"
35
36 HAVE_MARK_REFLECT = os.path.isfile(IPV4_MARK_REFLECT_SYSCTL)
37 HAVE_TCP_MARK_ACCEPT = os.path.isfile(TCP_MARK_ACCEPT_SYSCTL)
38
39 # The IP[V6]UNICAST_IF socket option was added between 3.1 and 3.4.
40 HAVE_UNICAST_IF = net_test.LINUX_VERSION >= (3, 4, 0)
41
42
43 class ConfigurationError(AssertionError):
44   pass
45
46
47 class Packets(object):
48
49   TCP_FIN = 1
50   TCP_SYN = 2
51   TCP_RST = 4
52   TCP_PSH = 8
53   TCP_ACK = 16
54
55   TCP_SEQ = 1692871236
56   TCP_WINDOW = 14400
57
58   @staticmethod
59   def RandomPort():
60     return random.randint(1025, 65535)
61
62   @staticmethod
63   def _GetIpLayer(version):
64     return {4: scapy.IP, 6: scapy.IPv6}[version]
65
66   @staticmethod
67   def _SetPacketTos(packet, tos):
68     if isinstance(packet, scapy.IPv6):
69       packet.tc = tos
70     elif isinstance(packet, scapy.IP):
71       packet.tos = tos
72     else:
73       raise ValueError("Can't find ToS Field")
74
75   @classmethod
76   def UDP(cls, version, srcaddr, dstaddr, sport=0):
77     ip = cls._GetIpLayer(version)
78     # Can't just use "if sport" because None has meaning (it means unspecified).
79     if sport == 0:
80       sport = cls.RandomPort()
81     return ("UDPv%d packet" % version,
82             ip(src=srcaddr, dst=dstaddr) /
83             scapy.UDP(sport=sport, dport=53) / UDP_PAYLOAD)
84
85   @classmethod
86   def UDPWithOptions(cls, version, srcaddr, dstaddr, sport=0):
87     if version == 4:
88       packet = (scapy.IP(src=srcaddr, dst=dstaddr, ttl=39, tos=0x83) /
89                 scapy.UDP(sport=sport, dport=53) /
90                 UDP_PAYLOAD)
91     else:
92       packet = (scapy.IPv6(src=srcaddr, dst=dstaddr,
93                            fl=0xbeef, hlim=39, tc=0x83) /
94                 scapy.UDP(sport=sport, dport=53) /
95                 UDP_PAYLOAD)
96     return ("UDPv%d packet with options" % version, packet)
97
98   @classmethod
99   def SYN(cls, dport, version, srcaddr, dstaddr, sport=0, seq=TCP_SEQ):
100     ip = cls._GetIpLayer(version)
101     if sport == 0:
102       sport = cls.RandomPort()
103     return ("TCP SYN",
104             ip(src=srcaddr, dst=dstaddr) /
105             scapy.TCP(sport=sport, dport=dport,
106                       seq=seq, ack=0,
107                       flags=cls.TCP_SYN, window=cls.TCP_WINDOW))
108
109   @classmethod
110   def RST(cls, version, srcaddr, dstaddr, packet):
111     ip = cls._GetIpLayer(version)
112     original = packet.getlayer("TCP")
113     return ("TCP RST",
114             ip(src=srcaddr, dst=dstaddr) /
115             scapy.TCP(sport=original.dport, dport=original.sport,
116                       ack=original.seq + 1, seq=None,
117                       flags=cls.TCP_RST | cls.TCP_ACK, window=cls.TCP_WINDOW))
118
119   @classmethod
120   def SYNACK(cls, version, srcaddr, dstaddr, packet):
121     ip = cls._GetIpLayer(version)
122     original = packet.getlayer("TCP")
123     return ("TCP SYN+ACK",
124             ip(src=srcaddr, dst=dstaddr) /
125             scapy.TCP(sport=original.dport, dport=original.sport,
126                       ack=original.seq + 1, seq=None,
127                       flags=cls.TCP_SYN | cls.TCP_ACK, window=None))
128
129   @classmethod
130   def ACK(cls, version, srcaddr, dstaddr, packet, payload=""):
131     ip = cls._GetIpLayer(version)
132     original = packet.getlayer("TCP")
133     was_syn_or_fin = (original.flags & (cls.TCP_SYN | cls.TCP_FIN)) != 0
134     ack_delta = was_syn_or_fin + len(original.payload)
135     desc = "TCP data" if payload else "TCP ACK"
136     flags = cls.TCP_ACK | cls.TCP_PSH if payload else cls.TCP_ACK
137     return (desc,
138             ip(src=srcaddr, dst=dstaddr) /
139             scapy.TCP(sport=original.dport, dport=original.sport,
140                       ack=original.seq + ack_delta, seq=original.ack,
141                       flags=flags, window=cls.TCP_WINDOW) /
142             payload)
143
144   @classmethod
145   def FIN(cls, version, srcaddr, dstaddr, packet):
146     ip = cls._GetIpLayer(version)
147     original = packet.getlayer("TCP")
148     was_fin = (original.flags & cls.TCP_FIN) != 0
149     return ("TCP FIN",
150             ip(src=srcaddr, dst=dstaddr) /
151             scapy.TCP(sport=original.dport, dport=original.sport,
152                       ack=original.seq + was_fin, seq=original.ack,
153                       flags=cls.TCP_ACK | cls.TCP_FIN, window=cls.TCP_WINDOW))
154
155   @classmethod
156   def GRE(cls, version, srcaddr, dstaddr, proto, packet):
157     if version == 4:
158       ip = scapy.IP(src=srcaddr, dst=dstaddr, proto=net_test.IPPROTO_GRE)
159     else:
160       ip = scapy.IPv6(src=srcaddr, dst=dstaddr, nh=net_test.IPPROTO_GRE)
161     packet = ip / scapy.GRE(proto=proto) / packet
162     return ("GRE packet", packet)
163
164   @classmethod
165   def ICMPPortUnreachable(cls, version, srcaddr, dstaddr, packet):
166     if version == 4:
167       # Linux hardcodes the ToS on ICMP errors to 0xc0 or greater because of
168       # RFC 1812 4.3.2.5 (!).
169       return ("ICMPv4 port unreachable",
170               scapy.IP(src=srcaddr, dst=dstaddr, proto=1, tos=0xc0) /
171               scapy.ICMPerror(type=3, code=3) / packet)
172     else:
173       return ("ICMPv6 port unreachable",
174               scapy.IPv6(src=srcaddr, dst=dstaddr) /
175               scapy.ICMPv6DestUnreach(code=4) / packet)
176
177   @classmethod
178   def ICMPPacketTooBig(cls, version, srcaddr, dstaddr, packet):
179     if version == 4:
180       return ("ICMPv4 fragmentation needed",
181               scapy.IP(src=srcaddr, dst=dstaddr, proto=1) /
182               scapy.ICMPerror(type=3, code=4, unused=1280) / str(packet)[:64])
183     else:
184       udp = packet.getlayer("UDP")
185       udp.payload = str(udp.payload)[:1280-40-8]
186       return ("ICMPv6 Packet Too Big",
187               scapy.IPv6(src=srcaddr, dst=dstaddr) /
188               scapy.ICMPv6PacketTooBig() / str(packet)[:1232])
189
190   @classmethod
191   def ICMPEcho(cls, version, srcaddr, dstaddr):
192     ip = cls._GetIpLayer(version)
193     icmp = {4: scapy.ICMP, 6: scapy.ICMPv6EchoRequest}[version]
194     packet = (ip(src=srcaddr, dst=dstaddr) /
195               icmp(id=PING_IDENT, seq=PING_SEQ) / PING_PAYLOAD)
196     cls._SetPacketTos(packet, PING_TOS)
197     return ("ICMPv%d echo" % version, packet)
198
199   @classmethod
200   def ICMPReply(cls, version, srcaddr, dstaddr, packet):
201     ip = cls._GetIpLayer(version)
202     # Scapy doesn't provide an ICMP echo reply constructor.
203     icmpv4_reply = lambda **kwargs: scapy.ICMP(type=0, **kwargs)
204     icmp = {4: icmpv4_reply, 6: scapy.ICMPv6EchoReply}[version]
205     packet = (ip(src=srcaddr, dst=dstaddr) /
206               icmp(id=PING_IDENT, seq=PING_SEQ) / PING_PAYLOAD)
207     # IPv6 only started copying the tclass to echo replies in 3.14.
208     if version == 4 or net_test.LINUX_VERSION >= (3, 14):
209       cls._SetPacketTos(packet, PING_TOS)
210     return ("ICMPv%d echo reply" % version, packet)
211
212   @classmethod
213   def NS(cls, srcaddr, tgtaddr, srcmac):
214     solicited = inet_pton(AF_INET6, tgtaddr)
215     last3bytes = tuple([ord(b) for b in solicited[-3:]])
216     solicited = "ff02::1:ff%02x:%02x%02x" % last3bytes
217     packet = (scapy.IPv6(src=srcaddr, dst=solicited) /
218               scapy.ICMPv6ND_NS(tgt=tgtaddr) /
219               scapy.ICMPv6NDOptSrcLLAddr(lladdr=srcmac))
220     return ("ICMPv6 NS", packet)
221
222   @classmethod
223   def NA(cls, srcaddr, dstaddr, srcmac):
224     packet = (scapy.IPv6(src=srcaddr, dst=dstaddr) /
225               scapy.ICMPv6ND_NA(tgt=srcaddr, R=0, S=1, O=1) /
226               scapy.ICMPv6NDOptDstLLAddr(lladdr=srcmac))
227     return ("ICMPv6 NA", packet)
228
229
230 class InboundMarkingTest(multinetwork_base.MultiNetworkBaseTest):
231
232   @classmethod
233   def _SetInboundMarking(cls, netid, is_add):
234     for version in [4, 6]:
235       # Run iptables to set up incoming packet marking.
236       iface = cls.GetInterfaceName(netid)
237       add_del = "-A" if is_add else "-D"
238       iptables = {4: "iptables", 6: "ip6tables"}[version]
239       args = "%s %s INPUT -t mangle -i %s -j MARK --set-mark %d" % (
240           iptables, add_del, iface, netid)
241       iptables = "/sbin/" + iptables
242       ret = os.spawnvp(os.P_WAIT, iptables, args.split(" "))
243       if ret:
244         raise ConfigurationError("Setup command failed: %s" % args)
245
246   @classmethod
247   def setUpClass(cls):
248     super(InboundMarkingTest, cls).setUpClass()
249     for netid in cls.tuns:
250       cls._SetInboundMarking(netid, True)
251
252   @classmethod
253   def tearDownClass(cls):
254     for netid in cls.tuns:
255       cls._SetInboundMarking(netid, False)
256     super(InboundMarkingTest, cls).tearDownClass()
257
258   @classmethod
259   def SetMarkReflectSysctls(cls, value):
260     cls.SetSysctl(IPV4_MARK_REFLECT_SYSCTL, value)
261     try:
262       cls.SetSysctl(IPV6_MARK_REFLECT_SYSCTL, value)
263     except IOError:
264       # This does not exist if we use the version of the patch that uses a
265       # common sysctl for IPv4 and IPv6.
266       pass
267
268
269 class OutgoingTest(multinetwork_base.MultiNetworkBaseTest):
270
271   # How many times to run outgoing packet tests.
272   ITERATIONS = 5
273
274   def CheckPingPacket(self, version, netid, routing_mode, dstaddr, packet):
275     s = self.BuildSocket(version, net_test.PingSocket, netid, routing_mode)
276
277     myaddr = self.MyAddress(version, netid)
278     s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
279     s.bind((myaddr, PING_IDENT))
280     net_test.SetSocketTos(s, PING_TOS)
281
282     desc, expected = Packets.ICMPEcho(version, myaddr, dstaddr)
283     msg = "IPv%d ping: expected %s on %s" % (
284         version, desc, self.GetInterfaceName(netid))
285
286     s.sendto(packet + PING_PAYLOAD, (dstaddr, 19321))
287
288     self.ExpectPacketOn(netid, msg, expected)
289
290   def CheckTCPSYNPacket(self, version, netid, routing_mode, dstaddr):
291     s = self.BuildSocket(version, net_test.TCPSocket, netid, routing_mode)
292
293     if version == 6 and dstaddr.startswith("::ffff"):
294       version = 4
295     myaddr = self.MyAddress(version, netid)
296     desc, expected = Packets.SYN(53, version, myaddr, dstaddr,
297                                  sport=None, seq=None)
298
299     # Non-blocking TCP connects always return EINPROGRESS.
300     self.assertRaisesErrno(errno.EINPROGRESS, s.connect, (dstaddr, 53))
301     msg = "IPv%s TCP connect: expected %s on %s" % (
302         version, desc, self.GetInterfaceName(netid))
303     self.ExpectPacketOn(netid, msg, expected)
304     s.close()
305
306   def CheckUDPPacket(self, version, netid, routing_mode, dstaddr):
307     s = self.BuildSocket(version, net_test.UDPSocket, netid, routing_mode)
308
309     if version == 6 and dstaddr.startswith("::ffff"):
310       version = 4
311     myaddr = self.MyAddress(version, netid)
312     desc, expected = Packets.UDP(version, myaddr, dstaddr, sport=None)
313     msg = "IPv%s UDP %%s: expected %s on %s" % (
314         version, desc, self.GetInterfaceName(netid))
315
316     s.sendto(UDP_PAYLOAD, (dstaddr, 53))
317     self.ExpectPacketOn(netid, msg % "sendto", expected)
318
319     # IP_UNICAST_IF doesn't seem to work on connected sockets, so no TCP.
320     if routing_mode != "ucast_oif":
321       s.connect((dstaddr, 53))
322       s.send(UDP_PAYLOAD)
323       self.ExpectPacketOn(netid, msg % "connect/send", expected)
324       s.close()
325
326   def CheckRawGrePacket(self, version, netid, routing_mode, dstaddr):
327     s = self.BuildSocket(version, net_test.RawGRESocket, netid, routing_mode)
328
329     inner_version = {4: 6, 6: 4}[version]
330     inner_src = self.MyAddress(inner_version, netid)
331     inner_dst = self.GetRemoteAddress(inner_version)
332     inner = str(Packets.UDP(inner_version, inner_src, inner_dst, sport=None)[1])
333
334     ethertype = {4: net_test.ETH_P_IP, 6: net_test.ETH_P_IPV6}[inner_version]
335     # A GRE header can be as simple as two zero bytes and the ethertype.
336     packet = struct.pack("!i", ethertype) + inner
337     myaddr = self.MyAddress(version, netid)
338
339     s.sendto(packet, (dstaddr, IPPROTO_GRE))
340     desc, expected = Packets.GRE(version, myaddr, dstaddr, ethertype, inner)
341     msg = "Raw IPv%d GRE with inner IPv%d UDP: expected %s on %s" % (
342         version, inner_version, desc, self.GetInterfaceName(netid))
343     self.ExpectPacketOn(netid, msg, expected)
344
345   def CheckOutgoingPackets(self, routing_mode):
346     v4addr = self.IPV4_ADDR
347     v6addr = self.IPV6_ADDR
348     v4mapped = "::ffff:" + v4addr
349
350     for _ in xrange(self.ITERATIONS):
351       for netid in self.tuns:
352
353         self.CheckPingPacket(4, netid, routing_mode, v4addr, self.IPV4_PING)
354         # Kernel bug.
355         if routing_mode != "oif":
356           self.CheckPingPacket(6, netid, routing_mode, v6addr, self.IPV6_PING)
357
358         # IP_UNICAST_IF doesn't seem to work on connected sockets, so no TCP.
359         if routing_mode != "ucast_oif":
360           self.CheckTCPSYNPacket(4, netid, routing_mode, v4addr)
361           self.CheckTCPSYNPacket(6, netid, routing_mode, v6addr)
362           self.CheckTCPSYNPacket(6, netid, routing_mode, v4mapped)
363
364         self.CheckUDPPacket(4, netid, routing_mode, v4addr)
365         self.CheckUDPPacket(6, netid, routing_mode, v6addr)
366         self.CheckUDPPacket(6, netid, routing_mode, v4mapped)
367
368         # Creating raw sockets on non-root UIDs requires properly setting
369         # capabilities, which is hard to do from Python.
370         # IP_UNICAST_IF is not supported on raw sockets.
371         if routing_mode not in ["uid", "ucast_oif"]:
372           self.CheckRawGrePacket(4, netid, routing_mode, v4addr)
373           self.CheckRawGrePacket(6, netid, routing_mode, v6addr)
374
375   def testMarkRouting(self):
376     """Checks that socket marking selects the right outgoing interface."""
377     self.CheckOutgoingPackets("mark")
378
379   @unittest.skipUnless(multinetwork_base.HAVE_UID_ROUTING, "no UID routes")
380   def testUidRouting(self):
381     """Checks that UID routing selects the right outgoing interface."""
382     self.CheckOutgoingPackets("uid")
383
384   def testOifRouting(self):
385     """Checks that oif routing selects the right outgoing interface."""
386     self.CheckOutgoingPackets("oif")
387
388   @unittest.skipUnless(HAVE_UNICAST_IF, "no support for UNICAST_IF")
389   def testUcastOifRouting(self):
390     """Checks that ucast oif routing selects the right outgoing interface."""
391     self.CheckOutgoingPackets("ucast_oif")
392
393   def CheckRemarking(self, version, use_connect):
394     # Remarking or resetting UNICAST_IF on connected sockets does not work.
395     if use_connect:
396       modes = ["oif"]
397     else:
398       modes = ["mark", "oif"]
399       if HAVE_UNICAST_IF:
400         modes += ["ucast_oif"]
401
402     for mode in modes:
403       s = net_test.UDPSocket(self.GetProtocolFamily(version))
404
405       # Figure out what packets to expect.
406       unspec = {4: "0.0.0.0", 6: "::"}[version]
407       sport = Packets.RandomPort()
408       s.bind((unspec, sport))
409       dstaddr = {4: self.IPV4_ADDR, 6: self.IPV6_ADDR}[version]
410       desc, expected = Packets.UDP(version, unspec, dstaddr, sport)
411
412       # If we're testing connected sockets, connect the socket on the first
413       # netid now.
414       if use_connect:
415         netid = self.tuns.keys()[0]
416         self.SelectInterface(s, netid, mode)
417         s.connect((dstaddr, 53))
418         expected.src = self.MyAddress(version, netid)
419
420       # For each netid, select that network without closing the socket, and
421       # check that the packets sent on that socket go out on the right network.
422       for netid in self.tuns:
423         self.SelectInterface(s, netid, mode)
424         if not use_connect:
425           expected.src = self.MyAddress(version, netid)
426         s.sendto(UDP_PAYLOAD, (dstaddr, 53))
427         connected_str = "Connected" if use_connect else "Unconnected"
428         msg = "%s UDPv%d socket remarked using %s: expecting %s on %s" % (
429             connected_str, version, mode, desc, self.GetInterfaceName(netid))
430         self.ExpectPacketOn(netid, msg, expected)
431         self.SelectInterface(s, None, mode)
432
433   def testIPv4Remarking(self):
434     """Checks that updating the mark on an IPv4 socket changes routing."""
435     self.CheckRemarking(4, False)
436     self.CheckRemarking(4, True)
437
438   def testIPv6Remarking(self):
439     """Checks that updating the mark on an IPv6 socket changes routing."""
440     self.CheckRemarking(6, False)
441     self.CheckRemarking(6, True)
442
443   def testIPv6StickyPktinfo(self):
444     for _ in xrange(self.ITERATIONS):
445       for netid in self.tuns:
446         s = net_test.UDPSocket(AF_INET6)
447
448         # Set a flowlabel.
449         net_test.SetFlowLabel(s, net_test.IPV6_ADDR, 0xdead)
450         s.setsockopt(net_test.SOL_IPV6, net_test.IPV6_FLOWINFO_SEND, 1)
451
452         # Set some destination options.
453         nonce = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c"
454         dstopts = "".join([
455             "\x11\x02",              # Next header=UDP, 24 bytes of options.
456             "\x01\x06", "\x00" * 6,  # PadN, 6 bytes of padding.
457             "\x8b\x0c",              # ILNP nonce, 12 bytes.
458             nonce
459         ])
460         s.setsockopt(net_test.SOL_IPV6, IPV6_DSTOPTS, dstopts)
461         s.setsockopt(net_test.SOL_IPV6, IPV6_UNICAST_HOPS, 255)
462
463         pktinfo = multinetwork_base.MakePktInfo(6, None, self.ifindices[netid])
464
465         # Set the sticky pktinfo option.
466         s.setsockopt(net_test.SOL_IPV6, IPV6_PKTINFO, pktinfo)
467
468         # Specify the flowlabel in the destination address.
469         s.sendto(UDP_PAYLOAD, (net_test.IPV6_ADDR, 53, 0xdead, 0))
470
471         sport = s.getsockname()[1]
472         srcaddr = self.MyAddress(6, netid)
473         expected = (scapy.IPv6(src=srcaddr, dst=net_test.IPV6_ADDR,
474                                fl=0xdead, hlim=255) /
475                     scapy.IPv6ExtHdrDestOpt(
476                         options=[scapy.PadN(optdata="\x00\x00\x00\x00\x00\x00"),
477                                  scapy.HBHOptUnknown(otype=0x8b,
478                                                      optdata=nonce)]) /
479                     scapy.UDP(sport=sport, dport=53) /
480                     UDP_PAYLOAD)
481         msg = "IPv6 UDP using sticky pktinfo: expected UDP packet on %s" % (
482             self.GetInterfaceName(netid))
483         self.ExpectPacketOn(netid, msg, expected)
484
485   def CheckPktinfoRouting(self, version):
486     for _ in xrange(self.ITERATIONS):
487       for netid in self.tuns:
488         family = self.GetProtocolFamily(version)
489         s = net_test.UDPSocket(family)
490
491         if version == 6:
492           # Create a flowlabel so we can use it.
493           net_test.SetFlowLabel(s, net_test.IPV6_ADDR, 0xbeef)
494
495           # Specify some arbitrary options.
496           cmsgs = [
497               (net_test.SOL_IPV6, IPV6_HOPLIMIT, 39),
498               (net_test.SOL_IPV6, IPV6_TCLASS, 0x83),
499               (net_test.SOL_IPV6, IPV6_FLOWINFO, int(htonl(0xbeef))),
500           ]
501         else:
502           # Support for setting IPv4 TOS and TTL via cmsg only appeared in 3.13.
503           cmsgs = []
504           s.setsockopt(net_test.SOL_IP, IP_TTL, 39)
505           s.setsockopt(net_test.SOL_IP, IP_TOS, 0x83)
506
507         dstaddr = self.GetRemoteAddress(version)
508         self.SendOnNetid(version, s, dstaddr, 53, netid, UDP_PAYLOAD, cmsgs)
509
510         sport = s.getsockname()[1]
511         srcaddr = self.MyAddress(version, netid)
512
513         desc, expected = Packets.UDPWithOptions(version, srcaddr, dstaddr,
514                                                 sport=sport)
515
516         msg = "IPv%d UDP using pktinfo routing: expected %s on %s" % (
517             version, desc, self.GetInterfaceName(netid))
518         self.ExpectPacketOn(netid, msg, expected)
519
520   def testIPv4PktinfoRouting(self):
521     self.CheckPktinfoRouting(4)
522
523   def testIPv6PktinfoRouting(self):
524     self.CheckPktinfoRouting(6)
525
526
527 class MarkTest(InboundMarkingTest):
528
529   def CheckReflection(self, version, gen_packet, gen_reply):
530     """Checks that replies go out on the same interface as the original.
531
532     For each combination:
533      - Calls gen_packet to generate a packet to that IP address.
534      - Writes the packet generated by gen_packet on the given tun
535        interface, causing the kernel to receive it.
536      - Checks that the kernel's reply matches the packet generated by
537        gen_reply.
538
539     Args:
540       version: An integer, 4 or 6.
541       gen_packet: A function taking an IP version (an integer), a source
542         address and a destination address (strings), and returning a scapy
543         packet.
544       gen_reply: A function taking the same arguments as gen_packet,
545         plus a scapy packet, and returning a scapy packet.
546     """
547     for netid, iif, ip_if, myaddr, remoteaddr in self.Combinations(version):
548       # Generate a test packet.
549       desc, packet = gen_packet(version, remoteaddr, myaddr)
550
551       # Test with mark reflection enabled and disabled.
552       for reflect in [0, 1]:
553         self.SetMarkReflectSysctls(reflect)
554         # HACK: IPv6 ping replies always do a routing lookup with the
555         # interface the ping came in on. So even if mark reflection is not
556         # working, IPv6 ping replies will be properly reflected. Don't
557         # fail when that happens.
558         if reflect or desc == "ICMPv6 echo":
559           reply_desc, reply = gen_reply(version, myaddr, remoteaddr, packet)
560         else:
561           reply_desc, reply = None, None
562
563         msg = self._FormatMessage(iif, ip_if, "reflect=%d" % reflect,
564                                   desc, reply_desc)
565         self._ReceiveAndExpectResponse(netid, packet, reply, msg)
566
567   def SYNToClosedPort(self, *args):
568     return Packets.SYN(999, *args)
569
570   @unittest.skipUnless(HAVE_MARK_REFLECT, "no mark reflection")
571   def testIPv4ICMPErrorsReflectMark(self):
572     self.CheckReflection(4, Packets.UDP, Packets.ICMPPortUnreachable)
573
574   @unittest.skipUnless(HAVE_MARK_REFLECT, "no mark reflection")
575   def testIPv6ICMPErrorsReflectMark(self):
576     self.CheckReflection(6, Packets.UDP, Packets.ICMPPortUnreachable)
577
578   @unittest.skipUnless(HAVE_MARK_REFLECT, "no mark reflection")
579   def testIPv4PingRepliesReflectMarkAndTos(self):
580     self.CheckReflection(4, Packets.ICMPEcho, Packets.ICMPReply)
581
582   @unittest.skipUnless(HAVE_MARK_REFLECT, "no mark reflection")
583   def testIPv6PingRepliesReflectMarkAndTos(self):
584     self.CheckReflection(6, Packets.ICMPEcho, Packets.ICMPReply)
585
586   @unittest.skipUnless(HAVE_MARK_REFLECT, "no mark reflection")
587   def testIPv4RSTsReflectMark(self):
588     self.CheckReflection(4, self.SYNToClosedPort, Packets.RST)
589
590   @unittest.skipUnless(HAVE_MARK_REFLECT, "no mark reflection")
591   def testIPv6RSTsReflectMark(self):
592     self.CheckReflection(6, self.SYNToClosedPort, Packets.RST)
593
594
595 class TCPAcceptTest(InboundMarkingTest):
596
597   MODE_BINDTODEVICE = "SO_BINDTODEVICE"
598   MODE_INCOMING_MARK = "incoming mark"
599   MODE_EXPLICIT_MARK = "explicit mark"
600   MODE_UID = "uid"
601
602   @classmethod
603   def setUpClass(cls):
604     super(TCPAcceptTest, cls).setUpClass()
605
606     # Open a port so we can observe SYN+ACKs. Since it's a dual-stack socket it
607     # will accept both IPv4 and IPv6 connections. We do this here instead of in
608     # each test so we can use the same socket every time. That way, if a kernel
609     # bug causes incoming packets to mark the listening socket instead of the
610     # accepted socket, the test will fail as soon as the next address/interface
611     # combination is tried.
612     cls.listenport = 1234
613     cls.listensocket = net_test.IPv6TCPSocket()
614     cls.listensocket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
615     cls.listensocket.bind(("::", cls.listenport))
616     cls.listensocket.listen(100)
617
618   def BounceSocket(self, s):
619     """Attempts to invalidate a socket's destination cache entry."""
620     if s.family == AF_INET:
621       tos = s.getsockopt(SOL_IP, IP_TOS)
622       s.setsockopt(net_test.SOL_IP, IP_TOS, 53)
623       s.setsockopt(net_test.SOL_IP, IP_TOS, tos)
624     else:
625       # UDP, 8 bytes dstopts; PAD1, 4 bytes padding; 4 bytes zeros.
626       pad8 = "".join(["\x11\x00", "\x01\x04", "\x00" * 4])
627       s.setsockopt(net_test.SOL_IPV6, IPV6_DSTOPTS, pad8)
628       s.setsockopt(net_test.SOL_IPV6, IPV6_DSTOPTS, "")
629
630   def _SetTCPMarkAcceptSysctl(self, value):
631     self.SetSysctl(TCP_MARK_ACCEPT_SYSCTL, value)
632
633   def CheckTCPConnection(self, mode, listensocket, netid, version,
634                          myaddr, remoteaddr, packet, reply, msg):
635     establishing_ack = Packets.ACK(version, remoteaddr, myaddr, reply)[1]
636
637     # Attempt to confuse the kernel.
638     self.BounceSocket(listensocket)
639
640     self.ReceivePacketOn(netid, establishing_ack)
641
642     # If we're using UID routing, the accept() call has to be run as a UID that
643     # is routed to the specified netid, because the UID of the socket returned
644     # by accept() is the effective UID of the process that calls it. It doesn't
645     # need to be the same UID; any UID that selects the same interface will do.
646     with net_test.RunAsUid(self.UidForNetid(netid)):
647       s, _ = listensocket.accept()
648
649     try:
650       # Check that data sent on the connection goes out on the right interface.
651       desc, data = Packets.ACK(version, myaddr, remoteaddr, establishing_ack,
652                                payload=UDP_PAYLOAD)
653       s.send(UDP_PAYLOAD)
654       self.ExpectPacketOn(netid, msg + ": expecting %s" % desc, data)
655       self.BounceSocket(s)
656
657       # Keep up our end of the conversation.
658       ack = Packets.ACK(version, remoteaddr, myaddr, data)[1]
659       self.BounceSocket(listensocket)
660       self.ReceivePacketOn(netid, ack)
661
662       mark = self.GetSocketMark(s)
663     finally:
664       self.BounceSocket(s)
665       s.close()
666
667     if mode == self.MODE_INCOMING_MARK:
668       self.assertEquals(netid, mark,
669                         msg + ": Accepted socket: Expected mark %d, got %d" % (
670                             netid, mark))
671     elif mode != self.MODE_EXPLICIT_MARK:
672       self.assertEquals(0, self.GetSocketMark(listensocket))
673
674     # Check the FIN was sent on the right interface, and ack it. We don't expect
675     # this to fail because by the time the connection is established things are
676     # likely working, but a) extra tests are always good and b) extra packets
677     # like the FIN (and retransmitted FINs) could cause later tests that expect
678     # no packets to fail.
679     desc, fin = Packets.FIN(version, myaddr, remoteaddr, ack)
680     self.ExpectPacketOn(netid, msg + ": expecting %s after close" % desc, fin)
681
682     desc, finack = Packets.FIN(version, remoteaddr, myaddr, fin)
683     self.ReceivePacketOn(netid, finack)
684
685     # Since we called close() earlier, the userspace socket object is gone, so
686     # the socket has no UID. If we're doing UID routing, the ack might be routed
687     # incorrectly. Not much we can do here.
688     desc, finackack = Packets.ACK(version, myaddr, remoteaddr, finack)
689     if mode != self.MODE_UID:
690       self.ExpectPacketOn(netid, msg + ": expecting final ack", finackack)
691     else:
692       self.ClearTunQueues()
693
694   def CheckTCP(self, version, modes):
695     """Checks that incoming TCP connections work.
696
697     Args:
698       version: An integer, 4 or 6.
699       modes: A list of modes to excercise.
700     """
701     for syncookies in [0, 2]:
702       for mode in modes:
703         for netid, iif, ip_if, myaddr, remoteaddr in self.Combinations(version):
704           if mode == self.MODE_UID:
705             listensocket = self.BuildSocket(6, net_test.TCPSocket, netid, mode)
706             listensocket.listen(100)
707           else:
708             listensocket = self.listensocket
709
710           listenport = listensocket.getsockname()[1]
711
712           if HAVE_TCP_MARK_ACCEPT:
713             accept_sysctl = 1 if mode == self.MODE_INCOMING_MARK else 0
714             self._SetTCPMarkAcceptSysctl(accept_sysctl)
715
716           bound_dev = iif if mode == self.MODE_BINDTODEVICE else None
717           self.BindToDevice(listensocket, bound_dev)
718
719           mark = netid if mode == self.MODE_EXPLICIT_MARK else 0
720           self.SetSocketMark(listensocket, mark)
721
722           # Generate the packet here instead of in the outer loop, so
723           # subsequent TCP connections use different source ports and
724           # retransmissions from old connections don't confuse subsequent
725           # tests.
726           desc, packet = Packets.SYN(listenport, version, remoteaddr, myaddr)
727
728           if mode:
729             reply_desc, reply = Packets.SYNACK(version, myaddr, remoteaddr,
730                                                packet)
731           else:
732             reply_desc, reply = None, None
733
734           extra = "mode=%s, syncookies=%d" % (mode, syncookies)
735           msg = self._FormatMessage(iif, ip_if, extra, desc, reply_desc)
736           reply = self._ReceiveAndExpectResponse(netid, packet, reply, msg)
737           if reply:
738             self.CheckTCPConnection(mode, listensocket, netid, version, myaddr,
739                                     remoteaddr, packet, reply, msg)
740
741   def testBasicTCP(self):
742     self.CheckTCP(4, [None, self.MODE_BINDTODEVICE, self.MODE_EXPLICIT_MARK])
743     self.CheckTCP(6, [None, self.MODE_BINDTODEVICE, self.MODE_EXPLICIT_MARK])
744
745   @unittest.skipUnless(HAVE_TCP_MARK_ACCEPT, "fwmark writeback not supported")
746   def testIPv4MarkAccept(self):
747     self.CheckTCP(4, [self.MODE_INCOMING_MARK])
748
749   @unittest.skipUnless(HAVE_TCP_MARK_ACCEPT, "fwmark writeback not supported")
750   def testIPv6MarkAccept(self):
751     self.CheckTCP(6, [self.MODE_INCOMING_MARK])
752
753   @unittest.skipUnless(multinetwork_base.HAVE_UID_ROUTING, "no UID routes")
754   def testIPv4UidAccept(self):
755     self.CheckTCP(4, [self.MODE_UID])
756
757   @unittest.skipUnless(multinetwork_base.HAVE_UID_ROUTING, "no UID routes")
758   def testIPv6UidAccept(self):
759     self.CheckTCP(6, [self.MODE_UID])
760
761   def testIPv6ExplicitMark(self):
762     self.CheckTCP(6, [self.MODE_EXPLICIT_MARK])
763
764
765 class RATest(multinetwork_base.MultiNetworkBaseTest):
766
767   def testDoesNotHaveObsoleteSysctl(self):
768     self.assertFalse(os.path.isfile(
769         "/proc/sys/net/ipv6/route/autoconf_table_offset"))
770
771   @unittest.skipUnless(multinetwork_base.HAVE_AUTOCONF_TABLE,
772                        "no support for per-table autoconf")
773   def testPurgeDefaultRouters(self):
774
775     def CheckIPv6Connectivity(expect_connectivity):
776       for netid in self.NETIDS:
777         s = net_test.UDPSocket(AF_INET6)
778         self.SetSocketMark(s, netid)
779         if expect_connectivity:
780           self.assertTrue(s.sendto(UDP_PAYLOAD, (net_test.IPV6_ADDR, 1234)))
781         else:
782           self.assertRaisesErrno(errno.ENETUNREACH, s.sendto, UDP_PAYLOAD,
783                                  (net_test.IPV6_ADDR, 1234))
784
785     try:
786       CheckIPv6Connectivity(True)
787       self.SetSysctl("/proc/sys/net/ipv6/conf/all/forwarding", 1)
788       CheckIPv6Connectivity(False)
789     finally:
790       self.SetSysctl("/proc/sys/net/ipv6/conf/all/forwarding", 0)
791       for netid in self.NETIDS:
792         self.SendRA(netid)
793       CheckIPv6Connectivity(True)
794
795   def testOnlinkCommunication(self):
796     """Checks that on-link communication goes direct and not through routers."""
797     for netid in self.tuns:
798       # Send a UDP packet to a random on-link destination.
799       s = net_test.UDPSocket(AF_INET6)
800       iface = self.GetInterfaceName(netid)
801       self.BindToDevice(s, iface)
802       # dstaddr can never be our address because GetRandomDestination only fills
803       # in the lower 32 bits, but our address has 0xff in the byte before that
804       # (since it's constructed from the EUI-64 and so has ff:fe in the middle).
805       dstaddr = self.GetRandomDestination(self.IPv6Prefix(netid))
806       s.sendto(UDP_PAYLOAD, (dstaddr, 53))
807
808       # Expect an NS for that destination on the interface.
809       myaddr = self.MyAddress(6, netid)
810       mymac = self.MyMacAddress(netid)
811       desc, expected = Packets.NS(myaddr, dstaddr, mymac)
812       msg = "Sending UDP packet to on-link destination: expecting %s" % desc
813       time.sleep(0.0001)  # Required to make the test work on kernel 3.1(!)
814       self.ExpectPacketOn(netid, msg, expected)
815
816       # Send an NA.
817       tgtmac = "02:00:00:00:%02x:99" % netid
818       _, reply = Packets.NA(dstaddr, myaddr, tgtmac)
819       # Don't use ReceivePacketOn, since that uses the router's MAC address as
820       # the source. Instead, construct our own Ethernet header with source
821       # MAC of tgtmac.
822       reply = scapy.Ether(src=tgtmac, dst=mymac) / reply
823       self.ReceiveEtherPacketOn(netid, reply)
824
825       # Expect the kernel to send the original UDP packet now that the ND cache
826       # entry has been populated.
827       sport = s.getsockname()[1]
828       desc, expected = Packets.UDP(6, myaddr, dstaddr, sport=sport)
829       msg = "After NA response, expecting %s" % desc
830       self.ExpectPacketOn(netid, msg, expected)
831
832   # This test documents a known issue: routing tables are never deleted.
833   @unittest.skipUnless(multinetwork_base.HAVE_AUTOCONF_TABLE,
834                        "no support for per-table autoconf")
835   def testLeftoverRoutes(self):
836     def GetNumRoutes():
837       return len(open("/proc/net/ipv6_route").readlines())
838
839     num_routes = GetNumRoutes()
840     for i in xrange(10, 20):
841       try:
842         self.tuns[i] = self.CreateTunInterface(i)
843         self.SendRA(i)
844         self.tuns[i].close()
845       finally:
846         del self.tuns[i]
847     self.assertLess(num_routes, GetNumRoutes())
848
849
850 class PMTUTest(InboundMarkingTest):
851
852   PAYLOAD_SIZE = 1400
853
854   # Socket options to change PMTU behaviour.
855   IP_MTU_DISCOVER = 10
856   IP_PMTUDISC_DO = 1
857   IPV6_DONTFRAG = 62
858
859   # Socket options to get the MTU.
860   IP_MTU = 14
861   IPV6_PATHMTU = 61
862
863   def GetSocketMTU(self, version, s):
864     if version == 6:
865       ip6_mtuinfo = s.getsockopt(net_test.SOL_IPV6, self.IPV6_PATHMTU, 32)
866       unused_sockaddr, mtu = struct.unpack("=28sI", ip6_mtuinfo)
867       return mtu
868     else:
869       return s.getsockopt(net_test.SOL_IP, self.IP_MTU)
870
871   def DisableFragmentationAndReportErrors(self, version, s):
872     if version == 4:
873       s.setsockopt(net_test.SOL_IP, self.IP_MTU_DISCOVER, self.IP_PMTUDISC_DO)
874       s.setsockopt(net_test.SOL_IP, net_test.IP_RECVERR, 1)
875     else:
876       s.setsockopt(net_test.SOL_IPV6, self.IPV6_DONTFRAG, 1)
877       s.setsockopt(net_test.SOL_IPV6, net_test.IPV6_RECVERR, 1)
878
879   def CheckPMTU(self, version, use_connect, modes):
880
881     def SendBigPacket(version, s, dstaddr, netid, payload):
882       if use_connect:
883         s.send(payload)
884       else:
885         self.SendOnNetid(version, s, dstaddr, 1234, netid, payload, [])
886
887     for netid in self.tuns:
888       for mode in modes:
889         s = self.BuildSocket(version, net_test.UDPSocket, netid, mode)
890         self.DisableFragmentationAndReportErrors(version, s)
891
892         srcaddr = self.MyAddress(version, netid)
893         dst_prefix, intermediate = {
894             4: ("172.19.", "172.16.9.12"),
895             6: ("2001:db8::", "2001:db8::1")
896         }[version]
897         dstaddr = self.GetRandomDestination(dst_prefix)
898
899         if use_connect:
900           s.connect((dstaddr, 1234))
901
902         payload = self.PAYLOAD_SIZE * "a"
903
904         # Send a packet and receive a packet too big.
905         SendBigPacket(version, s, dstaddr, netid, payload)
906         packets = self.ReadAllPacketsOn(netid)
907         self.assertEquals(1, len(packets))
908         _, toobig = Packets.ICMPPacketTooBig(version, intermediate, srcaddr,
909                                              packets[0])
910         self.ReceivePacketOn(netid, toobig)
911
912         # Check that another send on the same socket returns EMSGSIZE.
913         self.assertRaisesErrno(
914             errno.EMSGSIZE,
915             SendBigPacket, version, s, dstaddr, netid, payload)
916
917         # If this is a connected socket, make sure the socket MTU was set.
918         # Note that in IPv4 this only started working in Linux 3.6!
919         if use_connect and (version == 6 or net_test.LINUX_VERSION >= (3, 6)):
920           self.assertEquals(1280, self.GetSocketMTU(version, s))
921
922         s.close()
923
924         # Check that other sockets pick up the PMTU we have been told about by
925         # connecting another socket to the same destination and getting its MTU.
926         # This new socket can use any method to select its outgoing interface;
927         # here we use a mark for simplicity.
928         s2 = self.BuildSocket(version, net_test.UDPSocket, netid, "mark")
929         s2.connect((dstaddr, 1234))
930         self.assertEquals(1280, self.GetSocketMTU(version, s2))
931
932         # Also check the MTU reported by ip route get, this time using the oif.
933         routes = self.iproute.GetRoutes(dstaddr, self.ifindices[netid], 0, None)
934         self.assertTrue(routes)
935         route = routes[0]
936         rtmsg, attributes = route
937         self.assertEquals(iproute.RTN_UNICAST, rtmsg.type)
938         metrics = attributes["RTA_METRICS"]
939         self.assertEquals(metrics["RTAX_MTU"], 1280)
940
941   def testIPv4BasicPMTU(self):
942     self.CheckPMTU(4, True, ["mark", "oif"])
943     self.CheckPMTU(4, False, ["mark", "oif"])
944
945   def testIPv6BasicPMTU(self):
946     self.CheckPMTU(6, True, ["mark", "oif"])
947     self.CheckPMTU(6, False, ["mark", "oif"])
948
949   @unittest.skipUnless(multinetwork_base.HAVE_UID_ROUTING, "no UID routes")
950   def testIPv4UIDPMTU(self):
951     self.CheckPMTU(4, True, ["uid"])
952     self.CheckPMTU(4, False, ["uid"])
953
954   @unittest.skipUnless(multinetwork_base.HAVE_UID_ROUTING, "no UID routes")
955   def testIPv6UIDPMTU(self):
956     self.CheckPMTU(6, True, ["uid"])
957     self.CheckPMTU(6, False, ["uid"])
958
959   # Making Path MTU Discovery work on unmarked  sockets requires that mark
960   # reflection be enabled. Otherwise the kernel has no way to know what routing
961   # table the original packet used, and thus it won't be able to clone the
962   # correct route.
963
964   @unittest.skipUnless(HAVE_MARK_REFLECT, "no mark reflection")
965   def testIPv4UnmarkedSocketPMTU(self):
966     self.SetMarkReflectSysctls(1)
967     try:
968       self.CheckPMTU(4, False, [None])
969     finally:
970       self.SetMarkReflectSysctls(0)
971
972   @unittest.skipUnless(HAVE_MARK_REFLECT, "no mark reflection")
973   def testIPv6UnmarkedSocketPMTU(self):
974     self.SetMarkReflectSysctls(1)
975     try:
976       self.CheckPMTU(6, False, [None])
977     finally:
978       self.SetMarkReflectSysctls(0)
979
980
981 @unittest.skipUnless(multinetwork_base.HAVE_UID_ROUTING, "no UID routes")
982 class UidRoutingTest(multinetwork_base.MultiNetworkBaseTest):
983
984   def GetRulesAtPriority(self, version, priority):
985     rules = self.iproute.DumpRules(version)
986     out = [(rule, attributes) for rule, attributes in rules
987            if attributes.get("FRA_PRIORITY", 0) == priority]
988     return out
989
990   def CheckInitialTablesHaveNoUIDs(self, version):
991     rules = []
992     for priority in [0, 32766, 32767]:
993       rules.extend(self.GetRulesAtPriority(version, priority))
994     for _, attributes in rules:
995       self.assertNotIn("FRA_UID_START", attributes)
996       self.assertNotIn("FRA_UID_END", attributes)
997
998   def testIPv4InitialTablesHaveNoUIDs(self):
999     self.CheckInitialTablesHaveNoUIDs(4)
1000
1001   def testIPv6InitialTablesHaveNoUIDs(self):
1002     self.CheckInitialTablesHaveNoUIDs(6)
1003
1004   def CheckGetAndSetRules(self, version):
1005     def Random():
1006       return random.randint(1000000, 2000000)
1007
1008     start, end = tuple(sorted([Random(), Random()]))
1009     table = Random()
1010     priority = Random()
1011
1012     try:
1013       self.iproute.UidRangeRule(version, True, start, end, table,
1014                                 priority=priority)
1015
1016       rules = self.GetRulesAtPriority(version, priority)
1017       self.assertTrue(rules)
1018       _, attributes = rules[-1]
1019       self.assertEquals(priority, attributes["FRA_PRIORITY"])
1020       self.assertEquals(start, attributes["FRA_UID_START"])
1021       self.assertEquals(end, attributes["FRA_UID_END"])
1022       self.assertEquals(table, attributes["FRA_TABLE"])
1023     finally:
1024       self.iproute.UidRangeRule(version, False, start, end, table,
1025                                 priority=priority)
1026
1027   def testIPv4GetAndSetRules(self):
1028     self.CheckGetAndSetRules(4)
1029
1030   def testIPv6GetAndSetRules(self):
1031     self.CheckGetAndSetRules(6)
1032
1033   def ExpectNoRoute(self, addr, oif, mark, uid):
1034     # The lack of a route may be either an error, or an unreachable route.
1035     try:
1036       routes = self.iproute.GetRoutes(addr, oif, mark, uid)
1037       rtmsg, _ = routes[0]
1038       self.assertEquals(iproute.RTN_UNREACHABLE, rtmsg.type)
1039     except IOError, e:
1040       if int(e.errno) != -int(errno.ENETUNREACH):
1041         raise e
1042
1043   def ExpectRoute(self, addr, oif, mark, uid):
1044     routes = self.iproute.GetRoutes(addr, oif, mark, uid)
1045     rtmsg, _ = routes[0]
1046     self.assertEquals(iproute.RTN_UNICAST, rtmsg.type)
1047
1048   def CheckGetRoute(self, version, addr):
1049     self.ExpectNoRoute(addr, 0, 0, 0)
1050     for netid in self.NETIDS:
1051       uid = self.UidForNetid(netid)
1052       self.ExpectRoute(addr, 0, 0, uid)
1053     self.ExpectNoRoute(addr, 0, 0, 0)
1054
1055   def testIPv4RouteGet(self):
1056     self.CheckGetRoute(4, net_test.IPV4_ADDR)
1057
1058   def testIPv6RouteGet(self):
1059     self.CheckGetRoute(6, net_test.IPV6_ADDR)
1060
1061
1062 class RulesTest(net_test.NetworkTest):
1063
1064   RULE_PRIORITY = 99999
1065
1066   def setUp(self):
1067     self.iproute = iproute.IPRoute()
1068     for version in [4, 6]:
1069       self.iproute.DeleteRulesAtPriority(version, self.RULE_PRIORITY)
1070
1071   def tearDown(self):
1072     for version in [4, 6]:
1073       self.iproute.DeleteRulesAtPriority(version, self.RULE_PRIORITY)
1074
1075   def testRuleDeletionMatchesTable(self):
1076     for version in [4, 6]:
1077       # Add rules with mark 300 pointing at tables 301 and 302.
1078       # This checks for a kernel bug where deletion request for tables > 256
1079       # ignored the table.
1080       self.iproute.FwmarkRule(version, True, 300, 301,
1081                               priority=self.RULE_PRIORITY)
1082       self.iproute.FwmarkRule(version, True, 300, 302,
1083                               priority=self.RULE_PRIORITY)
1084       # Delete rule with mark 300 pointing at table 302.
1085       self.iproute.FwmarkRule(version, False, 300, 302,
1086                               priority=self.RULE_PRIORITY)
1087       # Check that the rule pointing at table 301 is still around.
1088       attributes = [a for _, a in self.iproute.DumpRules(version)
1089                     if a.get("FRA_PRIORITY", 0) == self.RULE_PRIORITY]
1090       self.assertEquals(1, len(attributes))
1091       self.assertEquals(301, attributes[0]["FRA_TABLE"])
1092
1093
1094 if __name__ == "__main__":
1095   unittest.main()