Skip to content

Commit

Permalink
Updated web interface to add new tests and run everything as a non ro…
Browse files Browse the repository at this point in the history
…ot user
  • Loading branch information
IITG committed Sep 9, 2024
1 parent 776cbd7 commit a5d2be5
Show file tree
Hide file tree
Showing 5 changed files with 295 additions and 113 deletions.
19 changes: 14 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,23 @@ FROM python:3.12-alpine

RUN pip install --no-cache-dir flask

RUN apk update && apk add traceroute \
RUN apk update && apk add mtr \
traceroute \
bind-tools \
iperf \
iperf3

COPY static /static/
COPY templates /templates/
COPY app.py .
# Security updates
RUN apk upgrade libexpat

COPY static /app/static/
COPY templates /app/templates/
COPY app.py /app/

EXPOSE 5000

CMD ["python", "app.py"]
RUN adduser -D iperf-web
RUN chown -R iperf-web:iperf-web /app

USER iperf-web
CMD ["python", "/app/app.py"]
159 changes: 120 additions & 39 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,61 @@
from flask import Flask, render_template, request, Response
import subprocess
import select
import shlex
import re
import os

app = Flask(__name__)

# function to convert a string to boolean
def str_to_boolean(s):
return str(s).lower() == "true"

# Function to run a command and stream the output
def run_command(command):
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
try:
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
except FileNotFoundError as e:
yield f"Error: {str(e)}<br>"
return
except OSError as e:
yield f"Error: {str(e)}<br>"
return

yield 'Executing command: ' + ' '.join(command) + '<br><br>'

# Monitor both stdout and stderr streams
while True:
output = process.stdout.readline().decode('utf-8')
if output:
yield output + '<br>'
elif process.poll() is not None:
# Use select to monitor stdout and stderr
reads = [process.stdout, process.stderr]
readable, _, _ = select.select(reads, [], [])

for stream in readable:
if stream == process.stdout:
stdout_line = process.stdout.readline().decode('utf-8')
if stdout_line:
yield stdout_line + '<br>'

if stream == process.stderr:
stderr_line = process.stderr.readline().decode('utf-8')
if stderr_line:
yield f"<span style='color:red;'>{stderr_line}</span><br>"

# Break if the process has terminated and there's no more output
if process.poll() is not None:
break

# Ensure that any remaining output is read after the process terminates
remaining_stdout = process.stdout.read().decode('utf-8')
if remaining_stdout:
yield remaining_stdout + '<br>'

remaining_stderr = process.stderr.read().decode('utf-8')
if remaining_stderr:
yield f"<span style='color:red;'>{remaining_stderr}</span><br>"

yield '<br>Execution finished!<br>'

# Helper function to sanitize parameters
def sanitize_parameters(parameters):
sanitized = shlex.split(parameters) # Split safely without executing
Expand All @@ -27,6 +68,16 @@ def validate_target(target):
return True
return False

def is_valid_port(port):
try:
port = int(port)
return 1 <= port <= 65535
except (ValueError, TypeError):
return False

app_port = os.getenv('IPERF_WEB_PORT', '5000')
debug_mode = str_to_boolean(os.getenv('IPERF_WEB_DEBUG_MODE', False))

# Route to display the page
@app.route('/')
def index():
Expand All @@ -35,49 +86,79 @@ def index():
# Route to handle form submission and execute selected test
@app.route('/run_test', methods=['POST'])
def run_test():
print("Form submitted") # Debugging statement
test_type = request.form.get('test_type')

output = ""
sanitized_params = ""
command = []

