diff mbox series

[1/5] test-runner: Support running hostapd in namespaces

Message ID 20220706165702.3241756-1-andrew.zaborowski@intel.com (mailing list archive)
State Not Applicable, archived
Headers show
Series [1/5] test-runner: Support running hostapd in namespaces | expand

Checks

Context Check Description
tedd_an/pre-ci_am success Success
prestwoj/iwd-alpine-ci-fetch success Fetch PR
prestwoj/iwd-ci-fetch success Fetch PR
prestwoj/iwd-ci-gitlint fail [5/5] test-runner: Mark source directory as safe for git 9: B3 Line contains hard tab characters (\t): " git config --global --add safe.directory /home/balrog/repos/iwd"
prestwoj/iwd-alpine-ci-makedistcheck success Make Distcheck
prestwoj/iwd-ci-makedistcheck success Make Distcheck
prestwoj/iwd-alpine-ci-build success Build - Configure
prestwoj/iwd-ci-build success Build - Configure
prestwoj/iwd-alpine-ci-makecheckvalgrind success Make Check w/Valgrind
prestwoj/iwd-alpine-ci-makecheck success Make Check
prestwoj/iwd-ci-makecheckvalgrind success Make Check w/Valgrind
prestwoj/iwd-ci-clang success clang PASS
prestwoj/iwd-ci-makecheck success Make Check
prestwoj/iwd-alpine-ci-incremental_build success Incremental Build with patches
prestwoj/iwd-ci-incremental_build success Incremental Build with patches
prestwoj/iwd-ci-testrunner fail test-runner - FAIL:

Commit Message

Andrew Zaborowski July 6, 2022, 4:56 p.m. UTC
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.
---
 tools/run-tests | 80 +++++++++++++++++++++++++++++++------------------
 tools/utils.py  |  2 +-
 2 files changed, 52 insertions(+), 30 deletions(-)

Comments

James Prestwood July 6, 2022, 5:57 p.m. UTC | #1
Hi Andrew,

On Wed, 2022-07-06 at 18:56 +0200, Andrew Zaborowski wrote:
> 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.
> ---
>  tools/run-tests | 80 +++++++++++++++++++++++++++++++------------------
>  tools/utils.py  |  2 +-
>  2 files changed, 52 insertions(+), 30 deletions(-)
> 

<snip>

Looks like this broke testEAD (or at least this is the most likely
patch). gitlint also wasn't happy but to be fair I just copied the
bluez config so I may need to take a look at that.

https://patchwork.kernel.org/project/iwd/patch/20220706165702.3241756-1-andrew.zaborowski@intel.com/

I'll go ahead and add you to the maintainers list so you get the CI
notifications.
Andrew Zaborowski July 6, 2022, 10:21 p.m. UTC | #2
On Wed, 6 Jul 2022 at 19:57, James Prestwood <prestwoj@gmail.com> wrote:
> Looks like this broke testEAD (or at least this is the most likely
> patch). gitlint also wasn't happy but to be fair I just copied the
> bluez config so I may need to take a look at that.
>
> https://patchwork.kernel.org/project/iwd/patch/20220706165702.3241756-1-andrew.zaborowski@intel.com/
>
> I'll go ahead and add you to the maintainers list so you get the CI
> notifications.

Thanks.  Honestly I was planning on looking at the CI results and
acting on failures.  Locally I could only test that the commit was
causing no new failures and testEAD was among the 6 already-failing
tests in my setup.  I'll take a look at the results now.

Best regards
diff mbox series

Patch

diff --git a/tools/run-tests b/tools/run-tests
index 565847df..f916fba6 100755
--- a/tools/run-tests
+++ b/tools/run-tests
@@ -58,16 +58,19 @@  def exit_vm():
 	runner.stop()
 
 class Interface:
-	def __init__(self, name, config):
+	def __init__(self, name, config, ns=None):
 		self.name = name
 		self.ctrl_interface = '/var/run/hostapd/' + name
 		self.config = config
+		self.ns = ns
 
 	def __del__(self):
