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) Guillaume Valadon <guillaume@valadon.net>
5
6"""
7Scapy *BSD native support - BPF sockets
8"""
9
10from select import select
11
12import abc
13import ctypes
14import errno
15import fcntl
16import os
17import platform
18import struct
19import sys
20import time
21
22from scapy.arch.bpf.core import get_dev_bpf, attach_filter
23from scapy.arch.bpf.consts import (
24 BIOCGBLEN,
25 BIOCGDLT,
26 BIOCGSTATS,
27 BIOCIMMEDIATE,
28 BIOCPROMISC,
29 BIOCSBLEN,
30 BIOCSDLT,
31 BIOCSETIF,
32 BIOCSHDRCMPLT,
33 BIOCSTSTAMP,
34 BPF_BUFFER_LENGTH,
35 BPF_T_NANOTIME,
36)
37from scapy.config import conf
38from scapy.consts import DARWIN, FREEBSD, NETBSD
39from scapy.data import ETH_P_ALL, DLT_IEEE802_11_RADIO
40from scapy.error import Scapy_Exception, warning
41from scapy.interfaces import network_name, _GlobInterfaceType
42from scapy.supersocket import SuperSocket
43from scapy.compat import raw
44
45# Typing
46from typing import (
47 Any,
48 List,
49 Optional,
50 Tuple,
51 Type,
52 TYPE_CHECKING,
53)
54if TYPE_CHECKING:
55 from scapy.packet import Packet
56
57# Structures & c types
58
59if FREEBSD or NETBSD:
60 # On 32bit architectures long might be 32bit.
61 BPF_ALIGNMENT = ctypes.sizeof(ctypes.c_long)
62else:
63 # DARWIN, OPENBSD
64 BPF_ALIGNMENT = ctypes.sizeof(ctypes.c_int32)
65
66_NANOTIME = FREEBSD # Kinda disappointing availability TBH
67
68if _NANOTIME:
69 # https://github.com/freebsd/freebsd-src/blob/aea4240ef5834fb4a47f80c659c80f902cb4bb06/sys/net/bpf.h#L206-L209
70 class bpf_timeval(ctypes.Structure):
71 # actually a bpf_timespec
72 _fields_ = [("tv_sec", ctypes.c_int64),
73 ("tv_nsec", ctypes.c_uint64)]
74elif NETBSD:
75 class bpf_timeval(ctypes.Structure):
76 _fields_ = [("tv_sec", ctypes.c_ulong),
77 ("tv_usec", ctypes.c_ulong)]
78else:
79 class bpf_timeval(ctypes.Structure): # type: ignore
80 _fields_ = [("tv_sec", ctypes.c_uint32),
81 ("tv_usec", ctypes.c_uint32)]
82
83
84class bpf_hdr(ctypes.Structure):
85 # Also called bpf_xhdr on some OSes
86 _fields_ = [("bh_tstamp", bpf_timeval),
87 ("bh_caplen", ctypes.c_uint32),
88 ("bh_datalen", ctypes.c_uint32),
89 ("bh_hdrlen", ctypes.c_uint16)]
90
91
92_bpf_hdr_len = ctypes.sizeof(bpf_hdr)
93
94# SuperSockets definitions
95
96
97class _L2bpfSocket(SuperSocket):
98 """"Generic Scapy BPF Super Socket"""
99 __slots__ = ["bpf_fd"]
100
101 desc = "read/write packets using BPF"
102 nonblocking_socket = True
103
104 def __init__(self,
105 iface=None, # type: Optional[_GlobInterfaceType]
106 type=ETH_P_ALL, # type: int
107 promisc=None, # type: Optional[bool]
108 filter=None, # type: Optional[str]
109 nofilter=0, # type: int
110 monitor=False, # type: bool
111 ):
112 if monitor:
113 raise Scapy_Exception(
114 "We do not natively support monitor mode on BPF. "
115 "Please turn on libpcap using conf.use_pcap = True"
116 )
117
118 self.fd_flags = None # type: Optional[int]
119 self.type = type
120 self.bpf_fd = -1
121
122 # SuperSocket mandatory variables
123 if promisc is None:
124 promisc = conf.sniff_promisc
125 self.promisc = promisc
126
127 self.iface = network_name(iface or conf.iface)
128
129 # Get the BPF handle
130 self.bpf_fd, self.dev_bpf = get_dev_bpf()
131
132 if FREEBSD:
133 # Set the BPF timeval format. Availability issues here !
134 try:
135 fcntl.ioctl(
136 self.bpf_fd, BIOCSTSTAMP,
137 struct.pack('I', BPF_T_NANOTIME)
138 )
139 except IOError:
140 raise Scapy_Exception("BIOCSTSTAMP failed on /dev/bpf%i" %
141 self.dev_bpf)
142 # Set the BPF buffer length
143 try:
144 fcntl.ioctl(
145 self.bpf_fd, BIOCSBLEN,
146 struct.pack('I', BPF_BUFFER_LENGTH)
147 )
148 except IOError:
149 raise Scapy_Exception("BIOCSBLEN failed on /dev/bpf%i" %
150 self.dev_bpf)
151
152 # Assign the network interface to the BPF handle
153 try:
154 fcntl.ioctl(
155 self.bpf_fd, BIOCSETIF,
156 struct.pack("16s16x", self.iface.encode())
157 )
158 except IOError:
159 raise Scapy_Exception("BIOCSETIF failed on %s" % self.iface)
160
161 # Set the interface into promiscuous
162 if self.promisc:
163 self.set_promisc(True)
164
165 # Set the interface to monitor mode
166 # Note: - trick from libpcap/pcap-bpf.c - monitor_mode()
167 # - it only works on OS X 10.5 and later
168 if DARWIN and monitor:
169 # Convert macOS version to an integer
170 try:
171 tmp_mac_version = platform.mac_ver()[0].split(".")
172 tmp_mac_version = [int(num) for num in tmp_mac_version]
173 macos_version = tmp_mac_version[0] * 10000
174 macos_version += tmp_mac_version[1] * 100 + tmp_mac_version[2]
175 except (IndexError, ValueError):
176 warning("Could not determine your macOS version!")
177 macos_version = sys.maxint
178
179 # Disable 802.11 monitoring on macOS Catalina (aka 10.15) and upper
180 if macos_version < 101500:
181 dlt_radiotap = struct.pack('I', DLT_IEEE802_11_RADIO)
182 try:
183 fcntl.ioctl(self.bpf_fd, BIOCSDLT, dlt_radiotap)
184 except IOError:
185 raise Scapy_Exception("Can't set %s into monitor mode!" %
186 self.iface)
187 else:
188 warning("Scapy won't activate 802.11 monitoring, "
189 "as it will crash your macOS kernel!")
190
191 # Don't block on read
192 try:
193 fcntl.ioctl(self.bpf_fd, BIOCIMMEDIATE, struct.pack('I', 1))
194 except IOError:
195 raise Scapy_Exception("BIOCIMMEDIATE failed on /dev/bpf%i" %
196 self.dev_bpf)
197
198 # Scapy will provide the link layer source address
199 # Otherwise, it is written by the kernel
200 try:
201 fcntl.ioctl(self.bpf_fd, BIOCSHDRCMPLT, struct.pack('i', 1))
202 except IOError:
203 raise Scapy_Exception("BIOCSHDRCMPLT failed on /dev/bpf%i" %
204 self.dev_bpf)
205
206 # Configure the BPF filter
207 filter_attached = False
208 if not nofilter:
209 if conf.except_filter:
210 if filter:
211 filter = "(%s) and not (%s)" % (filter, conf.except_filter)
212 else:
213 filter = "not (%s)" % conf.except_filter
214 if filter is not None:
215 try:
216 attach_filter(self.bpf_fd, filter, self.iface)
217 filter_attached = True
218 except (ImportError, Scapy_Exception) as ex:
219 raise Scapy_Exception("Cannot set filter: %s" % ex)
220 if NETBSD and filter_attached is False:
221 # On NetBSD, a filter must be attached to an interface, otherwise
222 # no frame will be received by os.read(). When no filter has been
223 # configured, Scapy uses a simple tcpdump filter that does nothing
224 # more than ensuring the length frame is not null.
225 filter = "greater 0"
226 try:
227 attach_filter(self.bpf_fd, filter, self.iface)
228 except ImportError as ex:
229 warning("Cannot set filter: %s" % ex)
230
231 # Set the guessed packet class
232 self.guessed_cls = self.guess_cls()
233
234 def set_promisc(self, value):
235 # type: (bool) -> None
236 """Set the interface in promiscuous mode"""
237
238 try:
239 fcntl.ioctl(self.bpf_fd, BIOCPROMISC, struct.pack('i', value))
240 except IOError:
241 raise Scapy_Exception("Cannot set promiscuous mode on interface "
242 "(%s)!" % self.iface)
243
244 def __del__(self):
245 # type: () -> None
246 """Close the file descriptor on delete"""
247 # When the socket is deleted on Scapy exits, __del__ is
248 # sometimes called "too late", and self is None
249 if self is not None:
250 self.close()
251
252 def guess_cls(self):
253 # type: () -> type
254 """Guess the packet class that must be used on the interface"""
255
256 # Get the data link type
257 try:
258 ret = fcntl.ioctl(self.bpf_fd, BIOCGDLT, struct.pack('I', 0))
259 linktype = struct.unpack('I', ret)[0]
260 except IOError:
261 cls = conf.default_l2
262 warning("BIOCGDLT failed: unable to guess type. Using %s !",
263 cls.name)
264 return cls
265
266 # Retrieve the corresponding class
267 try:
268 return conf.l2types.num2layer[linktype]
269 except KeyError:
270 cls = conf.default_l2
271 warning("Unable to guess type (type %i). Using %s", linktype, cls.name)
272 return cls
273
274 def set_nonblock(self, set_flag=True):
275 # type: (bool) -> None
276 """Set the non blocking flag on the socket"""
277
278 # Get the current flags
279 if self.fd_flags is None:
280 try:
281 self.fd_flags = fcntl.fcntl(self.bpf_fd, fcntl.F_GETFL)
282 except IOError:
283 warning("Cannot get flags on this file descriptor !")
284 return
285
286 # Set the non blocking flag
287 if set_flag:
288 new_fd_flags = self.fd_flags | os.O_NONBLOCK
289 else:
290 new_fd_flags = self.fd_flags & ~os.O_NONBLOCK
291
292 try:
293 fcntl.fcntl(self.bpf_fd, fcntl.F_SETFL, new_fd_flags)
294 self.fd_flags = new_fd_flags
295 except Exception:
296 warning("Can't set flags on this file descriptor !")
297
298 def get_stats(self):
299 # type: () -> Tuple[Optional[int], Optional[int]]
300 """Get received / dropped statistics"""
301
302 try:
303 ret = fcntl.ioctl(self.bpf_fd, BIOCGSTATS, struct.pack("2I", 0, 0))
304 return struct.unpack("2I", ret)
305 except IOError:
306 warning("Unable to get stats from BPF !")
307 return (None, None)
308
309 def get_blen(self):
310 # type: () -> Optional[int]
311 """Get the BPF buffer length"""
312
313 try:
314 ret = fcntl.ioctl(self.bpf_fd, BIOCGBLEN, struct.pack("I", 0))
315 return struct.unpack("I", ret)[0] # type: ignore
316 except IOError:
317 warning("Unable to get the BPF buffer length")
318 return None
319
320 def fileno(self):
321 # type: () -> int
322 """Get the underlying file descriptor"""
323 return self.bpf_fd
324
325 def close(self):
326 # type: () -> None
327 """Close the Super Socket"""
328
329 if not self.closed and self.bpf_fd != -1:
330 os.close(self.bpf_fd)
331 self.closed = True
332 self.bpf_fd = -1
333
334 @abc.abstractmethod
335 def send(self, x):
336 # type: (Packet) -> int
337 """Dummy send method"""
338 raise Exception(
339 "Can't send anything with %s" % self.__class__.__name__
340 )
341
342 @abc.abstractmethod
343 def recv_raw(self, x=BPF_BUFFER_LENGTH):
344 # type: (int) -> Tuple[Optional[Type[Packet]], Optional[bytes], Optional[float]] # noqa: E501
345 """Dummy recv method"""
346 raise Exception(
347 "Can't recv anything with %s" % self.__class__.__name__
348 )
349
350 @staticmethod
351 def select(sockets, remain=None):
352 # type: (List[SuperSocket], Optional[float]) -> List[SuperSocket]
353 """This function is called during sendrecv() routine to select
354 the available sockets.
355 """
356 # sockets, None (means use the socket's recv() )
357 return bpf_select(sockets, remain)
358
359
360class L2bpfListenSocket(_L2bpfSocket):
361 """"Scapy L2 BPF Listen Super Socket"""
362
363 def __init__(self, *args, **kwargs):
364 # type: (*Any, **Any) -> None
365 self.received_frames = [] # type: List[Tuple[Optional[type], Optional[bytes], Optional[float]]] # noqa: E501
366 super(L2bpfListenSocket, self).__init__(*args, **kwargs)
367
368 def buffered_frames(self):
369 # type: () -> int
370 """Return the number of frames in the buffer"""
371 return len(self.received_frames)
372
373 def get_frame(self):
374 # type: () -> Tuple[Optional[type], Optional[bytes], Optional[float]]
375 """Get a frame or packet from the received list"""
376 if self.received_frames:
377 return self.received_frames.pop(0)
378 else:
379 return None, None, None
380
381 @staticmethod
382 def bpf_align(bh_h, bh_c):
383 # type: (int, int) -> int
384 """Return the index to the end of the current packet"""
385
386 # from <net/bpf.h>
387 return ((bh_h + bh_c) + (BPF_ALIGNMENT - 1)) & ~(BPF_ALIGNMENT - 1)
388
389 def extract_frames(self, bpf_buffer):
390 # type: (bytes) -> None
391 """
392 Extract all frames from the buffer and stored them in the received list
393 """
394
395 # Ensure that the BPF buffer contains at least the header
396 len_bb = len(bpf_buffer)
397 if len_bb < _bpf_hdr_len:
398 return
399
400 # Extract useful information from the BPF header
401 bh_hdr = bpf_hdr.from_buffer_copy(bpf_buffer)
402 if bh_hdr.bh_datalen == 0:
403 return
404
405 # Get and store the Scapy object
406 frame_str = bpf_buffer[
407 bh_hdr.bh_hdrlen:bh_hdr.bh_hdrlen + bh_hdr.bh_caplen
408 ]
409 if _NANOTIME:
410 ts = bh_hdr.bh_tstamp.tv_sec + 1e-9 * bh_hdr.bh_tstamp.tv_nsec
411 else:
412 ts = bh_hdr.bh_tstamp.tv_sec + 1e-6 * bh_hdr.bh_tstamp.tv_usec
413 self.received_frames.append(
414 (self.guessed_cls, frame_str, ts)
415 )
416
417 # Extract the next frame
418 end = self.bpf_align(bh_hdr.bh_hdrlen, bh_hdr.bh_caplen)
419 if (len_bb - end) >= 20:
420 self.extract_frames(bpf_buffer[end:])
421
422 def recv_raw(self, x=BPF_BUFFER_LENGTH):
423 # type: (int) -> Tuple[Optional[type], Optional[bytes], Optional[float]]
424 """Receive a frame from the network"""
425
426 x = min(x, BPF_BUFFER_LENGTH)
427
428 if self.buffered_frames():
429 # Get a frame from the buffer
430 return self.get_frame()
431
432 # Get data from BPF
433 try:
434 bpf_buffer = os.read(self.bpf_fd, x)
435 except EnvironmentError as exc:
436 if exc.errno != errno.EAGAIN:
437 warning("BPF recv_raw()", exc_info=True)
438 return None, None, None
439
440 # Extract all frames from the BPF buffer
441 self.extract_frames(bpf_buffer)
442 return self.get_frame()
443
444
445class L2bpfSocket(L2bpfListenSocket):
446 """"Scapy L2 BPF Super Socket"""
447
448 def send(self, x):
449 # type: (Packet) -> int
450 """Send a frame"""
451 return os.write(self.bpf_fd, raw(x))
452
453 def nonblock_recv(self):
454 # type: () -> Optional[Packet]
455 """Non blocking receive"""
456
457 if self.buffered_frames():
458 # Get a frame from the buffer
459 return L2bpfListenSocket.recv(self)
460
461 # Set the non blocking flag, read from the socket, and unset the flag
462 self.set_nonblock(True)
463 pkt = L2bpfListenSocket.recv(self)
464 self.set_nonblock(False)
465 return pkt
466
467
468class L3bpfSocket(L2bpfSocket):
469
470 def __init__(self,
471 iface=None, # type: Optional[_GlobInterfaceType]
472 type=ETH_P_ALL, # type: int
473 promisc=None, # type: Optional[bool]
474 filter=None, # type: Optional[str]
475 nofilter=0, # type: int
476 monitor=False, # type: bool
477 ):
478 super(L3bpfSocket, self).__init__(
479 iface=iface,
480 type=type,
481 promisc=promisc,
482 filter=filter,
483 nofilter=nofilter,
484 monitor=monitor,
485 )
486 self.filter = filter
487 self.send_socks = {network_name(self.iface): self}
488
489 def recv(self, x: int = BPF_BUFFER_LENGTH, **kwargs: Any) -> Optional['Packet']:
490 """Receive on layer 3"""
491 r = SuperSocket.recv(self, x, **kwargs)
492 if r:
493 r.payload.time = r.time
494 return r.payload
495 return r
496
497 def send(self, pkt):
498 # type: (Packet) -> int
499 """Send a packet"""
500 from scapy.layers.l2 import Loopback
501
502 # Use the routing table to find the output interface
503 iff = pkt.route()[0]
504 if iff is None:
505 iff = network_name(conf.iface)
506
507 # Assign the network interface to the BPF handle
508 if iff not in self.send_socks:
509 self.send_socks[iff] = L3bpfSocket(
510 iface=iff,
511 type=self.type,
512 filter=self.filter,
513 promisc=self.promisc,
514 )
515 fd = self.send_socks[iff]
516
517 # Build the frame
518 #
519 # LINKTYPE_NULL / DLT_NULL (Loopback) is a special case. From the
520 # bpf(4) man page (from macOS/Darwin, but also for BSD):
521 #
522 # "A packet can be sent out on the network by writing to a bpf file
523 # descriptor. [...] Currently only writes to Ethernets and SLIP links
524 # are supported."
525 #
526 # Headers are only mentioned for reads, not writes, and it has the
527 # name "NULL" and id=0.
528 #
529 # The _correct_ behaviour appears to be that one should add a BSD
530 # Loopback header to every sent packet. This is needed by FreeBSD's
531 # if_lo, and Darwin's if_lo & if_utun.
532 #
533 # tuntaposx appears to have interpreted "NULL" as "no headers".
534 # Thankfully its interfaces have a different name (tunX) to Darwin's
535 # if_utun interfaces (utunX).
536 #
537 # There might be other drivers which make the same mistake as
538 # tuntaposx, but these are typically provided with VPN software, and
539 # Apple are breaking these kexts in a future version of macOS... so
540 # the problem will eventually go away. They already don't work on Macs
541 # with Apple Silicon (M1).
542 if DARWIN and iff.startswith('tun') and self.guessed_cls == Loopback:
543 frame = pkt
544 elif FREEBSD and (iff.startswith('tun') or iff.startswith('tap')):
545 # On FreeBSD, the bpf manpage states that it is only possible
546 # to write packets to Ethernet and SLIP network interfaces
547 # using /dev/bpf
548 #
549 # Note: `open("/dev/tun0", "wb").write(raw(pkt())) should be
550 # used
551 warning("Cannot write to %s according to the documentation!", iff)
552 return
553 else:
554 frame = fd.guessed_cls() / pkt
555
556 pkt.sent_time = time.time()
557
558 # Send the frame
559 return L2bpfSocket.send(fd, frame)
560
561 @staticmethod
562 def select(sockets, remain=None):
563 # type: (List[SuperSocket], Optional[float]) -> List[SuperSocket]
564 socks = [] # type: List[SuperSocket]
565 for sock in sockets:
566 if isinstance(sock, L3bpfSocket):
567 socks += sock.send_socks.values()
568 else:
569 socks.append(sock)
570 return L2bpfSocket.select(socks, remain=remain)
571
572
573# Sockets manipulation functions
574
575def bpf_select(fds_list, timeout=None):
576 # type: (List[SuperSocket], Optional[float]) -> List[SuperSocket]
577 """A call to recv() can return several frames. This functions hides the fact
578 that some frames are read from the internal buffer."""
579
580 # Check file descriptors types
581 bpf_scks_buffered = list() # type: List[SuperSocket]
582 select_fds = list()
583
584 for tmp_fd in fds_list:
585
586 # Specific BPF sockets: get buffers status
587 if isinstance(tmp_fd, L2bpfListenSocket) and tmp_fd.buffered_frames():
588 bpf_scks_buffered.append(tmp_fd)
589 continue
590
591 # Regular file descriptors or empty BPF buffer
592 select_fds.append(tmp_fd)
593
594 if select_fds:
595 # Call select for sockets with empty buffers
596 if timeout is None:
597 timeout = 0.05
598 ready_list, _, _ = select(select_fds, [], [], timeout)
599 return bpf_scks_buffered + ready_list
600 else:
601 return bpf_scks_buffered