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 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