瀏覽代碼

Create check.yml (#13)

* Create check.yml

* add black code format

* apply code format

* add action for docker build

* replace apt-get with apt

* fix tab and space mixes

* add docker compose

* fix actions yml
Markus Hofbauer 6 年之前
父節點
當前提交
be2fa96916
共有 8 個文件被更改,包括 196 次插入120 次删除
  1. 22 0
      .github/workflows/check.yml
  2. 13 0
      .github/workflows/docker.yml
  3. 10 0
      Makefile
  4. 9 6
      README.md
  5. 10 10
      Vagrantfile
  6. 11 0
      docker-compose.yml
  7. 119 104
      main.py
  8. 2 0
      requirements.txt

+ 22 - 0
.github/workflows/check.yml

@@ -0,0 +1,22 @@
+name: Check
+
+on: [push]
+
+jobs:
+  build:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v2
+    - name: Set up Python 3.8
+      uses: actions/setup-python@v1
+      with:
+        python-version: 3.8
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        pip install -r requirements.txt
+    - name: Check code format
+      run: |
+        make check_format

+ 13 - 0
.github/workflows/docker.yml

@@ -0,0 +1,13 @@
+name: Docker
+on: [push]
+
+jobs:
+
+  build:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v2
+    - name: Docker Build
+      run: docker build . --tag tcgui:$(date +%s)

+ 10 - 0
Makefile

@@ -0,0 +1,10 @@
+
+file_finder = find . -type f $(1) -not -path './venv/*'
+
+PY_FILES = $(call file_finder,-name "*.py")
+
+format:
+	$(PY_FILES) | xargs black
+
+check_format:
+	$(PY_FILES) | xargs black --diff --check

+ 9 - 6
README.md

@@ -1,5 +1,9 @@
 # tcgui
 # tcgui
 
 
+[![Actions Status](https://github.com/tum-lkn/tcgui/workflows/Check/badge.svg)](https://github.com/tum-lkn/tcgui)
+[![Actions Status](https://github.com/tum-lkn/tcgui/workflows/Docker/badge.svg)](https://github.com/tum-lkn/tcgui)
+[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
+
 A lightweight Python-based Web-GUI for Linux traffic control (`tc`) to set, view and delete traffic shaping rules. The Web-GUI is intended for short-term isolated testbeds or classroom scenarios and does not contain any security mechanisms.
 A lightweight Python-based Web-GUI for Linux traffic control (`tc`) to set, view and delete traffic shaping rules. The Web-GUI is intended for short-term isolated testbeds or classroom scenarios and does not contain any security mechanisms.
 
 
 No further changes are planned right now, but pull requests are welcome.
 No further changes are planned right now, but pull requests are welcome.
@@ -10,8 +14,8 @@ No further changes are planned right now, but pull requests are welcome.
 
 
 - Tested with Ubuntu 16.04 LTS & Ubuntu 18.04 LTS & Raspbian 4.14.98-v7+ (stretch, Debian 9.8)
 - Tested with Ubuntu 16.04 LTS & Ubuntu 18.04 LTS & Raspbian 4.14.98-v7+ (stretch, Debian 9.8)
 - `netem` tools & `python3-flask` are required
 - `netem` tools & `python3-flask` are required
-    - Ubuntu 16.04 : Install with `sudo apt-get install iproute python3-flask`
-    - Ubuntu 18.04 : Install with `sudo apt-get install iproute2 python3-flask`
+    - Ubuntu 16.04 : Install with `sudo apt install iproute python3-flask`
+    - Ubuntu 18.04 : Install with `sudo apt install iproute2 python3-flask`
 - More information:
 - More information:
     - https://calomel.org/network_loss_emulation.html
     - https://calomel.org/network_loss_emulation.html
     - https://wiki.linuxfoundation.org/networking/netem
     - https://wiki.linuxfoundation.org/networking/netem
@@ -19,10 +23,10 @@ No further changes are planned right now, but pull requests are welcome.
 ## Usage
 ## Usage
 
 
 - Execute the main.py file and go to http://localhost:5000:
 - Execute the main.py file and go to http://localhost:5000:
-    
+
     ```
     ```
     sudo python3 main.py
     sudo python3 main.py
-    
+
     --ip IP               The IP where the server is listening
     --ip IP               The IP where the server is listening
     --port PORT           The port where the server is listening
     --port PORT           The port where the server is listening
     --dev [DEV [DEV ...]] The interfaces to restrict to
     --dev [DEV [DEV ...]] The interfaces to restrict to
@@ -69,11 +73,10 @@ Start a receiver in the receiving VM:
 
 
 	vagrant ssh receiver
 	vagrant ssh receiver
 	iperf3 -s
 	iperf3 -s
-	
+
 Send a packet stream from the sender to the receiver:
 Send a packet stream from the sender to the receiver:
 
 
 	vagrant ssh sender
 	vagrant ssh sender
 	iperf3 -c 192.168.210.3 -t 300
 	iperf3 -c 192.168.210.3 -t 300
 
 
 Now access the GUI at http://192.168.210.2:5000/ and change the rate of interface eth1. You should see the sending/receiving rate to decrease to the set amount.
 Now access the GUI at http://192.168.210.2:5000/ and change the rate of interface eth1. You should see the sending/receiving rate to decrease to the set amount.
-

+ 10 - 10
Vagrantfile

@@ -5,18 +5,18 @@ Vagrant.configure("2") do |config|
   config.vm.box = "debian/contrib-jessie64"
   config.vm.box = "debian/contrib-jessie64"
 
 
   config.vm.define "sender" do |cfg|
   config.vm.define "sender" do |cfg|
-  	config.vm.provision "shell", inline: <<-SHELL
-            apt-get update
-            apt-get install -y iproute python3 python3-flask iperf3
-	SHELL
-	cfg.vm.network "private_network", ip: "192.168.210.2"
+    config.vm.provision "shell", inline: <<-SHELL
+            apt update
+            apt install -y iproute python3 python3-flask iperf3
+    SHELL
+    cfg.vm.network "private_network", ip: "192.168.210.2"
   end
   end
 
 
   config.vm.define "receiver" do |cfg|
   config.vm.define "receiver" do |cfg|
-  	config.vm.provision "shell", inline: <<-SHELL
-            apt-get update
-            apt-get install -y iperf3
-	SHELL
-	cfg.vm.network "private_network", ip: "192.168.210.3"
+    config.vm.provision "shell", inline: <<-SHELL
+            apt update
+            apt install -y iperf3
+    SHELL
+    cfg.vm.network "private_network", ip: "192.168.210.3"
   end
   end
 end
 end

+ 11 - 0
docker-compose.yml

@@ -0,0 +1,11 @@
+version: '3.7'
+services:
+  server:
+    build: .
+    environment:
+      TCGUI_IP: 0.0.0.0
+      TCGUI_PORT: 5000
+    ports:
+      - '5000:5000'
+    cap_add:
+      - NET_ADMIN

+ 119 - 104
main.py

@@ -7,16 +7,16 @@ from flask import Flask, render_template, redirect, request, url_for
 
 
 
 
 BANDWIDTH_UNITS = [
 BANDWIDTH_UNITS = [
-    "bit",   # Bits per second
+    "bit",  # Bits per second
     "kbit",  # Kilobits per second
     "kbit",  # Kilobits per second
     "mbit",  # Megabits per second
     "mbit",  # Megabits per second
     "gbit",  # Gigabits per second
     "gbit",  # Gigabits per second
     "tbit",  # Terabits per second
     "tbit",  # Terabits per second
-    "bps",   # Bytes per second
+    "bps",  # Bytes per second
     "kbps",  # Kilobytes per second
     "kbps",  # Kilobytes per second
     "mbps",  # Megabytes per second
     "mbps",  # Megabytes per second
     "gbps",  # Gigabytes per second
     "gbps",  # Gigabytes per second
-    "tbps"   # Terabytes per second
+    "tbps",  # Terabytes per second
 ]
 ]
 
 
 STANDARD_UNIT = "mbit"
 STANDARD_UNIT = "mbit"
@@ -28,155 +28,170 @@ dev_list = None
 
 
 
 
 def parse_arguments():
 def parse_arguments():
-    parser = argparse.ArgumentParser(description='TC web GUI')
-    parser.add_argument('--ip', type=str, required=False,
-                        help='The IP where the server is listening')
-    parser.add_argument('--port', type=int, required=False,
-                        help='The port where the server is listening')
-    parser.add_argument('--dev', type=str, nargs='*', required=False,
-                        help='The interfaces to restrict to')
-    parser.add_argument('--regex', type=str, required=False,
-                        help='A regex to match interfaces')
-    parser.add_argument('--debug', action='store_true',
-                        help='Run Flask in debug mode')
+    parser = argparse.ArgumentParser(description="TC web GUI")
+    parser.add_argument(
+        "--ip", type=str, required=False, help="The IP where the server is listening"
+    )
+    parser.add_argument(
+        "--port",
+        type=int,
+        required=False,
+        help="The port where the server is listening",
+    )
+    parser.add_argument(
+        "--dev",
+        type=str,
+        nargs="*",
+        required=False,
+        help="The interfaces to restrict to",
+    )
+    parser.add_argument(
+        "--regex", type=str, required=False, help="A regex to match interfaces"
+    )
+    parser.add_argument("--debug", action="store_true", help="Run Flask in debug mode")
     return parser.parse_args()
     return parser.parse_args()
 
 
 
 
 @app.route("/")
 @app.route("/")
 def main():
 def main():
     rules = get_active_rules()
     rules = get_active_rules()
-    return render_template('main.html', rules=rules, units=BANDWIDTH_UNITS,
-                           standard_unit=STANDARD_UNIT)
+    return render_template(
+        "main.html", rules=rules, units=BANDWIDTH_UNITS, standard_unit=STANDARD_UNIT
+    )
 
 
 
 
-@app.route('/new_rule/<interface>', methods=['POST'])
+@app.route("/new_rule/<interface>", methods=["POST"])
 def new_rule(interface):
 def new_rule(interface):
-    delay = request.form['Delay']
-    delay_variance = request.form['DelayVariance']
-    loss = request.form['Loss']
-    loss_correlation = request.form['LossCorrelation']
-    duplicate = request.form['Duplicate']
-    reorder = request.form['Reorder']
-    reorder_correlation = request.form['ReorderCorrelation']
-    corrupt = request.form['Corrupt']
-    limit = request.form['Limit']
-    rate = request.form['Rate']
-    rate_unit = request.form['rate_unit']
+    delay = request.form["Delay"]
+    delay_variance = request.form["DelayVariance"]
+    loss = request.form["Loss"]
+    loss_correlation = request.form["LossCorrelation"]
+    duplicate = request.form["Duplicate"]
+    reorder = request.form["Reorder"]
+    reorder_correlation = request.form["ReorderCorrelation"]
+    corrupt = request.form["Corrupt"]
+    limit = request.form["Limit"]
+    rate = request.form["Rate"]
+    rate_unit = request.form["rate_unit"]
 
 
     # remove old setup
     # remove old setup
-    command = 'tc qdisc del dev %s root netem' % interface
-    command = command.split(' ')
+    command = "tc qdisc del dev %s root netem" % interface
+    command = command.split(" ")
     proc = subprocess.Popen(command)
     proc = subprocess.Popen(command)
     proc.wait()
     proc.wait()
 
 
     # apply new setup
     # apply new setup
-    command = 'tc qdisc add dev %s root netem' % interface
-    if rate != '':
-        command += ' rate %s%s' % (rate, rate_unit)
-    if delay != '':
-        command += ' delay %sms' % delay
-        if delay_variance != '':
-            command += ' %sms' % delay_variance
-    if loss != '':
-        command += ' loss %s%%' % loss
-        if loss_correlation != '':
-            command += ' %s%%' % loss_correlation
-    if duplicate != '':
-        command += ' duplicate %s%%' % duplicate
-    if reorder != '':
-        command += ' reorder %s%%' % reorder
-        if reorder_correlation != '':
-            command += ' %s%%' % reorder_correlation
-    if corrupt != '':
-        command += ' corrupt %s%%' % corrupt
-    if limit != '':
-        command += ' limit %s' % limit
+    command = "tc qdisc add dev %s root netem" % interface
+    if rate != "":
+        command += " rate %s%s" % (rate, rate_unit)
+    if delay != "":
+        command += " delay %sms" % delay
+        if delay_variance != "":
+            command += " %sms" % delay_variance
+    if loss != "":
+        command += " loss %s%%" % loss
+        if loss_correlation != "":
+            command += " %s%%" % loss_correlation
+    if duplicate != "":
+        command += " duplicate %s%%" % duplicate
+    if reorder != "":
+        command += " reorder %s%%" % reorder
+        if reorder_correlation != "":
+            command += " %s%%" % reorder_correlation
+    if corrupt != "":
+        command += " corrupt %s%%" % corrupt
+    if limit != "":
+        command += " limit %s" % limit
     print(command)
     print(command)
-    command = command.split(' ')
+    command = command.split(" ")
     proc = subprocess.Popen(command)
     proc = subprocess.Popen(command)
     proc.wait()
     proc.wait()
-    return redirect(url_for('main'))
+    return redirect(url_for("main"))
 
 
 
 
-@app.route('/remove_rule/<interface>', methods=['POST'])
+@app.route("/remove_rule/<interface>", methods=["POST"])
 def remove_rule(interface):
 def remove_rule(interface):
     # remove old setup
     # remove old setup
-    command = 'tc qdisc del dev %s root netem' % interface
-    command = command.split(' ')
+    command = "tc qdisc del dev %s root netem" % interface
+    command = command.split(" ")
     proc = subprocess.Popen(command)
     proc = subprocess.Popen(command)
     proc.wait()
     proc.wait()
-    return redirect(url_for('main'))
+    return redirect(url_for("main"))
 
 
 
 
 def get_active_rules():
 def get_active_rules():
-    proc = subprocess.Popen(['tc', 'qdisc'], stdout=subprocess.PIPE)
+    proc = subprocess.Popen(["tc", "qdisc"], stdout=subprocess.PIPE)
     output = proc.communicate()[0].decode()
     output = proc.communicate()[0].decode()
-    lines = output.split('\n')[:-1]
+    lines = output.split("\n")[:-1]
     rules = []
     rules = []
     dev = set()
     dev = set()
     for line in lines:
     for line in lines:
         arguments = line.split()
         arguments = line.split()
         rule = parse_rule(arguments)
         rule = parse_rule(arguments)
-        if rule['name'] and rule['name'] not in dev:
+        if rule["name"] and rule["name"] not in dev:
             rules.append(rule)
             rules.append(rule)
-            dev.add(rule['name'])
+            dev.add(rule["name"])
     return rules
     return rules
 
 
 
 
 def parse_rule(split_rule):
 def parse_rule(split_rule):
-    rule = {'name':               None,
-            'rate':               None,
-            'delay':              None,
-            'delayVariance':      None,
-            'loss':               None,
-            'lossCorrelation':    None,
-            'duplicate':          None,
-            'reorder':            None,
-            'reorderCorrelation': None,
-            'corrupt':            None,
-            'limit':              None}
+    rule = {
+        "name": None,
+        "rate": None,
+        "delay": None,
+        "delayVariance": None,
+        "loss": None,
+        "lossCorrelation": None,
+        "duplicate": None,
+        "reorder": None,
+        "reorderCorrelation": None,
+        "corrupt": None,
+        "limit": None,
+    }
     i = 0
     i = 0
     for argument in split_rule:
     for argument in split_rule:
-        if argument == 'dev':
+        if argument == "dev":
             # Both regex pattern and dev name can be given
             # Both regex pattern and dev name can be given
             # An interface could match the pattern and/or
             # An interface could match the pattern and/or
             # be in the interface list
             # be in the interface list
             if pattern is None and dev_list is None:
             if pattern is None and dev_list is None:
-                rule['name'] = split_rule[i + 1]
+                rule["name"] = split_rule[i + 1]
             if pattern:
             if pattern:
                 if pattern.match(split_rule[i + 1]):
                 if pattern.match(split_rule[i + 1]):
-                    rule['name'] = split_rule[i + 1]
+                    rule["name"] = split_rule[i + 1]
             if dev_list:
             if dev_list:
                 if split_rule[i + 1] in dev_list:
                 if split_rule[i + 1] in dev_list:
-                    rule['name'] = split_rule[i + 1]
-        elif argument == 'rate':
-            rule['rate'] = split_rule[i + 1].split('Mbit')[0]
-        elif argument == 'delay':
-            rule['delay'] = split_rule[i + 1]
-            if len(split_rule) > (i + 2) and 'ms' in split_rule[i + 2]:
-                rule['delayVariance'] = split_rule[i + 2]
-        elif argument == 'loss':
-            rule['loss'] = split_rule[i + 1]
-            if len(split_rule) > (i + 2) and '%' in split_rule[i + 2]:
-                rule['lossCorrelation'] = split_rule[i + 2]
-        elif argument == 'duplicate':
-            rule['duplicate'] = split_rule[i + 1]
-        elif argument == 'reorder':
-            rule['reorder'] = split_rule[i + 1]
-            if len(split_rule) > (i + 2) and '%' in split_rule[i + 2]:
-                rule['reorderCorrelation'] = split_rule[i + 2]
-        elif argument == 'corrupt':
-            rule['corrupt'] = split_rule[i + 1]
-        elif argument == 'limit':
-            rule['limit'] = split_rule[i + 1]
+                    rule["name"] = split_rule[i + 1]
+        elif argument == "rate":
+            rule["rate"] = split_rule[i + 1].split("Mbit")[0]
+        elif argument == "delay":
+            rule["delay"] = split_rule[i + 1]
+            if len(split_rule) > (i + 2) and "ms" in split_rule[i + 2]:
+                rule["delayVariance"] = split_rule[i + 2]
+        elif argument == "loss":
+            rule["loss"] = split_rule[i + 1]
+            if len(split_rule) > (i + 2) and "%" in split_rule[i + 2]:
+                rule["lossCorrelation"] = split_rule[i + 2]
+        elif argument == "duplicate":
+            rule["duplicate"] = split_rule[i + 1]
+        elif argument == "reorder":
+            rule["reorder"] = split_rule[i + 1]
+            if len(split_rule) > (i + 2) and "%" in split_rule[i + 2]:
+                rule["reorderCorrelation"] = split_rule[i + 2]
+        elif argument == "corrupt":
+            rule["corrupt"] = split_rule[i + 1]
+        elif argument == "limit":
+            rule["limit"] = split_rule[i + 1]
         i += 1
         i += 1
     return rule
     return rule
 
 
 
 
 if __name__ == "__main__":
 if __name__ == "__main__":
     if os.geteuid() != 0:
     if os.geteuid() != 0:
-        print("You need to have root privileges to run this script.\n"
-              "Please try again, this time using 'sudo'. Exiting.")
+        print(
+            "You need to have root privileges to run this script.\n"
+            "Please try again, this time using 'sudo'. Exiting."
+        )
         exit(1)
         exit(1)
 
 
     # TC Variables
     # TC Variables
@@ -185,7 +200,7 @@ if __name__ == "__main__":
     pattern = os.environ.get("TCGUI_REGEX")
     pattern = os.environ.get("TCGUI_REGEX")
     if args.regex:
     if args.regex:
         pattern = re.compile(args.regex)
         pattern = re.compile(args.regex)
-    
+
     dev_list = os.environ.get("TCGUI_DEV")
     dev_list = os.environ.get("TCGUI_DEV")
     if args.dev:
     if args.dev:
         dev_list = args.dev
         dev_list = args.dev
@@ -193,14 +208,14 @@ if __name__ == "__main__":
     # Flask Variable
     # Flask Variable
     app_args = {}
     app_args = {}
 
 
-    app_args['host'] = os.environ.get("TCGUI_IP")
-    app_args['port'] = os.environ.get("TCGUI_PORT")
+    app_args["host"] = os.environ.get("TCGUI_IP")
+    app_args["port"] = os.environ.get("TCGUI_PORT")
 
 
     if args.ip:
     if args.ip:
-        app_args['host'] = args.ip
+        app_args["host"] = args.ip
     if args.port:
     if args.port:
-        app_args['port'] = args.port
+        app_args["port"] = args.port
     if not args.debug:
     if not args.debug:
-        app_args['debug'] = False
+        app_args["debug"] = False
     app.debug = True
     app.debug = True
     app.run(**app_args)
     app.run(**app_args)

+ 2 - 0
requirements.txt

@@ -0,0 +1,2 @@
+black
+Flask