Add RIOTest to multinetwork_test.py
am: ca2d6ec018

Change-Id: I7fd0bc2d55b4c7e8741520335f2a5f10be5d6028
diff --git a/net/test/iproute.py b/net/test/iproute.py
index 1d980af..a570d3d 100644
--- a/net/test/iproute.py
+++ b/net/test/iproute.py
@@ -86,6 +86,7 @@
 RTA_CACHEINFO = 12
 RTA_TABLE = 15
 RTA_MARK = 16
+RTA_PREF = 20
 RTA_UID = 25
 
 # Route metric attributes.
@@ -493,6 +494,11 @@
     routes = self._GetMsgList(RTMsg, data, False)
     return routes
 
+  def DumpRoutes(self, version, ifindex):
+    ndmsg = NdMsg((self._AddressFamily(version), 0, 0, 0, 0))
+    return [(m, r) for (m, r) in self._Dump(RTM_GETROUTE, ndmsg, NdMsg, "")
+            if r['RTA_TABLE'] == ifindex]
+
   def _Neighbour(self, version, is_add, addr, lladdr, dev, state, flags=0):
     """Adds or deletes a neighbour cache entry."""
     family = self._AddressFamily(version)
diff --git a/net/test/multinetwork_base.py b/net/test/multinetwork_base.py
index ef426db..a4ba472 100644
--- a/net/test/multinetwork_base.py
+++ b/net/test/multinetwork_base.py
@@ -113,6 +113,8 @@
   IPV4_PING = net_test.IPV4_PING
   IPV6_PING = net_test.IPV6_PING
 
+  RA_VALIDITY = 300 # seconds
+
   @classmethod
   def UidRangeForNetid(cls, netid):
     return (
@@ -210,8 +212,8 @@
     return f
 
   @classmethod
-  def SendRA(cls, netid, retranstimer=None, reachabletime=0):
-    validity = 300                 # seconds
+  def SendRA(cls, netid, retranstimer=None, reachabletime=0, options=()):
+    validity = cls.RA_VALIDITY # seconds
     macaddr = cls.RouterMacAddress(netid)
     lladdr = cls._RouterAddress(netid, 6)
 
@@ -236,6 +238,8 @@
                                       L=1, A=1,
                                       validlifetime=validity,
                                       preferredlifetime=validity))
+    for option in options:
+      ra /= option
     posix.write(cls.tuns[netid].fileno(), str(ra))
 
   @classmethod
diff --git a/net/test/multinetwork_test.py b/net/test/multinetwork_test.py
index 1e88416..f634d68 100755
--- a/net/test/multinetwork_test.py
+++ b/net/test/multinetwork_test.py
@@ -574,6 +574,115 @@
   def testIPv6ExplicitMark(self):
     self.CheckTCP(6, [self.MODE_EXPLICIT_MARK])
 
