Introduce support for AmarisoftEPC
* A new abstract generic base class EPC is created
* srsEPC and AmarisoftEPC inherit from that class
* options are loaded from defaults.conf in cascade. First generic "epc",
afterwards the specific enb type.
* A new scenario is added to select the EPC type to use. srsEPC is the
default unless stated by an scenario.
* AmarisoftEPC delegates setup of the tun IP address to an "ifup"
script. As a result, since we run without root permissions (ony with
CAP_NET_ADMIN), the ifup script itself is unablet o set the IP
address. To solve this, we introduce a new osmo-gsm-tester helper
script which must be installed in the slave node which can be called
through sudo to increase privileges to do so.
With this commit, I can already get srsUE<->amarisoftENB<->amarisoftEPC
to pass ping and iperf3 4g tests.
Change-Id: Ia50ea6a74b63b2d688c8d683aea11416ad40a6d3
diff --git a/example/defaults.conf b/example/defaults.conf
index bcd1fa2..56a41e5 100644
--- a/example/defaults.conf
+++ b/example/defaults.conf
@@ -93,12 +93,17 @@
trx_list:
- nominal_power: 25
-srsepc:
+epc:
+ type: srsepc
mcc: 901
mnc: 70
+srsepc:
rlc_drb_mode: UM
enable_pcap: false
+amarisoftepc:
+ license_server_addr: 10.12.1.139
+
enb:
mcc: 901
mnc: 70
diff --git a/example/scenarios/cfg-epc-type@.conf b/example/scenarios/cfg-epc-type@.conf
new file mode 100644
index 0000000..89b7fba
--- /dev/null
+++ b/example/scenarios/cfg-epc-type@.conf
@@ -0,0 +1,3 @@
+config:
+ epc:
+ type: ${param1}
diff --git a/src/osmo_gsm_tester/amarisoft_epc.py b/src/osmo_gsm_tester/amarisoft_epc.py
new file mode 100644
index 0000000..0361f54
--- /dev/null
+++ b/src/osmo_gsm_tester/amarisoft_epc.py
@@ -0,0 +1,203 @@
+# osmo_gsm_tester: specifics for running an SRS EPC process
+#
+# Copyright (C) 2020 by sysmocom - s.f.m.c. GmbH
+#
+# Author: Pau Espin Pedrol <pespin@sysmocom.de>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import pprint
+
+from . import log, util, config, template, process, remote
+from . import epc
+
+#def rlc_drb_mode2qci(rlc_drb_mode):
+# if rlc_drb_mode.upper() == "UM":
+# return 7;
+# elif rlc_drb_mode.upper() == "AM":
+# return 9;
+# raise log.Error('Unexpected rlc_drb_mode', rlc_drb_mode=rlc_drb_mode)
+
+class AmarisoftEPC(epc.EPC):
+
+ REMOTE_DIR = '/osmo-gsm-tester-amarisoftepc'
+ BINFILE = 'ltemme'
+ CFGFILE = 'amarisoft_ltemme.cfg'
+ LOGFILE = 'ltemme.log'
+ IFUPFILE = 'mme-ifup'
+
+ def __init__(self, suite_run, run_node):
+ super().__init__(suite_run, run_node, 'amarisoftepc')
+ self.run_dir = None
+ self.config_file = None
+ self.log_file = None
+ self.ifup_file = None
+ self.process = None
+ self.rem_host = None
+ self.remote_inst = None
+ self.remote_config_file = None
+ self.remote_log_file = None
+ self.remote_ifup_file =None
+ self._bin_prefix = None
+ self.inst = None
+ self.subscriber_list = []
+
+ def bin_prefix(self):
+ if self._bin_prefix is None:
+ self._bin_prefix = os.getenv('AMARISOFT_PATH_EPC', AmarisoftEPC.REMOTE_DIR)
+ return self._bin_prefix
+
+ def cleanup(self):
+ if self.process is None:
+ return
+ if self._run_node.is_local():
+ return
+ # copy back files (may not exist, for instance if there was an early error of process):
+ try:
+ self.rem_host.scpfrom('scp-back-log', self.remote_log_file, self.log_file)
+ except Exception as e:
+ self.log(repr(e))
+
+ def start(self):
+ self.log('Starting amarisoftepc')
+ self.run_dir = util.Dir(self.suite_run.get_test_run_dir().new_dir(self.name()))
+ self.configure()
+ if self._run_node.is_local():
+ self.start_locally()
+ else:
+ self.start_remotely()
+
+ def start_remotely(self):
+ remote_binary = self.remote_inst.child('', AmarisoftEPC.BINFILE)
+ # setting capabilities will later disable use of LD_LIBRARY_PATH from ELF loader -> modify RPATH instead.
+ self.log('Setting RPATH for amarisoftepc')
+ self.rem_host.change_elf_rpath(remote_binary, str(self.remote_inst))
+ # amarisoftepc requires CAP_NET_ADMIN to create tunnel devices: ioctl(TUNSETIFF):
+ self.log('Applying CAP_NET_ADMIN capability to amarisoftepc')
+ self.rem_host.setcap_net_admin(remote_binary)
+
+ args = (remote_binary, self.remote_config_file)
+
+ self.process = self.rem_host.RemoteProcess(AmarisoftEPC.BINFILE, args)
+ #self.process = self.rem_host.RemoteProcessFixIgnoreSIGHUP(AmarisoftEPC.BINFILE, remote_run_dir, args)
+ self.suite_run.remember_to_stop(self.process)
+ self.process.launch()
+
+ def start_locally(self):
+ binary = self.inst.child('', BINFILE)
+
+ env = {}
+ # setting capabilities will later disable use of LD_LIBRARY_PATH from ELF loader -> modify RPATH instead.
+ self.log('Setting RPATH for amarisoftepc')
+ util.change_elf_rpath(binary, util.prepend_library_path(str(self.inst)), self.run_dir.new_dir('patchelf'))
+ # amarisoftepc requires CAP_NET_ADMIN to create tunnel devices: ioctl(TUNSETIFF):
+ self.log('Applying CAP_NET_ADMIN capability to amarisoftepc')
+ util.setcap_net_admin(binary, self.run_dir.new_dir('setcap_net_admin'))
+
+ self.dbg(run_dir=self.run_dir, binary=binary, env=env)
+ args = (binary, os.path.abspath(self.config_file))
+
+ self.process = process.Process(self.name(), self.run_dir, args, env=env)
+ self.suite_run.remember_to_stop(self.process)
+ self.process.launch()
+
+ def configure(self):
+ self.inst = util.Dir(os.path.abspath(self.bin_prefix()))
+ if not self.inst.isfile('', AmarisoftEPC.BINFILE):
+ raise log.Error('No %s binary in' % AmarisoftEPC.BINFILE, self.inst)
+
+ self.config_file = self.run_dir.child(AmarisoftEPC.CFGFILE)
+ self.log_file = self.run_dir.child(AmarisoftEPC.LOGFILE)
+ self.ifup_file = self.run_dir.new_file(AmarisoftEPC.IFUPFILE)
+ os.chmod(self.ifup_file, 0o744) # add execution permission
+ self.dbg(config_file=self.config_file)
+ with open(self.ifup_file, 'w') as f:
+ r = '''#!/bin/sh
+ set -x -e
+ # script + sudoers file available in osmo-gsm-tester.git/utils/{bin,sudoers.d}
+ sudo /usr/local/bin/osmo-gsm-tester_amarisoft_ltemme_ifup.sh "$@"
+ '''
+ f.write(r)
+
+ if not self._run_node.is_local():
+ self.rem_host = remote.RemoteHost(self.run_dir, self._run_node.ssh_user(), self._run_node.ssh_addr())
+ remote_prefix_dir = util.Dir(AmarisoftEPC.REMOTE_DIR)
+ self.remote_inst = util.Dir(remote_prefix_dir.child(os.path.basename(str(self.inst))))
+ remote_run_dir = util.Dir(remote_prefix_dir.child(AmarisoftEPC.BINFILE))
+
+ self.remote_config_file = remote_run_dir.child(AmarisoftEPC.CFGFILE)
+ self.remote_log_file = remote_run_dir.child(AmarisoftEPC.LOGFILE)
+ self.remote_ifup_file = remote_run_dir.child(AmarisoftEPC.IFUPFILE)
+
+ values = super().configure('amarisoftepc')
+
+ logfile = self.log_file if self._run_node.is_local() else self.remote_log_file
+ ifupfile = self.ifup_file if self._run_node.is_local() else self.remote_ifup_file
+ config.overlay(values, dict(epc=dict(log_filename=logfile,
+ ifup_filename=ifupfile)))
+
+ # Set qci for each subscriber:
+ #rlc_drb_mode = values['epc'].get('rlc_drb_mode', None)
+ #assert rlc_drb_mode is not None
+ #for i in range(len(self.subscriber_list)):
+ # self.subscriber_list[i]['qci'] = rlc_drb_mode2qci(rlc_drb_mode)
+ config.overlay(values, dict(epc=dict(hss=dict(subscribers=self.subscriber_list))))
+
+ self.dbg('SRSEPC CONFIG:\n' + pprint.pformat(values))
+
+ with open(self.config_file, 'w') as f:
+ r = template.render(AmarisoftEPC.CFGFILE, values)
+ self.dbg(r)
+ f.write(r)
+
+ if not self._run_node.is_local():
+ self.rem_host.recreate_remote_dir(self.remote_inst)
+ self.rem_host.scp('scp-inst-to-remote', str(self.inst), remote_prefix_dir)
+ self.rem_host.recreate_remote_dir(remote_run_dir)
+ self.rem_host.scp('scp-cfg-to-remote', self.config_file, self.remote_config_file)
+ self.rem_host.scp('scp-ifup-to-remote', self.ifup_file, self.remote_ifup_file)
+
+ def subscriber_add(self, modem, msisdn=None, algo_str=None):
+ if msisdn is None:
+ msisdn = self.suite_run.resources_pool.next_msisdn(modem)
+ modem.set_msisdn(msisdn)
+
+ if algo_str is None:
+ algo_str = modem.auth_algo() or util.OSMO_AUTH_ALGO_NONE
+
+ if algo_str != util.OSMO_AUTH_ALGO_NONE and not modem.ki():
+ raise log.Error("Auth algo %r selected but no KI specified" % algo_str)
+
+ subscriber_id = len(self.subscriber_list) # list index
+ self.subscriber_list.append({'id': subscriber_id, 'imsi': modem.imsi(), 'msisdn': msisdn, 'auth_algo': algo_str, 'ki': modem.ki(), 'opc': None, 'apn_ipaddr': modem.apn_ipaddr()})
+
+ self.log('Add subscriber', msisdn=msisdn, imsi=modem.imsi(), subscriber_id=subscriber_id,
+ algo_str=algo_str)
+ return subscriber_id
+
+ def enb_is_connected(self, enb):
+ # TODO: improve this a bit, like matching IP addr of enb. CTRL iface?
+ # The string is only available in log file, not in stdout:
+ #return 'S1 setup response' in (self.process.get_stdout() or '')
+ return True
+
+ def running(self):
+ return not self.process.terminated()
+
+ def tun_addr(self):
+ # TODO: set proper addr
+ return '192.168.4.1'
+
+# vim: expandtab tabstop=4 shiftwidth=4
diff --git a/src/osmo_gsm_tester/epc.py b/src/osmo_gsm_tester/epc.py
new file mode 100644
index 0000000..da8302c
--- /dev/null
+++ b/src/osmo_gsm_tester/epc.py
@@ -0,0 +1,80 @@
+# osmo_gsm_tester: base classes to share code among EPC subclasses.
+#
+# Copyright (C) 2020 by sysmocom - s.f.m.c. GmbH
+#
+# Author: Pau Espin Pedrol <pespin@sysmocom.de>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from abc import ABCMeta, abstractmethod
+from . import log, config
+
+
+class EPC(log.Origin, metaclass=ABCMeta):
+
+##############
+# PROTECTED
+##############
+ def __init__(self, suite_run, run_node, name):
+ super().__init__(log.C_RUN, '%s' % name)
+ self._addr = run_node.run_addr()
+ self.set_name('%s_%s' % (name, self._addr))
+ self.suite_run = suite_run
+ self._run_node = run_node
+
+ def configure(self, default_specifics):
+ values = dict(epc=config.get_defaults('epc'))
+ config.overlay(values, dict(epc=config.get_defaults(default_specifics)))
+ config.overlay(values, dict(epc=self.suite_run.config().get('epc', {})))
+ config.overlay(values, dict(epc={'run_addr': self.addr()}))
+ return values
+
+########################
+# PUBLIC - INTERNAL API
+########################
+ def cleanup(self):
+ 'Nothing to do by default. Subclass can override if required.'
+ pass
+
+###################
+# PUBLIC (test API included)
+###################
+ @abstractmethod
+ def start(self, epc):
+ 'Starts ENB, it will connect to "epc"'
+ pass
+
+ @abstractmethod
+ def subscriber_add(self, modem, msisdn=None, algo_str=None):
+ pass
+
+ @abstractmethod
+ def enb_is_connected(self, enb):
+ pass
+
+ @abstractmethod
+ def running(self):
+ pass
+
+ @abstractmethod
+ def tun_addr(self):
+ pass
+
+ def addr(self):
+ return self._addr
+
+ def run_node(self):
+ return self._run_node
+
+# vim: expandtab tabstop=4 shiftwidth=4
diff --git a/src/osmo_gsm_tester/resource.py b/src/osmo_gsm_tester/resource.py
index d14ee96..383f09e 100644
--- a/src/osmo_gsm_tester/resource.py
+++ b/src/osmo_gsm_tester/resource.py
@@ -29,7 +29,7 @@
from . import bts_sysmo, bts_osmotrx, bts_osmovirtual, bts_octphy, bts_nanobts, bts_oc2g
from . import modem
from . import ms_osmo_mobile
-from . import srs_ue, srs_enb, amarisoft_enb
+from . import srs_ue, srs_enb, amarisoft_enb, srs_epc, amarisoft_epc
from .util import is_dict, is_list
@@ -122,6 +122,7 @@
{ 'defaults.timeout': schema.STR,
'config.bsc.net.codec_list[]': schema.CODEC,
'config.enb.enable_pcap': schema.BOOL_STR,
+ 'config.epc.type': schema.STR,
'config.epc.rlc_drb_mode': schema.LTE_RLC_DRB_MODE,
'config.epc.enable_pcap': schema.BOOL_STR,
'config.modem.enable_pcap': schema.BOOL_STR,
@@ -143,6 +144,11 @@
'amarisoftenb': amarisoft_enb.AmarisoftENB,
}
+KNOWN_EPC_TYPES = {
+ 'srsepc': srs_epc.srsEPC,
+ 'amarisoftepc': amarisoft_epc.AmarisoftEPC,
+}
+
KNOWN_MS_TYPES = {
# Map None to ofono for forward compability
None: modem.Modem,
diff --git a/src/osmo_gsm_tester/srs_epc.py b/src/osmo_gsm_tester/srs_epc.py
index 947eb7b..708ca5b 100644
--- a/src/osmo_gsm_tester/srs_epc.py
+++ b/src/osmo_gsm_tester/srs_epc.py
@@ -21,6 +21,7 @@
import pprint
from . import log, util, config, template, process, remote
+from . import epc
def rlc_drb_mode2qci(rlc_drb_mode):
if rlc_drb_mode.upper() == "UM":
@@ -29,7 +30,7 @@
return 9;
raise log.Error('Unexpected rlc_drb_mode', rlc_drb_mode=rlc_drb_mode)
-class srsEPC(log.Origin):
+class srsEPC(epc.EPC):
REMOTE_DIR = '/osmo-gsm-tester-srsepc'
BINFILE = 'srsepc'
@@ -39,9 +40,7 @@
LOGFILE = 'srsepc.log'
def __init__(self, suite_run, run_node):
- super().__init__(log.C_RUN, 'srsepc')
- self._addr = run_node.run_addr()
- self.set_name('srsepc_%s' % self._addr)
+ super().__init__(suite_run, run_node, 'srsepc')
self.run_dir = None
self.config_file = None
self.db_file = None
@@ -55,8 +54,6 @@
self.remote_pcap_file = None
self.enable_pcap = False
self.subscriber_list = []
- self.suite_run = suite_run
- self._run_node = run_node
def cleanup(self):
if self.process is None:
@@ -161,9 +158,7 @@
self.pcap_file = self.run_dir.child(srsEPC.PCAPFILE)
self.dbg(config_file=self.config_file, db_file=self.db_file)
- values = dict(epc=config.get_defaults('srsepc'))
- config.overlay(values, dict(epc=self.suite_run.config().get('epc', {})))
- config.overlay(values, dict(epc={'run_addr': self.addr()}))
+ values = super().configure('srsepc')
# Convert parsed boolean string to Python boolean:
self.enable_pcap = util.str2bool(values['epc'].get('enable_pcap', 'false'))
@@ -212,13 +207,7 @@
def running(self):
return not self.process.terminated()
- def addr(self):
- return self._addr
-
def tun_addr(self):
return '172.16.0.1'
- def run_node(self):
- return self._run_node
-
# vim: expandtab tabstop=4 shiftwidth=4
diff --git a/src/osmo_gsm_tester/suite.py b/src/osmo_gsm_tester/suite.py
index 8c79d35..5076671 100644
--- a/src/osmo_gsm_tester/suite.py
+++ b/src/osmo_gsm_tester/suite.py
@@ -25,7 +25,6 @@
from .event_loop import MainLoop
from . import osmo_nitb, osmo_hlr, osmo_mgcpgw, osmo_mgw, osmo_msc, osmo_bsc, osmo_stp, osmo_ggsn, osmo_sgsn, esme, osmocon, ms_driver, iperf3, process
from . import run_node
-from . import srs_epc
class Timeout(Exception):
pass
@@ -374,9 +373,9 @@
def epc(self, run_node=None):
if run_node is None:
run_node = self.run_node()
- epc_obj = srs_epc.srsEPC(self, run_node)
- self.register_for_cleanup(epc_obj)
- return epc_obj
+ epc = epc_obj(self, run_node)
+ self.register_for_cleanup(epc)
+ return epc
def osmocon(self, specifics=None):
conf = self.reserved_resources.get(resource.R_OSMOCON, specifics=specifics)
@@ -505,4 +504,16 @@
raise RuntimeError('No such ENB type is defined: %r' % enb_type)
return enb_class(suite_run, conf)
+def epc_obj(suite_run, run_node):
+ values = dict(epc=config.get_defaults('epc'))
+ config.overlay(values, dict(epc=suite_run.config().get('epc', {})))
+ epc_type = values['epc'].get('type', None)
+ if epc_type is None:
+ raise RuntimeError('EPC type is not defined!')
+ log.dbg('create EPC object', type=epc_type)
+ epc_class = resource.KNOWN_EPC_TYPES.get(epc_type)
+ if epc_class is None:
+ raise RuntimeError('No such EPC type is defined: %r' % epc_type)
+ return epc_class(suite_run, run_node)
+
# vim: expandtab tabstop=4 shiftwidth=4
diff --git a/src/osmo_gsm_tester/templates/amarisoft_ltemme.cfg.tmpl b/src/osmo_gsm_tester/templates/amarisoft_ltemme.cfg.tmpl
new file mode 100644
index 0000000..a2af69a
--- /dev/null
+++ b/src/osmo_gsm_tester/templates/amarisoft_ltemme.cfg.tmpl
@@ -0,0 +1,116 @@
+/* ltemme configuration file
+ * version 2018-10-18
+ * Copyright (C) 2015-2018 Amarisoft
+ */
+{
+
+ license_server: {
+ server_addr: "${epc.license_server_addr}",
+ name: "amarisoft",
+ },
+
+ /* Log filter: syntax: layer.field=value[,...]
+
+ Possible layers are nas, ip, s1ap, gtpu and all. The 'all' layer
+ is used to address all the layers at the same time.
+
+ field values:
+
+ - 'level': the log level of each layer can be set to 'none',
+ 'error', 'info' or 'debug'. Use 'debug' to log all the messages.
+
+ - 'max_size': set the maximum size of the hex dump. 0 means no
+ hex dump. -1 means no limit.
+ */
+ //log_options: "all.level=debug,all.max_size=32",
+ log_options: "all.level=error,all.max_size=0,nas.level=debug,nas.max_size=1,s1ap.level=debug,s1ap.max_size=1",
+ log_filename: "${epc.log_filename}",
+
+ /* Enable remote API and Web interface */
+ com_addr: "${epc.run_addr}:9000",
+
+ /* bind address for GTP-U. Normally = address of the PC, here bound
+ on local interface to be able to run ltemme on the same PC as
+ lteenb. By default, the S1AP SCTP connection is bound on the same
+ address. */
+ gtp_addr: "${epc.run_addr}",
+
+ s1ap_bind_addr: "${epc.run_addr}",
+
+ plmn: "${epc.mcc}${epc.mnc}",
+ mme_group_id: 32769,
+ mme_code: 1,
+
+ /* network name and network short name sent in the EMM information
+ message to the UE */
+ network_name: "Amarisoft Network",
+ network_short_name: "Amarisoft",
+
+ /* Control Plane Cellular IoT EPS optimization support */
+ cp_ciot_opt: true,
+
+ /* Public Data Networks. The first one is the default. */
+ pdn_list: [
+ {
+ /* Some UE requires a specific PDN for data access */
+ pdn_type: "ipv4",
+ access_point_name: "internet",
+ first_ip_addr: "192.168.4.2",
+ last_ip_addr: "192.168.4.254",
+ ip_addr_shift: 2,
+ dns_addr: "8.8.8.8", /* Google DNS address */
+ erabs: [
+ {
+ qci: 9,
+ priority_level: 15,
+ pre_emption_capability: "shall_not_trigger_pre_emption",
+ pre_emption_vulnerability: "not_pre_emptable",
+ },
+ ],
+ },
+ ],
+ /* Setup script for the network interface.
+ If no script is given, no network interface is created.
+ Script is called for each PDN with following parameters:
+ 1) Interface name
+ 2) PDN index
+ 3) Access Point Name
+ 4) IP version: 'ipv4' or 'ipv6'
+ 5) IP address: first IP address for ipv4 and link local address for IPv6
+ 6) First IP address
+ 7) Last IP address
+ */
+ tun_setup_script: "${epc.ifup_filename}",
+
+ /* If true, inter-UE routing is done inside the MME (hence no IP
+ packet is output to the virtual network interface in case of
+ inter-UE communication). Otherwise, it is done by the Linux IP
+ layer. */
+ ue_to_ue_forwarding: false,
+
+ /* NAS ciphering algorithm preference. EEA0 is always the last. */
+ nas_cipher_algo_pref: [ ],
+ /* NAS integrity algorithm preference. EIA0 is always the last. */
+ nas_integ_algo_pref: [ 2, 1 ],
+
+ /* user data base */
+ ue_db: [
+%for sub in epc.hss.subscribers:
+ {
+ sim_algo: "${sub.auth_algo}", /* USIM authentication algorithm: xor, milenage or tuak */
+ imsi: "${sub.imsi}", /* Anritsu Test USIM */
+ amf: 0x9001, /* Authentication Management Field */
+ sqn: "000000000000", /* Sequence Number */
+ K: "${sub.ki}", /* Anritsu Test USIM */
+ /* if true, allow several UEs to have the same IMSI (useful
+ with test SIM cards). They are distinguished with their
+ IMEI. default = false. */
+ multi_sim: true,
+ },
+%endfor
+ /* Add new entries for each IMSI/K */
+ ],
+
+ /* persistent user database */
+ //ue_db_filename: "lte_ue.db",
+}
diff --git a/utils/bin/osmo-gsm-tester_amarisoft_ltemme_ifup.sh b/utils/bin/osmo-gsm-tester_amarisoft_ltemme_ifup.sh
new file mode 100755
index 0000000..d502123
--- /dev/null
+++ b/utils/bin/osmo-gsm-tester_amarisoft_ltemme_ifup.sh
@@ -0,0 +1,16 @@
+#!/bin/bash -e
+ifname="$1" # Interface name
+index="$2" # Network index (PDN index)
+apn="$3" # Access point name
+type="$4" # ipv4 or ipv6
+ifaddr="$5" # Interface address
+addr1="$6" # First IP address
+addr2="$7" # Last IP address
+mask="$8" # Mask
+echo "*** Configuring $type APN[$index] '$apn' on ${ifname}, $ifaddr/$mask, ${addr1}..${addr2}"
+if [ "$type" = "ipv4" ] ; then
+ ifconfig ${ifname} ${ifaddr}/${mask} up
+else
+ ifconfig ${ifname} inet6 add ${addr1}/${mask} up
+fi
+echo "*** done configuring interface ${ifname}"
diff --git a/utils/sudoers.d/osmo-gsm-tester_amarisoft_ltemme_ifup b/utils/sudoers.d/osmo-gsm-tester_amarisoft_ltemme_ifup
new file mode 100644
index 0000000..a8cd298
--- /dev/null
+++ b/utils/sudoers.d/osmo-gsm-tester_amarisoft_ltemme_ifup
@@ -0,0 +1 @@
+%osmo-gsm-tester ALL=(root) NOPASSWD: /usr/local/bin/osmo-gsm-tester_amarisoft_ltemme_ifup.sh