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