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 compile_filter
25from scapy.config import conf
26from scapy.data import MTU, ETH_P_ALL, SOL_PACKET, SO_ATTACH_FILTER, \
27 SO_TIMESTAMPNS
28from scapy.error import (
29 ScapyInvalidPlatformException,
30 Scapy_Exception,
31 log_runtime,
32 warning,
33)
34from scapy.interfaces import (
35 InterfaceProvider,
36 NetworkInterface,
37 _GlobInterfaceType,
38 network_name,
39 resolve_iface,
40)
41from scapy.libs.structures import sock_fprog
42from scapy.packet import Packet, Padding
43from scapy.supersocket import SuperSocket
44
45# re-export
46from scapy.arch.common import get_if_raw_addr, read_nameservers # noqa: F401
47from scapy.arch.linux.rtnetlink import ( # noqa: F401
48 read_routes,
49 read_routes6,
50 in6_getifaddr,
51 _get_if_list,
52)
53
54# Typing imports
55from typing import (
56 Any,
57 Dict,
58 List,
59 NoReturn,
60 Optional,
61 Tuple,
62 Type,
63 Union,
64)
65
66# From sockios.h
67SIOCGIFHWADDR = 0x8927 # Get hardware address
68SIOCGIFADDR = 0x8915 # get PA address
69SIOCGIFNETMASK = 0x891b # get network PA mask
70SIOCGIFNAME = 0x8910 # get iface name
71SIOCSIFLINK = 0x8911 # set iface channel
72SIOCGIFCONF = 0x8912 # get iface list
73SIOCGIFFLAGS = 0x8913 # get flags
74SIOCSIFFLAGS = 0x8914 # set flags
75SIOCGIFINDEX = 0x8933 # name -> if_index mapping
76SIOCGIFCOUNT = 0x8938 # get number of devices
77SIOCGSTAMP = 0x8906 # get packet timestamp (as a timeval)
78
79# From if.h
80IFF_UP = 0x1 # Interface is up.
81IFF_BROADCAST = 0x2 # Broadcast address valid.
82IFF_DEBUG = 0x4 # Turn on debugging.
83IFF_LOOPBACK = 0x8 # Is a loopback net.
84IFF_POINTOPOINT = 0x10 # Interface is point-to-point link.
85IFF_NOTRAILERS = 0x20 # Avoid use of trailers.
86IFF_RUNNING = 0x40 # Resources allocated.
87IFF_NOARP = 0x80 # No address resolution protocol.
88IFF_PROMISC = 0x100 # Receive all packets.
89
90# From netpacket/packet.h
91PACKET_ADD_MEMBERSHIP = 1
92PACKET_DROP_MEMBERSHIP = 2
93PACKET_RECV_OUTPUT = 3
94PACKET_RX_RING = 5
95PACKET_STATISTICS = 6
96PACKET_MR_MULTICAST = 0
97PACKET_MR_PROMISC = 1
98PACKET_MR_ALLMULTI = 2
99
100# From net/route.h
101RTF_UP = 0x0001 # Route usable
102RTF_REJECT = 0x0200
103
104# From if_packet.h
105PACKET_HOST = 0 # To us
106PACKET_BROADCAST = 1 # To all
107PACKET_MULTICAST = 2 # To group
108PACKET_OTHERHOST = 3 # To someone else
109PACKET_OUTGOING = 4 # Outgoing of any type
110PACKET_LOOPBACK = 5 # MC/BRD frame looped back
111PACKET_USER = 6 # To user space
112PACKET_KERNEL = 7 # To kernel space
113PACKET_AUXDATA = 8
114PACKET_FASTROUTE = 6 # Fastrouted frame
115# Unused, PACKET_FASTROUTE and PACKET_LOOPBACK are invisible to user space
116
117
118# Utils
119
120def attach_filter(sock, bpf_filter, iface):
121 # type: (socket.socket, str, _GlobInterfaceType) -> None
122 """
123 Compile bpf filter and attach it to a socket
124
125 :param sock: the python socket
126 :param bpf_filter: the bpf string filter to compile
127 :param iface: the interface used to compile
128 """
129 bp = compile_filter(bpf_filter, iface)
130 if conf.use_pypy and sys.pypy_version_info <= (7, 3, 2): # type: ignore
131 # PyPy < 7.3.2 has a broken behavior
132 # https://foss.heptapod.net/pypy/pypy/-/issues/3298
133 bp = struct.pack( # type: ignore
134 'HL',
135 bp.bf_len, ctypes.addressof(bp.bf_insns.contents)
136 )
137 else:
138 bp = sock_fprog(bp.bf_len, bp.bf_insns) # type: ignore
139 sock.setsockopt(socket.SOL_SOCKET, SO_ATTACH_FILTER, bp)
140
141
142def set_promisc(s, iff, val=1):
143 # type: (socket.socket, _GlobInterfaceType, int) -> None
144 _iff = resolve_iface(iff)
145 mreq = struct.pack("IHH8s", _iff.index, PACKET_MR_PROMISC, 0, b"")
146 if val:
147 cmd = PACKET_ADD_MEMBERSHIP
148 else:
149 cmd = PACKET_DROP_MEMBERSHIP
150 s.setsockopt(SOL_PACKET, cmd, mreq)
151
152
153# Interface provider
154
155
156class LinuxInterfaceProvider(InterfaceProvider):
157 name = "sys"
158
159 def _is_valid(self, dev):
160 # type: (NetworkInterface) -> bool
161 return bool(dev.flags & IFF_UP)
162
163 def load(self):
164 # type: () -> Dict[str, NetworkInterface]
165 data = {}
166 for iface in _get_if_list().values():
167 if_data = iface.copy()
168 if_data.update({
169 "network_name": iface["name"],
170 "description": iface["name"],
171 "ips": [x["address"] for x in iface["ips"]]
172 })
173 data[iface["name"]] = NetworkInterface(self, if_data)
174 return data
175
176
177conf.ifaces.register_provider(LinuxInterfaceProvider)
178
179if os.uname()[4] in ['x86_64', 'aarch64']:
180 def get_last_packet_timestamp(sock):
181 # type: (socket.socket) -> float
182 ts = ioctl(sock, SIOCGSTAMP, "1234567890123456") # type: ignore
183 s, us = struct.unpack("QQ", ts) # type: Tuple[int, int]
184 return s + us / 1000000.0
185else:
186 def get_last_packet_timestamp(sock):
187 # type: (socket.socket) -> float
188 ts = ioctl(sock, SIOCGSTAMP, "12345678") # type: ignore
189 s, us = struct.unpack("II", ts) # type: Tuple[int, int]
190 return s + us / 1000000.0
191
192
193def _flush_fd(fd):
194 # type: (int) -> None
195 while True:
196 r, w, e = select([fd], [], [], 0)
197 if r:
198 os.read(fd, MTU)
199 else:
200 break
201
202
203class L2Socket(SuperSocket):
204 desc = "read/write packets at layer 2 using Linux PF_PACKET sockets"
205
206 def __init__(self,
207 iface=None, # type: Optional[Union[str, NetworkInterface]]
208 type=ETH_P_ALL, # type: int
209 promisc=None, # type: Optional[Any]
210 filter=None, # type: Optional[Any]
211 nofilter=0, # type: int
212 monitor=None, # type: Optional[Any]
213 ):
214 # type: (...) -> None
215 self.iface = network_name(iface or conf.iface)
216 self.type = type
217 self.promisc = conf.sniff_promisc if promisc is None else promisc
218 self.ins = socket.socket(
219 socket.AF_PACKET, socket.SOCK_RAW, socket.htons(type))
220 self.ins.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 0)
221 if not nofilter:
222 if conf.except_filter:
223 if filter:
224 filter = "(%s) and not (%s)" % (filter, conf.except_filter)
225 else:
226 filter = "not (%s)" % conf.except_filter
227 if filter is not None:
228 try:
229 attach_filter(self.ins, filter, self.iface)
230 except (ImportError, Scapy_Exception) as ex:
231 raise Scapy_Exception("Cannot set filter: %s" % ex)
232 if self.promisc:
233 set_promisc(self.ins, self.iface)
234 self.ins.bind((self.iface, type))
235 _flush_fd(self.ins.fileno())
236 self.ins.setsockopt(
237 socket.SOL_SOCKET,
238 socket.SO_RCVBUF,
239 conf.bufsize
240 )
241 # Receive Auxiliary Data (VLAN tags)
242 try:
243 self.ins.setsockopt(SOL_PACKET, PACKET_AUXDATA, 1)
244 self.ins.setsockopt(socket.SOL_SOCKET, SO_TIMESTAMPNS, 1)
245 self.auxdata_available = True
246 except OSError:
247 # Note: Auxiliary Data is only supported since
248 # Linux 2.6.21
249 msg = "Your Linux Kernel does not support Auxiliary Data!"
250 log_runtime.info(msg)
251 if not isinstance(self, L2ListenSocket):
252 self.outs = self.ins # type: socket.socket
253 self.outs.setsockopt(
254 socket.SOL_SOCKET,
255 socket.SO_SNDBUF,
256 conf.bufsize
257 )
258 else:
259 self.outs = None # type: ignore
260 sa_ll = self.ins.getsockname()
261 if sa_ll[3] in conf.l2types:
262 self.LL = conf.l2types.num2layer[sa_ll[3]]
263 self.lvl = 2
264 elif sa_ll[1] in conf.l3types:
265 self.LL = conf.l3types.num2layer[sa_ll[1]]
266 self.lvl = 3
267 else:
268 self.LL = conf.default_l2
269 self.lvl = 2
270 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
271
272 def close(self):
273 # type: () -> None
274 if self.closed:
275 return
276 try:
277 if self.promisc and getattr(self, "ins", None):
278 set_promisc(self.ins, self.iface, 0)
279 except (AttributeError, OSError, ValueError):
280 pass
281 SuperSocket.close(self)
282
283 def recv_raw(self, x=MTU):
284 # type: (int) -> Tuple[Optional[Type[Packet]], Optional[bytes], Optional[float]] # noqa: E501
285 """Receives a packet, then returns a tuple containing (cls, pkt_data, time)""" # noqa: E501
286 pkt, sa_ll, ts = self._recv_raw(self.ins, x)
287 if self.outs and sa_ll[2] == socket.PACKET_OUTGOING:
288 return None, None, None
289 if ts is None:
290 ts = get_last_packet_timestamp(self.ins)
291 return self.LL, pkt, ts
292
293 def send(self, x):
294 # type: (Packet) -> int
295 try:
296 return SuperSocket.send(self, x)
297 except socket.error as msg:
298 if msg.errno == 22 and len(x) < conf.min_pkt_size:
299 padding = b"\x00" * (conf.min_pkt_size - len(x))
300 if isinstance(x, Packet):
301 return SuperSocket.send(self, x / Padding(load=padding))
302 else:
303 return SuperSocket.send(self, raw(x) + padding)
304 raise
305
306
307class L2ListenSocket(L2Socket):
308 desc = "read packets at layer 2 using Linux PF_PACKET sockets. Also receives the packets going OUT" # noqa: E501
309
310 def send(self, x):
311 # type: (Packet) -> NoReturn
312 raise Scapy_Exception("Can't send anything with L2ListenSocket")
313
314
315class L3PacketSocket(L2Socket):
316 desc = "read/write packets at layer 3 using Linux PF_PACKET sockets"
317
318 def __init__(self,
319 iface=None, # type: Optional[Union[str, NetworkInterface]]
320 type=ETH_P_ALL, # type: int
321 promisc=None, # type: Optional[Any]
322 filter=None, # type: Optional[Any]
323 nofilter=0, # type: int
324 monitor=None, # type: Optional[Any]
325 ):
326 self.send_socks = {}
327 super(L3PacketSocket, self).__init__(
328 iface=iface,
329 type=type,
330 promisc=promisc,
331 filter=filter,
332 nofilter=nofilter,
333 monitor=monitor,
334 )
335 self.filter = filter
336 self.send_socks = {network_name(self.iface): self}
337
338 def recv(self, x=MTU, **kwargs):
339 # type: (int, **Any) -> Optional[Packet]
340 pkt = SuperSocket.recv(self, x, **kwargs)
341 if pkt and self.lvl == 2:
342 pkt.payload.time = pkt.time
343 return pkt.payload
344 return pkt
345
346 def send(self, x):
347 # type: (Packet) -> int
348 # Select the file descriptor to send the packet on.
349 iff = x.route()[0]
350 if iff is None:
351 iff = network_name(conf.iface)
352 type_x = type(x)
353 if iff not in self.send_socks:
354 self.send_socks[iff] = L3PacketSocket(
355 iface=iff,
356 type=conf.l3types.layer2num.get(type_x, self.type),
357 filter=self.filter,
358 promisc=self.promisc,
359 )
360 sock = self.send_socks[iff]
361 fd = sock.outs
362 if sock.lvl == 3:
363 if not issubclass(sock.LL, type_x):
364 warning("Incompatible L3 types detected using %s instead of %s !",
365 type_x, sock.LL)
366 sock.LL = type_x
367 if sock.lvl == 2:
368 sx = bytes(sock.LL() / x)
369 else:
370 sx = bytes(x)
371 # Now send.
372 try:
373 x.sent_time = time.time()
374 except AttributeError:
375 pass
376 try:
377 return fd.send(sx)
378 except socket.error as msg:
379 if msg.errno == 22 and len(sx) < conf.min_pkt_size:
380 return fd.send(
381 sx + b"\x00" * (conf.min_pkt_size - len(sx))
382 )
383 elif conf.auto_fragment and msg.errno == 90:
384 i = 0
385 for p in x.fragment():
386 i += fd.send(bytes(self.LL() / p))
387 return i
388 else:
389 raise
390
391 @staticmethod
392 def select(sockets, remain=None):
393 # type: (List[SuperSocket], Optional[float]) -> List[SuperSocket]
394 socks = [] # type: List[SuperSocket]
395 for sock in sockets:
396 if isinstance(sock, L3PacketSocket):
397 socks += sock.send_socks.values()
398 else:
399 socks.append(sock)
400 return L2Socket.select(socks, remain=remain)
401
402 def close(self):
403 # type: () -> None
404 if self.closed:
405 return
406 super(L3PacketSocket, self).close()
407 for fd in self.send_socks.values():
408 if fd is not self:
409 fd.close()
410
411
412class VEthPair(object):
413 """
414 encapsulates a virtual Ethernet interface pair
415 """
416
417 def __init__(self, iface_name, peer_name):
418 # type: (str, str) -> None
419 if not LINUX:
420 # ToDo: do we need a kernel version check here?
421 raise ScapyInvalidPlatformException(
422 'Virtual Ethernet interface pair only available on Linux'
423 )
424
425 self.ifaces = [iface_name, peer_name]
426
427 def iface(self):
428 # type: () -> str
429 return self.ifaces[0]
430
431 def peer(self):
432 # type: () -> str
433 return self.ifaces[1]
434
435 def setup(self):
436 # type: () -> None
437 """
438 create veth pair links
439 :raises subprocess.CalledProcessError if operation fails
440 """
441 subprocess.check_call(['ip', 'link', 'add', self.ifaces[0], 'type', 'veth', 'peer', 'name', self.ifaces[1]]) # noqa: E501
442
443 def destroy(self):
444 # type: () -> None
445 """
446 remove veth pair links
447 :raises subprocess.CalledProcessError if operation fails
448 """
449 subprocess.check_call(['ip', 'link', 'del', self.ifaces[0]])
450
451 def up(self):
452 # type: () -> None
453 """
454 set veth pair links up
455 :raises subprocess.CalledProcessError if operation fails
456 """
457 for idx in [0, 1]:
458 subprocess.check_call(["ip", "link", "set", self.ifaces[idx], "up"]) # noqa: E501
459
460 def down(self):
461 # type: () -> None
462 """
463 set veth pair links down
464 :raises subprocess.CalledProcessError if operation fails
465 """
466 for idx in [0, 1]:
467 subprocess.check_call(["ip", "link", "set", self.ifaces[idx], "down"]) # noqa: E501
468
469 def __enter__(self):
470 # type: () -> VEthPair
471 self.setup()
472 self.up()
473 conf.ifaces.reload()
474 return self
475
476 def __exit__(self, exc_type, exc_val, exc_tb):
477 # type: (Any, Any, Any) -> None
478 self.destroy()
479 conf.ifaces.reload()