/tests/net_test/neighbour_test.py
https://gitlab.com/cde/debian_android-tools_android-platform-system-extras · Python · 297 lines · 190 code · 52 blank · 55 comment · 27 complexity · 4de2de05c36456452d4e9414db2266ab MD5 · raw file
- #!/usr/bin/python
- #
- # Copyright 2015 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 errno
- import random
- from socket import * # pylint: disable=wildcard-import
- import time
- import unittest
- from scapy import all as scapy
- import multinetwork_base
- import net_test
- RTMGRP_NEIGH = 4
- NUD_INCOMPLETE = 0x01
- NUD_REACHABLE = 0x02
- NUD_STALE = 0x04
- NUD_DELAY = 0x08
- NUD_PROBE = 0x10
- NUD_FAILED = 0x20
- NUD_PERMANENT = 0x80
- # TODO: Support IPv4.
- class NeighbourTest(multinetwork_base.MultiNetworkBaseTest):
- # Set a 100-ms retrans timer so we can test for ND retransmits without
- # waiting too long. Apparently this cannot go below 500ms.
- RETRANS_TIME_MS = 500
- # This can only be in seconds, so 1000 is the minimum.
- DELAY_TIME_MS = 1000
- # Unfortunately, this must be above the delay timer or the kernel ND code will
- # not behave correctly (e.g., go straight from REACHABLE into DELAY). This is
- # is fuzzed by the kernel from 0.5x to 1.5x of its value, so we need a value
- # that's 2x the delay timer.
- REACHABLE_TIME_MS = 2 * DELAY_TIME_MS
- @classmethod
- def setUpClass(cls):
- super(NeighbourTest, cls).setUpClass()
- for netid in cls.tuns:
- iface = cls.GetInterfaceName(netid)
- # This can't be set in an RA.
- cls.SetSysctl(
- "/proc/sys/net/ipv6/neigh/%s/delay_first_probe_time" % iface,
- cls.DELAY_TIME_MS / 1000)
- def setUp(self):
- super(NeighbourTest, self).setUp()
- for netid in self.tuns:
- # Clear the ND cache entries for all routers, so each test starts with
- # the IPv6 default router in state STALE.
- addr = self._RouterAddress(netid, 6)
- ifindex = self.ifindices[netid]
- self.iproute.UpdateNeighbour(6, addr, None, ifindex, NUD_FAILED)
- # Configure IPv6 by sending an RA.
- self.SendRA(netid,
- retranstimer=self.RETRANS_TIME_MS,
- reachabletime=self.REACHABLE_TIME_MS)
- self.sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)
- self.sock.bind((0, RTMGRP_NEIGH))
- net_test.SetNonBlocking(self.sock)
- self.netid = random.choice(self.tuns.keys())
- self.ifindex = self.ifindices[self.netid]
- def GetNeighbour(self, addr):
- version = 6 if ":" in addr else 4
- for msg, args in self.iproute.DumpNeighbours(version):
- if args["NDA_DST"] == addr:
- return msg, args
- def GetNdEntry(self, addr):
- return self.GetNeighbour(addr)
- def CheckNoNdEvents(self):
- self.assertRaisesErrno(errno.EAGAIN, self.sock.recvfrom, 4096, MSG_PEEK)
- def assertNeighbourState(self, state, addr):
- self.assertEquals(state, self.GetNdEntry(addr)[0].state)
- def assertNeighbourAttr(self, addr, name, value):
- self.assertEquals(value, self.GetNdEntry(addr)[1][name])
- def ExpectNeighbourNotification(self, addr, state, attrs=None):
- msg = self.sock.recv(4096)
- msg, actual_attrs = self.iproute.ParseNeighbourMessage(msg)
- self.assertEquals(addr, actual_attrs["NDA_DST"])
- self.assertEquals(state, msg.state)
- if attrs:
- for name in attrs:
- self.assertEquals(attrs[name], actual_attrs[name])
- def ExpectProbe(self, is_unicast, addr):
- version = 6 if ":" in addr else 4
- if version == 6:
- llsrc = self.MyMacAddress(self.netid)
- if is_unicast:
- src = self.MyLinkLocalAddress(self.netid)
- dst = addr
- else:
- solicited = inet_pton(AF_INET6, addr)
- last3bytes = tuple([ord(b) for b in solicited[-3:]])
- dst = "ff02::1:ff%02x:%02x%02x" % last3bytes
- src = self.MyAddress(6, self.netid)
- expected = (
- scapy.IPv6(src=src, dst=dst) /
- scapy.ICMPv6ND_NS(tgt=addr) /
- scapy.ICMPv6NDOptSrcLLAddr(lladdr=llsrc)
- )
- msg = "%s probe" % ("Unicast" if is_unicast else "Multicast")
- self.ExpectPacketOn(self.netid, msg, expected)
- else:
- raise NotImplementedError
- def ExpectUnicastProbe(self, addr):
- self.ExpectProbe(True, addr)
- def ExpectMulticastNS(self, addr):
- self.ExpectProbe(False, addr)
- def ReceiveUnicastAdvertisement(self, addr, mac, srcaddr=None, dstaddr=None,
- S=1, O=0, R=1):
- version = 6 if ":" in addr else 4
- if srcaddr is None:
- srcaddr = addr
- if dstaddr is None:
- dstaddr = self.MyLinkLocalAddress(self.netid)
- if version == 6:
- packet = (
- scapy.Ether(src=mac, dst=self.MyMacAddress(self.netid)) /
- scapy.IPv6(src=srcaddr, dst=dstaddr) /
- scapy.ICMPv6ND_NA(tgt=addr, S=S, O=O, R=R) /
- scapy.ICMPv6NDOptDstLLAddr(lladdr=mac)
- )
- self.ReceiveEtherPacketOn(self.netid, packet)
- else:
- raise NotImplementedError
- def MonitorSleepMs(self, interval, addr):
- slept = 0
- while slept < interval:
- sleep_ms = min(100, interval - slept)
- time.sleep(sleep_ms / 1000.0)
- slept += sleep_ms
- print self.GetNdEntry(addr)
- def MonitorSleep(self, intervalseconds, addr):
- self.MonitorSleepMs(intervalseconds * 1000, addr)
- def SleepMs(self, ms):
- time.sleep(ms / 1000.0)
- def testNotifications(self):
- """Tests neighbour notifications.
- Relevant kernel commits:
- upstream net-next:
- 765c9c6 neigh: Better handling of transition to NUD_PROBE state
- 53385d2 neigh: Netlink notification for administrative NUD state change
- (only checked on kernel v3.13+, not on v3.10)
- android-3.10:
- e4a6d6b neigh: Better handling of transition to NUD_PROBE state
- android-3.18:
- 2011e72 neigh: Better handling of transition to NUD_PROBE state
- """
- router4 = self._RouterAddress(self.netid, 4)
- router6 = self._RouterAddress(self.netid, 6)
- self.assertNeighbourState(NUD_PERMANENT, router4)
- self.assertNeighbourState(NUD_STALE, router6)
- # Send a packet and check that we go into DELAY.
- routing_mode = random.choice(["mark", "oif", "uid"])
- s = self.BuildSocket(6, net_test.UDPSocket, self.netid, routing_mode)
- s.connect((net_test.IPV6_ADDR, 53))
- s.send(net_test.UDP_PAYLOAD)
- self.assertNeighbourState(NUD_DELAY, router6)
- # Wait for the probe interval, then check that we're in PROBE, and that the
- # kernel has notified us.
- self.SleepMs(self.DELAY_TIME_MS)
- self.ExpectNeighbourNotification(router6, NUD_PROBE)
- self.assertNeighbourState(NUD_PROBE, router6)
- self.ExpectUnicastProbe(router6)
- # Respond to the NS and verify we're in REACHABLE again.
- self.ReceiveUnicastAdvertisement(router6, self.RouterMacAddress(self.netid))
- self.assertNeighbourState(NUD_REACHABLE, router6)
- if net_test.LINUX_VERSION >= (3, 13, 0):
- # commit 53385d2 (v3.13) "neigh: Netlink notification for administrative
- # NUD state change" produces notifications for NUD_REACHABLE, but these
- # are not generated on earlier kernels.
- self.ExpectNeighbourNotification(router6, NUD_REACHABLE)
- # Wait until the reachable time has passed, and verify we're in STALE.
- self.SleepMs(self.REACHABLE_TIME_MS * 1.5)
- self.assertNeighbourState(NUD_STALE, router6)
- self.ExpectNeighbourNotification(router6, NUD_STALE)
- # Send a packet, and verify we go into DELAY and then to PROBE.
- s.send(net_test.UDP_PAYLOAD)
- self.assertNeighbourState(NUD_DELAY, router6)
- self.SleepMs(self.DELAY_TIME_MS)
- self.assertNeighbourState(NUD_PROBE, router6)
- self.ExpectNeighbourNotification(router6, NUD_PROBE)
- # Wait for the probes to time out, and expect a FAILED notification.
- self.assertNeighbourAttr(router6, "NDA_PROBES", 1)
- self.ExpectUnicastProbe(router6)
- self.SleepMs(self.RETRANS_TIME_MS)
- self.ExpectUnicastProbe(router6)
- self.assertNeighbourAttr(router6, "NDA_PROBES", 2)
- self.SleepMs(self.RETRANS_TIME_MS)
- self.ExpectUnicastProbe(router6)
- self.assertNeighbourAttr(router6, "NDA_PROBES", 3)
- self.SleepMs(self.RETRANS_TIME_MS)
- self.assertNeighbourState(NUD_FAILED, router6)
- self.ExpectNeighbourNotification(router6, NUD_FAILED, {"NDA_PROBES": 3})
- def testRepeatedProbes(self):
- router4 = self._RouterAddress(self.netid, 4)
- router6 = self._RouterAddress(self.netid, 6)
- routermac = self.RouterMacAddress(self.netid)
- self.assertNeighbourState(NUD_PERMANENT, router4)
- self.assertNeighbourState(NUD_STALE, router6)
- def ForceProbe(addr, mac):
- self.iproute.UpdateNeighbour(6, addr, None, self.ifindex, NUD_PROBE)
- self.assertNeighbourState(NUD_PROBE, addr)
- self.SleepMs(1) # TODO: Why is this necessary?
- self.assertNeighbourState(NUD_PROBE, addr)
- self.ExpectUnicastProbe(addr)
- self.ReceiveUnicastAdvertisement(addr, mac)
- self.assertNeighbourState(NUD_REACHABLE, addr)
- for _ in xrange(5):
- ForceProbe(router6, routermac)
- def testIsRouterFlag(self):
- router6 = self._RouterAddress(self.netid, 6)
- self.assertNeighbourState(NUD_STALE, router6)
- # Get into FAILED.
- ifindex = self.ifindices[self.netid]
- self.iproute.UpdateNeighbour(6, router6, None, ifindex, NUD_FAILED)
- self.ExpectNeighbourNotification(router6, NUD_FAILED)
- self.assertNeighbourState(NUD_FAILED, router6)
- time.sleep(1)
- # Send another packet and expect a multicast NS.
- routing_mode = random.choice(["mark", "oif", "uid"])
- s = self.BuildSocket(6, net_test.UDPSocket, self.netid, routing_mode)
- s.connect((net_test.IPV6_ADDR, 53))
- s.send(net_test.UDP_PAYLOAD)
- self.ExpectMulticastNS(router6)
- # Receive a unicast NA with the R flag set to 0.
- self.ReceiveUnicastAdvertisement(router6, self.RouterMacAddress(self.netid),
- srcaddr=self._RouterAddress(self.netid, 6),
- dstaddr=self.MyAddress(6, self.netid),
- S=1, O=0, R=0)
- # Expect that this takes us to REACHABLE.
- self.ExpectNeighbourNotification(router6, NUD_REACHABLE)
- self.assertNeighbourState(NUD_REACHABLE, router6)
- if __name__ == "__main__":
- unittest.main()