Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/scapy/arch/linux/__init__.py: 39%

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

268 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) Philippe Biondi <phil@secdev.org> 

5 

6""" 

7Linux specific functions. 

8""" 

9 

10 

11from fcntl import ioctl 

12from select import select 

13 

14import os 

15import socket 

16import struct 

17import subprocess 

18import sys 

19import time 

20 

21from scapy.compat import raw 

22from scapy.consts import LINUX 

23from scapy.arch.common import compile_filter, free_filter 

24from scapy.config import conf 

25from scapy.data import MTU, ETH_P_ALL, SOL_PACKET, SO_ATTACH_FILTER, \ 

26 SO_TIMESTAMPNS 

27from scapy.error import ( 

28 ScapyInvalidPlatformException, 

29 Scapy_Exception, 

30 log_runtime, 

31 warning, 

32) 

33from scapy.interfaces import ( 

34 InterfaceProvider, 

35 NetworkInterface, 

36 _GlobInterfaceType, 

37 network_name, 

38 resolve_iface, 

39) 

40from scapy.packet import Packet, Padding 

41from scapy.supersocket import SuperSocket 

42 

43# re-export 

44from scapy.arch.common import get_if_raw_addr, read_nameservers # noqa: F401 

45from scapy.arch.linux.rtnetlink import ( # noqa: F401 

46 read_routes, 

47 read_routes6, 

48 in6_getifaddr, 

49 _get_if_list, 

50) 

51 

52# Typing imports 

53from typing import ( 

54 Any, 

55 Dict, 

56 List, 

57 NoReturn, 

58 Optional, 

59 Tuple, 

60 Type, 

61 Union, 

62) 

63 

64# From sockios.h 

65SIOCGIFHWADDR = 0x8927 # Get hardware address 

66SIOCGIFADDR = 0x8915 # get PA address 

67SIOCGIFNETMASK = 0x891b # get network PA mask 

68SIOCGIFNAME = 0x8910 # get iface name 

69SIOCSIFLINK = 0x8911 # set iface channel 

70SIOCGIFCONF = 0x8912 # get iface list 

71SIOCGIFFLAGS = 0x8913 # get flags 

72SIOCSIFFLAGS = 0x8914 # set flags 

73SIOCGIFINDEX = 0x8933 # name -> if_index mapping 

74SIOCGIFCOUNT = 0x8938 # get number of devices 

75SIOCGSTAMP = 0x8906 # get packet timestamp (as a timeval) 

76 

77# From if.h 

78IFF_UP = 0x1 # Interface is up. 

79IFF_BROADCAST = 0x2 # Broadcast address valid. 

80IFF_DEBUG = 0x4 # Turn on debugging. 

81IFF_LOOPBACK = 0x8 # Is a loopback net. 

82IFF_POINTOPOINT = 0x10 # Interface is point-to-point link. 

83IFF_NOTRAILERS = 0x20 # Avoid use of trailers. 

84IFF_RUNNING = 0x40 # Resources allocated. 

85IFF_NOARP = 0x80 # No address resolution protocol. 

86IFF_PROMISC = 0x100 # Receive all packets. 

87 

88# From netpacket/packet.h 

89PACKET_ADD_MEMBERSHIP = 1 

90PACKET_DROP_MEMBERSHIP = 2 

91PACKET_RECV_OUTPUT = 3 

92PACKET_RX_RING = 5 

93PACKET_STATISTICS = 6 

94PACKET_MR_MULTICAST = 0 

95PACKET_MR_PROMISC = 1 

96PACKET_MR_ALLMULTI = 2 

97 

98# From net/route.h 

99RTF_UP = 0x0001 # Route usable 

100RTF_REJECT = 0x0200 

101 

102# From if_packet.h 

103PACKET_HOST = 0 # To us 

104PACKET_BROADCAST = 1 # To all 

105PACKET_MULTICAST = 2 # To group 

106PACKET_OTHERHOST = 3 # To someone else 

107PACKET_OUTGOING = 4 # Outgoing of any type 

108PACKET_LOOPBACK = 5 # MC/BRD frame looped back 

109PACKET_USER = 6 # To user space 

110PACKET_KERNEL = 7 # To kernel space 

111PACKET_AUXDATA = 8 

112PACKET_FASTROUTE = 6 # Fastrouted frame 

113# Unused, PACKET_FASTROUTE and PACKET_LOOPBACK are invisible to user space 

114 

115 

116# Utils 

117 

118def attach_filter(sock, bpf_filter, iface): 

119 # type: (socket.socket, str, _GlobInterfaceType) -> None 

120 """ 

121 Compile bpf filter and attach it to a socket 

122 

123 :param sock: the python socket 

124 :param bpf_filter: the bpf string filter to compile 

125 :param iface: the interface used to compile 

126 """ 

