Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

1019 lines
34KB

  1. #!/usr/bin/env python3
  2. import json
  3. import requests
  4. import os
  5. import re
  6. import socket
  7. import subprocess
  8. import asyncio
  9. import serial
  10. from argparse import ArgumentParser
  11. from datetime import datetime
  12. from threading import Thread
  13. from time import sleep
  14. # {} for interface
  15. GET_IPV4_SHELL_COMMAND = "ip a | grep {} | grep inet | cut -d' ' -f6 | cut -d'/' -f1"
  16. NR_CQI_COMMAND = b'AT+QNWCFG="nr5g_csi"\r\n'
  17. NR_SERVINGCELL_COMMAND = b'AT+QENG="servingcell"\r\n'
  18. NR_EN_DC_STATUS_COMMAND = b"AT+QENDC\r\n"
  19. NR_SERIAL_RESPOND_TOME = 0.3 # s
  20. CMD_TIME_EPOCH = "date +%s"
  21. class ProcessHandler:
  22. def __init__(self):
  23. self.processList = []
  24. def add_process(self, process, logfile=None):
  25. if logfile is not None:
  26. self.processList.append(dict(process=process, logfile=logfile))
  27. else:
  28. self.processList.append(process)
  29. def kill_all(self):
  30. for p in self.processList:
  31. if isinstance(p, dict):
  32. p["logfile"].close()
  33. p["process"].kill()
  34. else:
  35. p.kill()
  36. def parse_var(s):
  37. """
  38. Parse a key, value pair, separated by '='
  39. That's the reverse of ShellArgs.
  40. On the command line (argparse) a declaration will typically look like:
  41. foo=hello
  42. or
  43. foo="hello world"
  44. """
  45. items = s.split("=")
  46. key = items[0].strip() # we remove blanks around keys, as is logical
  47. value = ""
  48. if len(items) > 1:
  49. # rejoin the rest:
  50. value = "=".join(items[1:])
  51. return (key, value)
  52. def parse_vars(items):
  53. """
  54. Parse a series of key-value pairs and return a dictionary
  55. """
  56. d = {}
  57. if items:
  58. for item in items:
  59. key, value = parse_var(item)
  60. d[key] = value
  61. return d
  62. def write_to_file(filepath, content, overwrite=False):
  63. mode = None
  64. if overwrite:
  65. mode = "w"
  66. else:
  67. mode = "a"
  68. try:
  69. f = open(filepath, mode)
  70. f.write(content)
  71. f.close()
  72. except IOError:
  73. print_message("ERROR: Could not write to file: {}".format(filepath))
  74. # For none blocking filewrite
  75. def background_write_to_file(filepath, content, overwrite=False):
  76. thread = Thread(
  77. target=write_to_file,
  78. args=(
  79. filepath,
  80. content,
  81. overwrite,
  82. ),
  83. )
  84. thread.start()
  85. def execute_tcp_dump(processHandler, interface, outputfile, ws_filter, flags=[]):
  86. cmd = ["tcpdump", "-i" + interface, "-U", "-w", outputfile, ws_filter]
  87. if len(flags) > 0:
  88. # insert after -U
  89. n = 3
  90. for flag in flags:
  91. cmd.insert(n, flag)
  92. n += 1
  93. process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
  94. processHandler.add_process(process)
  95. def save_tcp_trace(processHandler, filename, sender_ip, port):
  96. cmd_uptime = ["cat", "/proc/uptime"]
  97. logfile = open(filename, "a")
  98. subprocess.call(cmd_uptime, stdout=logfile)
  99. # /sys/kernel/debug/tracing/events/tcp/tcp_probe/enable
  100. cmd = ["cat", "/sys/kernel/debug/tracing/trace_pipe"]
  101. process = subprocess.Popen(cmd, stdout=logfile)
  102. processHandler.add_process(process, logfile=logfile)
  103. def get_ip_from_interface(interface):
  104. c = GET_IPV4_SHELL_COMMAND.format(interface)
  105. ip = (
  106. subprocess.check_output(c, shell=True)
  107. .decode("utf-8")
  108. .replace(" ", "")
  109. .replace("\n", "")
  110. )
  111. return ip
  112. def config2json(args):
  113. config = vars(args).copy()
  114. # parse the key-value pairs from set arg
  115. set_args = parse_vars(args.set)
  116. config["set"] = set_args
  117. # delete not used keys
  118. del config["interface"]
  119. del config["server"]
  120. del config["client"]
  121. del config["folder"]
  122. return bytes(json.dumps(config), "utf-8")
  123. def json2config(json_string):
  124. return json.loads(json_string)
  125. def print_message(m):
  126. print("[{}]\t{}".format(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), m))
  127. def deactivate_hystart():
  128. print_message("Deactivate HyStart")
  129. os.system('echo "0" > /sys/module/tcp_cubic/parameters/hystart')
  130. def activate_hystart():
  131. print_message("Activate HyStart")
  132. os.system('echo "1" > /sys/module/tcp_cubic/parameters/hystart')
  133. def is_hystart_activated():
  134. return subprocess.check_output('cat /sys/module/tcp_cubic/parameters/hystart', shell=True) == "1"
  135. def set_default_receive_window():
  136. print_message("Set receive window to default values")
  137. os.system('echo "212992" > /proc/sys/net/core/rmem_max')
  138. os.system('echo "212992" > /proc/sys/net/core/rmem_default')
  139. os.system('echo "4096 131072 6291456" > /proc/sys/net/ipv4/tcp_rmem')
  140. def raise_receive_window():
  141. print_message("Multiply receive windows by factor 10")
  142. os.system('echo "2129920" > /proc/sys/net/core/rmem_max')
  143. os.system('echo "2129920" > /proc/sys/net/core/rmem_default')
  144. os.system('echo "40960 1310720 62914560" > /proc/sys/net/ipv4/tcp_rmem')
  145. def monitor_serial(ser, output_file):
  146. run_cmds = [NR_CQI_COMMAND, NR_SERVINGCELL_COMMAND, NR_EN_DC_STATUS_COMMAND]
  147. try:
  148. while ser.is_open:
  149. response = subprocess.check_output(CMD_TIME_EPOCH, shell=True).decode("utf-8")
  150. for cmd in run_cmds:
  151. ser.write(cmd)
  152. sleep(0.3)
  153. response += ser.read(ser.inWaiting()).decode("utf-8")
  154. response = (
  155. response.replace("\n", ";")
  156. .replace("\r", "")
  157. .replace(";;OK", ";")
  158. .replace(";;", ";")
  159. )
  160. write_to_file(output_file, response + "\n")
  161. except:
  162. if not ser.is_open:
  163. print_message("Serial port is closed. Exit monitoring thread.")
  164. else:
  165. print_message("Something went wrong while monitoring serial interface. Exit monitoring thread.")
  166. return
  167. class Server:
  168. def __init__(self, config):
  169. self.config = config
  170. def measure(self):
  171. print("Received config:")
  172. print(self.config)
  173. print_message("Start measurement")
  174. if self.config["bandwidth"]:
  175. self.bandwidth()
  176. elif self.config["drx"]:
  177. self.drx()
  178. elif self.config["harq"]:
  179. self.harq()
  180. elif self.config["cbr"]:
  181. self.cbr()
  182. elif self.config["tcp_parallel"]:
  183. self.tcp_parallel_flows()
  184. else:
  185. print_message("Nothing to do on server side.")
  186. def drx(self):
  187. print_message("DRX nothing to do on server side.")
  188. def harq(self):
  189. print_message("HARQ nothing to do on server side.")
  190. def bandwidth(self):
  191. use_reverse_mode = False
  192. if "reverse" in self.config["set"]:
  193. use_reverse_mode = self.config["set"]["reverse"]
  194. print_message("Use reverse mode: {}".format(use_reverse_mode))
  195. tcp_algo = list()
  196. if "algo" in self.config["set"]:
  197. for s in self.config["set"]["algo"].split(","):
  198. tcp_algo.append(s)
  199. print_message("Using {} for TCP transmissions.".format(s))
  200. else:
  201. tcp_algo.append("cubic")
  202. print_message("Using {} for TCP transmissions.".format(tcp_algo[0]))
  203. alternate_hystart = False
  204. if "alternate_hystart" in self.config["set"]:
  205. if self.config["set"]["alternate_hystart"] == "true":
  206. alternate_hystart = True
  207. # prevent address already in use
  208. sleep(2)
  209. ws_filter = ""
  210. if use_reverse_mode:
  211. # server sends
  212. for n in range(1, self.config["number_of_measurements"] + 1):
  213. print_message(
  214. "{} of {}".format(n, self.config["number_of_measurements"])
  215. )
  216. iperf_command = [
  217. "iperf3",
  218. "-s",
  219. "--port",
  220. str(self.config["port"]),
  221. "--one-off",
  222. ]
  223. filepath_tcp_trace = "{}{}_bandwidth_reverse_{}_{}_{}.txt".format(
  224. self.config["folder"], self.config["prefix"], "tcp", "tcp_trace", n
  225. )
  226. save_tcp_trace(
  227. processHandler,
  228. filepath_tcp_trace,
  229. self.config["server"],
  230. self.config["port"],
  231. )
  232. subprocess.call(iperf_command)
  233. processHandler.kill_all()
  234. else:
  235. # client sends
  236. ws_filter = "{} and port {}".format("tcp", self.config["port"])
  237. print_message("Use ws filter: {}".format(ws_filter))
  238. name_option = ""
  239. state_counter = 0
  240. congestion_control_index = 0
  241. for n in range(1, self.config["number_of_measurements"] + 1):
  242. print_message(
  243. "Measurement {} of {}".format(
  244. n, self.config["number_of_measurements"]
  245. )
  246. )
  247. tcpdump_flags = []
  248. if alternate_hystart:
  249. if state_counter == 0:
  250. tcp_algo = "cubic"
  251. raise_receive_window()
  252. name_option = "_cubic_slowstart_raise_"
  253. state_counter = 1
  254. elif state_counter == 1:
  255. tcp_algo = "cubic"
  256. set_default_receive_window()
  257. name_option = "_cubic_hystart_default_"
  258. state_counter = 2
  259. elif state_counter == 2:
  260. tcp_algo = "bbr"
  261. raise_receive_window()
  262. name_option = "_bbr_raise_"
  263. state_counter = 3
  264. elif state_counter == 3:
  265. tcp_algo = "bbr"
  266. set_default_receive_window()
  267. name_option = "_bbr_default_"
  268. state_counter = 0
  269. else:
  270. name_option = ""
  271. filepath = "{}{}{}_bandwidth_{}_{}_{}.pcap".format(
  272. self.config["folder"], self.config["prefix"], name_option, "tcp", tcp_algo[congestion_control_index], n
  273. )
  274. tcpdump_flags.append("-s96")
  275. thread = Thread(
  276. target=execute_tcp_dump,
  277. args=(
  278. processHandler,
  279. args.interface,
  280. filepath,
  281. ws_filter,
  282. tcpdump_flags,
  283. ),
  284. )
  285. thread.start()
  286. sleep(2)
  287. iperf_command = [
  288. "iperf3",
  289. "-s",
  290. "--port",
  291. str(self.config["port"]),
  292. "--one-off",
  293. ]
  294. subprocess.call(iperf_command)
  295. sleep(2)
  296. processHandler.kill_all()
  297. congestion_control_index = (congestion_control_index + 1) % len(tcp_algo)
  298. def cbr(self):
  299. use_reverse_mode = False
  300. if "reverse" in self.config["set"]:
  301. use_reverse_mode = self.config["set"]["reverse"] == "true"
  302. print_message("Use reverse mode: {}".format(use_reverse_mode))
  303. # bitrate is only used for filenames on server side
  304. bitrate = "1M"
  305. if "bitrate" in self.config["set"]:
  306. bitrate = self.config["set"]["bitrate"]
  307. sleep_time = 60.0
  308. if "sleep" in self.config["set"]:
  309. sleep_time = float(self.config["set"]["sleep"])
  310. print_message("Sleep time for each measurement: {}s".format(sleep_time))
  311. # build wireshark filter
  312. if use_reverse_mode:
  313. ws_filter = "{} and src {}".format("udp", self.config["server"])
  314. else:
  315. ws_filter = "{} and dst {} and port {}".format(
  316. "udp", self.config["server"], self.config["port"]
  317. )
  318. print_message("Use ws filter: {}".format(ws_filter))
  319. # build iperf3 command
  320. iperf_command = [
  321. "iperf3",
  322. "-s",
  323. "--port",
  324. str(self.config["port"]),
  325. "--one-off",
  326. ]
  327. for n in range(1, self.config["number_of_measurements"] + 1):
  328. print_message("{} of {}".format(n, self.config["number_of_measurements"]))
  329. sleep(sleep_time)
  330. pcap_filepath = "{}{}_cbr_server_{}_bitrate{}_{}.pcap".format(
  331. self.config["folder"],
  332. self.config["prefix"],
  333. "sender" if use_reverse_mode else "receiver",
  334. bitrate,
  335. n,
  336. )
  337. thread = Thread(
  338. target=execute_tcp_dump,
  339. args=(
  340. processHandler,
  341. self.config["interface"],
  342. pcap_filepath,
  343. ws_filter,
  344. ),
  345. )
  346. thread.start()
  347. sleep(1)
  348. subprocess.call(iperf_command)
  349. sleep(1)
  350. processHandler.kill_all()
  351. def tcp_parallel_flows(self):
  352. # for n in range(1, (self.config["number_of_measurements"] * 2) + 1):
  353. # print_message(
  354. # "Measurement {} of {}".format(
  355. # n, self.config["number_of_measurements"]
  356. # )
  357. # )
  358. sleep(4)
  359. iperf_command = [
  360. "iperf3",
  361. "-s",
  362. "--port",
  363. str(self.config["port"]),
  364. # "--one-off",
  365. ]
  366. subprocess.call(iperf_command)
  367. class Client:
  368. def __init__(self, config):
  369. self.config = config
  370. def measure(self):
  371. print("Config:")
  372. print(self.config)
  373. sleep(1)
  374. print_message("Start measurement")
  375. ser = None
  376. if self.config["serial"] is not None:
  377. print_message("Opening serial port for {}".format(self.config["serial"]))
  378. ser = serial.Serial(
  379. port=self.config["serial"],
  380. baudrate=self.config["baudrate"],
  381. )
  382. ser.isOpen()
  383. ser_filepath = "{}{}_serial_monitor_output.txt".format(
  384. self.config["folder"], self.config["prefix"]
  385. )
  386. ser_thread = Thread(
  387. target=monitor_serial,
  388. args=(
  389. ser,
  390. ser_filepath,
  391. ),
  392. )
  393. ser_thread.start()
  394. if self.config["bandwidth"]:
  395. self.bandwidth()
  396. elif self.config["drx"]:
  397. self.drx()
  398. elif self.config["harq"]:
  399. self.harq()
  400. elif self.config["cbr"]:
  401. self.cbr()
  402. elif self.config["tcp_parallel"]:
  403. self.tcp_parallel_flows()
  404. elif self.config["ping"]:
  405. self.ping()
  406. if ser is not None:
  407. print_message("Closing serial port...")
  408. ser.close()
  409. sleep(2)
  410. print_message("done...")
  411. def ping(self):
  412. c = "ping {} -I {} -i {} -c {}".format(
  413. self.config["server"],
  414. self.config["interface"],
  415. 0,
  416. self.config["number_of_measurements"],
  417. )
  418. command = [c]
  419. print_message("Start sending {} pings with nog gap.".format(self.config["number_of_measurements"]))
  420. ping_out = subprocess.check_output(command, shell=True).decode("utf-8")
  421. filepath = "{}{}_ping_no_gap.txt".format(
  422. self.config["folder"], self.config["prefix"]
  423. )
  424. print_message("Write measured pings to: {}".format(filepath))
  425. write_to_file(filepath, ping_out)
  426. def drx(self):
  427. # send ICMP pings to server and increase the gab
  428. # check for set args
  429. if "pre_ping" in self.config["set"]:
  430. is_pre_ping_enabled = (
  431. True if self.config["set"]["pre_ping"] == "true" else False
  432. )
  433. else:
  434. is_pre_ping_enabled = False
  435. if "intervals" in self.config["set"]:
  436. intervals = []
  437. interval_ranges = self.config["set"]["intervals"].split(",")
  438. for interval_range in interval_ranges:
  439. start, end, increase = [float(x) for x in interval_range.split(":")]
  440. curr = start
  441. while curr <= end:
  442. intervals.append(curr)
  443. curr += increase
  444. else:
  445. intervals = []
  446. filepath = "{}{}_ping_drx_raw.txt".format(
  447. self.config["folder"], self.config["prefix"]
  448. )
  449. if is_pre_ping_enabled:
  450. print_message("Send pre pings.")
  451. c = "ping {} -I {} -i {} -c {}".format(
  452. self.config["server"],
  453. self.config["interface"],
  454. intervals[0],
  455. self.config["number_of_measurements"],
  456. )
  457. command = [c]
  458. subprocess.check_output(command, shell=True)
  459. else:
  460. print_message("Preping is disabled. Wait 60s for drx-long-sleep...")
  461. sleep(60)
  462. for i in intervals:
  463. current = i
  464. if not is_pre_ping_enabled:
  465. sleep(current)
  466. c = "echo 'gap={}s' && ping {} -I {} -i {} -c {} && echo ';;;'".format(
  467. current,
  468. self.config["server"],
  469. self.config["interface"],
  470. current,
  471. self.config["number_of_measurements"],
  472. )
  473. command = [c]
  474. print_message("ping with {}s gap".format(current))
  475. background_write_to_file(
  476. filepath,
  477. subprocess.check_output(command, shell=True).decode("utf-8"),
  478. )
  479. # Read raw out and format it to csv
  480. regex = r"gap=(.*)s|64 bytes from (.*\..*\..*\..*) icmp_seq=(\d+) ttl=(\d+) time=(.*) ms"
  481. filepath = "{}{}_ping_drx_raw.txt".format(
  482. self.config["folder"], self.config["prefix"]
  483. )
  484. print_message("Format script output")
  485. f = open(filepath, "r")
  486. raw = f.read()
  487. f.close()
  488. lines = raw.split("\n")
  489. csv_out = "n,gap,rtt\n"
  490. n = 1
  491. gap = ""
  492. for l in lines:
  493. match = re.match(regex, l)
  494. if match:
  495. if match.group(5):
  496. time = match.group(5)
  497. csv_out = csv_out + "{},{},{}\n".format(n, gap, time)
  498. n += 1
  499. else:
  500. gap = match.group(1)
  501. filepath = "{}{}_ping_drx.csv".format(
  502. self.config["folder"], self.config["prefix"]
  503. )
  504. print_message("Write file {}".format(filepath))
  505. write_to_file(filepath, csv_out)
  506. def harq(self):
  507. ws_filter = "tcp[tcpflags] & (tcp-syn) != 0"
  508. print_message("Use ws filter: {}".format(ws_filter))
  509. filepath = "{}{}_{}_tcp_handshakes_http_client.pcap".format(
  510. self.config["folder"],
  511. self.config["prefix"],
  512. self.config["number_of_measurements"],
  513. )
  514. thread = Thread(
  515. target=execute_tcp_dump,
  516. args=(
  517. processHandler,
  518. self.config["interface"],
  519. filepath,
  520. ws_filter,
  521. ),
  522. )
  523. thread.start()
  524. sleep(5)
  525. for n in range(1, self.config["number_of_measurements"] + 1):
  526. print_message("{} of {}".format(n, self.config["number_of_measurements"]))
  527. requests.get("http://{}".format(self.config["server"]))
  528. sleep(5)
  529. processHandler.kill_all()
  530. def bandwidth(self):
  531. use_reverse_mode = False
  532. if "reverse" in self.config["set"]:
  533. use_reverse_mode = self.config["set"]["reverse"] == "true"
  534. print_message("Use reverse mode: {}".format(use_reverse_mode))
  535. tcp_algo = list()
  536. if "algo" in self.config["set"]:
  537. for s in self.config["set"]["algo"].split(","):
  538. tcp_algo.append(s)
  539. print_message("Using {} for TCP transmissions.".format(s))
  540. else:
  541. tcp_algo.append("cubic")
  542. print_message("Using {} for TCP transmissions.".format(tcp_algo[0]))
  543. alternate_hystart = False
  544. if "alternate_hystart" in self.config["set"]:
  545. if self.config["set"]["alternate_hystart"] == "true":
  546. alternate_hystart = True
  547. print_message("Alternate between HyStart and Slowstart for Cubic.")
  548. time = "10"
  549. if "time" in self.config["set"]:
  550. time = self.config["set"]["time"]
  551. sleep(2)
  552. congestion_control_index = 0
  553. if use_reverse_mode:
  554. # server is sending
  555. ws_filter = "{} and port {}".format("tcp", self.config["port"])
  556. print_message("Use ws filter: {}".format(ws_filter))
  557. for n in range(1, self.config["number_of_measurements"] + 1):
  558. print_message(
  559. "{} of {}".format(n, self.config["number_of_measurements"])
  560. )
  561. print_message(
  562. "Measurement {} of {}".format(
  563. n, self.config["number_of_measurements"]
  564. )
  565. )
  566. tcpdump_flags = []
  567. filepath = "{}{}_bandwidth_reverse_{}_{}_{}.pcap".format(
  568. self.config["folder"], self.config["prefix"], "tcp", tcp_algo[congestion_control_index], n
  569. )
  570. tcpdump_flags.append("-s96")
  571. thread = Thread(
  572. target=execute_tcp_dump,
  573. args=(
  574. processHandler,
  575. self.config["interface"],
  576. filepath,
  577. ws_filter,
  578. tcpdump_flags,
  579. ),
  580. )
  581. thread.start()
  582. sleep(5)
  583. iperf_command = [
  584. "iperf3",
  585. "-c",
  586. self.config["server"],
  587. "-R",
  588. "-t",
  589. time,
  590. "-C",
  591. tcp_algo[congestion_control_index],
  592. ]
  593. subprocess.call(iperf_command)
  594. sleep(4)
  595. processHandler.kill_all()
  596. congestion_control_index = (congestion_control_index + 1) % len(tcp_algo)
  597. else:
  598. # client is sending
  599. state_counter = 0
  600. name_option = ""
  601. for n in range(1, self.config["number_of_measurements"] + 1):
  602. print_message(
  603. "{} of {}".format(n, self.config["number_of_measurements"])
  604. )
  605. iperf_command = ["iperf3", "-c", self.config["server"], "-t", time]
  606. if alternate_hystart:
  607. if state_counter == 0:
  608. tcp_algo = "cubic"
  609. deactivate_hystart()
  610. name_option = "_cubic_slowstart_raise_"
  611. state_counter = 1
  612. elif state_counter == 1:
  613. tcp_algo = "cubic"
  614. activate_hystart()
  615. name_option = "_cubic_hystart_default_"
  616. state_counter = 2
  617. elif state_counter == 2:
  618. tcp_algo = "bbr"
  619. deactivate_hystart()
  620. name_option = "_bbr_raise_"
  621. state_counter = 3
  622. elif state_counter == 3:
  623. tcp_algo = "bbr"
  624. activate_hystart()
  625. name_option = "_bbr_default_"
  626. state_counter = 0
  627. iperf_command.append("-C")
  628. iperf_command.append(tcp_algo[congestion_control_index])
  629. filepath_tcp_trace = "{}{}_bandwidth_{}_{}_{}.txt".format(
  630. self.config["folder"], self.config["prefix"], "tcp", "tcp_trace", n
  631. )
  632. save_tcp_trace(
  633. processHandler,
  634. filepath_tcp_trace,
  635. self.config["client"],
  636. self.config["port"],
  637. )
  638. sleep(2)
  639. subprocess.call(iperf_command)
  640. processHandler.kill_all()
  641. congestion_control_index = (congestion_control_index + 1) % len(tcp_algo)
  642. sleep(4)
  643. def cbr(self):
  644. bitrate = "1M"
  645. if "bitrate" in self.config["set"]:
  646. bitrate = self.config["set"]["bitrate"]
  647. print("Set bitrate to {}.".format(bitrate))
  648. time = "2"
  649. if "time" in self.config["set"]:
  650. time = self.config["set"]["time"]
  651. use_reverse_mode = False
  652. if "reverse" in self.config["set"]:
  653. use_reverse_mode = self.config["set"]["reverse"] == "true"
  654. print_message("Use reverse mode: {}".format(use_reverse_mode))
  655. sleep_time = 60.0
  656. if "sleep" in self.config["set"]:
  657. sleep_time = float(self.config["set"]["sleep"])
  658. print_message("Sleep time for each measurement: {}s".format(sleep_time))
  659. # build wireshark filter
  660. if use_reverse_mode:
  661. ws_filter = "{} and dst {}".format("udp", self.config["client"])
  662. else:
  663. ws_filter = "{} and src {} and port {}".format(
  664. "udp", self.config["client"], self.config["port"]
  665. )
  666. print_message("Use ws filter: {}".format(ws_filter))
  667. # build iperf3 command
  668. iperf_command = [
  669. "iperf3",
  670. "-c",
  671. self.config["server"],
  672. "-t",
  673. time,
  674. "--udp",
  675. "-b",
  676. bitrate,
  677. ]
  678. if use_reverse_mode:
  679. iperf_command.append("-R")
  680. for n in range(1, self.config["number_of_measurements"] + 1):
  681. print_message("{} of {}".format(n, self.config["number_of_measurements"]))
  682. sleep(sleep_time)
  683. pcap_filepath = "{}{}_cbr_client_{}_bitrate{}_{}.pcap".format(
  684. self.config["folder"],
  685. self.config["prefix"],
  686. "receiver" if use_reverse_mode else "sender",
  687. bitrate,
  688. n,
  689. )
  690. thread = Thread(
  691. target=execute_tcp_dump,
  692. args=(
  693. processHandler,
  694. self.config["interface"],
  695. pcap_filepath,
  696. ws_filter,
  697. ),
  698. )
  699. thread.start()
  700. sleep(2)
  701. subprocess.call(iperf_command)
  702. sleep(2)
  703. processHandler.kill_all()
  704. def tcp_parallel_flows(self):
  705. tcp_algo = "cubic"
  706. if "algo" in self.config["set"]:
  707. tcp_algo = self.config["set"]["algo"]
  708. print_message("Using {} for tcp.".format(tcp_algo))
  709. time = "12"
  710. if "time" in self.config["set"]:
  711. time = self.config["set"]["time"]
  712. start = "1"
  713. if "start" in self.config["set"]:
  714. start = self.config["set"]["start"]
  715. end = "2"
  716. if "end" in self.config["set"]:
  717. end = self.config["set"]["end"]
  718. # build flow seq
  719. flows = []
  720. runs = []
  721. for i in range(int(start), int(end) + 1):
  722. flows.append(i)
  723. flows.append(i)
  724. for i in range(1, self.config["number_of_measurements"] + 1):
  725. runs.append(i)
  726. runs.append(i)
  727. print_message(
  728. "{} to {} flows, with {} runs.".format(
  729. start, end, self.config["number_of_measurements"]
  730. )
  731. )
  732. sleep(5)
  733. for flow in flows:
  734. for run in runs:
  735. if tcp_algo == "cubic":
  736. tcp_algo = "bbr"
  737. else:
  738. tcp_algo = "cubic"
  739. filepath = "{}{}_{}parallel_tcp_{}_flows_{}.txt".format(
  740. self.config["folder"], self.config["prefix"], flow, tcp_algo, run
  741. )
  742. c = "iperf3 -c {} -t {} -C {} -P {}".format(
  743. self.config["server"], time, tcp_algo, flow
  744. )
  745. iperf_command = [c]
  746. print_message("{} parallel {} flows".format(flow, tcp_algo))
  747. retry = True
  748. while retry:
  749. try:
  750. out = subprocess.check_output(iperf_command, shell=True).decode(
  751. "utf-8"
  752. )
  753. write_to_file(filepath, out)
  754. retry = False
  755. except:
  756. print("iPerf Error.\t{}".format(iperf_command))
  757. sleep(1)
  758. if retry:
  759. print_message("Retry in 5s")
  760. sleep(5)
  761. async def start_server(args):
  762. # get configuration from client
  763. print_message("Start Server")
  764. config = None
  765. ip = get_ip_from_interface(args.interface)
  766. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  767. s.setblocking(True)
  768. s.bind((ip, args.port))
  769. s.listen(1)
  770. conn, addr = s.accept()
  771. print_message("Got connection from {}".format(addr))
  772. while True:
  773. data = conn.recv(1024)
  774. if not data:
  775. break
  776. config = json2config(data.decode("utf-8"))
  777. conn.close()
  778. s.close()
  779. # add interface and ip to config
  780. config["interface"] = args.interface
  781. config["client"] = addr[0]
  782. config["server"] = ip
  783. config["folder"] = args.folder
  784. server = Server(config)
  785. server.measure()
  786. async def start_client(args):
  787. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  788. s.connect((args.client, args.port))
  789. s.send(config2json(args))
  790. s.close()
  791. print("Config send.")
  792. # overwrite ip
  793. config = vars(args).copy()
  794. config["server"] = args.client
  795. config["client"] = get_ip_from_interface(args.interface)
  796. new_set = dict()
  797. if config["set"] is not None:
  798. for pair in config["set"]:
  799. tmp = pair.split("=")
  800. new_set[tmp[0]] = tmp[1]
  801. config["set"] = new_set
  802. client = Client(config)
  803. client.measure()
  804. if __name__ == "__main__":
  805. now = datetime.now()
  806. processHandler = ProcessHandler()
  807. parser = ArgumentParser()
  808. # common arguments
  809. # required
  810. parser.add_argument("-i", "--interface", required=True, help="Interface.")
  811. # optional
  812. parser.add_argument("-p", "--port", default=5201, type=int, help="Port.")
  813. parser.add_argument(
  814. "-f",
  815. "--folder",
  816. default=os.path.dirname(os.path.realpath(__file__)) + "/",
  817. help="Folder for the pcap files.",
  818. )
  819. # server exclusive arguments
  820. parser.add_argument(
  821. "-s",
  822. "--server",
  823. action="store_true",
  824. default=False,
  825. help="Starts the script in server mode.",
  826. )
  827. # client exclusive arguments
  828. parser.add_argument(
  829. "-c",
  830. "--client",
  831. default=None,
  832. help="Start in client mode and set the server IPv4 address.",
  833. )
  834. parser.add_argument(
  835. "--prefix", default=now.strftime("%Y-%m-%d"), help="Prefix on filename."
  836. )
  837. parser.add_argument(
  838. "--set",
  839. metavar="KEY=VALUE",
  840. nargs="+",
  841. help="Set a number of key-value pairs "
  842. "(do not put spaces before or after the = sign). "
  843. "If a value contains spaces, you should define "
  844. "it with double quotes: "
  845. 'foo="this is a sentence". Note that '
  846. "values are always treated as strings.",
  847. )
  848. parser.add_argument(
  849. "-n",
  850. "--number_of_measurements",
  851. type=int,
  852. default=10,
  853. help="Number of measurements",
  854. )
  855. parser.add_argument(
  856. "--ping",
  857. action="store_true",
  858. default=False,
  859. help="Sending ICMP pings.",
  860. )
  861. parser.add_argument(
  862. "--bandwidth",
  863. action="store_true",
  864. default=False,
  865. help="Measure greedy tcp throughput with iperf3."
  866. "Use the --set flag for: "
  867. "reverse=false enable reverse mode. Server is sending."
  868. "algo=cubic set tcp algorithm. Can be a comma separated string for multiple congestion control algorithms."
  869. "alternate_hystart=false if enabled alternate reproduce every Cubic measurement wicht and without HyStart (Also raises the receive window.). "
  870. "time=10 length of transmission in seconds.",
  871. )
  872. parser.add_argument(
  873. "--tcp_parallel",
  874. action="store_true",
  875. default=False,
  876. help="Measure greedy tcp throughput with parallel tcp flows. Alternate between bbr and cubic."
  877. "Use the --set flag for: "
  878. "start=1 Start value for flows "
  879. "end=2 End value for flows "
  880. "time=12 transmission time. ",
  881. )
  882. parser.add_argument(
  883. "--cbr",
  884. action="store_true",
  885. default=False,
  886. help="Measure Udp CBR traffic."
  887. "Use the --set flag for: "
  888. "reverse=false enable reverse mode. Server is sending. "
  889. "bitrate=1M target bitrate in bits/sec (0 for unlimited) "
  890. "[KMG] indicates options that support a K/M/G suffix for kilo-, mega-, or giga-. "
  891. "time=2 time in seconds to transmit for. "
  892. "sleep=60 sleep before each cbr traffic in seconds. ",
  893. )
  894. parser.add_argument(
  895. "--drx",
  896. action="store_true",
  897. default=False,
  898. help="Measure RTTs with ping tool. To detect DRX cycles. "
  899. "Use the --set flag for: "
  900. "intervals=start:end:increase,start2:end2:increase2... set intervals for gaps, end is inclusive, "
  901. "values in [s]. At least one interval is required. E.g.: 0:3:1 creates values [0,1,2,3]"
  902. "pre_ping=false if enabled pings are send before measurements to set device in continuous reception mode. ",
  903. )
  904. parser.add_argument(
  905. "--harq",
  906. action="store_true",
  907. default=False,
  908. help="Captures http transmissions on client side.",
  909. )
  910. parser.add_argument(
  911. "--serial",
  912. default=None,
  913. help="Serial device e.g. /dev/ttyUSB2",
  914. )
  915. parser.add_argument(
  916. "--baudrate",
  917. default=115200,
  918. type=int,
  919. help="Serial device baudrate",
  920. )
  921. args = parser.parse_args()
  922. if args.server:
  923. asyncio.run(start_server(args))
  924. elif args.client is not None:
  925. asyncio.run(start_client(args))