-		Process(['iw', 'dev', self.name, 'del']).wait()
+		Process(['iw', 'dev', self.name, 'del'],
+				namespace=self.ns.name if self.ns else None).wait()
 
 	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 if self.ns else None).wait()
 
 class Radio:
 	def __init__(self, name):
@@ -75,11 +78,16 @@  class Radio:
 		# hostapd will reset this if this radio is used by it
 		self.use = 'iwd'
 		self.interface = None
+		self.ns = None
 
 	def __del__(self):
 		print("Removing radio %s" % self.name)
 		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):
 		global intf_id
 
@@ -87,19 +95,21 @@  class Radio:
 
 		intf_id += 1
 
-		self.interface = Interface(ifname, config)
+		self.interface = Interface(ifname, config, ns=self.ns)
 		self.use = use
 
 		Process(['iw', 'phy', self.name, 'interface', 'add', ifname,
-				'type', 'managed']).wait()
+				'type', 'managed'], namespace=self.ns.name if self.ns else None).wait()
 
 		return self.interface
 
 	def __str__(self):
 		ret = self.name + ':\n'
-		ret += '\tUsed By: %s ' % self.use
+		ret += '\tUsed By: %s' % self.use
 		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'
 
@@ -188,7 +198,7 @@  class Hostapd:
 		A set of running hostapd instances. This is really just a single
 		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):
 			raise Exception("Config (%d) and radio (%d) list length not equal" % \
 						(len(configs), len(radios)))
@@ -198,8 +208,8 @@  class Hostapd:
 		Process(['ip', 'link', 'set', 'eth0', '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)]
 
 		ifaces = [rad.interface.name for rad in radios]
@@ -227,7 +237,7 @@  class Hostapd:
 		if Process.is_verbose('hostapd'):
 			args.append('-d')
 
-		self.process = Process(args)
+		self.process = Process(args, namespace=ns.name)
 
 		self.process.wait_for_socket(self.global_ctrl_iface, 30)
 
@@ -397,32 +407,42 @@  class TestContext(Namespace):
 			#          tests. In this case you would not care what
 			#          was using each radio, just that there was
 			#          enough to run all tests.
-			nradios = 0
-			for k, _ in settings.items():
-				if k == 'radius_server':
-					continue
-				nradios += 1
-
-			hapd_radios = self.radios[:nradios]
-
+			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_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_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))
 
 		radius_config = settings.get('radius_server', None)
 
-		self.hostapd = Hostapd(hapd_radios, hapd_configs, radius_config)
-		self.hostapd.attach_cli()
+		self.hostapd = [Hostapd(ns, radios, configs, radius_config)
+				for ns, radios, configs in hapd_processes]
+
+		for hapd in self.hostapd:
+			hapd.attach_cli()
 
 	def get_frequencies(self):
 		frequencies = []
 
-		for hapd in self.hostapd.instances:
-			frequencies.append(hapd.cli.frequency)
+		for hapd in self.hostapd:
+			frequencies += [instance.cli.frequency for instance in hapd.instances]
 
 		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):
 		if 'WPA_SUPPLICANT' not in self.hw_config:
 			return
@@ -543,11 +563,13 @@  class TestContext(Namespace):
 		for arg in vars(self.args):
 			ret += '\t --%s %s\n' % (arg, str(getattr(self.args, arg)))
 
-		ret += 'Hostapd:\n'
 		if self.hostapd:
-			for h in self.hostapd.instances:
-				ret += '\t%s\n' % str(h)
+			for hapd in self.hostapd:
+				ret += 'Hostapd (ns=%s):\n' % (hapd.ns.name,)
+				for h in hapd.instances:
+					ret += '\t%s\n' % (str(h),)
 		else:
+			ret += 'Hostapd:\n'
 			ret += '\tNo Hostapd instances\n'
 
 		info = self.meminfo_to_dict()
diff --git a/tools/utils.py b/tools/utils.py
index f3e12a85..bc030230 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -324,7 +324,7 @@  class Namespace:
 
 		Process(['ip', 'netns', 'add', name]).wait()
 		for r in radios:
-			Process(['iw', 'phy', r.name, 'set', 'netns', 'name', name]).wait()
+			r.set_namespace(self)
 
 		self.start_dbus()