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