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