Home | History | Annotate | Download | only in test
      1 #!/usr/bin/python
      2 #
      3 # Copyright 2017 The Android Open Source Project
      4 #
      5 # Licensed under the Apache License, Version 2.0 (the "License");
      6 # you may not use this file except in compliance with the License.
      7 # You may obtain a copy of the License at
      8 #
      9 # http://www.apache.org/licenses/LICENSE-2.0
     10 #
     11 # Unless required by applicable law or agreed to in writing, software
     12 # distributed under the License is distributed on an "AS IS" BASIS,
     13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 # See the License for the specific language governing permissions and
     15 # limitations under the License.
     16 
     17 import posix
     18 import select
     19 from socket import *  # pylint: disable=wildcard-import
     20 import time
     21 import unittest
     22 from math import pow
     23 
     24 import multinetwork_base
     25 
     26 def accumulate(lis):
     27   total = 0
     28   for x in lis:
     29     total += x
     30     yield total
     31 
     32 # This test attempts to validate time related behavior of the kernel
     33 # under test and is therefore inherently prone to races. To avoid
     34 # flakes, this test is biased to declare RFC 7559 RS backoff is
     35 # present on the assumption that repeated runs will detect
     36 # non-compliant kernels with high probability.
     37 #
     38 # If higher confidence is required, REQUIRED_SAMPLES and
     39 # SAMPLE_INTERVAL can be increased at the cost of increased runtime.
     40 class ResilientRouterSolicitationTest(multinetwork_base.MultiNetworkBaseTest):
     41   """Tests for IPv6 'resilient rs' RFC 7559 backoff behaviour.
     42 
     43   Relevant kernel commits:
     44     upstream:
     45       bd11f0741fa5 ipv6 addrconf: implement RFC7559 router solicitation backoff
     46     android-4.4:
     47       e246a2f11fcc UPSTREAM: ipv6 addrconf: implement RFC7559 router solicitation backoff
     48 
     49     android-4.1:
     50       c6e9a50816a0 UPSTREAM: ipv6 addrconf: implement RFC7559 router solicitation backoff
     51 
     52     android-3.18:
     53       2a7561c61417 UPSTREAM: ipv6 addrconf: implement RFC7559 router solicitation backoff
     54 
     55     android-3.10:
     56       ce2d59ac01f3 BACKPORT: ipv6 addrconf: implement RFC7559 router solicitation backoff
     57 
     58   """
     59   ROUTER_SOLICIT = 133
     60 
     61   _TEST_NETID = 123
     62   _PROC_NET_TUNABLE = "/proc/sys/net/ipv6/conf/%s/%s"
     63 
     64   @classmethod
     65   def setUpClass(cls):
     66     return
     67 
     68   def setUp(self):
     69     return
     70 
     71   @classmethod
     72   def tearDownClass(cls):
     73     return
     74 
     75   def tearDown(self):
     76     return
     77 
     78   @classmethod
     79   def isIPv6RouterSolicitation(cls, packet):
     80     return ((len(packet) >= 14 + 40 + 1) and
     81             # Use net_test.ETH_P_IPV6 here
     82             (ord(packet[12]) == 0x86) and
     83             (ord(packet[13]) == 0xdd) and
     84             (ord(packet[14]) >> 4 == 6) and
     85             (ord(packet[14 + 40]) == cls.ROUTER_SOLICIT))
     86 
     87   def makeTunInterface(self, netid):
     88     defaultDisableIPv6Path = self._PROC_NET_TUNABLE % ("default", "disable_ipv6")
     89     savedDefaultDisableIPv6 = self.GetSysctl(defaultDisableIPv6Path)
     90     self.SetSysctl(defaultDisableIPv6Path, 1)
     91     tun = self.CreateTunInterface(netid)
     92     self.SetSysctl(defaultDisableIPv6Path, savedDefaultDisableIPv6)
     93     return tun
     94 
     95   def testFeatureExists(self):
     96     return
     97 
     98   def testRouterSolicitationBackoff(self):
     99     # Test error tolerance
    100     EPSILON = 0.1
    101     # Minimum RFC3315 S14 backoff
    102     MIN_EXP = 1.9 - EPSILON
    103     # Maximum RFC3315 S14 backoff
    104     MAX_EXP = 2.1 + EPSILON
    105     SOLICITATION_INTERVAL = 1
    106     # Linear backoff for 4 samples yields 3.6 < T < 4.4
    107     # Exponential backoff for 4 samples yields 4.83 < T < 9.65
    108     REQUIRED_SAMPLES = 4
    109     # Give up after 10 seconds. Tuned for REQUIRED_SAMPLES = 4
    110     SAMPLE_INTERVAL = 10
    111     # Practically unlimited backoff
    112     SOLICITATION_MAX_INTERVAL = 1000
    113     MIN_LIN = SOLICITATION_INTERVAL * (0.9 - EPSILON)
    114     MAX_LIN = SOLICITATION_INTERVAL * (1.1 + EPSILON)
    115     netid = self._TEST_NETID
    116     tun = self.makeTunInterface(netid)
    117     poll = select.poll()
    118     poll.register(tun, select.POLLIN | select.POLLPRI)
    119 
    120     PROC_SETTINGS = [
    121         ("router_solicitation_delay", 1),
    122         ("router_solicitation_interval", SOLICITATION_INTERVAL),
    123         ("router_solicitation_max_interval", SOLICITATION_MAX_INTERVAL),
    124         ("router_solicitations", -1),
    125         ("disable_ipv6", 0)  # MUST be last
    126     ]
    127 
    128     iface = self.GetInterfaceName(netid)
    129     for tunable, value in PROC_SETTINGS:
    130       self.SetSysctl(self._PROC_NET_TUNABLE % (iface, tunable), value)
    131 
    132     start = time.time()
    133     deadline = start + SAMPLE_INTERVAL
    134 
    135     rsSendTimes = []
    136     while True:
    137       now = time.time();
    138       poll.poll((deadline - now) * 1000)
    139       try:
    140         packet = posix.read(tun.fileno(), 4096)
    141       except OSError:
    142         break
    143 
    144       txTime = time.time()
    145       if txTime > deadline:
    146         break;
    147       if not self.isIPv6RouterSolicitation(packet):
    148         continue
    149 
    150       # Record time relative to first router solicitation
    151       rsSendTimes.append(txTime - start)
    152 
    153       # Exit early if we have at least REQUIRED_SAMPLES
    154       if len(rsSendTimes) >= REQUIRED_SAMPLES:
    155         continue
    156 
    157     # Expect at least REQUIRED_SAMPLES router solicitations
    158     self.assertLessEqual(REQUIRED_SAMPLES, len(rsSendTimes))
    159 
    160     # Compute minimum and maximum bounds for RFC3315 S14 exponential backoff.
    161     # First retransmit is linear backoff, subsequent retransmits are exponential
    162     min_exp_bound = accumulate(map(lambda i: MIN_LIN * pow(MIN_EXP, i), range(0, len(rsSendTimes))))
    163     max_exp_bound = accumulate(map(lambda i: MAX_LIN * pow(MAX_EXP, i), range(0, len(rsSendTimes))))
    164 
    165     # Assert that each sample falls within the worst case interval. If all samples fit we accept
    166     # the exponential backoff hypothesis
    167     for (t, min_exp, max_exp) in zip(rsSendTimes[1:], min_exp_bound, max_exp_bound):
    168       self.assertLess(min_exp, t)
    169       self.assertGreater(max_exp, t)
    170 
    171 if __name__ == "__main__":
    172   unittest.main()
    173