127 try: 

128 import ctypes 

129 from scapy.libs.structures import sock_fprog 

130 except OSError: 

131 raise Scapy_Exception("ctypes is not available") 

132 

133 bp = compile_filter(bpf_filter, iface) 

134 if conf.use_pypy and sys.pypy_version_info <= (7, 3, 2): # type: ignore 

135 # PyPy < 7.3.2 has a broken behavior 

136 # https://foss.heptapod.net/pypy/pypy/-/issues/3298 

137 fp = struct.pack( 

138 'HL', 

139 bp.bf_len, ctypes.addressof(bp.bf_insns.contents) 

140 ) 

141 else: 

142 fp = sock_fprog(bp.bf_len, bp.bf_insns) # type: ignore 

143 sock.setsockopt(socket.SOL_SOCKET, SO_ATTACH_FILTER, fp) 

144 free_filter(bp) 

145 

146 

147def set_promisc(s, iff, val=1): 

148 # type: (socket.socket, _GlobInterfaceType, int) -> None 

149 _iff = resolve_iface(iff) 

150 mreq = struct.pack("IHH8s", _iff.index, PACKET_MR_PROMISC, 0, b"") 

151 if val: 

152 cmd = PACKET_ADD_MEMBERSHIP 

153 else: 

154 cmd = PACKET_DROP_MEMBERSHIP 

155 s.setsockopt(SOL_PACKET, cmd, mreq) 

156 

157 

158# Interface provider 

159 

160 

161class LinuxInterfaceProvider(InterfaceProvider): 

162 name = "sys" 

163 

164 def _is_valid(self, dev): 

165 # type: (NetworkInterface) -> bool 

166 return bool(dev.flags & IFF_UP) 

167 

168 def load(self): 

169 # type: () -> Dict[str, NetworkInterface] 

170 data = {} 

171 for iface in _get_if_list().values(): 

172 if_data = iface.copy() 

173 if_data.update({ 

174 "network_name": iface["name"], 

175 "description": iface["name"], 

176 "ips": [x["address"] for x in iface["ips"]] 

177 }) 

178 data[iface["name"]] = NetworkInterface(self, if_data) 

179 return data 

180 

181 

182conf.ifaces.register_provider(LinuxInterfaceProvider) 

183 

184if os.uname()[4] in ['x86_64', 'aarch64']: 

185 def get_last_packet_timestamp(sock): 

186 # type: (socket.socket) -> float 

187 ts = ioctl(sock, SIOCGSTAMP, "1234567890123456") # type: ignore 

188 s, us = struct.unpack("QQ", ts) # type: Tuple[int, int] 

189 return s + us / 1000000.0 

190else: 

191 def get_last_packet_timestamp(sock): 

192 # type: (socket.socket) -> float 

193 ts = ioctl(sock, SIOCGSTAMP, "12345678") # type: ignore 

194 s, us = struct.unpack("II", ts) # type: Tuple[int, int] 

195 return s + us / 1000000.0 

196 

197 

198def _flush_fd(fd): 

199 # type: (int) -> None 

200 while True: 

201 r, w, e = select([fd], [], [], 0) 

202 if r: 

203 os.read(fd, MTU) 

204 else: 

205 break 

206 

207 

208class L2Socket(SuperSocket): 

209 desc = "read/write packets at layer 2 using Linux PF_PACKET sockets" 

210 

211 def __init__(self, 

212 iface=None, # type: Optional[Union[str, NetworkInterface]] 

213 type=ETH_P_ALL, # type: int 

214 promisc=None, # type: Optional[Any] 

215 filter=None, # type: Optional[Any] 

216 nofilter=0, # type: int 

217 monitor=None, # type: Optional[Any] 

218 ): 

219 # type: (...) -> None 

220 self.iface = network_name(iface or conf.iface) 

221 self.type = type 

222 self.promisc = conf.sniff_promisc if promisc is None else promisc 

223 self.ins = socket.socket( 

224 socket.AF_PACKET, socket.SOCK_RAW, socket.htons(type)) 

225 self.ins.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 0) 

226 if not nofilter: 

227 if conf.except_filter: 

228 if filter: 

229 filter = "(%s) and not (%s)" % (filter, conf.except_filter) 

230 else: 

231 filter = "not (%s)" % conf.except_filter 

232 if filter is not None: 

233 try: 

234 attach_filter(self.ins, filter, self.iface) 

235 except (ImportError, Scapy_Exception) as ex: 

236 raise Scapy_Exception("Cannot set filter: %s" % ex) 

237 if self.promisc: 

238 set_promisc(self.ins, self.iface) 

