main.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. import subprocess
  2. import os
  3. import re
  4. import argparse
  5. from flask import Flask, render_template, redirect, request, url_for
  6. BANDWIDTH_UNITS = [
  7. "bit", # Bits per second
  8. "kbit", # Kilobits per second
  9. "mbit", # Megabits per second
  10. "gbit", # Gigabits per second
  11. "tbit", # Terabits per second
  12. "bps", # Bytes per second
  13. "kbps", # Kilobytes per second
  14. "mbps", # Megabytes per second
  15. "gbps", # Gigabytes per second
  16. "tbps", # Terabytes per second
  17. ]
  18. STANDARD_UNIT = "mbit"
  19. app = Flask(__name__)
  20. pattern = None
  21. dev_list = None
  22. app.static_folder = "static"
  23. def parse_arguments():
  24. parser = argparse.ArgumentParser(
  25. description="TC web GUI",
  26. formatter_class=argparse.ArgumentDefaultsHelpFormatter,
  27. )
  28. parser.add_argument(
  29. "--ip",
  30. type=str,
  31. default=os.environ.get("TCGUI_IP"),
  32. help="The IP where the server is listening",
  33. )
  34. parser.add_argument(
  35. "--port",
  36. type=int,
  37. default=os.environ.get("TCGUI_PORT"),
  38. help="The port where the server is listening",
  39. )
  40. parser.add_argument(
  41. "--dev",
  42. type=str,
  43. nargs="*",
  44. default=os.environ.get("TCGUI_DEV"),
  45. help="The interfaces to restrict to",
  46. )
  47. parser.add_argument(
  48. "--regex",
  49. type=str,
  50. default=os.environ.get("TCGUI_REGEX"),
  51. help="A regex to match interfaces",
  52. )
  53. parser.add_argument("--debug", action="store_true", help="Run Flask in debug mode")
  54. return parser.parse_args()
  55. @app.route("/")
  56. def main():
  57. rules = get_active_rules()
  58. return render_template(
  59. "main.html", rules=rules, units=BANDWIDTH_UNITS, standard_unit=STANDARD_UNIT
  60. )
  61. @app.route("/new_rule/<interface>", methods=["POST"])
  62. def new_rule(interface):
  63. delay = request.form["Delay"]
  64. delay_variance = request.form["DelayVariance"]
  65. loss = request.form["Loss"]
  66. loss_correlation = request.form["LossCorrelation"]
  67. duplicate = request.form["Duplicate"]
  68. reorder = request.form["Reorder"]
  69. reorder_correlation = request.form["ReorderCorrelation"]
  70. corrupt = request.form["Corrupt"]
  71. limit = request.form["Limit"]
  72. rate = request.form["Rate"]
  73. rate_unit = request.form["rate_unit"]
  74. interface = filter_interface_name(interface)
  75. # remove old setup
  76. command = "tc qdisc del dev %s root netem" % interface
  77. command = command.split(" ")
  78. proc = subprocess.Popen(command)
  79. proc.wait()
  80. # apply new setup
  81. command = "tc qdisc add dev %s root netem" % interface
  82. if rate != "":
  83. command += " rate %s%s" % (rate, rate_unit)
  84. if delay != "":
  85. command += " delay %sms" % delay
  86. if delay_variance != "":
  87. command += " %sms" % delay_variance
  88. if loss != "":
  89. command += " loss %s%%" % loss
  90. if loss_correlation != "":
  91. command += " %s%%" % loss_correlation
  92. if duplicate != "":
  93. command += " duplicate %s%%" % duplicate
  94. if reorder != "":
  95. command += " reorder %s%%" % reorder
  96. if reorder_correlation != "":
  97. command += " %s%%" % reorder_correlation
  98. if corrupt != "":
  99. command += " corrupt %s%%" % corrupt
  100. if limit != "":
  101. command += " limit %s" % limit
  102. print(command)
  103. command = command.split(" ")
  104. proc = subprocess.Popen(command)
  105. proc.wait()
  106. return redirect(url_for("main") + "#" + interface)
  107. @app.route("/remove_rule/<interface>", methods=["POST"])
  108. def remove_rule(interface):
  109. interface = filter_interface_name(interface)
  110. # remove old setup
  111. command = "tc qdisc del dev %s root netem" % interface
  112. command = command.split(" ")
  113. proc = subprocess.Popen(command)
  114. proc.wait()
  115. return redirect(url_for("main") + "#" + interface)
  116. def filter_interface_name(interface):
  117. return re.sub(r"[^A-Za-z0-9_-]+", "", interface)
  118. def get_active_rules():
  119. proc = subprocess.Popen(["tc", "qdisc"], stdout=subprocess.PIPE)
  120. output = proc.communicate()[0].decode()
  121. lines = output.split("\n")[:-1]
  122. rules = []
  123. dev = set()
  124. for line in lines:
  125. arguments = line.split()
  126. rule = parse_rule(arguments)
  127. if rule["name"] and rule["name"] not in dev:
  128. rules.append(rule)
  129. dev.add(rule["name"])
  130. return rules
  131. def parse_rule(split_rule):
  132. rule = {
  133. "name": None,
  134. "rate": None,
  135. "delay": None,
  136. "delayVariance": None,
  137. "loss": None,
  138. "lossCorrelation": None,
  139. "duplicate": None,
  140. "reorder": None,
  141. "reorderCorrelation": None,
  142. "corrupt": None,
  143. "limit": None,
  144. }
  145. i = 0
  146. for argument in split_rule:
  147. if argument == "dev":
  148. # Both regex pattern and dev name can be given
  149. # An interface could match the pattern and/or
  150. # be in the interface list
  151. if pattern is None and dev_list is None:
  152. rule["name"] = split_rule[i + 1]
  153. if pattern:
  154. if pattern.match(split_rule[i + 1]):
  155. rule["name"] = split_rule[i + 1]
  156. if dev_list:
  157. if split_rule[i + 1] in dev_list:
  158. rule["name"] = split_rule[i + 1]
  159. elif argument == "rate":
  160. rule["rate"] = split_rule[i + 1].split("Mbit")[0]
  161. elif argument == "delay":
  162. rule["delay"] = split_rule[i + 1]
  163. if len(split_rule) > (i + 2) and "ms" in split_rule[i + 2]:
  164. rule["delayVariance"] = split_rule[i + 2]
  165. elif argument == "loss":
  166. rule["loss"] = split_rule[i + 1]
  167. if len(split_rule) > (i + 2) and "%" in split_rule[i + 2]:
  168. rule["lossCorrelation"] = split_rule[i + 2]
  169. elif argument == "duplicate":
  170. rule["duplicate"] = split_rule[i + 1]
  171. elif argument == "reorder":
  172. rule["reorder"] = split_rule[i + 1]
  173. if len(split_rule) > (i + 2) and "%" in split_rule[i + 2]:
  174. rule["reorderCorrelation"] = split_rule[i + 2]
  175. elif argument == "corrupt":
  176. rule["corrupt"] = split_rule[i + 1]
  177. elif argument == "limit":
  178. rule["limit"] = split_rule[i + 1]
  179. i += 1
  180. return rule
  181. if __name__ == "__main__":
  182. if os.geteuid() != 0:
  183. print(
  184. "You need to have root privileges to run this script.\n"
  185. "Please try again, this time using 'sudo'. Exiting."
  186. )
  187. exit(1)
  188. # TC Variables
  189. args = parse_arguments()
  190. pattern = re.compile(args.regex) if args.regex else args.regex
  191. dev_list = args.dev
  192. # Flask Variable
  193. app_args = {"host": args.ip, "port": args.port}
  194. if not args.debug:
  195. app_args["debug"] = False
  196. app.debug = True
  197. app.run(**app_args)