Skip to content

Commit

Permalink
started implementation of traceroute
Browse files Browse the repository at this point in the history
  • Loading branch information
chuckablack committed Oct 19, 2020
1 parent 3f6ff23 commit 8df666a
Show file tree
Hide file tree
Showing 9 changed files with 223 additions and 2 deletions.
2 changes: 2 additions & 0 deletions quokka/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@
capture_manager = CaptureManager()
from quokka.controller.PortscanManager import PortscanManager
portscan_manager = PortscanManager()
from quokka.controller.TracerouteManager import TracerouteManager
traceroute_manager = TracerouteManager()


def shutdown():
Expand Down
56 changes: 56 additions & 0 deletions quokka/controller/TracerouteManager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import pika
import json
import yaml
from quokka.controller.utils import log_console


class TracerouteManager:

try:
with open("quokka/data/" + "traceroutemonitors.yaml", "r") as import_file:
traceroute_monitors = yaml.safe_load(import_file.read())
except FileNotFoundError as e:
log_console(f"Could not import traceroute monitors file: {repr(e)}")
traceroute_monitors["0.0.0.0/0"] = "localhost"

@staticmethod
def get_channel(monitor):

connection = pika.BlockingConnection(pika.ConnectionParameters(host=monitor))
channel = connection.channel()
channel.queue_declare(queue="traceroute_queue", durable=True)

return channel

@staticmethod
def find_monitor(hostname=None):

if not hostname:
return "localhost"

if hostname in TracerouteManager.traceroute_monitors:
return TracerouteManager.traceroute_monitors[hostname]

else:
# If we didn't find a specific monitor, default to localhost
return "localhost"

@staticmethod
def initiate_traceroute(hostname, token):

monitor = TracerouteManager.find_monitor(hostname)
channel = TracerouteManager.get_channel(monitor)

traceroute_info = {
"hostname": hostname,
"token": token,
}

traceroute_info_json = json.dumps(traceroute_info)
channel.basic_publish(
exchange="", routing_key="traceroute_queue", body=traceroute_info_json
)

log_console(
f"Traceroute Manager: starting traceroute: hostname : {hostname}"
)
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
cfs eth distribute
feature vpc
feature telemetry
vlan 1-9,100-105
vpc domain 10
telemetry
sensor-group 1
Expand Down
1 change: 1 addition & 0 deletions quokka/data/traceroutemonitors.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.0.0.0/0 : localhost
19 changes: 19 additions & 0 deletions quokka/models/apis.py
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,25 @@ def get_port_scan_extended(host_ip, host_name, token):
return "failed", "No scan results in time provided"


def get_traceroute(hostname, token):

max_wait_time = 300 # extended port scan allowed to take 5 minutes max
start_time = datetime.now()
while (datetime.now() - start_time).total_seconds() < max_wait_time:

search = {"host_ip": host_ip, "host_name": host_name, "token": token}
portscan_obj = db.session.query(Portscan).filter_by(**search).one_or_none()

if not portscan_obj:
time.sleep(2)
continue

portscan = get_model_as_dict(portscan_obj)
return "success", portscan["scan_output"]

return "failed", "No scan results in time provided"


def record_device_config(device_id, config):

device_config = dict()
Expand Down
34 changes: 32 additions & 2 deletions quokka/views/ui_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@
get_all_events,
get_service_summary_data,
get_host_summary_data,
# get_host_capture,
# get_protocol_capture,
get_capture,
get_port_scan_extended,
get_device_config_diff,
get_traceroute,
)
import quokka.models.reset
from quokka.controller.ThreadManager import ThreadManager
from quokka.controller.CaptureManager import CaptureManager
from quokka.controller.PortscanManager import PortscanManager
from quokka.controller.host.portscan import get_port_scan_tcp_connection
from quokka.controller.TracerouteManager import TracerouteManager


@app.route("/ui/devices", methods=["GET", "POST"])
Expand Down Expand Up @@ -306,3 +306,33 @@ def scan_extended():

else:
return "Invalid request method"


@app.route("/ui/traceroute", methods=["GET", "POST"])
def traceroute():

hostname = request.args.get("hostname")

if not hostname:
return "Must provide hostname", 400

