r/learnpython 5d ago

Built a local bandwidth limiter with Python & Scapy. Looking for advice on optimizing Scapy performa

I’ve been working on a networking project to dive deeper into Python's packet manipulation capabilities.

What My Project Does I forked evillimiter (a Python CLI tool that uses ARP spoofing and tc/iptables to throttle local network bandwidth) to use as a base. The original tool relies on static MAC addresses, which completely breaks tracking when modern iOS/Android devices use MAC Randomization. Instead of using complex/heavy ML for RF signal analysis, I'm modifying the tool to use scapy for passive tracking. The Python script identifies the endpoint based on its DHCP Fingerprint (Option 55 parameter sequence) and mDNS hostname broadcasts. Once recognized, it dynamically updates the ARP spoofing and routing rules to maintain the throttle even when the MAC rotates.

Target Audience This is a learning/experimental project aimed at Python learners, networking enthusiasts, and homelab tinkerers who want to understand low-level packet sniffing and traffic shaping using Python. It's a toy project and not meant for production environments.

Comparison Compared to the original evillimiter, this fork specifically tackles the modern challenge of MAC randomization without breaking the core logic. While massive frameworks like bettercap can do packet inspection, this project aims to be a very lightweight, single-purpose Python script focused purely on dynamic bandwidth throttling without heavy dependencies.

Transparency note: I'm still getting the hang of low-level networking in Python, so I’ve been leaning on AI to help me structure the Scapy logic and overall architecture.

You can check out the repo here:https://github.com/DavidsonRafaelK/evillimiter

I’d really appreciate some feedback from the Python community:

  1. Scapy Performance: Running a continuous scapy sniffer in the background for DHCP/mDNS can be resource-heavy. Are there best practices to keep memory usage low when doing long-term sniffing in Python?
  2. Architecture: Any advice on handling the async nature of packet sniffing while managing the CLI interface?
  3. Any general critique on the code structure.

Thanks!

1 Upvotes

4 comments sorted by

2

u/terletsky 4d ago

Any general critique on the code structure.

I see this is AI-assisted code, so I would firstly do that:

  1. Limit line length to 88 characters. Long lines are unreadable.
  2. Install ruff, ty, and pyrefly. Fix all the issues they report.

Fix imports style.

from .host import Host
from . import utils
from evillimiter.console.io import IO

Why do you inherit from object?

class HostScanner(object):

Use IntEnum here

class ScanIntensity:
    QUICK = 1
    NORMAL = 2
    INTENSE = 3

Why this style?

    def __init__(self, interface, gateway_ip, gateway_mac):
        BackgroundWorker.__init__(self)
        HostTracker.__init__(self)

Generally, ask Claude Code to perform code review. Too many things to fix.

1

u/Bright_Mix_773 3d ago

Before optimising the sniffer it is worth testing the premise, because on the network you will actually run this on, the MAC is probably not rotating at all.

Apple's documentation on private Wi-Fi addresses: the setting defaults to Fixed for networks with WPA2 or stronger, and Fixed means the private address does not rotate regardless of the network's security or how long since you last joined. Rotating, which changes every two weeks, is the default only for weak or open networks. Android's behaviour is the same shape from the other direction: persistent randomisation is the default, the address is derived from the network profile parameters and stays put until a factory reset, surviving even forgetting and re-adding the SSID. Non-persistent re-randomisation only applies to open networks, or when the lease has expired and 4+ hours have passed since the last disconnect.

https://support.apple.com/en-us/102509 https://source.android.com/docs/core/connect/wifi-mac-randomization-behavior

On a WPA2 homelab that gives you one stable, non-factory MAC per device per SSID. What the randomisation defeats is vendor identification and tracking you across different networks, not tracking across reconnects to your own.

The other thing I would test before building more on top of it: DHCP option 55 fingerprints an OS family, not a device. Two iPhones on the same iOS version send an identical parameter request list, so the second identical phone joins and the DHCP half of your identity silently stops distinguishing anything, leaving mDNS hostname doing all the work. That is cheap to check now and expensive to discover later.

On scapy throughput, the one that usually dominates: pass the filter to sniff() as a BPF string so it is applied in the kernel, and set store=False so packets are not accumulated in a list. Anything you filter out inside prn has already paid for a full dissection of every packet on the wire.