if test_type == 'ping':
target = request.form.get('ping_target')
# Only run tests for valid test types
TEST_TYPES = {'dig', 'iperf', 'mtr', 'nc', 'nslookup', 'ping', 'traceroute'}
if test_type in TEST_TYPES:
target = request.form.get(test_type + '_target')
# Validate the target address
if not validate_target(target):
return Response("Invalid target address.", mimetype='text/plain')
parameters = request.form.get('ping_parameters', '')
sanitized_params = sanitize_parameters(parameters)
command = ['ping'] + sanitized_params + [target]
elif test_type == 'traceroute':
target = request.form.get('traceroute_target')
# Validate the target address
if not validate_target(target):
return Response("Invalid target address.", mimetype='text/plain')
parameters = request.form.get('traceroute_parameters', '')
sanitized_params = sanitize_parameters(parameters)
command = ['traceroute'] + sanitized_params + [target]
elif test_type == 'iperf':
server = request.form.get('iperf_server')
# Validate the target address
if not validate_target(server):
return Response("Invalid server address.", mimetype='text/plain')
iperf_version = request.form.get('iperf_version')
port = request.form.get('port')
conn_type = request.form.get('conn_type')
parameters = request.form.get('iperf_parameters', '-f m -i 5 -t 10')
return Response("'" + str(target) + "' is not a valid target address.", mimetype='text/plain')
parameters = request.form.get(test_type + '_parameters', '')

# MTR specific parameters
if test_type == 'mtr':
mtr_reportcycles = int(request.form.get('mtr_reportcycles', '200'))
parameters += ' --report --report-wide --report-cycles ' + str(mtr_reportcycles)

# netcat specific parameters
if test_type == 'nc':
port = request.form.get('nc_port')

if not is_valid_port(port):
return Response("'" + str(port) + "' is not a valid TCP port.", mimetype='text/plain')

command = [test_type, '-vz', target, port]

# netcat specific parameters
if test_type == 'nslookup':
server = request.form.get('nslookup_dns_server')
if server:
parameters = server
sanitized_params = sanitize_parameters(parameters)
command = [test_type, target] + sanitized_params

# ping specific parameters
if test_type == 'ping':
count = int(request.form.get('ping_count', '4'))
parameters += ' -c' + str(count)

# iperf specific parameters
if test_type == 'iperf':
iperf_version = request.form.get('iperf_version')
port = request.form.get('iperf_port')

if not is_valid_port(port):
port = 5001

conn_type = request.form.get('iperf_conn_type')

# Choose the correct iperf command based on version
base_command = 'iperf3' if iperf_version == '3' else 'iperf'

timeout = int(request.form.get('iperf_timeout', '10'))

sanitized_params = sanitize_parameters(parameters)

command = [base_command, '-c', target, '-p', port, '--forceflush', '-t', str(timeout)] + sanitized_params

# Add -u flag for UDP connection type
if conn_type == 'UDP':
command.append('-u')

sanitized_params = sanitize_parameters(parameters)
if not sanitized_params:
sanitized_params = sanitize_parameters(parameters)

# Choose the correct iperf command based on version
base_command = 'iperf3' if iperf_version == '3' else 'iperf'
command = [base_command, '-c', server, '-p', port, '--forceflush'] + sanitized_params
if not command:
command = [test_type] + sanitized_params + [target]

# Add -u flag for UDP connection type
if conn_type == 'UDP':
command.append('-u')
print(f"Executing command: {' '.join(command)}") # Debugging statement

print(f"Executing command: {' '.join(command)}") # Debugging statement
return Response(run_command(command), mimetype='text/html')

