mirror of
https://git.kernel.org/pub/scm/network/wireless/iwd.git
synced 2024-11-22 06:29:23 +01:00
test-runner: Support running hostapd in namespaces
The kernel will not let us test some scenarios of communication between two hwsim radios (e.g. STA and AP) if they're in the same net namespace. For example, when connected, you can't add normal IPv4 subnet routes for the same subnet on two different interfaces in one namespace (you'd either get an EEXIST or you'd replace the other route), you can set different metrics on the routes but that won't fix IP routing. For testNetconfig the result is that communication works for DHCP before we get the inital lease but renewals won't work because they're unicast. Allow hostapd to run on a radio that has been moved to a different namespace in hw.conf so we don't have to work around these issues.
This commit is contained in:
parent
6022ebaa36
commit
39fa246d9e
@ -58,28 +58,34 @@ def exit_vm():
|
|||||||
runner.stop()
|
runner.stop()
|
||||||
|
|
||||||
class Interface:
|
class Interface:
|
||||||
def __init__(self, name, config):
|
def __init__(self, name, config, ns):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.ctrl_interface = '/var/run/hostapd/' + name
|
self.ctrl_interface = '/var/run/hostapd/' + name
|
||||||
self.config = config
|
self.config = config
|
||||||
|
self.ns = ns
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
Process(['iw', 'dev', self.name, 'del']).wait()
|
Process(['iw', 'dev', self.name, 'del'], namespace=self.ns.name).wait()
|
||||||
|
|
||||||
def set_interface_state(self, state):
|
def set_interface_state(self, state):
|
||||||
Process(['ip', 'link', 'set', self.name, state]).wait()
|
Process(['ip', 'link', 'set', self.name, state], namespace=self.ns.name).wait()
|
||||||
|
|
||||||
class Radio:
|
class Radio:
|
||||||
def __init__(self, name):
|
def __init__(self, name, default_ns):
|
||||||
self.name = name
|
self.name = name
|
||||||
# hostapd will reset this if this radio is used by it
|
# hostapd will reset this if this radio is used by it
|
||||||
self.use = 'iwd'
|
self.use = 'iwd'
|
||||||
self.interface = None
|
self.interface = None
|
||||||
|
self.ns = default_ns
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
print("Removing radio %s" % self.name)
|
print("Removing radio %s" % self.name)
|
||||||
self.interface = None
|
self.interface = None
|
||||||
|
|
||||||
|
def set_namespace(self, ns):
|
||||||
|
self.ns = ns
|
||||||
|
Process(['iw', 'phy', self.name, 'set', 'netns', 'name', ns.name]).wait()
|
||||||
|
|
||||||
def create_interface(self, config, use):
|
def create_interface(self, config, use):
|
||||||
global intf_id
|
global intf_id
|
||||||
|
|
||||||
@ -87,19 +93,21 @@ class Radio:
|
|||||||
|
|
||||||
intf_id += 1
|
intf_id += 1
|
||||||
|
|
||||||
self.interface = Interface(ifname, config)
|
self.interface = Interface(ifname, config, self.ns)
|
||||||
self.use = use
|
self.use = use
|
||||||
|
|
||||||
Process(['iw', 'phy', self.name, 'interface', 'add', ifname,
|
Process(['iw', 'phy', self.name, 'interface', 'add', ifname,
|
||||||
'type', 'managed']).wait()
|
'type', 'managed'], namespace=self.ns.name).wait()
|
||||||
|
|
||||||
return self.interface
|
return self.interface
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
ret = self.name + ':\n'
|
ret = self.name + ':\n'
|
||||||
ret += '\tUsed By: %s ' % self.use
|
ret += '\tUsed By: %s' % self.use
|
||||||
if self.interface:
|
if self.interface:
|
||||||
ret += '(%s)' % self.interface.name
|
ret += ' (%s)' % self.interface.name
|
||||||
|
if self.ns is not None:
|
||||||
|
ret += ' (ns=%s)' % self.ns.name
|
||||||
|
|
||||||
ret += '\n'
|
ret += '\n'
|
||||||
|
|
||||||
@ -113,7 +121,7 @@ class VirtualRadio(Radio):
|
|||||||
than the command line.
|
than the command line.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, name, cfg=None):
|
def __init__(self, name, default_ns, cfg=None):
|
||||||
global config
|
global config
|
||||||
|
|
||||||
self.disable_cipher = None
|
self.disable_cipher = None
|
||||||
@ -129,7 +137,7 @@ class VirtualRadio(Radio):
|
|||||||
iftype_disable=self.disable_iftype,
|
iftype_disable=self.disable_iftype,
|
||||||
cipher_disable=self.disable_cipher)
|
cipher_disable=self.disable_cipher)
|
||||||
|
|
||||||
super().__init__(self._radio.name)
|
super().__init__(self._radio.name, default_ns)
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
super().__del__()
|
super().__del__()
|
||||||
@ -188,7 +196,7 @@ class Hostapd:
|
|||||||
A set of running hostapd instances. This is really just a single
|
A set of running hostapd instances. This is really just a single
|
||||||
process since hostapd can be started with multiple config files.
|
process since hostapd can be started with multiple config files.
|
||||||
'''
|
'''
|
||||||
def __init__(self, radios, configs, radius):
|
def __init__(self, ns, radios, configs, radius):
|
||||||
if len(configs) != len(radios):
|
if len(configs) != len(radios):
|
||||||
raise Exception("Config (%d) and radio (%d) list length not equal" % \
|
raise Exception("Config (%d) and radio (%d) list length not equal" % \
|
||||||
(len(configs), len(radios)))
|
(len(configs), len(radios)))
|
||||||
@ -198,8 +206,8 @@ class Hostapd:
|
|||||||
Process(['ip', 'link', 'set', 'eth0', 'up']).wait()
|
Process(['ip', 'link', 'set', 'eth0', 'up']).wait()
|
||||||
Process(['ip', 'link', 'set', 'eth1', 'up']).wait()
|
Process(['ip', 'link', 'set', 'eth1', 'up']).wait()
|
||||||
|
|
||||||
self.global_ctrl_iface = '/var/run/hostapd/ctrl'
|
self.ns = ns
|
||||||
|
self.global_ctrl_iface = '/var/run/hostapd/ctrl' + (str(ns.name) if ns.name else 'main')
|
||||||
self.instances = [HostapdInstance(c, r) for c, r in zip(configs, radios)]
|
self.instances = [HostapdInstance(c, r) for c, r in zip(configs, radios)]
|
||||||
|
|
||||||
ifaces = [rad.interface.name for rad in radios]
|
ifaces = [rad.interface.name for rad in radios]
|
||||||
@ -227,7 +235,7 @@ class Hostapd:
|
|||||||
if Process.is_verbose('hostapd'):
|
if Process.is_verbose('hostapd'):
|
||||||
args.append('-d')
|
args.append('-d')
|
||||||
|
|
||||||
self.process = Process(args)
|
self.process = Process(args, namespace=ns.name)
|
||||||
|
|
||||||
self.process.wait_for_socket(self.global_ctrl_iface, 30)
|
self.process.wait_for_socket(self.global_ctrl_iface, 30)
|
||||||
|
|
||||||
@ -340,7 +348,7 @@ class TestContext(Namespace):
|
|||||||
if self.hw_config.has_section(name):
|
if self.hw_config.has_section(name):
|
||||||
rad_config = self.hw_config[name]
|
rad_config = self.hw_config[name]
|
||||||
|
|
||||||
self.radios.append(VirtualRadio(name, rad_config))
|
self.radios.append(VirtualRadio(name, self, rad_config))
|
||||||
|
|
||||||
def discover_radios(self):
|
def discover_radios(self):
|
||||||
import pyroute2
|
import pyroute2
|
||||||
@ -362,7 +370,7 @@ class TestContext(Namespace):
|
|||||||
break
|
break
|
||||||
|
|
||||||
print('Discovered radios: %s' % str(phys))
|
print('Discovered radios: %s' % str(phys))
|
||||||
self.radios = [Radio(name) for name in phys]
|
self.radios = [Radio(name, self) for name in phys]
|
||||||
|
|
||||||
def start_radios(self):
|
def start_radios(self):
|
||||||
reg_domain = self.hw_config['SETUP'].get('reg_domain', None)
|
reg_domain = self.hw_config['SETUP'].get('reg_domain', None)
|
||||||
@ -397,32 +405,44 @@ class TestContext(Namespace):
|
|||||||
# tests. In this case you would not care what
|
# tests. In this case you would not care what
|
||||||
# was using each radio, just that there was
|
# was using each radio, just that there was
|
||||||
# enough to run all tests.
|
# enough to run all tests.
|
||||||
nradios = 0
|
|
||||||
for k, _ in settings.items():
|
|
||||||
if k == 'radius_server':
|
|
||||||
continue
|
|
||||||
nradios += 1
|
|
||||||
|
|
||||||
hapd_radios = self.radios[:nradios]
|
|
||||||
|
|
||||||
else:
|
|
||||||
hapd_radios = [rad for rad in self.radios if rad.name in settings]
|
|
||||||
|
|
||||||
hapd_configs = [conf for rad, conf in settings.items() if rad != 'radius_server']
|
hapd_configs = [conf for rad, conf in settings.items() if rad != 'radius_server']
|
||||||
|
hapd_processes = [(self, self.radios[:len(hapd_configs)], hapd_configs)]
|
||||||
|
else:
|
||||||
|
hapd_processes = []
|
||||||
|
for ns in [self] + self.namespaces:
|
||||||
|
ns_radios = [rad for rad in ns.radios if rad.name in settings]
|
||||||
|
if len(ns_radios):
|
||||||
|
ns_configs = [settings[rad.name] for rad in ns_radios]
|
||||||
|
hapd_processes.append((ns, ns_radios, ns_configs))
|
||||||
|
if not hapd_processes:
|
||||||
|
hapd_processes.append((self, [], []))
|
||||||
|
|
||||||
radius_config = settings.get('radius_server', None)
|
radius_config = settings.get('radius_server', None)
|
||||||
|
|
||||||
self.hostapd = Hostapd(hapd_radios, hapd_configs, radius_config)
|
self.hostapd = [Hostapd(ns, radios, configs, radius_config)
|
||||||
self.hostapd.attach_cli()
|
for ns, radios, configs in hapd_processes]
|
||||||
|
|
||||||
|
for hapd in self.hostapd:
|
||||||
|
hapd.attach_cli()
|
||||||
|
|
||||||
def get_frequencies(self):
|
def get_frequencies(self):
|
||||||
frequencies = []
|
frequencies = []
|
||||||
|
|
||||||
for hapd in self.hostapd.instances:
|
for hapd in self.hostapd:
|
||||||
frequencies.append(hapd.cli.frequency)
|
frequencies += [instance.cli.frequency for instance in hapd.instances]
|
||||||
|
|
||||||
return frequencies
|
return frequencies
|
||||||
|
|
||||||
|
def get_hapd_instance(self, config=None):
|
||||||
|
instances = [i for hapd in self.hostapd for i in hapd.instances]
|
||||||
|
|
||||||
|
if config is None:
|
||||||
|
return instances[0]
|
||||||
|
|
||||||
|
for hapd in instances:
|
||||||
|
if hapd.config == config:
|
||||||
|
return hapd
|
||||||
|
|
||||||
def start_wpas_interfaces(self):
|
def start_wpas_interfaces(self):
|
||||||
if 'WPA_SUPPLICANT' not in self.hw_config:
|
if 'WPA_SUPPLICANT' not in self.hw_config:
|
||||||
return
|
return
|
||||||
@ -543,11 +563,13 @@ class TestContext(Namespace):
|
|||||||
for arg in vars(self.args):
|
for arg in vars(self.args):
|
||||||
ret += '\t --%s %s\n' % (arg, str(getattr(self.args, arg)))
|
ret += '\t --%s %s\n' % (arg, str(getattr(self.args, arg)))
|
||||||
|
|
||||||
ret += 'Hostapd:\n'
|
|
||||||
if self.hostapd:
|
if self.hostapd:
|
||||||
for h in self.hostapd.instances:
|
for hapd in self.hostapd:
|
||||||
ret += '\t%s\n' % str(h)
|
ret += 'Hostapd (ns=%s):\n' % (hapd.ns.name,)
|
||||||
|
for h in hapd.instances:
|
||||||
|
ret += '\t%s\n' % (str(h),)
|
||||||
else:
|
else:
|
||||||
|
ret += 'Hostapd:\n'
|
||||||
ret += '\tNo Hostapd instances\n'
|
ret += '\tNo Hostapd instances\n'
|
||||||
|
|
||||||
info = self.meminfo_to_dict()
|
info = self.meminfo_to_dict()
|
||||||
|
@ -324,7 +324,7 @@ class Namespace:
|
|||||||
|
|
||||||
Process(['ip', 'netns', 'add', name]).wait()
|
Process(['ip', 'netns', 'add', name]).wait()
|
||||||
for r in radios:
|
for r in radios:
|
||||||
Process(['iw', 'phy', r.name, 'set', 'netns', 'name', name]).wait()
|
r.set_namespace(self)
|
||||||
|
|
||||||
self.start_dbus()
|
self.start_dbus()
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user