239 self.ins.bind((self.iface, type)) 

240 _flush_fd(self.ins.fileno()) 

241 self.ins.setsockopt( 

242 socket.SOL_SOCKET, 

243 socket.SO_RCVBUF, 

244 conf.bufsize 

245 ) 

246 # Receive Auxiliary Data (VLAN tags) 

247 try: 

248 self.ins.setsockopt(SOL_PACKET, PACKET_AUXDATA, 1) 

249 self.ins.setsockopt(socket.SOL_SOCKET, SO_TIMESTAMPNS, 1) 

250 self.auxdata_available = True 

251 except OSError: 

252 # Note: Auxiliary Data is only supported since 

253 # Linux 2.6.21 

254 msg = "Your Linux Kernel does not support Auxiliary Data!" 

255 log_runtime.info(msg) 

256 if not isinstance(self, L2ListenSocket): 

257 self.outs = self.ins # type: socket.socket 

258 self.outs.setsockopt( 

259 socket.SOL_SOCKET, 

260 socket.SO_SNDBUF, 

261 conf.bufsize 

262 ) 

263 else: 

264 self.outs = None # type: ignore 

265 sa_ll = self.ins.getsockname() 

266 if sa_ll[3] in conf.l2types: 

267 self.LL = conf.l2types.num2layer[sa_ll[3]] 

268 self.lvl = 2 

269 elif sa_ll[1] in conf.l3types: 

270 self.LL = conf.l3types.num2layer[sa_ll[1]] 

271 self.lvl = 3 

272 else: 

273 self.LL = conf.default_l2 

274 self.lvl = 2 

275 warning("Unable to guess type (interface=%s protocol=%#x family=%i). Using %s", sa_ll[0], sa_ll[1], sa_ll[3], self.LL.name) # noqa: E501 

276 

277 def close(self): 

278 # type: () -> None 

279 if self.closed: 

280 return 

281 try: 

282 if self.promisc and getattr(self, "ins", None): 

283 set_promisc(self.ins, self.iface, 0) 

284 except (AttributeError, OSError, ValueError): 

285 pass 

286 SuperSocket.close(self) 

287 

288 def recv_raw(self, x=MTU): 

289 # type: (int) -> Tuple[Optional[Type[Packet]], Optional[bytes], Optional[float]] # noqa: E501 

290 """Receives a packet, then returns a tuple containing (cls, pkt_data, time)""" # noqa: E501 

291 pkt, sa_ll, ts = self._recv_raw(self.ins, x) 

292 if self.outs and sa_ll[2] == socket.PACKET_OUTGOING: 

293 return None, None, None 

294 if ts is None: 

295 ts = get_last_packet_timestamp(self.ins) 

296 return self.LL, pkt, ts 

297 

298 def send(self, x): 

299 # type: (Packet) -> int 

300 try: 

301 return SuperSocket.send(self, x) 

302 except socket.error as msg: 

303 if msg.errno == 22 and len(x) < conf.min_pkt_size: 

304 padding = b"\x00" * (conf.min_pkt_size - len(x)) 

305 if isinstance(x, Packet): 

306 return SuperSocket.send(self, x / Padding(load=padding)) 

307 else: 

308 return SuperSocket.send(self, raw(x) + padding) 

309 raise 

310 

311 

312class L2ListenSocket(L2Socket): 

313 desc = "read packets at layer 2 using Linux PF_PACKET sockets. Also receives the packets going OUT" # noqa: E501 

314 

315 def send(self, x): 

316 # type: (Packet) -> NoReturn 

317 raise Scapy_Exception("Can't send anything with L2ListenSocket") 

318 

319 

320class L3PacketSocket(L2Socket): 

321 desc = "read/write packets at layer 3 using Linux PF_PACKET sockets" 

322 

323 def __init__(self, 

324 iface=None, # type: Optional[Union[str, NetworkInterface]] 

325 type=ETH_P_ALL, # type: int 

326 promisc=None, # type: Optional[Any] 

327 filter=None, # type: Optional[Any] 

328 nofilter=0, # type: int 

329 monitor=None, # type: Optional[Any] 

330 ): 

331 self.send_socks = {} 

332 super(L3PacketSocket, self).__init__( 

333 iface=iface, 

334 type=type, 

335 promisc=promisc, 

336 filter=filter, 

337 nofilter=nofilter, 

338 monitor=monitor, 

339 ) 

340 self.filter = filter 

341 self.send_socks = {network_name(self.iface): self} 

342 

343 def recv(self, x=MTU, **kwargs): 

344 # type: (int, **Any) -> Optional[Packet] 

345 pkt = SuperSocket.recv(self, x, **kwargs) 

346 if pkt and self.lvl == 2: 

