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