diff --git a/tracevis.py b/tracevis.py index 98ea76e..652d4f5 100755 --- a/tracevis.py +++ b/tracevis.py @@ -66,6 +66,9 @@ def get_args(): help="annotation for the second packets (dns and packet trace)") parser.add_argument('--rexmit', action='store_true', help="same as rexmit option (only one packet. all TTL steps, same stream)") + parser.add_argument('--paris', action='store_true', + help="same as 'new,rexmit' option (like Paris-Traceroute)") + # this argument ('-o', '--options') will be changed or removed before v1.0.0 parser.add_argument('-o', '--options', type=str, default="new", help="change the behavior of the trace route" + " - 'rexmit' : to be similar to doing retransmission with incremental TTL (only one packet, one destination)" @@ -128,14 +131,17 @@ def main(args): edge_lable = args["label"].lower() if args.get("rexmit"): trace_retransmission = True + if args.get("paris"): + trace_with_retransmission = True if args.get("options"): - trace_options= args["options"].replace(' ', '').split(',') + # this argument will be changed or removed before v1.0.0 + trace_options = args["options"].replace(' ', '').split(',') if "new" in trace_options and "rexmit" in trace_options: trace_with_retransmission = True elif "rexmit" in trace_options: trace_retransmission = True else: - pass # "new" is default + pass # "new" is default if args.get("dns") or args.get("dnstcp"): do_traceroute = True name_prefix += "dns" diff --git a/utils/convert_packetlist.py b/utils/convert_packetlist.py new file mode 100644 index 0000000..d270bc4 --- /dev/null +++ b/utils/convert_packetlist.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 + +from base64 import b64encode + + +# this function source: https://stackoverflow.com/a/64410921 +def packet2json(packet_obj): + packet_dict = {} + layer = '' + for line in packet_obj.show2(dump=True).split('\n'): + if '###' in line: + layer = line.strip('#[] ') + packet_dict[layer] = {} + elif '=' in line: + key, val = line.split('=', 1) + if layer == 'Raw' and key.strip() == 'load': + packet_dict[layer][key.strip()] = b64encode( + packet_obj['Raw'].load).decode() + else: + packet_dict[layer][key.strip()] = val.strip() + return packet_dict + + +def packetlist2json(answered, unanswered): + packetlist = {'sent': [], 'received': []} + if len(answered) == 0: + if len(unanswered) != 0: + packetlist["sent"] = packet2json(packet_obj=unanswered[0]) + else: + for sentp, receivedp in answered: + if len(packetlist["sent"]) == 0: + packetlist["sent"] = packet2json(packet_obj=sentp) + packetlist["received"].append( + packet2json(packet_obj=receivedp)) + return packetlist diff --git a/utils/trace.py b/utils/trace.py index b917fdb..4cd3b8c 100755 --- a/utils/trace.py +++ b/utils/trace.py @@ -7,6 +7,7 @@ import socket import sys import time +from copy import deepcopy from datetime import datetime from time import sleep @@ -26,38 +27,79 @@ OS_NAME = platform.system() -def parse_packet(request_and_answer, current_ttl, elapsed_ms, summary_postfix=""): - if request_and_answer is not None: - req_answer = request_and_answer[1] - packet_send_time = request_and_answer[0].sent_time - packet_receive_time = req_answer.time - packet_elapsed_ms = float( - format(abs((packet_receive_time - packet_send_time) * 1000), '.3f')) - if packet_elapsed_ms > 0: - elapsed_ms = packet_elapsed_ms - backttl = 0 - if req_answer[IP].ttl <= 20: - backttl = int((current_ttl - req_answer[IP].ttl) / 2) + 1 - elif req_answer[IP].ttl <= 64: - backttl = 64 - req_answer[IP].ttl + 1 - elif req_answer[IP].ttl <= 128: - backttl = 128 - req_answer[IP].ttl + 1 +def choose_desirable_packet(request_and_answers, do_tcphandshake): + # request_and_answers.summary() + summary_postfix = str(request_and_answers.summary) + print(" " + summary_postfix) + if do_tcphandshake and not request_and_answers[0][1].haslayer(ICMP): + desirable_packet = None + # [0][0] = sent packet 1 -- [0][1] == received packet 1 + # [1][0] = sent packet 1 -- [1][1] == received packet 2 + # [2][0] = sent packet 1 -- [2][1] == received packet 3 + if len(request_and_answers) > 1: + if request_and_answers[0][1][TCP].flags == "A" and request_and_answers[1][1].haslayer(ICMP): + # todo xhdix: flag the first hop as a middlebox + desirable_packet = request_and_answers[1] + # todo xhdix: flag as middlebox if [0][1][TCP].flags in ["R", "RA", "F", "FA"] and [1][1].haslayer(ICMP + elif request_and_answers[0][1][TCP].flags in ["R", "RA", "F", "FA"]: + desirable_packet = request_and_answers[0] + else: + desirable_packet = request_and_answers[1] + # we need hello from server, not ACK from middlebox + elif request_and_answers[0][1][TCP].flags != "A": + desirable_packet = request_and_answers[0] + # here we just want to have a correct path, so we ignore the lack of ACK before Server Hello in some weird networks + elif request_and_answers[0][1][TCP].flags == "A" and request_and_answers[0][1].haslayer(Raw): + desirable_packet = request_and_answers[0] else: - backttl = 255 - req_answer[IP].ttl + 1 - print(" <<< answer:" - + " ip.src: " + req_answer[IP].src - + " ip.ttl: " + str(req_answer[IP].ttl) - + " back-ttl: " + str(backttl)) - answer_summary = req_answer.summary() - print(" " + answer_summary) - print("· - · · · rtt: " + str(elapsed_ms) + "ms · · · - · ") - answer_summary += " . - - . - . " + summary_postfix - return req_answer[IP].src, elapsed_ms, len(req_answer), req_answer[IP].ttl, answer_summary + return None, "" + return desirable_packet, summary_postfix + else: + desirable_packet = request_and_answers[0] + return desirable_packet, "" + + +def guess_back_ttl(current_ttl, ttl): + backttl = 0 + if ttl <= 20: + backttl = int((current_ttl - ttl) / 2) + 1 + elif ttl <= 64: + backttl = 64 - ttl + 1 + elif ttl <= 128: + backttl = 128 - ttl + 1 else: - print(" *** no response *** ") - print("· - · · · rtt: " + str(elapsed_ms) + - "ms · · · · · · · · timeout ") - return "***", elapsed_ms, 0, 0, "*" + backttl = 255 - ttl + 1 + return backttl + + +def parse_packet(answered, unanswered, current_ttl, elapsed_ms, do_tcphandshake): + if answered is not None and len(answered) != 0: + request_and_answer, summary_postfix = choose_desirable_packet( + answered, do_tcphandshake) + if request_and_answer is not None and len(answered) != 0: + req_answer = request_and_answer[1] + packet_send_time = request_and_answer[0].sent_time + packet_receive_time = req_answer.time + packet_elapsed_ms = float( + format(abs((packet_receive_time - packet_send_time) * 1000), '.3f')) + if packet_elapsed_ms > 0: + elapsed_ms = packet_elapsed_ms + backttl = guess_back_ttl(current_ttl, req_answer[IP].ttl) + print(" <<< answer:" + + " ip.src: " + req_answer[IP].src + + " ip.ttl: " + str(req_answer[IP].ttl) + + " back-ttl: " + str(backttl)) + answer_summary = req_answer.summary() + print(" " + answer_summary) + print("· - · · · rtt: " + str(elapsed_ms) + "ms · · · - · ") + if len(summary_postfix) != 0: + answer_summary += " . - - . - . " + summary_postfix + return req_answer[IP].src, elapsed_ms, len(req_answer), req_answer[IP].ttl, answer_summary, answered, unanswered + # else for both: + print(" *** no response *** ") + print("· - · · · rtt: " + str(elapsed_ms) + + "ms · · · · · · · · timeout ") + return "***", elapsed_ms, 0, 0, "*", answered, unanswered # ephemeral_port_reserve() function is based on https://github.com/Yelp/ephemeral-port-reserve @@ -260,37 +302,10 @@ def send_packet(request_packet, request_ip, current_ttl, timeout, do_tcphandshak return request_and_answers, unanswered if do_tcphandshake: sleep(timeout) # double sleep ( ̄o ̄) . z Z. maybe we should wait more - if len(request_and_answers) == 0: - return parse_packet(None, current_ttl, elapsed_ms) - else: - if do_tcphandshake and not request_and_answers[0][1].haslayer(ICMP): - # request_and_answers.summary() - summary_postfix = str(request_and_answers.summary) - print(" " + summary_postfix) - if len(request_and_answers) > 1: - if request_and_answers[0][1][TCP].flags == "A" and request_and_answers[1][1].haslayer(ICMP): - # todo xhdix: flag the first hop as a middlebox - print( - "--.- .-. -- the first answer is from a middlebox (╯°□°)╯︵ ┻━┻") - return parse_packet(request_and_answers[1], current_ttl, elapsed_ms, summary_postfix) - # todo xhdix: flag as middlebox if [0][1][TCP].flags in ["R", "RA", "F", "FA"] and [1][1].haslayer(ICMP - elif request_and_answers[0][1][TCP].flags in ["R", "RA", "F", "FA"]: - return parse_packet(request_and_answers[0], current_ttl, elapsed_ms, summary_postfix) - else: - return parse_packet(request_and_answers[1], current_ttl, elapsed_ms, summary_postfix) - # we need hello from server, not ACK from middlebox - elif request_and_answers[0][1][TCP].flags != "A": - return parse_packet(request_and_answers[0], current_ttl, elapsed_ms, summary_postfix) - # here we just want to have a correct path, so we ignore the lack of ACK before Server Hello in some weird networks - elif request_and_answers[0][1][TCP].flags == "A" and request_and_answers[0][1].haslayer(Raw): - return parse_packet(request_and_answers[0], current_ttl, elapsed_ms, summary_postfix) - else: - return parse_packet(None, current_ttl, elapsed_ms, summary_postfix) - else: - return parse_packet(request_and_answers[0], current_ttl, elapsed_ms) + return parse_packet(request_and_answers, unanswered, current_ttl, elapsed_ms, do_tcphandshake) -def already_reached_destination(previous_node_id, current_node_ip): +def already_reached_destination_int(previous_node_id, current_node_ip): if previous_node_id == current_node_ip: return True else: @@ -312,7 +327,7 @@ def are_equal(original_list, result_list): return True -def initialize_first_nodes(request_ips): +def initialize_first_nodes_json(request_ips): nodes = [] for _ in request_ips: nodes.append(SOURCE_IP_ADDRESS) @@ -324,7 +339,7 @@ def initialize_first_nodes(request_ips): def initialize_json_first_nodes( request_ips, annotation_1, annotation_2, packet_1_proto, packet_2_proto, - packet_1_port, packet_2_port, packet_1_size, packet_2_size): + packet_1_port, packet_2_port, packet_1_size, packet_2_size, paris_id): # source_address = get_if_addr(conf.iface) #todo: xhdix source_address = SOURCE_IP_ADDRESS start_time = int(datetime.utcnow().timestamp()) @@ -333,7 +348,7 @@ def initialize_json_first_nodes( traceroute_data( dst_addr=request_ip, annotation=annotation_1, src_addr=source_address, proto=packet_1_proto, port=packet_1_port, - timestamp=start_time, size=packet_1_size + timestamp=start_time, paris_id=paris_id, size=packet_1_size ) ) if have_2_packet: @@ -341,7 +356,7 @@ def initialize_json_first_nodes( traceroute_data( dst_addr=request_ip, annotation=annotation_2, src_addr=source_address, proto=packet_2_proto, port=packet_2_port, - timestamp=start_time, size=packet_2_size + timestamp=start_time, paris_id=paris_id, size=packet_2_size ) ) @@ -384,19 +399,20 @@ def save_measurement_data( end_time = int(datetime.utcnow().timestamp()) measurement_data_json = [] ip_steps = 0 + measurement_data_save = deepcopy(measurement_data) while ip_steps < len(request_ips): - measurement_data[0][ip_steps].set_endtime(end_time) + measurement_data_save[0][ip_steps].set_endtime(end_time) if not continue_to_max_ttl: - measurement_data[0][ip_steps].clean_extra_result() - measurement_data_json.append(measurement_data[0][ip_steps]) + measurement_data_save[0][ip_steps].clean_extra_result() + measurement_data_json.append(measurement_data_save[0][ip_steps]) if have_2_packet: - measurement_data[1][ip_steps].set_endtime(end_time) + measurement_data_save[1][ip_steps].set_endtime(end_time) if not continue_to_max_ttl: - measurement_data[1][ip_steps].clean_extra_result() - measurement_data_json.append(measurement_data[1][ip_steps]) + measurement_data_save[1][ip_steps].clean_extra_result() + measurement_data_json.append(measurement_data_save[1][ip_steps]) ip_steps += 1 data_path = output_dir + measurement_name + ".json" - with open(data_path, "a") as jsonfile: + with open(data_path, "w") as jsonfile: jsonfile.write(json.dumps(measurement_data_json, default=lambda o: o.__dict__, indent=4)) print("saved: " + data_path) @@ -503,11 +519,16 @@ def trace_route( repeat_all_steps = 0 p1_proto, p2_proto, p1_port, p2_port, p1_size, p2_size = get_packets_info( request_packets) + paris_id = 0 + if trace_with_retransmission: + paris_id = repeat_requests + elif trace_retransmission: + paris_id = -1 initialize_json_first_nodes( request_ips=request_ips, annotation_1=annotation_1, annotation_2=annotation_2, packet_1_proto=p1_proto, packet_2_proto=p2_proto, packet_1_port=p1_port, packet_2_port=p2_port, - packet_1_size=p1_size, packet_2_size=p2_size + packet_1_size=p1_size, packet_2_size=p2_size, paris_id=paris_id ) print("- · - · - - · - · - - · - · - - · - · -") while repeat_all_steps < repeat_requests: @@ -517,7 +538,7 @@ def trace_route( request_packets_for_rexmit = generate_packets_for_each_ip( request_packets, request_ips, do_tcphandshake) trace_retransmission = True - previous_node_ids = initialize_first_nodes(request_ips) + previous_node_ids = initialize_first_nodes_json(request_ips) for current_ttl in range(1, max_ttl + 1): if not continue_to_max_ttl and are_equal(request_ips, previous_node_ids): ip_steps = 0 @@ -525,7 +546,7 @@ def trace_route( while ip_steps < len(request_ips): # to avoid confusing the order of results when we have already reached our destination measurement_data[access_block_steps][ip_steps].add_hop( - current_ttl, "", 0, 0, 0, "" + current_ttl, "", 0, 0, 0, "", None, None ) ip_steps += 1 if have_2_packet and ip_steps == len(request_ips) and access_block_steps == 0: @@ -540,7 +561,7 @@ def trace_route( print(" · · · - - - · · · · · · - - - · · · · · · - - - · · · ") while ip_steps < len(request_ips): sleep_time = SLEEP_TIME - not_yet_destination = not (already_reached_destination( + not_yet_destination = not (already_reached_destination_int( previous_node_ids[access_block_steps][ip_steps], request_ips[ip_steps])) current_packet = None @@ -550,26 +571,26 @@ def trace_route( current_packet = request_packets[access_block_steps] if not continue_to_max_ttl: if not_yet_destination: - answer_ip, elapsed_ms, packet_size, req_answer_ttl, answer_summary = send_packet( + answer_ip, elapsed_ms, packet_size, req_answer_ttl, answer_summary, answered, unanswered = send_packet( current_packet, request_ips[ip_steps], current_ttl, timeout, do_tcphandshake[access_block_steps], trace_retransmission, False) measurement_data[access_block_steps][ip_steps].add_hop( - current_ttl, answer_ip, elapsed_ms, packet_size, req_answer_ttl, answer_summary + current_ttl, answer_ip, elapsed_ms, packet_size, req_answer_ttl, answer_summary, answered, unanswered ) else: sleep_time = 0 # to avoid confusing the order of results when we have already reached our destination measurement_data[access_block_steps][ip_steps].add_hop( - current_ttl, "", 0, 0, 0, "" + current_ttl, "", 0, 0, 0, "", None, None ) else: - answer_ip, elapsed_ms, packet_size, req_answer_ttl, answer_summary = send_packet( + answer_ip, elapsed_ms, packet_size, req_answer_ttl, answer_summary, answered, unanswered = send_packet( current_packet, request_ips[ip_steps], current_ttl, timeout, do_tcphandshake[access_block_steps], trace_retransmission, False) measurement_data[access_block_steps][ip_steps].add_hop( - current_ttl, answer_ip, elapsed_ms, packet_size, req_answer_ttl, answer_summary + current_ttl, answer_ip, elapsed_ms, packet_size, req_answer_ttl, answer_summary, answered, unanswered ) if not_yet_destination: if answer_ip == "***": diff --git a/utils/traceroute_struct.py b/utils/traceroute_struct.py index ff56371..cfe7566 100755 --- a/utils/traceroute_struct.py +++ b/utils/traceroute_struct.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 import json +import utils.convert_packetlist + class traceroute_data: def __init__( @@ -29,7 +31,7 @@ def __init__( self.timestamp = timestamp self.ttr = ttr - def add_hop(self, hop, from_ip, rtt, size, ttl, answer_summary): + def add_hop(self, hop, from_ip, rtt, size, ttl, answer_summary, answered, unanswered): if len(self.result) < hop: (self.result).append({"hop": hop, "result": []}) if rtt == 0: @@ -37,16 +39,22 @@ def add_hop(self, hop, from_ip, rtt, size, ttl, answer_summary): "x": "-", }) elif from_ip == "***": + packetlist = utils.convert_packetlist.packetlist2json( + answered, unanswered) self.result[hop - 1]["result"].append({ "x": "*", + "packets": packetlist, }) else: + packetlist = utils.convert_packetlist.packetlist2json( + answered, unanswered) self.result[hop - 1]["result"].append({ "from": from_ip, "rtt": rtt, "size": size, "ttl": ttl, - "summary": answer_summary + "summary": answer_summary, + "packets": packetlist, }) def set_endtime(self, endtime): diff --git a/utils/vis.py b/utils/vis.py index 49adc7d..6ba8fcc 100755 --- a/utils/vis.py +++ b/utils/vis.py @@ -11,11 +11,18 @@ WINDOWS_COLOR = "blue" LINUX_COLOR = "purple" MIDDLEBOX_COLOR = "red" +PEP_COLOR = "green" +NAT_COLOR = "dodgerblue" NO_RESPONSE_COLOR = "gray" -DEVICE_OS_NAME = { - ROUTER_COLOR: "Router", WINDOWS_COLOR: "Windows", LINUX_COLOR: "Linux", - MIDDLEBOX_COLOR: "Middlebox", NO_RESPONSE_COLOR: "unknown" -} + +ROUTER_NAME = "Router" +WINDOWS_NAME = "Windows" +LINUX_NAME = "Linux" +MIDDLEBOX_NAME = "Middlebox" +PEP_NAME = "PEP" +NAT_NAME = "NAT" +NO_RESPONSE_NAME = "unknown" + REQUEST_COLORS = [ "DarkTurquoise", "HotPink", "LimeGreen", "Red", "DodgerBlue", "Orange", "MediumSlateBlue", "DarkGoldenrod", "Green", "Brown", "YellowGreen", "Magenta" @@ -28,22 +35,96 @@ multi_directed_graph = nx.MultiDiGraph() +def get_packet_type(packet_obj): + if len(packet_obj.keys()) > 1: + return list(packet_obj.keys())[1] + + +def detect_nat_pep_middlebox(sent, received): + is_nat = False + is_middlebox = False + is_pep = False + packet_type = "" + tcpflag = "" + if not 'ICMP' in received[0].keys(): + # sent packet 1 = {} + # received packets = [ + # {received packet 1}, + # {received packet 2}, + # {received packet 3} + # ] + if 'TCP' in received[0].keys(): + if len(received) > 1: + if received[0]['TCP']['flags'] == "A" and 'ICMP' in received[1].keys(): + is_pep = True + packet_type = get_packet_type(received[1]) + if received[1]['IP in ICMP']['id'] != sent['IP']['id']: + is_nat = True + elif received[0]['TCP']['flags'] in ["R", "RA", "F", "FA"] and 'ICMP' in received[1].keys(): + is_pep = True + is_middlebox = True + packet_type = get_packet_type(received[1]) + if received[1]['IP in ICMP']['id'] != sent['IP']['id']: + is_nat = True + elif received[0]['TCP']['flags'] in ["R", "RA", "F", "FA"]: + packet_type = get_packet_type(received[0]) + tcpflag = received[0]['TCP']['flags'] + if received[0]['IP']['id'] == sent['IP']['id']: + is_middlebox = True + else: + packet_type = get_packet_type(received[1]) + if packet_type == 'TCP': + tcpflag = received[1]['TCP']['flags'] + if received[1]['IP']['id'] == sent['IP']['id']: + is_middlebox = True + # we need hello from server, not ACK from middlebox + elif received[0]['TCP']['flags'] != "A": + packet_type = get_packet_type(received[0]) + tcpflag = received[0]['TCP']['flags'] + if received[0]['IP']['id'] == sent['IP']['id']: + is_middlebox = True + # here we just want to have a correct path, so we ignore the lack of ACK before Server Hello in some weird networks + elif received[0]['TCP']['flags'] == "A" and received[0].haslayer('Raw'): + packet_type = get_packet_type(received[0]) + tcpflag = received[0]['TCP']['flags'] + if received[0]['IP']['id'] == sent['IP']['id']: + is_middlebox = True + else: + is_pep = True + else: + packet_type = get_packet_type(received[0]) + if received[0]['IP']['id'] == sent['IP']['id']: + is_middlebox = True + else: + packet_type = 'ICMP' + if received[0]['IP in ICMP']['id'] != sent['IP']['id']: + is_nat = True + return is_nat, is_middlebox, is_pep, packet_type, tcpflag + + def parse_ttl(response_ttl, current_ttl): device_color = "" backttl = 0 + is_middlebox = False + device_os_name = "" if response_ttl <= 20: backttl = int((current_ttl - response_ttl) / 2) + 1 device_color = MIDDLEBOX_COLOR + is_middlebox = True + device_os_name = MIDDLEBOX_NAME elif response_ttl <= 64: backttl = 64 - response_ttl + 1 device_color = LINUX_COLOR + device_os_name = LINUX_NAME elif response_ttl <= 128: backttl = 128 - response_ttl + 1 device_color = WINDOWS_COLOR + device_os_name = WINDOWS_NAME else: backttl = 255 - response_ttl + 1 device_color = ROUTER_COLOR - return backttl, device_color + device_os_name = ROUTER_NAME + return backttl, device_color, device_os_name, is_middlebox def visualize(previous_node_id, current_node_id, @@ -58,9 +139,20 @@ def visualize(previous_node_id, current_node_id, color=requset_color, title=current_edge_title) +def tooltips_append_lines(is_nat, is_middlebox, is_pep, packet_type, tcpflag): + append_line = '' + if packet_type == "TCP": + append_line = "
response TCP flag: " + tcpflag + return ("
NAT: " + str(is_nat) + + "
Middlebox: " + str(is_middlebox) + + "
PEP: " + str(is_pep) + + "
response packet: " + packet_type + + append_line) + + def styled_tooltips( - current_request_colors, current_ttl_str, backttl, request_ip, elapsed_ms, - packet_size, repeat_all_steps, device_os_name, annotation): + current_request_color, current_ttl_str, backttl, request_ip, elapsed_ms, + packet_size, repeat_step, device_os_name, append_lines, annotation): time_size = "*" elapsed_ms_str = "*" packet_size_str = "*" @@ -69,30 +161,37 @@ def styled_tooltips( if elapsed_ms != "*": elapsed_ms_str = str(format(elapsed_ms, '.3f')) + "ms" time_size = str(format(elapsed_ms/packet_size, '.3f')) + "ms/B" - return ("
TTL: " + current_ttl_str
-            + "
Back-TTL: " + backttl - + "
Request to: " + request_ip - + "
annotation: " + annotation - + "
Time: " + elapsed_ms_str - + "
Size: " + packet_size_str - + "
Time/Size: " + time_size - + "
OS: " + device_os_name - + "
Repeat step: " + str(repeat_all_steps) + "
") - - -def already_reached_destination(previous_node_id, current_node_ip): - if previous_node_id in { - str(current_node_ip), - ("middlebox" + str(current_node_ip) + "x")}: + tooltips_str = "
TTL: " + current_ttl_str
+    tooltips_str += "
Back-TTL: " + backttl + tooltips_str += "
Request to: " + request_ip + tooltips_str += "
annotation: " + annotation + tooltips_str += "
Time: " + elapsed_ms_str + tooltips_str += "
Size: " + packet_size_str + tooltips_str += "
Time/Size: " + time_size + tooltips_str += "
OS: " + device_os_name + tooltips_str += append_lines + tooltips_str += "
Repeat step: " + repeat_step + "
" + return tooltips_str + + +def already_reached_destination_str(previous_node_id, dst_addr_id): + if dst_addr_id in previous_node_id: return True else: return False -def initialize_first_nodes(src_addr): +def initialize_detected(length_all): + nodes = [] + for _ in range(length_all): + nodes.append({"is_nat": False, "is_middlebox": False, "is_pep": False}) + return nodes + + +def initialize_first_nodes_nx(src_addr, length_all): nodes = [] - for _ in range(10): + for _ in range(length_all): nodes.append(str(src_addr)) return nodes @@ -118,29 +217,32 @@ def vis(measurement_path, attach_jscss, edge_lable: str = "none"): all_measurements = json.load(json_file) measurement_steps = 0 src_addr = all_measurements[0]["src_addr"] - src_addr_id = str(int(ipaddress.IPv4Address(src_addr))) + src_addr_id = 'x' + str(int(ipaddress.IPv4Address(src_addr))) + 'x' multi_directed_graph.add_node( src_addr_id, label=src_addr, color="Chocolate", title="source address", shape="diamond") for measurement in all_measurements: - previous_node_ids = initialize_first_nodes(src_addr_id) dst_addr = measurement["dst_addr"] - dst_addr_id = str(int(ipaddress.IPv4Address(dst_addr))) + dst_addr_id = 'x' + str(int(ipaddress.IPv4Address(dst_addr))) + 'x' annotation = "-" if "annotation" in measurement.keys(): annotation = measurement["annotation"] all_results = measurement["result"] + results_repeat_length = len(all_results[0]["result"]) + previous_node_ids = initialize_first_nodes_nx( + src_addr_id, results_repeat_length) + already_detected = initialize_detected(results_repeat_length) for try_step in all_results: # will be up to 255 current_ttl = try_step["hop"] current_ttl_str = str(current_ttl) results = try_step["result"] repeat_steps = 0 skip_next = False - for result in results: # will be up to 3 + for result in results: if skip_next: skip_next = False continue - not_yet_destination = not (already_reached_destination( + not_yet_destination = not (already_reached_destination_str( previous_node_ids[repeat_steps], dst_addr_id)) if not_yet_destination: if "late" in result.keys(): @@ -154,6 +256,9 @@ def vis(measurement_path, attach_jscss, edge_lable: str = "none"): packet_size = "*" backttl = "*" device_color = NO_RESPONSE_COLOR + device_name = NO_RESPONSE_NAME + append_lines = "" + is_middlebox = False if 'x' in result.keys(): current_node_id = ( "unknown" + previous_node_ids[repeat_steps] + "x") @@ -161,7 +266,7 @@ def vis(measurement_path, attach_jscss, edge_lable: str = "none"): current_edge_label = "*" else: answer_ip = result["from"] - backttl, device_color = parse_ttl( + backttl, device_color, device_name, is_middlebox_ttl = parse_ttl( result["ttl"], current_ttl) if "rtt" in result.keys(): elapsed_ms = result["rtt"] @@ -170,26 +275,59 @@ def vis(measurement_path, attach_jscss, edge_lable: str = "none"): current_edge_label = format(elapsed_ms, '.3f') elif edge_lable == "backttl": current_edge_label = str(backttl) - current_node_id = str( - int(ipaddress.IPv4Address(answer_ip))) - if device_color == MIDDLEBOX_COLOR: - current_node_id = ( - "middlebox" + str(current_node_id) + "x") + current_node_id = 'x' + str( + int(ipaddress.IPv4Address(answer_ip))) + 'x' + if "packets" in result.keys(): + if "received" in result['packets'].keys(): + if len(result['packets']['received']) != 0: + is_nat, is_middlebox, is_pep, packet_type, tcpflag = detect_nat_pep_middlebox( + result['packets']['sent'], result['packets']['received'] + ) + if (is_middlebox_ttl or is_middlebox + ) and not already_detected[repeat_steps]["is_middlebox"]: + pass # we decide about it later + elif is_pep and not already_detected[repeat_steps]["is_pep"]: + device_color = PEP_COLOR + device_name = PEP_NAME + current_node_shape = "star" + already_detected[repeat_steps]["is_pep"] = True + current_node_id = "pep" + current_node_id + "x" + elif is_nat and not already_detected[repeat_steps]["is_nat"]: + device_color = NAT_COLOR + device_name = NAT_NAME + already_detected[repeat_steps]["is_nat"] = True + current_node_id = "nat" + current_node_id + "x" + append_lines = tooltips_append_lines( + is_nat, is_middlebox, is_pep, packet_type, tcpflag) + if (is_middlebox_ttl or is_middlebox): + already_detected[repeat_steps]["is_middlebox"] = True + if is_pep: + already_detected[repeat_steps]["is_pep"] = True + if is_nat: + already_detected[repeat_steps]["is_nat"] = True + if is_middlebox_ttl or is_middlebox: + current_node_id = "middlebox" + current_node_id + "x" current_node_shape = "star" + device_color = MIDDLEBOX_COLOR + device_name = MIDDLEBOX_NAME + already_detected[repeat_steps]["is_middlebox"] = True elif current_node_id == dst_addr_id: current_node_shape = "square" current_node_label = answer_ip - current_edge_title = str(backttl) packet_size = result["size"] + repeat_step_str = str(repeat_steps + 1) current_edge_title = styled_tooltips( - REQUEST_COLORS[measurement_steps], current_ttl_str, - str(backttl), dst_addr, elapsed_ms, packet_size, - (repeat_steps + 1), DEVICE_OS_NAME[device_color], - annotation + current_request_color=( + REQUEST_COLORS[measurement_steps]), + current_ttl_str=current_ttl_str, backttl=str(backttl), + request_ip=dst_addr, elapsed_ms=elapsed_ms, + packet_size=packet_size, repeat_step=repeat_step_str, + device_os_name=device_name, append_lines=append_lines, + annotation=annotation ) visualize( previous_node_ids[repeat_steps], current_node_id, - current_node_label, DEVICE_OS_NAME[device_color], device_color, + current_node_label, device_name, device_color, current_edge_title, REQUEST_COLORS[measurement_steps], current_edge_label, current_node_shape )