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 17import posix 18import select 19from socket import * # pylint: disable=wildcard-import 20import time 21import unittest 22from math import pow 23 24import multinetwork_base 25 26def 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. 40class 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.15 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.5 < T < 4.5 107 # Exponential backoff for 4 samples yields 4.36 < T < 10.39 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([MIN_LIN * pow(MIN_EXP, i) for i in range(0, len(rsSendTimes))]) 163 max_exp_bound = accumulate([MAX_LIN * pow(MAX_EXP, i) for i in 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 171if __name__ == "__main__": 172 unittest.main() 173