| #!/usr/bin/python |
| # |
| # Copyright 2014 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| import os |
| from socket import * # pylint: disable=wildcard-import |
| import threading |
| import time |
| import unittest |
| |
| import cstruct |
| import multinetwork_base |
| import net_test |
| |
| IPV6_JOIN_ANYCAST = 27 |
| IPV6_LEAVE_ANYCAST = 28 |
| |
| # pylint: disable=invalid-name |
| IPv6Mreq = cstruct.Struct("IPv6Mreq", "=16si", "multiaddr ifindex") |
| |
| |
| _CLOSE_HUNG = False |
| |
| |
| def CauseOops(): |
| open("/proc/sysrq-trigger", "w").write("c") |
| |
| |
| class CloseFileDescriptorThread(threading.Thread): |
| |
| def __init__(self, fd): |
| super(CloseFileDescriptorThread, self).__init__() |
| self.daemon = True |
| self._fd = fd |
| self.finished = False |
| |
| def run(self): |
| global _CLOSE_HUNG |
| _CLOSE_HUNG = True |
| self._fd.close() |
| _CLOSE_HUNG = False |
| self.finished = True |
| |
| |
| class AnycastTest(multinetwork_base.MultiNetworkBaseTest): |
| """Tests for IPv6 anycast addresses. |
| |
| Relevant kernel commits: |
| upstream net-next: |
| 381f4dc ipv6: clean up anycast when an interface is destroyed |
| |
| android-3.10: |
| 86a47ad ipv6: clean up anycast when an interface is destroyed |
| """ |
| _TEST_NETID = 123 |
| |
| def AnycastSetsockopt(self, s, is_add, netid, addr): |
| ifindex = self.ifindices[netid] |
| self.assertTrue(ifindex) |
| ipv6mreq = IPv6Mreq((addr, ifindex)) |
| option = IPV6_JOIN_ANYCAST if is_add else IPV6_LEAVE_ANYCAST |
| s.setsockopt(IPPROTO_IPV6, option, ipv6mreq.Pack()) |
| |
| def testAnycastNetdeviceUnregister(self): |
| netid = self._TEST_NETID |
| self.assertNotIn(netid, self.tuns) |
| self.tuns[netid] = self.CreateTunInterface(netid) |
| self.SendRA(netid) |
| iface = self.GetInterfaceName(netid) |
| self.ifindices[netid] = net_test.GetInterfaceIndex(iface) |
| |
| s = socket(AF_INET6, SOCK_DGRAM, 0) |
| addr = self.MyAddress(6, netid) |
| self.assertIsNotNone(addr) |
| |
| addr = inet_pton(AF_INET6, addr) |
| addr = addr[:8] + os.urandom(8) |
| self.AnycastSetsockopt(s, True, netid, addr) |
| |
| # Close the tun fd in the background. |
| # This will hang if the kernel has the bug. |
| thread = CloseFileDescriptorThread(self.tuns[netid]) |
| thread.start() |
| time.sleep(0.1) |
| |
| # Make teardown work. |
| del self.tuns[netid] |
| # Check that the interface is gone. |
| try: |
| self.assertIsNone(self.MyAddress(6, netid)) |
| finally: |
| # This doesn't seem to help, but still. |
| self.AnycastSetsockopt(s, False, netid, addr) |
| self.assertTrue(thread.finished) |
| |
| |
| if __name__ == "__main__": |
| unittest.main(exit=False) |
| if _CLOSE_HUNG: |
| time.sleep(3) |
| CauseOops() |