Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

1325 lines
45KB

  1. #!/usr/bin/env python3
  2. # Master: ptpd -i {PTP Interface des Masters} -M -u {IP des Slaves}
  3. # Slave: ptpd -i {PTP Interface des Slaves} -s -u {IP des Masters}
  4. import json
  5. import requests
  6. import os
  7. import re
  8. import socket
  9. import subprocess
  10. import asyncio
  11. import serial
  12. from argparse import ArgumentParser
  13. from datetime import datetime
  14. from threading import Thread
  15. from time import sleep
  16. # {} for interface
  17. GET_IPV4_SHELL_COMMAND = "ip a | grep {} | grep inet | cut -d' ' -f6 | cut -d'/' -f1"
  18. NR_CQI_COMMAND = b'AT+QNWCFG="nr5g_csi"\r\n'
  19. NR_SERVINGCELL_COMMAND = b'AT+QENG="servingcell"\r\n'
  20. NR_EN_DC_STATUS_COMMAND = b"AT+QENDC\r\n"
  21. NE_CA_COMMAND = b'AT+QCAINFO\r\n'
  22. NR_SERIAL_RESPOND_TIME = 0.5 # s
  23. CMD_TIME_EPOCH = "date +%s"
  24. TIMEOUT_OFFSET = 10.0
  25. WAIT_AFTER_IPERF = 5.0
  26. modem_serial_obj = None
  27. gps_serial_obj = None
  28. MODEM_MODEL = None
  29. class ProcessHandler:
  30. def __init__(self):
  31. self.processList = []
  32. def add_process(self, process, logfile=None):
  33. if logfile is not None:
  34. self.processList.append(dict(process=process, logfile=logfile))
  35. else:
  36. self.processList.append(process)
  37. def kill_all(self):
  38. for p in self.processList:
  39. if isinstance(p, dict):
  40. p["logfile"].close()
  41. p["process"].kill()
  42. else:
  43. p.kill()
  44. def parse_var(s):
  45. """
  46. Parse a key, value pair, separated by '='
  47. That's the reverse of ShellArgs.
  48. On the command line (argparse) a declaration will typically look like:
  49. foo=hello
  50. or
  51. foo="hello world"
  52. """
  53. items = s.split("=")
  54. key = items[0].strip() # we remove blanks around keys, as is logical
  55. value = ""
  56. if len(items) > 1:
  57. # rejoin the rest:
  58. value = "=".join(items[1:])
  59. return (key, value)
  60. def parse_vars(items):
  61. """
  62. Parse a series of key-value pairs and return a dictionary
  63. """
  64. d = {}
  65. if items:
  66. for item in items:
  67. key, value = parse_var(item)
  68. d[key] = value
  69. return d
  70. def write_to_file(filepath, content, overwrite=False):
  71. mode = None
  72. if overwrite:
  73. mode = "w"
  74. else:
  75. mode = "a"
  76. try:
  77. f = open(filepath, mode)
  78. f.write(content)
  79. f.close()
  80. except IOError:
  81. print_message("ERROR: Could not write to file: {}".format(filepath))
  82. # For none blocking filewrite
  83. def background_write_to_file(filepath, content, overwrite=False):
  84. thread = Thread(
  85. target=write_to_file,
  86. args=(
  87. filepath,
  88. content,
  89. overwrite,
  90. ),
  91. )
  92. thread.start()
  93. def execute_tcp_dump(processHandler, interface, outputfile, ws_filter, flags=[]):
  94. cmd = ["tcpdump", "-i" + interface, "-U", "-w", outputfile, ws_filter]
  95. if len(flags) > 0:
  96. # insert after -U
  97. n = 3
  98. for flag in flags:
  99. cmd.insert(n, flag)
  100. n += 1
  101. process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
  102. processHandler.add_process(process)
  103. def save_tcp_trace(processHandler, filename, sender_ip, port):
  104. cmd_uptime = ["cat", "/proc/uptime"]
  105. logfile = open(filename, "a")
  106. subprocess.call(cmd_uptime, stdout=logfile)
  107. # /sys/kernel/debug/tracing/events/tcp/tcp_probe/enable
  108. cmd = ["cat", "/sys/kernel/debug/tracing/trace_pipe"]
  109. process = subprocess.Popen(cmd, stdout=logfile)
  110. processHandler.add_process(process, logfile=logfile)
  111. def get_ip_from_interface(interface):
  112. c = GET_IPV4_SHELL_COMMAND.format(interface)
  113. ip = (
  114. subprocess.check_output(c, shell=True)
  115. .decode("utf-8")
  116. .replace(" ", "")
  117. .replace("\n", "")
  118. )
  119. return ip
  120. def config2json(args):
  121. config = vars(args).copy()
  122. # parse the key-value pairs from set arg
  123. set_args = parse_vars(args.set)
  124. config["set"] = set_args
  125. # delete not used keys
  126. del config["interface"]
  127. del config["server"]
  128. del config["client"]
  129. del config["folder"]
  130. return bytes(json.dumps(config), "utf-8")
  131. def json2config(json_string):
  132. return json.loads(json_string)
  133. def print_message(m):
  134. print("[{}]\t{}".format(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), m))
  135. def deactivate_hystart():
  136. print_message("Deactivate HyStart")
  137. os.system('echo "0" > /sys/module/tcp_cubic/parameters/hystart')
  138. def activate_hystart():
  139. print_message("Activate HyStart")
  140. os.system('echo "1" > /sys/module/tcp_cubic/parameters/hystart')
  141. def is_hystart_activated():
  142. return (
  143. int(
  144. subprocess.check_output(
  145. "cat /sys/module/tcp_cubic/parameters/hystart", shell=True
  146. )
  147. )
  148. == 1
  149. )
  150. def is_tcp_probe_enabled():
  151. return (
  152. int(
  153. subprocess.check_output(
  154. "cat /sys/kernel/debug/tracing/events/tcp/tcp_probe/enable", shell=True
  155. )
  156. )
  157. == 1
  158. )
  159. def disable_tso(interface):
  160. os.system("ethtool -K {} tx off sg off tso off gro off".format(interface))
  161. def enable_tcp_probe():
  162. os.system("echo '1' > /sys/kernel/debug/tracing/events/tcp/tcp_probe/enable")
  163. def set_default_receive_window():
  164. print_message("Set receive window to default values")
  165. os.system('echo "212992" > /proc/sys/net/core/rmem_max')
  166. os.system('echo "212992" > /proc/sys/net/core/rmem_default')
  167. os.system('echo "4096 131072 6291456" > /proc/sys/net/ipv4/tcp_rmem')
  168. def raise_receive_window():
  169. print_message("Multiply receive windows by factor 10")
  170. os.system('echo "2129920" > /proc/sys/net/core/rmem_max')
  171. os.system('echo "2129920" > /proc/sys/net/core/rmem_default')
  172. os.system('echo "40960 1310720 62914560" > /proc/sys/net/ipv4/tcp_rmem')
  173. def monitor_serial(ser, output_file):
  174. #run_cmds = [NR_CQI_COMMAND, NR_SERVINGCELL_COMMAND, NR_EN_DC_STATUS_COMMAND, NE_CA_COMMAND]
  175. run_cmds = [b"at!gstatus?\r\n", b"AT!NRINFO?\r\n"]
  176. try:
  177. while ser.is_open:
  178. response = subprocess.check_output(CMD_TIME_EPOCH, shell=True).decode(
  179. "utf-8"
  180. )
  181. for cmd in run_cmds:
  182. ser.write(cmd)
  183. sleep(NR_SERIAL_RESPOND_TIME)
  184. response += ser.read(ser.inWaiting()).decode("utf-8")
  185. #response = (
  186. # response.replace("\n", ";")
  187. # .replace("\r", "")
  188. # .replace(";;OK", ";")
  189. # .replace(";;", ";")
  190. #)
  191. write_to_file(output_file, response + ";;;\n")
  192. except:
  193. if not ser.is_open:
  194. print_message("Serial port is closed. Exit monitoring thread.")
  195. else:
  196. print_message(
  197. "Something went wrong while monitoring serial interface. Exit monitoring thread."
  198. )
  199. return
  200. def start_serial_monitoring(ser, baudrate, folder, prefix):
  201. global modem_serial_obj
  202. print_message("Opening serial port for {}".format(ser))
  203. modem_serial_obj = serial.Serial(
  204. port=ser,
  205. baudrate=baudrate,
  206. )
  207. modem_serial_obj.isOpen()
  208. ser_filepath = "{}{}_serial_monitor_output.txt".format(
  209. folder, prefix
  210. )
  211. ser_thread = Thread(
  212. target=monitor_serial,
  213. args=(
  214. modem_serial_obj,
  215. ser_filepath,
  216. ),
  217. )
  218. ser_thread.start()
  219. def is_serial_monitoring_running():
  220. return modem_serial_obj.is_open
  221. def start_gps_monitoring(gps, baudrate, folder, prefix):
  222. global gps_serial_obj
  223. print_message("Opening GPS serial port for {}".format(gps))
  224. gps_serial_obj = serial.Serial(
  225. gps,
  226. baudrate=baudrate,
  227. )
  228. gps_ser_filepath = "{}{}_gps.nmea".format(
  229. folder, prefix
  230. )
  231. gps_ser_thread = Thread(
  232. target=monitor_gps,
  233. args=(
  234. gps_serial_obj,
  235. gps_ser_filepath,
  236. ),
  237. )
  238. gps_ser_thread.start()
  239. def monitor_gps(ser, output_file):
  240. ser.flushInput()
  241. ser.flushOutput()
  242. # skip first maybe uncompleted line
  243. ser.readline()
  244. try:
  245. while ser.is_open:
  246. nmea_sentence = ser.readline() # GPRMC
  247. nmea_str = nmea_sentence.decode("utf-8")
  248. if nmea_str.startswith("$GPRMC"):
  249. time_epoch = subprocess.check_output(CMD_TIME_EPOCH, shell=True).decode(
  250. "utf-8"
  251. )
  252. write_to_file(output_file, "{},{}".format(nmea_str.replace("\n", ""), time_epoch))
  253. except:
  254. if not ser.is_open:
  255. print_message("GPS serial port is closed. Exit monitoring thread.")
  256. else:
  257. print_message(
  258. "Something went wrong while monitoring GPS serial interface. Exit monitoring thread."
  259. )
  260. return
  261. def connect_moden(provider="telekom"):
  262. print_message("Connect modem with provider {} ...".format(provider))
  263. if MODEM_MODEL == "EM9191":
  264. os.system("/root/connection_mbim.py -l {}".format(provider))
  265. else:
  266. os.system("/root/connect-modem.py -l {}".format(provider))
  267. print_message("...done")
  268. def reconnect_modem(provider="telekom", hard=False):
  269. #TODO
  270. os.system("/root/connection_mbim.py -s")
  271. sleep(2)
  272. os.system("/root/connection_mbim.py -l {}".format(provider))
  273. return
  274. global modem_serial_obj
  275. print_message("Reonnect modem with provider {} ...".format(provider))
  276. if hard:
  277. print_message("Performing HARD reconnect...")
  278. try:
  279. if modem_serial_obj.is_open:
  280. modem_serial_obj.write(b'at+cfun=4\r\n')
  281. sleep(NR_SERIAL_RESPOND_TIME)
  282. sleep(2)
  283. modem_serial_obj.write(b'at+cfun=1\r\n')
  284. sleep(NR_SERIAL_RESPOND_TIME)
  285. except Exception as e:
  286. if not modem_serial_obj.is_open:
  287. print_message("Serial port is closed. {}".format(e))
  288. os.system("/root/connect-modem.py -s")
  289. else:
  290. print_message(
  291. "Something went wrong while writing to serial. {}".format(e)
  292. )
  293. os.system("/root/connect-modem.py -s")
  294. sleep(2)
  295. os.system("/root/connect-modem.py -s")
  296. sleep(5)
  297. os.system("/root/connect-modem.py -l {}".format(provider))
  298. else:
  299. os.system("/root/connect-modem.py -s")
  300. sleep(5)
  301. os.system("/root/connect-modem.py -l {}".format(provider))
  302. print_message("...done")
  303. def is_modem_connected():
  304. timeout = 5
  305. try:
  306. request = requests.get("http://130.75.73.69", timeout=timeout)
  307. return True
  308. except (requests.ConnectionError, requests.Timeout) as exception:
  309. pass
  310. return False
  311. class Server:
  312. def __init__(self, config):
  313. self.config = config
  314. def measure(self):
  315. print("Received config:")
  316. print(self.config)
  317. print_message("Start measurement")
  318. if self.config["bandwidth"]:
  319. self.bandwidth()
  320. elif self.config["drx"]:
  321. self.drx()
  322. elif self.config["harq"]:
  323. self.harq()
  324. elif self.config["cbr"]:
  325. self.cbr()
  326. elif self.config["tcp_parallel"]:
  327. self.tcp_parallel_flows()
  328. else:
  329. print_message("Nothing to do on server side.")
  330. def drx(self):
  331. print_message("DRX nothing to do on server side.")
  332. def harq(self):
  333. print_message("HARQ nothing to do on server side.")
  334. def bandwidth(self):
  335. server_is_sender = False
  336. if "server_is_sender" in self.config["set"]:
  337. server_is_sender = self.config["set"]["server_is_sender"]
  338. print_message("Server is sender: {}".format(server_is_sender))
  339. tcp_algo = list()
  340. if "algo" in self.config["set"]:
  341. for s in self.config["set"]["algo"].split(","):
  342. tcp_algo.append(s)
  343. print_message("Using {} for TCP transmissions.".format(s))
  344. else:
  345. tcp_algo.append("cubic")
  346. print_message("Using {} for TCP transmissions.".format(tcp_algo[0]))
  347. alternate_hystart = False
  348. if "alternate_hystart" in self.config["set"]:
  349. if self.config["set"]["alternate_hystart"] == "true":
  350. alternate_hystart = True
  351. time = "10"
  352. if "time" in self.config["set"]:
  353. time = self.config["set"]["time"]
  354. # prevent address already in use
  355. sleep(2)
  356. ws_filter = ""
  357. congestion_control_index = 0
  358. if server_is_sender:
  359. # server sends
  360. if not is_tcp_probe_enabled():
  361. print_message("tcp probe is not enabled!")
  362. enable_tcp_probe()
  363. print_message("tcp probe is now enabled")
  364. for n in range(1, self.config["number_of_measurements"] + 1):
  365. print_message(
  366. "{} of {}".format(n, self.config["number_of_measurements"])
  367. )
  368. print_message(
  369. "Using {} for congestion control".format(
  370. tcp_algo[congestion_control_index]
  371. )
  372. )
  373. iperf_command = [
  374. "iperf3",
  375. "-s",
  376. "--port",
  377. str(self.config["port"]),
  378. "--one-off",
  379. ]
  380. filepath_tcp_trace = "{}{}_bandwidth_reverse_{}_{}_{}.txt".format(
  381. self.config["folder"], self.config["prefix"], "tcp", "tcp_trace", n
  382. )
  383. save_tcp_trace(
  384. processHandler,
  385. filepath_tcp_trace,
  386. self.config["server"],
  387. self.config["port"],
  388. )
  389. subprocess.call(iperf_command)
  390. processHandler.kill_all()
  391. congestion_control_index = (congestion_control_index + 1) % len(
  392. tcp_algo
  393. )
  394. else:
  395. # client sends
  396. ws_filter = "{} and port {}".format("tcp", self.config["port"])
  397. print_message("Use ws filter: {}".format(ws_filter))
  398. name_option = ""
  399. state_counter = 0
  400. for n in range(1, self.config["number_of_measurements"] + 1):
  401. print_message(
  402. "Measurement {} of {}".format(
  403. n, self.config["number_of_measurements"]
  404. )
  405. )
  406. tcpdump_flags = []
  407. if alternate_hystart:
  408. if state_counter == 0:
  409. tcp_algo = "cubic"
  410. raise_receive_window()
  411. name_option = "_cubic_slowstart_raise_"
  412. state_counter = 1
  413. elif state_counter == 1:
  414. tcp_algo = "cubic"
  415. set_default_receive_window()
  416. name_option = "_cubic_hystart_default_"
  417. state_counter = 2
  418. elif state_counter == 2:
  419. tcp_algo = "bbr"
  420. raise_receive_window()
  421. name_option = "_bbr_raise_"
  422. state_counter = 3
  423. elif state_counter == 3:
  424. tcp_algo = "bbr"
  425. set_default_receive_window()
  426. name_option = "_bbr_default_"
  427. state_counter = 0
  428. else:
  429. name_option = ""
  430. filepath = "{}{}{}_bandwidth_{}_{}_{}.pcap".format(
  431. self.config["folder"],
  432. self.config["prefix"],
  433. name_option,
  434. "tcp",
  435. tcp_algo[congestion_control_index],
  436. n,
  437. )
  438. tcpdump_flags.append("-s96")
  439. thread = Thread(
  440. target=execute_tcp_dump,
  441. args=(
  442. processHandler,
  443. args.interface,
  444. filepath,
  445. ws_filter,
  446. tcpdump_flags,
  447. ),
  448. )
  449. thread.start()
  450. sleep(2)
  451. iperf_command = [
  452. "iperf3",
  453. "-s",
  454. "--port",
  455. str(self.config["port"]),
  456. "--one-off",
  457. ]
  458. subprocess.call(iperf_command)
  459. sleep(WAIT_AFTER_IPERF)
  460. processHandler.kill_all()
  461. congestion_control_index = (congestion_control_index + 1) % len(
  462. tcp_algo
  463. )
  464. def cbr(self):
  465. use_reverse_mode = False
  466. if "reverse" in self.config["set"]:
  467. use_reverse_mode = self.config["set"]["reverse"] == "true"
  468. print_message("Use reverse mode: {}".format(use_reverse_mode))
  469. # bitrate is only used for filenames on server side
  470. bitrate = "1M"
  471. if "bitrate" in self.config["set"]:
  472. bitrate = self.config["set"]["bitrate"]
  473. sleep_time = 60.0
  474. if "sleep" in self.config["set"]:
  475. sleep_time = float(self.config["set"]["sleep"])
  476. print_message("Sleep time for each measurement: {}s".format(sleep_time))
  477. # build wireshark filter
  478. if use_reverse_mode:
  479. ws_filter = "{} and src {}".format("udp", self.config["server"])
  480. else:
  481. ws_filter = "{} and dst {} and port {}".format(
  482. "udp", self.config["server"], self.config["port"]
  483. )
  484. print_message("Use ws filter: {}".format(ws_filter))
  485. # build iperf3 command
  486. iperf_command = [
  487. "iperf3",
  488. "-s",
  489. "--port",
  490. str(self.config["port"]),
  491. "--one-off",
  492. ]
  493. for n in range(1, self.config["number_of_measurements"] + 1):
  494. print_message("{} of {}".format(n, self.config["number_of_measurements"]))
  495. sleep(sleep_time)
  496. pcap_filepath = "{}{}_cbr_server_{}_bitrate{}_{}.pcap".format(
  497. self.config["folder"],
  498. self.config["prefix"],
  499. "sender" if use_reverse_mode else "receiver",
  500. bitrate,
  501. n,
  502. )
  503. thread = Thread(
  504. target=execute_tcp_dump,
  505. args=(
  506. processHandler,
  507. self.config["interface"],
  508. pcap_filepath,
  509. ws_filter,
  510. ),
  511. )
  512. thread.start()
  513. sleep(1)
  514. subprocess.call(iperf_command)
  515. sleep(1)
  516. processHandler.kill_all()
  517. def tcp_parallel_flows(self):
  518. # for n in range(1, (self.config["number_of_measurements"] * 2) + 1):
  519. # print_message(
  520. # "Measurement {} of {}".format(
  521. # n, self.config["number_of_measurements"]
  522. # )
  523. # )
  524. sleep(4)
  525. iperf_command = [
  526. "iperf3",
  527. "-s",
  528. "--port",
  529. str(self.config["port"]),
  530. # "--one-off",
  531. ]
  532. subprocess.call(iperf_command)
  533. class Client:
  534. def __init__(self, config):
  535. self.config = config
  536. def measure(self):
  537. print("Config:")
  538. print(self.config)
  539. sleep(1)
  540. print_message("Start measurement")
  541. if self.config["serial"] is not None:
  542. start_serial_monitoring(self.config["serial"], self.config["baudrate"], self.config["folder"], self.config["prefix"])
  543. if self.config["gps"] is not None:
  544. start_gps_monitoring(self.config["gps"], self.config["gps_baudrate"], self.config["folder"], self.config["prefix"])
  545. if self.config["bandwidth"]:
  546. self.bandwidth()
  547. elif self.config["drx"]:
  548. self.drx()
  549. elif self.config["harq"]:
  550. self.harq()
  551. elif self.config["cbr"]:
  552. self.cbr()
  553. elif self.config["tcp_parallel"]:
  554. self.tcp_parallel_flows()
  555. elif self.config["ping"]:
  556. self.ping()
  557. if modem_serial_obj is not None:
  558. print_message("Closing serial port...")
  559. modem_serial_obj.close()
  560. sleep(2)
  561. print_message("done...")
  562. if gps_serial_obj is not None:
  563. print_message("Closing GPS serial port...")
  564. gps_serial_obj.close()
  565. sleep(2)
  566. print_message("done...")
  567. def ping(self):
  568. c = "ping {} -I {} -i {} -c {}".format(
  569. self.config["server"],
  570. self.config["interface"],
  571. 0,
  572. self.config["number_of_measurements"],
  573. )
  574. command = [c]
  575. print_message(
  576. "Start sending {} pings with nog gap.".format(
  577. self.config["number_of_measurements"]
  578. )
  579. )
  580. ping_out = subprocess.check_output(command, shell=True).decode("utf-8")
  581. filepath = "{}{}_ping_no_gap.txt".format(
  582. self.config["folder"], self.config["prefix"]
  583. )
  584. print_message("Write measured pings to: {}".format(filepath))
  585. write_to_file(filepath, ping_out)
  586. def drx(self):
  587. # send ICMP pings to server and increase the gab
  588. # check for set args
  589. if "pre_ping" in self.config["set"]:
  590. is_pre_ping_enabled = (
  591. True if self.config["set"]["pre_ping"] == "true" else False
  592. )
  593. else:
  594. is_pre_ping_enabled = False
  595. if "intervals" in self.config["set"]:
  596. intervals = []
  597. interval_ranges = self.config["set"]["intervals"].split(",")
  598. for interval_range in interval_ranges:
  599. start, end, increase = [float(x) for x in interval_range.split(":")]
  600. curr = start
  601. while curr <= end:
  602. intervals.append(curr)
  603. curr += increase
  604. else:
  605. intervals = []
  606. filepath = "{}{}_ping_drx_raw.txt".format(
  607. self.config["folder"], self.config["prefix"]
  608. )
  609. if is_pre_ping_enabled:
  610. print_message("Send pre pings.")
  611. c = "ping {} -I {} -i {} -c {}".format(
  612. self.config["server"],
  613. self.config["interface"],
  614. intervals[0],
  615. self.config["number_of_measurements"],
  616. )
  617. command = [c]
  618. subprocess.check_output(command, shell=True)
  619. else:
  620. print_message("Preping is disabled. Wait 60s for drx-long-sleep...")
  621. sleep(60)
  622. for i in intervals:
  623. current = i
  624. if not is_pre_ping_enabled:
  625. sleep(current)
  626. c = "echo 'gap={}s' && ping {} -I {} -i {} -c {} && echo ';;;'".format(
  627. current,
  628. self.config["server"],
  629. self.config["interface"],
  630. current,
  631. self.config["number_of_measurements"],
  632. )
  633. command = [c]
  634. print_message("ping with {}s gap".format(current))
  635. background_write_to_file(
  636. filepath,
  637. subprocess.check_output(command, shell=True).decode("utf-8"),
  638. )
  639. # Read raw out and format it to csv
  640. regex = r"gap=(.*)s|64 bytes from (.*\..*\..*\..*) icmp_seq=(\d+) ttl=(\d+) time=(.*) ms"
  641. filepath = "{}{}_ping_drx_raw.txt".format(
  642. self.config["folder"], self.config["prefix"]
  643. )
  644. print_message("Format script output")
  645. f = open(filepath, "r")
  646. raw = f.read()
  647. f.close()
  648. lines = raw.split("\n")
  649. csv_out = "n,gap,rtt\n"
  650. n = 1
  651. gap = ""
  652. for l in lines:
  653. match = re.match(regex, l)
  654. if match:
  655. if match.group(5):
  656. time = match.group(5)
  657. csv_out = csv_out + "{},{},{}\n".format(n, gap, time)
  658. n += 1
  659. else:
  660. gap = match.group(1)
  661. filepath = "{}{}_ping_drx.csv".format(
  662. self.config["folder"], self.config["prefix"]
  663. )
  664. print_message("Write file {}".format(filepath))
  665. write_to_file(filepath, csv_out)
  666. def harq(self):
  667. ws_filter = "tcp[tcpflags] & (tcp-syn) != 0"
  668. print_message("Use ws filter: {}".format(ws_filter))
  669. filepath = "{}{}_{}_tcp_handshakes_http_client.pcap".format(
  670. self.config["folder"],
  671. self.config["prefix"],
  672. self.config["number_of_measurements"],
  673. )
  674. thread = Thread(
  675. target=execute_tcp_dump,
  676. args=(
  677. processHandler,
  678. self.config["interface"],
  679. filepath,
  680. ws_filter,
  681. ),
  682. )
  683. thread.start()
  684. sleep(5)
  685. for n in range(1, self.config["number_of_measurements"] + 1):
  686. print_message("{} of {}".format(n, self.config["number_of_measurements"]))
  687. requests.get("http://{}".format(self.config["server"]))
  688. sleep(5)
  689. processHandler.kill_all()
  690. def bandwidth(self):
  691. server_is_sender = False
  692. if "server_is_sender" in self.config["set"]:
  693. server_is_sender = self.config["set"]["server_is_sender"] == "true"
  694. print_message("Server is sender: {}".format(server_is_sender))
  695. tcp_algo = list()
  696. if "algo" in self.config["set"]:
  697. for s in self.config["set"]["algo"].split(","):
  698. tcp_algo.append(s)
  699. print_message("Using {} for TCP transmissions.".format(s))
  700. else:
  701. tcp_algo.append("cubic")
  702. print_message("Using {} for TCP transmissions.".format(tcp_algo[0]))
  703. alternate_hystart = False
  704. if "alternate_hystart" in self.config["set"]:
  705. if self.config["set"]["alternate_hystart"] == "true":
  706. alternate_hystart = True
  707. print_message("Alternate between HyStart and Slowstart for Cubic.")
  708. time = "10"
  709. if "time" in self.config["set"]:
  710. time = self.config["set"]["time"]
  711. sleep(2)
  712. congestion_control_index = 0
  713. if server_is_sender:
  714. # server is sending
  715. ws_filter = "{} and port {}".format("tcp", self.config["port"])
  716. print_message("Use ws filter: {}".format(ws_filter))
  717. for n in range(1, self.config["number_of_measurements"] + 1):
  718. reconnect_count = 0
  719. if not is_modem_connected():
  720. background_write_to_file(
  721. filepath="{}{}_reconnect.log".format(
  722. self.config["folder"], self.config["prefix"]
  723. ),
  724. content='{}\n'.format(datetime.timestamp(datetime.now())),
  725. )
  726. reconnect_modem()
  727. sleep(2)
  728. if not is_serial_monitoring_running():
  729. start_serial_monitoring()
  730. print_message(
  731. "{} of {}".format(n, self.config["number_of_measurements"])
  732. )
  733. print_message(
  734. "Using {} for congestion control".format(
  735. tcp_algo[congestion_control_index]
  736. )
  737. )
  738. tcpdump_flags = []
  739. filepath = "{}{}_bandwidth_reverse_{}_{}_{}.pcap".format(
  740. self.config["folder"],
  741. self.config["prefix"],
  742. "tcp",
  743. tcp_algo[congestion_control_index],
  744. n,
  745. )
  746. tcpdump_flags.append("-s96")
  747. thread = Thread(
  748. target=execute_tcp_dump,
  749. args=(
  750. processHandler,
  751. self.config["interface"],
  752. filepath,
  753. ws_filter,
  754. tcpdump_flags,
  755. ),
  756. )
  757. thread.start()
  758. sleep(5)
  759. iperf_command = [
  760. "iperf3",
  761. "-c",
  762. self.config["server"],
  763. "-R",
  764. "-t",
  765. time,
  766. "-C",
  767. tcp_algo[congestion_control_index],
  768. ]
  769. is_measurement_done = False
  770. iperf_return = 0
  771. while not is_measurement_done or iperf_return != 0:
  772. if iperf_return != 0:
  773. background_write_to_file(
  774. filepath="{}{}_reconnect.log".format(
  775. self.config["folder"], self.config["prefix"]
  776. ),
  777. content='{}\n'.format(datetime.timestamp(datetime.now())),
  778. )
  779. reconnect_modem(hard=reconnect_count > 5)
  780. reconnect_count += 1
  781. sleep(2)
  782. if not is_serial_monitoring_running():
  783. start_serial_monitoring()
  784. try:
  785. try:
  786. iperf_return = subprocess.call(
  787. iperf_command, timeout=float(time) + TIMEOUT_OFFSET
  788. )
  789. except:
  790. print_message("iPerf timed out...")
  791. background_write_to_file(
  792. filepath="{}{}_reconnect.log".format(
  793. self.config["folder"], self.config["prefix"]
  794. ),
  795. content='{}\n'.format(datetime.timestamp(datetime.now())),
  796. )
  797. reconnect_modem()
  798. except KeyboardInterrupt:
  799. exit()
  800. is_measurement_done = True
  801. sleep(4)
  802. processHandler.kill_all()
  803. congestion_control_index = (congestion_control_index + 1) % len(
  804. tcp_algo
  805. )
  806. else:
  807. # client is sending
  808. state_counter = 0
  809. name_option = ""
  810. if not is_tcp_probe_enabled():
  811. print_message("tcp probe is not enabled!")
  812. enable_tcp_probe()
  813. print_message("tcp probe is now enabled")
  814. for n in range(1, self.config["number_of_measurements"] + 1):
  815. reconnect_count = 0
  816. if not is_modem_connected():
  817. background_write_to_file(
  818. filepath="{}{}_reconnect.log".format(
  819. self.config["folder"], self.config["prefix"]
  820. ),
  821. content='{}\n'.format(datetime.timestamp(datetime.now())),
  822. )
  823. reconnect_modem()
  824. sleep(2)
  825. if not is_serial_monitoring_running():
  826. start_serial_monitoring()
  827. print_message(
  828. "{} of {}".format(n, self.config["number_of_measurements"])
  829. )
  830. iperf_command = ["iperf3", "-c", self.config["server"], "-t", time]
  831. if alternate_hystart:
  832. if state_counter == 0:
  833. tcp_algo = "cubic"
  834. deactivate_hystart()
  835. name_option = "_cubic_slowstart_raise_"
  836. state_counter = 1
  837. elif state_counter == 1:
  838. tcp_algo = "cubic"
  839. activate_hystart()
  840. name_option = "_cubic_hystart_default_"
  841. state_counter = 2
  842. elif state_counter == 2:
  843. tcp_algo = "bbr"
  844. deactivate_hystart()
  845. name_option = "_bbr_raise_"
  846. state_counter = 3
  847. elif state_counter == 3:
  848. tcp_algo = "bbr"
  849. activate_hystart()
  850. name_option = "_bbr_default_"
  851. state_counter = 0
  852. iperf_command.append("-C")
  853. iperf_command.append(tcp_algo[congestion_control_index])
  854. filepath_tcp_trace = "{}{}_bandwidth_{}_{}_{}.txt".format(
  855. self.config["folder"], self.config["prefix"], "tcp", "tcp_trace", n
  856. )
  857. save_tcp_trace(
  858. processHandler,
  859. filepath_tcp_trace,
  860. self.config["client"],
  861. self.config["port"],
  862. )
  863. sleep(2)
  864. is_measurement_done = False
  865. iperf_return = 0
  866. while not is_measurement_done or iperf_return != 0:
  867. if iperf_return != 0:
  868. background_write_to_file(
  869. filepath="{}{}_reconnect.log".format(
  870. self.config["folder"], self.config["prefix"]
  871. ),
  872. content='{}\n'.format(datetime.timestamp(datetime.now())),
  873. )
  874. reconnect_modem(hard=reconnect_count > 5)
  875. reconnect_count += 1
  876. sleep(2)
  877. if not is_serial_monitoring_running():
  878. start_serial_monitoring()
  879. try:
  880. try:
  881. iperf_return = subprocess.call(
  882. iperf_command, timeout=float(time) + TIMEOUT_OFFSET
  883. )
  884. except:
  885. print_message("iPerf timed out...")
  886. background_write_to_file(
  887. filepath="{}{}_reconnect.log".format(
  888. self.config["folder"], self.config["prefix"]
  889. ),
  890. content='{}\n'.format(datetime.timestamp(datetime.now())),
  891. )
  892. reconnect_modem()
  893. except KeyboardInterrupt:
  894. exit()
  895. is_measurement_done = True
  896. processHandler.kill_all()
  897. congestion_control_index = (congestion_control_index + 1) % len(
  898. tcp_algo
  899. )
  900. sleep(WAIT_AFTER_IPERF + 2)
  901. def cbr(self):
  902. bitrate = "1M"
  903. if "bitrate" in self.config["set"]:
  904. bitrate = self.config["set"]["bitrate"]
  905. print("Set bitrate to {}.".format(bitrate))
  906. time = "2"
  907. if "time" in self.config["set"]:
  908. time = self.config["set"]["time"]
  909. use_reverse_mode = False
  910. if "reverse" in self.config["set"]:
  911. use_reverse_mode = self.config["set"]["reverse"] == "true"
  912. print_message("Use reverse mode: {}".format(use_reverse_mode))
  913. sleep_time = 60.0
  914. if "sleep" in self.config["set"]:
  915. sleep_time = float(self.config["set"]["sleep"])
  916. print_message("Sleep time for each measurement: {}s".format(sleep_time))
  917. # build wireshark filter
  918. if use_reverse_mode:
  919. ws_filter = "{} and dst {}".format("udp", self.config["client"])
  920. else:
  921. ws_filter = "{} and src {} and port {}".format(
  922. "udp", self.config["client"], self.config["port"]
  923. )
  924. print_message("Use ws filter: {}".format(ws_filter))
  925. # build iperf3 command
  926. iperf_command = [
  927. "iperf3",
  928. "-c",
  929. self.config["server"],
  930. "-t",
  931. time,
  932. "--udp",
  933. "-b",
  934. bitrate,
  935. ]
  936. if use_reverse_mode:
  937. iperf_command.append("-R")
  938. for n in range(1, self.config["number_of_measurements"] + 1):
  939. print_message("{} of {}".format(n, self.config["number_of_measurements"]))
  940. sleep(sleep_time)
  941. pcap_filepath = "{}{}_cbr_client_{}_bitrate{}_{}.pcap".format(
  942. self.config["folder"],
  943. self.config["prefix"],
  944. "receiver" if use_reverse_mode else "sender",
  945. bitrate,
  946. n,
  947. )
  948. thread = Thread(
  949. target=execute_tcp_dump,
  950. args=(
  951. processHandler,
  952. self.config["interface"],
  953. pcap_filepath,
  954. ws_filter,
  955. ),
  956. )
  957. thread.start()
  958. sleep(2)
  959. subprocess.call(iperf_command)
  960. sleep(2)
  961. processHandler.kill_all()
  962. def tcp_parallel_flows(self):
  963. tcp_algo = "cubic"
  964. if "algo" in self.config["set"]:
  965. tcp_algo = self.config["set"]["algo"]
  966. print_message("Using {} for tcp.".format(tcp_algo))
  967. time = "12"
  968. if "time" in self.config["set"]:
  969. time = self.config["set"]["time"]
  970. start = "1"
  971. if "start" in self.config["set"]:
  972. start = self.config["set"]["start"]
  973. end = "2"
  974. if "end" in self.config["set"]:
  975. end = self.config["set"]["end"]
  976. # build flow seq
  977. flows = []
  978. runs = []
  979. for i in range(int(start), int(end) + 1):
  980. flows.append(i)
  981. flows.append(i)
  982. for i in range(1, self.config["number_of_measurements"] + 1):
  983. runs.append(i)
  984. runs.append(i)
  985. print_message(
  986. "{} to {} flows, with {} runs.".format(
  987. start, end, self.config["number_of_measurements"]
  988. )
  989. )
  990. sleep(5)
  991. for flow in flows:
  992. for run in runs:
  993. if tcp_algo == "cubic":
  994. tcp_algo = "bbr"
  995. else:
  996. tcp_algo = "cubic"
  997. filepath = "{}{}_{}parallel_tcp_{}_flows_{}.txt".format(
  998. self.config["folder"], self.config["prefix"], flow, tcp_algo, run
  999. )
  1000. c = "iperf3 -c {} -t {} -C {} -P {}".format(
  1001. self.config["server"], time, tcp_algo, flow
  1002. )
  1003. iperf_command = [c]
  1004. print_message("{} parallel {} flows".format(flow, tcp_algo))
  1005. retry = True
  1006. while retry:
  1007. try:
  1008. out = subprocess.check_output(iperf_command, shell=True).decode(
  1009. "utf-8"
  1010. )
  1011. write_to_file(filepath, out)
  1012. retry = False
  1013. except:
  1014. print("iPerf Error.\t{}".format(iperf_command))
  1015. sleep(1)
  1016. if retry:
  1017. print_message("Retry in 5s")
  1018. sleep(5)
  1019. async def start_server(args):
  1020. # get configuration from client
  1021. print_message("Start Server")
  1022. config = None
  1023. ip = get_ip_from_interface(args.interface)
  1024. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  1025. s.setblocking(True)
  1026. s.bind((ip, args.port))
  1027. s.listen(1)
  1028. conn, addr = s.accept()
  1029. print_message("Got connection from {}".format(addr))
  1030. while True:
  1031. data = conn.recv(1024)
  1032. if not data:
  1033. break
  1034. config = json2config(data.decode("utf-8"))
  1035. conn.close()
  1036. s.close()
  1037. # add interface and ip to config
  1038. config["interface"] = args.interface
  1039. config["client"] = addr[0]
  1040. config["server"] = ip
  1041. config["folder"] = args.folder
  1042. server = Server(config)
  1043. server.measure()
  1044. async def start_client(args):
  1045. if not is_modem_connected():
  1046. connect_moden()
  1047. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  1048. s.connect((args.client, args.port))
  1049. s.send(config2json(args))
  1050. s.close()
  1051. print("Config send.")
  1052. # overwrite ip
  1053. config = vars(args).copy()
  1054. config["server"] = args.client
  1055. config["client"] = get_ip_from_interface(args.interface)
  1056. new_set = dict()
  1057. if config["set"] is not None:
  1058. for pair in config["set"]:
  1059. tmp = pair.split("=")
  1060. new_set[tmp[0]] = tmp[1]
  1061. config["set"] = new_set
  1062. client = Client(config)
  1063. client.measure()
  1064. if __name__ == "__main__":
  1065. now = datetime.now()
  1066. processHandler = ProcessHandler()
  1067. parser = ArgumentParser()
  1068. # common arguments
  1069. # required
  1070. parser.add_argument("-i", "--interface", required=True, help="Interface.")
  1071. # optional
  1072. parser.add_argument("-p", "--port", default=5201, type=int, help="Port.")
  1073. parser.add_argument(
  1074. "-f",
  1075. "--folder",
  1076. default=os.path.dirname(os.path.realpath(__file__)) + "/",
  1077. help="Folder for the pcap files.",
  1078. )
  1079. # server exclusive arguments
  1080. parser.add_argument(
  1081. "-s",
  1082. "--server",
  1083. action="store_true",
  1084. default=False,
  1085. help="Starts the script in server mode.",
  1086. )
  1087. # client exclusive arguments
  1088. parser.add_argument(
  1089. "-c",
  1090. "--client",
  1091. default=None,
  1092. help="Start in client mode and set the server IPv4 address.",
  1093. )
  1094. parser.add_argument("--modem", default="EM9191", help="Modem model name.")
  1095. parser.add_argument(
  1096. "--prefix", default=now.strftime("%Y-%m-%d"), help="Prefix on filename."
  1097. )
  1098. parser.add_argument(
  1099. "--set",
  1100. metavar="KEY=VALUE",
  1101. nargs="+",
  1102. help="Set a number of key-value pairs "
  1103. "(do not put spaces before or after the = sign). "
  1104. "If a value contains spaces, you should define "
  1105. "it with double quotes: "
  1106. 'foo="this is a sentence". Note that '
  1107. "values are always treated as strings.",
  1108. )
  1109. parser.add_argument(
  1110. "-n",
  1111. "--number_of_measurements",
  1112. type=int,
  1113. default=10,
  1114. help="Number of measurements",
  1115. )
  1116. parser.add_argument(
  1117. "--ping",
  1118. action="store_true",
  1119. default=False,
  1120. help="Sending ICMP pings.",
  1121. )
  1122. parser.add_argument(
  1123. "--bandwidth",
  1124. action="store_true",
  1125. default=False,
  1126. help="Measure greedy tcp throughput with iperf3."
  1127. "Use the --set flag for: "
  1128. "server_is_sender=false if enable server is sending."
  1129. "algo=cubic set tcp algorithm. Can be a comma separated string for multiple congestion control algorithms."
  1130. "alternate_hystart=false if enabled alternate reproduce every Cubic measurement wicht and without HyStart (Also raises the receive window.). "
  1131. "time=10 length of transmission in seconds.",
  1132. )
  1133. parser.add_argument(
  1134. "--tcp_parallel",
  1135. action="store_true",
  1136. default=False,
  1137. help="Measure greedy tcp throughput with parallel tcp flows. Alternate between bbr and cubic."
  1138. "Use the --set flag for: "
  1139. "start=1 Start value for flows "
  1140. "end=2 End value for flows "
  1141. "time=12 transmission time. ",
  1142. )
  1143. parser.add_argument(
  1144. "--cbr",
  1145. action="store_true",
  1146. default=False,
  1147. help="Measure Udp CBR traffic."
  1148. "Use the --set flag for: "
  1149. "reverse=false enable reverse mode. Server is sending. "
  1150. "bitrate=1M target bitrate in bits/sec (0 for unlimited) "
  1151. "[KMG] indicates options that support a K/M/G suffix for kilo-, mega-, or giga-. "
  1152. "time=2 time in seconds to transmit for. "
  1153. "sleep=60 sleep before each cbr traffic in seconds. ",
  1154. )
  1155. parser.add_argument(
  1156. "--drx",
  1157. action="store_true",
  1158. default=False,
  1159. help="Measure RTTs with ping tool. To detect DRX cycles. "
  1160. "Use the --set flag for: "
  1161. "intervals=start:end:increase,start2:end2:increase2... set intervals for gaps, end is inclusive, "
  1162. "values in [s]. At least one interval is required. E.g.: 0:3:1 creates values [0,1,2,3]"
  1163. "pre_ping=false if enabled pings are send before measurements to set device in continuous reception mode. ",
  1164. )
  1165. parser.add_argument(
  1166. "--harq",
  1167. action="store_true",
  1168. default=False,
  1169. help="Captures http transmissions on client side.",
  1170. )
  1171. parser.add_argument(
  1172. "--serial",
  1173. default=None,
  1174. help="Serial device e.g. /dev/ttyUSB2",
  1175. )
  1176. parser.add_argument(
  1177. "--baudrate",
  1178. default=115200,
  1179. type=int,
  1180. help="Serial device baudrate",
  1181. )
  1182. parser.add_argument(
  1183. "--gps",
  1184. default=None,
  1185. help="GPS serial device e.g. /dev/serial/by-id/usb-u-blox_AG_-_www.u-blox.com_u-blox_5_-_GPS_Receiver-if00",
  1186. )
  1187. parser.add_argument(
  1188. "--gps_baudrate",
  1189. default=38400,
  1190. type=int,
  1191. help="GPS serial device baudrate",
  1192. )
  1193. args = parser.parse_args()
  1194. disable_tso(args.interface)
  1195. MODEM_MODEL = args.modem
  1196. if args.server:
  1197. asyncio.run(start_server(args))
  1198. elif args.client is not None:
  1199. asyncio.run(start_client(args))