esp32_bluetooth_classic_sni.../libs/scapy/arch/linux.py
Matheus Eduardo Garbelini 86890704fd initial commit
todo: add documentation & wireshark dissector
2021-08-31 19:51:03 +08:00

642 lines
21 KiB
Python
Executable file

# This file is part of Scapy
# See http://www.secdev.org/projects/scapy for more information
# Copyright (C) Philippe Biondi <phil@secdev.org>
# This program is published under a GPLv2 license
"""
Linux specific functions.
"""
from __future__ import absolute_import
import array
from fcntl import ioctl
import os
from select import select
import socket
import struct
import time
import re
import subprocess
from scapy.compat import raw, plain_str
from scapy.consts import LINUX
import scapy.utils
import scapy.utils6
from scapy.packet import Packet, Padding
from scapy.config import conf
from scapy.data import MTU, ETH_P_ALL, SOL_PACKET, SO_ATTACH_FILTER, \
SO_TIMESTAMPNS
from scapy.supersocket import SuperSocket
from scapy.error import warning, Scapy_Exception, \
ScapyInvalidPlatformException, log_runtime
from scapy.arch.common import get_if, compile_filter
import scapy.modules.six as six
from scapy.modules.six.moves import range
from scapy.arch.common import get_if_raw_hwaddr # noqa: F401
# From bits/ioctls.h
SIOCGIFHWADDR = 0x8927 # Get hardware address
SIOCGIFADDR = 0x8915 # get PA address
SIOCGIFNETMASK = 0x891b # get network PA mask
SIOCGIFNAME = 0x8910 # get iface name
SIOCSIFLINK = 0x8911 # set iface channel
SIOCGIFCONF = 0x8912 # get iface list
SIOCGIFFLAGS = 0x8913 # get flags
SIOCSIFFLAGS = 0x8914 # set flags
SIOCGIFINDEX = 0x8933 # name -> if_index mapping
SIOCGIFCOUNT = 0x8938 # get number of devices
SIOCGSTAMP = 0x8906 # get packet timestamp (as a timeval)
# From if.h
IFF_UP = 0x1 # Interface is up.
IFF_BROADCAST = 0x2 # Broadcast address valid.
IFF_DEBUG = 0x4 # Turn on debugging.
IFF_LOOPBACK = 0x8 # Is a loopback net.
IFF_POINTOPOINT = 0x10 # Interface is point-to-point link.
IFF_NOTRAILERS = 0x20 # Avoid use of trailers.
IFF_RUNNING = 0x40 # Resources allocated.
IFF_NOARP = 0x80 # No address resolution protocol.
IFF_PROMISC = 0x100 # Receive all packets.
# From netpacket/packet.h
PACKET_ADD_MEMBERSHIP = 1
PACKET_DROP_MEMBERSHIP = 2
PACKET_RECV_OUTPUT = 3
PACKET_RX_RING = 5
PACKET_STATISTICS = 6
PACKET_MR_MULTICAST = 0
PACKET_MR_PROMISC = 1
PACKET_MR_ALLMULTI = 2
# From net/route.h
RTF_UP = 0x0001 # Route usable
RTF_REJECT = 0x0200
# From if_packet.h
PACKET_HOST = 0 # To us
PACKET_BROADCAST = 1 # To all
PACKET_MULTICAST = 2 # To group
PACKET_OTHERHOST = 3 # To someone else
PACKET_OUTGOING = 4 # Outgoing of any type
PACKET_LOOPBACK = 5 # MC/BRD frame looped back
PACKET_USER = 6 # To user space
PACKET_KERNEL = 7 # To kernel space
PACKET_AUXDATA = 8
PACKET_FASTROUTE = 6 # Fastrouted frame
# Unused, PACKET_FASTROUTE and PACKET_LOOPBACK are invisible to user space
# Utils
def get_if_raw_addr(iff):
try:
return get_if(iff, SIOCGIFADDR)[20:24]
except IOError:
return b"\0\0\0\0"
def get_if_list():
try:
f = open("/proc/net/dev", "rb")
except IOError:
try:
f.close()
except Exception:
pass
warning("Can't open /proc/net/dev !")
return []
lst = []
f.readline()
f.readline()
for line in f:
line = plain_str(line)
lst.append(line.split(":")[0].strip())
f.close()
return lst
def get_working_if():
"""
Return the name of the first network interfcace that is up.
"""
for i in get_if_list():
if i == conf.loopback_name:
continue
ifflags = struct.unpack("16xH14x", get_if(i, SIOCGIFFLAGS))[0]
if ifflags & IFF_UP:
return i
return conf.loopback_name
def attach_filter(sock, bpf_filter, iface):
"""
Compile bpf filter and attach it to a socket
:param sock: the python socket
:param bpf_filter: the bpf string filter to compile
:param iface: the interface used to compile
"""
bp = compile_filter(bpf_filter, iface)
sock.setsockopt(socket.SOL_SOCKET, SO_ATTACH_FILTER, bp)
def set_promisc(s, iff, val=1):
mreq = struct.pack("IHH8s", get_if_index(iff), PACKET_MR_PROMISC, 0, b"")
if val:
cmd = PACKET_ADD_MEMBERSHIP
else:
cmd = PACKET_DROP_MEMBERSHIP
s.setsockopt(SOL_PACKET, cmd, mreq)
def get_alias_address(iface_name, ip_mask, gw_str, metric):
"""
Get the correct source IP address of an interface alias
"""
# Detect the architecture
if scapy.consts.IS_64BITS:
offset, name_len = 16, 40
else:
offset, name_len = 32, 32
# Retrieve interfaces structures
sck = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
names = array.array('B', b'\0' * 4096)
ifreq = ioctl(sck.fileno(), SIOCGIFCONF,
struct.pack("iL", len(names), names.buffer_info()[0]))
# Extract interfaces names
out = struct.unpack("iL", ifreq)[0]
names = names.tobytes() if six.PY3 else names.tostring()
names = [names[i:i + offset].split(b'\0', 1)[0] for i in range(0, out, name_len)] # noqa: E501
# Look for the IP address
for ifname in names:
# Only look for a matching interface name
if not ifname.decode("utf8").startswith(iface_name):
continue
# Retrieve and convert addresses
ifreq = ioctl(sck, SIOCGIFADDR, struct.pack("16s16x", ifname))
ifaddr = struct.unpack(">I", ifreq[20:24])[0]
ifreq = ioctl(sck, SIOCGIFNETMASK, struct.pack("16s16x", ifname))
msk = struct.unpack(">I", ifreq[20:24])[0]
# Get the full interface name
ifname = plain_str(ifname)
if ':' in ifname:
ifname = ifname[:ifname.index(':')]
else:
continue
# Check if the source address is included in the network
if (ifaddr & msk) == ip_mask:
sck.close()
return (ifaddr & msk, msk, gw_str, ifname,
scapy.utils.ltoa(ifaddr), metric)
sck.close()
return
def read_routes():
try:
f = open("/proc/net/route", "rb")
except IOError:
warning("Can't open /proc/net/route !")
return []
routes = []
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
ifreq = ioctl(s, SIOCGIFADDR, struct.pack("16s16x", conf.loopback_name.encode("utf8"))) # noqa: E501
addrfamily = struct.unpack("h", ifreq[16:18])[0]
if addrfamily == socket.AF_INET:
ifreq2 = ioctl(s, SIOCGIFNETMASK, struct.pack("16s16x", conf.loopback_name.encode("utf8"))) # noqa: E501
msk = socket.ntohl(struct.unpack("I", ifreq2[20:24])[0])
dst = socket.ntohl(struct.unpack("I", ifreq[20:24])[0]) & msk
ifaddr = scapy.utils.inet_ntoa(ifreq[20:24])
routes.append((dst, msk, "0.0.0.0", conf.loopback_name, ifaddr, 1)) # noqa: E501
else:
warning("Interface %s: unknown address family (%i)" % (conf.loopback_name, addrfamily)) # noqa: E501
except IOError as err:
if err.errno == 99:
warning("Interface %s: no address assigned" % conf.loopback_name) # noqa: E501
else:
warning("Interface %s: failed to get address config (%s)" % (conf.loopback_name, str(err))) # noqa: E501
for line in f.readlines()[1:]:
line = plain_str(line)
iff, dst, gw, flags, _, _, metric, msk, _, _, _ = line.split()
flags = int(flags, 16)
if flags & RTF_UP == 0:
continue
if flags & RTF_REJECT:
continue
try:
ifreq = ioctl(s, SIOCGIFADDR, struct.pack("16s16x", iff.encode("utf8"))) # noqa: E501
except IOError: # interface is present in routing tables but does not have any assigned IP # noqa: E501
ifaddr = "0.0.0.0"
ifaddr_int = 0
else:
addrfamily = struct.unpack("h", ifreq[16:18])[0]
if addrfamily == socket.AF_INET:
ifaddr = scapy.utils.inet_ntoa(ifreq[20:24])
ifaddr_int = struct.unpack("!I", ifreq[20:24])[0]
else:
warning("Interface %s: unknown address family (%i)", iff, addrfamily) # noqa: E501
continue
# Attempt to detect an interface alias based on addresses inconsistencies # noqa: E501
dst_int = socket.htonl(int(dst, 16)) & 0xffffffff
msk_int = socket.htonl(int(msk, 16)) & 0xffffffff
gw_str = scapy.utils.inet_ntoa(struct.pack("I", int(gw, 16)))
metric = int(metric)
if ifaddr_int & msk_int != dst_int:
tmp_route = get_alias_address(iff, dst_int, gw_str, metric)
if tmp_route:
routes.append(tmp_route)
else:
routes.append((dst_int, msk_int, gw_str, iff, ifaddr, metric))
else:
routes.append((dst_int, msk_int, gw_str, iff, ifaddr, metric))
f.close()
s.close()
return routes
############
# IPv6 #
############
def in6_getifaddr():
"""
Returns a list of 3-tuples of the form (addr, scope, iface) where
'addr' is the address of scope 'scope' associated to the interface
'iface'.
This is the list of all addresses of all interfaces available on
the system.
"""
ret = []
try:
fdesc = open("/proc/net/if_inet6", "rb")
except IOError:
return ret
for line in fdesc:
# addr, index, plen, scope, flags, ifname
tmp = plain_str(line).split()
addr = scapy.utils6.in6_ptop(
b':'.join(
struct.unpack('4s4s4s4s4s4s4s4s', tmp[0].encode())
).decode()
)
# (addr, scope, iface)
ret.append((addr, int(tmp[3], 16), tmp[5]))
fdesc.close()
return ret
def read_routes6():
try:
f = open("/proc/net/ipv6_route", "rb")
except IOError:
return []
# 1. destination network
# 2. destination prefix length
# 3. source network displayed
# 4. source prefix length
# 5. next hop
# 6. metric
# 7. reference counter (?!?)
# 8. use counter (?!?)
# 9. flags
# 10. device name
routes = []
def proc2r(p):
ret = struct.unpack('4s4s4s4s4s4s4s4s', p)
ret = b':'.join(ret).decode()
return scapy.utils6.in6_ptop(ret)
lifaddr = in6_getifaddr()
for line in f.readlines():
d, dp, _, _, nh, metric, rc, us, fl, dev = line.split()
metric = int(metric, 16)
fl = int(fl, 16)
dev = plain_str(dev)
if fl & RTF_UP == 0:
continue
if fl & RTF_REJECT:
continue
d = proc2r(d)
dp = int(dp, 16)
nh = proc2r(nh)
cset = [] # candidate set (possible source addresses)
if dev == conf.loopback_name:
if d == '::':
continue
cset = ['::1']
else:
devaddrs = (x for x in lifaddr if x[2] == dev)
cset = scapy.utils6.construct_source_candidate_set(d, dp, devaddrs)
if len(cset) != 0:
routes.append((d, dp, nh, dev, cset, metric))
f.close()
return routes
def get_if_index(iff):
return int(struct.unpack("I", get_if(iff, SIOCGIFINDEX)[16:20])[0])
if os.uname()[4] in ['x86_64', 'aarch64']:
def get_last_packet_timestamp(sock):
ts = ioctl(sock, SIOCGSTAMP, "1234567890123456")
s, us = struct.unpack("QQ", ts)
return s + us / 1000000.0
else:
def get_last_packet_timestamp(sock):
ts = ioctl(sock, SIOCGSTAMP, "12345678")
s, us = struct.unpack("II", ts)
return s + us / 1000000.0
def _flush_fd(fd):
if hasattr(fd, 'fileno'):
fd = fd.fileno()
while True:
r, w, e = select([fd], [], [], 0)
if r:
os.read(fd, MTU)
else:
break
def get_iface_mode(iface):
"""Return the interface mode.
params:
- iface: the iwconfig interface
"""
p = subprocess.Popen(["iwconfig", iface], stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
output, err = p.communicate()
match = re.search(br"mode:([a-zA-Z]*)", output.lower())
if match:
return plain_str(match.group(1))
return "unknown"
def set_iface_monitor(iface, monitor):
"""Sets the monitor mode (or remove it) from an interface.
params:
- iface: the iwconfig interface
- monitor: True if the interface should be set in monitor mode,
False if it should be in managed mode
"""
mode = get_iface_mode(iface)
if mode == "unknown":
warning("Could not parse iwconfig !")
current_monitor = mode == "monitor"
if monitor == current_monitor:
# Already correct
return True
s_mode = "monitor" if monitor else "managed"
def _check_call(commands):
p = subprocess.Popen(commands,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)
stdout, stderr = p.communicate()
if p.returncode != 0:
warning("%s failed !" % " ".join(commands))
return False
return True
if not _check_call(["ifconfig", iface, "down"]):
return False
if not _check_call(["iwconfig", iface, "mode", s_mode]):
return False
if not _check_call(["ifconfig", iface, "up"]):
return False
return True
class L2Socket(SuperSocket):
desc = "read/write packets at layer 2 using Linux PF_PACKET sockets"
def __init__(self, iface=None, type=ETH_P_ALL, promisc=None, filter=None,
nofilter=0, monitor=None):
self.iface = conf.iface if iface is None else iface
self.type = type
self.promisc = conf.sniff_promisc if promisc is None else promisc
if monitor is not None:
warning(
"The monitor argument is ineffective on native linux sockets."
" Use set_iface_monitor instead."
)
self.ins = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(type)) # noqa: E501
if not nofilter:
if conf.except_filter:
if filter:
filter = "(%s) and not (%s)" % (filter, conf.except_filter)
else:
filter = "not (%s)" % conf.except_filter
if filter is not None:
try:
attach_filter(self.ins, filter, iface)
except ImportError as ex:
warning("Cannot set filter: %s" % ex)
if self.promisc:
set_promisc(self.ins, self.iface)
self.ins.bind((self.iface, type))
_flush_fd(self.ins)
self.ins.setsockopt(
socket.SOL_SOCKET,
socket.SO_RCVBUF,
conf.bufsize
)
if not six.PY2:
# Receive Auxiliary Data (VLAN tags)
try:
self.ins.setsockopt(SOL_PACKET, PACKET_AUXDATA, 1)
self.ins.setsockopt(
socket.SOL_SOCKET,
SO_TIMESTAMPNS,
1
)
self.auxdata_available = True
except OSError:
# Note: Auxiliary Data is only supported since
# Linux 2.6.21
msg = "Your Linux Kernel does not support Auxiliary Data!"
log_runtime.info(msg)
if isinstance(self, L2ListenSocket):
self.outs = None
else:
self.outs = self.ins
self.outs.setsockopt(
socket.SOL_SOCKET,
socket.SO_SNDBUF,
conf.bufsize
)
sa_ll = self.ins.getsockname()
if sa_ll[3] in conf.l2types:
self.LL = conf.l2types[sa_ll[3]]
self.lvl = 2
elif sa_ll[1] in conf.l3types:
self.LL = conf.l3types[sa_ll[1]]
self.lvl = 3
else:
self.LL = conf.default_l2
self.lvl = 2
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
def close(self):
if self.closed:
return
try:
if self.promisc and self.ins:
set_promisc(self.ins, self.iface, 0)
except (AttributeError, OSError):
pass
SuperSocket.close(self)
def recv_raw(self, x=MTU):
"""Receives a packet, then returns a tuple containing (cls, pkt_data, time)""" # noqa: E501
pkt, sa_ll, ts = self._recv_raw(self.ins, x)
if self.outs and sa_ll[2] == socket.PACKET_OUTGOING:
return None, None, None
if ts is None:
ts = get_last_packet_timestamp(self.ins)
return self.LL, pkt, ts
def send(self, x):
try:
return SuperSocket.send(self, x)
except socket.error as msg:
if msg.errno == 22 and len(x) < conf.min_pkt_size:
padding = b"\x00" * (conf.min_pkt_size - len(x))
if isinstance(x, Packet):
return SuperSocket.send(self, x / Padding(load=padding))
else:
return SuperSocket.send(self, raw(x) + padding)
raise
class L2ListenSocket(L2Socket):
desc = "read packets at layer 2 using Linux PF_PACKET sockets. Also receives the packets going OUT" # noqa: E501
def send(self, x):
raise Scapy_Exception("Can't send anything with L2ListenSocket")
class L3PacketSocket(L2Socket):
desc = "read/write packets at layer 3 using Linux PF_PACKET sockets"
def recv(self, x=MTU):
pkt = SuperSocket.recv(self, x)
if pkt and self.lvl == 2:
pkt.payload.time = pkt.time
return pkt.payload
return pkt
def send(self, x):
iff = x.route()[0]
if iff is None:
iff = conf.iface
sdto = (iff, self.type)
self.outs.bind(sdto)
sn = self.outs.getsockname()
ll = lambda x: x
type_x = type(x)
if type_x in conf.l3types:
sdto = (iff, conf.l3types[type_x])
if sn[3] in conf.l2types:
ll = lambda x: conf.l2types[sn[3]]() / x
if self.lvl == 3 and type_x != self.LL:
warning("Incompatible L3 types detected using %s instead of %s !",
type_x, self.LL)
self.LL = type_x
sx = raw(ll(x))
x.sent_time = time.time()
try:
self.outs.sendto(sx, sdto)
except socket.error as msg:
if msg.errno == 22 and len(sx) < conf.min_pkt_size:
self.outs.send(sx + b"\x00" * (conf.min_pkt_size - len(sx)))
elif conf.auto_fragment and msg.errno == 90:
for p in x.fragment():
self.outs.sendto(raw(ll(p)), sdto)
else:
raise
class VEthPair(object):
"""
encapsulates a virtual Ethernet interface pair
"""
def __init__(self, iface_name, peer_name):
if not LINUX:
# ToDo: do we need a kernel version check here?
raise ScapyInvalidPlatformException(
'Virtual Ethernet interface pair only available on Linux'
)
self.ifaces = [iface_name, peer_name]
def iface(self):
return self.ifaces[0]
def peer(self):
return self.ifaces[1]
def setup(self):
"""
create veth pair links
:raises subprocess.CalledProcessError if operation fails
"""
subprocess.check_call(['ip', 'link', 'add', self.ifaces[0], 'type', 'veth', 'peer', 'name', self.ifaces[1]]) # noqa: E501
def destroy(self):
"""
remove veth pair links
:raises subprocess.CalledProcessError if operation fails
"""
subprocess.check_call(['ip', 'link', 'del', self.ifaces[0]])
def up(self):
"""
set veth pair links up
:raises subprocess.CalledProcessError if operation fails
"""
for idx in [0, 1]:
subprocess.check_call(["ip", "link", "set", self.ifaces[idx], "up"]) # noqa: E501
def down(self):
"""
set veth pair links down
:raises subprocess.CalledProcessError if operation fails
"""
for idx in [0, 1]:
subprocess.check_call(["ip", "link", "set", self.ifaces[idx], "down"]) # noqa: E501
def __enter__(self):
self.setup()
self.up()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.destroy()