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