- initial work to support offline mode

This commit is contained in:
Dmytro Bogovych 2023-08-20 13:00:37 +03:00
parent 0c5652f98a
commit d6a880efec
6 changed files with 372 additions and 0 deletions

7
bad_network_start.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
# Oneliner to find script's directory. Please note - last path component should NOT be symlink.
SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]:-$0}"; )" &> /dev/null && pwd 2> /dev/null; )";
/usr/bin/python3 ${SCRIPT_DIR}/src/utils_network_impairment.py --start

6
bad_network_stop.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
# Oneliner to find script's directory. Please note - last path component should NOT be symlink.
SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]:-$0}"; )" &> /dev/null && pwd 2> /dev/null; )";
/usr/bin/python3 ${SCRIPT_DIR}/src/utils_network_impairment.py --stop

28
debug_node.sh Executable file
View File

@ -0,0 +1,28 @@
#!/bin/bash
# Oneliner to find script's directory. Please note - last path component should NOT be symlink.
SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]:-$0}"; )" &> /dev/null && pwd 2> /dev/null; )";
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
# DBUS_SESSION_BUS_PID=`cat /run/dbus/pid`
export DBUS_SESSION_BUS_ADDRESS
# export DBUS_SESSION_BUS_PID
# To avoid problems with pulseaudio
pkill pulseaudio
while true; do
read -p "Do you wish to enable bad network simulation for gsm.sevana.biz ? " yn
case $yn in
[Yy]* ) $SCRIPT_DIR/start_bad_network.sh; break;;
[Nn]* ) exit;;
* ) echo "Please answer yes or no.";;
esac
done
# Ensure BT stack is here
python3 -u $SCRIPT_DIR/src/bt_preconnect.py $SCRIPT_DIR/config/agent.yaml
python3 -u $SCRIPT_DIR/src/agent_gsm.py --config $SCRIPT_DIR/config/agent.yaml --test

0
src/utils_bt_audio.py Executable file → Normal file
View File

94
src/utils_cache.py Normal file
View File

@ -0,0 +1,94 @@
#!/usr/bin/python3
from pathlib import Path
from utils_qualtest import Phone, TaskList
import os
import shutil
import json
import uuid
class InfoCache:
dir: Path
def __init__(self, dir: Path) -> None:
self.dir = dir
if dir is not None and not dir.exists():
os.mkdir(dir)
def is_active(self) -> bool:
return self.dir is not None
def add_reference_audio(self, audio_id: int, src_path: Path):
if not self.is_active():
return
p = self.dir / f'ref_{audio_id}.wav'
if not p.exists():
shutil.copy(src_path, p)
def get_reference_audio(self, audio_id: int, dst_path: Path) -> bool:
if not self.is_active():
return False
p = self.dir / f'ref_{audio_id}.wav'
if p.exists():
shutil.copy(p, dst_path)
return True
return False
def add_recorded_audio(self, src_path: Path, probe_id: str) -> Path:
if not self.is_active():
return None
p = self.dir / f'audio_{probe_id}.wav'
shutil.copy(src_path, p)
return p
def get_recorded_audio(self, probe_id: str) -> Path:
if not self.is_active():
return None
p = self.dir / f'audio_{probe_id}.wav'
if p.exists():
return p
else:
return None
def put_phone(self, phone: Phone):
if self.is_active():
with open(self.dir / f'phone_{phone.name}.json', 'wt') as f:
f.write(phone.dump())
def get_phone(self, name: str) -> Phone:
p = self.dir / f'phone_{name}.json', 'wt'
if p.exists():
with open(p, 'rt') as f:
return Phone.make(f.read())
def put_tasks(self, name: str, tasks: TaskList):
p = self.dir / f'tasks_{name}.json'
with open(p, 'wt') as f:
f.write(json.dumps(tasks.tasks))
def get_tasks(self, name: str) -> TaskList:
p = self.dir / f'tasks_{name}.json'
def add_report(self, report: dict) -> str:
if not self.is_active():
return None
# Generate UUID manually and save under this name to cache dir
probe_id = uuid.uuid1().urn[9:]
with open(self.dir / f'{probe_id}.json', 'wt') as f:
f.write(json.dumps(report, indent=4))
return probe_id

