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

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

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

15import os 

16import socket 

17import struct 

18import subprocess 

19import sys 

20import time 

21 

22from scapy.compat import raw 

23from scapy.consts import LINUX 

24from scapy.arch.common import ( 

25 compile_filter, 

26) 

27from scapy.arch.unix import get_if 

28from scapy.config import conf 

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

30 SO_TIMESTAMPNS 

31from scapy.error import ( 

32 ScapyInvalidPlatformException, 

33 Scapy_Exception, 

34 log_runtime, 

35 warning, 

36) 

37from scapy.interfaces import ( 

38 InterfaceProvider, 

39 NetworkInterface, 

40 network_name, 

41 _GlobInterfaceType, 

42) 

43from scapy.libs.structures import sock_fprog 

44from scapy.packet import Packet, Padding 

45from scapy.supersocket import SuperSocket 

46 

47# re-export 

48from scapy.arch.common import get_if_raw_addr # noqa: F401 

49from scapy.arch.unix import read_nameservers, get_if_raw_hwaddr # noqa: F401 

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

51 read_routes, 

52 read_routes6, 

53 in6_getifaddr, 

54 _get_if_list, 

55) 

56 

57# Typing imports 

58from typing import ( 

59 Any, 

60 Callable, 

61 Dict, 

62 NoReturn, 

63 Optional, 

64 Tuple, 

65 Type, 

66 Union, 

67) 

68 

69# From sockios.h 

70SIOCGIFHWADDR = 0x8927 # Get hardware address 

71SIOCGIFADDR = 0x8915 # get PA address 

72SIOCGIFNETMASK = 0x891b # get network PA mask 

73SIOCGIFNAME = 0x8910 # get iface name 

74SIOCSIFLINK = 0x8911 # set iface channel 

75SIOCGIFCONF = 0x8912 # get iface list 

76SIOCGIFFLAGS = 0x8913 # get flags 

77SIOCSIFFLAGS = 0x8914 # set flags 

78SIOCGIFINDEX = 0x8933 # name -> if_index mapping 

79SIOCGIFCOUNT = 0x8938 # get number of devices 

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

81 

82# From if.h 

83IFF_UP = 0x1 # Interface is up. 

84IFF_BROADCAST = 0x2 # Broadcast address valid. 

85IFF_DEBUG = 0x4 # Turn on debugging. 

86IFF_LOOPBACK = 0x8 # Is a loopback net. 

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

88IFF_NOTRAILERS = 0x20 # Avoid use of trailers. 

89IFF_RUNNING = 0x40 # Resources allocated. 

90IFF_NOARP = 0x80 # No address resolution protocol. 

91IFF_PROMISC = 0x100 # Receive all packets. 

92 

93# From netpacket/packet.h 

94PACKET_ADD_MEMBERSHIP = 1 

95PACKET_DROP_MEMBERSHIP = 2 

96PACKET_RECV_OUTPUT = 3 

97PACKET_RX_RING = 5 

98PACKET_STATISTICS = 6 

99PACKET_MR_MULTICAST = 0 

100PACKET_MR_PROMISC = 1 

101PACKET_MR_ALLMULTI = 2 

102 

103# From net/route.h 

104RTF_UP = 0x0001 # Route usable 

105RTF_REJECT = 0x0200 

106 

107# From if_packet.h 

108PACKET_HOST = 0 # To us 

109PACKET_BROADCAST = 1 # To all 

110PACKET_MULTICAST = 2 # To group 

111PACKET_OTHERHOST = 3 # To someone else 

112PACKET_OUTGOING = 4 # Outgoing of any type 

113PACKET_LOOPBACK = 5 # MC/BRD frame looped back 

114PACKET_USER = 6 # To user space 

115PACKET_KERNEL = 7 # To kernel space 

116PACKET_AUXDATA = 8 

117PACKET_FASTROUTE = 6 # Fastrouted frame 

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

119 

120 

121# Utils 

122 