if __name__ == '__main__':
app.run(debug=True,host='0.0.0.0')
app.run(debug=debug_mode,host='0.0.0.0',port=app_port)
111 changes: 69 additions & 42 deletions static/app.js
Original file line number Diff line number Diff line change
@@ -1,54 +1,81 @@
function showTestFields() {
// Map of test types to the associated fields and required attributes
const fieldMap = {
'dig': {
fieldsToShow: ['dig_fields'],
requiredFields: ['dig_target'],
defaults: { 'dig_parameters': '+short' }
},
'iperf': {
fieldsToShow: ['iperf_fields'],
requiredFields: ['iperf_target', 'iperf_port', 'iperf_timeout'],
defaults: { 'iperf_parameters': '--format m', 'iperf_timeout': '10' }
},
'mtr': {
fieldsToShow: ['mtr_fields'],
requiredFields: ['mtr_target', 'mtr_reportcycles'],
defaults: { 'mtr_reportcycles': '200', 'mtr_parameters': '-n -T' }
},
'nc': {
fieldsToShow: ['nc_fields'],
requiredFields: ['nc_target', 'nc_port'],
defaults: {}
},
'nslookup': {
fieldsToShow: ['nslookup_fields'],
requiredFields: ['nslookup_target'],
defaults: {}
},
'ping': {
fieldsToShow: ['ping_fields'],
requiredFields: ['ping_target', 'ping_count'],
defaults: { 'ping_count': '4' }
},
'traceroute': {
fieldsToShow: ['traceroute_fields'],
requiredFields: ['traceroute_target'],
defaults: { 'traceroute_parameters': '--icmp'}
}
};

// Get the selected test type
var testType = document.getElementById("test_type").value;

if (testType === 'ping') {
document.getElementById("ping_fields").style.display = 'block';
document.getElementById("traceroute_fields").style.display = 'none';
document.getElementById("iperf_fields").style.display = 'none';

// Reset parameters and target for Ping
document.getElementById("ping_target").value = '';
document.getElementById("ping_parameters").value = '-c4';

// Enable required for Ping, disable for others
document.getElementById("ping_target").setAttribute('required', 'required');
document.getElementById("traceroute_target").removeAttribute('required');
document.getElementById("iperf_server").removeAttribute('required');
document.getElementById("port").removeAttribute('required');
} else if (testType === 'traceroute') {
document.getElementById("ping_fields").style.display = 'none';
document.getElementById("traceroute_fields").style.display = 'block';
document.getElementById("iperf_fields").style.display = 'none';

// Reset parameters and target for Traceroute
document.getElementById("traceroute_target").value = '';
document.getElementById("traceroute_parameters").value = '';

// Enable required for Traceroute, disable for others
document.getElementById("traceroute_target").setAttribute('required', 'required');
document.getElementById("ping_target").removeAttribute('required');
document.getElementById("iperf_server").removeAttribute('required');
document.getElementById("port").removeAttribute('required');
} else if (testType === 'iperf') {
document.getElementById("ping_fields").style.display = 'none';
document.getElementById("traceroute_fields").style.display = 'none';
document.getElementById("iperf_fields").style.display = 'block';

// Set default parameters for IPerf
document.getElementById("iperf_parameters").value = '-f m -i 5 -t 30';

// Enable required for IPerf, disable for others
document.getElementById("iperf_server").setAttribute('required', 'required');
document.getElementById("port").setAttribute('required', 'required');
document.getElementById("ping_target").removeAttribute('required');
document.getElementById("traceroute_target").removeAttribute('required');
// Hide all fields initially
const allFields = ['dig_fields', 'iperf_fields', 'mtr_fields', 'nc_fields', 'nslookup_fields', 'ping_fields', 'traceroute_fields'];
allFields.forEach(field => {
document.getElementById(field).style.display = 'none';
});

// Remove 'required' from all input fields
const allInputFields = ['dig_target', 'iperf_target', 'iperf_port', 'iperf_timeout', 'mtr_target', 'mtr_reportcycles', 'nc_target', 'nc_port', 'nslookup_target', 'ping_target', 'ping_count', 'traceroute_target'];
allInputFields.forEach(field => {
document.getElementById(field).removeAttribute('required');
});

// Set defaults and show relevant fields based on the selected test type
if (fieldMap[testType]) {
// Show the relevant fields
fieldMap[testType].fieldsToShow.forEach(field => {
document.getElementById(field).style.display = 'block';
});

// Set required attributes for relevant fields
fieldMap[testType].requiredFields.forEach(field => {
document.getElementById(field).setAttribute('required', 'required');
});

// Apply default values if any
for (const [field, value] of Object.entries(fieldMap[testType].defaults)) {
document.getElementById(field).value = value;
}
}
}

// Initialize fields visibility on page load
window.onload = showTestFields;


// Scroll the iframe automatically
function scrollIframe() {
var iframe = document.getElementById("output_frame");
iframe.contentWindow.scrollTo(0, iframe.contentWindow.document.body.scrollHeight);
Expand Down
2 changes: 1 addition & 1 deletion templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@

<div class="container">{% block content %} {% endblock %}</div>
<script
src="{{ url_for('static', filename='jquery-3.7.1.slim.min.js') }}"
src="{{ url_for('static', filename='jquery-3.2.1.slim.min.js') }}"
integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
crossorigin="anonymous"
></script>
Expand Down
Loading

0 comments on commit a5d2be5

Please sign in to comment.