From fd4c14404908c683a00e9132a50fdb9a91ccd698 Mon Sep 17 00:00:00 2001 From: Pau Espin Pedrol Date: Thu, 25 Oct 2018 17:37:23 +0200 Subject: [PATCH] Add support to test gprs IPv4 data plane Since the modem iface and the GGSN iface are on the same host/netns, it's really difficult to conveniently test data plane without getting routing loops. As a result, either GGSN or modem iface must be moved to a different namespace. The decision after a few discussions was finally to move modem interfaces to a different netns. Expected setup: * ofono is patched to avoid removing modem if it detects through udev that its net iface was removed (due to for instance, net iface being moved to another netns and thus not being reachable anymore by systemd-udev process running in root netns). * After ofono is started (and successfully configured all the modems and detected its net ifaces through syfs/udev), script "modem-netns-setup.py start" which creates a netns for each modem, naming it after its usb path ID. net ifaces for that modem are moved into its netns. * Modem is configured to use 802-3 data format, and as a result the net iface is configured through DHCP (DHCP req only replied AFTER pdp ctx is activated!). * Since osmo-gsm-tester knowns the modem USB path ID (available in resources.conf), it can run required steps (ifup, DHCP) to configure the interface. The interface name is provided by ofono to osmo-gsm-tester. * As a result, any process willing to transmit data through the modem must be in the modem netns. Related: OS#2308 Change-Id: Icb06bdfcdd37c797be95ab5addb28da2d9f6681c --- example/resources.conf.prod | 6 +++--- example/resources.conf.rnd | 6 +++--- src/osmo_gsm_tester/modem.py | 29 +++++++++++++++++++++++++--- src/osmo_gsm_tester/process.py | 30 ++++++++++++++++++++++++++--- src/osmo_gsm_tester/suite.py | 2 +- suites/gprs/ping.py | 6 ++++-- utils/osmo-gsm-tester_netns_exec.sh | 5 +++++ 7 files changed, 69 insertions(+), 15 deletions(-) create mode 100755 utils/osmo-gsm-tester_netns_exec.sh diff --git a/example/resources.conf.prod b/example/resources.conf.prod index 22134e73..b12a7bc9 100644 --- a/example/resources.conf.prod +++ b/example/resources.conf.prod @@ -122,14 +122,14 @@ modem: ki: 'EBAB63D06C3F546A16C977CB40E57C68' auth_algo: 'comp128v1' ciphers: [a5_0, a5_1] - features: ['sms', 'voice', 'ussd', 'gprs', 'sim'] + features: ['sms', 'voice', 'ussd', 'sim'] - label: sierra_2nd path: '/sys/devices/pci0000:00/0000:00:12.2/usb1/1-5/1-5.4/1-5.4.1/1-5.4.1.3' ki: 'EBD2B5F6CF3374106D0A66C11F922001' auth_algo: 'comp128v1' ciphers: [a5_0, a5_1] - features: ['sms', 'voice', 'ussd', 'gprs', 'sim'] + features: ['sms', 'voice', 'ussd', 'sim'] - label: ec20 path: '/sys/devices/pci0000:00/0000:00:12.2/usb1/1-5/1-5.4/1-5.4.1/1-5.4.1.6' @@ -143,7 +143,7 @@ modem: ki: '5752B3F43277C35D2D1D957007DF74E2' auth_algo: 'comp128v1' ciphers: [a5_0, a5_1] - features: ['gprs', 'sim'] + features: ['sim'] osmocon_phone: - serial_device: '/dev/serial/by-id/usb-Silicon_Labs_CP2104_USB_to_UART_Bridge_Controller_00897B41-if00-port0' diff --git a/example/resources.conf.rnd b/example/resources.conf.rnd index dbdf3cce..63650e18 100644 --- a/example/resources.conf.rnd +++ b/example/resources.conf.rnd @@ -72,14 +72,14 @@ modem: ki: '80A37E6FDEA931EAC92FFA5F671EFEAD' auth_algo: 'comp128v1' ciphers: [a5_0, a5_1] - features: ['sms', 'voice', 'ussd', 'gprs', 'sim'] + features: ['sms', 'voice', 'ussd', 'sim'] - label: sierra_2nd path: '/sys/devices/pci0000:00/0000:00:12.2/usb1/1-1/1-1.3' ki: '00969E283349D354A8239E877F2E0866' auth_algo: 'comp128v1' ciphers: [a5_0, a5_1] - features: ['sms', 'voice', 'ussd', 'gprs', 'sim'] + features: ['sms', 'voice', 'ussd', 'sim'] - label: ec20 path: '/sys/devices/pci0000:00/0000:00:12.2/usb1/1-1/1-1.6' @@ -93,7 +93,7 @@ modem: ki: '2F70DCA43C45ACB97E947FDD0C7CA30A' auth_algo: 'comp128v1' ciphers: [a5_0, a5_1] - features: ['gprs', 'sim'] + features: ['sim'] osmocon_phone: - serial_device: '/dev/serial/by-id/usb-Silicon_Labs_CP2104_USB_to_UART_Bridge_Controller_0089279D-if00-port0' diff --git a/src/osmo_gsm_tester/modem.py b/src/osmo_gsm_tester/modem.py index 21b208c8..d35933a0 100644 --- a/src/osmo_gsm_tester/modem.py +++ b/src/osmo_gsm_tester/modem.py @@ -17,10 +17,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from . import log, util, sms +from . import log, util, sms, process from .event_loop import MainLoop from pydbus import SystemBus, Variant +import os # Required for Gio.Cancellable. # See https://lazka.github.io/pgi-docs/Gio-2.0/classes/Cancellable.html#Gio.Cancellable @@ -323,15 +324,17 @@ class Modem(log.Origin): CTX_PROT_IPv6 = 'ipv6' CTX_PROT_IPv46 = 'dual' - def __init__(self, conf): + def __init__(self, suite_run, conf): + self.suite_run = suite_run self.conf = conf self.syspath = conf.get('path') self.dbuspath = get_dbuspath_from_syspath(self.syspath) super().__init__(log.C_TST, self.dbuspath) - self.dbg('creating from syspath %s', self.syspath) + self.dbg('creating from syspath %s' % self.syspath) self.msisdn = None self._ki = None self._imsi = None + self.run_dir = util.Dir(self.suite_run.get_test_run_dir().new_dir(self.name().strip('/'))) self.sms_received_list = [] self.dbus = ModemDbusInteraction(self.dbuspath) self.register_attempts = 0 @@ -358,6 +361,9 @@ class Modem(log.Origin): self.dbus.cleanup() self.dbus = None + def netns(self): + return os.path.basename(self.syspath.rstrip('/')) + def properties(self, *args, **kwargs): '''Return a dict of properties on this modem. For the actual arguments, see ModemDbusInteraction.properties(), which this function calls. The @@ -627,6 +633,23 @@ class Modem(log.Origin): connmgr.RemoveContext(ctx_id) self.log('context deactivated', path=ctx_id) + def run_netns_wait(self, name, popen_args): + proc = process.NetNSProcess(name, self.run_dir.new_dir(name), self.netns(), popen_args, + env={}) + process.run_proc_sync(proc) + + def setup_context_data_plane(self, ctx_id): + self.dbg('setup_context_data', path=ctx_id) + ctx = systembus_get(ctx_id) + ctx_settings = ctx.GetProperties().get('Settings', None) + if not ctx_settings: + raise log.Error('%s no Settings found! No way to get iface!' % ctx_id) + iface = ctx_settings.get('Interface', None) + if not iface: + raise log.Error('%s Settings contains no iface! %r' % (ctx_id, repr(ctx_settings))) + self.run_netns_wait('ifup', ('ip', 'link', 'set', 'dev', iface, 'up')) + self.run_netns_wait('dhcp', ('udhcpc', '-q', '-i', iface)) + def sms_send(self, to_msisdn_or_modem, *tokens): if isinstance(to_msisdn_or_modem, Modem): to_msisdn = to_msisdn_or_modem.msisdn diff --git a/src/osmo_gsm_tester/process.py b/src/osmo_gsm_tester/process.py index fb5c6f6b..a845f7fa 100644 --- a/src/osmo_gsm_tester/process.py +++ b/src/osmo_gsm_tester/process.py @@ -100,6 +100,9 @@ class Process(log.Origin): time.sleep(wait_step) return False + def send_signal(self, sig): + os.kill(self.process_obj.pid, sig) + def terminate(self): if self.process_obj is None: return @@ -109,21 +112,21 @@ class Process(log.Origin): while True: # first try SIGINT to allow stdout+stderr flushing self.log('Terminating (SIGINT)') - os.kill(self.process_obj.pid, signal.SIGINT) + self.send_signal(signal.SIGINT) self.killed = signal.SIGINT if self._poll_termination(): break # SIGTERM maybe? self.log('Terminating (SIGTERM)') - self.process_obj.terminate() + self.send_signal(signal.SIGTERM) self.killed = signal.SIGTERM if self._poll_termination(): break # out of patience self.log('Terminating (SIGKILL)') - self.process_obj.kill() + self.send_signal(signal.SIGKILL) self.killed = signal.SIGKILL break; @@ -236,6 +239,22 @@ class RemoteProcess(Process): ' '.join(self.popen_args))] self.dbg(self.popen_args, dir=self.run_dir, conf=self.popen_kwargs) +class NetNSProcess(Process): + NETNS_EXEC_BIN = 'osmo-gsm-tester_netns_exec.sh' + def __init__(self, name, run_dir, netns, popen_args, **popen_kwargs): + super().__init__(name, run_dir, popen_args, **popen_kwargs) + self.netns = netns + + self.popen_args = ['sudo', self.NETNS_EXEC_BIN, self.netns] + list(popen_args) + self.dbg(self.popen_args, dir=self.run_dir, conf=self.popen_kwargs) + + # HACK: Since we run under sudo, only way to kill root-owned process is to kill as root... + # This function is overwritten from Process. + def send_signal(self, sig): + kill_cmd = ('kill', '-%d' % int(sig), str(self.process_obj.pid)) + run_local_netns_sync(self.run_dir, self.name()+"-kill", self.netns, kill_cmd) + + def run_proc_sync(proc): try: proc.launch() @@ -252,6 +271,11 @@ def run_local_sync(run_dir, name, popen_args): proc = Process(name, run_dir, popen_args) run_proc_sync(proc) +def run_local_netns_sync(run_dir, name, netns, popen_args): + run_dir =run_dir.new_dir(name) + proc = NetNSProcess(name, run_dir, netns, popen_args) + run_proc_sync(proc) + def run_remote_sync(run_dir, remote_user, remote_addr, name, popen_args, remote_cwd=None): run_dir = run_dir.new_dir(name) proc = RemoteProcess(name, run_dir, remote_user, remote_addr, remote_cwd, popen_args) diff --git a/src/osmo_gsm_tester/suite.py b/src/osmo_gsm_tester/suite.py index 22a47a23..67ddefdc 100644 --- a/src/osmo_gsm_tester/suite.py +++ b/src/osmo_gsm_tester/suite.py @@ -328,7 +328,7 @@ class SuiteRun(log.Origin): def modem(self, specifics=None): conf = self.reserved_resources.get(resource.R_MODEM, specifics=specifics) self.dbg('create Modem object', conf=conf) - ms = modem.Modem(conf) + ms = modem.Modem(self, conf) self.register_for_cleanup(ms) return ms diff --git a/suites/gprs/ping.py b/suites/gprs/ping.py index 1647445d..9186fe62 100755 --- a/suites/gprs/ping.py +++ b/suites/gprs/ping.py @@ -48,8 +48,10 @@ wait(ms.is_attached) # We need to use inet46 since ofono qmi only uses ipv4v6 eua (OS#2713) ctx_id_v4 = ms.activate_context(apn='inet46', protocol=ms.CTX_PROT_IPv4) -sleep(5) -# TODO: send ping to server or open TCP conn with a socket in python +print("Setting up data plan for %r" % repr(ctx_id_v4)) +ms.setup_context_data_plane(ctx_id_v4) +print("Running 10 ping requests for %r" % repr(ctx_id_v4)) +ms.run_netns_wait('ping', ('ping', '-c', '10', ggsn.addr())) ms.deactivate_context(ctx_id_v4) # We need to use inet46 since ofono qmi only uses ipv4v6 eua (OS#2713) diff --git a/utils/osmo-gsm-tester_netns_exec.sh b/utils/osmo-gsm-tester_netns_exec.sh new file mode 100755 index 00000000..336b746e --- /dev/null +++ b/utils/osmo-gsm-tester_netns_exec.sh @@ -0,0 +1,5 @@ +#!/bin/bash +netns="$1" +shift +#TODO: Later on I may want to call myself with specific ENV and calling sudo in order to run inside the netns but with dropped privileges +ip netns exec $netns "$@"