PNG IHDR x sBIT|d pHYs + tEXtSoftware www.inkscape.org< ,tEXtComment
import asyncio
import logging
from socket import AF_PACKET, SOCK_RAW, SOL_SOCKET, errno, error, htons, socket
from typing import Optional
from pyroute2.ext import bpf
from pyroute2.iproute.linux import AsyncIPRoute
from pyroute2.netlink.rtnl import RTMGRP_LINK
LOG = logging.getLogger(__name__)
ETH_P_IP = 0x0800
ETH_P_ALL = 3
SO_ATTACH_FILTER = 26
SO_DETACH_FILTER = 27
total_filter = [[0x06, 0, 0, 0]]
class AsyncRawSocket(socket):
'''
This raw socket binds to an interface and optionally installs a BPF
filter.
When created, the socket's buffer is cleared to remove packets that
arrived before bind() or the BPF filter is installed. Doing so
requires calling recvfrom() which may raise an exception if the
interface is down.
In order to allow creating the socket when the interface is
down, the ENETDOWN exception is caught and discarded.
'''
fprog = None
def __init__(self, ifname: str, bpf: Optional[list[list[int]]] = None):
self.ifname = ifname
self.bpf = bpf
# start watching for mac addr changes
self._l2addr_watcher: Optional[asyncio.Task] = None
async def __aexit__(self, *_):
self._l2addr_watcher.cancel()
self.close()
async def __aenter__(self):
# lookup the interface details
async with AsyncIPRoute() as ip:
async for link in await ip.get_links():
if link.get_attr('IFLA_IFNAME') == self.ifname:
break
else:
raise IOError(2, 'Link not found')
self.l2addr: str = link.get_attr('IFLA_ADDRESS')
self.ifindex: int = link['index']
# bring up the socket
socket.__init__(self, AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))
socket.setblocking(self, False)
socket.bind(self, (self.ifname, ETH_P_ALL))
if self.bpf:
self.clear_buffer()
fstring, self.fprog = bpf.compile(self.bpf)
socket.setsockopt(self, SOL_SOCKET, SO_ATTACH_FILTER, fstring)
else:
# FIXME: should be async
self.clear_buffer(remove_total_filter=True)
# change self.l2addr if it changes
self._l2addr_watcher = asyncio.create_task(
self._watch_l2addr_changes(),
name=f'Watch {self.ifname} for l2addr changes',
)
return self
async def _watch_l2addr_changes(self):
'''Updates self.l2addr when the interfaces's mac changes.
During the lifetime of the socket, the interface's mac can change, and
since it's read it at startup & used to build packets, they will then
have the wrong mac.
'''
async with AsyncIPRoute() as ipr:
await ipr.bind(RTMGRP_LINK)
while True:
async for msg in ipr.get():
if msg.get('IFLA_IFNAME') != self.ifname:
continue
new_l2addr = msg.get_attr('IFLA_ADDRESS')
if new_l2addr and new_l2addr != self.l2addr:
LOG.info(
'l2addr for %s changed from %s to %s',
self.ifname,
self.l2addr,
new_l2addr,
)
self.l2addr = new_l2addr
def clear_buffer(self, remove_total_filter: bool = False):
# there is a window of time after the socket has been created and
# before bind/attaching a filter where packets can be queued onto the
# socket buffer
# see comments in function set_kernel_filter() in libpcap's
# pcap-linux.c. libpcap sets a total filter which does not match any
# packet. It then clears what is already in the socket
# before setting the desired filter
total_fstring, prog = bpf.compile(total_filter)
socket.setsockopt(self, SOL_SOCKET, SO_ATTACH_FILTER, total_fstring)
while True:
try:
self.recvfrom(0)
except error as e:
if e.args[0] == errno.ENETDOWN:
# we only get this exception once per down event
# there may be more packets left to clean
pass
elif e.args[0] in [errno.EAGAIN, errno.EWOULDBLOCK]:
break
else:
raise
if remove_total_filter:
# total_fstring ignored
socket.setsockopt(
self, SOL_SOCKET, SO_DETACH_FILTER, total_fstring
)
@staticmethod
def csum(data: bytes) -> int:
'''Compute the "Internet checksum" for the given bytes.'''
if len(data) % 2:
data += b'\x00'
csum: int = 0
# pretty much the fastest way to compute this in Python
for i in range(len(data) // 2):
offset = i * 2
csum += (data[offset] << 8) + data[offset + 1]
csum = (csum >> 16) + (csum & 0xFFFF)
csum += csum >> 16
return ~csum & 0xFFFF
b IDATxytVսϓ22 A@IR:hCiZ[v*E:WũZA ^dQeQ @ !jZ'>gsV仿$|?g)&x-E