Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/scapy/arch/bpf/supersocket.py: 3%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

278 statements  

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