# This file is part of Scapy # See http://www.secdev.org/projects/scapy for more informations # Copyright (C) Philippe Biondi <phil@secdev.org> # This program is published under a GPLv2 license """ IPv4 (Internet Protocol v4). """ import os import time import struct import re import socket from select import select from collections import defaultdict from kamene.utils import checksum, is_private_addr from kamene.layers.l2 import ( BitEnumField, BitField, ByteEnumField, ByteField, ConditionalField, CookedLinux, Dot3, ETH_P_ALL, ETH_P_IP, Emph, Ether, FieldLenField, FieldListField, FlagsField, GRE, Gen, IPField, IP_PROTOS, IncrementalValue, IntAutoMicroTime, IntField, MultiEnumField, Net, NoPayload, Packet, PacketListField, RandInt, RandShort, RandString, RandStringTerm, Raw, SNAP, ShortEnumField, ShortField, SourceIPField, StrField, StrFixedLenField, StrLenField, TCP_SERVICES, UDP_SERVICES, X3BytesField, XByteField, XShortField, bind_layers, colgen, conf, do_graph, getmacbyip, incremental_label, inet_aton, linehexdump, log_runtime, os, random, re, socket, struct, strxor, time, warning) from kamene.sendrecv import sr, sr1 from kamene.plist import PacketList, SndRcvList from kamene.automaton import Automaton, ATMT import kamene.as_resolvers ################## # IP Tools class # ################## class IPTools: """Add more powers to a class that have a "src" attribute.""" def whois(self): os.system("whois %s" % self.src) def ottl(self): t = [32, 64, 128, 255] + [self.ttl] t.sort() return t[t.index(self.ttl) + 1] def hops(self): return self.ottl() - self.ttl - 1 def is_priv_addr(self): return is_private_addr(self.src) _ip_options_names = {0: "end_of_list", 1: "nop", 2: "security", 3: "loose_source_route", 4: "timestamp", 5: "extended_security", 6: "commercial_security", 7: "record_route", 8: "stream_id", 9: "strict_source_route", 10: "experimental_measurement", 11: "mtu_probe", 12: "mtu_reply", 13: "flow_control", 14: "access_control", 15: "encode", 16: "imi_traffic_descriptor", 17: "extended_IP", 18: "traceroute", 19: "address_extension", 20: "router_alert", 21: "selective_directed_broadcast_mode", 23: "dynamic_packet_state", 24: "upstream_multicast_packet", 25: "quick_start", 30: "rfc4727_experiment", } class _IPOption_HDR(Packet): fields_desc = [BitField("copy_flag", 0, 1), BitEnumField("optclass", 0, 2, {0: "control", 2: "debug"}), BitEnumField("option", 0, 5, _ip_options_names)] class IPOption(Packet): name = "IP Option" fields_desc = [_IPOption_HDR, FieldLenField("length", None, fmt="B", # Only option 0 and 1 have no length and value length_of="value", adjust=lambda pkt, l:l + 2), StrLenField("value", "", length_from=lambda pkt:pkt.length - 2)] def extract_padding(self, p): return b"", p registered_ip_options = {} @classmethod def register_variant(cls): cls.registered_ip_options[cls.option.default] = cls @classmethod def dispatch_hook(cls, pkt=None, *args, **kargs): if pkt: opt = pkt[0] & 0x1f if opt in cls.registered_ip_options: return cls.registered_ip_options[opt] return cls class IPOption_EOL(IPOption): name = "IP Option End of Options List" option = 0 fields_desc = [_IPOption_HDR] class IPOption_NOP(IPOption): name = "IP Option No Operation" option = 1 fields_desc = [_IPOption_HDR] class IPOption_Security(IPOption): name = "IP Option Security" copy_flag = 1 option = 2 fields_desc = [_IPOption_HDR, ByteField("length", 11), ShortField("security", 0), ShortField("compartment", 0), ShortField("handling_restrictions", 0), StrFixedLenField("transmission_control_code", "xxx", 3), ] class IPOption_LSRR(IPOption): name = "IP Option Loose Source and Record Route" copy_flag = 1 option = 3 fields_desc = [_IPOption_HDR, FieldLenField("length", None, fmt="B", length_of="routers", adjust=lambda pkt, l:l + 3), ByteField("pointer", 4), # 4 is first IP FieldListField("routers", [], IPField("", "0.0.0.0"), length_from=lambda pkt:pkt.length - 3) ] def get_current_router(self): return self.routers[self.pointer // 4 - 1] class IPOption_RR(IPOption_LSRR): name = "IP Option Record Route" option = 7 class IPOption_SSRR(IPOption_LSRR): name = "IP Option Strict Source and Record Route" option = 9 class IPOption_Stream_Id(IPOption): name = "IP Option Stream ID" option = 8 fields_desc = [_IPOption_HDR, ByteField("length", 4), ShortField("security", 0), ] class IPOption_MTU_Probe(IPOption): name = "IP Option MTU Probe" option = 11 fields_desc = [_IPOption_HDR, ByteField("length", 4), ShortField("mtu", 0), ] class IPOption_MTU_Reply(IPOption_MTU_Probe): name = "IP Option MTU Reply" option = 12 class IPOption_Traceroute(IPOption): name = "IP Option Traceroute" copy_flag = 1 option = 18 fields_desc = [_IPOption_HDR, ByteField("length", 12), ShortField("id", 0), ShortField("outbound_hops", 0), ShortField("return_hops", 0), IPField("originator_ip", "0.0.0.0")] class IPOption_Address_Extension(IPOption): name = "IP Option Address Extension" copy_flag = 1 option = 19 fields_desc = [_IPOption_HDR, ByteField("length", 10), IPField("src_ext", "0.0.0.0"), IPField("dst_ext", "0.0.0.0")] class IPOption_Router_Alert(IPOption): name = "IP Option Router Alert" copy_flag = 1 option = 20 fields_desc = [_IPOption_HDR, ByteField("length", 4), ShortEnumField("alert", 0, {0: "router_shall_examine_packet"}), ] class IPOption_SDBM(IPOption): name = "IP Option Selective Directed Broadcast Mode" copy_flag = 1 option = 21 fields_desc = [_IPOption_HDR, FieldLenField("length", None, fmt="B", length_of="addresses", adjust=lambda pkt, l:l + 2), FieldListField("addresses", [], IPField("", "0.0.0.0"), length_from=lambda pkt:pkt.length - 2) ] TCPOptions = ( {0: ("EOL", None), 1: ("NOP", None), 2: ("MSS", "!H"), 3: ("WScale", "!B"), 4: ("SAckOK", None), 5: ("SAck", "!"), 8: ("Timestamp", "!II"), 14: ("AltChkSum", "!BH"), 15: ("AltChkSumOpt", None), 25: ("Mood", "!p") }, {"EOL": 0, "NOP": 1, "MSS": 2, "WScale": 3, "SAckOK": 4, "SAck": 5, "Timestamp": 8, "AltChkSum": 14, "AltChkSumOpt": 15, "Mood": 25 }) class TCPOptionsField(StrField): islist = 1 def getfield(self, pkt, s): opsz = (pkt.dataofs - 5) * 4 if opsz < 0: warning("bad dataofs (%i). Assuming dataofs=5" % pkt.dataofs) opsz = 0 return s[opsz:], self.m2i(pkt, s[:opsz]) def m2i(self, pkt, x): opt = [] while x: onum = x[0] if onum == 0: opt.append(("EOL", None)) x = x[1:] break if onum == 1: opt.append(("NOP", None)) x = x[1:] continue olen = x[1] if olen < 2: warning("Malformed TCP option (announced length is %i)" % olen) olen = 2 oval = x[2:olen] if onum in TCPOptions[0]: oname, ofmt = TCPOptions[0][onum] if onum == 5: # SAck ofmt += "%iI" % (len(oval) // 4) if ofmt and struct.calcsize(ofmt) == len(oval): oval = struct.unpack(ofmt, oval) if len(oval) == 1: oval = oval[0] opt.append((oname, oval)) else: opt.append((onum, oval)) x = x[olen:] return opt def i2m(self, pkt, x): opt = b"" for oname, oval in x: if type(oname) is str: if oname == "NOP": opt += b"\x01" continue elif oname == "EOL": opt += b"\x00" continue elif oname in TCPOptions[1]: onum = TCPOptions[1][oname] ofmt = TCPOptions[0][onum][1] if onum == 5: # SAck ofmt += "%iI" % len(oval) if ofmt is not None and (type(oval) is not str or "s" in ofmt): if type(oval) is not tuple: oval = (oval,) oval = struct.pack(ofmt, *oval) else: warning("option [%s] unknown. Skipped." % oname) continue else: onum = oname if type(oval) is not bytes: warning("option [%i] is not of type bytes." % onum) continue opt += bytes([(onum), (2 + len(oval))]) + oval return opt + b"\x00" * (3 - ((len(opt) + 3) % 4)) def randval(self): return [] # XXX class ICMPTimeStampField(IntField): re_hmsm = re.compile("([0-2]?[0-9])[Hh:](([0-5]?[0-9])([Mm:]([0-5]?[0-9])([sS:.]([0-9]{0,3}))?)?)?$") def i2repr(self, pkt, val): if val is None: return "--" else: sec, milli = divmod(val, 1000) min, sec = divmod(sec, 60) hour, min = divmod(min, 60) return "%d:%d:%d.%d" % (hour, min, sec, int(milli)) def any2i(self, pkt, val): if type(val) is str: hmsms = self.re_hmsm.match(val) if hmsms: h, _, m, _, s, _, ms = hmsms = hmsms.groups() ms = int(((ms or "") + "000")[:3]) val = ((int(h) * 60 + int(m or 0)) * 60 + int(s or 0)) * 1000 + ms else: val = 0 elif val is None: val = int((time.time() % (24 * 60 * 60)) * 1000) return val class IP(Packet, IPTools): name = "IP" fields_desc = [BitField("version", 4, 4), BitField("ihl", None, 4), XByteField("tos", 0), ShortField("len", None), ShortField("id", 1), FlagsField("flags", 0, 3, ["MF", "DF", "evil"]), BitField("frag", 0, 13), ByteField("ttl", 64), ByteEnumField("proto", 0, IP_PROTOS), XShortField("chksum", None), #IPField("src", "127.0.0.1"), Emph(SourceIPField("src", "dst")), Emph(IPField("dst", "127.0.0.1")), PacketListField("options", [], IPOption, length_from=lambda p:p.ihl * 4 - 20)] def post_build(self, p, pay): ihl = self.ihl p += b"\0" * ((-len(p)) % 4) # pad IP options if needed if ihl is None: ihl = len(p) // 4 p = bytes([((self.version & 0xf) << 4) | ihl & 0x0f]) + p[1:] if self.len is None: l = len(p) + len(pay) p = p[:2] + struct.pack("!H", l) + p[4:] if self.chksum is None: ck = checksum(p) p = p[:10] + bytes([ck >> 8]) + bytes([ck & 0xff]) + p[12:] return p + pay def extract_padding(self, s): l = self.len - (self.ihl << 2) return s[:l], s[l:] def send(self, s, slp=0): for p in self: try: s.sendto(bytes(p), (p.dst, 0)) except socket.error as msg: log_runtime.error(msg) if slp: time.sleep(slp) def route(self): dst = self.dst if isinstance(dst, Gen): dst = next(iter(dst)) return conf.route.route(dst) def hashret(self): if ((self.proto == socket.IPPROTO_ICMP) and (isinstance(self.payload, ICMP)) and (self.payload.type in [3, 4, 5, 11, 12])): return self.payload.payload.hashret() else: if conf.checkIPsrc and conf.checkIPaddr: return strxor(inet_aton(self.src), inet_aton(self.dst)) + struct.pack("B", self.proto) + self.payload.hashret() else: return struct.pack("B", self.proto) + self.payload.hashret() def answers(self, other): if not isinstance(other, IP): return 0 if conf.checkIPaddr and (self.dst != other.src): return 0 if ((self.proto == socket.IPPROTO_ICMP) and (isinstance(self.payload, ICMP)) and (self.payload.type in [3, 4, 5, 11, 12])): # ICMP error message return self.payload.payload.answers(other) else: if ((conf.checkIPaddr and (self.src != other.dst)) or (self.proto != other.proto)): return 0 return self.payload.answers(other.payload) def mysummary(self): s = self.sprintf("%IP.src% > %IP.dst% %IP.proto%") if self.frag: s += " frag:%i" % self.frag return s def fragment(self, fragsize=1480): """Fragment IP datagrams""" fragsize = (fragsize + 7) // 8 * 8 lst = [] fnb = 0 fl = self while fl.underlayer is not None: fnb += 1 fl = fl.underlayer for p in fl: s = bytes(p[fnb].payload) nb = (len(s) + fragsize - 1) // fragsize for i in range(nb): q = p.copy() del q[fnb].payload del q[fnb].chksum del q[fnb].len if i == nb - 1: q[IP].flags &= ~1 else: q[IP].flags |= 1 q[IP].frag = i * fragsize // 8 r = conf.raw_layer(load=s[i * fragsize:(i + 1) * fragsize]) r.overload_fields = p[IP].payload.overload_fields.copy() q.add_payload(r) lst.append(q) return lst class TCP(Packet): name = "TCP" fields_desc = [ShortEnumField("sport", 20, TCP_SERVICES), ShortEnumField("dport", 80, TCP_SERVICES), IntField("seq", 0), IntField("ack", 0), BitField("dataofs", None, 4), BitField("reserved", 0, 3), FlagsField("flags", 0x2, 9, "FSRPAUECN"), ShortField("window", 8192), XShortField("chksum", None), ShortField("urgptr", 0), TCPOptionsField("options", {})] def post_build(self, p, pay): p += pay dataofs = self.dataofs if dataofs is None: dataofs = 5 + ((len(self.get_field("options").i2m(self, self.options)) + 3) // 4) p = p[:12] + bytes([(dataofs << 4) | (p[12]) & 0x0f]) + p[13:] if self.chksum is None: if isinstance(self.underlayer, IP): if self.underlayer.len is not None: ln = self.underlayer.len - 20 else: ln = len(p) psdhdr = struct.pack("!4s4sHH", inet_aton(self.underlayer.src), inet_aton(self.underlayer.dst), self.underlayer.proto, ln) ck = checksum(psdhdr + p) p = p[:16] + struct.pack("!H", ck) + p[18:] elif conf.ipv6_enabled and isinstance(self.underlayer, kamene.layers.inet6.IPv6) or isinstance(self.underlayer, kamene.layers.inet6._IPv6ExtHdr): ck = kamene.layers.inet6.in6_chksum(socket.IPPROTO_TCP, self.underlayer, p) p = p[:16] + struct.pack("!H", ck) + p[18:] else: warning("No IP underlayer to compute checksum. Leaving null.") return p def hashret(self): if conf.checkIPsrc: return struct.pack("H", self.sport ^ self.dport) + self.payload.hashret() else: return self.payload.hashret() def answers(self, other): if not isinstance(other, TCP): return 0 if conf.checkIPsrc: if not ((self.sport == other.dport) and (self.dport == other.sport)): return 0 if abs(other.seq - self.ack) > 2 + len(other.payload): return 0 return 1 def mysummary(self): if isinstance(self.underlayer, IP): return self.underlayer.sprintf("TCP %IP.src%:%TCP.sport% > %IP.dst%:%TCP.dport% %TCP.flags%") elif conf.ipv6_enabled and isinstance(self.underlayer, kamene.layers.inet6.IPv6): return self.underlayer.sprintf("TCP %IPv6.src%:%TCP.sport% > %IPv6.dst%:%TCP.dport% %TCP.flags%") else: return self.sprintf("TCP %TCP.sport% > %TCP.dport% %TCP.flags%") class UDP(Packet): name = "UDP" fields_desc = [ShortEnumField("sport", 53, UDP_SERVICES), ShortEnumField("dport", 53, UDP_SERVICES), ShortField("len", None), XShortField("chksum", None), ] def post_build(self, p, pay): p += pay l = self.len if l is None: l = len(p) p = p[:4] + struct.pack("!H", l) + p[6:] if self.chksum is None: if isinstance(self.underlayer, IP): if self.underlayer.len is not None: ln = self.underlayer.len - 20 else: ln = len(p) psdhdr = struct.pack("!4s4sHH", inet_aton(self.underlayer.src), inet_aton(self.underlayer.dst), self.underlayer.proto, ln) ck = checksum(psdhdr + p) p = p[:6] + struct.pack("!H", ck) + p[8:] elif isinstance(self.underlayer, kamene.layers.inet6.IPv6) or isinstance(self.underlayer, kamene.layers.inet6._IPv6ExtHdr): ck = kamene.layers.inet6.in6_chksum(socket.IPPROTO_UDP, self.underlayer, p) p = p[:6] + struct.pack("!H", ck) + p[8:] else: warning("No IP underlayer to compute checksum. Leaving null.") return p def extract_padding(self, s): l = self.len - 8 return s[:l], s[l:] def hashret(self): return self.payload.hashret() def answers(self, other): if not isinstance(other, UDP): return 0 if conf.checkIPsrc: if self.dport != other.sport: return 0 return self.payload.answers(other.payload) def mysummary(self): if isinstance(self.underlayer, IP): return self.underlayer.sprintf("UDP %IP.src%:%UDP.sport% > %IP.dst%:%UDP.dport%") elif isinstance(self.underlayer, kamene.layers.inet6.IPv6): return self.underlayer.sprintf("UDP %IPv6.src%:%UDP.sport% > %IPv6.dst%:%UDP.dport%") else: return self.sprintf("UDP %UDP.sport% > %UDP.dport%") icmptypes = {0: "echo-reply", 3: "dest-unreach", 4: "source-quench", 5: "redirect", 8: "echo-request", 9: "router-advertisement", 10: "router-solicitation", 11: "time-exceeded", 12: "parameter-problem", 13: "timestamp-request", 14: "timestamp-reply", 15: "information-request", 16: "information-response", 17: "address-mask-request", 18: "address-mask-reply"} icmpcodes = {3: {0: "network-unreachable", 1: "host-unreachable", 2: "protocol-unreachable", 3: "port-unreachable", 4: "fragmentation-needed", 5: "source-route-failed", 6: "network-unknown", 7: "host-unknown", 9: "network-prohibited", 10: "host-prohibited", 11: "TOS-network-unreachable", 12: "TOS-host-unreachable", 13: "communication-prohibited", 14: "host-precedence-violation", 15: "precedence-cutoff", }, 5: {0: "network-redirect", 1: "host-redirect", 2: "TOS-network-redirect", 3: "TOS-host-redirect", }, 11: {0: "ttl-zero-during-transit", 1: "ttl-zero-during-reassembly", }, 12: {0: "ip-header-bad", 1: "required-option-missing", }, } class ICMP(Packet): name = "ICMP" fields_desc = [ByteEnumField("type", 8, icmptypes), MultiEnumField("code", 0, icmpcodes, depends_on=lambda pkt:pkt.type, fmt="B"), XShortField("chksum", None), ConditionalField(XShortField("id", 0), lambda pkt:pkt.type in [0, 8, 13, 14, 15, 16, 17, 18]), ConditionalField(XShortField("seq", 0), lambda pkt:pkt.type in [0, 8, 13, 14, 15, 16, 17, 18]), ConditionalField(ICMPTimeStampField("ts_ori", None), lambda pkt:pkt.type in [13, 14]), ConditionalField(ICMPTimeStampField("ts_rx", None), lambda pkt:pkt.type in [13, 14]), ConditionalField(ICMPTimeStampField("ts_tx", None), lambda pkt:pkt.type in [13, 14]), ConditionalField(IPField("gw", "0.0.0.0"), lambda pkt:pkt.type == 5), ConditionalField(ByteField("ptr", 0), lambda pkt:pkt.type == 12), ConditionalField(X3BytesField("reserved", 0), lambda pkt:pkt.type == 12), ConditionalField(IPField("addr_mask", "0.0.0.0"), lambda pkt:pkt.type in [17, 18]), ConditionalField(IntField("unused", 0), lambda pkt:pkt.type not in [0, 5, 8, 12, 13, 14, 15, 16, 17, 18]), ] def post_build(self, p, pay): p += pay if self.chksum is None: ck = checksum(p) p = p[:2] + bytes([ck >> 8, ck & 0xff]) + p[4:] return p def hashret(self): if self.type in [0, 8, 13, 14, 15, 16, 17, 18]: return struct.pack("HH", self.id, self.seq) + self.payload.hashret() return self.payload.hashret() def answers(self, other): if not isinstance(other, ICMP): return 0 if ((other.type, self.type) in [(8, 0), (13, 14), (15, 16), (17, 18)] and self.id == other.id and self.seq == other.seq): return 1 return 0 def guess_payload_class(self, payload): if self.type in [3, 4, 5, 11, 12]: return IPerror else: return None def mysummary(self): if isinstance(self.underlayer, IP): return self.underlayer.sprintf("ICMP %IP.src% > %IP.dst% %ICMP.type% %ICMP.code%") else: return self.sprintf("ICMP %ICMP.type% %ICMP.code%") class IPerror(IP): name = "IP in ICMP" def answers(self, other): if not isinstance(other, IP): return 0 if not (((conf.checkIPsrc == 0) or (self.dst == other.dst)) and (self.src == other.src) and (((conf.checkIPID == 0) or (self.id == other.id) or (conf.checkIPID == 1 and self.id == socket.htons(other.id)))) and (self.proto == other.proto)): return 0 return self.payload.answers(other.payload) def mysummary(self): return Packet.mysummary(self) class TCPerror(TCP): # Better fix to be found for building and parsing TCPerror inside ICMP destination unreachable. With this at least the test suite passes fields_desc = [ShortEnumField("sport", 20, TCP_SERVICES), ShortEnumField("dport", 80, TCP_SERVICES), IntField("seq", 0), IntField("ack", 0), BitField("dataofs", None, 4), BitField("reserved", 0, 4), FlagsField("flags", 0x2, 8, "FSRPAUEC"), ShortField("window", 8192), XShortField("chksum", None), ShortField("urgptr", 0), TCPOptionsField("options", {})] name = "TCP in ICMP" def post_build(self, p, pay): p += pay return p def answers(self, other): if not isinstance(other, TCP): return 0 if conf.checkIPsrc: if not ((self.sport == other.sport) and (self.dport == other.dport)): return 0 if conf.check_TCPerror_seqack: if self.seq is not None: if self.seq != other.seq: return 0 if self.ack is not None: if self.ack != other.ack: return 0 return 1 def mysummary(self): return Packet.mysummary(self) class UDPerror(UDP): name = "UDP in ICMP" def answers(self, other): if not isinstance(other, UDP): return 0 if conf.checkIPsrc: if not ((self.sport == other.sport) and (self.dport == other.dport)): return 0 return 1 def mysummary(self): return Packet.mysummary(self) class ICMPerror(ICMP): name = "ICMP in ICMP" def answers(self, other): if not isinstance(other, ICMP): return 0 if not ((self.type == other.type) and (self.code == other.code)): return 0 if self.code in [0, 8, 13, 14, 17, 18]: if (self.id == other.id and self.seq == other.seq): return 1 else: return 0 else: return 1 def mysummary(self): return Packet.mysummary(self) bind_layers(Ether, IP, type=2048) bind_layers(CookedLinux, IP, proto=2048) bind_layers(GRE, IP, proto=2048) bind_layers(SNAP, IP, code=2048) bind_layers(IPerror, IPerror, frag=0, proto=4) bind_layers(IPerror, ICMPerror, frag=0, proto=1) bind_layers(IPerror, TCPerror, frag=0, proto=6) bind_layers(IPerror, UDPerror, frag=0, proto=17) bind_layers(IP, IP, frag=0, proto=4) bind_layers(IP, ICMP, frag=0, proto=1) bind_layers(IP, TCP, frag=0, proto=6) bind_layers(IP, UDP, frag=0, proto=17) bind_layers(IP, GRE, frag=0, proto=47) conf.l2types.register(101, IP) conf.l2types.register_num2layer(12, IP) conf.l3types.register(ETH_P_IP, IP) conf.l3types.register_num2layer(ETH_P_ALL, IP) conf.neighbor.register_l3(Ether, IP, lambda l2, l3: getmacbyip(l3.dst)) conf.neighbor.register_l3(Dot3, IP, lambda l2, l3: getmacbyip(l3.dst)) ################# # Fragmentation # ################# @conf.commands.register def fragment(pkt, fragsize=1480): """Fragment a big IP datagram""" fragsize = (fragsize + 7) // 8 * 8 lst = [] for p in pkt: s = bytes(p[IP].payload) nb = (len(s) + fragsize - 1) // fragsize for i in range(nb): q = p.copy() del q[IP].payload del q[IP].chksum del q[IP].len if i == nb - 1: q[IP].flags &= ~1 else: q[IP].flags |= 1 q[IP].frag = i * fragsize // 8 r = conf.raw_layer(load=s[i * fragsize:(i + 1) * fragsize]) r.overload_fields = p[IP].payload.overload_fields.copy() q.add_payload(r) lst.append(q) return lst def overlap_frag(p, overlap, fragsize=8, overlap_fragsize=None): if overlap_fragsize is None: overlap_fragsize = fragsize q = p.copy() del q[IP].payload q[IP].add_payload(overlap) qfrag = fragment(q, overlap_fragsize) qfrag[-1][IP].flags |= 1 return qfrag + fragment(p, fragsize) @conf.commands.register def defrag(plist): """defrag(plist) -> ([not fragmented], [defragmented], [ [bad fragments], [bad fragments], ... ])""" frags = defaultdict(PacketList) nofrag = PacketList() for p in plist: ip = p[IP] if IP not in p: nofrag.append(p) continue if ip.frag == 0 and ip.flags & 1 == 0: nofrag.append(p) continue uniq = (ip.id, ip.src, ip.dst, ip.proto) frags[uniq].append(p) defrag = [] missfrag = [] for lst in frags.values(): lst.sort(key=lambda x: x.frag) p = lst[0] lastp = lst[-1] if p.frag > 0 or lastp.flags & 1 != 0: # first or last fragment missing missfrag.append(lst) continue p = p.copy() if conf.padding_layer in p: del p[conf.padding_layer].underlayer.payload ip = p[IP] if ip.len is None or ip.ihl is None: clen = len(ip.payload) else: clen = ip.len - (ip.ihl << 2) txt = conf.raw_layer() for q in lst[1:]: if clen != q.frag << 3: # Wrong fragmentation offset if clen > q.frag << 3: warning("Fragment overlap (%i > %i) %r || %r || %r" % (clen, q.frag << 3, p, txt, q)) missfrag.append(lst) break if q[IP].len is None or q[IP].ihl is None: clen += len(q[IP].payload) else: clen += q[IP].len - (q[IP].ihl << 2) if conf.padding_layer in q: del q[conf.padding_layer].underlayer.payload txt.add_payload(q[IP].payload.copy()) else: ip.flags &= ~1 # !MF del ip.chksum del ip.len p = p / txt defrag.append(p) defrag2 = PacketList() for p in defrag: defrag2.append(p.__class__(bytes(p))) return nofrag, defrag2, missfrag @conf.commands.register def defragment(plist): """defragment(plist) -> plist defragmented as much as possible """ frags = defaultdict(lambda: []) final = [] pos = 0 for p in plist: p._defrag_pos = pos pos += 1 if IP in p: ip = p[IP] if ip.frag != 0 or ip.flags & 1: ip = p[IP] uniq = (ip.id, ip.src, ip.dst, ip.proto) frags[uniq].append(p) continue final.append(p) defrag = [] missfrag = [] for lst in frags.values(): lst.sort(key=lambda x: x.frag) p = lst[0] lastp = lst[-1] if p.frag > 0 or lastp.flags & 1 != 0: # first or last fragment missing missfrag += lst continue p = p.copy() if conf.padding_layer in p: del p[conf.padding_layer].underlayer.payload ip = p[IP] if ip.len is None or ip.ihl is None: clen = len(ip.payload) else: clen = ip.len - (ip.ihl << 2) txt = conf.raw_layer() for q in lst[1:]: if clen != q.frag << 3: # Wrong fragmentation offset if clen > q.frag << 3: warning("Fragment overlap (%i > %i) %r || %r || %r" % (clen, q.frag << 3, p, txt, q)) missfrag += lst break if q[IP].len is None or q[IP].ihl is None: clen += len(q[IP].payload) else: clen += q[IP].len - (q[IP].ihl << 2) if conf.padding_layer in q: del q[conf.padding_layer].underlayer.payload txt.add_payload(q[IP].payload.copy()) else: ip.flags &= ~1 # !MF del ip.chksum del ip.len p = p / txt p._defrag_pos = max(x._defrag_pos for x in lst) defrag.append(p) defrag2 = [] for p in defrag: q = p.__class__(bytes(p)) q._defrag_pos = p._defrag_pos defrag2.append(q) final += defrag2 final += missfrag final.sort(key=lambda x: x._defrag_pos) for p in final: del p._defrag_pos if hasattr(plist, "listname"): name = "Defragmented %s" % plist.listname else: name = "Defragmented" return PacketList(final, name=name) # Add timeskew_graph() method to PacketList def _packetlist_timeskew_graph(self, ip, **kargs): """Tries to graph the timeskew between the timestamps and real time for a given ip""" res = map(lambda x: self._elt2pkt(x), self.res) b = filter(lambda x: x.haslayer(IP) and x.getlayer(IP).src == ip and x.haslayer(TCP), res) c = [] for p in b: opts = p.getlayer(TCP).options for o in opts: if o[0] == "Timestamp": c.append((p.time, o[1][0])) if not c: warning("No timestamps found in packet list") return # d = map(lambda (x,y): (x%2000,((x-c[0][0])-((y-c[0][1])/1000.0))),c) d = map(lambda a: (a[0] % 2000, ((a[0] - c[0][0]) - ((a[1] - c[0][1]) / 1000.0))), c) return plt.plot(d, **kargs) #PacketList.timeskew_graph = types.MethodType(_packetlist_timeskew_graph, None) # Create a new packet list class TracerouteResult(SndRcvList): def __init__(self, res=None, name="Traceroute", stats=None): PacketList.__init__(self, res, name, stats, vector_index=1) self.graphdef = None self.graphASres = 0 self.padding = 0 self.hloc = None self.nloc = None def show(self): # return self.make_table(lambda (s,r): (s.sprintf("%IP.dst%:{TCP:tcp%ir,TCP.dport%}{UDP:udp%ir,UDP.dport%}{ICMP:ICMP}"), return self.make_table(lambda s, r: (s.sprintf("%IP.dst%:{TCP:tcp%ir,TCP.dport%}{UDP:udp%ir,UDP.dport%}{ICMP:ICMP}"), s.ttl, r.sprintf("%-15s,IP.src% {TCP:%TCP.flags%}{ICMP:%ir,ICMP.type%}"))) def get_trace(self): raw_trace = {} for s, r in self.res: if IP not in s: continue d = s[IP].dst if d not in raw_trace: raw_trace[d] = {} raw_trace[d][s[IP].ttl] = r[IP].src, ICMP not in r trace = {} for k in raw_trace.keys(): m = [x for x in raw_trace[k].keys() if raw_trace[k][x][1]] if not m: trace[k] = raw_trace[k] else: m = min(m) trace[k] = {i: raw_trace[k][i] for i in raw_trace[k].keys() if not raw_trace[k][i][1] or i <= m} return trace def trace3D(self): """Give a 3D representation of the traceroute. right button: rotate the scene middle button: zoom left button: move the scene left button on a ball: toggle IP displaying ctrl-left button on a ball: scan ports 21,22,23,25,80 and 443 and display the result""" trace = self.get_trace() import visual class IPsphere(visual.sphere): def __init__(self, ip, **kargs): visual.sphere.__init__(self, **kargs) self.ip = ip self.label = None self.setlabel(self.ip) def setlabel(self, txt, visible=None): if self.label is not None: if visible is None: visible = self.label.visible self.label.visible = 0 elif visible is None: visible = 0 self.label = visual.label(text=txt, pos=self.pos, space=self.radius, xoffset=10, yoffset=20, visible=visible) def action(self): self.label.visible ^= 1 visual.scene = visual.display() visual.scene.exit = True start = visual.box() rings = {} tr3d = {} for i in trace: tr = trace[i] tr3d[i] = [] ttl = tr.keys() for t in range(1, max(ttl) + 1): if t not in rings: rings[t] = [] if t in tr: if tr[t] not in rings[t]: rings[t].append(tr[t]) tr3d[i].append(rings[t].index(tr[t])) else: rings[t].append(("unk", -1)) tr3d[i].append(len(rings[t]) - 1) for t in rings: r = rings[t] l = len(r) for i in range(l): if r[i][1] == -1: col = (0.75, 0.75, 0.75) elif r[i][1]: col = visual.color.green else: col = visual.color.blue s = IPsphere(pos=((l - 1) * visual.cos(2 * i * visual.pi / l), (l - 1) * visual.sin(2 * i * visual.pi / l), 2 * t), ip=r[i][0], color=col) for trlst in tr3d.values(): if t <= len(trlst): if trlst[t - 1] == i: trlst[t - 1] = s forecol = colgen(0.625, 0.4375, 0.25, 0.125) for trlst in tr3d.values(): col = next(forecol) start = (0, 0, 0) for ip in trlst: visual.cylinder(pos=start, axis=ip.pos - start, color=col, radius=0.2) start = ip.pos movcenter = None while 1: visual.rate(50) if visual.scene.kb.keys: k = visual.scene.kb.getkey() if k == "esc" or k == "q": break if visual.scene.mouse.events: ev = visual.scene.mouse.getevent() if ev.press == "left": o = ev.pick if o: if ev.ctrl: if o.ip == "unk": continue savcolor = o.color o.color = (1, 0, 0) a, _ = sr(IP(dst=o.ip) / TCP(dport=[21, 22, 23, 25, 80, 443]), timeout=2) o.color = savcolor if len(a) == 0: txt = "%s:\nno results" % o.ip else: txt = "%s:\n" % o.ip for s, r in a: txt += r.sprintf("{TCP:%IP.src%:%TCP.sport% %TCP.flags%}{TCPerror:%IPerror.dst%:%TCPerror.dport% %IP.src% %ir,ICMP.type%}\n") o.setlabel(txt, visible=1) else: if hasattr(o, "action"): o.action() elif ev.drag == "left": movcenter = ev.pos elif ev.drop == "left": movcenter = None if movcenter: visual.scene.center -= visual.scene.mouse.pos - movcenter movcenter = visual.scene.mouse.pos # # world_trace needs to be reimplemented as gnuplot dependency is removed # def world_trace(self): # from modules.geo import locate_ip # ips = {} # rt = {} # ports_done = {} # for s,r in self.res: # ips[r.src] = None # if s.haslayer(TCP) or s.haslayer(UDP): # trace_id = (s.src,s.dst,s.proto,s.dport) # elif s.haslayer(ICMP): # trace_id = (s.src,s.dst,s.proto,s.type) # else: # trace_id = (s.src,s.dst,s.proto,0) # trace = rt.get(trace_id,{}) # if not r.haslayer(ICMP) or r.type != 11: # if trace_id in ports_done: # continue # ports_done[trace_id] = None # trace[s.ttl] = r.src # rt[trace_id] = trace # # trt = {} # for trace_id in rt: # trace = rt[trace_id] # loctrace = [] # for i in range(max(trace.keys())): # ip = trace.get(i,None) # if ip is None: # continue # loc = locate_ip(ip) # if loc is None: # continue # # loctrace.append((ip,loc)) # no labels yet # loctrace.append(loc) # if loctrace: # trt[trace_id] = loctrace # # tr = map(lambda x: Gnuplot.Data(x,with_="lines"), trt.values()) # g = Gnuplot.Gnuplot() # world = Gnuplot.File(conf.gnuplot_world,with_="lines") # g.plot(world,*tr) # return g def make_graph(self, ASres=None, padding=0): if ASres is None: ASres = conf.AS_resolver self.graphASres = ASres self.graphpadding = padding ips = {} rt = {} ports = {} ports_done = {} for s, r in self.res: r = r.getlayer(IP) or (conf.ipv6_enabled and r[kamene.layers.inet6.IPv6]) or r s = s.getlayer(IP) or (conf.ipv6_enabled and s[kamene.layers.inet6.IPv6]) or s ips[r.src] = None if TCP in s: trace_id = (s.src, s.dst, 6, s.dport) elif UDP in s: trace_id = (s.src, s.dst, 17, s.dport) elif ICMP in s: trace_id = (s.src, s.dst, 1, s.type) else: trace_id = (s.src, s.dst, s.proto, 0) trace = rt.get(trace_id, {}) ttl = conf.ipv6_enabled and kamene.layers.inet6.IPv6 in s and s.hlim or s.ttl if not (ICMP in r and r[ICMP].type == 11) and not (conf.ipv6_enabled and kamene.layers.inet6.IPv6 in r and kamene.layers.inet6.ICMPv6TimeExceeded in r): if trace_id in ports_done: continue ports_done[trace_id] = None p = ports.get(r.src, []) if TCP in r: p.append(r.sprintf("<T%ir,TCP.sport%> %TCP.sport% %TCP.flags%")) trace[ttl] = r.sprintf('"%r,src%":T%ir,TCP.sport%') elif UDP in r: p.append(r.sprintf("<U%ir,UDP.sport%> %UDP.sport%")) trace[ttl] = r.sprintf('"%r,src%":U%ir,UDP.sport%') elif ICMP in r: p.append(r.sprintf("<I%ir,ICMP.type%> ICMP %ICMP.type%")) trace[ttl] = r.sprintf('"%r,src%":I%ir,ICMP.type%') else: p.append(r.sprintf("{IP:<P%ir,proto%> IP %proto%}{IPv6:<P%ir,nh%> IPv6 %nh%}")) trace[ttl] = r.sprintf('"%r,src%":{IP:P%ir,proto%}{IPv6:P%ir,nh%}') ports[r.src] = p else: trace[ttl] = r.sprintf('"%r,src%"') rt[trace_id] = trace # Fill holes with unk%i nodes unknown_label = incremental_label("unk%i") blackholes = [] bhip = {} for rtk in rt: trace = rt[rtk] k = trace.keys() for n in range(min(k), max(k)): if not n in trace: trace[n] = next(unknown_label) if not rtk in ports_done: if rtk[2] == 1: # ICMP bh = "%s %i/icmp" % (rtk[1], rtk[3]) elif rtk[2] == 6: # TCP bh = "%s %i/tcp" % (rtk[1], rtk[3]) elif rtk[2] == 17: # UDP bh = '%s %i/udp' % (rtk[1], rtk[3]) else: bh = '%s %i/proto' % (rtk[1], rtk[2]) ips[bh] = None bhip[rtk[1]] = bh bh = '"%s"' % bh trace[max(k) + 1] = bh blackholes.append(bh) # Find AS numbers ASN_query_list = dict.fromkeys(map(lambda x: x.rsplit(" ", 1)[0], ips)).keys() if ASres is None: ASNlist = [] else: ASNlist = ASres.resolve(*ASN_query_list) ASNs = {} ASDs = {} for ip, asn, desc, in ASNlist: if asn is None: continue iplist = ASNs.get(asn, []) if ip in bhip: if ip in ports: iplist.append(ip) iplist.append(bhip[ip]) else: iplist.append(ip) ASNs[asn] = iplist ASDs[asn] = desc backcolorlist = colgen("60", "86", "ba", "ff") forecolorlist = colgen("a0", "70", "40", "20") s = "digraph trace {\n" s += "\n\tnode [shape=ellipse,color=black,style=solid];\n\n" s += "\n#ASN clustering\n" for asn in ASNs: s += '\tsubgraph cluster_%s {\n' % asn col = next(backcolorlist) s += '\t\tcolor="#%s%s%s";' % col s += '\t\tnode [fillcolor="#%s%s%s",style=filled];' % col s += '\t\tfontsize = 10;' s += '\t\tlabel = "%s\\n[%s]"\n' % (asn, ASDs[asn]) for ip in ASNs[asn]: s += '\t\t"%s";\n' % ip s += "\t}\n" s += "#endpoints\n" for p in ports: s += '\t"%s" [shape=record,color=black,fillcolor=green,style=filled,label="%s|%s"];\n' % (p, p, "|".join(ports[p])) s += "\n#Blackholes\n" for bh in blackholes: s += '\t%s [shape=octagon,color=black,fillcolor=red,style=filled];\n' % bh if padding: s += "\n#Padding\n" pad = {} for _, rcv in self.res: if rcv.src not in ports and rcv.haslayer(conf.padding_layer): p = rcv.getlayer(conf.padding_layer).load if p != "\x00" * len(p): pad[rcv.src] = None for rcv in pad: s += '\t"%s" [shape=triangle,color=black,fillcolor=red,style=filled];\n' % rcv s += "\n\tnode [shape=ellipse,color=black,style=solid];\n\n" for rtk in rt: s += "#---[%s\n" % repr(rtk) s += '\t\tedge [color="#%s%s%s"];\n' % next(forecolorlist) trace = rt[rtk] k = trace.keys() for n in range(min(k), max(k)): s += '\t%s ->\n' % trace[n] s += '\t%s;\n' % trace[max(k)] s += "}\n" self.graphdef = s def graph(self, ASres=None, padding=0, **kargs): """x.graph(ASres=conf.AS_resolver, other args): ASres=None : no AS resolver => no clustering ASres=AS_resolver() : default whois AS resolver (riswhois.ripe.net) ASres=AS_resolver_cymru(): use whois.cymru.com whois database ASres=AS_resolver(server="whois.ra.net") format: output type (svg, ps, gif, jpg, etc.), passed to dot's "-T" option figsize: w,h tuple in inches. See matplotlib documentation target: filename. If None uses matplotlib to display prog: which graphviz program to use""" if ASres is None: ASres = conf.AS_resolver if (self.graphdef is None or self.graphASres != ASres or self.graphpadding != padding): self.make_graph(ASres, padding) return do_graph(self.graphdef, **kargs) @conf.commands.register def traceroute(target, dport=80, minttl=1, maxttl=30, sport=RandShort(), l4=None, filter=None, timeout=2, verbose=None, **kargs): """Instant TCP traceroute traceroute(target, [maxttl=30,] [dport=80,] [sport=80,] [verbose=conf.verb]) -> None """ if verbose is None: verbose = conf.verb if filter is None: # we only consider ICMP error packets and TCP packets with at # least the ACK flag set *and* either the SYN or the RST flag # set filter = "(icmp and (icmp[0]=3 or icmp[0]=4 or icmp[0]=5 or icmp[0]=11 or icmp[0]=12)) or (tcp and (tcp[13] & 0x16 > 0x10))" if l4 is None: a, b = sr(IP(dst=target, id=RandShort(), ttl=(minttl, maxttl)) / TCP(seq=RandInt(), sport=sport, dport=dport), timeout=timeout, filter=filter, verbose=verbose, **kargs) else: # this should always work filter = "ip" a, b = sr(IP(dst=target, id=RandShort(), ttl=(minttl, maxttl)) / l4, timeout=timeout, filter=filter, verbose=verbose, **kargs) a = TracerouteResult(a.res) if verbose: a.show() return a, b ########################## # Multi-Traceroute Class # ########################## class MTR: # # Initialize Multi-Traceroute Object Vars... def __init__(self, nquery=1, target=''): self._nquery = nquery # Number or traceroute queries self._ntraces = 1 # Number of trace runs self._iface = '' # Interface to use for trace self._gw = '' # Default Gateway IPv4 Address for trace self._netprotocol = 'TCP' # MTR network protocol to use for trace self._target = target # Session targets self._exptrg = [] # Expanded Session targets self._host2ip = {} # Target Host Name to IP Address self._ip2host = {} # Target IP Address to Host Name self._tcnt = 0 # Total Trace count self._tlblid = [] # Target Trace label IDs self._res = [] # Trace Send/Receive Response Packets self._ures = [] # Trace UnResponse Sent Packets self._ips = {} # Trace Unique IPv4 Addresses self._hops = {} # Traceroute Hop Ranges self._rt = [] # Individual Route Trace Summaries self._ports = {} # Completed Targets & Ports self._portsdone = {} # Completed Traceroutes & Ports self._rtt = {} # Round Trip Times (msecs) for Trace Nodes self._unknownlabel = incremental_label('"Unk%i"') self._asres = conf.AS_resolver # Initial ASN Resolver self._asns = {} # Found AS Numbers for the MTR session self._asds = {} # Associated AS Number descriptions self._unks = {} # Unknown Hops ASN IP boundaries self._graphdef = None self._graphasres = 0 self._graphpadding = 0 # # Get the protocol name from protocol integer value. # # proto - Protocol integer value. # # Returns a string value representing the given integer protocol. def get_proto_name(self, proto): ps = str(proto) if ps == '6': pt = 'tcp' elif ps == '17': pt = 'udp' elif ps == '1': pt = 'icmp' else: pt = str(proto) return pt # # Compute Black Holes... def get_black_holes(self): for t in range(0, self._ntraces): for rtk in self._rt[t]: trace = self._rt[t][rtk] k = trace.keys() for n in range(min(k), max(k)): if not n in trace: # Fill in 'Unknown' hops trace[n] = next(self._unknownlabel) if not rtk in self._portsdone: if rtk[2] == 1: # ICMP bh = "%s %i/icmp" % (rtk[1], rtk[3]) elif rtk[2] == 6: # TCP bh = "{ip:s} {dp:d}/tcp".format(ip=rtk[1], dp=rtk[3]) elif rtk[2] == 17: # UDP bh = '%s %i/udp' % (rtk[1], rtk[3]) else: bh = '%s %i/proto' % (rtk[1], rtk[2]) self._ips[rtk[1]] = None # Add the Blackhole IP to list of unique IP Addresses # # Update trace with Blackhole info... bh = '"{bh:s}"'.format(bh=bh) trace[max(k) + 1] = bh # # Detection for Blackhole - Failed target not set as last Hop in trace... for t in range(0, self._ntraces): for rtk in self._rt[t]: trace = self._rt[t][rtk] k = trace.keys() if (' ' not in trace[max(k)]) and (':' not in trace[max(k)]): if rtk[2] == 1: # ICMP bh = "%s %i/icmp" % (rtk[1], rtk[3]) elif rtk[2] == 6: # TCP bh = "{ip:s} {dp:d}/tcp".format(ip=rtk[1], dp=rtk[3]) elif rtk[2] == 17: # UDP bh = '%s %i/udp' % (rtk[1], rtk[3]) else: bh = '%s %i/proto' % (rtk[1], rtk[2]) self._ips[rtk[1]] = None # Add the Blackhole IP to list of unique IP Addresses # # Update trace with Blackhole info... bh = '"{bh:s}"'.format(bh=bh) trace[max(k) + 1] = bh # # Compute the Hop range for each trace... def compute_hop_ranges(self): n = 1 for t in range(0, self._ntraces): for rtk in self._rt[t]: trace = self._rt[t][rtk] k = trace.keys() # # Detect Blackhole Endpoints... h = rtk[1] mt = max(k) if not ':' in trace[max(k)]: h = trace[max(k)].replace('"', '') # Add a Blackhole Endpoint (':' Char does not exist) if max(k) == 1: # # Special case: Max TTL set to 1... mt = 1 else: mt = max(k) - 1 # Blackhole - remove Hop for Blackhole -> Host never reached hoplist = self._hops.get(h, []) # Get previous hop value hoplist.append([n, min(k), mt]) # Append trace hop range for this trace self._hops[h] = hoplist # Update mtr Hop value n += 1 # # Get AS Numbers... def get_asns(self, privaddr=0): """Obtain associated AS Numbers for IPv4 Addreses. privaddr: 0 - Normal display of AS numbers, 1 - Do not show an associated AS Number bound box (cluster) on graph for a private IPv4 Address.""" ips = {} if privaddr: for k, v in self._ips.items(): if not is_private_addr(k): ips[k] = v else: ips = self._ips # # Special case for the loopback IP Address: 127.0.0.1 - Do not ASN resolve... if '127.0.0.1' in ips: del ips['127.0.0.1'] # # ASN Lookup... asnquerylist = dict.fromkeys(map(lambda x: x.rsplit(" ", 1)[0], ips)).keys() if self._asres is None: asnlist = [] else: try: asnlist = self._asres.resolve(*asnquerylist) except: pass for ip, asn, desc, in asnlist: if asn is None: continue iplist = self._asns.get(asn, []) # Get previous ASN value iplist.append(ip) # Append IP Address to previous ASN # # If ASN is a string Convert to a number: (i.e., 'AS3257' => 3257) if type(asn) == str: asn = asn.upper() asn = asn.replace('AS', '') try: asn = int(asn) self._asns[asn] = iplist self._asds[asn] = desc except: continue else: self._asns[asn] = iplist self._asds[asn] = desc # # Get the ASN for a given IP Address. # # ip - IP Address to get the ASN for. # # Return the ASN for a given IP Address if found. # A -1 is returned if not found. def get_asn_ip(self, ip): for a in self._asns: for i in self._asns[a]: if ip == i: return a return -1 # # Guess Traceroute 'Unknown (Unkn) Hops' ASNs. # # Technique: Method to guess ASNs for Traceroute 'Unknown Hops'. # If the assign ASN for the known Ancestor IP is the # same as the known Descendant IP then use this ASN # for the 'Unknown Hop'. # Special case guess: If the Descendant IP is a # Endpoint Host Target the assign it to its # associated ASN. def guess_unk_asns(self): t = 1 for q in range(0, self._ntraces): for rtk in self._rt[q]: trace = self._rt[q][rtk] tk = trace.keys() begip = endip = '' unklist = [] for n in range(min(tk), (max(tk) + 1)): if trace[n].find('Unk') == -1: # # IP Address Hop found... if len(unklist) == 0: # # No 'Unknown Hop' found yet... begip = trace[n] else: # # At least one Unknown Hop found - Store IP boundary... endip = trace[n] for u in unklist: idx = begip.find(':') if idx != -1: # Remove Endpoint Trace port info: '"162.144.22.85":T443' begip = begip[:idx] idx = endip.find(':') if idx != -1: endip = endip[:idx] # # u[0] - Unknown Hop name... # u[1] - Hop number... self._unks[u[0]] = [begip, endip, '{t:d}:{h:d}'.format(t=t, h=u[1])] # # Init var for new Unknown Hop search... begip = endip = '' unklist = [] else: # # 'Unknown Hop' found... unklist.append([trace[n], n]) t += 1 # Inc next trace count # # Assign 'Unknown Hop' ASN... for u in self._unks: bip = self._unks[u][0] bip = bip.replace('"', '') # Begin IP - Strip off surrounding double quotes (") basn = self.get_asn_ip(bip) if basn == -1: continue eip = self._unks[u][1] eip = eip.replace('"', '') easn = self.get_asn_ip(eip) if easn == -1: continue # # Append the 'Unknown Hop' to an ASN if # Ancestor/Descendant IP ASN match... if basn == easn: self._asns[basn].append(u.replace('"', '')) else: # # Special case guess: If the Descendant IP is # a Endpoint Host Target the assign it to its # associated ASN. for d in self._tlblid: if eip in d: self._asns[easn].append(u.replace('"', '')) break # Make the DOT graph... def make_dot_graph(self, ASres=None, padding=0, vspread=0.75, title="Multi-Traceroute (MTR) Probe", timestamp="", rtt=1): import datetime import html if ASres is None: self._asres = conf.AS_resolver self._graphasres = ASres self._graphpadding = padding # # ASN box color generator... backcolorlist = colgen("60", "86", "ba", "ff") # # Edge (trace arrows) color generator... forecolorlist = colgen("a0", "70", "40", "20") # # Begin the DOT Digraph... s = "### kamene Multi-Traceroute (MTR) DOT Graph Results ({t:s}) ###\n".format(t=datetime.datetime.now().isoformat(' ')) s += "\ndigraph mtr {\n" # # Define the default graph attributes... s += '\tgraph [bgcolor=transparent,ranksep={vs:.2f}];\n'.format(vs=vspread) # # Define the default node shape and drawing color... s += '\tnode [shape="ellipse",fontname="Sans-Serif",fontsize=11,color="black",gradientangle=270,fillcolor="white:#a0a0a0",style="filled"];\n' # # Combine Trace Probe Begin Points... # # k0 k1 k2 v0 v1 k0 k1 k2 v0 v1 # Ex: bp = {('192.168.43.48',5555,''): ['T1','T3'], ('192.168.43.48',443,'https'): ['T2','T4']} bp = {} # ep -> A single services label for a given IP for d in self._tlblid: # k v0 v1 v2 v3 v4 v5 v6 v7 for k, v in d.items(): # Ex: k: '162.144.22.87' v: ('T1', '192.168.43.48', '162.144.22.87', 6, 443, 'https', 'SA', '') p = bp.get((v[1], v[4], v[5])) if p == None: bp[(v[1], v[4], v[5])] = [v[0]] # Add new (TCP Flags / ICMP / Proto) and initial trace ID else: bp[(v[1], v[4], v[5])].append(v[0]) # Append additional trace IDs # # Combine Begin Point services... # k sv0 sv1 sv0 sv1 # Ex bpip = {'192.168.43.48': [('<BT2>T2|<BT4>T4', 'https(443)'), ('<BB1>T1|<BT3>T3', '5555')]} bpip = {} # epip -> Combined Endpoint services label for a given IP for k, v in bp.items(): tr = '' for t in range(0, len(v)): if tr == '': tr += '<B{ts:s}>{ts:s}'.format(ts=v[t]) else: tr += '|<B{ts:s}>{ts:s}'.format(ts=v[t]) p = k[2] if p == '': # Use port number not name if resolved p = str(k[1]) else: p += '(' + str(k[1]) + ')' # Use both name and port if k[0] in bpip: bpip[k[0]].append((tr, p)) else: bpip[k[0]] = [(tr, p)] # # Create Endpoint Target Clusters... epc = {} # Endpoint Target Cluster Dictionary epip = [] # Endpoint IPs array oip = [] # Only Endpoint IP array epprb = [] # Endpoint Target and Probe the same IP array for d in self._tlblid: # Spin thru Target IDs for k, v in d.items(): # Get access to Target Endpoints h = k if v[6] == 'BH': # Add a Blackhole Endpoint Target h = '{bh:s} {bhp:d}/{bht:s}'.format(bh=k, bhp=v[4], bht=v[3]) elif v[1] == v[2]: # When the Target and host running the mtr session are epprb.append(k) # the same then append IP to list target and probe the same array epip.append(h) oip.append(k) # # Create unique arrays... uepip = set(epip) # Get a unique set of Endpoint IPs uepipo = set(oip) # Get a unique set of Only Endpoint IPs uepprb = set(epprb) # Get a unique set of Only IPs: Endpoint Target and Probe the same # # Now create unique endpoint target clusters.... for ep in uepip: # # Get Host only string... eph = ep f = ep.find(' ') if f >= 0: eph = ep[0:f] # # Build Traceroute Hop Range label... if ep in self._hops: # Is Endpoint IP in the Hops dictionary hr = self._hops[ep] elif eph in self._hops: # Is Host only endpoint in the Hops dictionary hr = self._hops[eph] else: continue # Not found in the Hops dictionary l = len(hr) if l == 1: hrs = "Hop Range (" else: hrs = "Hop Ranges (" c = 0 for r in hr: hrs += 'T{s1:d}: {s2:d} → {s3:d}'.format(s1=r[0], s2=r[1], s3=r[2]) c += 1 if c < l: hrs += ', ' hrs += ')' ecs = "\t\t### MTR Target Cluster ###\n" uep = ep.replace('.', '_') uep = uep.replace(' ', '_') uep = uep.replace('/', '_') gwl = '' if self._gw == eph: gwl = ' (Default Gateway)' ecs += '\t\tsubgraph cluster_{ep:s} {{\n'.format(ep=uep) ecs += '\t\t\ttooltip="MTR Target: {trg:s}{gwl:s}";\n'.format(trg=self._ip2host[eph], gwl=gwl) ecs += '\t\t\tcolor="darkgreen";\n' ecs += '\t\t\tfontsize=11;\n' ecs += '\t\t\tfontname="Sans-Serif";\n' ecs += '\t\t\tgradientangle=270;\n' ecs += '\t\t\tfillcolor="white:#a0a0a0";\n' ecs += '\t\t\tstyle="filled,rounded";\n' ecs += '\t\t\tpenwidth=2;\n' ecs += '\t\t\tlabel=<<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0"><TR><TD ALIGN="center"><B>Target: {h:s}{gwl:s}</B></TD></TR><TR><TD><FONT POINT-SIZE="9">{hr:s}</FONT></TD></TR></TABLE>>;\n'.format(h=html.escape(self._ip2host[eph]), gwl=html.escape(gwl), hr=hrs) ecs += '\t\t\tlabelloc="b";\n' pre = '' if ep in uepprb: # Special Case: Separate Endpoint Target from Probe pre = '_' # when they are the same -> Prepend an underscore char: '_' ecs += '\t\t\t"{pre:s}{ep:s}";\n'.format(pre=pre, ep=ep) ecs += "\t\t}\n" # # Store Endpoint Cluster... epc[ep] = ecs # # Create ASN Clusters (i.e. DOT subgraph and nodes) s += "\n\t### ASN Clusters ###\n" cipall = [] # Array of IPs consumed by all ASN Cluster cepipall = [] # Array of IP Endpoints (Targets) consumed by all ASN Cluster for asn in self._asns: cipcur = [] s += '\tsubgraph cluster_{asn:d} {{\n'.format(asn=asn) s += '\t\ttooltip="AS: {asn:d} - [{asnd:s}]";\n'.format(asn=asn, asnd=self._asds[asn]) col = next(backcolorlist) s += '\t\tcolor="#{s0:s}{s1:s}{s2:s}";\n'.format(s0=col[0], s1=col[1], s2=col[2]) # # Fill in ASN Cluster the associated generated color using an 11.7% alpha channel value (30/256)... s += '\t\tfillcolor="#{s0:s}{s1:s}{s2:s}30";\n'.format(s0=col[0], s1=col[1], s2=col[2]) s += '\t\tstyle="filled,rounded";\n' s += '\t\tnode [color="#{s0:s}{s1:s}{s2:s}",gradientangle=270,fillcolor="white:#{s0:s}{s1:s}{s2:s}",style="filled"];\n'.format(s0=col[0], s1=col[1], s2=col[2]) s += '\t\tfontsize=10;\n' s += '\t\tfontname="Sans-Serif";\n' s += '\t\tlabel=<<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0"><TR><TD ALIGN="center"><B><FONT POINT-SIZE="11">AS: {asn:d}</FONT></B></TD></TR><TR><TD>[{des:s}]</TD></TR></TABLE>>;\n'.format(asn=asn, des=html.escape(self._asds[asn])) s += '\t\tlabelloc="t";\n' s += '\t\tpenwidth=3;\n' for ip in self._asns[asn]: # # Only add IP if not an Endpoint Target... if not ip in uepipo: # # Spin thru all traces and only Add IP if not an ICMP Destination Unreachable node... for tr in range(0, self._ntraces): for rtk in self._rt[tr]: trace = self._rt[tr][rtk] k = trace.keys() for n in range(min(k), (max(k) + 1)): # # Check for not already added... if not ip in cipall: # # Add IP Hop - found in trace and not an ICMP Destination Unreachable node... if '"{ip:s}"'.format(ip=ip) == trace[n]: s += '\t\t"{ip:s}" [tooltip="Hop Host: {ip:s}"];\n'.format(ip=ip) cipall.append(ip) # # Special check for ICMP Destination Unreachable nodes... if ip in self._ports: for p in self._ports[ip]: if p.find('ICMP dest-unreach') >= 0: # # Check for not already added... uip = '{uip:s} 3/icmp'.format(uip=ip) if uip not in cipall: s += '\t\t"{uip:s}";\n'.format(uip=uip) cipall.append(uip) else: cipcur.append(ip) # Current list of Endpoints consumed by this ASN Cluster cepipall.append(ip) # Accumulated list of Endpoints consumed by all ASN Clusters # # Add Endpoint Cluster(s) if part of this ASN Cluster (Nested Clusters)... if len(cipcur) > 0: for ip in cipcur: for e in epc: # Loop thru each Endpoint Target Clusters h = e f = e.find(' ') # Strip off 'port/proto' if f >= 0: h = e[0:f] if h == ip: s += epc[e] s += "\t}\n" # # Add any Endpoint Target Clusters not consumed by an ASN Cluster (Stand-alone Cluster) # and not the same as the host running the mtr session... for ip in epc: h = ip f = h.find(' ') # Strip off 'port/proto' if f >= 0: h = ip[0:f] if not h in cepipall: for k, v in bpip.items(): # Check for target = host running the mtr session - Try to Add if k != h: # this Endpoint target to the Probe Target Cluster below. s += epc[ip] # Finally add the Endpoint Cluster if Stand-alone and # not running the mtr session. # # Probe Target Cluster... s += "\n\t### Probe Target Cluster ###\n" s += '\tsubgraph cluster_probe_Title {\n' p = '' for k, v in bpip.items(): p += ' {ip:s}'.format(ip=k) s += '\t\ttooltip="Multi-Traceroute (MTR) Probe: {ip:s}";\n'.format(ip=p) s += '\t\tcolor="darkorange";\n' s += '\t\tgradientangle=270;\n' s += '\t\tfillcolor="white:#a0a0a0";\n' s += '\t\tstyle="filled,rounded";\n' s += '\t\tpenwidth=3;\n' s += '\t\tfontsize=11;\n' s += '\t\tfontname="Sans-Serif";\n' # # Format Label including trace targets... tstr = '' for t in self._target: tstr += '<TR><TD ALIGN="center"><FONT POINT-SIZE="9">Target: {t:s} ('.format(t=t) # # Append resolve IP Addresses... l = len(self._host2ip[t]) c = 0 for ip in self._host2ip[t]: tstr += '{ip:s} → '.format(ip=ip) # # Append all associated Target IDs... ti = [] for d in self._tlblid: # Spin thru Target IDs for k, v in d.items(): # Get access to Target ID (v[0]) if k == ip: ti.append(v[0]) lt = len(ti) ct = 0 for i in ti: tstr += '{i:s}'.format(i=i) ct += 1 if ct < lt: tstr += ', ' c += 1 if c < l: tstr += ', ' tstr += ')</FONT></TD></TR>' s += '\t\tlabel=<<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0"><TR><TD ALIGN="center"><B>{s0:s}</B></TD></TR>'.format(s0=html.escape(title)) if timestamp != "": s += '<TR><TD ALIGN="center"><FONT POINT-SIZE="9">{s0:s}</FONT></TD></TR>'.format(s0=timestamp) s += '{s0:s}</TABLE>>;\n'.format(s0=tstr) s += '\t\tlabelloc="t";\n' for k, v in bpip.items(): s += '\t\t"{ip:s}";\n'.format(ip=k) # # Add in any Endpoint target that is the same as the host running the mtr session... for ip in epc: h = ip f = h.find(' ') # Strip off 'port/proto' if f >= 0: h = ip[0:f] for k, v in bpip.items(): # Check for target = host running the mtr session - Try to Add if k == h: # this Endpoint target to the Probe Target Cluster. s += epc[ip] s += "\t}\n" # # Default Gateway Cluster... s += "\n\t### Default Gateway Cluster ###\n" if self._gw != '': if self._gw in self._ips: if not self._gw in self._exptrg: s += '\tsubgraph cluster_default_gateway {\n' s += '\t\ttooltip="Default Gateway Host: {gw:s}";\n'.format(gw=self._gw) s += '\t\tcolor="goldenrod";\n' s += '\t\tgradientangle=270;\n' s += '\t\tfillcolor="white:#b8860b30";\n' s += '\t\tstyle="filled,rounded";\n' s += '\t\tpenwidth=3;\n' s += '\t\tfontsize=11;\n' s += '\t\tfontname="Sans-Serif";\n' s += '\t\tlabel=<<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" ALIGN="center"><TR><TD><B><FONT POINT-SIZE="9">Default Gateway</FONT></B></TD></TR></TABLE>>;\n' s += '\t\t"{gw:s}" [shape="diamond",fontname="Sans-Serif",fontsize=11,color="black",gradientangle=270,fillcolor="white:goldenrod",style="rounded,filled",tooltip="Default Gateway Host: {gw:s}"];\n'.format(gw=self._gw) s += "\t}\n" # # Build Begin Point strings... # Ex bps = '192.168.43.48" [shape="record",color="black",gradientangle=270,fillcolor="white:darkorange",style="filled",' # + 'label="192.168.43.48\nProbe|{http|{<BT1>T1|<BT3>T3}}|{https:{<BT2>T4|<BT3>T4}}"];' s += "\n\t### Probe Begin Traces ###\n" for k, v in bpip.items(): tr = '' for sv in v: if self._netprotocol == 'ICMP': if sv[1].find('ICMP') >= 0: ps = '{p:s} echo-request'.format(p=sv[1]) else: ps = 'ICMP({p:s}) echo-request'.format(p=sv[1]) else: ps = '{pr:s}: {p:s}'.format(pr=self._netprotocol, p=sv[1]) if tr == '': tr += '{{{ps:s}|{{{t:s}}}}}'.format(ps=ps, t=sv[0]) else: tr += '|{{{ps:s}|{{{t:s}}}}}'.format(ps=ps, t=sv[0]) bps1 = '\t"{ip:s}" [shape="record",color="black",gradientangle=270,fillcolor="white:darkorange",style="filled,rounded",'.format(ip=k) if self._iface != '': bps2 = 'label="Probe: {ip:s}\\nNetwork Interface: {ifc:s}|{tr:s}",tooltip="Begin Host Probe: {ip:s}"];\n'.format(ip=k, ifc=self._iface, tr=tr) else: bps2 = 'label="Probe: {ip:s}|{tr:s}",tooltip="Begin Host Probe: {ip:s}"];\n'.format(ip=k, tr=tr) s += bps1 + bps2 # s += "\n\t### Target Endpoints ###\n" # # Combine Trace Target Endpoints... # # k0 k1 k2 v0 v1 v2 k0 k1 k2 v0 v1 v2 # Ex: ep = {('162.144.22.87',80,'http'): ['SA','T1','T3'], ('10.14.22.8',443,'https'): ['SA','T2','T4']} ep = {} # ep -> A single services label for a given IP for d in self._tlblid: # k v0 v1 v2 v3 v4 v5 v6 v7 for k, v in d.items(): # Ex: k: 162.144.22.87 v: ('T1', '10.222.222.10', '162.144.22.87', 6, 443, 'https', 'SA', '') if not v[6] == 'BH': # Blackhole detection - do not create Endpoint p = ep.get((k, v[4], v[5])) if p == None: ep[(k, v[4], v[5])] = [v[6], v[0]] # Add new (TCP Flags / ICMP type / Proto) and initial trace ID else: ep[(k, v[4], v[5])].append(v[0]) # Append additional trace IDs # # Combine Endpoint services... # k v v # k sv0 sv1 sv2 sv0 sv1 sv2 # Ex epip = {'206.111.13.58': [('<ET8>T8|<ET10>T10', 'https', 'SA'), ('<ET7>T7|<ET6>T6', 'http', 'SA')]} epip = {} # epip -> Combined Endpoint services label for a given IP for k, v in ep.items(): tr = '' for t in range(1, len(v)): if tr == '': tr += '<E{ts:s}>{ts:s}'.format(ts=v[t]) else: tr += '|<E{ts:s}>{ts:s}'.format(ts=v[t]) p = k[2] if p == '': # Use port number not name if resolved p = str(k[1]) else: p += '(' + str(k[1]) + ')' # Use both name and port if k[0] in epip: epip[k[0]].append((tr, p, v[0])) else: epip[k[0]] = [(tr, p, v[0])] # # Build Endpoint strings... # Ex eps = '162.144.22.87" [shape=record,color="black",gradientangle=270,fillcolor="darkgreen:green",style=i"filled,rounded",' # + 'label="162.144.22.87\nTarget|{{<ET1>T1|<ET3>T3}|https SA}|{{<ET2>T4|<ET3>T4}|http SA}"];' for k, v in epip.items(): tr = '' for sv in v: if self._netprotocol == 'ICMP': ps = 'ICMP(0) echo-reply' else: ps = '{p:s} {f:s}'.format(p=sv[1], f=sv[2]) if tr == '': tr += '{{{{{t:s}}}|{ps:s}}}'.format(t=sv[0], ps=ps) else: tr += '|{{{{{t:s}}}|{ps:s}}}'.format(t=sv[0], ps=ps) pre = '' if k in uepprb: # Special Case: Separate Endpoint Target from Probe pre = '_' # when they are the same eps1 = '\t"{pre:s}{ip:s}" [shape="record",color="black",gradientangle=270,fillcolor="darkgreen:green",style="filled,rounded",'.format(pre=pre, ip=k) eps2 = 'label="Resolved Target\\n{ip:s}|{tr:s}",tooltip="MTR Resolved Target: {ip:s}"];\n'.format(ip=k, tr=tr) s += eps1 + eps2 # # Blackholes... # # ***Note: Order matters: If a hop is both a Blackhole on one trace and # a ICMP destination unreachable hop on another, # it will appear in the dot file as two nodes in # both sections. The ICMP destination unreachable # hop node will take precedents and appear only # since it is defined last. s += "\n\t### Blackholes ###\n" bhhops = [] for d in self._tlblid: # k v0 v1 v2 v3 v4 v5 v6 v7 for k, v in d.items(): # Ex: k: 162.144.22.87 v: ('T1', '10.222.222.10', '162.144.22.87', 'tcp', 5555, '', 'BH', 'I3') if v[6] == 'BH': # Blackhole detection # # If both a target blackhole and an ICMP packet hop, then skip creating this # node we be created in the 'ICMP Destination Unreachable Hops' section. if v[7] != 'I3': # ICMP destination not reached detection nd = '{b:s} {prt:d}/{pro:s}'.format(b=v[2], prt=v[4], pro=v[3]) if self._netprotocol == 'ICMP': bhh = '{b:s}<BR/><FONT POINT-SIZE="9">ICMP(0) echo-reply</FONT>'.format(b=v[2]) else: bhh = nd # # If not already added... if bhh not in bhhops: lb = 'label=<{lh:s}<BR/><FONT POINT-SIZE="8">Failed Target</FONT>>'.format(lh=bhh) s += '\t"{bh:s}" [{l:s},shape="doubleoctagon",color="black",gradientangle=270,fillcolor="white:red",style="filled,rounded",tooltip="Failed MTR Resolved Target: {b:s}"];\n'.format(bh=nd, l=lb, b=v[2]) bhhops.append(bhh) # # ICMP Destination Unreachable Hops... s += "\n\t### ICMP Destination Unreachable Hops ###\n" for d in self._ports: for p in self._ports[d]: if d in self._exptrg: # # Create Node: Target same as node that returns an ICMP packet... if p.find('ICMP dest-unreach') >= 0: unreach = 'ICMP(3): Destination' # 0 1 2 3 4 5 # Ex ICMP ports: '<I3> ICMP dest-unreach port-unreachable 17 53' icmpparts = p.split(' ') if icmpparts[3] == 'network-unreachable': unreach += '/Network' elif icmpparts[3] == 'host-unreachable': unreach += '/Host' elif icmpparts[3] == 'protocol-unreachable': unreach += '/Protocol' elif icmpparts[3] == 'port-unreachable': unreach += '/Port' protoname = self.get_proto_name(icmpparts[4]) protoport = '{pr:s}/{pt:s}'.format(pr=icmpparts[5], pt=protoname) lb = 'label=<{lh:s} {pp:s}<BR/><FONT POINT-SIZE="8">{u:s} Unreachable</FONT><BR/><FONT POINT-SIZE="8">Failed Target</FONT>>'.format(lh=d, pp=protoport, u=unreach) s += '\t"{lh:s} {pp:s}" [{lb:s},shape="doubleoctagon",color="black",gradientangle=270,fillcolor="yellow:red",style="filled,rounded",tooltip="{u:s} Unreachable, Failed Resolved Target: {lh:s} {pp:s}"];\n'.format(lb=lb, pp=protoport, lh=d, u=unreach) else: # # Create Node: Target not same as node that returns an ICMP packet... if p.find('ICMP dest-unreach') >= 0: unreach = 'ICMP(3): Destination' if p.find('network-unreachable') >= 0: unreach += '/Network' elif p.find('host-unreachable') >= 0: unreach += '/Host' elif p.find('protocol-unreachable') >= 0: unreach += '/Protocol' elif p.find('port-unreachable') >= 0: unreach += '/Port' lb = 'label=<{lh:s} 3/icmp<BR/><FONT POINT-SIZE="8">{u:s} Unreachable</FONT>>'.format(lh=d, u=unreach) s += '\t"{lh:s} 3/icmp" [{lb:s},shape="doubleoctagon",color="black",gradientangle=270,fillcolor="white:yellow",style="filled,rounded",tooltip="{u:s} Unreachable, Hop Host: {lh:s}"];\n'.format(lb=lb, lh=d, u=unreach) # # Padding check... if self._graphpadding: s += "\n\t### Nodes With Padding ###\n" pad = {} for t in range(0, self._ntraces): for _, rcv in self._res[t]: if rcv.src not in self._ports and rcv.haslayer(conf.padding_layer): p = rcv.getlayer(conf.padding_layer).load if p != "\x00" * len(p): pad[rcv.src] = None for sr in pad: lb = 'label=<<BR/>{r:s}<BR/><FONT POINT-SIZE="8">Padding</FONT>>'.format(r=sr) s += '\t"{r:s}" [{l:s},shape="box3d",color="black",gradientangle=270,fillcolor="white:red",style="filled,rounded"];\n'.format(r=sr, l=lb) # # Draw each trace (i.e., DOT edge) for each number of queries... s += "\n\t### Traces ###\n" t = 0 for q in range(0, self._ntraces): for rtk in self._rt[q]: s += "\t### T{tr:d} -> {r:s} ###\n".format(tr=(t + 1), r=repr(rtk)) col = next(forecolorlist) s += '\tedge [color="#{s0:s}{s1:s}{s2:s}"];\n'.format(s0=col[0], s1=col[1], s2=col[2]) # # Probe Begin Point (i.e., Begining of a trace)... for k, v in self._tlblid[t].items(): ptr = probe = v[1] s += '\t"{bp:s}":B{tr:s}:s -> '.format(bp=ptr, tr=v[0]) # # In between traces (i.e., Not at the begining or end of a trace)... trace = self._rt[q][rtk] tk = trace.keys() ntr = trace[min(tk)] # # Skip in between traces if there are none... if len(trace) > 1: lb = 'Trace: {tr:d}:{tn:d}, {lbp:s} -> {lbn:s}'.format(tr=(t + 1), tn=min(tk), lbp=ptr, lbn=ntr.replace('"', '')) if not 'Unk' in ntr: lb += ' (RTT: {prb:s} <-> {lbn:s} ({rtt:s}ms))'.format(prb=probe, lbn=ntr.replace('"', ''), rtt=self._rtt[t + 1][min(tk)]) if rtt: if not 'Unk' in ntr: llb = 'Trace: {tr:d}:{tn:d}, RTT: {prb:s} <-> {lbn:s} ({rtt:s}ms)'.format(tr=(t + 1), tn=min(tk), prb=probe, lbn=ntr.replace('"', ''), rtt=self._rtt[t + 1][min(tk)]) s += '{ntr:s} [label=<<FONT POINT-SIZE="8"> {rtt:s}ms</FONT>>,edgetooltip="{lb:s}",labeltooltip="{llb:s}"];\n'.format(ntr=ntr, rtt=self._rtt[t + 1][min(tk)], lb=lb, llb=llb) else: s += '{ntr:s} [edgetooltip="{lb:s}"];\n'.format(ntr=ntr, lb=lb) else: s += '{ntr:s} [edgetooltip="{lb:s}"];\n'.format(ntr=ntr, lb=lb) for n in range(min(tk) + 1, max(tk)): ptr = ntr ntr = trace[n] lb = 'Trace: {tr:d}:{tn:d}, {lbp:s} -> {lbn:s}'.format(tr=(t + 1), tn=n, lbp=ptr.replace('"', ''), lbn=ntr.replace('"', '')) if not 'Unk' in ntr: lb += ' (RTT: {prb:s} <-> {lbn:s} ({rtt:s}ms))'.format(prb=probe, lbn=ntr.replace('"', ''), rtt=self._rtt[t + 1][n]) if rtt: if not 'Unk' in ntr: llb = 'Trace: {tr:d}:{tn:d}, RTT: {prb:s} <-> {lbn:s} ({rtt:s}ms)'.format(tr=(t + 1), tn=n, prb=probe, lbn=ntr.replace('"', ''), rtt=self._rtt[t + 1][n]) # # Special check to see if the next and previous nodes are the same. # If yes use the DOT 'xlabel' attribute to spread out labels so that they # do not clash and 'forcelabel' so that they are placed. if ptr == ntr: s += '\t{ptr:s} -> {ntr:s} [xlabel=<<FONT POINT-SIZE="8"> {rtt:s}ms</FONT>>,forcelabel=True,edgetooltip="{lb:s}",labeltooltip="{llb:s}"];\n'.format(ptr=ptr, ntr=ntr, rtt=self._rtt[t + 1][n], lb=lb, llb=llb) else: s += '\t{ptr:s} -> {ntr:s} [label=<<FONT POINT-SIZE="8"> {rtt:s}ms</FONT>>,edgetooltip="{lb:s}",labeltooltip="{llb:s}"];\n'.format(ptr=ptr, ntr=ntr, rtt=self._rtt[t + 1][n], lb=lb, llb=llb) else: s += '\t{ptr:s} -> {ntr:s} [edgetooltip="{lb:s}"];\n'.format(ptr=ptr, ntr=ntr, lb=lb) else: s += '\t{ptr:s} -> {ntr:s} [edgetooltip="{lb:s}"];\n'.format(ptr=ptr, ntr=ntr, lb=lb) # # Enhance target Endpoint (i.e., End of a trace) replacement... for k, v in self._tlblid[t].items(): if v[6] == 'BH': # Blackhole detection - do not create Enhanced Endpoint # # Check for Last Hop / Backhole (Failed Target) match: lh = trace[max(tk)] lhicmp = False if lh.find(':I3') >= 0: # Is last hop and ICMP packet from target? lhicmp = True f = lh.find(' ') # Strip off 'port/proto' ''"100.41.207.244":I3' if f >= 0: lh = lh[0:f] f = lh.find(':') # Strip off 'proto:port' -> '"100.41.207.244 801/tcp"' if f >= 0: lh = lh[0:f] lh = lh.replace('"', '') # Remove surrounding double quotes ("") if k == lh: # Does Hop match final Target? # # Backhole last hop matched target: # # Check to skip in between traces... if len(trace) > 1: s += '\t{ptr:s} -> '.format(ptr=ntr) if lhicmp: # # Last hop is an ICMP packet from target and was reached... lb = 'Trace: {tr:d}:{tn:d}, {lbp:s} -> {lbn:s}'.format(tr=(t + 1), tn=max(tk), lbp=ntr.replace('"', ''), lbn=k) lb += ' (RTT: {prb:s} <-> {lbn:s} ({rtt:s}ms))'.format(prb=v[1], lbn=lh, rtt=self._rtt[t + 1][max(tk)]) if rtt: llb = 'Trace: {tr:d}:{tn:d}, RTT: {prb:s} <-> {lbn:s} ({rtt:s}ms)'.format(tr=(t + 1), tn=max(tk), prb=v[1], lbn=k, rtt=self._rtt[t + 1][max(tk)]) s += '"{bh:s} {bhp:d}/{bht:s}" [style="solid",label=<<FONT POINT-SIZE="8"> {rtt:s}ms</FONT>>,edgetooltip="{lb:s}",labeltooltip="{llb:s}"];\n'.format(bh=k, bhp=v[4], bht=v[3], rtt=self._rtt[t + 1][max(tk)], lb=lb, llb=llb) else: s += '"{bh:s} {bhp:d}/{bht:s}" [style="solid",edgetooltip="{lb:s}"];\n'.format(bh=k, bhp=v[4], bht=v[3], lb=lb) else: # # Last hop is not ICMP packet from target (Fake hop - never reached - use dashed trace)... lb = 'Trace: {tr:d} - Failed MTR Resolved Target: {bh:s} {bhp:d}/{bht:s}'.format(tr=(t + 1), bh=k, bhp=v[4], bht=v[3]) s += '"{bh:s} {bhp:d}/{bht:s}" [style="dashed",label=<<FONT POINT-SIZE="8"> T{tr:d}</FONT>>,edgetooltip="{lb:s}",labeltooltip="{lb:s}"];\n'.format(bh=k, bhp=v[4], bht=v[3], tr=(t + 1), lb=lb) else: # # Backhole not matched (Most likely: 'ICMP (3) destination-unreached' # but last hop not equal to the target: # # Add this last Hop (This Hop is not the Target)... # # Check to skip in between traces... if len(trace) > 1: s += '\t{ptr:s} -> '.format(ptr=ntr) lb = 'Trace: {tr:d}:{tn:d}, {lbp:s} -> {lbn:s}'.format(tr=(t + 1), tn=max(tk), lbp=ntr.replace('"', ''), lbn=lh) lb += ' (RTT: {prb:s} <-> {lbn:s} ({rtt:s}ms))'.format(prb=v[1], lbn=lh, rtt=self._rtt[t + 1][max(tk)]) llb = 'Trace: {tr:d}:{tn:d}, RTT: {prb:s} <-> {lbn:s} ({rtt:s}ms)'.format(tr=(t + 1), tn=max(tk), prb=v[1], lbn=lh, rtt=self._rtt[t + 1][max(tk)]) if rtt: s += '"{lh:s} 3/icmp" [style="solid",label=<<FONT POINT-SIZE="8"> {rtt:s}ms</FONT>>,edgetooltip="{lb:s}",labeltooltip="{llb:s}"];\n'.format(lh=lh, rtt=self._rtt[t + 1][max(tk)], lb=lb, llb=llb) else: s += '"{lh:s} 3/icmp" [style="solid",edgetooltip="{lb:s} 3/icmp",labeltooltip="{llb:s}"];\n'.format(lh=lh, lb=lb, llb=llb) # # Add the Failed Target (Blackhole - Fake hop - never reached - use dashed trace)... s += '\t"{lh:s} 3/icmp" -> '.format(lh=lh) lb = 'Trace: {tr:d} - Failed MTR Resolved Target: {bh:s} {bhp:d}/{bht:s}'.format(tr=(t + 1), bh=k, bhp=v[4], bht=v[3]) s += '"{bh:s} {bhp:d}/{bht:s}" [style="dashed",label=<<FONT POINT-SIZE="8"> T{tr:d}</FONT>>,edgetooltip="{lb:s}",labeltooltip="{llb:s}"];\n'.format(bh=k, bhp=v[4], bht=v[3], tr=(t + 1), lb=lb, llb=lb) else: # Enhanced Target Endpoint # # Check to skip in between traces... if len(trace) > 1: s += '\t{ptr:s} -> '.format(ptr=ntr) lb = 'Trace: {tr:d}:{tn:d}, {lbp:s} -> {lbn:s}'.format(tr=(t + 1), tn=max(tk), lbp=ntr.replace('"', ''), lbn=k) if not 'Unk' in k: lb += ' (RTT: {prb:s} <-> {lbn:s} ({rtt:s}ms))'.format(prb=v[1], lbn=k, rtt=self._rtt[t + 1][max(tk)]) pre = '' if k in uepprb: # Special Case: Distinguish the Endpoint Target from Probe pre = '_' # when they are the same using the underscore char: '_'. if rtt: if not 'Unk' in k: llb = 'Trace: {tr:d}:{tn:d}, RTT: {prb:s} <-> {lbn:s} ({rtt:s}ms)'.format(tr=(t + 1), tn=max(tk), prb=v[1], lbn=k, rtt=self._rtt[t + 1][max(tk)]) # # Check to remove label clashing... ntrs = ntr.replace('"', '') # Remove surrounding double quotes ("") if ntrs == k: s += '"{pre:s}{ep:s}":E{tr:s}:n [style="solid",xlabel=<<FONT POINT-SIZE="8"> {rtt:s}ms</FONT>>,forcelabel=True,edgetooltip="{lb:s}",labeltooltip="{llb:s}"];\n'.format(pre=pre, ep=k, tr=v[0], rtt=self._rtt[t + 1][max(tk)], lb=lb, llb=llb) else: s += '"{pre:s}{ep:s}":E{tr:s}:n [style="solid",label=<<FONT POINT-SIZE="8"> {rtt:s}ms</FONT>>,edgetooltip="{lb:s}",labeltooltip="{llb:s}"];\n'.format(pre=pre, ep=k, tr=v[0], rtt=self._rtt[t + 1][max(tk)], lb=lb, llb=llb) else: s += '"{pre:s}{ep:s}":E{tr:s}:n [style="solid",edgetooltip="{lb:s}"];\n'.format(pre=pre, ep=k, tr=v[0], lb=lb) else: s += '"{pre:s}{ep:s}":E{tr:s}:n [style="solid",edgetooltip="{lb:s}"];\n'.format(pre=pre, ep=k, tr=v[0], lb=lb) t += 1 # Next trace out of total traces # # Decorate Unknown ('Unkn') Nodes... s += "\n\t### Decoration For Unknown (Unkn) Node Hops ###\n" for u in self._unks: s += '\t{u:s} [tooltip="Trace: {t:s}, Unknown Hop: {u2:s}",shape="egg",fontname="Sans-Serif",fontsize=9,height=0.2,width=0.2,color="black",gradientangle=270,fillcolor="white:#d8d8d8",style="filled"];\n'.format(u=u, t=self._unks[u][2], u2=u.replace('"', '')) # # Create tooltip for standalone nodes... s += "\n\t### Tooltip for Standalone Node Hops ###\n" for k, v in self._ips.items(): if not k in cipall: if k != self._gw: if not k in cepipall: if not k in self._ports: found = False for tid in self._tlblid: if k in tid: found = True break if not found: s += '\t"{ip:s}" [tooltip="Hop Host: {ip:s}"];\n'.format(ip=k) # # End the DOT Digraph... s += "}\n" # # Store the DOT Digraph results... self._graphdef = s # # Graph the Multi-Traceroute... def graph(self, ASres=None, padding=0, vspread=0.75, title="Multi-Traceroute Probe (MTR)", timestamp="", rtt=1, **kargs): """x.graph(ASres=conf.AS_resolver, other args): ASres = None : Use AS default resolver => 'conf.AS_resolver' ASres = AS_resolver() : default whois AS resolver (riswhois.ripe.net) ASres = AS_resolver_cymru(): use whois.cymru.com whois database ASres = AS_resolver(server="whois.ra.net") padding: Show packets with padding as a red 3D-Box. vspread: Vertical separation between nodes on graph. title: Title text for the rendering graphic. timestamp: Title Time Stamp text to appear below the Title text. rtt: Display Round-Trip Times (msec) for Hops along trace edges. format: Output type (svg, ps, gif, jpg, etc.), passed to dot's "-T" option. figsize: w,h tuple in inches. See matplotlib documentation. target: filename. If None, uses matplotlib to display. prog: Which graphviz program to use.""" if self._asres is None: self._asres = conf.AS_resolver if (self._graphdef is None or # Remake the graph if there are any changes self._graphasres != self._asres or self._graphpadding != padding): self.make_dot_graph(ASres, padding, vspread, title, timestamp, rtt) return do_graph(self._graphdef, **kargs) ################################## # Multi-Traceroute Results Class # ################################## class MTracerouteResult(SndRcvList): def __init__(self, res=None, name="MTraceroute", stats=None): PacketList.__init__(self, res, name, stats, vector_index=1) def show(self, ntrace): return self.make_table(lambda s, r: (s.sprintf("Trace: " + str(ntrace) + " - %IP.dst%:{TCP:tcp%ir,TCP.dport%}{UDP:udp%ir,UDP.dport%}{ICMP:ICMP}"), s.ttl, r.sprintf("%-15s,IP.src% {TCP:%TCP.flags%}{ICMP:%ir,ICMP.type%}"))) # # Get trace components... # # mtrc - Instance of a MTRC class # # nq - Traceroute query number def get_trace_components(self, mtrc, nq): ips = {} rt = {} rtt = {} trtt = {} ports = {} portsdone = {} trgttl = {} if len(self.res) > 0: # # Responses found... for s, r in self.res: s = s.getlayer(IP) or (conf.ipv6_enabled and s[kamene.layers.inet6.IPv6]) or s r = r.getlayer(IP) or (conf.ipv6_enabled and r[kamene.layers.inet6.IPv6]) or r # # Make sure 'r.src' is an IP Address (e.g., Case where r.src = '24.97.150.188 80/tcp') rs = r.src.split() ips[rs[0]] = None if TCP in s: trace_id = (s.src, s.dst, 6, s.dport) elif UDP in s: trace_id = (s.src, s.dst, 17, s.dport) elif ICMP in s: trace_id = (s.src, s.dst, 1, s.type) else: trace_id = (s.src, s.dst, s.proto, 0) trace = rt.get(trace_id, {}) ttl = conf.ipv6_enabled and kamene.layers.inet6.IPv6 in s and s.hlim or s.ttl # # Check for packet response types: if not (ICMP in r and r[ICMP].type == 11) and not (conf.ipv6_enabled and kamene.layers.inet6.IPv6 in r and kamene.layers.inet6.ICMPv6TimeExceeded in r): # # Mostly: Process target reached or ICMP Unreachable... if trace_id in portsdone: # # Special check for out or order response packets: If previous trace was determined # done, but a ttl arrives with a lower value then process this response packet as the # final ttl target packet. if ttl >= trgttl[trace_id]: continue # Next Send/Receive packet else: # # Out of order response packet - process this packet as the possible # final ttl target packet. try: if trgttl[trace_id] in trace: del trace[trgttl[trace_id]] # Remove previous ttl target except: pass portsdone[trace_id] = None trgttl[trace_id] = ttl # Save potential target ttl packet p = ports.get(r.src, []) if TCP in r: p.append(r.sprintf("<T%ir,TCP.sport%> %TCP.sport% %TCP.flags%")) trace[ttl] = r.sprintf('"%r,src%":T%ir,TCP.sport%') elif UDP in r: p.append(r.sprintf("<U%ir,UDP.sport%> %UDP.sport%")) trace[ttl] = r.sprintf('"%r,src%":U%ir,UDP.sport%') elif ICMP in r: if r[ICMP].type == 0: # # Process echo-reply... p.append(r.sprintf("<I%ir,ICMP.type%> ICMP %ICMP.type%")) trace[ttl] = r.sprintf('"%r,src%":I%ir,ICMP.type%') else: # # Format Ex: '<I3> ICMP dest-unreach port-unreachable 17 53' p.append(r.sprintf("<I%ir,ICMP.type%> ICMP %ICMP.type% %ICMP.code% %ICMP.proto% %r,ICMP.dport%")) trace[ttl] = r.sprintf('"%r,src%":I%ir,ICMP.type%') else: p.append(r.sprintf("{IP:<P%ir,proto%> IP %proto%}{IPv6:<P%ir,nh%> IPv6 %nh%}")) trace[ttl] = r.sprintf('"%r,src%":{IP:P%ir,proto%}{IPv6:P%ir,nh%}') ports[r.src] = p else: # # Mostly ICMP Time-Exceeded packet - Save Hop Host IP Address... trace[ttl] = r.sprintf('"%r,src%"') rt[trace_id] = trace # # Compute the Round Trip Time for this trace packet in (msec)... rtrace = rtt.get(trace_id, {}) crtt = (r.time - s.sent_time) * 1000 rtrace[ttl] = "{crtt:.3f}".format(crtt=crtt) rtt[trace_id] = rtrace else: # # No Responses found - Most likely target same as host running the mtr session... # # Create a 'fake' failed target (Blackhole) trace using the destination host # found in unanswered packets... for p in mtrc._ures[nq]: ips[p.dst] = None trace_id = (p.src, p.dst, p.proto, p.dport) portsdone[trace_id] = None if trace_id not in rt: pt = mtrc.get_proto_name(p.proto) # # Set trace number to zero (0) (i.e., ttl = 0) for this special case: # target = mtr session host - 'fake' failed target... rt[trace_id] = {1: '"{ip:s} {pr:d}/{pt:s}"'.format(ip=p.dst, pr=p.dport, pt=pt)} # # Store each trace component... mtrc._ips.update(ips) # Add unique IP Addresses mtrc._rt.append(rt) # Append a new Traceroute mtrc._ports.update(ports) # Append completed Traceroute target and port info mtrc._portsdone.update(portsdone) # Append completed Traceroute with associated target and port # # Create Round Trip Times Trace lookup dictionary... tcnt = mtrc._tcnt for rttk in rtt: tcnt += 1 trtt[tcnt] = rtt[rttk] mtrc._rtt.update(trtt) # Update Round Trip Times for Trace Nodes # # Update the Target Trace Label IDs and Blackhole (Failed Target) detection... # # rtk0 rtk1 rtk2 rtk3 # Ex: {('10.222.222.10', '10.222.222.1', 6, 9980): {1: '"10.222.222.10":T9980'}} for rtk in rt: mtrc._tcnt += 1 # Compute the total trace count # # Derive flags from ports: # Ex: {'63.117.14.247': ['<T80> http SA', '<T443> https SA']} prtflgs = ports.get(rtk[1], []) found = False for pf in prtflgs: if mtrc._netprotocol == 'ICMP': pat = '<I0>' # ICMP: Create reg exp pattern else: pat = '<[TU]{p:d}>'.format(p=rtk[3]) # TCP/UDP: Create reg exp pattern match = re.search(pat, pf) # Search for port match if match: found = True s = pf.split(' ') if len(s) == 3: pn = s[1] # Service Port name / ICMP fl = s[2] # TCP Flags / ICMP Type / Proto elif len(s) == 2: pn = s[1] # Service Port name fl = '' else: pn = '' fl = '' break ic = '' # ICMP Destination not reachable flag if not found: # Set Blackhole found - (fl -> 'BH') # # Set flag for last hop is a target and ICMP destination not reached flag set... trace = rt[rtk] tk = trace.keys() lh = trace[max(tk)] f = lh.find(':I3') # Is hop an ICMP destination not reached node? if f >= 0: lh = lh[0:f] # Strip off 'proto:port' -> '"100.41.207.244":I3' lh = lh.replace('"', '') # Remove surrounding double quotes ("") if lh in mtrc._exptrg: # Is last hop a target? ic = 'I3' pn = '' fl = 'BH' # # Update the Target Trace Label ID: # Ex: {'63.117.14.247': ('T2', '10.222.222.10', '162.144.22.87', 6, 443, 'https', 'SA', '')} pt = mtrc.get_proto_name(rtk[2]) tlid = {rtk[1]: ('T' + str(mtrc._tcnt), rtk[0], rtk[1], pt, rtk[3], pn, fl, ic)} mtrc._tlblid.append(tlid) #################### # Multi-Traceroute # #################### @conf.commands.register def mtr(target, dport=80, minttl=1, maxttl=30, stype="Random", srcport=50000, iface=None, l4=None, filter=None, timeout=2, verbose=None, gw=None, netproto="TCP", nquery=1, ptype=None, payload=b'', privaddr=0, rasn=1, **kargs): """A Multi-Traceroute (mtr) command: mtr(target, [maxttl=30,] [dport=80,] [sport=80,] [minttl=1,] [maxttl=1,] [iface=None] [l4=None,] [filter=None,] [nquery=1,] [privaddr=0,] [rasn=1,] [verbose=conf.verb]) stype: Source Port Type: "Random" or "Increment". srcport: Source Port. Default: 50000. gw: IPv4 Address of the Default Gateway. netproto: Network Protocol (One of: "TCP", "UDP" or "ICMP"). nquery: Number of Traceroute queries to perform. ptype: Payload Type: "Disable", "RandStr", "RandStrTerm" or "Custom". payload: A byte object for each packet payload (e.g., b'\x01A\x0f\xff\x00') for ptype: 'Custom'. privaddr: 0 - Default: Normal display of all resolved AS numbers. 1 - Do not show an associated AS Number bound box (cluster) on graph for a private IPv4 Address. rasn: 0 - Do not resolve AS Numbers - No graph clustering. 1 - Default: Resolve all AS numbers.""" # # Initialize vars... trace = [] # Individual trace array # # Range check number of query traces if nquery < 1: nquery = 1 # # Create instance of an MTR class... mtrc = MTR(nquery=nquery, target=target) # # Default to network protocol: "TCP" if not found in list... plist = ["TCP", "UDP", "ICMP"] netproto = netproto.upper() if netproto not in plist: netproto = "TCP" mtrc._netprotocol = netproto # # Default to source type: "Random" if not found in list... slist = ["Random", "Increment"] stype = stype.title() if stype not in slist: stype = "Random" if stype == "Random": sport = RandShort() # Random elif stype == "Increment": if srcport != None: sport = IncrementalValue(start=(srcport - 1), step=1, restart=65535) # Increment # # Default to payload type to it's default network protocol value if not found in list... pllist = ["Disabled", "RandStr", "RandStrTerm", "Custom"] if ptype is None or (not ptype in pllist): if netproto == "ICMP": ptype = "RandStr" # ICMP: A random string payload to fill out the minimum packet size elif netproto == "UDP": ptype = "RandStrTerm" # UDP: A random string terminated payload to fill out the minimum packet size elif netproto == "TCP": ptype = "Disabled" # TCP: Disabled -> The minimum packet size satisfied - no payload required # # Set trace interface... if not iface is None: mtrc._iface = iface else: mtrc._iface = conf.iface # # Set Default Gateway... if not gw is None: mtrc._gw = gw # # Set default verbosity if no override... if verbose is None: verbose = conf.verb # # Only consider ICMP error packets and TCP packets with at # least the ACK flag set *and* either the SYN or the RST flag set... filterundefined = False if filter is None: filterundefined = True filter = "(icmp and (icmp[0]=3 or icmp[0]=4 or icmp[0]=5 or icmp[0]=11 or icmp[0]=12)) or (tcp and (tcp[13] & 0x16 > 0x10))" # # Resolve and expand each target... ntraces = 0 # Total trace count exptrg = [] # Expanded targets for t in target: # # Use kamene's 'Net' function to expand target... et = [ip for ip in iter(Net(t))] exptrg.extend(et) # # Map Host Names to IP Addresses and store... if t in mtrc._host2ip: mtrc._host2ip[t].extend(et) else: mtrc._host2ip[t] = et # # Map IP Addresses to Host Names and store... for a in et: mtrc._ip2host[a] = t # # Store resolved and expanded targets... mtrc._exptrg = exptrg # # Traceroute each expanded target value... if l4 is None: # # Standard Layer: 3 ('TCP', 'UDP' or 'ICMP') tracing... for n in range(0, nquery): for t in exptrg: # # Execute a traceroute based on network protocol setting... if netproto == "ICMP": # # MTR Network Protocol: 'ICMP' tid = 8 # Use a 'Type: 8 - Echo Request' packet for the trace: id = 0x8888 # MTR ICMP identifier: '0x8888' seq = IncrementalValue(start=(minttl - 2), step=1, restart=-10) # Use a Sequence number in step with TTL value if filterundefined: # # Update Filter -> Allow for ICMP echo-request (8) and ICMP echo-reply (0) packet to be processed... filter = "(icmp and (icmp[0]=8 or icmp[0]=0 or icmp[0]=3 or icmp[0]=4 or icmp[0]=5 or icmp[0]=11 or icmp[0]=12))" # # Check payload types: if ptype == 'Disabled': a, b = sr(IP(dst=[t], id=RandShort(), ttl=(minttl, maxttl)) / ICMP(type=tid, id=id, seq=seq), timeout=timeout, filter=filter, verbose=verbose, **kargs) else: if ptype == 'RandStr': # # Use a random payload string to full out a minimum size PDU of 46 bytes for each ICMP packet: # Length of 'IP()/ICMP()' = 28, Minimum Protocol Data Unit (PDU) is = 46 -> Therefore a # payload of 18 octets is required. pload = RandString(size=18) elif ptype == 'RandStrTerm': pload = RandStringTerm(size=17, term=b'\n') # Random string terminated elif ptype == 'Custom': pload = payload # # ICMP trace with payload... a, b = sr(IP(dst=[t], id=RandShort(), ttl=(minttl, maxttl)) / ICMP(type=tid, id=id, seq=seq) / Raw(load=pload), timeout=timeout, filter=filter, verbose=verbose, **kargs) elif netproto == "UDP": # # MTR Network Protocol: 'UDP' if filterundefined: filter += " or udp" # Update Filter -> Allow for processing UDP packets # # Check payload types: if ptype == 'Disabled': a, b = sr(IP(dst=[t], id=RandShort(), ttl=(minttl, maxttl)) / UDP(sport=sport, dport=dport), timeout=timeout, filter=filter, verbose=verbose, **kargs) else: if ptype == 'RandStr': # # Use a random payload string to full out a minimum size PDU of 46 bytes for each UDP packet: # Length of 'IP()/UDP()' = 28, Minimum PDU is = 46 -> Therefore a payload of 18 octets is required. pload = RandString(size=18) elif ptype == 'RandStrTerm': pload = RandStringTerm(size=17, term=b'\n') # Random string terminated elif ptype == 'Custom': pload = payload # # UDP trace with payload... a, b = sr(IP(dst=[t], id=RandShort(), ttl=(minttl, maxttl)) / UDP(sport=sport, dport=dport) / Raw(load=pload), timeout=timeout, filter=filter, verbose=verbose, **kargs) else: # # Default MTR Network Protocol: 'TCP' # # Use some TCP options for the trace. Some firewalls will filter # TCP/IP packets without the 'Timestamp' option set. # # Note: The minimum PDU size of 46 is statisfied with the use of TCP options. # # Use an integer encoded microsecond timestamp for the TCP option timestamp for each trace sequence. uts = IntAutoMicroTime() opts = [('MSS', 1460), ('NOP', None), ('NOP', None), ('Timestamp', (uts, 0)), ('NOP', None), ('WScale', 7)] seq = RandInt() # Use a random TCP sequence number # # Check payload types: if ptype == 'Disabled': a, b = sr(IP(dst=[t], id=RandShort(), ttl=(minttl, maxttl)) / TCP(seq=seq, sport=sport, dport=dport, options=opts), timeout=timeout, filter=filter, verbose=verbose, **kargs) else: if ptype == 'RandStr': pload = RandString(size=32) # Use a 32 byte random string elif ptype == 'RandStrTerm': pload = RandStringTerm(size=32, term=b'\n') # Use a 32 byte random string terminated elif ptype == 'Custom': pload = payload # # TCP trace with payload... a, b = sr(IP(dst=[t], id=RandShort(), ttl=(minttl, maxttl)) / TCP(seq=seq, sport=sport, dport=dport, options=opts) / Raw(load=pload), timeout=timeout, filter=filter, verbose=verbose, **kargs) # # Create an 'MTracerouteResult' instance for each result packets... trace.append(MTracerouteResult(res=a.res)) mtrc._res.append(a) # Store Response packets mtrc._ures.append(b) # Store Unresponse packets if verbose: trace[ntraces].show(ntrace=(ntraces + 1)) print() ntraces += 1 else: # # Custom Layer: 4 tracing... filter = "ip" for n in range(0, nquery): for t in exptrg: # # Run traceroute... a, b = sr(IP(dst=[t], id=RandShort(), ttl=(minttl, maxttl)) / l4, timeout=timeout, filter=filter, verbose=verbose, **kargs) trace.append(MTracerouteResult(res=a.res)) mtrc._res.append(a) mtrc._ures.append(b) if verbose: trace[ntraces].show(ntrace=(ntraces + 1)) print() ntraces += 1 # # Store total trace run count... mtrc._ntraces = ntraces # # Get the trace components... # for n in range(0, ntraces): for n in range(0, mtrc._ntraces): trace[n].get_trace_components(mtrc, n) # # Compute any Black Holes... mtrc.get_black_holes() # # Compute Trace Hop Ranges... mtrc.compute_hop_ranges() # # Resolve AS Numbers... if rasn: mtrc.get_asns(privaddr) # # Try to guess ASNs for Traceroute 'Unkown Hops'... mtrc.guess_unk_asns() # # Debug: Print object vars at verbose level 8... if verbose == 8: print("mtrc._target (User Target(s)):") print("=======================================================") print(mtrc._target) print("\nmtrc._exptrg (Resolved and Expanded Target(s)):") print("=======================================================") print(mtrc._exptrg) print("\nmtrc._host2ip (Target Host Name to IP Address):") print("=======================================================") print(mtrc._host2ip) print("\nmtrc._ip2host (Target IP Address to Host Name):") print("=======================================================") print(mtrc._ip2host) print("\nmtrc._res (Trace Response Packets):") print("=======================================================") print(mtrc._res) print("\nmtrc._ures (Trace Unresponse Packets):") print("=======================================================") print(mtrc._ures) print("\nmtrc._ips (Trace Unique IPv4 Addresses):") print("=======================================================") print(mtrc._ips) print("\nmtrc._rt (Individual Route Traces):") print("=======================================================") print(mtrc._rt) print("\nmtrc._rtt (Round Trip Times (msecs) for Trace Nodes):") print("=======================================================") print(mtrc._rtt) print("\nmtrc._hops (Traceroute Hop Ranges):") print("=======================================================") print(mtrc._hops) print("\nmtrc._tlblid (Target Trace Label IDs):") print("=======================================================") print(mtrc._tlblid) print("\nmtrc._ports (Completed Targets & Ports):") print("=======================================================") print(mtrc._ports) print("\nmtrc._portsdone (Completed Trace Routes & Ports):") print("=======================================================") print(mtrc._portsdone) print("\nconf.L3socket (Layer 3 Socket Method):") print("=======================================================") print(conf.L3socket) print("\nconf.AS_resolver Resolver (AS Resolver Method):") print("=======================================================") print(conf.AS_resolver) print("\nmtrc._asns (AS Numbers):") print("=======================================================") print(mtrc._asns) print("\nmtrc._asds (AS Descriptions):") print("=======================================================") print(mtrc._asds) print("\nmtrc._unks (Unknown Hops IP Boundary for AS Numbers):") print("=======================================================") print(mtrc._unks) print("\nmtrc._iface (Trace Interface):") print("=======================================================") print(mtrc._iface) print("\nmtrc._gw (Trace Default Gateway IPv4 Address):") print("=======================================================") print(mtrc._gw) return mtrc ########################### # Simple TCP client stack # ########################### class TCP_client(Automaton): def parse_args(self, ip, port, *args, **kargs): self.dst = next(iter(Net(ip))) self.dport = port self.sport = random.randrange(0, 2**16) self.l4 = IP(dst=ip) / TCP(sport=self.sport, dport=self.dport, flags=0, seq=random.randrange(0, 2**32)) self.src = self.l4.src self.swin = self.l4[TCP].window self.dwin = 1 self.rcvbuf = "" bpf = "host %s and host %s and port %i and port %i" % (self.src, self.dst, self.sport, self.dport) # bpf=None Automaton.parse_args(self, filter=bpf, **kargs) def master_filter(self, pkt): return (IP in pkt and pkt[IP].src == self.dst and pkt[IP].dst == self.src and TCP in pkt and pkt[TCP].sport == self.dport and pkt[TCP].dport == self.sport and self.l4[TCP].seq >= pkt[TCP].ack and # XXX: seq/ack 2^32 wrap up ((self.l4[TCP].ack == 0) or (self.l4[TCP].ack <= pkt[TCP].seq <= self.l4[TCP].ack + self.swin))) @ATMT.state(initial=1) def START(self): pass @ATMT.state() def SYN_SENT(self): pass @ATMT.state() def ESTABLISHED(self): pass @ATMT.state() def LAST_ACK(self): pass @ATMT.state(final=1) def CLOSED(self): pass @ATMT.condition(START) def connect(self): raise self.SYN_SENT() @ATMT.action(connect) def send_syn(self): self.l4[TCP].flags = "S" self.send(self.l4) self.l4[TCP].seq += 1 @ATMT.receive_condition(SYN_SENT) def synack_received(self, pkt): if pkt[TCP].flags & 0x3f == 0x12: raise self.ESTABLISHED().action_parameters(pkt) @ATMT.action(synack_received) def send_ack_of_synack(self, pkt): self.l4[TCP].ack = pkt[TCP].seq + 1 self.l4[TCP].flags = "A" self.send(self.l4) @ATMT.receive_condition(ESTABLISHED) def incoming_data_received(self, pkt): if not isinstance(pkt[TCP].payload, NoPayload) and not isinstance(pkt[TCP].payload, conf.padding_layer): raise self.ESTABLISHED().action_parameters(pkt) @ATMT.action(incoming_data_received) def receive_data(self, pkt): data = (bytes(pkt[TCP].payload)) if data and self.l4[TCP].ack == pkt[TCP].seq: self.l4[TCP].ack += len(data) self.l4[TCP].flags = "A" self.send(self.l4) self.rcvbuf += data if pkt[TCP].flags & 8 != 0: # PUSH self.oi.tcp.send(self.rcvbuf) self.rcvbuf = "" @ATMT.ioevent(ESTABLISHED, name="tcp", as_supersocket="tcplink") def outgoing_data_received(self, fd): raise self.ESTABLISHED().action_parameters(fd.recv()) @ATMT.action(outgoing_data_received) def send_data(self, d): self.l4[TCP].flags = "PA" self.send(self.l4 / d) self.l4[TCP].seq += len(d) @ATMT.receive_condition(ESTABLISHED) def reset_received(self, pkt): if pkt[TCP].flags & 4 != 0: raise self.CLOSED() @ATMT.receive_condition(ESTABLISHED) def fin_received(self, pkt): if pkt[TCP].flags & 0x1 == 1: raise self.LAST_ACK().action_parameters(pkt) @ATMT.action(fin_received) def send_finack(self, pkt): self.l4[TCP].flags = "FA" self.l4[TCP].ack = pkt[TCP].seq + 1 self.send(self.l4) self.l4[TCP].seq += 1 @ATMT.receive_condition(LAST_ACK) def ack_of_fin_received(self, pkt): if pkt[TCP].flags & 0x3f == 0x10: raise self.CLOSED() ################### # Reporting stuff # ################### def report_ports(target, ports): """portscan a target and output a LaTeX table report_ports(target, ports) -> string""" ans, unans = sr(IP(dst=target) / TCP(dport=ports), timeout=5) rep = "\\begin{tabular}{|r|l|l|}\n\\hline\n" for _, r in ans: if not r.haslayer(ICMP): if r.payload.flags == 0x12: rep += r.sprintf("%TCP.sport% & open & SA \\\\\n") rep += "\\hline\n" for _, r in ans: if r.haslayer(ICMP): rep += r.sprintf("%TCPerror.dport% & closed & ICMP type %ICMP.type%/%ICMP.code% from %IP.src% \\\\\n") elif r.payload.flags != 0x12: rep += r.sprintf("%TCP.sport% & closed & TCP %TCP.flags% \\\\\n") rep += "\\hline\n" for i in unans: rep += i.sprintf("%TCP.dport% & ? & unanswered \\\\\n") rep += "\\hline\n\\end{tabular}\n" return rep def IPID_count(lst, funcID=lambda x: x[1].id, funcpres=lambda x: x[1].summary()): idlst = map(funcID, lst) idlst.sort() # classes = [idlst[0]]+map(lambda x:x[1],filter(lambda (x,y): abs(x-y)>50, map(lambda x,y: (x,y),idlst[:-1], idlst[1:]))) classes = [idlst[0]] + list(map(lambda x: x[1], filter(lambda a: abs(a[0] - a[1]) > 50, map(lambda x, y: (x, y), idlst[:-1], idlst[1:])))) lst = map(lambda x: (funcID(x), funcpres(x)), lst) lst.sort() print("Probably %i classes:" % len(classes), classes) for id, pr in lst: print("%5i" % id, pr) def fragleak(target, sport=123, dport=123, timeout=0.2, onlyasc=0): load = "XXXXYYYYYYYYYY" # getmacbyip(target) # pkt = IP(dst=target, id=RandShort(), options="\x22"*40)/UDP()/load pkt = IP(dst=target, id=RandShort(), options="\x00" * 40, flags=1) / UDP(sport=sport, dport=sport) / load s = conf.L3socket() intr = 0 found = {} try: while 1: try: if not intr: s.send(pkt) sin, _, _ = select([s], [], [], timeout) if not sin: continue ans = s.recv(1600) if not isinstance(ans, IP): # TODO: IPv6 continue if not isinstance(ans.payload, ICMP): continue if not isinstance(ans.payload.payload, IPerror): continue if ans.payload.payload.dst != target: continue if ans.src != target: print("leak from", ans.src, end=" ") # print repr(ans) if not ans.haslayer(conf.padding_layer): continue # print repr(ans.payload.payload.payload.payload) # if not isinstance(ans.payload.payload.payload.payload, conf.raw_layer): # continue # leak = ans.payload.payload.payload.payload.load[len(load):] leak = ans.getlayer(conf.padding_layer).load if leak not in found: found[leak] = None linehexdump(leak, onlyasc=onlyasc) except KeyboardInterrupt: if intr: raise intr = 1 except KeyboardInterrupt: pass def fragleak2(target, timeout=0.4, onlyasc=0): found = {} try: while 1: p = sr1(IP(dst=target, options="\x00" * 40, proto=200) / "XXXXYYYYYYYYYYYY", timeout=timeout, verbose=0) if not p: continue if conf.padding_layer in p: leak = p[conf.padding_layer].load if leak not in found: found[leak] = None linehexdump(leak, onlyasc=onlyasc) except: pass conf.stats_classic_protocols += [TCP, UDP, ICMP] conf.stats_dot11_protocols += [TCP, UDP, ICMP] if conf.ipv6_enabled: import kamene.layers.inet6