• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# SPDX-License-Identifier: GPL-2.0-only
2# This file is part of Scapy
3# See https://scapy.net/ for more information
4# Copyright (C) Philippe Biondi <phil@secdev.org>
5
6"""
7IPv4 (Internet Protocol v4).
8"""
9
10import time
11import struct
12import re
13import random
14import select
15import socket
16from collections import defaultdict
17
18from scapy.utils import checksum, do_graph, incremental_label, \
19    linehexdump, strxor, whois, colgen
20from scapy.ansmachine import AnsweringMachine
21from scapy.base_classes import Gen, Net, _ScopedIP
22from scapy.data import ETH_P_IP, ETH_P_ALL, DLT_RAW, DLT_RAW_ALT, DLT_IPV4, \
23    IP_PROTOS, TCP_SERVICES, UDP_SERVICES
24from scapy.layers.l2 import (
25    CookedLinux,
26    Dot3,
27    Ether,
28    GRE,
29    Loopback,
30    SNAP,
31    arpcachepoison,
32    getmacbyip,
33)
34from scapy.compat import raw, chb, orb, bytes_encode, Optional
35from scapy.config import conf
36from scapy.fields import (
37    BitEnumField,
38    BitField,
39    ByteEnumField,
40    ByteField,
41    ConditionalField,
42    DestField,
43    Emph,
44    FieldLenField,
45    FieldListField,
46    FlagsField,
47    IPField,
48    IP6Field,
49    IntField,
50    MayEnd,
51    MultiEnumField,
52    MultipleTypeField,
53    PacketField,
54    PacketListField,
55    ShortEnumField,
56    ShortField,
57    SourceIPField,
58    StrField,
59    StrFixedLenField,
60    StrLenField,
61    TrailerField,
62    XByteField,
63    XShortField,
64)
65from scapy.packet import Packet, bind_layers, bind_bottom_up, NoPayload
66from scapy.volatile import RandShort, RandInt, RandBin, RandNum, VolatileValue
67from scapy.sendrecv import sr, sr1
68from scapy.plist import _PacketList, PacketList, SndRcvList
69from scapy.automaton import Automaton, ATMT
70from scapy.error import log_runtime, warning
71from scapy.pton_ntop import inet_pton
72
73import scapy.as_resolvers
74
75####################
76#  IP Tools class  #
77####################
78
79
80class IPTools(object):
81    """Add more powers to a class with an "src" attribute."""
82    __slots__ = []
83
84    def whois(self):
85        """whois the source and print the output"""
86        print(whois(self.src).decode("utf8", "ignore"))
87
88    def _ttl(self):
89        """Returns ttl or hlim, depending on the IP version"""
90        return self.hlim if isinstance(self, scapy.layers.inet6.IPv6) else self.ttl  # noqa: E501
91
92    def ottl(self):
93        t = sorted([32, 64, 128, 255] + [self._ttl()])
94        return t[t.index(self._ttl()) + 1]
95
96    def hops(self):
97        return self.ottl() - self._ttl()
98
99
100_ip_options_names = {0: "end_of_list",
101                     1: "nop",
102                     2: "security",
103                     3: "loose_source_route",
104                     4: "timestamp",
105                     5: "extended_security",
106                     6: "commercial_security",
107                     7: "record_route",
108                     8: "stream_id",
109                     9: "strict_source_route",
110                     10: "experimental_measurement",
111                     11: "mtu_probe",
112                     12: "mtu_reply",
113                     13: "flow_control",
114                     14: "access_control",
115                     15: "encode",
116                     16: "imi_traffic_descriptor",
117                     17: "extended_IP",
118                     18: "traceroute",
119                     19: "address_extension",
120                     20: "router_alert",
121                     21: "selective_directed_broadcast_mode",
122                     23: "dynamic_packet_state",
123                     24: "upstream_multicast_packet",
124                     25: "quick_start",
125                     30: "rfc4727_experiment",
126                     }
127
128
129class _IPOption_HDR(Packet):
130    fields_desc = [BitField("copy_flag", 0, 1),
131                   BitEnumField("optclass", 0, 2, {0: "control", 2: "debug"}),
132                   BitEnumField("option", 0, 5, _ip_options_names)]
133
134
135class IPOption(Packet):
136    name = "IP Option"
137    fields_desc = [_IPOption_HDR,
138                   FieldLenField("length", None, fmt="B",  # Only option 0 and 1 have no length and value  # noqa: E501
139                                 length_of="value", adjust=lambda pkt, l:l + 2),  # noqa: E501
140                   StrLenField("value", "", length_from=lambda pkt:pkt.length - 2)]  # noqa: E501
141
142    def extract_padding(self, p):
143        return b"", p
144
145    registered_ip_options = {}
146
147    @classmethod
148    def register_variant(cls):
149        cls.registered_ip_options[cls.option.default] = cls
150
151    @classmethod
152    def dispatch_hook(cls, pkt=None, *args, **kargs):
153        if pkt:
154            opt = orb(pkt[0]) & 0x1f
155            if opt in cls.registered_ip_options:
156                return cls.registered_ip_options[opt]
157        return cls
158
159
160class IPOption_EOL(IPOption):
161    name = "IP Option End of Options List"
162    option = 0
163    fields_desc = [_IPOption_HDR]
164
165
166class IPOption_NOP(IPOption):
167    name = "IP Option No Operation"
168    option = 1
169    fields_desc = [_IPOption_HDR]
170
171
172class IPOption_Security(IPOption):
173    name = "IP Option Security"
174    copy_flag = 1
175    option = 2
176    fields_desc = [_IPOption_HDR,
177                   ByteField("length", 11),
178                   ShortField("security", 0),
179                   ShortField("compartment", 0),
180                   ShortField("handling_restrictions", 0),
181                   StrFixedLenField("transmission_control_code", "xxx", 3),
182                   ]
183
184
185class IPOption_RR(IPOption):
186    name = "IP Option Record Route"
187    option = 7
188    fields_desc = [_IPOption_HDR,
189                   FieldLenField("length", None, fmt="B",
190                                 length_of="routers", adjust=lambda pkt, l:l + 3),  # noqa: E501
191                   ByteField("pointer", 4),  # 4 is first IP
192                   FieldListField("routers", [], IPField("", "0.0.0.0"),
193                                  length_from=lambda pkt:pkt.length - 3)
194                   ]
195
196    def get_current_router(self):
197        return self.routers[self.pointer // 4 - 1]
198
199
200class IPOption_LSRR(IPOption_RR):
201    name = "IP Option Loose Source and Record Route"
202    copy_flag = 1
203    option = 3
204
205
206class IPOption_SSRR(IPOption_RR):
207    name = "IP Option Strict Source and Record Route"
208    copy_flag = 1
209    option = 9
210
211
212class IPOption_Stream_Id(IPOption):
213    name = "IP Option Stream ID"
214    copy_flag = 1
215    option = 8
216    fields_desc = [_IPOption_HDR,
217                   ByteField("length", 4),
218                   ShortField("security", 0), ]
219
220
221class IPOption_MTU_Probe(IPOption):
222    name = "IP Option MTU Probe"
223    option = 11
224    fields_desc = [_IPOption_HDR,
225                   ByteField("length", 4),
226                   ShortField("mtu", 0), ]
227
228
229class IPOption_MTU_Reply(IPOption_MTU_Probe):
230    name = "IP Option MTU Reply"
231    option = 12
232
233
234class IPOption_Traceroute(IPOption):
235    name = "IP Option Traceroute"
236    option = 18
237    fields_desc = [_IPOption_HDR,
238                   ByteField("length", 12),
239                   ShortField("id", 0),
240                   ShortField("outbound_hops", 0),
241                   ShortField("return_hops", 0),
242                   IPField("originator_ip", "0.0.0.0")]
243
244
245class IPOption_Timestamp(IPOption):
246    name = "IP Option Timestamp"
247    optclass = 2
248    option = 4
249    fields_desc = [_IPOption_HDR,
250                   ByteField("length", None),
251                   ByteField("pointer", 9),
252                   BitField("oflw", 0, 4),
253                   BitEnumField("flg", 1, 4,
254                                {0: "timestamp_only",
255                                 1: "timestamp_and_ip_addr",
256                                 3: "prespecified_ip_addr"}),
257                   ConditionalField(IPField("internet_address", "0.0.0.0"),
258                                    lambda pkt: pkt.flg != 0),
259                   IntField('timestamp', 0)]
260
261    def post_build(self, p, pay):
262        if self.length is None:
263            p = p[:1] + struct.pack("!B", len(p)) + p[2:]
264        return p + pay
265
266
267class IPOption_Address_Extension(IPOption):
268    name = "IP Option Address Extension"
269    copy_flag = 1
270    option = 19
271    fields_desc = [_IPOption_HDR,
272                   ByteField("length", 10),
273                   IPField("src_ext", "0.0.0.0"),
274                   IPField("dst_ext", "0.0.0.0")]
275
276
277class IPOption_Router_Alert(IPOption):
278    name = "IP Option Router Alert"
279    copy_flag = 1
280    option = 20
281    fields_desc = [_IPOption_HDR,
282                   ByteField("length", 4),
283                   ShortEnumField("alert", 0, {0: "router_shall_examine_packet"}), ]  # noqa: E501
284
285
286class IPOption_SDBM(IPOption):
287    name = "IP Option Selective Directed Broadcast Mode"
288    copy_flag = 1
289    option = 21
290    fields_desc = [_IPOption_HDR,
291                   FieldLenField("length", None, fmt="B",
292                                 length_of="addresses", adjust=lambda pkt, l:l + 2),  # noqa: E501
293                   FieldListField("addresses", [], IPField("", "0.0.0.0"),
294                                  length_from=lambda pkt:pkt.length - 2)
295                   ]
296
297
298TCPOptions = (
299    {0: ("EOL", None),
300     1: ("NOP", None),
301     2: ("MSS", "!H"),
302     3: ("WScale", "!B"),
303     4: ("SAckOK", None),
304     5: ("SAck", "!"),
305     8: ("Timestamp", "!II"),
306     14: ("AltChkSum", "!BH"),
307     15: ("AltChkSumOpt", None),
308     19: ("MD5", "16s"),
309     25: ("Mood", "!p"),
310     28: ("UTO", "!H"),
311     29: ("AO", None),
312     34: ("TFO", "!II"),
313     # RFC 3692
314     # 253: ("Experiment", "!HHHH"),
315     # 254: ("Experiment", "!HHHH"),
316     },
317    {"EOL": 0,
318     "NOP": 1,
319     "MSS": 2,
320     "WScale": 3,
321     "SAckOK": 4,
322     "SAck": 5,
323     "Timestamp": 8,
324     "AltChkSum": 14,
325     "AltChkSumOpt": 15,
326     "MD5": 19,
327     "Mood": 25,
328     "UTO": 28,
329     "AO": 29,
330     "TFO": 34,
331     })
332
333
334class TCPAOValue(Packet):
335    """Value of TCP-AO option"""
336    fields_desc = [
337        ByteField("keyid", None),
338        ByteField("rnextkeyid", None),
339        StrLenField("mac", "", length_from=lambda p:len(p.original) - 2),
340    ]
341
342
343def get_tcpao(tcphdr):
344    # type: (TCP) -> Optional[TCPAOValue]
345    """Get the TCP-AO option from the header"""
346    for optid, optval in tcphdr.options:
347        if optid == 'AO':
348            return optval
349    return None
350
351
352class RandTCPOptions(VolatileValue):
353    def __init__(self, size=None):
354        if size is None:
355            size = RandNum(1, 5)
356        self.size = size
357
358    def _fix(self):
359        # Pseudo-Random amount of options
360        # Random ("NAME", fmt)
361        rand_patterns = [
362            random.choice(list(
363                (opt, fmt) for opt, fmt in TCPOptions[0].values()
364                if opt != 'EOL'
365            ))
366            for _ in range(self.size)
367        ]
368        rand_vals = []
369        for oname, fmt in rand_patterns:
370            if fmt is None:
371                rand_vals.append((oname, b''))
372            else:
373                # Process the fmt arguments 1 by 1
374                structs = re.findall(r"!?([bBhHiIlLqQfdpP]|\d+[spx])", fmt)
375                rval = []
376                for stru in structs:
377                    stru = "!" + stru
378                    if "s" in stru or "p" in stru:  # str / chr
379                        v = bytes(RandBin(struct.calcsize(stru)))
380                    else:  # int
381                        _size = struct.calcsize(stru)
382                        v = random.randint(0, 2 ** (8 * _size) - 1)
383                    rval.append(v)
384                rand_vals.append((oname, tuple(rval)))
385        return rand_vals
386
387    def __bytes__(self):
388        return TCPOptionsField.i2m(None, None, self._fix())
389
390
391class TCPOptionsField(StrField):
392    islist = 1
393
394    def getfield(self, pkt, s):
395        opsz = (pkt.dataofs - 5) * 4
396        if opsz < 0:
397            log_runtime.info(
398                "bad dataofs (%i). Assuming dataofs=5", pkt.dataofs
399            )
400            opsz = 0
401        return s[opsz:], self.m2i(pkt, s[:opsz])
402
403    def m2i(self, pkt, x):
404        opt = []
405        while x:
406            onum = orb(x[0])
407            if onum == 0:
408                opt.append(("EOL", None))
409                break
410            if onum == 1:
411                opt.append(("NOP", None))
412                x = x[1:]
413                continue
414            try:
415                olen = orb(x[1])
416            except IndexError:
417                olen = 0
418            if olen < 2:
419                log_runtime.info(
420                    "Malformed TCP option (announced length is %i)", olen
421                )
422                olen = 2
423            oval = x[2:olen]
424            if onum in TCPOptions[0]:
425                oname, ofmt = TCPOptions[0][onum]
426                if onum == 5:  # SAck
427                    ofmt += "%iI" % (len(oval) // 4)
428                if onum == 29:  # AO
429                    oval = TCPAOValue(oval)
430                if ofmt and struct.calcsize(ofmt) == len(oval):
431                    oval = struct.unpack(ofmt, oval)
432                    if len(oval) == 1:
433                        oval = oval[0]
434                opt.append((oname, oval))
435            else:
436                opt.append((onum, oval))
437            x = x[olen:]
438        return opt
439
440    def i2h(self, pkt, x):
441        if not x:
442            return []
443        return x
444
445    def i2m(self, pkt, x):
446        opt = b""
447        for oname, oval in x:
448            # We check for a (0, b'') or (1, b'') option first
449            oname = {0: "EOL", 1: "NOP"}.get(oname, oname)
450            if isinstance(oname, str):
451                if oname == "NOP":
452                    opt += b"\x01"
453                    continue
454                elif oname == "EOL":
455                    opt += b"\x00"
456                    continue
457                elif oname in TCPOptions[1]:
458                    onum = TCPOptions[1][oname]
459                    ofmt = TCPOptions[0][onum][1]
460                    if onum == 5:  # SAck
461                        ofmt += "%iI" % len(oval)
462                    _test_isinstance = not isinstance(oval, (bytes, str))
463                    if ofmt is not None and (_test_isinstance or "s" in ofmt):
464                        if not isinstance(oval, tuple):
465                            oval = (oval,)
466                        oval = struct.pack(ofmt, *oval)
467                    if onum == 29:  # AO
468                        oval = bytes(oval)
469                else:
470                    warning("Option [%s] unknown. Skipped.", oname)
471                    continue
472            else:
473                onum = oname
474                if not isinstance(onum, int):
475                    warning("Invalid option number [%i]" % onum)
476                    continue
477                if not isinstance(oval, (bytes, str)):
478                    warning("Option [%i] is not bytes." % onum)
479                    continue
480            if isinstance(oval, str):
481                oval = bytes_encode(oval)
482            opt += chb(onum) + chb(2 + len(oval)) + oval
483        return opt + b"\x00" * (3 - ((len(opt) + 3) % 4))  # Padding
484
485    def randval(self):
486        return RandTCPOptions()
487
488
489class ICMPTimeStampField(IntField):
490    re_hmsm = re.compile("([0-2]?[0-9])[Hh:](([0-5]?[0-9])([Mm:]([0-5]?[0-9])([sS:.]([0-9]{0,3}))?)?)?$")  # noqa: E501
491
492    def i2repr(self, pkt, val):
493        if val is None:
494            return "--"
495        else:
496            sec, milli = divmod(val, 1000)
497            min, sec = divmod(sec, 60)
498            hour, min = divmod(min, 60)
499            return "%d:%d:%d.%d" % (hour, min, sec, int(milli))
500
501    def any2i(self, pkt, val):
502        if isinstance(val, str):
503            hmsms = self.re_hmsm.match(val)
504            if hmsms:
505                h, _, m, _, s, _, ms = hmsms.groups()
506                ms = int(((ms or "") + "000")[:3])
507                val = ((int(h) * 60 + int(m or 0)) * 60 + int(s or 0)) * 1000 + ms  # noqa: E501
508            else:
509                val = 0
510        elif val is None:
511            val = int((time.time() % (24 * 60 * 60)) * 1000)
512        return val
513
514
515class DestIPField(IPField, DestField):
516    bindings = {}
517
518    def __init__(self, name, default):
519        IPField.__init__(self, name, None)
520        DestField.__init__(self, name, default)
521
522    def i2m(self, pkt, x):
523        if x is None:
524            x = self.dst_from_pkt(pkt)
525        return IPField.i2m(self, pkt, x)
526
527    def i2h(self, pkt, x):
528        if x is None:
529            x = self.dst_from_pkt(pkt)
530        return IPField.i2h(self, pkt, x)
531
532
533class IP(Packet, IPTools):
534    name = "IP"
535    fields_desc = [BitField("version", 4, 4),
536                   BitField("ihl", None, 4),
537                   XByteField("tos", 0),
538                   ShortField("len", None),
539                   ShortField("id", 1),
540                   FlagsField("flags", 0, 3, ["MF", "DF", "evil"]),
541                   BitField("frag", 0, 13),
542                   ByteField("ttl", 64),
543                   ByteEnumField("proto", 0, IP_PROTOS),
544                   XShortField("chksum", None),
545                   # IPField("src", "127.0.0.1"),
546                   Emph(SourceIPField("src")),
547                   Emph(DestIPField("dst", "127.0.0.1")),
548                   PacketListField("options", [], IPOption, length_from=lambda p:p.ihl * 4 - 20)]  # noqa: E501
549
550    def post_build(self, p, pay):
551        ihl = self.ihl
552        p += b"\0" * ((-len(p)) % 4)  # pad IP options if needed
553        if ihl is None:
554            ihl = len(p) // 4
555            p = chb(((self.version & 0xf) << 4) | ihl & 0x0f) + p[1:]
556        if self.len is None:
557            tmp_len = len(p) + len(pay)
558            p = p[:2] + struct.pack("!H", tmp_len) + p[4:]
559        if self.chksum is None:
560            ck = checksum(p)
561            p = p[:10] + chb(ck >> 8) + chb(ck & 0xff) + p[12:]
562        return p + pay
563
564    def extract_padding(self, s):
565        tmp_len = self.len - (self.ihl << 2)
566        if tmp_len < 0:
567            return s, b""
568        return s[:tmp_len], s[tmp_len:]
569
570    def route(self):
571        dst = self.dst
572        scope = None
573        if isinstance(dst, (Net, _ScopedIP)):
574            scope = dst.scope
575        if isinstance(dst, (Gen, list)):
576            dst = next(iter(dst))
577        if conf.route is None:
578            # unused import, only to initialize conf.route
579            import scapy.route  # noqa: F401
580        return conf.route.route(dst, dev=scope)
581
582    def hashret(self):
583        if ((self.proto == socket.IPPROTO_ICMP) and
584            (isinstance(self.payload, ICMP)) and
585                (self.payload.type in [3, 4, 5, 11, 12])):
586            return self.payload.payload.hashret()
587        if not conf.checkIPinIP and self.proto in [4, 41]:  # IP, IPv6
588            return self.payload.hashret()
589        if self.dst == "224.0.0.251":  # mDNS
590            return struct.pack("B", self.proto) + self.payload.hashret()
591        if conf.checkIPsrc and conf.checkIPaddr:
592            return (strxor(inet_pton(socket.AF_INET, self.src),
593                           inet_pton(socket.AF_INET, self.dst)) +
594                    struct.pack("B", self.proto) + self.payload.hashret())
595        return struct.pack("B", self.proto) + self.payload.hashret()
596
597    def answers(self, other):
598        if not conf.checkIPinIP:  # skip IP in IP and IPv6 in IP
599            if self.proto in [4, 41]:
600                return self.payload.answers(other)
601            if isinstance(other, IP) and other.proto in [4, 41]:
602                return self.answers(other.payload)
603            if conf.ipv6_enabled \
604               and isinstance(other, scapy.layers.inet6.IPv6) \
605               and other.nh in [4, 41]:
606                return self.answers(other.payload)
607        if not isinstance(other, IP):
608            return 0
609        if conf.checkIPaddr:
610            if other.dst == "224.0.0.251" and self.dst == "224.0.0.251":  # mDNS  # noqa: E501
611                return self.payload.answers(other.payload)
612            elif (self.dst != other.src):
613                return 0
614        if ((self.proto == socket.IPPROTO_ICMP) and
615            (isinstance(self.payload, ICMP)) and
616                (self.payload.type in [3, 4, 5, 11, 12])):
617            # ICMP error message
618            return self.payload.payload.answers(other)
619
620        else:
621            if ((conf.checkIPaddr and (self.src != other.dst)) or
622                    (self.proto != other.proto)):
623                return 0
624            return self.payload.answers(other.payload)
625
626    def mysummary(self):
627        s = self.sprintf("%IP.src% > %IP.dst% %IP.proto%")
628        if self.frag:
629            s += " frag:%i" % self.frag
630        return s
631
632    def fragment(self, fragsize=1480):
633        """Fragment IP datagrams"""
634        return fragment(self, fragsize=fragsize)
635
636
637def in4_pseudoheader(proto, u, plen):
638    # type: (int, IP, int) -> bytes
639    """IPv4 Pseudo Header as defined in RFC793 as bytes
640
641    :param proto: value of upper layer protocol
642    :param u: IP layer instance
643    :param plen: the length of the upper layer and payload
644    """
645    u = u.copy()
646    if u.len is not None:
647        if u.ihl is None:
648            olen = sum(len(x) for x in u.options)
649            ihl = 5 + olen // 4 + (1 if olen % 4 else 0)
650        else:
651            ihl = u.ihl
652        ln = max(u.len - 4 * ihl, 0)
653    else:
654        ln = plen
655
656    # Filter out IPOption_LSRR and IPOption_SSRR
657    sr_options = [opt for opt in u.options if isinstance(opt, IPOption_LSRR) or
658                  isinstance(opt, IPOption_SSRR)]
659    len_sr_options = len(sr_options)
660    if len_sr_options == 1 and len(sr_options[0].routers):
661        # The checksum must be computed using the final
662        # destination address
663        u.dst = sr_options[0].routers[-1]
664    elif len_sr_options > 1:
665        message = "Found %d Source Routing Options! "
666        message += "Falling back to IP.dst for checksum computation."
667        warning(message, len_sr_options)
668
669    return struct.pack("!4s4sHH",
670                       inet_pton(socket.AF_INET, u.src),
671                       inet_pton(socket.AF_INET, u.dst),
672                       proto,
673                       ln)
674
675
676def in4_chksum(proto, u, p):
677    # type: (int, IP, bytes) -> int
678    """IPv4 Pseudo Header checksum as defined in RFC793
679
680    :param proto: value of upper layer protocol
681    :param u: upper layer instance
682    :param p: the payload of the upper layer provided as a string
683    """
684    if not isinstance(u, IP):
685        warning("No IP underlayer to compute checksum. Leaving null.")
686        return 0
687    psdhdr = in4_pseudoheader(proto, u, len(p))
688    return checksum(psdhdr + p)
689
690
691def _is_ipv6_layer(p):
692    # type: (Packet) -> bytes
693    return (isinstance(p, scapy.layers.inet6.IPv6) or
694            isinstance(p, scapy.layers.inet6._IPv6ExtHdr))
695
696
697def tcp_pseudoheader(tcp):
698    # type: (TCP) -> bytes
699    """Pseudoheader of a TCP packet as bytes
700
701    Requires underlayer to be either IP or IPv6
702    """
703    if isinstance(tcp.underlayer, IP):
704        plen = len(bytes(tcp))
705        return in4_pseudoheader(socket.IPPROTO_TCP, tcp.underlayer, plen)
706    elif conf.ipv6_enabled and _is_ipv6_layer(tcp.underlayer):
707        plen = len(bytes(tcp))
708        return raw(scapy.layers.inet6.in6_pseudoheader(
709            socket.IPPROTO_TCP, tcp.underlayer, plen))
710    else:
711        raise ValueError("TCP packet does not have IP or IPv6 underlayer")
712
713
714def calc_tcp_md5_hash(tcp, key):
715    # type: (TCP, bytes) -> bytes
716    """Calculate TCP-MD5 hash from packet and return a 16-byte string"""
717    import hashlib
718
719    h = hashlib.md5()  # nosec
720    tcp_bytes = bytes(tcp)
721    h.update(tcp_pseudoheader(tcp))
722    h.update(tcp_bytes[:16])
723    h.update(b"\x00\x00")
724    h.update(tcp_bytes[18:])
725    h.update(key)
726
727    return h.digest()
728
729
730def sign_tcp_md5(tcp, key):
731    # type: (TCP, bytes) -> None
732    """Append TCP-MD5 signature to tcp packet"""
733    sig = calc_tcp_md5_hash(tcp, key)
734    tcp.options = tcp.options + [('MD5', sig)]
735
736
737class TCP(Packet):
738    name = "TCP"
739    fields_desc = [ShortEnumField("sport", 20, TCP_SERVICES),
740                   ShortEnumField("dport", 80, TCP_SERVICES),
741                   IntField("seq", 0),
742                   IntField("ack", 0),
743                   BitField("dataofs", None, 4),
744                   BitField("reserved", 0, 3),
745                   FlagsField("flags", 0x2, 9, "FSRPAUECN"),
746                   ShortField("window", 8192),
747                   XShortField("chksum", None),
748                   ShortField("urgptr", 0),
749                   TCPOptionsField("options", "")]
750
751    def post_build(self, p, pay):
752        p += pay
753        dataofs = self.dataofs
754        if dataofs is None:
755            opt_len = len(self.get_field("options").i2m(self, self.options))
756            dataofs = 5 + ((opt_len + 3) // 4)
757            dataofs = (dataofs << 4) | orb(p[12]) & 0x0f
758            p = p[:12] + chb(dataofs & 0xff) + p[13:]
759        if self.chksum is None:
760            if isinstance(self.underlayer, IP):
761                ck = in4_chksum(socket.IPPROTO_TCP, self.underlayer, p)
762                p = p[:16] + struct.pack("!H", ck) + p[18:]
763            elif conf.ipv6_enabled and isinstance(self.underlayer, scapy.layers.inet6.IPv6) or isinstance(self.underlayer, scapy.layers.inet6._IPv6ExtHdr):  # noqa: E501
764                ck = scapy.layers.inet6.in6_chksum(socket.IPPROTO_TCP, self.underlayer, p)  # noqa: E501
765                p = p[:16] + struct.pack("!H", ck) + p[18:]
766            else:
767                log_runtime.info(
768                    "No IP underlayer to compute checksum. Leaving null."
769                )
770        return p
771
772    def hashret(self):
773        if conf.checkIPsrc:
774            return struct.pack("H", self.sport ^ self.dport) + self.payload.hashret()  # noqa: E501
775        else:
776            return self.payload.hashret()
777
778    def answers(self, other):
779        if not isinstance(other, TCP):
780            return 0
781        # RST packets don't get answers
782        if other.flags.R:
783            return 0
784        # We do not support the four-way handshakes with the SYN+ACK
785        # answer split in two packets (one ACK and one SYN): in that
786        # case the ACK will be seen as an answer, but not the SYN.
787        if self.flags.S:
788            # SYN packets without ACK are not answers
789            if not self.flags.A:
790                return 0
791            # SYN+ACK packets answer SYN packets
792            if not other.flags.S:
793                return 0
794        if conf.checkIPsrc:
795            if not ((self.sport == other.dport) and
796                    (self.dport == other.sport)):
797                return 0
798        # Do not check ack value for SYN packets without ACK
799        if not (other.flags.S and not other.flags.A) \
800           and abs(other.ack - self.seq) > 2:
801            return 0
802        # Do not check ack value for RST packets without ACK
803        if self.flags.R and not self.flags.A:
804            return 1
805        if abs(other.seq - self.ack) > 2 + len(other.payload):
806            return 0
807        return 1
808
809    def mysummary(self):
810        if isinstance(self.underlayer, IP):
811            return self.underlayer.sprintf("TCP %IP.src%:%TCP.sport% > %IP.dst%:%TCP.dport% %TCP.flags%")  # noqa: E501
812        elif conf.ipv6_enabled and isinstance(self.underlayer, scapy.layers.inet6.IPv6):  # noqa: E501
813            return self.underlayer.sprintf("TCP %IPv6.src%:%TCP.sport% > %IPv6.dst%:%TCP.dport% %TCP.flags%")  # noqa: E501
814        else:
815            return self.sprintf("TCP %TCP.sport% > %TCP.dport% %TCP.flags%")
816
817
818class UDP(Packet):
819    name = "UDP"
820    fields_desc = [ShortEnumField("sport", 53, UDP_SERVICES),
821                   ShortEnumField("dport", 53, UDP_SERVICES),
822                   ShortField("len", None),
823                   XShortField("chksum", None), ]
824
825    def post_build(self, p, pay):
826        p += pay
827        tmp_len = self.len
828        if tmp_len is None:
829            tmp_len = len(p)
830            p = p[:4] + struct.pack("!H", tmp_len) + p[6:]
831        if self.chksum is None:
832            if isinstance(self.underlayer, IP):
833                ck = in4_chksum(socket.IPPROTO_UDP, self.underlayer, p)
834                # According to RFC768 if the result checksum is 0, it should be set to 0xFFFF  # noqa: E501
835                if ck == 0:
836                    ck = 0xFFFF
837                p = p[:6] + struct.pack("!H", ck) + p[8:]
838            elif isinstance(self.underlayer, scapy.layers.inet6.IPv6) or isinstance(self.underlayer, scapy.layers.inet6._IPv6ExtHdr):  # noqa: E501
839                ck = scapy.layers.inet6.in6_chksum(socket.IPPROTO_UDP, self.underlayer, p)  # noqa: E501
840                # According to RFC2460 if the result checksum is 0, it should be set to 0xFFFF  # noqa: E501
841                if ck == 0:
842                    ck = 0xFFFF
843                p = p[:6] + struct.pack("!H", ck) + p[8:]
844            else:
845                log_runtime.info(
846                    "No IP underlayer to compute checksum. Leaving null."
847                )
848        return p
849
850    def extract_padding(self, s):
851        tmp_len = self.len - 8
852        return s[:tmp_len], s[tmp_len:]
853
854    def hashret(self):
855        return self.payload.hashret()
856
857    def answers(self, other):
858        if not isinstance(other, UDP):
859            return 0
860        if conf.checkIPsrc:
861            if self.dport != other.sport:
862                return 0
863        return self.payload.answers(other.payload)
864
865    def mysummary(self):
866        if isinstance(self.underlayer, IP):
867            return self.underlayer.sprintf("UDP %IP.src%:%UDP.sport% > %IP.dst%:%UDP.dport%")  # noqa: E501
868        elif isinstance(self.underlayer, scapy.layers.inet6.IPv6):
869            return self.underlayer.sprintf("UDP %IPv6.src%:%UDP.sport% > %IPv6.dst%:%UDP.dport%")  # noqa: E501
870        else:
871            return self.sprintf("UDP %UDP.sport% > %UDP.dport%")
872
873
874# RFC 4884 ICMP extensions
875_ICMP_classnums = {
876    # https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml#icmp-parameters-ext-classes
877    1: "MPLS",
878    2: "Interface Information",
879    3: "Interface Identification",
880    4: "Extended Information",
881}
882
883
884class ICMPExtension_Object(Packet):
885    name = "ICMP Extension Object"
886    show_indent = 0
887    fields_desc = [
888        ShortField("len", None),
889        ByteEnumField("classnum", 0, _ICMP_classnums),
890        ByteField("classtype", 0),
891    ]
892
893    def post_build(self, p, pay):
894        if self.len is None:
895            tmp_len = len(p) + len(pay)
896            p = struct.pack("!H", tmp_len) + p[2:]
897        return p + pay
898
899    registered_icmp_exts = {}
900
901    @classmethod
902    def register_variant(cls):
903        cls.registered_icmp_exts[cls.classnum.default] = cls
904
905    @classmethod
906    def dispatch_hook(cls, _pkt=None, *args, **kargs):
907        if _pkt and len(_pkt) >= 4:
908            classnum = _pkt[2]
909            if classnum in cls.registered_icmp_exts:
910                return cls.registered_icmp_exts[classnum]
911        return cls
912
913
914class ICMPExtension_InterfaceInformation(ICMPExtension_Object):
915    name = "ICMP Extension Object - Interface Information Object (RFC5837)"
916
917    fields_desc = [
918        ShortField("len", None),
919        ByteEnumField("classnum", 2, _ICMP_classnums),
920        BitField("classtype", 0, 2),
921        BitField("reserved", 0, 2),
922        BitField("has_ifindex", 0, 1),
923        BitField("has_ipaddr", 0, 1),
924        BitField("has_ifname", 0, 1),
925        BitField("has_mtu", 0, 1),
926        ConditionalField(IntField("ifindex", None), lambda pkt: pkt.has_ifindex == 1),
927        ConditionalField(ShortField("afi", None), lambda pkt: pkt.has_ipaddr == 1),
928        ConditionalField(ShortField("reserved2", 0), lambda pkt: pkt.has_ipaddr == 1),
929        ConditionalField(IPField("ip4", None), lambda pkt: pkt.afi == 1),
930        ConditionalField(IP6Field("ip6", None), lambda pkt: pkt.afi == 2),
931        ConditionalField(
932            FieldLenField("ifname_len", None, fmt="B", length_of="ifname"),
933            lambda pkt: pkt.has_ifname == 1,
934        ),
935        ConditionalField(
936            StrLenField("ifname", None, length_from=lambda pkt: pkt.ifname_len),
937            lambda pkt: pkt.has_ifname == 1,
938        ),
939        ConditionalField(IntField("mtu", None), lambda pkt: pkt.has_mtu == 1),
940    ]
941
942    def self_build(self, **kwargs):
943        if self.afi is None:
944            if self.ip4 is not None:
945                self.afi = 1
946            elif self.ip6 is not None:
947                self.afi = 2
948        return ICMPExtension_Object.self_build(self, **kwargs)
949
950
951class ICMPExtension_Header(Packet):
952    r"""
953    ICMP Extension per RFC4884.
954
955    Example::
956
957        pkt = IP(dst="127.0.0.1", src="127.0.0.1") / ICMP(
958            type="time-exceeded",
959            code="ttl-zero-during-transit",
960            ext=ICMPExtension_Header() / ICMPExtension_InterfaceInformation(
961                has_ifindex=1,
962                has_ipaddr=1,
963                has_ifname=1,
964                ip4="10.10.10.10",
965                ifname="hey",
966            )
967        ) / IPerror(src="12.4.4.4", dst="12.1.1.1") / \
968            UDPerror(sport=42315, dport=33440) /  \
969            b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
970    """
971
972    name = "ICMP Extension Header (RFC4884)"
973    show_indent = 0
974    fields_desc = [
975        BitField("version", 2, 4),
976        BitField("reserved", 0, 12),
977        XShortField("chksum", None),
978    ]
979
980    _min_ieo_len = len(ICMPExtension_Object())
981
982    def post_build(self, p, pay):
983        p += pay
984        if self.chksum is None:
985            ck = checksum(p)
986            p = p[:2] + chb(ck >> 8) + chb(ck & 0xFF) + p[4:]
987        return p
988
989    def guess_payload_class(self, payload):
990        if len(payload) < self._min_ieo_len:
991            return Packet.guess_payload_class(self, payload)
992        return ICMPExtension_Object
993
994
995class _ICMPExtensionField(TrailerField):
996    # We use a TrailerField for building only. Dissection is normal.
997
998    def __init__(self):
999        super(_ICMPExtensionField, self).__init__(
1000            PacketField(
1001                "ext",
1002                None,
1003                ICMPExtension_Header,
1004            ),
1005        )
1006
1007    def getfield(self, pkt, s):
1008        # RFC4884 section 5.2 says if the ICMP packet length
1009        # is >144 then ICMP extensions start at byte 137.
1010        if len(pkt.original) < 144:
1011            return s, None
1012        offset = 136 + len(s) - len(pkt.original)
1013        data = s[offset:]
1014        # Validate checksum
1015        if checksum(data) == data[3:5]:
1016            return s, None  # failed
1017        # Dissect
1018        return s[:offset], ICMPExtension_Header(data)
1019
1020    def addfield(self, pkt, s, val):
1021        if val is None:
1022            return s
1023        data = bytes(val)
1024        # Calc how much padding we need, not how much we deserve
1025        pad = 136 - len(pkt.payload) - len(s)
1026        if pad < 0:
1027            warning("ICMPExtension_Header is after the 136th octet of ICMP.")
1028            return data
1029        return super(_ICMPExtensionField, self).addfield(pkt, s, b"\x00" * pad + data)
1030
1031
1032class _ICMPExtensionPadField(TrailerField):
1033    def __init__(self):
1034        super(_ICMPExtensionPadField, self).__init__(
1035            StrFixedLenField("extpad", "", length=0)
1036        )
1037
1038    def i2repr(self, pkt, s):
1039        if s and s == b"\x00" * len(s):
1040            return "b'' (%s octets)" % len(s)
1041        return self.fld.i2repr(pkt, s)
1042
1043
1044def _ICMP_extpad_post_dissection(self, pkt):
1045    # If we have padding, put it in 'extpad' for re-build
1046    if pkt.ext:
1047        pad = pkt.lastlayer()
1048        if isinstance(pad, conf.padding_layer):
1049            pad.underlayer.remove_payload()
1050            pkt.extpad = pad.load
1051
1052
1053icmptypes = {0: "echo-reply",
1054             3: "dest-unreach",
1055             4: "source-quench",
1056             5: "redirect",
1057             8: "echo-request",
1058             9: "router-advertisement",
1059             10: "router-solicitation",
1060             11: "time-exceeded",
1061             12: "parameter-problem",
1062             13: "timestamp-request",
1063             14: "timestamp-reply",
1064             15: "information-request",
1065             16: "information-response",
1066             17: "address-mask-request",
1067             18: "address-mask-reply",
1068             30: "traceroute",
1069             31: "datagram-conversion-error",
1070             32: "mobile-host-redirect",
1071             33: "ipv6-where-are-you",
1072             34: "ipv6-i-am-here",
1073             35: "mobile-registration-request",
1074             36: "mobile-registration-reply",
1075             37: "domain-name-request",
1076             38: "domain-name-reply",
1077             39: "skip",
1078             40: "photuris"}
1079
1080
1081icmpcodes = {3: {0: "network-unreachable",
1082                    1: "host-unreachable",
1083                    2: "protocol-unreachable",
1084                    3: "port-unreachable",
1085                    4: "fragmentation-needed",
1086                    5: "source-route-failed",
1087                    6: "network-unknown",
1088                    7: "host-unknown",
1089                    9: "network-prohibited",
1090                    10: "host-prohibited",
1091                    11: "TOS-network-unreachable",
1092                    12: "TOS-host-unreachable",
1093                    13: "communication-prohibited",
1094                    14: "host-precedence-violation",
1095                    15: "precedence-cutoff", },
1096             5: {0: "network-redirect",
1097                 1: "host-redirect",
1098                 2: "TOS-network-redirect",
1099                 3: "TOS-host-redirect", },
1100             11: {0: "ttl-zero-during-transit",
1101                  1: "ttl-zero-during-reassembly", },
1102             12: {0: "ip-header-bad",
1103                  1: "required-option-missing", },
1104             40: {0: "bad-spi",
1105                  1: "authentication-failed",
1106                  2: "decompression-failed",
1107                  3: "decryption-failed",
1108                  4: "need-authentification",
1109                  5: "need-authorization", }, }
1110
1111
1112_icmp_answers = [
1113    (8, 0),
1114    (13, 14),
1115    (15, 16),
1116    (17, 18),
1117    (33, 34),
1118    (35, 36),
1119    (37, 38),
1120]
1121
1122icmp_id_seq_types = [0, 8, 13, 14, 15, 16, 17, 18, 37, 38]
1123
1124
1125class ICMP(Packet):
1126    name = "ICMP"
1127    fields_desc = [
1128        ByteEnumField("type", 8, icmptypes),
1129        MultiEnumField("code", 0, icmpcodes,
1130                       depends_on=lambda pkt:pkt.type, fmt="B"),
1131        XShortField("chksum", None),
1132        ConditionalField(
1133            XShortField("id", 0),
1134            lambda pkt: pkt.type in icmp_id_seq_types
1135        ),
1136        ConditionalField(
1137            XShortField("seq", 0),
1138            lambda pkt: pkt.type in icmp_id_seq_types
1139        ),
1140        ConditionalField(
1141            # Timestamp only (RFC792)
1142            ICMPTimeStampField("ts_ori", None),
1143            lambda pkt: pkt.type in [13, 14]
1144        ),
1145        ConditionalField(
1146            # Timestamp only (RFC792)
1147            ICMPTimeStampField("ts_rx", None),
1148            lambda pkt: pkt.type in [13, 14]
1149        ),
1150        ConditionalField(
1151            # Timestamp only (RFC792)
1152            ICMPTimeStampField("ts_tx", None),
1153            lambda pkt: pkt.type in [13, 14]
1154        ),
1155        ConditionalField(
1156            # Redirect only (RFC792)
1157            IPField("gw", "0.0.0.0"),
1158            lambda pkt: pkt.type == 5
1159        ),
1160        ConditionalField(
1161            # Parameter problem only (RFC792)
1162            ByteField("ptr", 0),
1163            lambda pkt: pkt.type == 12
1164        ),
1165        ConditionalField(
1166            ByteField("reserved", 0),
1167            lambda pkt: pkt.type in [3, 11]
1168        ),
1169        ConditionalField(
1170            ByteField("length", 0),
1171            lambda pkt: pkt.type in [3, 11, 12]
1172        ),
1173        ConditionalField(
1174            IPField("addr_mask", "0.0.0.0"),
1175            lambda pkt: pkt.type in [17, 18]
1176        ),
1177        ConditionalField(
1178            ShortField("nexthopmtu", 0),
1179            lambda pkt: pkt.type == 3
1180        ),
1181        MultipleTypeField(
1182            [
1183                (ShortField("unused", 0),
1184                    lambda pkt:pkt.type in [11, 12]),
1185                (IntField("unused", 0),
1186                    lambda pkt:pkt.type not in [0, 3, 5, 8, 11, 12,
1187                                                13, 14, 15, 16, 17,
1188                                                18])
1189            ],
1190            StrFixedLenField("unused", "", length=0),
1191        ),
1192        # RFC4884 ICMP extension
1193        ConditionalField(
1194            _ICMPExtensionPadField(),
1195            lambda pkt: pkt.type in [3, 11, 12],
1196        ),
1197        ConditionalField(
1198            _ICMPExtensionField(),
1199            lambda pkt: pkt.type in [3, 11, 12],
1200        ),
1201    ]
1202
1203    # To handle extpad
1204    post_dissection = _ICMP_extpad_post_dissection
1205
1206    def post_build(self, p, pay):
1207        p += pay
1208        if self.chksum is None:
1209            ck = checksum(p)
1210            p = p[:2] + chb(ck >> 8) + chb(ck & 0xff) + p[4:]
1211        return p
1212
1213    def hashret(self):
1214        if self.type in icmp_id_seq_types:
1215            return struct.pack("HH", self.id, self.seq) + self.payload.hashret()  # noqa: E501
1216        return self.payload.hashret()
1217
1218    def answers(self, other):
1219        if not isinstance(other, ICMP):
1220            return 0
1221        if ((other.type, self.type) in _icmp_answers and
1222            self.id == other.id and
1223                self.seq == other.seq):
1224            return 1
1225        return 0
1226
1227    def guess_payload_class(self, payload):
1228        if self.type in [3, 4, 5, 11, 12]:
1229            return IPerror
1230        else:
1231            return None
1232
1233    def mysummary(self):
1234        extra = ""
1235        if self.ext:
1236            extra = self.ext.payload.sprintf(" ext:%classnum%")
1237        if isinstance(self.underlayer, IP):
1238            return self.underlayer.sprintf(
1239                "ICMP %IP.src% > %IP.dst% %ICMP.type% %ICMP.code%"
1240            ) + extra
1241        else:
1242            return self.sprintf("ICMP %ICMP.type% %ICMP.code%") + extra
1243
1244
1245# IP / TCP / UDP error packets
1246
1247class IPerror(IP):
1248    name = "IP in ICMP"
1249
1250    def answers(self, other):
1251        if not isinstance(other, IP):
1252            return 0
1253
1254        # Check if IP addresses match
1255        test_IPsrc = not conf.checkIPsrc or self.src == other.src
1256        test_IPdst = self.dst == other.dst
1257
1258        # Check if IP ids match
1259        test_IPid = not conf.checkIPID or self.id == other.id
1260        test_IPid |= conf.checkIPID and self.id == socket.htons(other.id)
1261
1262        # Check if IP protocols match
1263        test_IPproto = self.proto == other.proto
1264
1265        if not (test_IPsrc and test_IPdst and test_IPid and test_IPproto):
1266            return 0
1267
1268        return self.payload.answers(other.payload)
1269
1270    def mysummary(self):
1271        return Packet.mysummary(self)
1272
1273
1274class TCPerror(TCP):
1275    name = "TCP in ICMP"
1276    fields_desc = (
1277        TCP.fields_desc[:2] +
1278        # MayEnd after the 8 first octets.
1279        [MayEnd(TCP.fields_desc[2])] +
1280        TCP.fields_desc[3:]
1281    )
1282
1283    def answers(self, other):
1284        if not isinstance(other, TCP):
1285            return 0
1286        if conf.checkIPsrc:
1287            if not ((self.sport == other.sport) and
1288                    (self.dport == other.dport)):
1289                return 0
1290        if conf.check_TCPerror_seqack:
1291            if self.seq is not None:
1292                if self.seq != other.seq:
1293                    return 0
1294            if self.ack is not None:
1295                if self.ack != other.ack:
1296                    return 0
1297        return 1
1298
1299    def mysummary(self):
1300        return Packet.mysummary(self)
1301
1302
1303class UDPerror(UDP):
1304    name = "UDP in ICMP"
1305
1306    def answers(self, other):
1307        if not isinstance(other, UDP):
1308            return 0
1309        if conf.checkIPsrc:
1310            if not ((self.sport == other.sport) and
1311                    (self.dport == other.dport)):
1312                return 0
1313        return 1
1314
1315    def mysummary(self):
1316        return Packet.mysummary(self)
1317
1318
1319class ICMPerror(ICMP):
1320    name = "ICMP in ICMP"
1321
1322    def answers(self, other):
1323        if not isinstance(other, ICMP):
1324            return 0
1325        if not ((self.type == other.type) and
1326                (self.code == other.code)):
1327            return 0
1328        if self.code in [0, 8, 13, 14, 17, 18]:
1329            if (self.id == other.id and
1330                    self.seq == other.seq):
1331                return 1
1332            else:
1333                return 0
1334        else:
1335            return 1
1336
1337    def mysummary(self):
1338        return Packet.mysummary(self)
1339
1340
1341bind_layers(Ether, IP, type=2048)
1342bind_layers(CookedLinux, IP, proto=2048)
1343bind_layers(GRE, IP, proto=2048)
1344bind_layers(SNAP, IP, code=2048)
1345bind_bottom_up(Loopback, IP, type=0)
1346bind_layers(Loopback, IP, type=socket.AF_INET)
1347bind_layers(IPerror, IPerror, frag=0, proto=4)
1348bind_layers(IPerror, ICMPerror, frag=0, proto=1)
1349bind_layers(IPerror, TCPerror, frag=0, proto=6)
1350bind_layers(IPerror, UDPerror, frag=0, proto=17)
1351bind_layers(IP, IP, frag=0, proto=4)
1352bind_layers(IP, ICMP, frag=0, proto=1)
1353bind_layers(IP, TCP, frag=0, proto=6)
1354bind_layers(IP, UDP, frag=0, proto=17)
1355bind_layers(IP, GRE, frag=0, proto=47)
1356bind_layers(UDP, GRE, dport=4754)
1357
1358conf.l2types.register(DLT_RAW, IP)
1359conf.l2types.register_num2layer(DLT_RAW_ALT, IP)
1360conf.l2types.register(DLT_IPV4, IP)
1361
1362conf.l3types.register(ETH_P_IP, IP)
1363conf.l3types.register_num2layer(ETH_P_ALL, IP)
1364
1365
1366def inet_register_l3(l2, l3):
1367    """
1368    Resolves the default L2 destination address when IP is used.
1369    """
1370    return getmacbyip(l3.dst)
1371
1372
1373conf.neighbor.register_l3(Ether, IP, inet_register_l3)
1374conf.neighbor.register_l3(Dot3, IP, inet_register_l3)
1375
1376
1377###################
1378#  Fragmentation  #
1379###################
1380
1381@conf.commands.register
1382def fragment(pkt, fragsize=1480):
1383    """Fragment a big IP datagram"""
1384    if fragsize < 8:
1385        warning("fragsize cannot be lower than 8")
1386        fragsize = max(fragsize, 8)
1387    lastfragsz = fragsize
1388    fragsize -= fragsize % 8
1389    lst = []
1390    for p in pkt:
1391        s = raw(p[IP].payload)
1392        nb = (len(s) - lastfragsz + fragsize - 1) // fragsize + 1
1393        for i in range(nb):
1394            q = p.copy()
1395            del q[IP].payload
1396            del q[IP].chksum
1397            del q[IP].len
1398            if i != nb - 1:
1399                q[IP].flags |= 1
1400                fragend = (i + 1) * fragsize
1401            else:
1402                fragend = i * fragsize + lastfragsz
1403            q[IP].frag += i * fragsize // 8
1404            r = conf.raw_layer(load=s[i * fragsize:fragend])
1405            r.overload_fields = p[IP].payload.overload_fields.copy()
1406            q.add_payload(r)
1407            lst.append(q)
1408    return lst
1409
1410
1411@conf.commands.register
1412def overlap_frag(p, overlap, fragsize=8, overlap_fragsize=None):
1413    """Build overlapping fragments to bypass NIPS
1414
1415p:                the original packet
1416overlap:          the overlapping data
1417fragsize:         the fragment size of the packet
1418overlap_fragsize: the fragment size of the overlapping packet"""
1419
1420    if overlap_fragsize is None:
1421        overlap_fragsize = fragsize
1422    q = p.copy()
1423    del q[IP].payload
1424    q[IP].add_payload(overlap)
1425
1426    qfrag = fragment(q, overlap_fragsize)
1427    qfrag[-1][IP].flags |= 1
1428    return qfrag + fragment(p, fragsize)
1429
1430
1431class BadFragments(ValueError):
1432    def __init__(self, *args, **kwargs):
1433        self.frags = kwargs.pop("frags", None)
1434        super(BadFragments, self).__init__(*args, **kwargs)
1435
1436
1437def _defrag_iter_and_check_offsets(frags):
1438    """
1439    Internal generator used in _defrag_ip_pkt
1440    """
1441    offset = 0
1442    for pkt, o, length in frags:
1443        if offset != o:
1444            if offset > o:
1445                op = ">"
1446            else:
1447                op = "<"
1448            warning("Fragment overlap (%i %s %i) on %r" % (offset, op, o, pkt))
1449            raise BadFragments
1450        offset += length
1451        yield bytes(pkt[IP].payload)
1452
1453
1454def _defrag_ip_pkt(pkt, frags):
1455    """
1456    Defragment a single IP packet.
1457
1458    :param pkt: the new pkt
1459    :param frags: a defaultdict(list) used for storage
1460    :return: a tuple (fragmented, defragmented_value)
1461    """
1462    ip = pkt[IP]
1463    if pkt.frag != 0 or ip.flags.MF:
1464        # fragmented !
1465        uid = (ip.id, ip.src, ip.dst, ip.proto)
1466        if ip.len is None or ip.ihl is None:
1467            fraglen = len(ip.payload)
1468        else:
1469            fraglen = ip.len - (ip.ihl << 2)
1470        # (pkt, frag offset, frag len)
1471        frags[uid].append((pkt, ip.frag << 3, fraglen))
1472        if not ip.flags.MF:  # no more fragments = last fragment
1473            curfrags = sorted(frags[uid], key=lambda x: x[1])  # sort by offset
1474            try:
1475                data = b"".join(_defrag_iter_and_check_offsets(curfrags))
1476            except ValueError:
1477                # bad fragment
1478                badfrags = frags[uid]
1479                del frags[uid]
1480                raise BadFragments(frags=badfrags)
1481            # re-build initial packet without fragmentation
1482            p = curfrags[0][0].copy()
1483            pay_class = p[IP].payload.__class__
1484            p[IP].flags.MF = False
1485            p[IP].remove_payload()
1486            p[IP].len = None
1487            p[IP].chksum = None
1488            # append defragmented payload
1489            p /= pay_class(data)
1490            # cleanup
1491            del frags[uid]
1492            return True, p
1493        return True, None
1494    return False, pkt
1495
1496
1497def _defrag_logic(plist, complete=False):
1498    """
1499    Internal function used to defragment a list of packets.
1500    It contains the logic behind the defrag() and defragment() functions
1501    """
1502    frags = defaultdict(list)
1503    final = []
1504    notfrag = []
1505    badfrag = []
1506    # Defrag
1507    for i, pkt in enumerate(plist):
1508        if IP not in pkt:
1509            # no IP layer
1510            if complete:
1511                final.append(pkt)
1512            continue
1513        try:
1514            fragmented, defragmented_value = _defrag_ip_pkt(
1515                pkt,
1516                frags,
1517            )
1518        except BadFragments as ex:
1519            if complete:
1520                final.extend(ex.frags)
1521            else:
1522                badfrag.extend(ex.frags)
1523            continue
1524        if complete and defragmented_value:
1525            final.append(defragmented_value)
1526        elif defragmented_value:
1527            if fragmented:
1528                final.append(defragmented_value)
1529            else:
1530                notfrag.append(defragmented_value)
1531    # Return
1532    if complete:
1533        if hasattr(plist, "listname"):
1534            name = "Defragmented %s" % plist.listname
1535        else:
1536            name = "Defragmented"
1537        return PacketList(final, name=name)
1538    else:
1539        return PacketList(notfrag), PacketList(final), PacketList(badfrag)
1540
1541
1542@conf.commands.register
1543def defrag(plist):
1544    """defrag(plist) -> ([not fragmented], [defragmented],
1545                  [ [bad fragments], [bad fragments], ... ])"""
1546    return _defrag_logic(plist, complete=False)
1547
1548
1549@conf.commands.register
1550def defragment(plist):
1551    """defragment(plist) -> plist defragmented as much as possible """
1552    return _defrag_logic(plist, complete=True)
1553
1554
1555# Add timeskew_graph() method to PacketList
1556def _packetlist_timeskew_graph(self, ip, **kargs):
1557    """Tries to graph the timeskew between the timestamps and real time for a given ip"""  # noqa: E501
1558    # Defer imports of matplotlib until its needed
1559    # because it has a heavy dep chain
1560    from scapy.libs.matplot import (
1561        plt,
1562        MATPLOTLIB_INLINED,
1563        MATPLOTLIB_DEFAULT_PLOT_KARGS
1564    )
1565
1566    # Filter TCP segments which source address is 'ip'
1567    tmp = (self._elt2pkt(x) for x in self.res)
1568    b = (x for x in tmp if IP in x and x[IP].src == ip and TCP in x)
1569
1570    # Build a list of tuples (creation_time, replied_timestamp)
1571    c = []
1572    tsf = ICMPTimeStampField("", None)
1573    for p in b:
1574        opts = p.getlayer(TCP).options
1575        for o in opts:
1576            if o[0] == "Timestamp":
1577                c.append((p.time, tsf.any2i("", o[1][0])))
1578
1579    # Stop if the list is empty
1580    if not c:
1581        warning("No timestamps found in packet list")
1582        return []
1583
1584    # Prepare the data that will be plotted
1585    first_creation_time = c[0][0]
1586    first_replied_timestamp = c[0][1]
1587
1588    def _wrap_data(ts_tuple, wrap_seconds=2000):
1589        """Wrap the list of tuples."""
1590
1591        ct, rt = ts_tuple  # (creation_time, replied_timestamp)
1592        X = ct % wrap_seconds
1593        Y = ((ct - first_creation_time) - ((rt - first_replied_timestamp) / 1000.0))  # noqa: E501
1594
1595        return X, Y
1596
1597    data = [_wrap_data(e) for e in c]
1598
1599    # Mimic the default gnuplot output
1600    if kargs == {}:
1601        kargs = MATPLOTLIB_DEFAULT_PLOT_KARGS
1602    lines = plt.plot(data, **kargs)
1603
1604    # Call show() if matplotlib is not inlined
1605    if not MATPLOTLIB_INLINED:
1606        plt.show()
1607
1608    return lines
1609
1610
1611_PacketList.timeskew_graph = _packetlist_timeskew_graph
1612
1613
1614# Create a new packet list
1615class TracerouteResult(SndRcvList):
1616    __slots__ = ["graphdef", "graphpadding", "graphASres", "padding", "hloc",
1617                 "nloc"]
1618
1619    def __init__(self, res=None, name="Traceroute", stats=None):
1620        SndRcvList.__init__(self, res, name, stats)
1621        self.graphdef = None
1622        self.graphASres = None
1623        self.padding = 0
1624        self.hloc = None
1625        self.nloc = None
1626
1627    def show(self):
1628        return self.make_table(lambda s, r: (s.sprintf("%IP.dst%:{TCP:tcp%ir,TCP.dport%}{UDP:udp%ir,UDP.dport%}{ICMP:ICMP}"),  # noqa: E501
1629                                             s.ttl,
1630                                             r.sprintf("%-15s,IP.src% {TCP:%TCP.flags%}{ICMP:%ir,ICMP.type%}")))  # noqa: E501
1631
1632    def get_trace(self):
1633        trace = {}
1634        for s, r in self.res:
1635            if IP not in s:
1636                continue
1637            d = s[IP].dst
1638            if d not in trace:
1639                trace[d] = {}
1640            trace[d][s[IP].ttl] = r[IP].src, ICMP not in r
1641        for k in trace.values():
1642            try:
1643                m = min(x for x, y in k.items() if y[1])
1644            except ValueError:
1645                continue
1646            for li in list(k):  # use list(): k is modified in the loop
1647                if li > m:
1648                    del k[li]
1649        return trace
1650
1651    def trace3D(self, join=True):
1652        """Give a 3D representation of the traceroute.
1653        right button: rotate the scene
1654        middle button: zoom
1655        shift-left button: move the scene
1656        left button on a ball: toggle IP displaying
1657        double-click button on a ball: scan ports 21,22,23,25,80 and 443 and display the result"""  # noqa: E501
1658        # When not ran from a notebook, vpython pooly closes itself
1659        # using os._exit once finished. We pack it into a Process
1660        import multiprocessing
1661        p = multiprocessing.Process(target=self.trace3D_notebook)
1662        p.start()
1663        if join:
1664            p.join()
1665
1666    def trace3D_notebook(self):
1667        """Same than trace3D, used when ran from Jupyter notebooks"""
1668        trace = self.get_trace()
1669        import vpython
1670
1671        class IPsphere(vpython.sphere):
1672            def __init__(self, ip, **kargs):
1673                vpython.sphere.__init__(self, **kargs)
1674                self.ip = ip
1675                self.label = None
1676                self.setlabel(self.ip)
1677                self.last_clicked = None
1678                self.full = False
1679                self.savcolor = vpython.vec(*self.color.value)
1680
1681            def fullinfos(self):
1682                self.full = True
1683                self.color = vpython.vec(1, 0, 0)
1684                a, b = sr(IP(dst=self.ip) / TCP(dport=[21, 22, 23, 25, 80, 443], flags="S"), timeout=2, verbose=0)  # noqa: E501
1685                if len(a) == 0:
1686                    txt = "%s:\nno results" % self.ip
1687                else:
1688                    txt = "%s:\n" % self.ip
1689                    for s, r in a:
1690                        txt += r.sprintf("{TCP:%IP.src%:%TCP.sport% %TCP.flags%}{TCPerror:%IPerror.dst%:%TCPerror.dport% %IP.src% %ir,ICMP.type%}\n")  # noqa: E501
1691                self.setlabel(txt, visible=1)
1692
1693            def unfull(self):
1694                self.color = self.savcolor
1695                self.full = False
1696                self.setlabel(self.ip)
1697
1698            def setlabel(self, txt, visible=None):
1699                if self.label is not None:
1700                    if visible is None:
1701                        visible = self.label.visible
1702                    self.label.visible = 0
1703                elif visible is None:
1704                    visible = 0
1705                self.label = vpython.label(text=txt, pos=self.pos, space=self.radius, xoffset=10, yoffset=20, visible=visible)  # noqa: E501
1706
1707            def check_double_click(self):
1708                try:
1709                    if self.full or not self.label.visible:
1710                        return False
1711                    if self.last_clicked is not None:
1712                        return (time.time() - self.last_clicked) < 0.5
1713                    return False
1714                finally:
1715                    self.last_clicked = time.time()
1716
1717            def action(self):
1718                self.label.visible ^= 1
1719                if self.full:
1720                    self.unfull()
1721
1722        vpython.scene = vpython.canvas()
1723        vpython.scene.title = "<center><u><b>%s</b></u></center>" % self.listname  # noqa: E501
1724        vpython.scene.append_to_caption(
1725            re.sub(
1726                r'\%(.*)\%',
1727                r'<span style="color: red">\1</span>',
1728                re.sub(
1729                    r'\`(.*)\`',
1730                    r'<span style="color: #3399ff">\1</span>',
1731                    """<u><b>Commands:</b></u>
1732%Click% to toggle information about a node.
1733%Double click% to perform a quick web scan on this node.
1734<u><b>Camera usage:</b></u>
1735`Right button drag or Ctrl-drag` to rotate "camera" to view scene.
1736`Shift-drag` to move the object around.
1737`Middle button or Alt-drag` to drag up or down to zoom in or out.
1738  On a two-button mouse, `middle is wheel or left + right`.
1739Touch screen: pinch/extend to zoom, swipe or two-finger rotate."""
1740                )
1741            )
1742        )
1743        vpython.scene.exit = True
1744        rings = {}
1745        tr3d = {}
1746        for i in trace:
1747            tr = trace[i]
1748            tr3d[i] = []
1749            for t in range(1, max(tr) + 1):
1750                if t not in rings:
1751                    rings[t] = []
1752                if t in tr:
1753                    if tr[t] not in rings[t]:
1754                        rings[t].append(tr[t])
1755                    tr3d[i].append(rings[t].index(tr[t]))
1756                else:
1757                    rings[t].append(("unk", -1))
1758                    tr3d[i].append(len(rings[t]) - 1)
1759
1760        for t in rings:
1761            r = rings[t]
1762            tmp_len = len(r)
1763            for i in range(tmp_len):
1764                if r[i][1] == -1:
1765                    col = vpython.vec(0.75, 0.75, 0.75)
1766                elif r[i][1]:
1767                    col = vpython.color.green
1768                else:
1769                    col = vpython.color.blue
1770
1771                s = IPsphere(pos=vpython.vec((tmp_len - 1) * vpython.cos(2 * i * vpython.pi / tmp_len), (tmp_len - 1) * vpython.sin(2 * i * vpython.pi / tmp_len), 2 * t),  # noqa: E501
1772                             ip=r[i][0],
1773                             color=col)
1774                for trlst in tr3d.values():
1775                    if t <= len(trlst):
1776                        if trlst[t - 1] == i:
1777                            trlst[t - 1] = s
1778        forecol = colgen(0.625, 0.4375, 0.25, 0.125)
1779        for trlst in tr3d.values():
1780            col = vpython.vec(*next(forecol))
1781            start = vpython.vec(0, 0, 0)
1782            for ip in trlst:
1783                vpython.cylinder(pos=start, axis=ip.pos - start, color=col, radius=0.2)  # noqa: E501
1784                start = ip.pos
1785
1786        vpython.rate(50)
1787
1788        # Keys handling
1789        # TODO: there is currently no way of closing vpython correctly
1790        # https://github.com/BruceSherwood/vpython-jupyter/issues/36
1791        # def keyboard_press(ev):
1792        #     k = ev.key
1793        #     if k == "esc" or k == "q":
1794        #         pass  # TODO: close
1795        #
1796        # vpython.scene.bind('keydown', keyboard_press)
1797
1798        # Mouse handling
1799        def mouse_click(ev):
1800            if ev.press == "left":
1801                o = vpython.scene.mouse.pick
1802                if o and isinstance(o, IPsphere):
1803                    if o.check_double_click():
1804                        if o.ip == "unk":
1805                            return
1806                        o.fullinfos()
1807                    else:
1808                        o.action()
1809
1810        vpython.scene.bind('mousedown', mouse_click)
1811
1812    def world_trace(self):
1813        """Display traceroute results on a world map."""
1814
1815        # Check that the geoip2 module can be imported
1816        # Doc: http://geoip2.readthedocs.io/en/latest/
1817        from scapy.libs.matplot import plt, MATPLOTLIB, MATPLOTLIB_INLINED
1818
1819        try:
1820            # GeoIP2 modules need to be imported as below
1821            import geoip2.database
1822            import geoip2.errors
1823        except ImportError:
1824            log_runtime.error(
1825                "Cannot import geoip2. Won't be able to plot the world."
1826            )
1827            return []
1828        # Check availability of database
1829        if not conf.geoip_city:
1830            log_runtime.error(
1831                "Cannot import the geolite2 CITY database.\n"
1832                "Download it from http://dev.maxmind.com/geoip/geoip2/geolite2/"  # noqa: E501
1833                " then set its path to conf.geoip_city"
1834            )
1835            return []
1836        # Check availability of plotting devices
1837        try:
1838            import cartopy.crs as ccrs
1839        except ImportError:
1840            log_runtime.error(
1841                "Cannot import cartopy.\n"
1842                "More infos on http://scitools.org.uk/cartopy/docs/latest/installing.html"  # noqa: E501
1843            )
1844            return []
1845        if not MATPLOTLIB:
1846            log_runtime.error(
1847                "Matplotlib is not installed. Won't be able to plot the world."
1848            )
1849            return []
1850
1851        # Open & read the GeoListIP2 database
1852        try:
1853            db = geoip2.database.Reader(conf.geoip_city)
1854        except Exception:
1855            log_runtime.error(
1856                "Cannot open geoip2 database at %s",
1857                conf.geoip_city
1858            )
1859            return []
1860
1861        # Regroup results per trace
1862        ips = {}
1863        rt = {}
1864        ports_done = {}
1865        for s, r in self.res:
1866            ips[r.src] = None
1867            if s.haslayer(TCP) or s.haslayer(UDP):
1868                trace_id = (s.src, s.dst, s.proto, s.dport)
1869            elif s.haslayer(ICMP):
1870                trace_id = (s.src, s.dst, s.proto, s.type)
1871            else:
1872                trace_id = (s.src, s.dst, s.proto, 0)
1873            trace = rt.get(trace_id, {})
1874            if not r.haslayer(ICMP) or r.type != 11:
1875                if trace_id in ports_done:
1876                    continue
1877                ports_done[trace_id] = None
1878            trace[s.ttl] = r.src
1879            rt[trace_id] = trace
1880
1881        # Get the addresses locations
1882        trt = {}
1883        for trace_id in rt:
1884            trace = rt[trace_id]
1885            loctrace = []
1886            for i in range(max(trace)):
1887                ip = trace.get(i, None)
1888                if ip is None:
1889                    continue
1890                # Fetch database
1891                try:
1892                    sresult = db.city(ip)
1893                except geoip2.errors.AddressNotFoundError:
1894                    continue
1895                loctrace.append((sresult.location.longitude, sresult.location.latitude))  # noqa: E501
1896            if loctrace:
1897                trt[trace_id] = loctrace
1898
1899        # Load the map renderer
1900        plt.figure(num='Scapy')
1901        ax = plt.axes(projection=ccrs.PlateCarree())
1902        # Draw countries
1903        ax.coastlines()
1904        ax.stock_img()
1905        # Set normal size
1906        ax.set_global()
1907        # Add title
1908        plt.title("Scapy traceroute results")
1909
1910        from matplotlib.collections import LineCollection
1911        from matplotlib import colors as mcolors
1912        colors_cycle = iter(mcolors.BASE_COLORS)
1913        lines = []
1914
1915        # Split traceroute measurement
1916        for key, trc in trt.items():
1917            # Get next color
1918            color = next(colors_cycle)
1919            # Gather mesurments data
1920            data_lines = [(trc[i], trc[i + 1]) for i in range(len(trc) - 1)]
1921            # Create line collection
1922            line_col = LineCollection(data_lines, linewidths=2,
1923                                      label=key[1],
1924                                      color=color)
1925            lines.append(line_col)
1926            ax.add_collection(line_col)
1927            # Create map points
1928            lines.extend([ax.plot(*x, marker='.', color=color) for x in trc])
1929
1930        # Generate legend
1931        ax.legend()
1932
1933        # Call show() if matplotlib is not inlined
1934        if not MATPLOTLIB_INLINED:
1935            plt.show()
1936
1937        # Clean
1938        ax.remove()
1939
1940        # Return the drawn lines
1941        return lines
1942
1943    def make_graph(self, ASres=None, padding=0):
1944        self.graphASres = ASres
1945        self.graphpadding = padding
1946        ips = {}
1947        rt = {}
1948        ports = {}
1949        ports_done = {}
1950        for s, r in self.res:
1951            r = r.getlayer(IP) or (conf.ipv6_enabled and r[scapy.layers.inet6.IPv6]) or r  # noqa: E501
1952            s = s.getlayer(IP) or (conf.ipv6_enabled and s[scapy.layers.inet6.IPv6]) or s  # noqa: E501
1953            ips[r.src] = None
1954            if TCP in s:
1955                trace_id = (s.src, s.dst, 6, s.dport)
1956            elif UDP in s:
1957                trace_id = (s.src, s.dst, 17, s.dport)
1958            elif ICMP in s:
1959                trace_id = (s.src, s.dst, 1, s.type)
1960            else:
1961                trace_id = (s.src, s.dst, s.proto, 0)
1962            trace = rt.get(trace_id, {})
1963            ttl = conf.ipv6_enabled and scapy.layers.inet6.IPv6 in s and s.hlim or s.ttl  # noqa: E501
1964            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):  # noqa: E501
1965                if trace_id in ports_done:
1966                    continue
1967                ports_done[trace_id] = None
1968                p = ports.get(r.src, [])
1969                if TCP in r:
1970                    p.append(r.sprintf("<T%ir,TCP.sport%> %TCP.sport% %TCP.flags%"))  # noqa: E501
1971                    trace[ttl] = r.sprintf('"%r,src%":T%ir,TCP.sport%')
1972                elif UDP in r:
1973                    p.append(r.sprintf("<U%ir,UDP.sport%> %UDP.sport%"))
1974                    trace[ttl] = r.sprintf('"%r,src%":U%ir,UDP.sport%')
1975                elif ICMP in r:
1976                    p.append(r.sprintf("<I%ir,ICMP.type%> ICMP %ICMP.type%"))
1977                    trace[ttl] = r.sprintf('"%r,src%":I%ir,ICMP.type%')
1978                else:
1979                    p.append(r.sprintf("{IP:<P%ir,proto%> IP %proto%}{IPv6:<P%ir,nh%> IPv6 %nh%}"))  # noqa: E501
1980                    trace[ttl] = r.sprintf('"%r,src%":{IP:P%ir,proto%}{IPv6:P%ir,nh%}')  # noqa: E501
1981                ports[r.src] = p
1982            else:
1983                trace[ttl] = r.sprintf('"%r,src%"')
1984            rt[trace_id] = trace
1985
1986        # Fill holes with unk%i nodes
1987        unknown_label = incremental_label("unk%i")
1988        blackholes = []
1989        bhip = {}
1990        for rtk in rt:
1991            trace = rt[rtk]
1992            max_trace = max(trace)
1993            for n in range(min(trace), max_trace):
1994                if n not in trace:
1995                    trace[n] = next(unknown_label)
1996            if rtk not in ports_done:
1997                if rtk[2] == 1:  # ICMP
1998                    bh = "%s %i/icmp" % (rtk[1], rtk[3])
1999                elif rtk[2] == 6:  # TCP
2000                    bh = "%s %i/tcp" % (rtk[1], rtk[3])
2001                elif rtk[2] == 17:  # UDP
2002                    bh = '%s %i/udp' % (rtk[1], rtk[3])
2003                else:
2004                    bh = '%s %i/proto' % (rtk[1], rtk[2])
2005                ips[bh] = None
2006                bhip[rtk[1]] = bh
2007                bh = '"%s"' % bh
2008                trace[max_trace + 1] = bh
2009                blackholes.append(bh)
2010
2011        # Find AS numbers
2012        ASN_query_list = set(x.rsplit(" ", 1)[0] for x in ips)
2013        if ASres is None:
2014            ASNlist = []
2015        else:
2016            ASNlist = ASres.resolve(*ASN_query_list)
2017
2018        ASNs = {}
2019        ASDs = {}
2020        for ip, asn, desc, in ASNlist:
2021            if asn is None:
2022                continue
2023            iplist = ASNs.get(asn, [])
2024            if ip in bhip:
2025                if ip in ports:
2026                    iplist.append(ip)
2027                iplist.append(bhip[ip])
2028            else:
2029                iplist.append(ip)
2030            ASNs[asn] = iplist
2031            ASDs[asn] = desc
2032
2033        backcolorlist = colgen("60", "86", "ba", "ff")
2034        forecolorlist = colgen("a0", "70", "40", "20")
2035
2036        s = "digraph trace {\n"
2037
2038        s += "\n\tnode [shape=ellipse,color=black,style=solid];\n\n"
2039
2040        s += "\n#ASN clustering\n"
2041        for asn in ASNs:
2042            s += '\tsubgraph cluster_%s {\n' % asn
2043            col = next(backcolorlist)
2044            s += '\t\tcolor="#%s%s%s";' % col
2045            s += '\t\tnode [fillcolor="#%s%s%s",style=filled];' % col
2046            s += '\t\tfontsize = 10;'
2047            s += '\t\tlabel = "%s\\n[%s]"\n' % (asn, ASDs[asn])
2048            for ip in ASNs[asn]:
2049
2050                s += '\t\t"%s";\n' % ip
2051            s += "\t}\n"
2052
2053        s += "#endpoints\n"
2054        for p in ports:
2055            s += '\t"%s" [shape=record,color=black,fillcolor=green,style=filled,label="%s|%s"];\n' % (p, p, "|".join(ports[p]))  # noqa: E501
2056
2057        s += "\n#Blackholes\n"
2058        for bh in blackholes:
2059            s += '\t%s [shape=octagon,color=black,fillcolor=red,style=filled];\n' % bh  # noqa: E501
2060
2061        if padding:
2062            s += "\n#Padding\n"
2063            pad = {}
2064            for snd, rcv in self.res:
2065                if rcv.src not in ports and rcv.haslayer(conf.padding_layer):
2066                    p = rcv.getlayer(conf.padding_layer).load
2067                    if p != b"\x00" * len(p):
2068                        pad[rcv.src] = None
2069            for rcv in pad:
2070                s += '\t"%s" [shape=triangle,color=black,fillcolor=red,style=filled];\n' % rcv  # noqa: E501
2071
2072        s += "\n\tnode [shape=ellipse,color=black,style=solid];\n\n"
2073
2074        for rtk in rt:
2075            s += "#---[%s\n" % repr(rtk)
2076            s += '\t\tedge [color="#%s%s%s"];\n' % next(forecolorlist)
2077            trace = rt[rtk]
2078            maxtrace = max(trace)
2079            for n in range(min(trace), maxtrace):
2080                s += '\t%s ->\n' % trace[n]
2081            s += '\t%s;\n' % trace[maxtrace]
2082
2083        s += "}\n"
2084        self.graphdef = s
2085
2086    def graph(self, ASres=conf.AS_resolver, padding=0, **kargs):
2087        """x.graph(ASres=conf.AS_resolver, other args):
2088        ASres=None          : no AS resolver => no clustering
2089        ASres=AS_resolver() : default whois AS resolver (riswhois.ripe.net)
2090        ASres=AS_resolver_cymru(): use whois.cymru.com whois database
2091        ASres=AS_resolver(server="whois.ra.net")
2092        type: output type (svg, ps, gif, jpg, etc.), passed to dot's "-T" option  # noqa: E501
2093        target: filename or redirect. Defaults pipe to Imagemagick's display program  # noqa: E501
2094        prog: which graphviz program to use"""
2095        if (self.graphdef is None or
2096            self.graphASres != ASres or
2097                self.graphpadding != padding):
2098            self.make_graph(ASres, padding)
2099
2100        return do_graph(self.graphdef, **kargs)
2101
2102
2103@conf.commands.register
2104def traceroute(target, dport=80, minttl=1, maxttl=30, sport=RandShort(), l4=None, filter=None, timeout=2, verbose=None, **kargs):  # noqa: E501
2105    """Instant TCP traceroute
2106
2107       :param target:  hostnames or IP addresses
2108       :param dport:   TCP destination port (default is 80)
2109       :param minttl:  minimum TTL (default is 1)
2110       :param maxttl:  maximum TTL (default is 30)
2111       :param sport:   TCP source port (default is random)
2112       :param l4:      use a Scapy packet instead of TCP
2113       :param filter:  BPF filter applied to received packets
2114       :param timeout: time to wait for answers (default is 2s)
2115       :param verbose: detailed output
2116       :return: an TracerouteResult, and a list of unanswered packets"""
2117    if verbose is None:
2118        verbose = conf.verb
2119    if filter is None:
2120        # we only consider ICMP error packets and TCP packets with at
2121        # least the ACK flag set *and* either the SYN or the RST flag
2122        # set
2123        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))"  # noqa: E501
2124    if l4 is None:
2125        a, b = sr(IP(dst=target, id=RandShort(), ttl=(minttl, maxttl)) / TCP(seq=RandInt(), sport=sport, dport=dport),  # noqa: E501
2126                  timeout=timeout, filter=filter, verbose=verbose, **kargs)
2127    else:
2128        # this should always work
2129        filter = "ip"
2130        a, b = sr(IP(dst=target, id=RandShort(), ttl=(minttl, maxttl)) / l4,
2131                  timeout=timeout, filter=filter, verbose=verbose, **kargs)
2132
2133    a = TracerouteResult(a.res)
2134    if verbose:
2135        a.show()
2136    return a, b
2137
2138
2139@conf.commands.register
2140def traceroute_map(ips, **kargs):
2141    """Util function to call traceroute on multiple targets, then
2142    show the different paths on a map.
2143
2144    :param ips: a list of IPs on which traceroute will be called
2145    :param kargs: (optional) kwargs, passed to traceroute
2146    """
2147    kargs.setdefault("verbose", 0)
2148    return traceroute(ips)[0].world_trace()
2149
2150#############################
2151#  Simple TCP client stack  #
2152#############################
2153
2154
2155class TCP_client(Automaton):
2156    """
2157    Creates a TCP Client Automaton.
2158    This automaton will handle TCP 3-way handshake.
2159
2160    Usage: the easiest usage is to use it as a SuperSocket.
2161        >>> a = TCP_client.tcplink(HTTP, "www.google.com", 80)
2162        >>> a.send(HTTPRequest())
2163        >>> a.recv()
2164
2165    :param ip: the ip to connect to
2166    :param port:
2167    :param src: (optional) use another source IP
2168    """
2169
2170    def parse_args(self, ip, port, srcip=None, **kargs):
2171        from scapy.sessions import TCPSession
2172        self.dst = str(Net(ip))
2173        self.dport = port
2174        self.sport = random.randrange(0, 2**16)
2175        self.l4 = IP(dst=ip, src=srcip) / TCP(
2176            sport=self.sport, dport=self.dport,
2177            flags=0, seq=random.randrange(0, 2**32)
2178        )
2179        self.src = self.l4.src
2180        self.sack = self.l4[TCP].ack
2181        self.rel_seq = None
2182        self.rcvbuf = TCPSession()
2183        bpf = "host %s  and host %s and port %i and port %i" % (self.src,
2184                                                                self.dst,
2185                                                                self.sport,
2186                                                                self.dport)
2187        Automaton.parse_args(self, filter=bpf, **kargs)
2188
2189    def _transmit_packet(self, pkt):
2190        """Transmits a packet from TCPSession to the SuperSocket"""
2191        self.oi.tcp.send(raw(pkt[TCP].payload))
2192
2193    def master_filter(self, pkt):
2194        return (IP in pkt and
2195                pkt[IP].src == self.dst and
2196                pkt[IP].dst == self.src and
2197                TCP in pkt and
2198                pkt[TCP].sport == self.dport and
2199                pkt[TCP].dport == self.sport and
2200                self.l4[TCP].seq >= pkt[TCP].ack and  # XXX: seq/ack 2^32 wrap up  # noqa: E501
2201                ((self.l4[TCP].ack == 0) or (self.sack <= pkt[TCP].seq <= self.l4[TCP].ack + pkt[TCP].window)))  # noqa: E501
2202
2203    @ATMT.state(initial=1)
2204    def START(self):
2205        pass
2206
2207    @ATMT.state()
2208    def SYN_SENT(self):
2209        pass
2210
2211    @ATMT.state()
2212    def ESTABLISHED(self):
2213        pass
2214
2215    @ATMT.state()
2216    def LAST_ACK(self):
2217        pass
2218
2219    @ATMT.state(final=1)
2220    def CLOSED(self):
2221        pass
2222
2223    @ATMT.state(stop=1)
2224    def STOP(self):
2225        pass
2226
2227    @ATMT.state()
2228    def STOP_SENT_FIN_ACK(self):
2229        pass
2230
2231    @ATMT.condition(START)
2232    def connect(self):
2233        raise self.SYN_SENT()
2234
2235    @ATMT.action(connect)
2236    def send_syn(self):
2237        self.l4[TCP].flags = "S"
2238        self.send(self.l4)
2239        self.l4[TCP].seq += 1
2240
2241    @ATMT.receive_condition(SYN_SENT)
2242    def synack_received(self, pkt):
2243        if pkt[TCP].flags.SA:
2244            raise self.ESTABLISHED().action_parameters(pkt)
2245
2246    @ATMT.action(synack_received)
2247    def send_ack_of_synack(self, pkt):
2248        self.l4[TCP].ack = pkt[TCP].seq + 1
2249        self.l4[TCP].flags = "A"
2250        self.send(self.l4)
2251
2252    @ATMT.receive_condition(ESTABLISHED)
2253    def incoming_data_received(self, pkt):
2254        if not isinstance(pkt[TCP].payload, (NoPayload, conf.padding_layer)):
2255            raise self.ESTABLISHED().action_parameters(pkt)
2256
2257    @ATMT.action(incoming_data_received)
2258    def receive_data(self, pkt):
2259        data = raw(pkt[TCP].payload)
2260        if data and self.l4[TCP].ack == pkt[TCP].seq:
2261            self.sack = self.l4[TCP].ack
2262            self.l4[TCP].ack += len(data)
2263            self.l4[TCP].flags = "A"
2264            # Answer with an Ack
2265            self.send(self.l4)
2266            # Process data - will be sent to the SuperSocket through this
2267            pkt = self.rcvbuf.process(pkt)
2268            if pkt:
2269                self._transmit_packet(pkt)
2270
2271    @ATMT.ioevent(ESTABLISHED, name="tcp", as_supersocket="tcplink")
2272    def outgoing_data_received(self, fd):
2273        raise self.ESTABLISHED().action_parameters(fd.recv())
2274
2275    @ATMT.action(outgoing_data_received)
2276    def send_data(self, d):
2277        self.l4[TCP].flags = "PA"
2278        self.send(self.l4 / d)
2279        self.l4[TCP].seq += len(d)
2280
2281    @ATMT.receive_condition(ESTABLISHED)
2282    def reset_received(self, pkt):
2283        if pkt[TCP].flags.R:
2284            raise self.CLOSED()
2285
2286    @ATMT.receive_condition(ESTABLISHED)
2287    def fin_received(self, pkt):
2288        if pkt[TCP].flags.F:
2289            raise self.LAST_ACK().action_parameters(pkt)
2290
2291    @ATMT.action(fin_received)
2292    def send_finack(self, pkt):
2293        self.l4[TCP].flags = "FA"
2294        self.l4[TCP].ack = pkt[TCP].seq + 1
2295        self.send(self.l4)
2296        self.l4[TCP].seq += 1
2297
2298    @ATMT.receive_condition(LAST_ACK)
2299    def ack_of_fin_received(self, pkt):
2300        if pkt[TCP].flags.A:
2301            raise self.CLOSED()
2302
2303    @ATMT.condition(STOP)
2304    def stop_requested(self):
2305        raise self.STOP_SENT_FIN_ACK()
2306
2307    @ATMT.action(stop_requested)
2308    def stop_send_finack(self):
2309        self.l4[TCP].flags = "FA"
2310        self.send(self.l4)
2311        self.l4[TCP].seq += 1
2312
2313    @ATMT.receive_condition(STOP_SENT_FIN_ACK)
2314    def stop_fin_received(self, pkt):
2315        if pkt[TCP].flags.F:
2316            raise self.CLOSED().action_parameters(pkt)
2317
2318    @ATMT.action(stop_fin_received)
2319    def stop_send_ack(self, pkt):
2320        self.l4[TCP].flags = "A"
2321        self.l4[TCP].ack = pkt[TCP].seq + 1
2322        self.send(self.l4)
2323
2324    @ATMT.timeout(SYN_SENT, 1)
2325    def syn_ack_timeout(self):
2326        raise self.CLOSED()
2327
2328    @ATMT.timeout(STOP_SENT_FIN_ACK, 1)
2329    def stop_ack_timeout(self):
2330        raise self.CLOSED()
2331
2332
2333#####################
2334#  Reporting stuff  #
2335#####################
2336
2337
2338@conf.commands.register
2339def report_ports(target, ports):
2340    """portscan a target and output a LaTeX table
2341report_ports(target, ports) -> string"""
2342    ans, unans = sr(IP(dst=target) / TCP(dport=ports), timeout=5)
2343    rep = "\\begin{tabular}{|r|l|l|}\n\\hline\n"
2344    for s, r in ans:
2345        if not r.haslayer(ICMP):
2346            if r.payload.flags == 0x12:
2347                rep += r.sprintf("%TCP.sport% & open & SA \\\\\n")
2348    rep += "\\hline\n"
2349    for s, r in ans:
2350        if r.haslayer(ICMP):
2351            rep += r.sprintf("%TCPerror.dport% & closed & ICMP type %ICMP.type%/%ICMP.code% from %IP.src% \\\\\n")  # noqa: E501
2352        elif r.payload.flags != 0x12:
2353            rep += r.sprintf("%TCP.sport% & closed & TCP %TCP.flags% \\\\\n")
2354    rep += "\\hline\n"
2355    for i in unans:
2356        rep += i.sprintf("%TCP.dport% & ? & unanswered \\\\\n")
2357    rep += "\\hline\n\\end{tabular}\n"
2358    return rep
2359
2360
2361@conf.commands.register
2362def IPID_count(lst, funcID=lambda x: x[1].id, funcpres=lambda x: x[1].summary()):  # noqa: E501
2363    """Identify IP id values classes in a list of packets
2364
2365lst:      a list of packets
2366funcID:   a function that returns IP id values
2367funcpres: a function used to summarize packets"""
2368    idlst = [funcID(e) for e in lst]
2369    idlst.sort()
2370    classes = [idlst[0]]
2371    classes += [t[1] for t in zip(idlst[:-1], idlst[1:]) if abs(t[0] - t[1]) > 50]  # noqa: E501
2372    lst = [(funcID(x), funcpres(x)) for x in lst]
2373    lst.sort()
2374    print("Probably %i classes: %s" % (len(classes), classes))
2375    for id, pr in lst:
2376        print("%5i" % id, pr)
2377
2378
2379@conf.commands.register
2380def fragleak(target, sport=123, dport=123, timeout=0.2, onlyasc=0, count=None):
2381    load = "XXXXYYYYYYYYYY"
2382    pkt = IP(dst=target, id=RandShort(), options=b"\x00" * 40, flags=1)
2383    pkt /= UDP(sport=sport, dport=sport) / load
2384    s = conf.L3socket()
2385    intr = 0
2386    found = {}
2387    try:
2388        while count is None or count:
2389            if count is not None and isinstance(count, int):
2390                count -= 1
2391            try:
2392                if not intr:
2393                    s.send(pkt)
2394                sin = select.select([s], [], [], timeout)[0]
2395                if not sin:
2396                    continue
2397                ans = s.recv(1600)
2398                if not isinstance(ans, IP):  # TODO: IPv6
2399                    continue
2400                if not isinstance(ans.payload, ICMP):
2401                    continue
2402                if not isinstance(ans.payload.payload, IPerror):
2403                    continue
2404                if ans.payload.payload.dst != target:
2405                    continue
2406                if ans.src != target:
2407                    print("leak from", ans.src)
2408                if not ans.haslayer(conf.padding_layer):
2409                    continue
2410                leak = ans.getlayer(conf.padding_layer).load
2411                if leak not in found:
2412                    found[leak] = None
2413                    linehexdump(leak, onlyasc=onlyasc)
2414            except KeyboardInterrupt:
2415                if intr:
2416                    raise
2417                intr = 1
2418    except KeyboardInterrupt:
2419        pass
2420
2421
2422@conf.commands.register
2423def fragleak2(target, timeout=0.4, onlyasc=0, count=None):
2424    found = {}
2425    try:
2426        while count is None or count:
2427            if count is not None and isinstance(count, int):
2428                count -= 1
2429
2430            pkt = IP(dst=target, options=b"\x00" * 40, proto=200)
2431            pkt /= "XXXXYYYYYYYYYYYY"
2432            p = sr1(pkt, timeout=timeout, verbose=0)
2433            if not p:
2434                continue
2435            if conf.padding_layer in p:
2436                leak = p[conf.padding_layer].load
2437                if leak not in found:
2438                    found[leak] = None
2439                    linehexdump(leak, onlyasc=onlyasc)
2440    except Exception:
2441        pass
2442
2443
2444@conf.commands.register
2445class connect_from_ip:
2446    """
2447    Open a TCP socket to a host:port while spoofing another IP.
2448
2449    :param host: the host to connect to
2450    :param port: the port to connect to
2451    :param srcip: the IP to spoof. the cache of the gateway will
2452                  be poisonned with this IP.
2453    :param poison: (optional, default True) ARP poison the gateway (or next hop),
2454                   so that it answers us (only one packet).
2455    :param timeout: (optional) the socket timeout.
2456
2457    Example - Connect to 192.168.0.1:80 spoofing 192.168.0.2::
2458
2459        from scapy.layers.http import HTTP, HTTPRequest
2460        client = connect_from_ip("192.168.0.1", 80, "192.168.0.2")
2461        sock = SSLStreamSocket(client.sock, HTTP)
2462        resp = sock.sr1(HTTP() / HTTPRequest(Path="/"))
2463
2464    Example - Connect to 192.168.0.1:443 with TLS wrapping spoofing 192.168.0.2::
2465
2466        import ssl
2467        from scapy.layers.http import HTTP, HTTPRequest
2468        context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
2469        context.check_hostname = False
2470        context.verify_mode = ssl.CERT_NONE
2471        client = connect_from_ip("192.168.0.1", 443, "192.168.0.2")
2472        sock = context.wrap_socket(client.sock)
2473        sock = SSLStreamSocket(client.sock, HTTP)
2474        resp = sock.sr1(HTTP() / HTTPRequest(Path="/"))
2475    """
2476
2477    def __init__(self, host, port, srcip, poison=True, timeout=1, debug=0):
2478        host = str(Net(host))
2479        if poison:
2480            # poison the next hop
2481            gateway = conf.route.route(host)[2]
2482            if gateway == "0.0.0.0":
2483                # on lan
2484                gateway = host
2485            getmacbyip(gateway)  # cache real gateway before poisoning
2486            arpcachepoison(gateway, srcip, count=1, interval=0, verbose=0)
2487        # create a socket pair
2488        self._sock, self.sock = socket.socketpair()
2489        self.sock.settimeout(timeout)
2490        self.client = TCP_client(
2491            host, port,
2492            srcip=srcip,
2493            external_fd={"tcp": self._sock},
2494            debug=debug,
2495        )
2496        # start the TCP_client
2497        self.client.runbg()
2498
2499    def close(self):
2500        self.client.stop()
2501        self.client.destroy()
2502        self.sock.close()
2503        self._sock.close()
2504
2505
2506class ICMPEcho_am(AnsweringMachine):
2507    """Responds to ICMP Echo-Requests (ping)"""
2508    function_name = "icmpechod"
2509
2510    def is_request(self, req):
2511        if req.haslayer(ICMP):
2512            icmp_req = req.getlayer(ICMP)
2513            if icmp_req.type == 8:  # echo-request
2514                return True
2515
2516        return False
2517
2518    def print_reply(self, req, reply):
2519        print("Replying %s to %s" % (reply[IP].dst, req[IP].dst))
2520
2521    def make_reply(self, req):
2522        reply = req.copy()
2523        reply[ICMP].type = 0  # echo-reply
2524        # Force re-generation of the checksum
2525        reply[ICMP].chksum = None
2526        if req.haslayer(IP):
2527            reply[IP].src, reply[IP].dst = req[IP].dst, req[IP].src
2528            reply[IP].chksum = None
2529        if req.haslayer(Ether):
2530            reply[Ether].src, reply[Ether].dst = (
2531                None if req[Ether].dst == "ff:ff:ff:ff:ff:ff" else req[Ether].dst,
2532                req[Ether].src,
2533            )
2534        return reply
2535
2536
2537conf.stats_classic_protocols += [TCP, UDP, ICMP]
2538conf.stats_dot11_protocols += [TCP, UDP, ICMP]
2539
2540if conf.ipv6_enabled:
2541    import scapy.layers.inet6
2542