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