blob: 8fe30290c532872e785e143a9b309edee8901c9f [file] [log] [blame]
Neels Hofmeyr3531a192017-03-28 14:30:28 +02001# osmo_gsm_tester: language snippets
2#
3# Copyright (C) 2016-2017 by sysmocom - s.f.m.c. GmbH
4#
5# Author: Neels Hofmeyr <neels@hofmeyr.de>
6#
7# This program is free software: you can redistribute it and/or modify
Harald Welte27205342017-06-03 09:51:45 +02008# it under the terms of the GNU General Public License as
Neels Hofmeyr3531a192017-03-28 14:30:28 +02009# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Harald Welte27205342017-06-03 09:51:45 +020015# GNU General Public License for more details.
Neels Hofmeyr3531a192017-03-28 14:30:28 +020016#
Harald Welte27205342017-06-03 09:51:45 +020017# You should have received a copy of the GNU General Public License
Neels Hofmeyr3531a192017-03-28 14:30:28 +020018# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20import os
21import sys
22import time
23import fcntl
24import hashlib
25import tempfile
26import shutil
27import atexit
28import threading
29import importlib.util
30import fcntl
31import tty
Neels Hofmeyracf0c932017-05-06 16:05:33 +020032import readline
Pau Espin Pedrol13143bc2017-05-08 17:07:28 +020033import subprocess
Neels Hofmeyr3531a192017-03-28 14:30:28 +020034
Neels Hofmeyr0af893c2017-12-14 15:18:05 +010035# This mirrors enum osmo_auth_algo in libosmocore/include/osmocom/crypt/auth.h
36# so that the index within the tuple matches the enum value.
37OSMO_AUTH_ALGO_NONE = 'none'
38ENUM_OSMO_AUTH_ALGO = (OSMO_AUTH_ALGO_NONE, 'comp128v1', 'comp128v2', 'comp128v3', 'xor', 'milenage')
39
40def osmo_auth_algo_by_name(algo_str):
41 'Return enum osmo_auth_algo numeric value as from libosmocore, raise ValueError if not defined.'
42 return ENUM_OSMO_AUTH_ALGO.index(algo_str.lower())
43
Pau Espin Pedrole39c6f12017-05-08 16:21:38 +020044def prepend_library_path(path):
45 lp = os.getenv('LD_LIBRARY_PATH')
46 if not lp:
47 return path
48 return path + ':' + lp
49
Pau Espin Pedroled6d4452017-10-25 17:06:53 +020050def change_elf_rpath(binary, paths, run_dir):
51 '''
52 Change RPATH field in ELF executable binary.
53 This feature can be used to tell the loaded to load the trial libraries, as
54 LD_LIBRARY_PATH is disabled for paths with modified capabilities.
55 '''
56 from .process import Process
57 proc = Process('patchelf', run_dir, ['patchelf', '--set-rpath', paths, binary])
58 proc.launch()
59 proc.wait()
60 if proc.result != 0:
61 raise RuntimeError('patchelf finished with err code %d' % proc.result)
62
Pau Espin Pedrol13143bc2017-05-08 17:07:28 +020063def ip_to_iface(ip):
64 try:
65 for iface in os.listdir('/sys/class/net'):
66 proc = subprocess.Popen(['ip', 'addr', 'show', 'dev', iface], stdout=subprocess.PIPE, universal_newlines=True)
67 for line in proc.stdout.readlines():
68 if 'inet' in line and ' ' + ip + '/' in line:
Pau Espin Pedrol74a76762017-08-10 12:07:49 +020069 return line.split()[-1]
Pau Espin Pedrol13143bc2017-05-08 17:07:28 +020070 except Exception as e:
71 pass
72 return None
73
Pau Espin Pedrola238ed92018-03-26 13:06:12 +020074def dst_ip_get_local_bind(ip):
75 '''Retrieve default IP addr to bind to in order to route traffic to dst addr'''
76 try:
77 proc = subprocess.Popen(['ip', 'route', 'get', ip], stdout=subprocess.PIPE, universal_newlines=True)
78 output = proc.stdout.readlines()
79 words = output[0].split()
80 i = 0
81 while i < len(words):
82 if words[i] == 'src':
83 return words[i+1]
84 i += 1
85 except Exception as e:
86 pass
87 return None
88
Pau Espin Pedrol33c154b2017-10-25 16:16:25 +020089def setcap_net_raw(binary, run_dir):
90 '''
91 This functionality requires specific setup on the host running
92 osmo-gsm-tester. See osmo-gsm-tester manual for more information.
93 '''
94 from .process import Process
Pau Espin Pedrolf0b8e372017-12-13 16:08:35 +010095 SETCAP_NET_RAW_BIN = 'osmo-gsm-tester_setcap_net_raw.sh'
96 proc = Process(SETCAP_NET_RAW_BIN, run_dir, ['sudo', SETCAP_NET_RAW_BIN, binary])
Pau Espin Pedrol33c154b2017-10-25 16:16:25 +020097 proc.launch()
98 proc.wait()
99 if proc.result != 0:
Pau Espin Pedrolf0b8e372017-12-13 16:08:35 +0100100 raise RuntimeError('%s finished with err code %d' % (SETCAP_NET_RAW_BIN, proc.result))
101
102def setcap_net_admin(binary, run_dir):
103 '''
104 This functionality requires specific setup on the host running
105 osmo-gsm-tester. See osmo-gsm-tester manual for more information.
106 '''
107 from .process import Process
108 SETCAP_NET_ADMIN_BIN = 'osmo-gsm-tester_setcap_net_admin.sh'
109 proc = Process(SETCAP_NET_ADMIN_BIN, run_dir, ['sudo', SETCAP_NET_ADMIN_BIN, binary])
110 proc.launch()
111 proc.wait()
112 if proc.result != 0:
113 raise RuntimeError('%s finished with err code %d' % (SETCAP_NET_ADMIN_BIN, proc.result))
Pau Espin Pedrol33c154b2017-10-25 16:16:25 +0200114
Neels Hofmeyr803d87c2017-05-10 13:31:41 +0200115class listdict(dict):
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200116 'a dict of lists { "a": [1, 2, 3], "b": [1, 2] }'
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200117
Neels Hofmeyr803d87c2017-05-10 13:31:41 +0200118 def add(self, name, item):
119 l = self.get(name)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200120 if not l:
121 l = []
Neels Hofmeyr803d87c2017-05-10 13:31:41 +0200122 self[name] = l
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200123 l.append(item)
124 return l
125
Neels Hofmeyr803d87c2017-05-10 13:31:41 +0200126 def add_dict(self, d):
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200127 for k,v in d.items():
Neels Hofmeyr803d87c2017-05-10 13:31:41 +0200128 self.add(k, v)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200129
130class DictProxy:
131 '''
132 allow accessing dict entries like object members
133 syntactical sugar, adapted from http://stackoverflow.com/a/31569634
134 so that e.g. templates can do ${bts.member} instead of ${bts['member']}
135 '''
136 def __init__(self, obj):
137 self.obj = obj
138
139 def __getitem__(self, key):
140 return dict2obj(self.obj[key])
141
142 def __getattr__(self, key):
Your Name44af3412017-04-13 03:11:59 +0200143 'provide error information to know which template item was missing'
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200144 try:
145 return dict2obj(getattr(self.obj, key))
146 except AttributeError:
147 try:
148 return self[key]
149 except KeyError:
150 raise AttributeError(key)
151
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200152def dict2obj(value):
Your Name44af3412017-04-13 03:11:59 +0200153 if is_list(value) or is_dict(value):
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200154 return DictProxy(value)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200155 return value
156
157
158class FileLock:
159 def __init__(self, path, owner):
160 self.path = path
161 self.owner = owner
162 self.f = None
163
164 def __enter__(self):
165 if self.f is not None:
166 return
Neels Hofmeyr936a81e2017-09-14 01:31:41 +0200167 self.fd = os.open(self.path, os.O_CREAT | os.O_WRONLY)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200168 fcntl.flock(self.fd, fcntl.LOCK_EX)
169 os.truncate(self.fd, 0)
170 os.write(self.fd, str(self.owner).encode('utf-8'))
171 os.fsync(self.fd)
172
173 def __exit__(self, *exc_info):
174 #fcntl.flock(self.fd, fcntl.LOCK_UN)
175 os.truncate(self.fd, 0)
176 os.fsync(self.fd)
177 os.close(self.fd)
178 self.fd = -1
179
180 def lock(self):
181 self.__enter__()
182
183 def unlock(self):
184 self.__exit__()
185
186
187class Dir():
188 LOCK_FILE = 'lock'
189
190 def __init__(self, path):
191 self.path = path
192 self.lock_path = os.path.join(self.path, Dir.LOCK_FILE)
193
194 def lock(self, origin_id):
195 '''
196 return lock context, usage:
197
198 with my_dir.lock(origin):
199 read_from(my_dir.child('foo.txt'))
200 write_to(my_dir.child('bar.txt'))
201 '''
202 self.mkdir()
203 return FileLock(self.lock_path, origin_id)
204
205 @staticmethod
206 def ensure_abs_dir_exists(*path_elements):
207 l = len(path_elements)
208 if l < 1:
209 raise RuntimeError('Cannot create empty path')
210 if l == 1:
211 path = path_elements[0]
212 else:
213 path = os.path.join(*path_elements)
214 if not os.path.isdir(path):
215 os.makedirs(path)
216
217 def child(self, *rel_path):
218 if not rel_path:
219 return self.path
220 return os.path.join(self.path, *rel_path)
221
222 def mk_parentdir(self, *rel_path):
223 child = self.child(*rel_path)
224 child_parent = os.path.dirname(child)
225 Dir.ensure_abs_dir_exists(child_parent)
226 return child
227
228 def mkdir(self, *rel_path):
229 child = self.child(*rel_path)
230 Dir.ensure_abs_dir_exists(child)
231 return child
232
233 def children(self):
234 return os.listdir(self.path)
235
236 def exists(self, *rel_path):
237 return os.path.exists(self.child(*rel_path))
238
239 def isdir(self, *rel_path):
240 return os.path.isdir(self.child(*rel_path))
241
242 def isfile(self, *rel_path):
243 return os.path.isfile(self.child(*rel_path))
244
245 def new_child(self, *rel_path):
246 attempt = 1
247 prefix, suffix = os.path.splitext(self.child(*rel_path))
248 rel_path_fmt = '%s%%s%s' % (prefix, suffix)
249 while True:
250 path = rel_path_fmt % (('_%d'%attempt) if attempt > 1 else '')
251 if not os.path.exists(path):
252 break
253 attempt += 1
254 continue
255 Dir.ensure_abs_dir_exists(os.path.dirname(path))
256 return path
257
258 def rel_path(self, path):
259 return os.path.relpath(path, self.path)
260
261 def touch(self, *rel_path):
262 touch_file(self.child(*rel_path))
263
264 def new_file(self, *rel_path):
265 path = self.new_child(*rel_path)
266 touch_file(path)
267 return path
268
269 def new_dir(self, *rel_path):
270 path = self.new_child(*rel_path)
271 Dir.ensure_abs_dir_exists(path)
272 return path
273
274 def __str__(self):
275 return self.path
276 def __repr__(self):
277 return self.path
278
279def touch_file(path):
280 with open(path, 'a') as f:
281 f.close()
282
283def is_dict(l):
284 return isinstance(l, dict)
285
286def is_list(l):
287 return isinstance(l, (list, tuple))
288
289
290def dict_add(a, *b, **c):
291 for bb in b:
292 a.update(bb)
293 a.update(c)
294 return a
295
296def _hash_recurse(acc, obj, ignore_keys):
297 if is_dict(obj):
298 for key, val in sorted(obj.items()):
299 if key in ignore_keys:
300 continue
301 _hash_recurse(acc, val, ignore_keys)
302 return
303
304 if is_list(obj):
305 for item in obj:
306 _hash_recurse(acc, item, ignore_keys)
307 return
308
309 acc.update(str(obj).encode('utf-8'))
310
311def hash_obj(obj, *ignore_keys):
312 acc = hashlib.sha1()
313 _hash_recurse(acc, obj, ignore_keys)
314 return acc.hexdigest()
315
316
317def md5(of_content):
318 if isinstance(of_content, str):
319 of_content = of_content.encode('utf-8')
320 return hashlib.md5(of_content).hexdigest()
321
322def md5_of_file(path):
323 with open(path, 'rb') as f:
324 return md5(f.read())
325
326_tempdir = None
327
328def get_tempdir(remove_on_exit=True):
329 global _tempdir
330 if _tempdir is not None:
331 return _tempdir
332 _tempdir = tempfile.mkdtemp()
333 if remove_on_exit:
334 atexit.register(lambda: shutil.rmtree(_tempdir))
335 return _tempdir
336
337
338if hasattr(importlib.util, 'module_from_spec'):
339 def run_python_file(module_name, path):
340 spec = importlib.util.spec_from_file_location(module_name, path)
341 spec.loader.exec_module( importlib.util.module_from_spec(spec) )
342else:
343 from importlib.machinery import SourceFileLoader
344 def run_python_file(module_name, path):
345 SourceFileLoader(module_name, path).load_module()
346
347def msisdn_inc(msisdn_str):
348 'add 1 and preserve leading zeros'
349 return ('%%0%dd' % len(msisdn_str)) % (int(msisdn_str) + 1)
350
Neels Hofmeyracf0c932017-05-06 16:05:33 +0200351class InputThread(threading.Thread):
352 def __init__(self, prompt):
353 super().__init__()
354 self.prompt = prompt
355 self.result = None
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200356
Neels Hofmeyracf0c932017-05-06 16:05:33 +0200357 def run(self):
358 self.result = input(self.prompt)
359
360def input_polling(prompt, poll_func):
361 input_thread = InputThread(prompt)
362 input_thread.start()
363
364 while input_thread.is_alive():
365 poll_func()
366 time.sleep(1)
367
368 input_thread.join()
369 return input_thread.result
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200370
Pau Espin Pedrol404e1502017-08-22 11:17:43 +0200371def str2bool(val):
372 if val is None or not val:
373 return False
374 if val.upper() in ['FALSE', 'NO', 'OFF']:
375 return False
376 if val.upper() in ['TRUE','YES', 'ON']:
377 return True
378 raise ValueError('Invalid BOOL field: %r' % val)
379
Pau Espin Pedrol43737da2017-08-28 14:04:07 +0200380def list_validate_same_elem_type(li):
381 '''
382 Checks that all elements in the list are of the same type and returns that type.
383 If the list is empty, returns None
384 If one of the elements is not of the same type, it throws a ValueError exception.
385 '''
386 if len(li) == 0:
387 return None
388 t = type(li[0])
389 for elem in li:
390 if type(elem) != t:
391 raise ValueError('List contains elements of different types: %r vs %r' % (t, type(elem)))
392 return t
393
394def empty_instance_type(t):
395 if t == dict:
396 return {}
397 elif t == list:
398 return []
399 elif t == tuple:
400 return ()
401 raise ValueError('type %r not supported!' % t)
402
Pau Espin Pedrolabd556a2017-09-04 16:26:08 +0200403def encryption2osmovty(val):
404 assert val[:3] == 'a5_'
405 return 'a5 ' + val[3:]
406
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200407# vim: expandtab tabstop=4 shiftwidth=4