Enable full RIOTest.testZeroLengthPrefix test on all kernel versions

Also add new tests to guarantee that RIOs are being accepted as well as
the rejection power of min_plen.

Bug: 33333670
Test: net_test passes on android 3.10, 3.18, 4.1, 4.4, 4.9 common and
device variants.

Change-Id: Ib7c0adff7f081e36d279ff88c5133adb6740a81b
diff --git a/net/test/multinetwork_test.py b/net/test/multinetwork_test.py
index 0ade759..3deea46 100755
--- a/net/test/multinetwork_test.py
+++ b/net/test/multinetwork_test.py
@@ -43,9 +43,6 @@
 # The IP[V6]UNICAST_IF socket option was added between 3.1 and 3.4.
 HAVE_UNICAST_IF = net_test.LINUX_VERSION >= (3, 4, 0)
 
-MAX_PLEN_SYSCTL = "/proc/sys/net/ipv6/conf/default/accept_ra_rt_info_max_plen"
-HAVE_MAX_PLEN = os.path.isfile(MAX_PLEN_SYSCTL)
-
 class ConfigurationError(AssertionError):
   pass
 
@@ -576,19 +573,57 @@
   def testIPv6ExplicitMark(self):
     self.CheckTCP(6, [self.MODE_EXPLICIT_MARK])
 
+@unittest.skipUnless(multinetwork_base.HAVE_AUTOCONF_TABLE,
+                     "need support for per-table autoconf")
 class RIOTest(multinetwork_base.MultiNetworkBaseTest):
+  """Test for IPv6 RFC 4191 route information option
+
+  Relevant kernel commits:
+    upstream:
+      f104a567e673 ipv6: use rt6_get_dflt_router to get default router in rt6_route_rcv
+      bbea124bc99d net: ipv6: Add sysctl for minimum prefix len acceptable in RIOs.
+
+    android-4.9:
+      d860b2e8a7f1 FROMLIST: net: ipv6: Add sysctl for minimum prefix len acceptable in RIOs
+
+    android-4.4:
+      e953f89b8563 net: ipv6: Add sysctl for minimum prefix len acceptable in RIOs.
+
+    android-4.1:
+      84f2f47716cd net: ipv6: Add sysctl for minimum prefix len acceptable in RIOs.
+
+    android-3.18:
+      65f8936934fa net: ipv6: Add sysctl for minimum prefix len acceptable in RIOs.
+
+    android-3.10:
+      161e88ebebc7 net: ipv6: Add sysctl for minimum prefix len acceptable in RIOs.
+
+  """
 
   def setUp(self):
+    super(RIOTest, self).setUp()
     self.NETID = random.choice(self.NETIDS)
     self.IFACE = self.GetInterfaceName(self.NETID)
+    # return min/max plen to default values before each test case
+    self.SetAcceptRaRtInfoMinPlen(0)
+    self.SetAcceptRaRtInfoMaxPlen(0)
 
   def GetRoutingTable(self):
     return self._TableForNetid(self.NETID)
 
