## 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,time,struct,re,socket,types
from select import select
from collections import defaultdict
from scapy.utils import checksum,is_private_addr
from scapy.layers.l2 import *
from scapy.config import conf
from scapy.fields import *
from scapy.packet import *
from scapy.volatile import *
from scapy.sendrecv import sr,sr1,srp1
from scapy.plist import PacketList,SndRcvList
from scapy.automaton import Automaton,ATMT

import scapy.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 str:
                    warning("option [%i] is not string."%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, 4),
                    FlagsField("flags", 0x2, 8, "FSRPAUEC"),
                    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, scapy.layers.inet6.IPv6) or isinstance(self.underlayer, scapy.layers.inet6._IPv6ExtHdr):
                ck = scapy.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, scapy.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, scapy.layers.inet6.IPv6) or isinstance(self.underlayer, scapy.layers.inet6._IPv6ExtHdr):
                ck = scapy.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, scapy.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):
    fields_desc = [ ShortEnumField("sport", 20, TCP_SERVICES),
                    ShortEnumField("dport", 80, TCP_SERVICES),
                    IntField("seq", 0) ]
    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,b=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[scapy.layers.inet6.IPv6]) or r
            s = s.getlayer(IP) or (conf.ipv6_enabled and s[scapy.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 scapy.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 scapy.layers.inet6.IPv6 in r and scapy.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 snd,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
        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 = "### Scapy3k 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} &rarr; {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="green";\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 = self._ip2host[eph], gwl = 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 = 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 not uip 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} &rarr; '.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 = 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="lightgreen: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="lightgreen: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 not bhh 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 snd,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">&nbsp; {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">&nbsp; {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">&nbsp; {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">&nbsp; {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">&nbsp; 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">&nbsp; {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">&nbsp; 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">&nbsp; {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">&nbsp; {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[scapy.layers.inet6.IPv6]) or s
                r = r.getlayer(IP) or (conf.ipv6_enabled and r[scapy.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 scapy.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 scapy.layers.inet6.IPv6 in r and scapy.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 not netproto in plist:
        netproto = "TCP"
    mtrc._netprotocol = netproto
    #
    # Default to source type: "Random" if not found in list...
    slist = ["Random", "Increment"]
    stype = stype.title()
    if not stype 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 scapy'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 s,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 s,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,sout,serr = 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 scapy.layers.inet6
