@@ -12,7 +12,7 @@ from hostapd import HostapdCLI
import testutil
from config import ctx
import os
-import subprocess
+import socket
class Test(unittest.TestCase):
@@ -54,6 +54,29 @@ class Test(unittest.TestCase):
ctx.non_block_wait(check_addr, 10, device,
exception=Exception("IPv6 address was not set"))
+ ifname = str(device.name)
+ router_ll_addr = [addr for addr, _, _ in testutil.get_addrs6(self.hapd.ifname) if addr[0:2] == b'\xfe\x80'][0]
+ # Since we're in an isolated VM with freshly created interfaces we know any routes
+ # will have been created by IWD and don't have to allow for pre-existing routes
+ # in the table.
+ # Flags: 1=RTF_UP, 2=RTF_GATEWAY
+ expected_routes4 = {
+ testutil.RouteInfo(gw=socket.inet_pton(socket.AF_INET, '192.168.1.1'),
+ flags=3, ifname=ifname),
+ testutil.RouteInfo(dst=socket.inet_pton(socket.AF_INET, '192.168.0.0'), plen=17,
+ flags=1, ifname=ifname)
+ }
+ expected_routes6 = {
+ # Default router
+ testutil.RouteInfo(gw=router_ll_addr, flags=3, ifname=ifname),
+ # On-link prefix
+ testutil.RouteInfo(dst=socket.inet_pton(socket.AF_INET6, '3ffe:501:ffff:100::'), plen=72,
+ flags=1, ifname=ifname),
+ }
+ self.maxDiff = None
+ self.assertEqual(expected_routes4, set(testutil.get_routes4(ifname)))
+ self.assertEqual(expected_routes6, set(testutil.get_routes6(ifname)))
+
device.disconnect()
condition = 'not obj.connected'
@@ -77,6 +100,7 @@ class Test(unittest.TestCase):
pass
hapd = HostapdCLI()
+ cls.hapd = hapd
# TODO: This could be moved into test-runner itself if other tests ever
# require this functionality (p2p, FILS, etc.). Since its simple
# enough it can stay here for now.
@@ -94,9 +118,18 @@ class Test(unittest.TestCase):
'-lf', '/tmp/dhcpd6.leases',
hapd.ifname], cleanup=remove_lease6)
ctx.start_process(['sysctl', 'net.ipv6.conf.' + hapd.ifname + '.forwarding=1']).wait()
- # Tell clients to use DHCPv6
+ # Send out Router Advertisements telling clients to use DHCPv6.
+ # Note trying to send the RAs from the router's global IPv6 address by adding a
+ # "AdvRASrcAddress { 3ffe:501:ffff:100::1; };" line will fail because the client
+ # and the router interfaces are in the same namespace and Linux won't allow routes
+ # with a non-link-local gateway address that is present on another interface in the
+ # same namespace.
config = open('/tmp/radvd.conf', 'w')
- config.write('interface ' + hapd.ifname + ' { AdvSendAdvert on; AdvManagedFlag on; };')
+ config.write('interface ' + hapd.ifname + ''' {
+ AdvSendAdvert on;
+ AdvManagedFlag on;
+ prefix 3ffe:501:ffff:100::/72 { AdvAutonomous off; };
+ };''')
config.close()
cls.radvd_pid = ctx.start_process(['radvd', '-n', '-d5', '-p', '/tmp/radvd.pid', '-C', '/tmp/radvd.conf'])
@@ -3,12 +3,12 @@
# as DHCP would assign us to produce a conflict and test ACD
Address=192.168.1.10
Netmask=255.255.255.128
-Gateway=192.168.1.1
+Gateway=192.168.1.3
[IPv6]
# Use a different subnet than DHCP on purpose
Address=3ffe:501:ffff:200::10/80
-Gateway=3ffe:501:ffff:200::1
+Gateway=3ffe:501:ffff:200::3
[Settings]
AutoConnect=false
@@ -12,6 +12,7 @@ from hostapd import HostapdCLI
import testutil
from config import ctx
import os
+import socket
class Test(unittest.TestCase):
@@ -49,6 +50,28 @@ class Test(unittest.TestCase):
testutil.test_ip_address_match(dev1.name, '192.168.1.10', 25)
testutil.test_ip_address_match(dev1.name, '3ffe:501:ffff:200::10', 80)
+ ifname = str(dev1.name)
+ # Since we're in an isolated VM with freshly created interfaces we know any routes
+ # will have been created by IWD and don't have to allow for pre-existing routes
+ # in the table.
+ # Flags: 1=RTF_UP, 2=RTF_GATEWAY
+ expected_routes4 = {
+ testutil.RouteInfo(gw=socket.inet_pton(socket.AF_INET, '192.168.1.3'),
+ flags=3, ifname=ifname),
+ testutil.RouteInfo(dst=socket.inet_pton(socket.AF_INET, '192.168.1.0'), plen=25,
+ flags=1, ifname=ifname)
+ }
+ expected_routes6 = {
+ testutil.RouteInfo(gw=socket.inet_pton(socket.AF_INET6, '3ffe:501:ffff:200::3'),
+ flags=3, ifname=ifname),
+ testutil.RouteInfo(dst=socket.inet_pton(socket.AF_INET6, '3ffe:501:ffff:200::'), plen=80,
+ flags=1, ifname=ifname),
+ }
+
+ self.maxDiff = None
+ self.assertEqual(expected_routes4, set(testutil.get_routes4(ifname)))
+ self.assertEqual(expected_routes6, set(testutil.get_routes6(ifname)))
+
ordered_network = dev2.get_ordered_network('ssidTKIP')
condition = 'not obj.connected'
@@ -5,6 +5,7 @@ import fcntl
import struct
import select
import codecs
+import collections
import iwd
from config import ctx
@@ -230,3 +231,59 @@ def test_ip_connected(tup0, tup1):
ns1.start_process(['ping', '-c', '5', '-i', '0.2', ip0], check=True)
except:
raise Exception('Could not ping between %s and %s' % (ip0, ip1))
+
+RouteInfo = collections.namedtuple('RouteInfo', 'dst plen gw flags ifname',
+ defaults=(None, None, None, 0, ''))
+
+def get_routes4(ifname=None):
+ f = open('/proc/net/route', 'r')
+ lines = f.readlines()
+ f.close()
+ for line in lines[1:]: # Skip header line
+ route_ifname, dst_str, gw_str, flags, ref_cnt, use_cnt, metric, mask_str, \
+ mtu = line.strip().split(maxsplit=8)
+ if ifname is not None and route_ifname != ifname:
+ continue
+
+ dst = codecs.decode(dst_str, 'hex')[::-1]
+ mask = int(mask_str, 16)
+ plen = sum([(mask >> bit) & 1 for bit in range(0, 32)]) # count bits
+ gw = codecs.decode(gw_str, 'hex')[::-1]
+
+ if dst == b'\0\0\0\0':
+ dst = None
+ plen = None
+ if gw == b'\0\0\0\0':
+ gw = None
+ yield RouteInfo(dst, plen, gw, int(flags, 16), route_ifname)
+
+def get_routes6(ifname=None):
+ f = open('/proc/net/ipv6_route', 'r')
+ lines = f.readlines()
+ f.close()
+ for line in lines:
+ dst_str, dst_plen_str, src_str, src_plen_str, gw_str, metric, ref_cnt, \
+ use_cnt, flags, route_ifname = line.strip().split(maxsplit=9)
+ if ifname is not None and route_ifname != ifname:
+ continue
+
+ dst = codecs.decode(dst_str, 'hex')
+ plen = int(dst_plen_str, 16)
+ gw = codecs.decode(gw_str, 'hex')
+
+ if dst[0] == 0xff or dst[:2] == b'\xfe\x80': # Skip link-local and multicast
+ continue
+
+ # Skip RTN_LOCAL-type routes, we don't need to validate them since they're added by
+ # the kernel and we can't simply add them to the expected list (the list that we
+ # validate against) because they're added a short time after an address (due to DAD?)
+ # and would create race conditions
+ if int(flags, 16) & (1 << 31):
+ continue
+
+ if dst == b'\0' * 16:
+ dst = None
+ plen = None
+ if gw == b'\0' * 16:
+ gw = None
+ yield RouteInfo(dst, plen, gw, int(flags, 16) & 0xf, route_ifname)