View File

@ -0,0 +1,237 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# FLEDGE_BEGIN
# See: http://fledge-iot.readthedocs.io/
# FLEDGE_END
""" Module for applying network impairments."""
__author__ = "Deepanshu Yadav"
__copyright__ = "Copyright (c) 2022 Dianomic Systems Inc."
__license__ = "Apache 2.0"
__version__ = "${VERSION}"
# References
# 1. http://myconfigure.blogspot.com/2012/03/traffic-shaping.html
# 2. https://lartc.org/howto/lartc.qdisc.classful.html
# 3. https://lartc.org/howto/lartc.qdisc.filters.html
# 4. https://serverfault.com/a/841865
# 5. https://serverfault.com/a/906499
# 6. https://wiki.linuxfoundation.org/networking/netem
# 7. https://srtlab.github.io/srt-cookbook/how-to-articles/using-netem-to-emulate-networks/
# 8. https://wiki.linuxfoundation.org/networking/netem
import subprocess
import multiprocessing
import datetime
import time
import socket
import argparse
def check_for_interface(interface):
"""Checks for given interface if present in output of ifconfig"""
for tup in socket.if_nameindex():
if tup[1] == interface:
return True
return False
class Distortion(multiprocessing.Process):
def __init__(self, run_cmd_list, clear_cmd, duration):
super(Distortion, self).__init__()
self.run_cmd_list = run_cmd_list
self.duration = duration
self.clear_cmd = clear_cmd
@staticmethod
def run_command(command):
"""Executes a shell command using subprocess module."""
try:
process = subprocess.Popen(command, cwd=None, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
except Exception as inst:
print("Problem running command : \n ", str(command))
return False
[stdoutdata, stderrdata] = process.communicate(None)
if process.returncode:
print(stderrdata)
print("Problem running command : \n ", str(command), " ", process.returncode)
return False
return True
def run(self) -> None:
# Make sure we are in clean state. Ignore error if there.
_ = Distortion.run_command(self.clear_cmd)
for run_cmd in self.run_cmd_list:
print("Executing {}".format(run_cmd), flush=True)
ret_val = Distortion.run_command(run_cmd)
if not ret_val:
print("Could not perform execution of command {}".format(run_cmd), flush=True)
return
end_time = datetime.datetime.now() + datetime.timedelta(seconds=self.duration)
while datetime.datetime.now() < end_time:
time.sleep(0.5)
print("Executing {}".format(self.clear_cmd), flush=True)
ret_val = Distortion.run_command(self.clear_cmd)
if not ret_val:
print("Could not perform execution of command {}".format(self.clear_cmd), flush=True)
return
print("Network Impairment complete.", flush=True)
def reset_network(interface):
"""
Reset the network in the middle of impairment.
:param interface: The interface of the network.
:type interface: string
:return: True/False If successful.
:rtype: boolean
"""
if not check_for_interface(interface):
raise Exception("Could not find given {} among present interfaces.".format(interface))
clear_cmd = "sudo tc qdisc del dev {} root".format(interface)
ret_val = Distortion.run_command(command=clear_cmd)
if ret_val:
print("Network has been reset.")
else:
print("Could not reset the network.")
def distort_network(interface, duration, rate_limit, latency, ip=None, port=None,
traffic=''):
"""
:param interface: Interface on which network impairment will be applied. See ifconfig in
your linux machine to decide.
:type interface: string
:param duration: The duration (in seconds) for which impairment will be applied. Note it will
get auto cleared after application.
:type duration: integer
:param traffic: If inbound then the given ip and port will be used to filter packets coming
from destination. For these packets only the impairment will be applied.
If outbound then we are talking about packets leaving this machine for destination.
This is exactly the opposite of first case.
:type traffic: inbound/ outbound string
:param ip: The ip of machine where packets are coming / leaving to filter. Keep None
if no filter required.
:type ip: string
:param port: The port of machine where packets are coming / leaving to filter. Keep None
if no filter required.
:type port: integer
:param rate_limit: The restriction in rate in kbps. Use value 20 for 20 kbps.
:type rate_limit: integer
:param latency: The delay to cause for every packet leaving/ coming from machine in
milliseconds. Use something like 300 for causing a delay for 300 milliseconds.
:type latency: integer
:return: None
:rtype: None
"""
if not check_for_interface(interface):
raise Exception("Could not find given {} among present interfaces.".format(interface))
if not latency and not rate_limit:
raise Exception("Could not find latency or rate_limit.")
if latency:
latency_converted = str(latency) + 'ms'
else:
latency_converted = None
if rate_limit:
rate_limit_converted = str(rate_limit) + 'Kbit'
else:
rate_limit_converted = None
if not (ip and port):
if rate_limit_converted and latency_converted:
run_cmd = "sudo tc qdisc add dev {} root netem" \
" delay {} rate {}".format(interface, latency_converted,
rate_limit_converted)
elif rate_limit_converted and not latency_converted:
run_cmd = "sudo tc qdisc add dev {} root netem" \
" rate {}".format(interface, rate_limit_converted)
elif not rate_limit_converted and latency_converted:
run_cmd = "sudo tc qdisc add dev {} root netem" \
"delay {}".format(interface, latency_converted)
clear_cmd = "sudo tc qdisc del dev {} root".format(interface)
p = Distortion([run_cmd], clear_cmd, duration)
p.daemon = True
p.start()
else:
r1 = "sudo tc qdisc add dev {} root handle 1: prio" \
" priomap 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2".format(interface)
if latency_converted and rate_limit_converted:
r2 = "sudo tc qdisc add dev {} parent 1:1 " \
"handle 10: netem delay {} rate {}".format(interface,
latency_converted,
rate_limit_converted)
elif not latency_converted and rate_limit_converted:
r2 = "sudo tc qdisc add dev {} parent 1:1 " \
"handle 10: netem rate {}".format(interface,
rate_limit_converted)
elif latency_converted and not rate_limit_converted:
r2 = "sudo tc qdisc add dev {} parent 1:1 " \
"handle 10: netem delay {} ".format(interface,
latency_converted)
if traffic.lower() == 'outbound':
ip_param = 'dst'
port_param = 'dport'
elif traffic.lower() == "inbound":
ip_param = 'src'
port_param = 'sport'
else:
raise Exception("For ip and port are given then traffic has to be either inbound or outbound."
" But got other than these two. ")
r3 = "sudo tc filter add dev {} protocol ip parent 1:0 prio 1 u32 " \
"match ip {} {}/32 match ip {} {} 0xffff flowid 1:1".format(interface, ip_param,
ip, port_param, port)
clear_cmd = "sudo tc qdisc del dev {} root".format(interface)
run_cmd_list = [r1, r2, r3]
p = Distortion(run_cmd_list, clear_cmd, duration)
p.daemon = True
p.start()
""" -------------------------Usage -------------------------------------"""
# from network_impairment import distort_network, reset_network
# distort_network(interface="wlp2s0", duration=40, rate_limit=20, latency=300,
# ip="192.168.1.80", port=8081, traffic="inbound")
#
# distort_network(interface="wlp2s0", duration=40, rate_limit=20, latency=300,
# ip="192.168.1.80", port=8081, traffic="outbound")
#
# distort_network(interface="wlp2s0", duration=40, rate_limit=20, latency=300)
# reset_network(interface="wlp2s0")
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--stop', action='store_true')
parser.add_argument('--start', action='store_true')
parser.add_argument('--ip')
config = parser.parse_args()
interface_name = 'wlp2s0'
if config.start and config.ip:
distort_network(interface='', duration=300, rate_limit=20, latency=300, ip=config.ip, port=443, traffic='outbound')
distort_network(interface='', duration=300, rate_limit=20, latency=300, ip=config.ip, port=443, traffic='inbound')
elif config.stop:
reset_network(interface='')