• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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):
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()
480