if request.method == "GET":
token = request.args.get("token")
if not token:
return "Must provide token on GET request", 400

result, traceroute_image = get_traceroute(hostname, token)
return {
"result": result,
"traceroute_output": str(traceroute_image),
"hostname": hostname,
}

elif request.method == "POST":
token = str(datetime.now())[:-3]
TracerouteManager.initiate_traceroute(hostname, token)
return {"result": f"Traceroute initiated for hostname: {hostname}",
"token": token}

else:
return "Invalid request method"
52 changes: 52 additions & 0 deletions quokka/workers/TracerouteThread.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import base64
from datetime import datetime
from socket import gethostname
from threading import Thread

from scapy.layers.inet import traceroute
from util import send_traceroute


class TracerouteThread(Thread):

def __init__(self, quokka_ip, serial_no, traceroute_info):
super().__init__()
print(f"TracerouteThread: initializing thread object: traceroute_info={traceroute_info}")

if "hostname" not in traceroute_info:
print(f"TracerouteThread: missing information in traceroute_info: {traceroute_info}")
return

self.quokka_ip = quokka_ip
self.serial_no = serial_no
self.hostname = traceroute_info["hostname"]
self.token = traceroute_info["token"]

def process_traceroute(self, traceroute):

print(f"TracerouteThread: generating traceroute image: {traceroute}")
tmp_png = 'tmp-traceroute-graph.png'
traceroute.graph(format='png', target=tmp_png)
with open(tmp_png, 'rb') as png_file:
traceroute_graph_bytes = base64.b64encode(png_file.read())

print(f"TracerouteThread: sending traceroute: {traceroute_graph_bytes}")
status_code = send_traceroute(
gethostname(),
self.quokka_ip,
self.serial_no,
self.hostname,
self.token,
str(datetime.now())[:-1],
traceroute_graph_bytes,
)
print(f"TracerouteThread: traceroute sent, result={status_code}\n")

def run(self):

print(f"TracerouteThread: starting traceroute: hostname = {self.hostname}")

traceroute_output = traceroute(self.hostname, verbose=0)
self.process_traceroute(traceroute_output[0])

print(f"\n\n-----> TracerouteThread: competed traceroute")
39 changes: 39 additions & 0 deletions quokka/workers/traceroute_worker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# ---- Worker application to traceroute packets --------------------------------

import pika
import json
from TracerouteThread import TracerouteThread

quokka_ip = "localhost"
serial_no = "111.111.111"

connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

channel.queue_declare(queue='traceroute_queue', durable=True)
print('\n\n [*] Traceroute Worker: waiting for messages.')


def receive_traceroute_request(traceroute_channel, method, properties, body):

print(f"traceroute worker: received message")
traceroute_info = json.loads(body)
print(f"traceroute info: {traceroute_info}")

channel.basic_ack(delivery_tag=method.delivery_tag)

traceroute_thread = TracerouteThread(quokka_ip, serial_no, traceroute_info)
traceroute_thread.start()

print('\n\n [*] Traceroute Worker: waiting for messages.')


channel.basic_qos(prefetch_count=1)
channel.basic_consume(on_message_callback=receive_traceroute_request, queue='traceroute_queue')

try:
channel.start_consuming()

except KeyboardInterrupt as e:
print("\nTraceroute worker shutting down")
connection.close()
21 changes: 21 additions & 0 deletions quokka/workers/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,24 @@ def send_portscan(source, destination, serial_no, host_ip, host_name, token, tim
)

return rsp.status_code


def send_traceroute(source, destination, serial_no, target_name, token, timestamp, traceroute_graph_bytes):

portscan_payload = {
"source": source,
"serial": serial_no,
"target_name": target_name,
"token": token,
"timestamp": timestamp,
"traceroute": traceroute_graph_bytes,
}
rsp = requests.post(
"http://" + destination + ":5000/portscan/store", json=portscan_payload
)
if rsp.status_code != 200:
print(
f"{str(datetime.now())[:-3]}: Error calling /portscan/store response: {rsp.status_code}, {rsp.content}"
)

return rsp.status_code

0 comments on commit 8df666a

Please sign in to comment.