123def attach_filter(sock, bpf_filter, iface): 

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

125 """ 

126 Compile bpf filter and attach it to a socket 

127 

128 :param sock: the python socket 

129 :param bpf_filter: the bpf string filter to compile 

130 :param iface: the interface used to compile 

131 """ 

132 bp = compile_filter(bpf_filter, iface) 

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

134 # PyPy < 7.3.2 has a broken behavior 

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

136 bp = struct.pack( # type: ignore 

137 'HL', 

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

139 ) 

140 else: 

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

142 sock.setsockopt(socket.SOL_SOCKET, SO_ATTACH_FILTER, bp) 

143 

144 

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

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

147 mreq = struct.pack("IHH8s", get_if_index(iff), PACKET_MR_PROMISC, 0, b"") 

148 if val: 

149 cmd = PACKET_ADD_MEMBERSHIP 

150 else: 

151 cmd = PACKET_DROP_MEMBERSHIP 

152 s.setsockopt(SOL_PACKET, cmd, mreq) 

153 

154 

155def get_if_index(iff): 

156 # type: (_GlobInterfaceType) -> int 

157 return int(struct.unpack("I", get_if(iff, SIOCGIFINDEX)[16:20])[0]) 

158 

159 

160# Interface provider 

161 

162 

163class LinuxInterfaceProvider(InterfaceProvider): 

164 name = "sys" 

165 

166 def _is_valid(self, dev): 

167 # type: (NetworkInterface) -> bool 

168 return bool(dev.flags & IFF_UP) 

169 

170 def load(self): 

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

172 data = {} 

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

174 if_data = iface.copy() 

175 if_data.update({ 

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

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

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

179 }) 

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

181 return data 

182 

183 

184conf.ifaces.register_provider(LinuxInterfaceProvider) 

185 

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

187 def get_last_packet_timestamp(sock): 

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

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

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

191 return s + us / 1000000.0 

192else: 

193 def get_last_packet_timestamp(sock): 

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

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

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

197 return s + us / 1000000.0 

198 

199 

200def _flush_fd(fd): 

201 # type: (int) -> None 

202 while True: 

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

204 if r: 

205 os.read(fd, MTU) 

206 else: 

207 break 

208 

209 

210class L2Socket(SuperSocket): 

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

212 

213 def __init__(self, 

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

215 type=ETH_P_ALL, # type: int 

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

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

218 nofilter=0, # type: int 

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

220 ): 

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

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

223 self.type = type 

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

