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