+class RIOTest(multinetwork_base.MultiNetworkBaseTest):
+
+  def setUp(self):
+    self.NETID = random.choice(self.NETIDS)
+    self.IFACE = self.GetInterfaceName(self.NETID)
+
+  def GetRoutingTable(self):
+    return self._TableForNetid(self.NETID)
+
+  def SetAcceptRaRtInfoMaxPlen(self, plen):
+    self.SetSysctl(
+        "/proc/sys/net/ipv6/conf/%s/accept_ra_rt_info_max_plen"
+        % self.IFACE, str(plen))
+
+  def GetAcceptRaRtInfoMaxPlen(self):
+    return int(self.GetSysctl(
+        "/proc/sys/net/ipv6/conf/%s/accept_ra_rt_info_max_plen" % self.IFACE))
+
+  def SendRIO(self, rtlifetime, plen, prefix, prf):
+    options = scapy.ICMPv6NDOptRouteInfo(rtlifetime=rtlifetime, plen=plen,
+                                         prefix=prefix, prf=prf)
+    self.SendRA(self.NETID, options=(options,))
+
+  def FindRoutesWithDestination(self, destination):
+    canonical = net_test.CanonicalizeIPv6Address(destination)
+    return [r for _, r in self.iproute.DumpRoutes(6, self.GetRoutingTable())
+            if ('RTA_DST' in r and r['RTA_DST'] == canonical)]
+
+  def FindRoutesWithGateway(self):
+    return [r for _, r in self.iproute.DumpRoutes(6, self.GetRoutingTable())
+            if 'RTA_GATEWAY' in r]
+
+  def CountRoutes(self):
+    return len(self.iproute.DumpRoutes(6, self.GetRoutingTable()))
+
+  def GetRouteExpiration(self, route):
+    return float(route['RTA_CACHEINFO'].expires) / 100.0
+
+  def testSetAcceptRaRtInfoMaxPlen(self):
+    for plen in xrange(-1, 130):
+      self.SetAcceptRaRtInfoMaxPlen(plen)
+      self.assertEquals(plen, self.GetAcceptRaRtInfoMaxPlen())
+
+  @unittest.skipUnless(multinetwork_base.HAVE_AUTOCONF_TABLE,
+                       "no support for per-table autoconf")
+  def testZeroRtLifetime(self):
+    PREFIX = "2001:db8:8901:2300::"
+    RTLIFETIME = 7372
+    PLEN = 56
+    PRF = 0
+    self.SetAcceptRaRtInfoMaxPlen(PLEN)
+    self.SendRIO(RTLIFETIME, PLEN, PREFIX, PRF)
+    self.assertTrue(self.FindRoutesWithDestination(PREFIX))
+    # RIO with rtlifetime = 0 should remove from routing table
+    self.SendRIO(0, PLEN, PREFIX, PRF)
+    self.assertFalse(self.FindRoutesWithDestination(PREFIX))
+
+  @unittest.skipUnless(multinetwork_base.HAVE_AUTOCONF_TABLE,
+                       "no support for per-table autoconf")
+  def testMaxPrefixLenRejection(self):
+    PREFIX = "2001:db8:8901:2345::"
+    RTLIFETIME = 7372
+    PRF = 0
+    for plen in xrange(0, 64):
+      self.SetAcceptRaRtInfoMaxPlen(plen)
+      # RIO with plen > max_plen should be ignored
+      self.SendRIO(RTLIFETIME, plen + 1, PREFIX, PRF)
+      routes = self.FindRoutesWithDestination(PREFIX)
+      self.assertFalse(routes)
+
+  @unittest.skipUnless(multinetwork_base.HAVE_AUTOCONF_TABLE,
+                       "no support for per-table autoconf")
+  def testZeroLengthPrefix(self):
+    PREFIX = "::"
+    RTLIFETIME = self.RA_VALIDITY * 2
+    PLEN = 0
+    PRF = 0
+    # Max plen = 0 still allows default RIOs!
+    self.SetAcceptRaRtInfoMaxPlen(PLEN)
+    default = self.FindRoutesWithGateway()
+    self.assertTrue(default)
+    self.assertLess(self.GetRouteExpiration(default[0]), self.RA_VALIDITY)
+    # RIO with prefix length = 0, should overwrite default route lifetime
+    # note that the RIO lifetime overwrites the RA lifetime.
+    self.SendRIO(RTLIFETIME, PLEN, PREFIX, PRF)
+    default = self.FindRoutesWithGateway()
+    self.assertTrue(default)
+    self.assertGreater(self.GetRouteExpiration(default[0]), self.RA_VALIDITY)
+
+  @unittest.skipUnless(multinetwork_base.HAVE_AUTOCONF_TABLE,
+                       "no support for per-table autoconf")
+  def testManyRIOs(self):
+    RTLIFETIME = 6809
+    PLEN = 56
+    PRF = 0
+    COUNT = 1000
+    baseline = self.CountRoutes()
+    self.SetAcceptRaRtInfoMaxPlen(56)
+    # Send many RIOs compared to the expected number on a healthy system.
+    for i in xrange(0, COUNT):
+      prefix = "2001:db8:%x:1100::" % i
+      self.SendRIO(RTLIFETIME, PLEN, prefix, PRF)
+    self.assertEquals(COUNT + baseline, self.CountRoutes())
+    # Use lifetime = 0 to cleanup all previously announced RIOs.
+    for i in xrange(0, COUNT):
+      prefix = "2001:db8:%x:1100::" % i
+      self.SendRIO(0, PLEN, prefix, PRF)
+    # Expect that we can return to baseline config without lingering routes.
+    self.assertEquals(baseline, self.CountRoutes())
 
 class RATest(multinetwork_base.MultiNetworkBaseTest):
 
diff --git a/net/test/net_test.py b/net/test/net_test.py
index 0048ae6..b469009 100755
--- a/net/test/net_test.py
+++ b/net/test/net_test.py
@@ -226,13 +226,17 @@
   return SetInterfaceState(ifname, False)
 
 
+def CanonicalizeIPv6Address(addr):
+  return inet_ntop(AF_INET6, inet_pton(AF_INET6, addr))
+
+
 def FormatProcAddress(unformatted):
   groups = []
   for i in xrange(0, len(unformatted), 4):
     groups.append(unformatted[i:i+4])
   formatted = ":".join(groups)
   # Compress the address.
-  address = inet_ntop(AF_INET6, inet_pton(AF_INET6, formatted))
+  address = CanonicalizeIPv6Address(formatted)
   return address
 
 
diff --git a/net/test/run_net_test.sh b/net/test/run_net_test.sh
index 6179b28..e07d10b 100755
--- a/net/test/run_net_test.sh
+++ b/net/test/run_net_test.sh
@@ -9,6 +9,7 @@
 OPTIONS="$OPTIONS IP_NF_IPTABLES IP_NF_MANGLE IP_NF_FILTER"
 OPTIONS="$OPTIONS IP6_NF_IPTABLES IP6_NF_MANGLE IP6_NF_FILTER INET6_IPCOMP"
 OPTIONS="$OPTIONS IPV6_PRIVACY IPV6_OPTIMISTIC_DAD"
+OPTIONS="$OPTIONS CONFIG_IPV6_ROUTE_INFO CONFIG_IPV6_ROUTER_PREF"
 OPTIONS="$OPTIONS CONFIG_NETFILTER_XT_TARGET_NFLOG"
 OPTIONS="$OPTIONS CONFIG_NETFILTER_XT_MATCH_QUOTA"
 OPTIONS="$OPTIONS CONFIG_NETFILTER_XT_MATCH_QUOTA2"