225 self.ins = socket.socket( 

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

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

228 if not nofilter: 

229 if conf.except_filter: 

230 if filter: 

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

232 else: 

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

234 if filter is not None: 

235 try: 

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

237 except (ImportError, Scapy_Exception) as ex: 

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

239 if self.promisc: 

240 set_promisc(self.ins, self.iface) 

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

242 _flush_fd(self.ins.fileno()) 

243 self.ins.setsockopt( 

244 socket.SOL_SOCKET, 

245 socket.SO_RCVBUF, 

246 conf.bufsize 

247 ) 

248 # Receive Auxiliary Data (VLAN tags) 

249 try: 

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

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

252 self.auxdata_available = True 

253 except OSError: 

254 # Note: Auxiliary Data is only supported since 

255 # Linux 2.6.21 

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

257 log_runtime.info(msg) 

258 if not isinstance(self, L2ListenSocket): 

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

260 self.outs.setsockopt( 

261 socket.SOL_SOCKET, 

262 socket.SO_SNDBUF, 

263 conf.bufsize 

264 ) 

265 else: 

266 self.outs = None # type: ignore 

267 sa_ll = self.ins.getsockname() 

268 if sa_ll[3] in conf.l2types: 

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

270 self.lvl = 2 

271 elif sa_ll[1] in conf.l3types: 

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

273 self.lvl = 3 

274 else: 

275 self.LL = conf.default_l2 

276 self.lvl = 2 

277 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 

278 

279 def close(self): 

280 # type: () -> None 

281 if self.closed: 

282 return 

283 try: 

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

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

286 except (AttributeError, OSError): 

287 pass 

288 SuperSocket.close(self) 

289 

290 def recv_raw(self, x=MTU): 

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

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

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

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

295 return None, None, None 

296 if ts is None: 

297 ts = get_last_packet_timestamp(self.ins) 

298 return self.LL, pkt, ts 

299 

300 def send(self, x): 

301 # type: (Packet) -> int 

302 try: 

303 return SuperSocket.send(self, x) 

304 except socket.error as msg: 

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

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

307 if isinstance(x, Packet): 

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

309 else: 

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

311 raise 

312 

313 

314class L2ListenSocket(L2Socket): 

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

316 

317 def send(self, x): 

318 # type: (Packet) -> NoReturn 

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

320 

321 

322class L3PacketSocket(L2Socket): 

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

324 

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

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

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

328 if pkt and self.lvl == 2: 

329 pkt.payload.time = pkt.time 

330 return pkt.payload 

331 return pkt 

332 

333 def send(self, x): 

334 # type: (Packet) -> int 

335 iff = x.route()[0] 

336 if iff is None: 

337 iff = network_name(conf.iface) 

338 sdto = (iff, self.type) 

339 self.outs.bind(sdto) 

340 sn = self.outs.getsockname() 

341 ll = lambda x: x # type: Callable[[Packet], Packet] 

342 type_x = type(x) 

343 if type_x in conf.l3types: 

344 sdto = (iff, conf.l3types.layer2num[type_x]) 

345 if sn[3] in conf.l2types: 

346 ll = lambda x: conf.l2types.num2layer[sn[3]]() / x 

347 if self.lvl == 3 and type_x != self.LL: 

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

349 type_x, self.LL) 

350 self.LL = type_x 

351 sx = raw(ll(x)) 

352 x.sent_time = time.time() 

353 try: 

354 return self.outs.sendto(sx, sdto) 

355 except socket.error as msg: 

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

357 return self.outs.send( 

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

359 ) 

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

361 i = 0 

362 for p in x.fragment(): 

363 i += self.outs.sendto(raw(ll(p)), sdto) 

364 return i 

365 else: 

366 raise 

367 

368 

369class VEthPair(object): 

370 """ 

371 encapsulates a virtual Ethernet interface pair 

372 """ 

373 

374 def __init__(self, iface_name, peer_name): 

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

376 if not LINUX: 

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

378 raise ScapyInvalidPlatformException( 

379 'Virtual Ethernet interface pair only available on Linux' 

380 ) 

381 

382 self.ifaces = [iface_name, peer_name] 

383 

384 def iface(self): 

385 # type: () -> str 

386 return self.ifaces[0] 

387 

388 def peer(self): 

389 # type: () -> str 

390 return self.ifaces[1] 

391 

392 def setup(self): 

393 # type: () -> None 

394 """ 

395 create veth pair links 

396 :raises subprocess.CalledProcessError if operation fails 

397 """ 

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

399 

400 def destroy(self): 

401 # type: () -> None 

402 """ 

403 remove veth pair links 

404 :raises subprocess.CalledProcessError if operation fails 

405 """ 

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

407 

408 def up(self): 

409 # type: () -> None 

410 """ 

411 set veth pair links up 

412 :raises subprocess.CalledProcessError if operation fails 

413 """ 

414 for idx in [0, 1]: 

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

416 

417 def down(self): 

418 # type: () -> None 

419 """ 

420 set veth pair links down 

421 :raises subprocess.CalledProcessError if operation fails 

422 """ 

423 for idx in [0, 1]: 

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

425 

426 def __enter__(self): 

427 # type: () -> VEthPair 

428 self.setup() 

429 self.up() 

430 conf.ifaces.reload() 

431 return self 

432 

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

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

435 self.destroy() 

436 conf.ifaces.reload()