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, 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, orb, 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 = orb(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 = orb(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 = orb(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 h.update(tcp_pseudoheader(tcp))
733 h.update(tcp_bytes[:16])
734 h.update(b"\x00\x00")
735 h.update(tcp_bytes[18:])
736 h.update(key)
738 return h.digest()
741def sign_tcp_md5(tcp, key):
742 # type: (TCP, bytes) -> None
743 """Append TCP-MD5 signature to tcp packet"""
744 sig = calc_tcp_md5_hash(tcp, key)
745 tcp.options = tcp.options + [('MD5', sig)]
748class TCP(Packet):
749 name = "TCP"
750 fields_desc = [ShortEnumField("sport", 20, TCP_SERVICES),
751 ShortEnumField("dport", 80, TCP_SERVICES),
752 IntField("seq", 0),
753 IntField("ack", 0),
754 BitField("dataofs", None, 4),
755 BitField("reserved", 0, 3),
756 FlagsField("flags", 0x2, 9, "FSRPAUECN"),
757 ShortField("window", 8192),
758 XShortField("chksum", None),
759 ShortField("urgptr", 0),
760 TCPOptionsField("options", "")]
762 def post_build(self, p, pay):
763 p += pay
764 dataofs = self.dataofs
765 if dataofs is None:
766 opt_len = len(self.get_field("options").i2m(self, self.options))
767 dataofs = 5 + ((opt_len + 3) // 4)
768 dataofs = (dataofs << 4) | orb(p[12]) & 0x0f
769 p = p[:12] + chb(dataofs & 0xff) + p[13:]
770 if self.chksum is None:
771 if isinstance(self.underlayer, IP):
772 ck = in4_chksum(socket.IPPROTO_TCP, self.underlayer, p)
773 p = p[:16] + struct.pack("!H", ck) + p[18:]
774 elif conf.ipv6_enabled and isinstance(self.underlayer, scapy.layers.inet6.IPv6) or isinstance(self.underlayer, scapy.layers.inet6._IPv6ExtHdr): # noqa: E501
775 ck = scapy.layers.inet6.in6_chksum(socket.IPPROTO_TCP, self.underlayer, p) # noqa: E501
776 p = p[:16] + struct.pack("!H", ck) + p[18:]
777 else:
778 log_runtime.info(
779 "No IP underlayer to compute checksum. Leaving null."
780 )
781 return p
783 def hashret(self):
784 if conf.checkIPsrc:
785 return struct.pack("H", self.sport ^ self.dport) + self.payload.hashret() # noqa: E501
786 else:
787 return self.payload.hashret()
789 def answers(self, other):
790 if not isinstance(other, TCP):
791 return 0
792 # RST packets don't get answers
793 if other.flags.R:
794 return 0
795 # We do not support the four-way handshakes with the SYN+ACK
796 # answer split in two packets (one ACK and one SYN): in that
797 # case the ACK will be seen as an answer, but not the SYN.
798 if self.flags.S:
799 # SYN packets without ACK are not answers
800 if not self.flags.A:
801 return 0
802 # SYN+ACK packets answer SYN packets
803 if not other.flags.S:
804 return 0
805 if conf.checkIPsrc:
806 if not ((self.sport == other.dport) and
807 (self.dport == other.sport)):
808 return 0
809 # Do not check ack value for SYN packets without ACK
810 if not (other.flags.S and not other.flags.A) \
811 and abs(other.ack - self.seq) > 2:
812 return 0
813 # Do not check ack value for RST packets without ACK
814 if self.flags.R and not self.flags.A:
815 return 1
816 if abs(other.seq - self.ack) > 2 + len(other.payload):
817 return 0
818 return 1
820 def mysummary(self):
821 if isinstance(self.underlayer, IP):
822 return self.underlayer.sprintf("TCP %IP.src%:%TCP.sport% > %IP.dst%:%TCP.dport% %TCP.flags%") # noqa: E501
823 elif conf.ipv6_enabled and isinstance(self.underlayer, scapy.layers.inet6.IPv6): # noqa: E501
824 return self.underlayer.sprintf("TCP %IPv6.src%:%TCP.sport% > %IPv6.dst%:%TCP.dport% %TCP.flags%") # noqa: E501
825 else:
826 return self.sprintf("TCP %TCP.sport% > %TCP.dport% %TCP.flags%")
829class UDP(Packet):
830 name = "UDP"
831 fields_desc = [ShortEnumField("sport", 53, UDP_SERVICES),
832 ShortEnumField("dport", 53, UDP_SERVICES),
833 ShortField("len", None),
834 XShortField("chksum", None), ]
836 def post_build(self, p, pay):
837 p += pay
838 tmp_len = self.len
839 if tmp_len is None:
840 tmp_len = len(p)
841 p = p[:4] + struct.pack("!H", tmp_len) + p[6:]
842 if self.chksum is None:
843 if isinstance(self.underlayer, IP):
844 ck = in4_chksum(socket.IPPROTO_UDP, self.underlayer, p)
845 # According to RFC768 if the result checksum is 0, it should be set to 0xFFFF # noqa: E501
846 if ck == 0:
847 ck = 0xFFFF
848 p = p[:6] + struct.pack("!H", ck) + p[8:]
849 elif isinstance(self.underlayer, scapy.layers.inet6.IPv6) or isinstance(self.underlayer, scapy.layers.inet6._IPv6ExtHdr): # noqa: E501
850 ck = scapy.layers.inet6.in6_chksum(socket.IPPROTO_UDP, self.underlayer, p) # noqa: E501
851 # According to RFC2460 if the result checksum is 0, it should be set to 0xFFFF # noqa: E501
852 if ck == 0:
853 ck = 0xFFFF
854 p = p[:6] + struct.pack("!H", ck) + p[8:]
855 else:
856 log_runtime.info(
857 "No IP underlayer to compute checksum. Leaving null."
858 )
859 return p
861 def extract_padding(self, s):
862 tmp_len = self.len - 8
863 return s[:tmp_len], s[tmp_len:]
865 def hashret(self):
866 return self.payload.hashret()
868 def answers(self, other):
869 if not isinstance(other, UDP):
870 return 0
871 if conf.checkIPsrc:
872 if self.dport != other.sport:
873 return 0
874 return self.payload.answers(other.payload)
876 def mysummary(self):
877 if isinstance(self.underlayer, IP):
878 return self.underlayer.sprintf("UDP %IP.src%:%UDP.sport% > %IP.dst%:%UDP.dport%") # noqa: E501
879 elif isinstance(self.underlayer, scapy.layers.inet6.IPv6):
880 return self.underlayer.sprintf("UDP %IPv6.src%:%UDP.sport% > %IPv6.dst%:%UDP.dport%") # noqa: E501
881 else:
882 return self.sprintf("UDP %UDP.sport% > %UDP.dport%")
885# RFC 4884 ICMP extensions
886_ICMP_classnums = {
887 # https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml#icmp-parameters-ext-classes
888 1: "MPLS",
889 2: "Interface Information",
890 3: "Interface Identification",
891 4: "Extended Information",
892}
895class ICMPExtension_Object(Packet):
896 name = "ICMP Extension Object"
897 show_indent = 0
898 fields_desc = [
899 ShortField("len", None),
900 ByteEnumField("classnum", 0, _ICMP_classnums),
901 ByteField("classtype", 0),
902 ]
904 def post_build(self, p, pay):
905 if self.len is None:
906 tmp_len = len(p) + len(pay)
907 p = struct.pack("!H", tmp_len) + p[2:]
908 return p + pay
910 registered_icmp_exts = {}
912 @classmethod
913 def register_variant(cls):
914 cls.registered_icmp_exts[cls.classnum.default] = cls
916 @classmethod
917 def dispatch_hook(cls, _pkt=None, *args, **kargs):
918 if _pkt and len(_pkt) >= 4:
919 classnum = _pkt[2]
920 if classnum in cls.registered_icmp_exts:
921 return cls.registered_icmp_exts[classnum]
922 return cls
925class ICMPExtension_InterfaceInformation(ICMPExtension_Object):
926 name = "ICMP Extension Object - Interface Information Object (RFC5837)"
928 fields_desc = [
929 ShortField("len", None),
930 ByteEnumField("classnum", 2, _ICMP_classnums),
931 BitField("classtype", 0, 2),
932 BitField("reserved", 0, 2),
933 BitField("has_ifindex", 0, 1),
934 BitField("has_ipaddr", 0, 1),
935 BitField("has_ifname", 0, 1),
936 BitField("has_mtu", 0, 1),
937 ConditionalField(IntField("ifindex", None), lambda pkt: pkt.has_ifindex == 1),
938 ConditionalField(ShortField("afi", None), lambda pkt: pkt.has_ipaddr == 1),
939 ConditionalField(ShortField("reserved2", 0), lambda pkt: pkt.has_ipaddr == 1),
940 ConditionalField(IPField("ip4", None), lambda pkt: pkt.afi == 1),
941 ConditionalField(IP6Field("ip6", None), lambda pkt: pkt.afi == 2),
942 ConditionalField(
943 FieldLenField("ifname_len", None, fmt="B", length_of="ifname"),
944 lambda pkt: pkt.has_ifname == 1,
945 ),
946 ConditionalField(
947 StrLenField("ifname", None, length_from=lambda pkt: pkt.ifname_len),
948 lambda pkt: pkt.has_ifname == 1,
949 ),
950 ConditionalField(IntField("mtu", None), lambda pkt: pkt.has_mtu == 1),
951 ]
953 def self_build(self, **kwargs):
954 if self.afi is None:
955 if self.ip4 is not None:
956 self.afi = 1
957 elif self.ip6 is not None:
958 self.afi = 2
959 return ICMPExtension_Object.self_build(self, **kwargs)
962class ICMPExtension_Header(Packet):
963 r"""
964 ICMP Extension per RFC4884.
966 Example::
968 pkt = IP(dst="127.0.0.1", src="127.0.0.1") / ICMP(
969 type="time-exceeded",
970 code="ttl-zero-during-transit",
971 ext=ICMPExtension_Header() / ICMPExtension_InterfaceInformation(
972 has_ifindex=1,
973 has_ipaddr=1,
974 has_ifname=1,
975 ip4="10.10.10.10",
976 ifname="hey",
977 )
978 ) / IPerror(src="12.4.4.4", dst="12.1.1.1") / \
979 UDPerror(sport=42315, dport=33440) / \
980 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
981 """
983 name = "ICMP Extension Header (RFC4884)"
984 show_indent = 0
985 fields_desc = [
986 BitField("version", 2, 4),
987 BitField("reserved", 0, 12),
988 XShortField("chksum", None),
989 ]
991 _min_ieo_len = len(ICMPExtension_Object())
993 def post_build(self, p, pay):
994 p += pay
995 if self.chksum is None:
996 ck = checksum(p)
997 p = p[:2] + chb(ck >> 8) + chb(ck & 0xFF) + p[4:]
998 return p
1000 def guess_payload_class(self, payload):
1001 if len(payload) < self._min_ieo_len:
1002 return Packet.guess_payload_class(self, payload)
1003 return ICMPExtension_Object
1006class _ICMPExtensionField(TrailerField):
1007 # We use a TrailerField for building only. Dissection is normal.
1009 def __init__(self):
1010 super(_ICMPExtensionField, self).__init__(
1011 PacketField(
1012 "ext",
1013 None,
1014 ICMPExtension_Header,
1015 ),
1016 )
1018 def getfield(self, pkt, s):
1019 # RFC4884 section 5.2 says if the ICMP packet length
1020 # is >144 then ICMP extensions start at byte 137.
1021 if len(pkt.original) < 144:
1022 return s, None
1023 offset = 136 + len(s) - len(pkt.original)
1024 data = s[offset:]
1025 # Validate checksum
1026 if checksum(data) == data[3:5]:
1027 return s, None # failed
1028 # Dissect
1029 return s[:offset], ICMPExtension_Header(data)
1031 def addfield(self, pkt, s, val):
1032 if val is None:
1033 return s
1034 data = bytes(val)
1035 # Calc how much padding we need, not how much we deserve
1036 pad = 136 - len(pkt.payload) - len(s)
1037 if pad < 0:
1038 warning("ICMPExtension_Header is after the 136th octet of ICMP.")
1039 return data
1040 return super(_ICMPExtensionField, self).addfield(pkt, s, b"\x00" * pad + data)
1043class _ICMPExtensionPadField(TrailerField):
1044 def __init__(self):
1045 super(_ICMPExtensionPadField, self).__init__(
1046 StrFixedLenField("extpad", "", length=0)
1047 )
1049 def i2repr(self, pkt, s):
1050 if s and s == b"\x00" * len(s):
1051 return "b'' (%s octets)" % len(s)
1052 return self.fld.i2repr(pkt, s)
1055def _ICMP_extpad_post_dissection(self, pkt):
1056 # If we have padding, put it in 'extpad' for re-build
1057 if pkt.ext:
1058 pad = pkt.lastlayer()
1059 if isinstance(pad, conf.padding_layer):
1060 pad.underlayer.remove_payload()
1061 pkt.extpad = pad.load
1064icmptypes = {0: "echo-reply",
1065 3: "dest-unreach",
1066 4: "source-quench",
1067 5: "redirect",
1068 8: "echo-request",
1069 9: "router-advertisement",
1070 10: "router-solicitation",
1071 11: "time-exceeded",
1072 12: "parameter-problem",
1073 13: "timestamp-request",
1074 14: "timestamp-reply",
1075 15: "information-request",
1076 16: "information-response",
1077 17: "address-mask-request",
1078 18: "address-mask-reply",
1079 30: "traceroute",
1080 31: "datagram-conversion-error",
1081 32: "mobile-host-redirect",
1082 33: "ipv6-where-are-you",
1083 34: "ipv6-i-am-here",
1084 35: "mobile-registration-request",
1085 36: "mobile-registration-reply",
1086 37: "domain-name-request",
1087 38: "domain-name-reply",
1088 39: "skip",
1089 40: "photuris"}
1092icmpcodes = {3: {0: "network-unreachable",
1093 1: "host-unreachable",
1094 2: "protocol-unreachable",
1095 3: "port-unreachable",
1096 4: "fragmentation-needed",
1097 5: "source-route-failed",
1098 6: "network-unknown",
1099 7: "host-unknown",
1100 9: "network-prohibited",
1101 10: "host-prohibited",
1102 11: "TOS-network-unreachable",
1103 12: "TOS-host-unreachable",
1104 13: "communication-prohibited",
1105 14: "host-precedence-violation",
1106 15: "precedence-cutoff", },
1107 5: {0: "network-redirect",
1108 1: "host-redirect",
1109 2: "TOS-network-redirect",
1110 3: "TOS-host-redirect", },
1111 11: {0: "ttl-zero-during-transit",
1112 1: "ttl-zero-during-reassembly", },
1113 12: {0: "ip-header-bad",
1114 1: "required-option-missing", },
1115 40: {0: "bad-spi",
1116 1: "authentication-failed",
1117 2: "decompression-failed",
1118 3: "decryption-failed",
1119 4: "need-authentification",
1120 5: "need-authorization", }, }
1123_icmp_answers = [
1124 (8, 0),
1125 (13, 14),
1126 (15, 16),
1127 (17, 18),
1128 (33, 34),
1129 (35, 36),
1130 (37, 38),
1131]
1133icmp_id_seq_types = [0, 8, 13, 14, 15, 16, 17, 18, 37, 38]
1136class ICMP(Packet):
1137 name = "ICMP"
1138 fields_desc = [
1139 ByteEnumField("type", 8, icmptypes),
1140 MultiEnumField("code", 0, icmpcodes,
1141 depends_on=lambda pkt:pkt.type, fmt="B"),
1142 XShortField("chksum", None),
1143 ConditionalField(
1144 XShortField("id", 0),
1145 lambda pkt: pkt.type in icmp_id_seq_types
1146 ),
1147 ConditionalField(
1148 XShortField("seq", 0),
1149 lambda pkt: pkt.type in icmp_id_seq_types
1150 ),
1151 ConditionalField(
1152 # Timestamp only (RFC792)
1153 ICMPTimeStampField("ts_ori", None),
1154 lambda pkt: pkt.type in [13, 14]
1155 ),
1156 ConditionalField(
1157 # Timestamp only (RFC792)
1158 ICMPTimeStampField("ts_rx", None),
1159 lambda pkt: pkt.type in [13, 14]
1160 ),
1161 ConditionalField(
1162 # Timestamp only (RFC792)
1163 ICMPTimeStampField("ts_tx", None),
1164 lambda pkt: pkt.type in [13, 14]
1165 ),
1166 ConditionalField(
1167 # Redirect only (RFC792)
1168 IPField("gw", "0.0.0.0"),
1169 lambda pkt: pkt.type == 5
1170 ),
1171 ConditionalField(
1172 # Parameter problem only (RFC792)
1173 ByteField("ptr", 0),
1174 lambda pkt: pkt.type == 12
1175 ),
1176 ConditionalField(
1177 ByteField("reserved", 0),
1178 lambda pkt: pkt.type in [3, 11]
1179 ),
1180 ConditionalField(
1181 ByteField("length", 0),
1182 lambda pkt: pkt.type in [3, 11, 12]
1183 ),
1184 ConditionalField(
1185 IPField("addr_mask", "0.0.0.0"),
1186 lambda pkt: pkt.type in [17, 18]
1187 ),
1188 ConditionalField(
1189 ShortField("nexthopmtu", 0),
1190 lambda pkt: pkt.type == 3
1191 ),
1192 MultipleTypeField(
1193 [
1194 (ShortField("unused", 0),
1195 lambda pkt:pkt.type in [11, 12]),
1196 (IntField("unused", 0),
1197 lambda pkt:pkt.type not in [0, 3, 5, 8, 11, 12,
1198 13, 14, 15, 16, 17,
1199 18])
1200 ],
1201 StrFixedLenField("unused", "", length=0),
1202 ),
1203 # RFC4884 ICMP extension
1204 ConditionalField(
1205 _ICMPExtensionPadField(),
1206 lambda pkt: pkt.type in [3, 11, 12],
1207 ),
1208 ConditionalField(
1209 _ICMPExtensionField(),
1210 lambda pkt: pkt.type in [3, 11, 12],
1211 ),
1212 ]
1214 # To handle extpad
1215 post_dissection = _ICMP_extpad_post_dissection
1217 def post_build(self, p, pay):
1218 p += pay
1219 if self.chksum is None:
1220 ck = checksum(p)
1221 p = p[:2] + chb(ck >> 8) + chb(ck & 0xff) + p[4:]
1222 return p
1224 def hashret(self):
1225 if self.type in icmp_id_seq_types:
1226 return struct.pack("HH", self.id, self.seq) + self.payload.hashret() # noqa: E501
1227 return self.payload.hashret()
1229 def answers(self, other):
1230 if not isinstance(other, ICMP):
1231 return 0
1232 if ((other.type, self.type) in _icmp_answers and
1233 self.id == other.id and
1234 self.seq == other.seq):
1235 return 1
1236 return 0
1238 def guess_payload_class(self, payload):
1239 if self.type in [3, 4, 5, 11, 12]:
1240 return IPerror
1241 else:
1242 return None
1244 def mysummary(self):
1245 extra = ""
1246 if self.ext:
1247 extra = self.ext.payload.sprintf(" ext:%classnum%")
1248 if isinstance(self.underlayer, IP):
1249 return self.underlayer.sprintf(
1250 "ICMP %IP.src% > %IP.dst% %ICMP.type% %ICMP.code%"
1251 ) + extra
1252 else:
1253 return self.sprintf("ICMP %ICMP.type% %ICMP.code%") + extra
1256# IP / TCP / UDP error packets
1258class IPerror(IP):
1259 name = "IP in ICMP"
1261 def answers(self, other):
1262 if not isinstance(other, IP):
1263 return 0
1265 # Check if IP addresses match
1266 test_IPsrc = not conf.checkIPsrc or self.src == other.src
1267 test_IPdst = self.dst == other.dst
1269 # Check if IP ids match
1270 test_IPid = not conf.checkIPID or self.id == other.id
1271 test_IPid |= conf.checkIPID and self.id == socket.htons(other.id)
1273 # Check if IP protocols match
1274 test_IPproto = self.proto == other.proto
1276 if not (test_IPsrc and test_IPdst and test_IPid and test_IPproto):
1277 return 0
1279 return self.payload.answers(other.payload)
1281 def mysummary(self):
1282 return Packet.mysummary(self)
1285class TCPerror(TCP):
1286 name = "TCP in ICMP"
1287 fields_desc = (
1288 TCP.fields_desc[:2] +
1289 # MayEnd after the 8 first octets.
1290 [MayEnd(TCP.fields_desc[2])] +
1291 TCP.fields_desc[3:]
1292 )
1294 def answers(self, other):
1295 if not isinstance(other, TCP):
1296 return 0
1297 if conf.checkIPsrc:
1298 if not ((self.sport == other.sport) and
1299 (self.dport == other.dport)):
1300 return 0
1301 if conf.check_TCPerror_seqack:
1302 if self.seq is not None:
1303 if self.seq != other.seq:
1304 return 0
1305 if self.ack is not None:
1306 if self.ack != other.ack:
1307 return 0
1308 return 1
1310 def mysummary(self):
1311 return Packet.mysummary(self)
1314class UDPerror(UDP):
1315 name = "UDP in ICMP"
1317 def answers(self, other):
1318 if not isinstance(other, UDP):
1319 return 0
1320 if conf.checkIPsrc:
1321 if not ((self.sport == other.sport) and
1322 (self.dport == other.dport)):
1323 return 0
1324 return 1
1326 def mysummary(self):
1327 return Packet.mysummary(self)
1330class ICMPerror(ICMP):
1331 name = "ICMP in ICMP"
1333 def answers(self, other):
1334 if not isinstance(other, ICMP):
1335 return 0
1336 if not ((self.type == other.type) and
1337 (self.code == other.code)):
1338 return 0
1339 if self.code in [0, 8, 13, 14, 17, 18]:
1340 if (self.id == other.id and
1341 self.seq == other.seq):
1342 return 1
1343 else:
1344 return 0
1345 else:
1346 return 1
1348 def mysummary(self):
1349 return Packet.mysummary(self)
1352bind_layers(Ether, IP, type=2048)
1353bind_layers(CookedLinux, IP, proto=2048)
1354bind_layers(GRE, IP, proto=2048)
1355bind_layers(SNAP, IP, code=2048)
1356bind_bottom_up(Loopback, IP, type=0)
1357bind_layers(Loopback, IP, type=socket.AF_INET)
1358bind_layers(IPerror, IPerror, frag=0, proto=4)
1359bind_layers(IPerror, ICMPerror, frag=0, proto=1)
1360bind_layers(IPerror, TCPerror, frag=0, proto=6)
1361bind_layers(IPerror, UDPerror, frag=0, proto=17)
1362bind_layers(IP, IP, frag=0, proto=4)
1363bind_layers(IP, ICMP, frag=0, proto=1)
1364bind_layers(IP, TCP, frag=0, proto=6)
1365bind_layers(IP, UDP, frag=0, proto=17)
1366bind_layers(IP, GRE, frag=0, proto=47)
1367bind_layers(UDP, GRE, dport=4754)
1369conf.l2types.register(DLT_RAW, IP)
1370conf.l2types.register_num2layer(DLT_RAW_ALT, IP)
1371conf.l2types.register(DLT_IPV4, IP)
1372if OPENBSD:
1373 conf.l2types.register_num2layer(228, IP)
1375conf.l3types.register(ETH_P_IP, IP)
1376conf.l3types.register_num2layer(ETH_P_ALL, IP)
1379def inet_register_l3(l2, l3):
1380 """
1381 Resolves the default L2 destination address when IP is used.
1382 """
1383 return getmacbyip(l3.dst)
1386conf.neighbor.register_l3(Ether, IP, inet_register_l3)
1387conf.neighbor.register_l3(Dot3, IP, inet_register_l3)
1390###################
1391# Fragmentation #
1392###################
1394@conf.commands.register
1395def fragment(pkt, fragsize=1480):
1396 """Fragment a big IP datagram"""
1397 if fragsize < 8:
1398 warning("fragsize cannot be lower than 8")
1399 fragsize = max(fragsize, 8)
1400 lastfragsz = fragsize
1401 fragsize -= fragsize % 8
1402 lst = []
1403 for p in pkt:
1404 s = raw(p[IP].payload)
1405 nb = (len(s) - lastfragsz + fragsize - 1) // fragsize + 1
1406 for i in range(nb):
1407 q = p.copy()
1408 del q[IP].payload
1409 del q[IP].chksum
1410 del q[IP].len
1411 if i != nb - 1:
1412 q[IP].flags |= 1
1413 fragend = (i + 1) * fragsize
1414 else:
1415 fragend = i * fragsize + lastfragsz
1416 q[IP].frag += i * fragsize // 8
1417 r = conf.raw_layer(load=s[i * fragsize:fragend])
1418 r.overload_fields = p[IP].payload.overload_fields.copy()
1419 q.add_payload(r)
1420 lst.append(q)
1421 return lst
1424@conf.commands.register
1425def overlap_frag(p, overlap, fragsize=8, overlap_fragsize=None):
1426 """Build overlapping fragments to bypass NIPS
1428p: the original packet
1429overlap: the overlapping data
1430fragsize: the fragment size of the packet
1431overlap_fragsize: the fragment size of the overlapping packet"""
1433 if overlap_fragsize is None:
1434 overlap_fragsize = fragsize
1435 q = p.copy()
1436 del q[IP].payload
1437 q[IP].add_payload(overlap)
1439 qfrag = fragment(q, overlap_fragsize)
1440 qfrag[-1][IP].flags |= 1
1441 return qfrag + fragment(p, fragsize)
1444class BadFragments(ValueError):
1445 def __init__(self, *args, **kwargs):
1446 self.frags = kwargs.pop("frags", None)
1447 super(BadFragments, self).__init__(*args, **kwargs)
1450def _defrag_iter_and_check_offsets(frags):
1451 """
1452 Internal generator used in _defrag_ip_pkt
1453 """
1454 offset = 0
1455 for pkt, o, length in frags:
1456 if offset != o:
1457 if offset > o:
1458 op = ">"
1459 else:
1460 op = "<"
1461 warning("Fragment overlap (%i %s %i) on %r" % (offset, op, o, pkt))
1462 raise BadFragments
1463 offset += length
1464 yield bytes(pkt[IP].payload)
1467def _defrag_ip_pkt(pkt, frags):
1468 """
1469 Defragment a single IP packet.
1471 :param pkt: the new pkt
1472 :param frags: a defaultdict(list) used for storage
1473 :return: a tuple (fragmented, defragmented_value)
1474 """
1475 ip = pkt[IP]
1476 if pkt.frag != 0 or ip.flags.MF:
1477 # fragmented !
1478 uid = (ip.id, ip.src, ip.dst, ip.proto)
1479 if ip.len is None or ip.ihl is None:
1480 fraglen = len(ip.payload)
1481 else:
1482 fraglen = ip.len - (ip.ihl << 2)
1483 # (pkt, frag offset, frag len)
1484 frags[uid].append((pkt, ip.frag << 3, fraglen))
1485 if not ip.flags.MF: # no more fragments = last fragment
1486 curfrags = sorted(frags[uid], key=lambda x: x[1]) # sort by offset
1487 try:
1488 data = b"".join(_defrag_iter_and_check_offsets(curfrags))
1489 except ValueError:
1490 # bad fragment
1491 badfrags = frags[uid]
1492 del frags[uid]
1493 raise BadFragments(frags=badfrags)
1494 # re-build initial packet without fragmentation
1495 p = curfrags[0][0].copy()
1496 pay_class = p[IP].payload.__class__
1497 p[IP].flags.MF = False
1498 p[IP].remove_payload()
1499 p[IP].len = None
1500 p[IP].chksum = None
1501 # append defragmented payload
1502 p /= pay_class(data)
1503 # cleanup
1504 del frags[uid]
1505 return True, p
1506 return True, None
1507 return False, pkt
1510def _defrag_logic(plist, complete=False):
1511 """
1512 Internal function used to defragment a list of packets.
1513 It contains the logic behind the defrag() and defragment() functions
1514 """
1515 frags = defaultdict(list)
1516 final = []
1517 notfrag = []
1518 badfrag = []
1519 # Defrag
1520 for i, pkt in enumerate(plist):
1521 if IP not in pkt:
1522 # no IP layer
1523 if complete:
1524 final.append(pkt)
1525 continue
1526 try:
1527 fragmented, defragmented_value = _defrag_ip_pkt(
1528 pkt,
1529 frags,
1530 )
1531 except BadFragments as ex:
1532 if complete:
1533 final.extend(ex.frags)
1534 else:
1535 badfrag.extend(ex.frags)
1536 continue
1537 if complete and defragmented_value:
1538 final.append(defragmented_value)
1539 elif defragmented_value:
1540 if fragmented:
1541 final.append(defragmented_value)
1542 else:
1543 notfrag.append(defragmented_value)
1544 # Return
1545 if complete:
1546 if hasattr(plist, "listname"):
1547 name = "Defragmented %s" % plist.listname
1548 else:
1549 name = "Defragmented"
1550 return PacketList(final, name=name)
1551 else:
1552 return PacketList(notfrag), PacketList(final), PacketList(badfrag)
1555@conf.commands.register
1556def defrag(plist):
1557 """defrag(plist) -> ([not fragmented], [defragmented],
1558 [ [bad fragments], [bad fragments], ... ])"""
1559 return _defrag_logic(plist, complete=False)
1562@conf.commands.register
1563def defragment(plist):
1564 """defragment(plist) -> plist defragmented as much as possible """
1565 return _defrag_logic(plist, complete=True)
1568# Add timeskew_graph() method to PacketList
1569def _packetlist_timeskew_graph(self, ip, **kargs):
1570 """Tries to graph the timeskew between the timestamps and real time for a given ip""" # noqa: E501
1571 # Defer imports of matplotlib until its needed
1572 # because it has a heavy dep chain
1573 from scapy.libs.matplot import (
1574 plt,
1575 MATPLOTLIB_INLINED,
1576 MATPLOTLIB_DEFAULT_PLOT_KARGS
1577 )
1579 # Filter TCP segments which source address is 'ip'
1580 tmp = (self._elt2pkt(x) for x in self.res)
1581 b = (x for x in tmp if IP in x and x[IP].src == ip and TCP in x)
1583 # Build a list of tuples (creation_time, replied_timestamp)
1584 c = []
1585 tsf = ICMPTimeStampField("", None)
1586 for p in b:
1587 opts = p.getlayer(TCP).options
1588 for o in opts:
1589 if o[0] == "Timestamp":
1590 c.append((p.time, tsf.any2i("", o[1][0])))
1592 # Stop if the list is empty
1593 if not c:
1594 warning("No timestamps found in packet list")
1595 return []
1597 # Prepare the data that will be plotted
1598 first_creation_time = c[0][0]
1599 first_replied_timestamp = c[0][1]
1601 def _wrap_data(ts_tuple, wrap_seconds=2000):
1602 """Wrap the list of tuples."""
1604 ct, rt = ts_tuple # (creation_time, replied_timestamp)
1605 X = ct % wrap_seconds
1606 Y = ((ct - first_creation_time) - ((rt - first_replied_timestamp) / 1000.0)) # noqa: E501
1608 return X, Y
1610 data = [_wrap_data(e) for e in c]
1612 # Mimic the default gnuplot output
1613 if kargs == {}:
1614 kargs = MATPLOTLIB_DEFAULT_PLOT_KARGS
1615 lines = plt.plot(data, **kargs)
1617 # Call show() if matplotlib is not inlined
1618 if not MATPLOTLIB_INLINED:
1619 plt.show()
1621 return lines
1624_PacketList.timeskew_graph = _packetlist_timeskew_graph
1627# Create a new packet list
1628class TracerouteResult(SndRcvList):
1629 __slots__ = ["graphdef", "graphpadding", "graphASres", "padding", "hloc",
1630 "nloc"]
1632 def __init__(self, res=None, name="Traceroute", stats=None):
1633 SndRcvList.__init__(self, res, name, stats)
1634 self.graphdef = None
1635 self.graphASres = None
1636 self.padding = 0
1637 self.hloc = None
1638 self.nloc = None
1640 def show(self):
1641 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
1642 s.ttl,
1643 r.sprintf("%-15s,IP.src% {TCP:%TCP.flags%}{ICMP:%ir,ICMP.type%}"))) # noqa: E501
1645 def get_trace(self):
1646 trace = {}
1647 for s, r in self.res:
1648 if IP not in s:
1649 continue
1650 d = s[IP].dst
1651 if d not in trace:
1652 trace[d] = {}
1653 trace[d][s[IP].ttl] = r[IP].src, ICMP not in r
1654 for k in trace.values():
1655 try:
1656 m = min(x for x, y in k.items() if y[1])
1657 except ValueError:
1658 continue
1659 for li in list(k): # use list(): k is modified in the loop
1660 if li > m:
1661 del k[li]
1662 return trace
1664 def trace3D(self, join=True):
1665 """Give a 3D representation of the traceroute.
1666 right button: rotate the scene
1667 middle button: zoom
1668 shift-left button: move the scene
1669 left button on a ball: toggle IP displaying
1670 double-click button on a ball: scan ports 21,22,23,25,80 and 443 and display the result""" # noqa: E501
1671 # When not ran from a notebook, vpython pooly closes itself
1672 # using os._exit once finished. We pack it into a Process
1673 import multiprocessing
1674 p = multiprocessing.Process(target=self.trace3D_notebook)
1675 p.start()
1676 if join:
1677 p.join()
1679 def trace3D_notebook(self):
1680 """Same than trace3D, used when ran from Jupyter notebooks"""
1681 trace = self.get_trace()
1682 import vpython
1684 class IPsphere(vpython.sphere):
1685 def __init__(self, ip, **kargs):
1686 vpython.sphere.__init__(self, **kargs)
1687 self.ip = ip
1688 self.label = None
1689 self.setlabel(self.ip)
1690 self.last_clicked = None
1691 self.full = False
1692 self.savcolor = vpython.vec(*self.color.value)
1694 def fullinfos(self):
1695 self.full = True
1696 self.color = vpython.vec(1, 0, 0)
1697 a, b = sr(IP(dst=self.ip) / TCP(dport=[21, 22, 23, 25, 80, 443], flags="S"), timeout=2, verbose=0) # noqa: E501
1698 if len(a) == 0:
1699 txt = "%s:\nno results" % self.ip
1700 else:
1701 txt = "%s:\n" % self.ip
1702 for s, r in a:
1703 txt += r.sprintf("{TCP:%IP.src%:%TCP.sport% %TCP.flags%}{TCPerror:%IPerror.dst%:%TCPerror.dport% %IP.src% %ir,ICMP.type%}\n") # noqa: E501
1704 self.setlabel(txt, visible=1)
1706 def unfull(self):
1707 self.color = self.savcolor
1708 self.full = False
1709 self.setlabel(self.ip)
1711 def setlabel(self, txt, visible=None):
1712 if self.label is not None:
1713 if visible is None:
1714 visible = self.label.visible
1715 self.label.visible = 0
1716 elif visible is None:
1717 visible = 0
1718 self.label = vpython.label(text=txt, pos=self.pos, space=self.radius, xoffset=10, yoffset=20, visible=visible) # noqa: E501
1720 def check_double_click(self):
1721 try:
1722 if self.full or not self.label.visible:
1723 return False
1724 if self.last_clicked is not None:
1725 return (time.time() - self.last_clicked) < 0.5
1726 return False
1727 finally:
1728 self.last_clicked = time.time()
1730 def action(self):
1731 self.label.visible ^= 1
1732 if self.full:
1733 self.unfull()
1735 vpython.scene = vpython.canvas()
1736 vpython.scene.title = "<center><u><b>%s</b></u></center>" % self.listname # noqa: E501
1737 vpython.scene.append_to_caption(
1738 re.sub(
1739 r'\%(.*)\%',
1740 r'<span style="color: red">\1</span>',
1741 re.sub(
1742 r'\`(.*)\`',
1743 r'<span style="color: #3399ff">\1</span>',
1744 """<u><b>Commands:</b></u>
1745%Click% to toggle information about a node.
1746%Double click% to perform a quick web scan on this node.
1747<u><b>Camera usage:</b></u>
1748`Right button drag or Ctrl-drag` to rotate "camera" to view scene.
1749`Shift-drag` to move the object around.
1750`Middle button or Alt-drag` to drag up or down to zoom in or out.
1751 On a two-button mouse, `middle is wheel or left + right`.
1752Touch screen: pinch/extend to zoom, swipe or two-finger rotate."""
1753 )
1754 )
1755 )
1756 vpython.scene.exit = True
1757 rings = {}
1758 tr3d = {}
1759 for i in trace:
1760 tr = trace[i]
1761 tr3d[i] = []
1762 for t in range(1, max(tr) + 1):
1763 if t not in rings:
1764 rings[t] = []
1765 if t in tr:
1766 if tr[t] not in rings[t]:
1767 rings[t].append(tr[t])
1768 tr3d[i].append(rings[t].index(tr[t]))
1769 else:
1770 rings[t].append(("unk", -1))
1771 tr3d[i].append(len(rings[t]) - 1)
1773 for t in rings:
1774 r = rings[t]
1775 tmp_len = len(r)
1776 for i in range(tmp_len):
1777 if r[i][1] == -1:
1778 col = vpython.vec(0.75, 0.75, 0.75)
1779 elif r[i][1]:
1780 col = vpython.color.green
1781 else:
1782 col = vpython.color.blue
1784 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
1785 ip=r[i][0],
1786 color=col)
1787 for trlst in tr3d.values():
1788 if t <= len(trlst):
1789 if trlst[t - 1] == i:
1790 trlst[t - 1] = s
1791 forecol = colgen(0.625, 0.4375, 0.25, 0.125)
1792 for trlst in tr3d.values():
1793 col = vpython.vec(*next(forecol))
1794 start = vpython.vec(0, 0, 0)
1795 for ip in trlst:
1796 vpython.cylinder(pos=start, axis=ip.pos - start, color=col, radius=0.2) # noqa: E501
1797 start = ip.pos
1799 vpython.rate(50)
1801 # Keys handling
1802 # TODO: there is currently no way of closing vpython correctly
1803 # https://github.com/BruceSherwood/vpython-jupyter/issues/36
1804 # def keyboard_press(ev):
1805 # k = ev.key
1806 # if k == "esc" or k == "q":
1807 # pass # TODO: close
1808 #
1809 # vpython.scene.bind('keydown', keyboard_press)
1811 # Mouse handling
1812 def mouse_click(ev):
1813 if ev.press == "left":
1814 o = vpython.scene.mouse.pick
1815 if o and isinstance(o, IPsphere):
1816 if o.check_double_click():
1817 if o.ip == "unk":
1818 return
1819 o.fullinfos()
1820 else:
1821 o.action()
1823 vpython.scene.bind('mousedown', mouse_click)
1825 def world_trace(self):
1826 """Display traceroute results on a world map."""
1828 # Check that the geoip2 module can be imported
1829 # Doc: http://geoip2.readthedocs.io/en/latest/
1830 from scapy.libs.matplot import plt, MATPLOTLIB, MATPLOTLIB_INLINED
1832 try:
1833 # GeoIP2 modules need to be imported as below
1834 import geoip2.database
1835 import geoip2.errors
1836 except ImportError:
1837 log_runtime.error(
1838 "Cannot import geoip2. Won't be able to plot the world."
1839 )
1840 return []
1841 # Check availability of database
1842 if not conf.geoip_city:
1843 log_runtime.error(
1844 "Cannot import the geolite2 CITY database.\n"
1845 "Download it from http://dev.maxmind.com/geoip/geoip2/geolite2/" # noqa: E501
1846 " then set its path to conf.geoip_city"
1847 )
1848 return []
1849 # Check availability of plotting devices
1850 try:
1851 import cartopy.crs as ccrs
1852 except ImportError:
1853 log_runtime.error(
1854 "Cannot import cartopy.\n"
1855 "More infos on http://scitools.org.uk/cartopy/docs/latest/installing.html" # noqa: E501
1856 )
1857 return []
1858 if not MATPLOTLIB:
1859 log_runtime.error(
1860 "Matplotlib is not installed. Won't be able to plot the world."
1861 )
1862 return []
1864 # Open & read the GeoListIP2 database
1865 try:
1866 db = geoip2.database.Reader(conf.geoip_city)
1867 except Exception:
1868 log_runtime.error(
1869 "Cannot open geoip2 database at %s",
1870 conf.geoip_city
1871 )
1872 return []
1874 # Regroup results per trace
1875 ips = {}
1876 rt = {}
1877 ports_done = {}
1878 for s, r in self.res:
1879 ips[r.src] = None
1880 if s.haslayer(TCP) or s.haslayer(UDP):
1881 trace_id = (s.src, s.dst, s.proto, s.dport)
1882 elif s.haslayer(ICMP):
1883 trace_id = (s.src, s.dst, s.proto, s.type)
1884 else:
1885 trace_id = (s.src, s.dst, s.proto, 0)
1886 trace = rt.get(trace_id, {})
1887 if not r.haslayer(ICMP) or r.type != 11:
1888 if trace_id in ports_done:
1889 continue
1890 ports_done[trace_id] = None
1891 trace[s.ttl] = r.src
1892 rt[trace_id] = trace
1894 # Get the addresses locations
1895 trt = {}
1896 for trace_id in rt:
1897 trace = rt[trace_id]
1898 loctrace = []
1899 for i in range(max(trace)):
1900 ip = trace.get(i, None)
1901 if ip is None:
1902 continue
1903 # Fetch database
1904 try:
1905 sresult = db.city(ip)
1906 except geoip2.errors.AddressNotFoundError:
1907 continue
1908 loctrace.append((sresult.location.longitude, sresult.location.latitude)) # noqa: E501
1909 if loctrace:
1910 trt[trace_id] = loctrace
1912 # Load the map renderer
1913 plt.figure(num='Scapy')
1914 ax = plt.axes(projection=ccrs.PlateCarree())
1915 # Draw countries
1916 ax.coastlines()
1917 ax.stock_img()
1918 # Set normal size
1919 ax.set_global()
1920 # Add title
1921 plt.title("Scapy traceroute results")
1923 from matplotlib.collections import LineCollection
1924 from matplotlib import colors as mcolors
1925 colors_cycle = iter(mcolors.BASE_COLORS)
1926 lines = []
1928 # Split traceroute measurement
1929 for key, trc in trt.items():
1930 # Get next color
1931 color = next(colors_cycle)
1932 # Gather mesurments data
1933 data_lines = [(trc[i], trc[i + 1]) for i in range(len(trc) - 1)]
1934 # Create line collection
1935 line_col = LineCollection(data_lines, linewidths=2,
1936 label=key[1],
1937 color=color)
1938 lines.append(line_col)
1939 ax.add_collection(line_col)
1940 # Create map points
1941 lines.extend([ax.plot(*x, marker='.', color=color) for x in trc])
1943 # Generate legend
1944 ax.legend()
1946 # Call show() if matplotlib is not inlined
1947 if not MATPLOTLIB_INLINED:
1948 plt.show()
1950 # Clean
1951 ax.remove()
1953 # Return the drawn lines
1954 return lines
1956 def make_graph(self, ASres=None, padding=0):
1957 self.graphASres = ASres
1958 self.graphpadding = padding
1959 ips = {}
1960 rt = {}
1961 ports = {}
1962 ports_done = {}
1963 for s, r in self.res:
1964 r = r.getlayer(IP) or (conf.ipv6_enabled and r[scapy.layers.inet6.IPv6]) or r # noqa: E501
1965 s = s.getlayer(IP) or (conf.ipv6_enabled and s[scapy.layers.inet6.IPv6]) or s # noqa: E501
1966 ips[r.src] = None
1967 if TCP in s:
1968 trace_id = (s.src, s.dst, 6, s.dport)
1969 elif UDP in s:
1970 trace_id = (s.src, s.dst, 17, s.dport)
1971 elif ICMP in s:
1972 trace_id = (s.src, s.dst, 1, s.type)
1973 else:
1974 trace_id = (s.src, s.dst, s.proto, 0)
1975 trace = rt.get(trace_id, {})
1976 ttl = conf.ipv6_enabled and scapy.layers.inet6.IPv6 in s and s.hlim or s.ttl # noqa: E501
1977 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
1978 if trace_id in ports_done:
1979 continue
1980 ports_done[trace_id] = None
1981 p = ports.get(r.src, [])
1982 if TCP in r:
1983 p.append(r.sprintf("<T%ir,TCP.sport%> %TCP.sport% %TCP.flags%")) # noqa: E501
1984 trace[ttl] = r.sprintf('"%r,src%":T%ir,TCP.sport%')
1985 elif UDP in r:
1986 p.append(r.sprintf("<U%ir,UDP.sport%> %UDP.sport%"))
1987 trace[ttl] = r.sprintf('"%r,src%":U%ir,UDP.sport%')
1988 elif ICMP in r:
1989 p.append(r.sprintf("<I%ir,ICMP.type%> ICMP %ICMP.type%"))
1990 trace[ttl] = r.sprintf('"%r,src%":I%ir,ICMP.type%')
1991 else:
1992 p.append(r.sprintf("{IP:<P%ir,proto%> IP %proto%}{IPv6:<P%ir,nh%> IPv6 %nh%}")) # noqa: E501
1993 trace[ttl] = r.sprintf('"%r,src%":{IP:P%ir,proto%}{IPv6:P%ir,nh%}') # noqa: E501
1994 ports[r.src] = p
1995 else:
1996 trace[ttl] = r.sprintf('"%r,src%"')
1997 rt[trace_id] = trace
1999 # Fill holes with unk%i nodes
2000 unknown_label = incremental_label("unk%i")
2001 blackholes = []
2002 bhip = {}
2003 for rtk in rt:
2004 trace = rt[rtk]
2005 max_trace = max(trace)
2006 for n in range(min(trace), max_trace):
2007 if n not in trace:
2008 trace[n] = next(unknown_label)
2009 if rtk not in ports_done:
2010 if rtk[2] == 1: # ICMP
2011 bh = "%s %i/icmp" % (rtk[1], rtk[3])
2012 elif rtk[2] == 6: # TCP
2013 bh = "%s %i/tcp" % (rtk[1], rtk[3])
2014 elif rtk[2] == 17: # UDP
2015 bh = '%s %i/udp' % (rtk[1], rtk[3])
2016 else:
2017 bh = '%s %i/proto' % (rtk[1], rtk[2])
2018 ips[bh] = None
2019 bhip[rtk[1]] = bh
2020 bh = '"%s"' % bh
2021 trace[max_trace + 1] = bh
2022 blackholes.append(bh)
2024 # Find AS numbers
2025 ASN_query_list = set(x.rsplit(" ", 1)[0] for x in ips)
2026 if ASres is None:
2027 ASNlist = []
2028 else:
2029 ASNlist = ASres.resolve(*ASN_query_list)
2031 ASNs = {}
2032 ASDs = {}
2033 for ip, asn, desc, in ASNlist:
2034 if asn is None:
2035 continue
2036 iplist = ASNs.get(asn, [])
2037 if ip in bhip:
2038 if ip in ports:
2039 iplist.append(ip)
2040 iplist.append(bhip[ip])
2041 else:
2042 iplist.append(ip)
2043 ASNs[asn] = iplist
2044 ASDs[asn] = desc
2046 backcolorlist = colgen("60", "86", "ba", "ff")
2047 forecolorlist = colgen("a0", "70", "40", "20")
2049 s = "digraph trace {\n"
2051 s += "\n\tnode [shape=ellipse,color=black,style=solid];\n\n"
2053 s += "\n#ASN clustering\n"
2054 for asn in ASNs:
2055 s += '\tsubgraph cluster_%s {\n' % asn
2056 col = next(backcolorlist)
2057 s += '\t\tcolor="#%s%s%s";' % col
2058 s += '\t\tnode [fillcolor="#%s%s%s",style=filled];' % col
2059 s += '\t\tfontsize = 10;'
2060 s += '\t\tlabel = "%s\\n[%s]"\n' % (asn, ASDs[asn])
2061 for ip in ASNs[asn]:
2063 s += '\t\t"%s";\n' % ip
2064 s += "\t}\n"
2066 s += "#endpoints\n"
2067 for p in ports:
2068 s += '\t"%s" [shape=record,color=black,fillcolor=green,style=filled,label="%s|%s"];\n' % (p, p, "|".join(ports[p])) # noqa: E501
2070 s += "\n#Blackholes\n"
2071 for bh in blackholes:
2072 s += '\t%s [shape=octagon,color=black,fillcolor=red,style=filled];\n' % bh # noqa: E501
2074 if padding:
2075 s += "\n#Padding\n"
2076 pad = {}
2077 for snd, rcv in self.res:
2078 if rcv.src not in ports and rcv.haslayer(conf.padding_layer):
2079 p = rcv.getlayer(conf.padding_layer).load
2080 if p != b"\x00" * len(p):
2081 pad[rcv.src] = None
2082 for rcv in pad:
2083 s += '\t"%s" [shape=triangle,color=black,fillcolor=red,style=filled];\n' % rcv # noqa: E501
2085 s += "\n\tnode [shape=ellipse,color=black,style=solid];\n\n"
2087 for rtk in rt:
2088 s += "#---[%s\n" % repr(rtk)
2089 s += '\t\tedge [color="#%s%s%s"];\n' % next(forecolorlist)
2090 trace = rt[rtk]
2091 maxtrace = max(trace)
2092 for n in range(min(trace), maxtrace):
2093 s += '\t%s ->\n' % trace[n]
2094 s += '\t%s;\n' % trace[maxtrace]
2096 s += "}\n"
2097 self.graphdef = s
2099 def graph(self, ASres=conf.AS_resolver, padding=0, **kargs):
2100 """x.graph(ASres=conf.AS_resolver, other args):
2101 ASres=None : no AS resolver => no clustering
2102 ASres=AS_resolver() : default whois AS resolver (riswhois.ripe.net)
2103 ASres=AS_resolver_cymru(): use whois.cymru.com whois database
2104 ASres=AS_resolver(server="whois.ra.net")
2105 type: output type (svg, ps, gif, jpg, etc.), passed to dot's "-T" option # noqa: E501
2106 target: filename or redirect. Defaults pipe to Imagemagick's display program # noqa: E501
2107 prog: which graphviz program to use"""
2108 if (self.graphdef is None or
2109 self.graphASres != ASres or
2110 self.graphpadding != padding):
2111 self.make_graph(ASres, padding)
2113 return do_graph(self.graphdef, **kargs)
2116@conf.commands.register
2117def traceroute(target, dport=80, minttl=1, maxttl=30, sport=RandShort(), l4=None, filter=None, timeout=2, verbose=None, **kargs): # noqa: E501
2118 """Instant TCP traceroute
2120 :param target: hostnames or IP addresses
2121 :param dport: TCP destination port (default is 80)
2122 :param minttl: minimum TTL (default is 1)
2123 :param maxttl: maximum TTL (default is 30)
2124 :param sport: TCP source port (default is random)
2125 :param l4: use a Scapy packet instead of TCP
2126 :param filter: BPF filter applied to received packets
2127 :param timeout: time to wait for answers (default is 2s)
2128 :param verbose: detailed output
2129 :return: an TracerouteResult, and a list of unanswered packets"""
2130 if verbose is None:
2131 verbose = conf.verb
2132 if filter is None:
2133 # we only consider ICMP error packets and TCP packets with at
2134 # least the ACK flag set *and* either the SYN or the RST flag
2135 # set
2136 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
2137 if l4 is None:
2138 a, b = sr(IP(dst=target, id=RandShort(), ttl=(minttl, maxttl)) / TCP(seq=RandInt(), sport=sport, dport=dport), # noqa: E501
2139 timeout=timeout, filter=filter, verbose=verbose, **kargs)
2140 else:
2141 # this should always work
2142 filter = "ip"
2143 a, b = sr(IP(dst=target, id=RandShort(), ttl=(minttl, maxttl)) / l4,
2144 timeout=timeout, filter=filter, verbose=verbose, **kargs)
2146 a = TracerouteResult(a.res)
2147 if verbose:
2148 a.show()
2149 return a, b
2152@conf.commands.register
2153def traceroute_map(ips, **kargs):
2154 """Util function to call traceroute on multiple targets, then
2155 show the different paths on a map.
2157 :param ips: a list of IPs on which traceroute will be called
2158 :param kargs: (optional) kwargs, passed to traceroute
2159 """
2160 kargs.setdefault("verbose", 0)
2161 return traceroute(ips)[0].world_trace()
2163#############################
2164# Simple TCP client stack #
2165#############################
2168class TCP_client(Automaton):
2169 """
2170 Creates a TCP Client Automaton.
2171 This automaton will handle TCP 3-way handshake.
2173 Usage: the easiest usage is to use it as a SuperSocket.
2174 >>> a = TCP_client.tcplink(HTTP, "www.google.com", 80)
2175 >>> a.send(HTTPRequest())
2176 >>> a.recv()
2178 :param ip: the ip to connect to
2179 :param port:
2180 :param src: (optional) use another source IP
2181 :param sport: (optional) the TCP source port (default: random)
2182 :param seq: (optional) initial TCP sequence number (default: random)
2183 """
2185 def parse_args(self, ip, port, srcip=None, sport=None, seq=None, ack=0, **kargs):
2186 from scapy.sessions import TCPSession
2187 self.dst = str(Net(ip))
2188 self.dport = port
2189 self.sport = sport if sport is not None else random.randrange(0, 2**16)
2190 self.l4 = IP(dst=ip, src=srcip) / TCP(
2191 sport=self.sport, dport=self.dport,
2192 flags=0,
2193 seq=seq if seq is not None else random.randrange(0, 2**32),
2194 ack=ack,
2195 )
2196 self.src = self.l4.src
2197 self.sack = self.l4[TCP].ack
2198 self.rel_seq = None
2199 self.rcvbuf = TCPSession()
2200 bpf = "host %s and host %s and port %i and port %i" % (self.src,
2201 self.dst,
2202 self.sport,
2203 self.dport)
2204 Automaton.parse_args(self, filter=bpf, **kargs)
2206 def _transmit_packet(self, pkt):
2207 """Transmits a packet from TCPSession to the SuperSocket"""
2208 self.oi.tcp.send(raw(pkt[TCP].payload))
2210 def master_filter(self, pkt):
2211 return (IP in pkt and
2212 pkt[IP].src == self.dst and
2213 pkt[IP].dst == self.src and
2214 TCP in pkt and
2215 pkt[TCP].sport == self.dport and
2216 pkt[TCP].dport == self.sport and
2217 self.l4[TCP].seq >= pkt[TCP].ack and # XXX: seq/ack 2^32 wrap up # noqa: E501
2218 ((self.l4[TCP].ack == 0) or (self.sack <= pkt[TCP].seq <= self.l4[TCP].ack + pkt[TCP].window))) # noqa: E501
2220 @ATMT.state(initial=1)
2221 def START(self):
2222 pass
2224 @ATMT.state()
2225 def SYN_SENT(self):
2226 pass
2228 @ATMT.state()
2229 def ESTABLISHED(self):
2230 pass
2232 @ATMT.state()
2233 def LAST_ACK(self):
2234 pass
2236 @ATMT.state(final=1)
2237 def CLOSED(self):
2238 pass
2240 @ATMT.state(stop=1)
2241 def STOP(self):
2242 pass
2244 @ATMT.state()
2245 def STOP_SENT_FIN_ACK(self):
2246 pass
2248 @ATMT.condition(START)
2249 def connect(self):
2250 raise self.SYN_SENT()
2252 @ATMT.action(connect)
2253 def send_syn(self):
2254 self.l4[TCP].flags = "S"
2255 self.send(self.l4)
2256 self.l4[TCP].seq += 1
2258 @ATMT.receive_condition(SYN_SENT)
2259 def synack_received(self, pkt):
2260 if pkt[TCP].flags.SA:
2261 raise self.ESTABLISHED().action_parameters(pkt)
2263 @ATMT.action(synack_received)
2264 def send_ack_of_synack(self, pkt):
2265 self.l4[TCP].ack = pkt[TCP].seq + 1
2266 self.l4[TCP].flags = "A"
2267 self.send(self.l4)
2269 @ATMT.receive_condition(ESTABLISHED)
2270 def incoming_data_received(self, pkt):
2271 if not isinstance(pkt[TCP].payload, (NoPayload, conf.padding_layer)):
2272 raise self.ESTABLISHED().action_parameters(pkt)
2274 @ATMT.action(incoming_data_received)
2275 def receive_data(self, pkt):
2276 data = raw(pkt[TCP].payload)
2277 if data and self.l4[TCP].ack == pkt[TCP].seq:
2278 self.sack = self.l4[TCP].ack
2279 self.l4[TCP].ack += len(data)
2280 self.l4[TCP].flags = "A"
2281 # Answer with an Ack
2282 self.send(self.l4)
2283 # Process data - will be sent to the SuperSocket through this
2284 pkt = self.rcvbuf.process(pkt)
2285 if pkt:
2286 self._transmit_packet(pkt)
2288 @ATMT.ioevent(ESTABLISHED, name="tcp", as_supersocket="tcplink")
2289 def outgoing_data_received(self, fd):
2290 raise self.ESTABLISHED().action_parameters(fd.recv())
2292 @ATMT.action(outgoing_data_received)
2293 def send_data(self, d):
2294 self.l4[TCP].flags = "PA"
2295 self.send(self.l4 / d)
2296 self.l4[TCP].seq += len(d)
2298 @ATMT.receive_condition(ESTABLISHED)
2299 def reset_received(self, pkt):
2300 if pkt[TCP].flags.R:
2301 raise self.CLOSED()
2303 @ATMT.receive_condition(ESTABLISHED)
2304 def fin_received(self, pkt):
2305 if pkt[TCP].flags.F:
2306 raise self.LAST_ACK().action_parameters(pkt)
2308 @ATMT.action(fin_received)
2309 def send_finack(self, pkt):
2310 self.l4[TCP].flags = "FA"
2311 self.l4[TCP].ack = pkt[TCP].seq + 1
2312 self.send(self.l4)
2313 self.l4[TCP].seq += 1
2315 @ATMT.receive_condition(LAST_ACK)
2316 def ack_of_fin_received(self, pkt):
2317 if pkt[TCP].flags.A:
2318 raise self.CLOSED()
2320 @ATMT.condition(STOP)
2321 def stop_requested(self):
2322 raise self.STOP_SENT_FIN_ACK()
2324 @ATMT.action(stop_requested)
2325 def stop_send_finack(self):
2326 self.l4[TCP].flags = "FA"
2327 self.send(self.l4)
2328 self.l4[TCP].seq += 1
2330 @ATMT.receive_condition(STOP_SENT_FIN_ACK)
2331 def stop_fin_received(self, pkt):
2332 if pkt[TCP].flags.F:
2333 raise self.CLOSED().action_parameters(pkt)
2335 @ATMT.action(stop_fin_received)
2336 def stop_send_ack(self, pkt):
2337 self.l4[TCP].flags = "A"
2338 self.l4[TCP].ack = pkt[TCP].seq + 1
2339 self.send(self.l4)
2341 @ATMT.timeout(SYN_SENT, 1)
2342 def syn_ack_timeout(self):
2343 raise self.CLOSED()
2345 @ATMT.timeout(STOP_SENT_FIN_ACK, 1)
2346 def stop_ack_timeout(self):
2347 raise self.CLOSED()
2350#####################
2351# Reporting stuff #
2352#####################
2355@conf.commands.register
2356def report_ports(target, ports):
2357 """portscan a target and output a LaTeX table
2358report_ports(target, ports) -> string"""
2359 ans, unans = sr(IP(dst=target) / TCP(dport=ports), timeout=5)
2360 rep = "\\begin{tabular}{|r|l|l|}\n\\hline\n"
2361 for s, r in ans:
2362 if not r.haslayer(ICMP):
2363 if r.payload.flags == 0x12:
2364 rep += r.sprintf("%TCP.sport% & open & SA \\\\\n")
2365 rep += "\\hline\n"
2366 for s, r in ans:
2367 if r.haslayer(ICMP):
2368 rep += r.sprintf("%TCPerror.dport% & closed & ICMP type %ICMP.type%/%ICMP.code% from %IP.src% \\\\\n") # noqa: E501
2369 elif r.payload.flags != 0x12:
2370 rep += r.sprintf("%TCP.sport% & closed & TCP %TCP.flags% \\\\\n")
2371 rep += "\\hline\n"
2372 for i in unans:
2373 rep += i.sprintf("%TCP.dport% & ? & unanswered \\\\\n")
2374 rep += "\\hline\n\\end{tabular}\n"
2375 return rep
2378@conf.commands.register
2379def IPID_count(lst, funcID=lambda x: x[1].id, funcpres=lambda x: x[1].summary()): # noqa: E501
2380 """Identify IP id values classes in a list of packets
2382lst: a list of packets
2383funcID: a function that returns IP id values
2384funcpres: a function used to summarize packets"""
2385 idlst = [funcID(e) for e in lst]
2386 idlst.sort()
2387 classes = [idlst[0]]
2388 classes += [t[1] for t in zip(idlst[:-1], idlst[1:]) if abs(t[0] - t[1]) > 50] # noqa: E501
2389 lst = [(funcID(x), funcpres(x)) for x in lst]
2390 lst.sort()
2391 print("Probably %i classes: %s" % (len(classes), classes))
2392 for id, pr in lst:
2393 print("%5i" % id, pr)
2396@conf.commands.register
2397def fragleak(target, sport=123, dport=123, timeout=0.2, onlyasc=0, count=None):
2398 load = "XXXXYYYYYYYYYY"
2399 pkt = IP(dst=target, id=RandShort(), options=b"\x00" * 40, flags=1)
2400 pkt /= UDP(sport=sport, dport=sport) / load
2401 s = conf.L3socket()
2402 intr = 0
2403 found = {}
2404 try:
2405 while count is None or count:
2406 if count is not None and isinstance(count, int):
2407 count -= 1
2408 try:
2409 if not intr:
2410 s.send(pkt)
2411 sin = select.select([s], [], [], timeout)[0]
2412 if not sin:
2413 continue
2414 ans = s.recv(1600)
2415 if not isinstance(ans, IP): # TODO: IPv6
2416 continue
2417 if not isinstance(ans.payload, ICMP):
2418 continue
2419 if not isinstance(ans.payload.payload, IPerror):
2420 continue
2421 if ans.payload.payload.dst != target:
2422 continue
2423 if ans.src != target:
2424 print("leak from", ans.src)
2425 if not ans.haslayer(conf.padding_layer):
2426 continue
2427 leak = ans.getlayer(conf.padding_layer).load
2428 if leak not in found:
2429 found[leak] = None
2430 linehexdump(leak, onlyasc=onlyasc)
2431 except KeyboardInterrupt:
2432 if intr:
2433 raise
2434 intr = 1
2435 except KeyboardInterrupt:
2436 pass
2439@conf.commands.register
2440def fragleak2(target, timeout=0.4, onlyasc=0, count=None):
2441 found = {}
2442 try:
2443 while count is None or count:
2444 if count is not None and isinstance(count, int):
2445 count -= 1
2447 pkt = IP(dst=target, options=b"\x00" * 40, proto=200)
2448 pkt /= "XXXXYYYYYYYYYYYY"
2449 p = sr1(pkt, timeout=timeout, verbose=0)
2450 if not p:
2451 continue
2452 if conf.padding_layer in p:
2453 leak = p[conf.padding_layer].load
2454 if leak not in found:
2455 found[leak] = None
2456 linehexdump(leak, onlyasc=onlyasc)
2457 except Exception:
2458 pass
2461@conf.commands.register
2462class connect_from_ip:
2463 """
2464 Open a TCP socket to a host:port while spoofing another IP.
2466 :param host: the host to connect to
2467 :param port: the port to connect to
2468 :param srcip: the IP to spoof. the cache of the gateway will
2469 be poisonned with this IP.
2470 :param poison: (optional, default True) ARP poison the gateway (or next hop),
2471 so that it answers us (only one packet).
2472 :param timeout: (optional) the socket timeout.
2474 Example - Connect to 192.168.0.1:80 spoofing 192.168.0.2::
2476 from scapy.layers.http import HTTP, HTTPRequest
2477 client = connect_from_ip("192.168.0.1", 80, "192.168.0.2")
2478 sock = SSLStreamSocket(client.sock, HTTP)
2479 resp = sock.sr1(HTTP() / HTTPRequest(Path="/"))
2481 Example - Connect to 192.168.0.1:443 with TLS wrapping spoofing 192.168.0.2::
2483 import ssl
2484 from scapy.layers.http import HTTP, HTTPRequest
2485 context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
2486 context.check_hostname = False
2487 context.verify_mode = ssl.CERT_NONE
2488 client = connect_from_ip("192.168.0.1", 443, "192.168.0.2")
2489 sock = context.wrap_socket(client.sock)
2490 sock = SSLStreamSocket(client.sock, HTTP)
2491 resp = sock.sr1(HTTP() / HTTPRequest(Path="/"))
2492 """
2494 def __init__(self, host, port, srcip, poison=True, timeout=1, debug=0):
2495 host = str(Net(host))
2496 if poison:
2497 # poison the next hop
2498 gateway = conf.route.route(host)[2]
2499 if gateway == "0.0.0.0":
2500 # on lan
2501 gateway = host
2502 getmacbyip(gateway) # cache real gateway before poisoning
2503 arpcachepoison(gateway, srcip, count=1, interval=0, verbose=0)
2504 # create a socket pair
2505 self._sock, self.sock = socket.socketpair()
2506 self.sock.settimeout(timeout)
2507 self.client = TCP_client(
2508 host, port,
2509 srcip=srcip,
2510 external_fd={"tcp": self._sock},
2511 debug=debug,
2512 )
2513 # start the TCP_client
2514 self.client.runbg()
2516 def close(self):
2517 self.client.stop()
2518 self.client.destroy()
2519 self.sock.close()
2520 self._sock.close()
2523class ICMPEcho_am(AnsweringMachine):
2524 """Responds to ICMP Echo-Requests (ping)"""
2525 function_name = "icmpechod"
2527 def is_request(self, req):
2528 if req.haslayer(ICMP):
2529 icmp_req = req.getlayer(ICMP)
2530 if icmp_req.type == 8: # echo-request
2531 return True
2533 return False
2535 def print_reply(self, req, reply):
2536 print("Replying %s to %s" % (reply[IP].dst, req[IP].dst))
2538 def make_reply(self, req):
2539 reply = req.copy()
2540 reply[ICMP].type = 0 # echo-reply
2541 # Force re-generation of the checksum
2542 reply[ICMP].chksum = None
2543 if req.haslayer(IP):
2544 reply[IP].src, reply[IP].dst = req[IP].dst, req[IP].src
2545 reply[IP].chksum = None
2546 if req.haslayer(Ether):
2547 reply[Ether].src, reply[Ether].dst = (
2548 None if req[Ether].dst == "ff:ff:ff:ff:ff:ff" else req[Ether].dst,
2549 req[Ether].src,
2550 )
2551 return reply
2554conf.stats_classic_protocols += [TCP, UDP, ICMP]
2555conf.stats_dot11_protocols += [TCP, UDP, ICMP]
2557if conf.ipv6_enabled:
2558 import scapy.layers.inet6