1## This file is part of Scapy
2## See http://www.secdev.org/projects/scapy for more informations
3## Copyright (C) Philippe Biondi <phil@secdev.org>
4## This program is published under a GPLv2 license
5
6"""
7IPv4 (Internet Protocol v4).
8"""
9
10from __future__ import absolute_import
11from __future__ import print_function
12import os, time, struct, re, socket, types
13from select import select
14from collections import defaultdict
15
16from scapy.utils import checksum,inet_aton,inet_ntoa
17from scapy.base_classes import Gen
18from scapy.data import *
19from scapy.layers.l2 import *
20from scapy.compat import *
21from scapy.config import conf
22from scapy.consts import WINDOWS
23from scapy.fields import *
24from scapy.packet import *
25from scapy.volatile import *
26from scapy.sendrecv import sr,sr1,srp1
27from scapy.plist import PacketList,SndRcvList
28from scapy.automaton import Automaton,ATMT
29from scapy.error import warning
30from scapy.utils import whois
31
32import scapy.as_resolvers
33
34from scapy.arch import plt, MATPLOTLIB_INLINED, MATPLOTLIB_DEFAULT_PLOT_KARGS
35import scapy.modules.six as six
36from scapy.modules.six.moves import range
37
38####################
39## IP Tools class ##
40####################
41
42class IPTools(object):
43    """Add more powers to a class with an "src" attribute."""
44    __slots__ = []
45    def whois(self):
46        """whois the source and print the output"""
47        if WINDOWS:
48            print(whois(self.src))
49        else:
50            os.system("whois %s" % self.src)
51    def _ttl(self):
52        """Returns ttl or hlim, depending on the IP version"""
53        return self.hlim if isinstance(self, scapy.layers.inet6.IPv6) else self.ttl
54    def ottl(self):
55        t = sorted([32,64,128,255]+[self._ttl()])
56        return t[t.index(self._ttl())+1]
57    def hops(self):
58        return self.ottl() - self._ttl()
59
60
61_ip_options_names = { 0: "end_of_list",
62                      1: "nop",
63                      2: "security",
64                      3: "loose_source_route",
65                      4: "timestamp",
66                      5: "extended_security",
67                      6: "commercial_security",
68                      7: "record_route",
69                      8: "stream_id",
70                      9: "strict_source_route",
71                      10: "experimental_measurement",
72                      11: "mtu_probe",
73                      12: "mtu_reply",
74                      13: "flow_control",
75                      14: "access_control",
76                      15: "encode",
77                      16: "imi_traffic_descriptor",
78                      17: "extended_IP",
79                      18: "traceroute",
80                      19: "address_extension",
81                      20: "router_alert",
82                      21: "selective_directed_broadcast_mode",
83                      23: "dynamic_packet_state",
84                      24: "upstream_multicast_packet",
85                      25: "quick_start",
86                      30: "rfc4727_experiment",
87                      }
88
89
90class _IPOption_HDR(Packet):
91    fields_desc = [ BitField("copy_flag",0, 1),
92                    BitEnumField("optclass",0,2,{0:"control",2:"debug"}),
93                    BitEnumField("option",0,5, _ip_options_names) ]
94
95class IPOption(Packet):
96    name = "IP Option"
97    fields_desc = [ _IPOption_HDR,
98                    FieldLenField("length", None, fmt="B",  # Only option 0 and 1 have no length and value
99                                  length_of="value", adjust=lambda pkt,l:l+2),
100                    StrLenField("value", "",length_from=lambda pkt:pkt.length-2) ]
101
102    def extract_padding(self, p):
103        return b"",p
104
105    registered_ip_options = {}
106    @classmethod
107    def register_variant(cls):
108        cls.registered_ip_options[cls.option.default] = cls
109    @classmethod
110    def dispatch_hook(cls, pkt=None, *args, **kargs):
111        if pkt:
112            opt = orb(pkt[0])&0x1f
113            if opt in cls.registered_ip_options:
114                return cls.registered_ip_options[opt]
115        return cls
116
117class IPOption_EOL(IPOption):
118    name = "IP Option End of Options List"
119    option = 0
120    fields_desc = [ _IPOption_HDR ]
121
122
123class IPOption_NOP(IPOption):
124    name = "IP Option No Operation"
125    option=1
126    fields_desc = [ _IPOption_HDR ]
127
128class IPOption_Security(IPOption):
129    name = "IP Option Security"
130    copy_flag = 1
131    option = 2
132    fields_desc = [ _IPOption_HDR,
133                    ByteField("length", 11),
134                    ShortField("security",0),
135                    ShortField("compartment",0),
136                    ShortField("handling_restrictions",0),
137                    StrFixedLenField("transmission_control_code","xxx",3),
138                    ]
139
140class IPOption_RR(IPOption):
141    name = "IP Option Record Route"
142    option = 7
143    fields_desc = [ _IPOption_HDR,
144                    FieldLenField("length", None, fmt="B",
145                                  length_of="routers", adjust=lambda pkt,l:l+3),
146                    ByteField("pointer",4), # 4 is first IP
147                    FieldListField("routers",[],IPField("","0.0.0.0"),
148                                   length_from=lambda pkt:pkt.length-3)
149                    ]
150    def get_current_router(self):
151        return self.routers[self.pointer//4-1]
152
153class IPOption_LSRR(IPOption_RR):
154    name = "IP Option Loose Source and Record Route"
155    copy_flag = 1
156    option = 3
157
158class IPOption_SSRR(IPOption_RR):
159    name = "IP Option Strict Source and Record Route"
160    copy_flag = 1
161    option = 9
162
163class IPOption_Stream_Id(IPOption):
164    name = "IP Option Stream ID"
165    copy_flag = 1
166    option = 8
167    fields_desc = [ _IPOption_HDR,
168                    ByteField("length", 4),
169                    ShortField("security",0), ]
170
171class IPOption_MTU_Probe(IPOption):
172    name = "IP Option MTU Probe"
173    option = 11
174    fields_desc = [ _IPOption_HDR,
175                    ByteField("length", 4),
176                    ShortField("mtu",0), ]
177
178class IPOption_MTU_Reply(IPOption_MTU_Probe):
179    name = "IP Option MTU Reply"
180    option = 12
181
182class IPOption_Traceroute(IPOption):
183    name = "IP Option Traceroute"
184    option = 18
185    fields_desc = [ _IPOption_HDR,
186                    ByteField("length", 12),
187                    ShortField("id",0),
188                    ShortField("outbound_hops",0),
189                    ShortField("return_hops",0),
190                    IPField("originator_ip","0.0.0.0") ]
191
192class IPOption_Address_Extension(IPOption):
193    name = "IP Option Address Extension"
194    copy_flag = 1
195    option = 19
196    fields_desc = [ _IPOption_HDR,
197                    ByteField("length", 10),
198                    IPField("src_ext","0.0.0.0"),
199                    IPField("dst_ext","0.0.0.0") ]
200
201class IPOption_Router_Alert(IPOption):
202    name = "IP Option Router Alert"
203    copy_flag = 1
204    option = 20
205    fields_desc = [ _IPOption_HDR,
206                    ByteField("length", 4),
207                    ShortEnumField("alert",0, {0:"router_shall_examine_packet"}), ]
208
209
210class IPOption_SDBM(IPOption):
211    name = "IP Option Selective Directed Broadcast Mode"
212    copy_flag = 1
213    option = 21
214    fields_desc = [ _IPOption_HDR,
215                    FieldLenField("length", None, fmt="B",
216                                  length_of="addresses", adjust=lambda pkt,l:l+2),
217                    FieldListField("addresses",[],IPField("","0.0.0.0"),
218                                   length_from=lambda pkt:pkt.length-2)
219                    ]
220
221
222
223TCPOptions = (
224              { 0 : ("EOL",None),
225                1 : ("NOP",None),
226                2 : ("MSS","!H"),
227                3 : ("WScale","!B"),
228                4 : ("SAckOK",None),
229                5 : ("SAck","!"),
230                8 : ("Timestamp","!II"),
231                14 : ("AltChkSum","!BH"),
232                15 : ("AltChkSumOpt",None),
233                25 : ("Mood","!p"),
234                28 : ("UTO", "!H"),
235                34 : ("TFO", "!II"),
236                # RFC 3692
237                253 : ("Experiment","!HHHH"),
238                254 : ("Experiment","!HHHH"),
239                },
240              { "EOL":0,
241                "NOP":1,
242                "MSS":2,
243                "WScale":3,
244                "SAckOK":4,
245                "SAck":5,
246                "Timestamp":8,
247                "AltChkSum":14,
248                "AltChkSumOpt":15,
249                "Mood":25,
250                "UTO":28,
251                "TFO":34,
252                } )
253
254class TCPOptionsField(StrField):
255    islist=1
256    def getfield(self, pkt, s):
257        opsz = (pkt.dataofs-5)*4
258        if opsz < 0:
259            warning("bad dataofs (%i). Assuming dataofs=5"%pkt.dataofs)
260            opsz = 0
261        return s[opsz:],self.m2i(pkt,s[:opsz])
262    def m2i(self, pkt, x):
263        opt = []
264        while x:
265            onum = orb(x[0])
266            if onum == 0:
267                opt.append(("EOL",None))
268                x=x[1:]
269                break
270            if onum == 1:
271                opt.append(("NOP",None))
272                x=x[1:]
273                continue
274            olen = orb(x[1])
275            if olen < 2:
276                warning("Malformed TCP option (announced length is %i)" % olen)
277                olen = 2
278            oval = x[2:olen]
279            if onum in TCPOptions[0]:
280                oname, ofmt = TCPOptions[0][onum]
281                if onum == 5: #SAck
282                    ofmt += "%iI" % (len(oval)//4)
283                if ofmt and struct.calcsize(ofmt) == len(oval):
284                    oval = struct.unpack(ofmt, oval)
285                    if len(oval) == 1:
286                        oval = oval[0]
287                opt.append((oname, oval))
288            else:
289                opt.append((onum, oval))
290            x = x[olen:]
291        return opt
292
293    def i2m(self, pkt, x):
294        opt = b""
295        for oname, oval in x:
296            if isinstance(oname, str):
297                if oname == "NOP":
298                    opt += b"\x01"
299                    continue
300                elif oname == "EOL":
301                    opt += b"\x00"
302                    continue
303                elif oname in TCPOptions[1]:
304                    onum = TCPOptions[1][oname]
305                    ofmt = TCPOptions[0][onum][1]
306                    if onum == 5: #SAck
307                        ofmt += "%iI" % len(oval)
308                    if ofmt is not None and (not isinstance(oval, str) or "s" in ofmt):
309                        if not isinstance(oval, tuple):
310                            oval = (oval,)
311                        oval = struct.pack(ofmt, *oval)
312                else:
313                    warning("option [%s] unknown. Skipped.", oname)
314                    continue
315            else:
316                onum = oname
317                if not isinstance(oval, str):
318                    warning("option [%i] is not string."%onum)
319                    continue
320            opt += chb(onum) + chb(2+len(oval))+ raw(oval)
321        return opt+b"\x00"*(3-((len(opt)+3)%4))
322    def randval(self):
323        return [] # XXX
324
325
326class ICMPTimeStampField(IntField):
327    re_hmsm = re.compile("([0-2]?[0-9])[Hh:](([0-5]?[0-9])([Mm:]([0-5]?[0-9])([sS:.]([0-9]{0,3}))?)?)?$")
328    def i2repr(self, pkt, val):
329        if val is None:
330            return "--"
331        else:
332            sec, milli = divmod(val, 1000)
333            min, sec = divmod(sec, 60)
334            hour, min = divmod(min, 60)
335            return "%d:%d:%d.%d" %(hour, min, sec, int(milli))
336    def any2i(self, pkt, val):
337        if isinstance(val, str):
338            hmsms = self.re_hmsm.match(val)
339            if hmsms:
340                h,_,m,_,s,_,ms = hmsms = hmsms.groups()
341                ms = int(((ms or "")+"000")[:3])
342                val = ((int(h)*60+int(m or 0))*60+int(s or 0))*1000+ms
343            else:
344                val = 0
345        elif val is None:
346            val = int((time.time()%(24*60*60))*1000)
347        return val
348
349
350class DestIPField(IPField, DestField):
351    bindings = {}
352    def __init__(self, name, default):
353        IPField.__init__(self, name, None)
354        DestField.__init__(self, name, default)
355    def i2m(self, pkt, x):
356        if x is None:
357            x = self.dst_from_pkt(pkt)
358        return IPField.i2m(self, pkt, x)
359    def i2h(self, pkt, x):
360        if x is None:
361            x = self.dst_from_pkt(pkt)
362        return IPField.i2h(self, pkt, x)
363
364
365class IP(Packet, IPTools):
366    __slots__ = ["_defrag_pos"]
367    name = "IP"
368    fields_desc = [ BitField("version" , 4 , 4),
369                    BitField("ihl", None, 4),
370                    XByteField("tos", 0),
371                    ShortField("len", None),
372                    ShortField("id", 1),
373                    FlagsField("flags", 0, 3, ["MF","DF","evil"]),
374                    BitField("frag", 0, 13),
375                    ByteField("ttl", 64),
376                    ByteEnumField("proto", 0, IP_PROTOS),
377                    XShortField("chksum", None),
378                    #IPField("src", "127.0.0.1"),
379                    Emph(SourceIPField("src","dst")),
380                    Emph(DestIPField("dst", "127.0.0.1")),
381                    PacketListField("options", [], IPOption, length_from=lambda p:p.ihl*4-20) ]
382    def post_build(self, p, pay):
383        ihl = self.ihl
384        p += b"\0"*((-len(p))%4) # pad IP options if needed
385        if ihl is None:
386            ihl = len(p)//4
387            p = chb(((self.version&0xf)<<4) | ihl&0x0f)+p[1:]
388        if self.len is None:
389            l = len(p)+len(pay)
390            p = p[:2]+struct.pack("!H", l)+p[4:]
391        if self.chksum is None:
392            ck = checksum(p)
393            p = p[:10]+chb(ck>>8)+chb(ck&0xff)+p[12:]
394        return p+pay
395
396    def extract_padding(self, s):
397        l = self.len - (self.ihl << 2)
398        return s[:l],s[l:]
399
400    def route(self):
401        dst = self.dst
402        if isinstance(dst, Gen):
403            dst = next(iter(dst))
404        if conf.route is None:
405            # unused import, only to initialize conf.route
406            import scapy.route
407        return conf.route.route(dst)
408    def hashret(self):
409        if ( (self.proto == socket.IPPROTO_ICMP)
410             and (isinstance(self.payload, ICMP))
411             and (self.payload.type in [3,4,5,11,12]) ):
412            return self.payload.payload.hashret()
413        if not conf.checkIPinIP and self.proto in [4, 41]:  # IP, IPv6
414            return self.payload.hashret()
415        if self.dst == "224.0.0.251":  # mDNS
416            return struct.pack("B", self.proto) + self.payload.hashret()
417        if conf.checkIPsrc and conf.checkIPaddr:
418            return (strxor(inet_aton(self.src), inet_aton(self.dst))
419                    + struct.pack("B",self.proto) + self.payload.hashret())
420        return struct.pack("B", self.proto) + self.payload.hashret()
421    def answers(self, other):
422        if not conf.checkIPinIP:  # skip IP in IP and IPv6 in IP
423            if self.proto in [4, 41]:
424                return self.payload.answers(other)
425            if isinstance(other, IP) and other.proto in [4, 41]:
426                return self.answers(other.payload)
427            if conf.ipv6_enabled \
428               and isinstance(other, scapy.layers.inet6.IPv6) \
429               and other.nh in [4, 41]:
430                return self.answers(other.payload)
431        if not isinstance(other,IP):
432            return 0
433        if conf.checkIPaddr:
434            if other.dst == "224.0.0.251" and self.dst == "224.0.0.251":  # mDNS
435                return self.payload.answers(other.payload)
436            elif (self.dst != other.src):
437                return 0
438        if ( (self.proto == socket.IPPROTO_ICMP) and
439             (isinstance(self.payload, ICMP)) and
440             (self.payload.type in [3,4,5,11,12]) ):
441            # ICMP error message
442            return self.payload.payload.answers(other)
443
444        else:
445            if ( (conf.checkIPaddr and (self.src != other.dst)) or
446                 (self.proto != other.proto) ):
447                return 0
448            return self.payload.answers(other.payload)
449    def mysummary(self):
450        s = self.sprintf("%IP.src% > %IP.dst% %IP.proto%")
451        if self.frag:
452            s += " frag:%i" % self.frag
453        return s
454
455    def fragment(self, fragsize=1480):
456        """Fragment IP datagrams"""
457        fragsize = (fragsize+7)//8*8
458        lst = []
459        fnb = 0
460        fl = self
461        while fl.underlayer is not None:
462            fnb += 1
463            fl = fl.underlayer
464
465        for p in fl:
466            s = raw(p[fnb].payload)
467            nb = (len(s)+fragsize-1)//fragsize
468            for i in range(nb):
469                q = p.copy()
470                del(q[fnb].payload)
471                del(q[fnb].chksum)
472                del(q[fnb].len)
473                if i != nb - 1:
474                    q[fnb].flags |= 1
475                q[fnb].frag += i * fragsize // 8
476                r = conf.raw_layer(load=s[i*fragsize:(i+1)*fragsize])
477                r.overload_fields = p[fnb].payload.overload_fields.copy()
478                q.add_payload(r)
479                lst.append(q)
480        return lst
481
482def in4_chksum(proto, u, p):
483    """
484    As Specified in RFC 2460 - 8.1 Upper-Layer Checksums
485
486    Performs IPv4 Upper Layer checksum computation. Provided parameters are:
487    - 'proto' : value of upper layer protocol
488    - 'u'  : IP upper layer instance
489    - 'p'  : the payload of the upper layer provided as a string
490    """
491    if not isinstance(u, IP):
492        warning("No IP underlayer to compute checksum. Leaving null.")
493        return 0
494    if u.len is not None:
495        if u.ihl is None:
496            olen = sum(len(x) for x in u.options)
497            ihl = 5 + olen // 4 + (1 if olen % 4 else 0)
498        else:
499            ihl = u.ihl
500        ln = u.len - 4 * ihl
501    else:
502        ln = len(p)
503    psdhdr = struct.pack("!4s4sHH",
504                         inet_aton(u.src),
505                         inet_aton(u.dst),
506                         proto,
507                         ln)
508    return checksum(psdhdr+p)
509
510class TCP(Packet):
511    name = "TCP"
512    fields_desc = [ ShortEnumField("sport", 20, TCP_SERVICES),
513                    ShortEnumField("dport", 80, TCP_SERVICES),
514                    IntField("seq", 0),
515                    IntField("ack", 0),
516                    BitField("dataofs", None, 4),
517                    BitField("reserved", 0, 3),
518                    FlagsField("flags", 0x2, 9, "FSRPAUECN"),
519                    ShortField("window", 8192),
520                    XShortField("chksum", None),
521                    ShortField("urgptr", 0),
522                    TCPOptionsField("options", []) ]
523    def post_build(self, p, pay):
524        p += pay
525        dataofs = self.dataofs
526        if dataofs is None:
527            dataofs = 5+((len(self.get_field("options").i2m(self,self.options))+3)//4)
528            p = p[:12]+chb((dataofs << 4) | orb(p[12])&0x0f)+p[13:]
529        if self.chksum is None:
530            if isinstance(self.underlayer, IP):
531                ck = in4_chksum(socket.IPPROTO_TCP, self.underlayer, p)
532                p = p[:16]+struct.pack("!H", ck)+p[18:]
533            elif conf.ipv6_enabled and isinstance(self.underlayer, scapy.layers.inet6.IPv6) or isinstance(self.underlayer, scapy.layers.inet6._IPv6ExtHdr):
534                ck = scapy.layers.inet6.in6_chksum(socket.IPPROTO_TCP, self.underlayer, p)
535                p = p[:16]+struct.pack("!H", ck)+p[18:]
536            else:
537                warning("No IP underlayer to compute checksum. Leaving null.")
538        return p
539    def hashret(self):
540        if conf.checkIPsrc:
541            return struct.pack("H",self.sport ^ self.dport)+self.payload.hashret()
542        else:
543            return self.payload.hashret()
544    def answers(self, other):
545        if not isinstance(other, TCP):
546            return 0
547        # RST packets don't get answers
548        if other.flags.R:
549            return 0
550        # We do not support the four-way handshakes with the SYN+ACK
551        # answer split in two packets (one ACK and one SYN): in that
552        # case the ACK will be seen as an answer, but not the SYN.
553        if self.flags.S:
554            # SYN packets without ACK are not answers
555            if not self.flags.A:
556                return 0
557            # SYN+ACK packets answer SYN packets
558            if not other.flags.S:
559                return 0
560        if conf.checkIPsrc:
561            if not ((self.sport == other.dport) and
562                    (self.dport == other.sport)):
563                return 0
564        # Do not check ack value for SYN packets without ACK
565        if not (other.flags.S and not other.flags.A) \
566           and abs(other.ack - self.seq) > 2:
567            return 0
568        # Do not check ack value for RST packets without ACK
569        if self.flags.R and not self.flags.A:
570            return 1
571        if abs(other.seq - self.ack) > 2 + len(other.payload):
572            return 0
573        return 1
574    def mysummary(self):
575        if isinstance(self.underlayer, IP):
576            return self.underlayer.sprintf("TCP %IP.src%:%TCP.sport% > %IP.dst%:%TCP.dport% %TCP.flags%")
577        elif conf.ipv6_enabled and isinstance(self.underlayer, scapy.layers.inet6.IPv6):
578            return self.underlayer.sprintf("TCP %IPv6.src%:%TCP.sport% > %IPv6.dst%:%TCP.dport% %TCP.flags%")
579        else:
580            return self.sprintf("TCP %TCP.sport% > %TCP.dport% %TCP.flags%")
581
582class UDP(Packet):
583    name = "UDP"
584    fields_desc = [ ShortEnumField("sport", 53, UDP_SERVICES),
585                    ShortEnumField("dport", 53, UDP_SERVICES),
586                    ShortField("len", None),
587                    XShortField("chksum", None), ]
588    def post_build(self, p, pay):
589        p += pay
590        l = self.len
591        if l is None:
592            l = len(p)
593            p = p[:4]+struct.pack("!H",l)+p[6:]
594        if self.chksum is None:
595            if isinstance(self.underlayer, IP):
596                ck = in4_chksum(socket.IPPROTO_UDP, self.underlayer, p)
597                # According to RFC768 if the result checksum is 0, it should be set to 0xFFFF
598                if ck == 0:
599                    ck = 0xFFFF
600                p = p[:6]+struct.pack("!H", ck)+p[8:]
601            elif isinstance(self.underlayer, scapy.layers.inet6.IPv6) or isinstance(self.underlayer, scapy.layers.inet6._IPv6ExtHdr):
602                ck = scapy.layers.inet6.in6_chksum(socket.IPPROTO_UDP, self.underlayer, p)
603                # According to RFC2460 if the result checksum is 0, it should be set to 0xFFFF
604                if ck == 0:
605                    ck = 0xFFFF
606                p = p[:6]+struct.pack("!H", ck)+p[8:]
607            else:
608                warning("No IP underlayer to compute checksum. Leaving null.")
609        return p
610    def extract_padding(self, s):
611        l = self.len - 8
612        return s[:l],s[l:]
613    def hashret(self):
614        return self.payload.hashret()
615    def answers(self, other):
616        if not isinstance(other, UDP):
617            return 0
618        if conf.checkIPsrc:
619            if self.dport != other.sport:
620                return 0
621        return self.payload.answers(other.payload)
622    def mysummary(self):
623        if isinstance(self.underlayer, IP):
624            return self.underlayer.sprintf("UDP %IP.src%:%UDP.sport% > %IP.dst%:%UDP.dport%")
625        elif isinstance(self.underlayer, scapy.layers.inet6.IPv6):
626            return self.underlayer.sprintf("UDP %IPv6.src%:%UDP.sport% > %IPv6.dst%:%UDP.dport%")
627        else:
628            return self.sprintf("UDP %UDP.sport% > %UDP.dport%")
629
630icmptypes = { 0 : "echo-reply",
631              3 : "dest-unreach",
632              4 : "source-quench",
633              5 : "redirect",
634              8 : "echo-request",
635              9 : "router-advertisement",
636              10 : "router-solicitation",
637              11 : "time-exceeded",
638              12 : "parameter-problem",
639              13 : "timestamp-request",
640              14 : "timestamp-reply",
641              15 : "information-request",
642              16 : "information-response",
643              17 : "address-mask-request",
644              18 : "address-mask-reply" }
645
646icmpcodes = { 3 : { 0  : "network-unreachable",
647                    1  : "host-unreachable",
648                    2  : "protocol-unreachable",
649                    3  : "port-unreachable",
650                    4  : "fragmentation-needed",
651                    5  : "source-route-failed",
652                    6  : "network-unknown",
653                    7  : "host-unknown",
654                    9  : "network-prohibited",
655                    10 : "host-prohibited",
656                    11 : "TOS-network-unreachable",
657                    12 : "TOS-host-unreachable",
658                    13 : "communication-prohibited",
659                    14 : "host-precedence-violation",
660                    15 : "precedence-cutoff", },
661              5 : { 0  : "network-redirect",
662                    1  : "host-redirect",
663                    2  : "TOS-network-redirect",
664                    3  : "TOS-host-redirect", },
665              11 : { 0 : "ttl-zero-during-transit",
666                     1 : "ttl-zero-during-reassembly", },
667              12 : { 0 : "ip-header-bad",
668                     1 : "required-option-missing", }, }
669
670
671
672
673class ICMP(Packet):
674    name = "ICMP"
675    fields_desc = [ ByteEnumField("type",8, icmptypes),
676                    MultiEnumField("code",0, icmpcodes, depends_on=lambda pkt:pkt.type,fmt="B"),
677                    XShortField("chksum", None),
678                    ConditionalField(XShortField("id",0),  lambda pkt:pkt.type in [0,8,13,14,15,16,17,18]),
679                    ConditionalField(XShortField("seq",0), lambda pkt:pkt.type in [0,8,13,14,15,16,17,18]),
680                    ConditionalField(ICMPTimeStampField("ts_ori", None), lambda pkt:pkt.type in [13,14]),
681                    ConditionalField(ICMPTimeStampField("ts_rx", None), lambda pkt:pkt.type in [13,14]),
682                    ConditionalField(ICMPTimeStampField("ts_tx", None), lambda pkt:pkt.type in [13,14]),
683                    ConditionalField(IPField("gw","0.0.0.0"),  lambda pkt:pkt.type==5),
684                    ConditionalField(ByteField("ptr",0),   lambda pkt:pkt.type==12),
685                    ConditionalField(ByteField("reserved",0), lambda pkt:pkt.type in [3,11]),
686                    ConditionalField(ByteField("length",0), lambda pkt:pkt.type in [3,11,12]),
687                    ConditionalField(IPField("addr_mask","0.0.0.0"), lambda pkt:pkt.type in [17,18]),
688                    ConditionalField(ShortField("nexthopmtu",0), lambda pkt:pkt.type==3),
689                    ConditionalField(ShortField("unused",0), lambda pkt:pkt.type in [11,12]),
690                    ConditionalField(IntField("unused",0), lambda pkt:pkt.type not in [0,3,5,8,11,12,13,14,15,16,17,18])
691                    ]
692    def post_build(self, p, pay):
693        p += pay
694        if self.chksum is None:
695            ck = checksum(p)
696            p = p[:2] + chb(ck>>8) + chb(ck&0xff) + p[4:]
697        return p
698
699    def hashret(self):
700        if self.type in [0,8,13,14,15,16,17,18]:
701            return struct.pack("HH",self.id,self.seq)+self.payload.hashret()
702        return self.payload.hashret()
703    def answers(self, other):
704        if not isinstance(other,ICMP):
705            return 0
706        if ( (other.type,self.type) in [(8,0),(13,14),(15,16),(17,18)] and
707             self.id == other.id and
708             self.seq == other.seq ):
709            return 1
710        return 0
711
712    def guess_payload_class(self, payload):
713        if self.type in [3,4,5,11,12]:
714            return IPerror
715        else:
716            return None
717    def mysummary(self):
718        if isinstance(self.underlayer, IP):
719            return self.underlayer.sprintf("ICMP %IP.src% > %IP.dst% %ICMP.type% %ICMP.code%")
720        else:
721            return self.sprintf("ICMP %ICMP.type% %ICMP.code%")
722
723
724
725
726
727class IPerror(IP):
728    name = "IP in ICMP"
729    def answers(self, other):
730        if not isinstance(other, IP):
731            return 0
732        if not ( ((conf.checkIPsrc == 0) or (self.dst == other.dst)) and
733                 (self.src == other.src) and
734                 ( ((conf.checkIPID == 0)
735                    or (self.id == other.id)
736                    or (conf.checkIPID == 1 and self.id == socket.htons(other.id)))) and
737                 (self.proto == other.proto) ):
738            return 0
739        return self.payload.answers(other.payload)
740    def mysummary(self):
741        return Packet.mysummary(self)
742
743
744class TCPerror(TCP):
745    name = "TCP in ICMP"
746    def answers(self, other):
747        if not isinstance(other, TCP):
748            return 0
749        if conf.checkIPsrc:
750            if not ((self.sport == other.sport) and
751                    (self.dport == other.dport)):
752                return 0
753        if conf.check_TCPerror_seqack:
754            if self.seq is not None:
755                if self.seq != other.seq:
756                    return 0
757            if self.ack is not None:
758                if self.ack != other.ack:
759                    return 0
760        return 1
761    def mysummary(self):
762        return Packet.mysummary(self)
763
764
765class UDPerror(UDP):
766    name = "UDP in ICMP"
767    def answers(self, other):
768        if not isinstance(other, UDP):
769            return 0
770        if conf.checkIPsrc:
771            if not ((self.sport == other.sport) and
772                    (self.dport == other.dport)):
773                return 0
774        return 1
775    def mysummary(self):
776        return Packet.mysummary(self)
777
778
779
780class ICMPerror(ICMP):
781    name = "ICMP in ICMP"
782    def answers(self, other):
783        if not isinstance(other,ICMP):
784            return 0
785        if not ((self.type == other.type) and
786                (self.code == other.code)):
787            return 0
788        if self.code in [0,8,13,14,17,18]:
789            if (self.id == other.id and
790                self.seq == other.seq):
791                return 1
792            else:
793                return 0
794        else:
795            return 1
796    def mysummary(self):
797        return Packet.mysummary(self)
798
799bind_layers( Ether,         IP,            type=2048)
800bind_layers( CookedLinux,   IP,            proto=2048)
801bind_layers( GRE,           IP,            proto=2048)
802bind_layers( SNAP,          IP,            code=2048)
803bind_layers( Loopback,      IP,            type=0)
804bind_layers( Loopback,      IP,            type=2)
805bind_layers( IPerror,       IPerror,       frag=0, proto=4)
806bind_layers( IPerror,       ICMPerror,     frag=0, proto=1)
807bind_layers( IPerror,       TCPerror,      frag=0, proto=6)
808bind_layers( IPerror,       UDPerror,      frag=0, proto=17)
809bind_layers( IP,            IP,            frag=0, proto=4)
810bind_layers( IP,            ICMP,          frag=0, proto=1)
811bind_layers( IP,            TCP,           frag=0, proto=6)
812bind_layers( IP,            UDP,           frag=0, proto=17)
813bind_layers( IP,            GRE,           frag=0, proto=47)
814
815conf.l2types.register(DLT_RAW, IP)
816conf.l2types.register_num2layer(DLT_RAW_ALT, IP)
817conf.l2types.register(DLT_IPV4, IP)
818
819conf.l3types.register(ETH_P_IP, IP)
820conf.l3types.register_num2layer(ETH_P_ALL, IP)
821
822
823def inet_register_l3(l2, l3):
824    return getmacbyip(l3.dst)
825conf.neighbor.register_l3(Ether, IP, inet_register_l3)
826conf.neighbor.register_l3(Dot3, IP, inet_register_l3)
827
828
829###################
830## Fragmentation ##
831###################
832
833@conf.commands.register
834def fragment(pkt, fragsize=1480):
835    """Fragment a big IP datagram"""
836    fragsize = (fragsize+7)//8*8
837    lst = []
838    for p in pkt:
839        s = raw(p[IP].payload)
840        nb = (len(s)+fragsize-1)//fragsize
841        for i in range(nb):
842            q = p.copy()
843            del(q[IP].payload)
844            del(q[IP].chksum)
845            del(q[IP].len)
846            if i != nb - 1:
847                q[IP].flags |= 1
848            q[IP].frag += i * fragsize // 8
849            r = conf.raw_layer(load=s[i*fragsize:(i+1)*fragsize])
850            r.overload_fields = p[IP].payload.overload_fields.copy()
851            q.add_payload(r)
852            lst.append(q)
853    return lst
854
855@conf.commands.register
856def overlap_frag(p, overlap, fragsize=8, overlap_fragsize=None):
857    """Build overlapping fragments to bypass NIPS
858
859p:                the original packet
860overlap:          the overlapping data
861fragsize:         the fragment size of the packet
862overlap_fragsize: the fragment size of the overlapping packet"""
863
864    if overlap_fragsize is None:
865        overlap_fragsize = fragsize
866    q = p.copy()
867    del(q[IP].payload)
868    q[IP].add_payload(overlap)
869
870    qfrag = fragment(q, overlap_fragsize)
871    qfrag[-1][IP].flags |= 1
872    return qfrag+fragment(p, fragsize)
873
874@conf.commands.register
875def defrag(plist):
876    """defrag(plist) -> ([not fragmented], [defragmented],
877                  [ [bad fragments], [bad fragments], ... ])"""
878    frags = defaultdict(PacketList)
879    nofrag = PacketList()
880    for p in plist:
881        if IP not in p:
882            nofrag.append(p)
883            continue
884        ip = p[IP]
885        if ip.frag == 0 and ip.flags & 1 == 0:
886            nofrag.append(p)
887            continue
888        uniq = (ip.id,ip.src,ip.dst,ip.proto)
889        frags[uniq].append(p)
890    defrag = []
891    missfrag = []
892    for lst in six.itervalues(frags):
893        lst.sort(key=lambda x: x.frag)
894        p = lst[0]
895        lastp = lst[-1]
896        if p.frag > 0 or lastp.flags & 1 != 0: # first or last fragment missing
897            missfrag.append(lst)
898            continue
899        p = p.copy()
900        if conf.padding_layer in p:
901            del(p[conf.padding_layer].underlayer.payload)
902        ip = p[IP]
903        if ip.len is None or ip.ihl is None:
904            clen = len(ip.payload)
905        else:
906            clen = ip.len - (ip.ihl<<2)
907        txt = conf.raw_layer()
908        for q in lst[1:]:
909            if clen != q.frag<<3: # Wrong fragmentation offset
910                if clen > q.frag<<3:
911                    warning("Fragment overlap (%i > %i) %r || %r ||  %r" % (clen, q.frag<<3, p,txt,q))
912                missfrag.append(lst)
913                break
914            if q[IP].len is None or q[IP].ihl is None:
915                clen += len(q[IP].payload)
916            else:
917                clen += q[IP].len - (q[IP].ihl<<2)
918            if conf.padding_layer in q:
919                del(q[conf.padding_layer].underlayer.payload)
920            txt.add_payload(q[IP].payload.copy())
921        else:
922            ip.flags &= ~1 # !MF
923            del(ip.chksum)
924            del(ip.len)
925            p = p/txt
926            defrag.append(p)
927    defrag2=PacketList()
928    for p in defrag:
929        defrag2.append(p.__class__(raw(p)))
930    return nofrag,defrag2,missfrag
931
932@conf.commands.register
933def defragment(plist):
934    """defrag(plist) -> plist defragmented as much as possible """
935    frags = defaultdict(lambda:[])
936    final = []
937
938    pos = 0
939    for p in plist:
940        p._defrag_pos = pos
941        pos += 1
942        if IP in p:
943            ip = p[IP]
944            if ip.frag != 0 or ip.flags & 1:
945                ip = p[IP]
946                uniq = (ip.id,ip.src,ip.dst,ip.proto)
947                frags[uniq].append(p)
948                continue
949        final.append(p)
950
951    defrag = []
952    missfrag = []
953    for lst in six.itervalues(frags):
954        lst.sort(key=lambda x: x.frag)
955        p = lst[0]
956        lastp = lst[-1]
957        if p.frag > 0 or lastp.flags & 1 != 0: # first or last fragment missing
958            missfrag += lst
959            continue
960        p = p.copy()
961        if conf.padding_layer in p:
962            del(p[conf.padding_layer].underlayer.payload)
963        ip = p[IP]
964        if ip.len is None or ip.ihl is None:
965            clen = len(ip.payload)
966        else:
967            clen = ip.len - (ip.ihl<<2)
968        txt = conf.raw_layer()
969        for q in lst[1:]:
970            if clen != q.frag<<3: # Wrong fragmentation offset
971                if clen > q.frag<<3:
972                    warning("Fragment overlap (%i > %i) %r || %r ||  %r" % (clen, q.frag<<3, p,txt,q))
973                missfrag += lst
974                break
975            if q[IP].len is None or q[IP].ihl is None:
976                clen += len(q[IP].payload)
977            else:
978                clen += q[IP].len - (q[IP].ihl<<2)
979            if conf.padding_layer in q:
980                del(q[conf.padding_layer].underlayer.payload)
981            txt.add_payload(q[IP].payload.copy())
982        else:
983            ip.flags &= ~1 # !MF
984            del(ip.chksum)
985            del(ip.len)
986            p = p/txt
987            p._defrag_pos = max(x._defrag_pos for x in lst)
988            defrag.append(p)
989    defrag2=[]
990    for p in defrag:
991        q = p.__class__(raw(p))
992        q._defrag_pos = p._defrag_pos
993        defrag2.append(q)
994    final += defrag2
995    final += missfrag
996    final.sort(key=lambda x: x._defrag_pos)
997    for p in final:
998        del(p._defrag_pos)
999
1000    if hasattr(plist, "listname"):
1001        name = "Defragmented %s" % plist.listname
1002    else:
1003        name = "Defragmented"
1004
1005    return PacketList(final, name=name)
1006
1007
1008
1009### Add timeskew_graph() method to PacketList
1010def _packetlist_timeskew_graph(self, ip, **kargs):
1011    """Tries to graph the timeskew between the timestamps and real time for a given ip"""
1012
1013    # Filter TCP segments which source address is 'ip'
1014    tmp = (self._elt2pkt(x) for x in self.res)
1015    b = (x for x in tmp if IP in x and x[IP].src == ip and TCP in x)
1016
1017    # Build a list of tuples (creation_time, replied_timestamp)
1018    c = []
1019    tsf = ICMPTimeStampField("", None)
1020    for p in b:
1021        opts = p.getlayer(TCP).options
1022        for o in opts:
1023            if o[0] == "Timestamp":
1024                c.append((p.time, tsf.any2i("", o[1][0])))
1025
1026    # Stop if the list is empty
1027    if not c:
1028        warning("No timestamps found in packet list")
1029        return []
1030
1031    # Prepare the data that will be plotted
1032    first_creation_time = c[0][0]
1033    first_replied_timestamp = c[0][1]
1034
1035    def _wrap_data(ts_tuple, wrap_seconds=2000):
1036        """Wrap the list of tuples."""
1037
1038        ct,rt = ts_tuple # (creation_time, replied_timestamp)
1039        X = ct % wrap_seconds
1040        Y = ((ct-first_creation_time) - ((rt-first_replied_timestamp)/1000.0))
1041
1042        return X, Y
1043
1044    data = [_wrap_data(e) for e in c]
1045
1046    # Mimic the default gnuplot output
1047    if kargs == {}:
1048        kargs = MATPLOTLIB_DEFAULT_PLOT_KARGS
1049    lines = plt.plot(data, **kargs)
1050
1051    # Call show() if matplotlib is not inlined
1052    if not MATPLOTLIB_INLINED:
1053        plt.show()
1054
1055    return lines
1056
1057PacketList.timeskew_graph = _packetlist_timeskew_graph
1058
1059
1060### Create a new packet list
1061class TracerouteResult(SndRcvList):
1062    __slots__ = ["graphdef", "graphpadding", "graphASres", "padding", "hloc",
1063                 "nloc"]
1064    def __init__(self, res=None, name="Traceroute", stats=None):
1065        PacketList.__init__(self, res, name, stats)
1066        self.graphdef = None
1067        self.graphASres = 0
1068        self.padding = 0
1069        self.hloc = None
1070        self.nloc = None
1071
1072    def show(self):
1073        return self.make_table(lambda s_r: (s_r[0].sprintf("%IP.dst%:{TCP:tcp%ir,TCP.dport%}{UDP:udp%ir,UDP.dport%}{ICMP:ICMP}"),
1074                                            s_r[0].ttl,
1075                                            s_r[1].sprintf("%-15s,IP.src% {TCP:%TCP.flags%}{ICMP:%ir,ICMP.type%}")))
1076
1077
1078    def get_trace(self):
1079        trace = {}
1080        for s,r in self.res:
1081            if IP not in s:
1082                continue
1083            d = s[IP].dst
1084            if d not in trace:
1085                trace[d] = {}
1086            trace[d][s[IP].ttl] = r[IP].src, ICMP not in r
1087        for k in six.itervalues(trace):
1088            try:
1089                m = min(x for x, y in six.itervalues(k) if y)
1090            except ValueError:
1091                continue
1092            for l in list(k):  # use list(): k is modified in the loop
1093                if l > m:
1094                    del k[l]
1095        return trace
1096
1097    def trace3D(self):
1098        """Give a 3D representation of the traceroute.
1099        right button: rotate the scene
1100        middle button: zoom
1101        left button: move the scene
1102        left button on a ball: toggle IP displaying
1103        ctrl-left button on a ball: scan ports 21,22,23,25,80 and 443 and display the result"""
1104        trace = self.get_trace()
1105        import visual
1106
1107        class IPsphere(visual.sphere):
1108            def __init__(self, ip, **kargs):
1109                visual.sphere.__init__(self, **kargs)
1110                self.ip=ip
1111                self.label=None
1112                self.setlabel(self.ip)
1113            def setlabel(self, txt,visible=None):
1114                if self.label is not None:
1115                    if visible is None:
1116                        visible = self.label.visible
1117                    self.label.visible = 0
1118                elif visible is None:
1119                    visible=0
1120                self.label=visual.label(text=txt, pos=self.pos, space=self.radius, xoffset=10, yoffset=20, visible=visible)
1121            def action(self):
1122                self.label.visible ^= 1
1123
1124        visual.scene = visual.display()
1125        visual.scene.exit = True
1126        start = visual.box()
1127        rings={}
1128        tr3d = {}
1129        for i in trace:
1130            tr = trace[i]
1131            tr3d[i] = []
1132            for t in range(1, max(tr) + 1):
1133                if t not in rings:
1134                    rings[t] = []
1135                if t in tr:
1136                    if tr[t] not in rings[t]:
1137                        rings[t].append(tr[t])
1138                    tr3d[i].append(rings[t].index(tr[t]))
1139                else:
1140                    rings[t].append(("unk",-1))
1141                    tr3d[i].append(len(rings[t])-1)
1142        for t in rings:
1143            r = rings[t]
1144            l = len(r)
1145            for i in range(l):
1146                if r[i][1] == -1:
1147                    col = (0.75,0.75,0.75)
1148                elif r[i][1]:
1149                    col = visual.color.green
1150                else:
1151                    col = visual.color.blue
1152
1153                s = IPsphere(pos=((l-1)*visual.cos(2*i*visual.pi/l),(l-1)*visual.sin(2*i*visual.pi/l),2*t),
1154                             ip = r[i][0],
1155                             color = col)
1156                for trlst in six.itervalues(tr3d):
1157                    if t <= len(trlst):
1158                        if trlst[t-1] == i:
1159                            trlst[t-1] = s
1160        forecol = colgen(0.625, 0.4375, 0.25, 0.125)
1161        for trlst in six.itervalues(tr3d):
1162            col = next(forecol)
1163            start = (0,0,0)
1164            for ip in trlst:
1165                visual.cylinder(pos=start,axis=ip.pos-start,color=col,radius=0.2)
1166                start = ip.pos
1167
1168        movcenter=None
1169        while True:
1170            visual.rate(50)
1171            if visual.scene.kb.keys:
1172                k = visual.scene.kb.getkey()
1173                if k == "esc" or k == "q":
1174                    break
1175            if visual.scene.mouse.events:
1176                ev = visual.scene.mouse.getevent()
1177                if ev.press == "left":
1178                    o = ev.pick
1179                    if o:
1180                        if ev.ctrl:
1181                            if o.ip == "unk":
1182                                continue
1183                            savcolor = o.color
1184                            o.color = (1,0,0)
1185                            a,b=sr(IP(dst=o.ip)/TCP(dport=[21,22,23,25,80,443]),timeout=2)
1186                            o.color = savcolor
1187                            if len(a) == 0:
1188                                txt = "%s:\nno results" % o.ip
1189                            else:
1190                                txt = "%s:\n" % o.ip
1191                                for s,r in a:
1192                                    txt += r.sprintf("{TCP:%IP.src%:%TCP.sport% %TCP.flags%}{TCPerror:%IPerror.dst%:%TCPerror.dport% %IP.src% %ir,ICMP.type%}\n")
1193                            o.setlabel(txt, visible=1)
1194                        else:
1195                            if hasattr(o, "action"):
1196                                o.action()
1197                elif ev.drag == "left":
1198                    movcenter = ev.pos
1199                elif ev.drop == "left":
1200                    movcenter = None
1201            if movcenter:
1202                visual.scene.center -= visual.scene.mouse.pos-movcenter
1203                movcenter = visual.scene.mouse.pos
1204
1205
1206    def world_trace(self, **kargs):
1207        """Display traceroute results on a world map."""
1208
1209        # Check that the GeoIP module can be imported
1210        try:
1211            import GeoIP
1212        except ImportError:
1213            message = "Can't import GeoIP. Won't be able to plot the world."
1214            scapy.utils.log_loading.info(message)
1215            return list()
1216
1217        # Check if this is an IPv6 traceroute and load the correct file
1218        if isinstance(self, scapy.layers.inet6.TracerouteResult6):
1219            geoip_city_filename = conf.geoip_city_ipv6
1220        else:
1221            geoip_city_filename = conf.geoip_city
1222
1223        # Check that the GeoIP database can be opened
1224        try:
1225            db = GeoIP.open(conf.geoip_city, 0)
1226        except:
1227            message = "Can't open GeoIP database at %s" % conf.geoip_city
1228            scapy.utils.log_loading.info(message)
1229            return list()
1230
1231        # Regroup results per trace
1232        ips = {}
1233        rt = {}
1234        ports_done = {}
1235        for s,r in self.res:
1236            ips[r.src] = None
1237            if s.haslayer(TCP) or s.haslayer(UDP):
1238                trace_id = (s.src,s.dst,s.proto,s.dport)
1239            elif s.haslayer(ICMP):
1240                trace_id = (s.src,s.dst,s.proto,s.type)
1241            else:
1242                trace_id = (s.src,s.dst,s.proto,0)
1243            trace = rt.get(trace_id,{})
1244            if not r.haslayer(ICMP) or r.type != 11:
1245                if trace_id in ports_done:
1246                    continue
1247                ports_done[trace_id] = None
1248            trace[s.ttl] = r.src
1249            rt[trace_id] = trace
1250
1251        # Get the addresses locations
1252        trt = {}
1253        for trace_id in rt:
1254            trace = rt[trace_id]
1255            loctrace = []
1256            for i in range(max(trace)):
1257                ip = trace.get(i,None)
1258                if ip is None:
1259                    continue
1260                loc = db.record_by_addr(ip)
1261                if loc is None:
1262                    continue
1263                loc = loc.get('longitude'), loc.get('latitude')
1264                if loc == (None, None):
1265                    continue
1266                loctrace.append(loc)
1267            if loctrace:
1268                trt[trace_id] = loctrace
1269
1270        # Load the map renderer
1271        from mpl_toolkits.basemap import Basemap
1272        bmap = Basemap()
1273
1274        # Split latitudes and longitudes per traceroute measurement
1275        locations = [zip(*tr) for tr in six.itervalues(trt)]
1276
1277        # Plot the traceroute measurement as lines in the map
1278        lines = [bmap.plot(*bmap(lons, lats)) for lons, lats in locations]
1279
1280        # Draw countries
1281        bmap.drawcoastlines()
1282
1283        # Call show() if matplotlib is not inlined
1284        if not MATPLOTLIB_INLINED:
1285            plt.show()
1286
1287        # Return the drawn lines
1288        return lines
1289
1290    def make_graph(self,ASres=None,padding=0):
1291        if ASres is None:
1292            ASres = conf.AS_resolver
1293        self.graphASres = ASres
1294        self.graphpadding = padding
1295        ips = {}
1296        rt = {}
1297        ports = {}
1298        ports_done = {}
1299        for s,r in self.res:
1300            r = r.getlayer(IP) or (conf.ipv6_enabled and r[scapy.layers.inet6.IPv6]) or r
1301            s = s.getlayer(IP) or (conf.ipv6_enabled and s[scapy.layers.inet6.IPv6]) or s
1302            ips[r.src] = None
1303            if TCP in s:
1304                trace_id = (s.src,s.dst,6,s.dport)
1305            elif UDP in s:
1306                trace_id = (s.src,s.dst,17,s.dport)
1307            elif ICMP in s:
1308                trace_id = (s.src,s.dst,1,s.type)
1309            else:
1310                trace_id = (s.src,s.dst,s.proto,0)
1311            trace = rt.get(trace_id,{})
1312            ttl = conf.ipv6_enabled and scapy.layers.inet6.IPv6 in s and s.hlim or s.ttl
1313            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):
1314                if trace_id in ports_done:
1315                    continue
1316                ports_done[trace_id] = None
1317                p = ports.get(r.src,[])
1318                if TCP in r:
1319                    p.append(r.sprintf("<T%ir,TCP.sport%> %TCP.sport% %TCP.flags%"))
1320                    trace[ttl] = r.sprintf('"%r,src%":T%ir,TCP.sport%')
1321                elif UDP in r:
1322                    p.append(r.sprintf("<U%ir,UDP.sport%> %UDP.sport%"))
1323                    trace[ttl] = r.sprintf('"%r,src%":U%ir,UDP.sport%')
1324                elif ICMP in r:
1325                    p.append(r.sprintf("<I%ir,ICMP.type%> ICMP %ICMP.type%"))
1326                    trace[ttl] = r.sprintf('"%r,src%":I%ir,ICMP.type%')
1327                else:
1328                    p.append(r.sprintf("{IP:<P%ir,proto%> IP %proto%}{IPv6:<P%ir,nh%> IPv6 %nh%}"))
1329                    trace[ttl] = r.sprintf('"%r,src%":{IP:P%ir,proto%}{IPv6:P%ir,nh%}')
1330                ports[r.src] = p
1331            else:
1332                trace[ttl] = r.sprintf('"%r,src%"')
1333            rt[trace_id] = trace
1334
1335        # Fill holes with unk%i nodes
1336        unknown_label = incremental_label("unk%i")
1337        blackholes = []
1338        bhip = {}
1339        for rtk in rt:
1340            trace = rt[rtk]
1341            max_trace = max(trace)
1342            for n in range(min(trace), max_trace):
1343                if n not in trace:
1344                    trace[n] = next(unknown_label)
1345            if rtk not in ports_done:
1346                if rtk[2] == 1: #ICMP
1347                    bh = "%s %i/icmp" % (rtk[1],rtk[3])
1348                elif rtk[2] == 6: #TCP
1349                    bh = "%s %i/tcp" % (rtk[1],rtk[3])
1350                elif rtk[2] == 17: #UDP
1351                    bh = '%s %i/udp' % (rtk[1],rtk[3])
1352                else:
1353                    bh = '%s %i/proto' % (rtk[1],rtk[2])
1354                ips[bh] = None
1355                bhip[rtk[1]] = bh
1356                bh = '"%s"' % bh
1357                trace[max_trace + 1] = bh
1358                blackholes.append(bh)
1359
1360        # Find AS numbers
1361        ASN_query_list = set(x.rsplit(" ",1)[0] for x in ips)
1362        if ASres is None:
1363            ASNlist = []
1364        else:
1365            ASNlist = ASres.resolve(*ASN_query_list)
1366
1367        ASNs = {}
1368        ASDs = {}
1369        for ip,asn,desc, in ASNlist:
1370            if asn is None:
1371                continue
1372            iplist = ASNs.get(asn,[])
1373            if ip in bhip:
1374                if ip in ports:
1375                    iplist.append(ip)
1376                iplist.append(bhip[ip])
1377            else:
1378                iplist.append(ip)
1379            ASNs[asn] = iplist
1380            ASDs[asn] = desc
1381
1382
1383        backcolorlist=colgen("60","86","ba","ff")
1384        forecolorlist=colgen("a0","70","40","20")
1385
1386        s = "digraph trace {\n"
1387
1388        s += "\n\tnode [shape=ellipse,color=black,style=solid];\n\n"
1389
1390        s += "\n#ASN clustering\n"
1391        for asn in ASNs:
1392            s += '\tsubgraph cluster_%s {\n' % asn
1393            col = next(backcolorlist)
1394            s += '\t\tcolor="#%s%s%s";' % col
1395            s += '\t\tnode [fillcolor="#%s%s%s",style=filled];' % col
1396            s += '\t\tfontsize = 10;'
1397            s += '\t\tlabel = "%s\\n[%s]"\n' % (asn,ASDs[asn])
1398            for ip in ASNs[asn]:
1399
1400                s += '\t\t"%s";\n'%ip
1401            s += "\t}\n"
1402
1403
1404
1405
1406        s += "#endpoints\n"
1407        for p in ports:
1408            s += '\t"%s" [shape=record,color=black,fillcolor=green,style=filled,label="%s|%s"];\n' % (p,p,"|".join(ports[p]))
1409
1410        s += "\n#Blackholes\n"
1411        for bh in blackholes:
1412            s += '\t%s [shape=octagon,color=black,fillcolor=red,style=filled];\n' % bh
1413
1414        if padding:
1415            s += "\n#Padding\n"
1416            pad={}
1417            for snd,rcv in self.res:
1418                if rcv.src not in ports and rcv.haslayer(conf.padding_layer):
1419                    p = rcv.getlayer(conf.padding_layer).load
1420                    if p != b"\x00"*len(p):
1421                        pad[rcv.src]=None
1422            for rcv in pad:
1423                s += '\t"%s" [shape=triangle,color=black,fillcolor=red,style=filled];\n' % rcv
1424
1425
1426
1427        s += "\n\tnode [shape=ellipse,color=black,style=solid];\n\n"
1428
1429
1430        for rtk in rt:
1431            s += "#---[%s\n" % repr(rtk)
1432            s += '\t\tedge [color="#%s%s%s"];\n' % next(forecolorlist)
1433            trace = rt[rtk]
1434            maxtrace = max(trace)
1435            for n in range(min(trace), maxtrace):
1436                s += '\t%s ->\n' % trace[n]
1437            s += '\t%s;\n' % trace[maxtrace]
1438
1439        s += "}\n";
1440        self.graphdef = s
1441
1442    def graph(self, ASres=None, padding=0, **kargs):
1443        """x.graph(ASres=conf.AS_resolver, other args):
1444        ASres=None          : no AS resolver => no clustering
1445        ASres=AS_resolver() : default whois AS resolver (riswhois.ripe.net)
1446        ASres=AS_resolver_cymru(): use whois.cymru.com whois database
1447        ASres=AS_resolver(server="whois.ra.net")
1448        type: output type (svg, ps, gif, jpg, etc.), passed to dot's "-T" option
1449        target: filename or redirect. Defaults pipe to Imagemagick's display program
1450        prog: which graphviz program to use"""
1451        if ASres is None:
1452            ASres = conf.AS_resolver
1453        if (self.graphdef is None or
1454            self.graphASres != ASres or
1455            self.graphpadding != padding):
1456            self.make_graph(ASres,padding)
1457
1458        return do_graph(self.graphdef, **kargs)
1459
1460
1461
1462@conf.commands.register
1463def traceroute(target, dport=80, minttl=1, maxttl=30, sport=RandShort(), l4 = None, filter=None, timeout=2, verbose=None, **kargs):
1464    """Instant TCP traceroute
1465traceroute(target, [maxttl=30,] [dport=80,] [sport=80,] [verbose=conf.verb]) -> None
1466"""
1467    if verbose is None:
1468        verbose = conf.verb
1469    if filter is None:
1470        # we only consider ICMP error packets and TCP packets with at
1471        # least the ACK flag set *and* either the SYN or the RST flag
1472        # set
1473        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))"
1474    if l4 is None:
1475        a,b = sr(IP(dst=target, id=RandShort(), ttl=(minttl,maxttl))/TCP(seq=RandInt(),sport=sport, dport=dport),
1476                 timeout=timeout, filter=filter, verbose=verbose, **kargs)
1477    else:
1478        # this should always work
1479        filter="ip"
1480        a,b = sr(IP(dst=target, id=RandShort(), ttl=(minttl,maxttl))/l4,
1481                 timeout=timeout, filter=filter, verbose=verbose, **kargs)
1482
1483    a = TracerouteResult(a.res)
1484    if verbose:
1485        a.show()
1486    return a,b
1487
1488
1489
1490#############################
1491## Simple TCP client stack ##
1492#############################
1493
1494class TCP_client(Automaton):
1495
1496    def parse_args(self, ip, port, *args, **kargs):
1497        self.dst = str(Net(ip))
1498        self.dport = port
1499        self.sport = random.randrange(0,2**16)
1500        self.l4 = IP(dst=ip)/TCP(sport=self.sport, dport=self.dport, flags=0,
1501                                 seq=random.randrange(0,2**32))
1502        self.src = self.l4.src
1503        self.swin=self.l4[TCP].window
1504        self.dwin=1
1505        self.rcvbuf = b""
1506        bpf = "host %s  and host %s and port %i and port %i" % (self.src,
1507                                                                self.dst,
1508                                                                self.sport,
1509                                                                self.dport)
1510
1511#        bpf=None
1512        Automaton.parse_args(self, filter=bpf, **kargs)
1513
1514
1515    def master_filter(self, pkt):
1516        return (IP in pkt and
1517                pkt[IP].src == self.dst and
1518                pkt[IP].dst == self.src and
1519                TCP in pkt and
1520                pkt[TCP].sport == self.dport and
1521                pkt[TCP].dport == self.sport and
1522                self.l4[TCP].seq >= pkt[TCP].ack and # XXX: seq/ack 2^32 wrap up
1523                ((self.l4[TCP].ack == 0) or (self.l4[TCP].ack <= pkt[TCP].seq <= self.l4[TCP].ack+self.swin)) )
1524
1525
1526    @ATMT.state(initial=1)
1527    def START(self):
1528        pass
1529
1530    @ATMT.state()
1531    def SYN_SENT(self):
1532        pass
1533
1534    @ATMT.state()
1535    def ESTABLISHED(self):
1536        pass
1537
1538    @ATMT.state()
1539    def LAST_ACK(self):
1540        pass
1541
1542    @ATMT.state(final=1)
1543    def CLOSED(self):
1544        pass
1545
1546
1547    @ATMT.condition(START)
1548    def connect(self):
1549        raise self.SYN_SENT()
1550    @ATMT.action(connect)
1551    def send_syn(self):
1552        self.l4[TCP].flags = "S"
1553        self.send(self.l4)
1554        self.l4[TCP].seq += 1
1555
1556
1557    @ATMT.receive_condition(SYN_SENT)
1558    def synack_received(self, pkt):
1559        if pkt[TCP].flags & 0x3f == 0x12:
1560            raise self.ESTABLISHED().action_parameters(pkt)
1561    @ATMT.action(synack_received)
1562    def send_ack_of_synack(self, pkt):
1563        self.l4[TCP].ack = pkt[TCP].seq+1
1564        self.l4[TCP].flags = "A"
1565        self.send(self.l4)
1566
1567    @ATMT.receive_condition(ESTABLISHED)
1568    def incoming_data_received(self, pkt):
1569        if not isinstance(pkt[TCP].payload, NoPayload) and not isinstance(pkt[TCP].payload, conf.padding_layer):
1570            raise self.ESTABLISHED().action_parameters(pkt)
1571    @ATMT.action(incoming_data_received)
1572    def receive_data(self,pkt):
1573        data = raw(pkt[TCP].payload)
1574        if data and self.l4[TCP].ack == pkt[TCP].seq:
1575            self.l4[TCP].ack += len(data)
1576            self.l4[TCP].flags = "A"
1577            self.send(self.l4)
1578            self.rcvbuf += data
1579            if pkt[TCP].flags.P:
1580                self.oi.tcp.send(self.rcvbuf)
1581                self.rcvbuf = b""
1582
1583    @ATMT.ioevent(ESTABLISHED,name="tcp", as_supersocket="tcplink")
1584    def outgoing_data_received(self, fd):
1585        raise self.ESTABLISHED().action_parameters(fd.recv())
1586    @ATMT.action(outgoing_data_received)
1587    def send_data(self, d):
1588        self.l4[TCP].flags = "PA"
1589        self.send(self.l4/d)
1590        self.l4[TCP].seq += len(d)
1591
1592
1593    @ATMT.receive_condition(ESTABLISHED)
1594    def reset_received(self, pkt):
1595        if pkt[TCP].flags & 4 != 0:
1596            raise self.CLOSED()
1597
1598    @ATMT.receive_condition(ESTABLISHED)
1599    def fin_received(self, pkt):
1600        if pkt[TCP].flags & 0x1 == 1:
1601            raise self.LAST_ACK().action_parameters(pkt)
1602    @ATMT.action(fin_received)
1603    def send_finack(self, pkt):
1604        self.l4[TCP].flags = "FA"
1605        self.l4[TCP].ack = pkt[TCP].seq+1
1606        self.send(self.l4)
1607        self.l4[TCP].seq += 1
1608
1609    @ATMT.receive_condition(LAST_ACK)
1610    def ack_of_fin_received(self, pkt):
1611        if pkt[TCP].flags & 0x3f == 0x10:
1612            raise self.CLOSED()
1613
1614
1615
1616
1617#####################
1618## Reporting stuff ##
1619#####################
1620
1621
1622@conf.commands.register
1623def report_ports(target, ports):
1624    """portscan a target and output a LaTeX table
1625report_ports(target, ports) -> string"""
1626    ans,unans = sr(IP(dst=target)/TCP(dport=ports),timeout=5)
1627    rep = "\\begin{tabular}{|r|l|l|}\n\\hline\n"
1628    for s,r in ans:
1629        if not r.haslayer(ICMP):
1630            if r.payload.flags == 0x12:
1631                rep += r.sprintf("%TCP.sport% & open & SA \\\\\n")
1632    rep += "\\hline\n"
1633    for s,r in ans:
1634        if r.haslayer(ICMP):
1635            rep += r.sprintf("%TCPerror.dport% & closed & ICMP type %ICMP.type%/%ICMP.code% from %IP.src% \\\\\n")
1636        elif r.payload.flags != 0x12:
1637            rep += r.sprintf("%TCP.sport% & closed & TCP %TCP.flags% \\\\\n")
1638    rep += "\\hline\n"
1639    for i in unans:
1640        rep += i.sprintf("%TCP.dport% & ? & unanswered \\\\\n")
1641    rep += "\\hline\n\\end{tabular}\n"
1642    return rep
1643
1644
1645@conf.commands.register
1646def IPID_count(lst, funcID=lambda x:x[1].id, funcpres=lambda x:x[1].summary()):
1647    """Identify IP id values classes in a list of packets
1648
1649lst:      a list of packets
1650funcID:   a function that returns IP id values
1651funcpres: a function used to summarize packets"""
1652    idlst = [funcID(e) for e in lst]
1653    idlst.sort()
1654    classes = [idlst[0]]
1655    classes += [t[1] for t in zip(idlst[:-1], idlst[1:]) if abs(t[0]-t[1]) > 50]
1656    lst = [(funcID(x), funcpres(x)) for x in lst]
1657    lst.sort()
1658    print("Probably %i classes:" % len(classes), classes)
1659    for id,pr in lst:
1660        print("%5i" % id, pr)
1661
1662
1663@conf.commands.register
1664def fragleak(target,sport=123, dport=123, timeout=0.2, onlyasc=0):
1665    load = "XXXXYYYYYYYYYY"
1666#    getmacbyip(target)
1667#    pkt = IP(dst=target, id=RandShort(), options=b"\x22"*40)/UDP()/load
1668    pkt = IP(dst=target, id=RandShort(), options=b"\x00"*40, flags=1)/UDP(sport=sport, dport=sport)/load
1669    s=conf.L3socket()
1670    intr=0
1671    found={}
1672    try:
1673        while True:
1674            try:
1675                if not intr:
1676                    s.send(pkt)
1677                sin,sout,serr = select([s],[],[],timeout)
1678                if not sin:
1679                    continue
1680                ans=s.recv(1600)
1681                if not isinstance(ans, IP): #TODO: IPv6
1682                    continue
1683                if not isinstance(ans.payload, ICMP):
1684                    continue
1685                if not isinstance(ans.payload.payload, IPerror):
1686                    continue
1687                if ans.payload.payload.dst != target:
1688                    continue
1689                if ans.src  != target:
1690                    print("leak from", ans.src, end=' ')
1691
1692
1693#                print repr(ans)
1694                if not ans.haslayer(conf.padding_layer):
1695                    continue
1696
1697
1698#                print repr(ans.payload.payload.payload.payload)
1699
1700#                if not isinstance(ans.payload.payload.payload.payload, conf.raw_layer):
1701#                    continue
1702#                leak = ans.payload.payload.payload.payload.load[len(load):]
1703                leak = ans.getlayer(conf.padding_layer).load
1704                if leak not in found:
1705                    found[leak]=None
1706                    linehexdump(leak, onlyasc=onlyasc)
1707            except KeyboardInterrupt:
1708                if intr:
1709                    raise
1710                intr=1
1711    except KeyboardInterrupt:
1712        pass
1713
1714
1715@conf.commands.register
1716def fragleak2(target, timeout=0.4, onlyasc=0):
1717    found={}
1718    try:
1719        while True:
1720            p = sr1(IP(dst=target, options=b"\x00"*40, proto=200)/"XXXXYYYYYYYYYYYY",timeout=timeout,verbose=0)
1721            if not p:
1722                continue
1723            if conf.padding_layer in p:
1724                leak  = p[conf.padding_layer].load
1725                if leak not in found:
1726                    found[leak]=None
1727                    linehexdump(leak,onlyasc=onlyasc)
1728    except:
1729        pass
1730
1731
1732conf.stats_classic_protocols += [TCP,UDP,ICMP]
1733conf.stats_dot11_protocols += [TCP,UDP,ICMP]
1734
1735if conf.ipv6_enabled:
1736    import scapy.layers.inet6
1737