+  def SetAcceptRaRtInfoMinPlen(self, plen):
+    self.SetSysctl(
+        "/proc/sys/net/ipv6/conf/%s/accept_ra_rt_info_min_plen"
+        % self.IFACE, plen)
+
+  def GetAcceptRaRtInfoMinPlen(self):
+    return int(self.GetSysctl(
+        "/proc/sys/net/ipv6/conf/%s/accept_ra_rt_info_min_plen" % self.IFACE))
+
   def SetAcceptRaRtInfoMaxPlen(self, plen):
     self.SetSysctl(
         "/proc/sys/net/ipv6/conf/%s/accept_ra_rt_info_max_plen"
-        % self.IFACE, str(plen))
+        % self.IFACE, plen)
 
   def GetAcceptRaRtInfoMaxPlen(self):
     return int(self.GetSysctl(
@@ -614,18 +649,40 @@
   def GetRouteExpiration(self, route):
     return float(route['RTA_CACHEINFO'].expires) / 100.0
 
-  @unittest.skipUnless(HAVE_MAX_PLEN and multinetwork_base.HAVE_AUTOCONF_TABLE,
-                       "need support for RIO and per-table autoconf")
+  def AssertExpirationInRange(self, routes, lifetime, epsilon):
+    self.assertTrue(routes)
+    found = False
+    # Assert that at least one route in routes has the expected lifetime
+    for route in routes:
+      expiration = self.GetRouteExpiration(route)
+      if expiration < lifetime - epsilon:
+        continue
+      if expiration > lifetime + epsilon:
+        continue
+      found = True
+    self.assertTrue(found)
+
+  def DelRoute6(self, prefix, plen):
+    version = 6
+    netid = self.NETID
+    table = self._TableForNetid(netid)
+    router = self._RouterAddress(netid, version)
+    ifindex = self.ifindices[netid]
+    self.iproute.DelRoute(version, table, prefix, plen, router, ifindex)
+
+  def testSetAcceptRaRtInfoMinPlen(self):
+    for plen in xrange(-1, 130):
+      self.SetAcceptRaRtInfoMinPlen(plen)
+      self.assertEquals(plen, self.GetAcceptRaRtInfoMinPlen())
+
   def testSetAcceptRaRtInfoMaxPlen(self):
     for plen in xrange(-1, 130):
       self.SetAcceptRaRtInfoMaxPlen(plen)
       self.assertEquals(plen, self.GetAcceptRaRtInfoMaxPlen())
 
-  @unittest.skipUnless(HAVE_MAX_PLEN and multinetwork_base.HAVE_AUTOCONF_TABLE,
-                       "need support for RIO and per-table autoconf")
   def testZeroRtLifetime(self):
     PREFIX = "2001:db8:8901:2300::"
-    RTLIFETIME = 7372
+    RTLIFETIME = 73500
     PLEN = 56
     PRF = 0
     self.SetAcceptRaRtInfoMaxPlen(PLEN)
@@ -639,52 +696,87 @@
     time.sleep(0.01)
     self.assertFalse(self.FindRoutesWithDestination(PREFIX))
 
-  @unittest.skipUnless(HAVE_MAX_PLEN and multinetwork_base.HAVE_AUTOCONF_TABLE,
-                       "need support for RIO and per-table autoconf")
-  def testMaxPrefixLenRejection(self):
-    PREFIX = "2001:db8:8901:2345::"
-    RTLIFETIME = 7372
+  def testMinPrefixLenRejection(self):
+    PREFIX = "2001:db8:8902:2345::"
+    RTLIFETIME = 70372
     PRF = 0
-    for plen in xrange(0, 64):
+    # sweep from high to low to avoid spurious failures from late arrivals.
+    for plen in xrange(130, 1, -1):
+      self.SetAcceptRaRtInfoMinPlen(plen)
+      # RIO with plen < min_plen should be ignored
+      self.SendRIO(RTLIFETIME, plen - 1, PREFIX, PRF)
+    # Give the kernel time to notice our RAs
+    time.sleep(0.1)
+    # Expect no routes
+    routes = self.FindRoutesWithDestination(PREFIX)
+    self.assertFalse(routes)
+
+  def testMaxPrefixLenRejection(self):
+    PREFIX = "2001:db8:8903:2345::"
+    RTLIFETIME = 73078
+    PRF = 0
+    # sweep from low to high to avoid spurious failures from late arrivals.
+    for plen in xrange(-1, 128, 1):
       self.SetAcceptRaRtInfoMaxPlen(plen)
       # RIO with plen > max_plen should be ignored
       self.SendRIO(RTLIFETIME, plen + 1, PREFIX, PRF)
-      # Give the kernel time to notice our RA
-      time.sleep(0.01)
-      routes = self.FindRoutesWithDestination(PREFIX)
-      self.assertFalse(routes)
+    # Give the kernel time to notice our RAs
+    time.sleep(0.1)
+    # Expect no routes
+    routes = self.FindRoutesWithDestination(PREFIX)
+    self.assertFalse(routes)
 
-  @unittest.skipUnless(HAVE_MAX_PLEN and multinetwork_base.HAVE_AUTOCONF_TABLE,
-                       "need support for RIO and per-table autoconf")
+  def testSimpleAccept(self):
+    PREFIX = "2001:db8:8904:2345::"
+    RTLIFETIME = 9993
+    PRF = 0
+    PLEN = 56
+    self.SetAcceptRaRtInfoMinPlen(48)
+    self.SetAcceptRaRtInfoMaxPlen(64)
+    self.SendRIO(RTLIFETIME, PLEN, PREFIX, PRF)
+    # Give the kernel time to notice our RA
+    time.sleep(0.01)
+    routes = self.FindRoutesWithGateway()
+    self.AssertExpirationInRange(routes, RTLIFETIME, 1)
+    self.DelRoute6(PREFIX, PLEN)
+
+  def testEqualMinMaxAccept(self):
+    PREFIX = "2001:db8:8905:2345::"
+    RTLIFETIME = 6326
+    PLEN = 21
+    PRF = 0
+    self.SetAcceptRaRtInfoMinPlen(PLEN)
+    self.SetAcceptRaRtInfoMaxPlen(PLEN)
+    self.SendRIO(RTLIFETIME, PLEN, PREFIX, PRF)
+    # Give the kernel time to notice our RA
+    time.sleep(0.01)
+    routes = self.FindRoutesWithGateway()
+    self.AssertExpirationInRange(routes, RTLIFETIME, 1)
+    self.DelRoute6(PREFIX, PLEN)
+
   def testZeroLengthPrefix(self):
-    PREFIX = "::"
+    PREFIX = "2001:db8:8906:2345::"
     RTLIFETIME = self.RA_VALIDITY * 2
     PLEN = 0
     PRF = 0
     # Max plen = 0 still allows default RIOs!
     self.SetAcceptRaRtInfoMaxPlen(PLEN)
+    self.SendRA(self.NETID)
+    # Give the kernel time to notice our RA
+    time.sleep(0.01)
     default = self.FindRoutesWithGateway()
-    self.assertTrue(default)
-    self.assertLess(self.GetRouteExpiration(default[0]), self.RA_VALIDITY)
+    self.AssertExpirationInRange(default, self.RA_VALIDITY, 1)
     # 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)
     # Give the kernel time to notice our RA
     time.sleep(0.01)
     default = self.FindRoutesWithGateway()
-    self.assertTrue(default)
-    if net_test.LINUX_VERSION > (3, 12, 0):
-      # Vanilla linux earlier than 3.13 handles RIOs with zero length prefixes
-      # incorrectly. There's nothing useful to assert other than the existence
-      # of a default route.
-      # TODO: remove this condition after pulling bullhead/angler backports to
-      # other 3.10 flavors.
-      self.assertGreater(self.GetRouteExpiration(default[0]), self.RA_VALIDITY)
+    self.AssertExpirationInRange(default, RTLIFETIME, 1)
+    self.DelRoute6(PREFIX, PLEN)
 
-  @unittest.skipUnless(HAVE_MAX_PLEN and multinetwork_base.HAVE_AUTOCONF_TABLE,
-                       "need support for RIO and per-table autoconf")
   def testManyRIOs(self):
-    RTLIFETIME = 6809
+    RTLIFETIME = 68012
     PLEN = 56
     PRF = 0
     COUNT = 1000
@@ -694,11 +786,11 @@
     for i in xrange(0, COUNT):
       prefix = "2001:db8:%x:1100::" % i
       self.SendRIO(RTLIFETIME, PLEN, prefix, PRF)
+    time.sleep(0.1)
     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)
+      self.DelRoute6(prefix, PLEN)
     # Expect that we can return to baseline config without lingering routes.
     self.assertEquals(baseline, self.CountRoutes())