def setUp(self): self.ip_ver = 4 # UDP self.l4_proto = 17 self.ip_daddr = "1.1.1.1" self.ip_saddr = traceflow.socket_handler.get_egress_ip(self.ip_daddr) self.udp_src_port = 35000 self.udp_dst_port = 53 self.ttl = 3 # packet_encode(ip_ver, ip_daddr, udp_src_port, udp_dst_port, ttl, l4_proto, ip_id, **kwargs) self.test_encode_class_instance = traceflow.packet_encode( self.ip_ver, self.ip_daddr, self.udp_src_port, self.udp_dst_port, self.ttl, self.l4_proto, None, )
def main(): # ha ha ha args = get_help() # CLI arguments set here. daddr = socket.gethostbyname(args.destination) print(f"Resolved {args.destination} to {daddr}") TOT_RUNS = args.paths DST_PORT = args.dstport SRC_PORT = args.srcport MAX_TTL = args.ttl if args.debug: logger = logging.getLogger() logger.setLevel(logging.DEBUG) # Setup the background thread listener here. Note that we need to pass daddr so we can snag the dst port unreachable # ICMP message. listener = traceflow.socket_listener(daddr) run_ids = dict() # Keep track of which path we're looking to enumerate for path in range(1, TOT_RUNS + 1): port = SRC_PORT + path run_ids[path] = port print(f"Looking at Path ID {path} (src port:{port} , dst port:{DST_PORT})") for ttl in list(range(1, MAX_TTL)): # Here we will combine the path we're after with the TTL, and use this to track the returning ICMP payload ip_id = ints_to_ipid(path, ttl) # TODO: Hide this behind a class packet = { "ip_ver": 4, "ip_daddr": daddr, "udp_src_port": port, "udp_dst_port": DST_PORT, "ttl": ttl, "l4_proto": 17, "ip_id": ip_id, } # Create our packet here. i = traceflow.packet_encode(**packet) # TODO: Maybe refactor to hide these behind a single function, to be v4/v6 agnostic # Combine the IPv4 and UDP headers here probe = i.encode_ipv4_header() + i.encode_ipv4_udp_packet() s = traceflow.socket_handler(packet["ip_daddr"]) _ = s.send_ipv4(probe) time.sleep(args.wait) # Since we are not running a sequential trace, we should check in to see if we've gotten a reply from the destination yet packets = listener.get_packets_by_pathid(path) end = [i for i in packets if i["ip_saddr"] == daddr] if len(end) > 0: logging.debug(f"Breaking trace to {daddr} at TTL {ttl}") break # We should get all the packets the listener received here rx_icmp = listener.get_all_packets() traces = dict() for i in rx_icmp: icmp_packet = traceflow.packet_decode.decode_icmp(rx_icmp[i]["payload"]) ipv4_packet = traceflow.packet_decode.decode_ipv4_header(icmp_packet["payload"]) (path, ttl) = ipid_to_ints(ipv4_packet["ip_id"]) if path not in traces.keys(): traces[path] = dict() if ttl not in traces[path].keys(): traces[path][ttl] = rx_icmp[i]["ip_saddr"] logging.debug("Run: %s TTL: %s" % (path, ttl)) # Here we will fill in missing probes with a * # We should also trim any duplicate replies from daddr # and also fill in an x to pad up unequal path lengths path_max = max([max(traces[i].keys()) for i in traces.keys()]) for path in traces.keys(): # Remove any duplicate answers from daddr dup_keys = [i for i in traces[path] if traces[path][i] == daddr] while len(dup_keys) > 1: logging.debug("dup keys: %s" % dup_keys) traces[path].pop(max(dup_keys)) dup_keys = [i for i in traces[path] if traces[path][i] == daddr] # Now we fill in * for any missing hops last_ttl = sorted(traces[path])[-1] for ttl in list(range(1, last_ttl + 1)): if ttl not in traces[path]: logging.debug(f"Missing TTL({ttl}) for path {path}") traces[path][ttl] = "*" # Now we should handle unequal length paths path_length = len(traces[path]) if path_length < path_max: for i in range(path_length, path_max + 1): if i not in traces[path].keys(): logging.debug(f"Insert fake hop at {i} for path {path}") traces[path][i] = "x" if args.format.lower() == "vert": # Print horizontal results traceflow.printer.print_vertical(traces) if args.format.lower() == "horiz": # print vertical results traceflow.printer.print_horizontal(traces) if args.format.lower() == "viz": # Experimental vis.js / browser based visualisation traceflow.printer.start_viz(traces) exit(0)
class TestPacketEncode(unittest.TestCase): packet = dict() packet["ip_ver"] = 4 # UDP packet["l4_proto"] = 17 packet["ip_daddr"] = "1.1.1.1" packet["ip_saddr"] = traceflow.socket_handler.get_egress_ip( packet["ip_daddr"]) packet["udp_src_port"] = 35000 packet["udp_dst_port"] = 53 packet["ttl"] = 3 global i i = traceflow.packet_encode(**packet) # IPv4 Tests here def test_decode_ipv4(self): ipv4_hdr = i.encode_ipv4_header() self.assertIsInstance(ipv4_hdr, bytes) # packet["ttl"] = 3 def test_encode_ipv4_ttl(self): # TTL is 9th byte ttl = i.encode_ipv4_header()[8] self.assertEqual(ttl, 3) # packet["l4_proto"] = 17 def test_encode_ipv4_l4_proto(self): # Proto is 9th byte l4_proto = i.encode_ipv4_header()[9] self.assertEqual(l4_proto, 17) # Checksum is 9059 - pre-computed. def test_encode_ipv4_l4_proto(self): # Checksum is 10th and 11th bytes l4_proto = i.encode_ipv4_header()[10:12] self.assertEqual(l4_proto, struct.pack("H", 9059)) # packet["ip_saddr"] = traceflow.socket_handler.get_egress_ip(packet["ip_daddr"]) # This was pre-computed to 192.168.0.31 for this packet. def test_encode_ipv4_l4_proto(self): # Checksum is 10th and 11th bytes ip_saddr = i.encode_ipv4_header()[12:16] self.assertEqual(ip_saddr, socket.inet_aton("192.168.0.31")) # packet["ip_daddr"] = "1.1.1.1" def test_encode_ipv4_l4_proto(self): # Checksum is 10th and 11th bytes ip_saddr = i.encode_ipv4_header()[16:20] self.assertEqual(ip_saddr, socket.inet_aton("1.1.1.1")) # UDP Tests here def test_decode_udp(self): udp_hdr = i.encode_ipv4_udp_packet() self.assertIsInstance(udp_hdr, bytes) # packet["udp_src_port"] = 35000 def test_encode_udp_src(self): # src is 1st word udp_src_port = i.encode_ipv4_udp_packet()[0:2] self.assertEqual(udp_src_port, struct.pack("!H", 35000)) # packet["udp_dst_port"] = 53 def test_encode_udp_dst(self): # dst is 2nd word udp_dst_port = i.encode_ipv4_udp_packet()[2:4] self.assertEqual(udp_dst_port, struct.pack("!H", 53)) # UDP Packet Length: 18, computed. def test_encode_udp_len(self): # Len is 3rd word udp_len = i.encode_ipv4_udp_packet()[4:6] self.assertEqual(udp_len, struct.pack("!H", 18))
def main(): # ha ha ha args = helpers.get_help() # CLI arguments set here. try: daddr = socket.gethostbyname(args.destination) except socket.gaierror as e: if "Name or service not known" in str(e): print(f"Error, could not resolve {args.destination}, exiting") exit(1) else: print(f"General error resolving {args.destination}") print("exiting") exit(1) print(f"Resolved {args.destination} to {daddr}") TOT_RUNS = args.paths DST_PORT = args.dstport SRC_PORT = args.srcport MAX_TTL = args.ttl BIND_IP = args.bind if args.debug: logger = logging.getLogger() logger.setLevel(logging.DEBUG) if TOT_RUNS > 255: print( f"Max paths we can probe is 255. Setting --paths to 255 and continuing" ) TOT_RUNS = 255 # Setup the background thread listener here. Note that we need to pass daddr so we can snag the dst port unreachable # ICMP message. listener = traceflow.socket_listener(daddr) run_ids = dict() # Keep track of which path we're looking to enumerate for path in range(1, TOT_RUNS + 1): port = SRC_PORT + path run_ids[path] = port print( f"Looking at Path ID {path} (src port:{port} , dst port:{DST_PORT})" ) for ttl in list(range(1, MAX_TTL)): # Here we will combine the path we're after with the TTL, and use this to track the returning ICMP payload ip_id = helpers.ints_to_ipid(path, ttl) # TODO: Hide this behind a class ip_ver = 4 ip_daddr = daddr udp_src_port = port udp_dst_port = DST_PORT ttl = ttl l4_proto = 17 ip_id = ip_id additional_params = {"ip_tos": None, "ip_frag_off": None} # Create our packet here. i = traceflow.packet_encode( ip_ver, ip_daddr, udp_src_port, udp_dst_port, ttl, l4_proto, ip_id, **additional_params, ) # TODO: Maybe refactor to hide these behind a single function, to be v4/v6 agnostic # Combine the IPv4 and UDP headers here probe = i.ipv4_packet + i.udp_packet s = traceflow.socket_handler(ip_daddr) _ = s.send_ipv4(probe) time.sleep(args.wait) # Since we are not running a sequential trace, we should check in to see if we've gotten a reply from the destination yet packets = listener.get_packets_by_pathid(path) end = [i for i in packets if i["ip_saddr"] == daddr] if len(end) > 0: logging.debug(f"Breaking trace to {daddr} at TTL {ttl}") break # We should get all the packets the listener received here rx_icmp = listener.get_all_packets() if len(rx_icmp) == 0: logging.debug(f"rx_icmp is {len(rx_icmp)}") print(f"Did not receive any TTL expired ICMP packets. Exiting") exit(1) traces = dict() # For each packet the listener got, loop across the ICMP message and see what the TTL/Path combo is. # Then add them to the dict traces as: traces[path][ttl] for i in rx_icmp: icmp_packet = traceflow.packet_decode.decode_icmp( rx_icmp[i]["payload"]) ipv4_packet = traceflow.packet_decode.decode_ipv4_header( icmp_packet["payload"]) (path, ttl) = helpers.ipid_to_ints(ipv4_packet["ip_id"]) if path not in traces.keys(): traces[path] = dict() if ttl not in traces[path].keys(): traces[path][ttl] = rx_icmp[i]["ip_saddr"] logging.debug("Run: %s TTL: %s" % (path, ttl)) # Here we will fill in missing probes with a * # We should also trim any duplicate replies from daddr # and also fill in an x to pad up unequal path lengths traces = helpers.remove_duplicates(traces, daddr) path_max = max([max(traces[i].keys()) for i in traces.keys()]) for path in traces.keys(): # Now we fill in * for any missing hops last_ttl = sorted(traces[path])[-1] for ttl in list(range(1, last_ttl + 1)): if ttl not in traces[path]: logging.debug(f"Missing TTL({ttl}) for path {path}") traces[path][ttl] = "*" # Now we should handle unequal length paths path_length = len(traces[path]) if path_length < path_max: for i in range(path_length, path_max + 1): if i not in traces[path].keys(): logging.debug(f"Insert fake hop at {i} for path {path}") traces[path][i] = "x" if args.dedup: traces = helpers.remove_duplicate_paths(traces) if args.format.lower() == "vert": # Print horizontal results traceflow.printer.print_vertical(traces) if args.format.lower() == "horiz": # print vertical results traceflow.printer.print_horizontal(traces) if args.format.lower() == "viz": # Experimental vis.js / browser based visualisation traceflow.printer.start_viz(traces, BIND_IP) exit(0)
def compute_traces(daddr, tot_runs=4, dst_port=33452, src_port=33452, max_ttl=64, to_wait=0.1): # Setup the background thread listener here. # Note that we need to pass daddr # so we can snag the dst port unreachable ICMP message. listener = traceflow.socket_listener(daddr) run_ids = dict() # Keep track of which path we're looking to enumerate for path in range(1, tot_runs + 1): port = src_port + path run_ids[path] = port print( f"Looking at Path ID {path} (src port:{port} , dst port:{dst_port})" ) for ttl in list(range(1, max_ttl)): # Here we will combine the path we're after with the TTL, # and use this to track the returning ICMP payload ip_id = helpers.ints_to_ipid(path, ttl) # TODO: Hide this behind a class ip_ver = 4 ip_daddr = daddr udp_src_port = port udp_dst_port = dst_port ttl = ttl l4_proto = 17 ip_id = ip_id additional_params = {"ip_tos": None, "ip_frag_off": None} # Create our packet here. i = traceflow.packet_encode( ip_ver, ip_daddr, udp_src_port, udp_dst_port, ttl, l4_proto, ip_id, **additional_params, ) # TODO: Maybe refactor to hide these behind a single function, to be v4/v6 agnostic # Combine the IPv4 and UDP headers here probe = i.ipv4_packet + i.udp_packet s = traceflow.socket_handler(ip_daddr) _ = s.send_ipv4(probe) time.sleep(to_wait) # Since we are not running a sequential trace, # we should check in to see if we've gotten a reply from the destination yet packets = listener.get_packets_by_pathid(path) end = [i for i in packets if i["ip_saddr"] == daddr] if len(end) > 0: logging.debug(f"Breaking trace to {daddr} at TTL {ttl}") break # We should get all the packets the listener received here rx_icmp = listener.get_all_packets() if len(rx_icmp) == 0: logging.debug(f"rx_icmp is {len(rx_icmp)}") print(f"Did not receive any TTL expired ICMP packets. Exiting") exit(1) traces = dict() # For each packet the listener got, loop across the ICMP message # and see what the TTL/Path combo is. # Then add them to the dict traces as: traces[path][ttl] for i in rx_icmp: icmp_packet = traceflow.packet_decode.decode_icmp( rx_icmp[i]["payload"]) ipv4_packet = traceflow.packet_decode.decode_ipv4_header( icmp_packet["payload"]) (path, ttl) = helpers.ipid_to_ints(ipv4_packet["ip_id"]) if path not in traces.keys(): traces[path] = dict() if ttl not in traces[path].keys(): traces[path][ttl] = rx_icmp[i]["ip_saddr"] logging.debug("Run: %s TTL: %s" % (path, ttl)) # Here we will fill in missing probes with a * # We should also trim any duplicate replies from daddr # and also fill in an x to pad up unequal path lengths traces = helpers.remove_duplicates(traces, daddr) path_max = max([max(traces[i].keys()) for i in traces.keys()]) for path in traces.keys(): # Now we fill in * for any missing hops last_ttl = sorted(traces[path])[-1] for ttl in list(range(1, last_ttl + 1)): if ttl not in traces[path]: logging.debug(f"Missing TTL({ttl}) for path {path}") traces[path][ttl] = "*" # Now we should handle unequal length paths path_length = len(traces[path]) if path_length < path_max: for i in range(path_length, path_max + 1): if i not in traces[path].keys(): logging.debug(f"Insert fake hop at {i} for path {path}") traces[path][i] = "x" return traces