347 pkt.payload.time = pkt.time 

348 return pkt.payload 

349 return pkt 

350 

351 def send(self, x): 

352 # type: (Packet) -> int 

353 # Select the file descriptor to send the packet on. 

354 iff = x.route()[0] 

355 if iff is None: 

356 iff = network_name(conf.iface) 

357 type_x = type(x) 

358 if iff not in self.send_socks: 

359 self.send_socks[iff] = L3PacketSocket( 

360 iface=iff, 

361 type=conf.l3types.layer2num.get(type_x, self.type), 

362 filter=self.filter, 

363 promisc=self.promisc, 

364 ) 

365 sock = self.send_socks[iff] 

366 fd = sock.outs 

367 if sock.lvl == 3: 

368 if not issubclass(sock.LL, type_x): 

369 warning("Incompatible L3 types detected using %s instead of %s !", 

370 type_x, sock.LL) 

371 sock.LL = type_x 

372 if sock.lvl == 2: 

373 sx = bytes(sock.LL() / x) 

374 else: 

375 sx = bytes(x) 

376 # Now send. 

377 try: 

378 x.sent_time = time.time() 

379 except AttributeError: 

380 pass 

381 try: 

382 return fd.send(sx) 

383 except socket.error as msg: 

384 if msg.errno == 22 and len(sx) < conf.min_pkt_size: 

385 return fd.send( 

386 sx + b"\x00" * (conf.min_pkt_size - len(sx)) 

387 ) 

388 elif conf.auto_fragment and msg.errno == 90: 

389 i = 0 

390 for p in x.fragment(): 

391 i += fd.send(bytes(self.LL() / p)) 

392 return i 

393 else: 

394 raise 

395 

396 @staticmethod 

397 def select(sockets, remain=None): 

398 # type: (List[SuperSocket], Optional[float]) -> List[SuperSocket] 

399 socks = [] # type: List[SuperSocket] 

400 for sock in sockets: 

401 if isinstance(sock, L3PacketSocket): 

402 socks += sock.send_socks.values() 

403 else: 

404 socks.append(sock) 

405 return L2Socket.select(socks, remain=remain) 

406 

407 def close(self): 

408 # type: () -> None 

409 if self.closed: 

410 return 

411 super(L3PacketSocket, self).close() 

412 for fd in self.send_socks.values(): 

413 if fd is not self: 

414 fd.close() 

415 

416 

417class VEthPair(object): 

418 """ 

419 encapsulates a virtual Ethernet interface pair 

420 """ 

421 

422 def __init__(self, iface_name, peer_name): 

423 # type: (str, str) -> None 

424 if not LINUX: 

425 # ToDo: do we need a kernel version check here? 

426 raise ScapyInvalidPlatformException( 

427 'Virtual Ethernet interface pair only available on Linux' 

428 ) 

429 

430 self.ifaces = [iface_name, peer_name] 

431 

432 def iface(self): 

433 # type: () -> str 

434 return self.ifaces[0] 

435 

436 def peer(self): 

437 # type: () -> str 

438 return self.ifaces[1] 

439 

440 def setup(self): 

441 # type: () -> None 

442 """ 

443 create veth pair links 

444 :raises subprocess.CalledProcessError if operation fails 

445 """ 

446 subprocess.check_call(['ip', 'link', 'add', self.ifaces[0], 'type', 'veth', 'peer', 'name', self.ifaces[1]]) # noqa: E501 

447 

448 def destroy(self): 

449 # type: () -> None 

450 """ 

451 remove veth pair links 

452 :raises subprocess.CalledProcessError if operation fails 

453 """ 

454 subprocess.check_call(['ip', 'link', 'del', self.ifaces[0]]) 

455 

456 def up(self): 

457 # type: () -> None 

458 """ 

459 set veth pair links up 

460 :raises subprocess.CalledProcessError if operation fails 

461 """ 

462 for idx in [0, 1]: 

463 subprocess.check_call(["ip", "link", "set", self.ifaces[idx], "up"]) # noqa: E501 

464 

465 def down(self): 

466 # type: () -> None 

467 """ 

468 set veth pair links down 

469 :raises subprocess.CalledProcessError if operation fails 

470 """ 

471 for idx in [0, 1]: 

472 subprocess.check_call(["ip", "link", "set", self.ifaces[idx], "down"]) # noqa: E501 

473 

474 def __enter__(self): 

475 # type: () -> VEthPair 

476 self.setup() 

477 self.up() 

478 conf.ifaces.reload() 

479 return self 

480 

481 def __exit__(self, exc_type, exc_val, exc_tb): 

482 # type: (Any, Any, Any) -> None 

483 self.destroy() 

484 conf.ifaces.reload()