LAN Fence
LAN Fence is an open-source defensive network device monitor for Linux (developed and tested on Raspberry Pi OS / Debian, and reasonably portable to other Debian/Ubuntu systems).
- Home page: https://lanfence.com
- Source & downloads: https://github.com/rosscooney/lanfence (releases)
- Package:
lanfenceon PyPI
LAN Fence runs on a small Linux box (a Raspberry Pi is the common case) sitting on your network. It continuously scans for connected devices via ARP, maintains an allowlist of devices you already trust, and alerts in plain language when something unknown joins - a rogue device, unauthorized hardware, or a supply-chain implant on your LAN.
LAN Fence only observes. It sends nothing beyond a standard ARP "who-has" request (the same thing every device on your LAN does routinely) and never touches, blocks, deauthenticates or spoofs anything.
⚠️ LAN Fence cannot prove a device is malicious, or that a MAC address is genuine. MAC vendor prefixes and hostnames are trivially spoofed by anyone deliberately trying to blend in. A finding is a lead worth checking by hand, not a verdict - use it as one input to your own judgement.
How it works
- Active scanning - LAN Fence periodically ARP-sweeps your subnet
(
lanfence scanfor one sweep, or on an interval insidelanfence monitor), collecting every MAC/IP that answers. - Passive monitoring - between active sweeps,
lanfence monitoralso listens for ARP traffic on the wire, so a device that joins mid-interval is caught sooner rather than waiting for the next sweep. - Every sighting is folded into a persistent SQLite database keyed by MAC
address, which tracks each device's lifecycle:
new_devicethe first time it's ever seen,reappearedif it had gone offline and came back, anddisconnectedwhen an active sweep no longer sees it. - Each device is fingerprinted: an offline OUI → vendor lookup, a set of built-in rogue-device signatures (see below), and a check of whether its MAC is locally administered (randomized/spoofed rather than vendor-assigned).
- Each device is checked against your allowlist
(
lanfence allow <mac>). A brand-new or reappearing device not on the allowlist produces a plain-language finding with a severity (high/medium/info), a rationale, and a recommendation; an allowlisted device is downgraded toinfoso your own hardware stops shouting every time it reconnects. - Findings can be dispatched to syslog, email, or a webhook, and everything is available as a CLI table or JSON for automation.
Built-in rogue-device signatures
Heuristics, not proof - a match is a lead to check by hand:
| Signal | Category | Why it matters |
|---|---|---|
| Vendor: Espressif | esp32_esp8266 |
ESP32/ESP8266 - the chipset behind most cheap DIY hidden cameras, rogue APs, and ESP32-based Wi-Fi implants (as well as plenty of legitimate IoT). |
| Vendor: Raspberry Pi | raspberry_pi |
Legitimate everywhere, but also the common hardware basis for rogue network-tap / implant projects (P4wnP1, home-built taps). |
| Vendor: ASIX Electronics | usb_ethernet_gadget |
USB-Ethernet chipset used both by ordinary dongles and by BadUSB tools (Bash Bunny, LAN Turtle, O.MG cable) presenting as a network adapter. |
Hostname contains pwnagotchi |
pwnagotchi |
Pwnagotchi's distinctive default hostname. |
Hostname contains bashbunny / lanturtle / pineapple / omg-cable |
Hak5/O.MG tooling | Default hostnames of common commercial implant/pentest hardware. |
Hostname contains flipper |
flipper_zero |
Flipper Zero (via its Wi-Fi dev board). |
| Locally administered MAC | locally_administered_mac |
No vendor OUI - common for privacy MAC-randomization on phones/laptops, but also for spoofed or gadget hardware. |
Extend or override these with your own rogue_signatures_file: (same YAML
shape as lanfence/data/rogue_signatures.yaml) and vendor_file: (same
tab-separated shape as lanfence/data/oui_vendors.txt) in config.
Install
pipx install "lanfence[scan]" # isolated, recommended - includes scapy for scanning
scan/monitor need the scan extra (scapy) to actually send/receive ARP
packets; allow, report and check work without it. Already installed
without the extra? Add it in place:
pipx inject lanfence scapy
or, without pipx:
python3 -m venv ~/.venvs/lanfence
~/.venvs/lanfence/bin/pip install 'lanfence[scan]'
Scanning needs raw-socket access, so scan/monitor typically need sudo
(or CAP_NET_RAW on the interpreter). allow, report and check do not.
Commands
lanfence scan # one-time active ARP scan; table + findings
lanfence scan --format json # same, machine-readable
lanfence monitor # continuous: active sweeps + passive sniffing
lanfence allow <MAC> --name X # trust a device; its findings become info
lanfence allow --list # show the allowlist
lanfence allow --remove <MAC> # untrust a device
lanfence report --since 24h # summarize events/findings from the database
lanfence check # verify permissions, scapy, interface, storage
lanfence upgrade # check PyPI and install a newer release, if any
lanfence upgrade --check # only report whether an update is available
lanfence link # make `sudo lanfence` work (pipx/--user installs)
scan/monitor warn (and show copy-pasteable fixes) if not run as root, since
ARP scanning needs raw-socket access. A pipx / pip install --user install
puts the lanfence launcher in ~/.local/bin, which sudo does not see by
default - sudo lanfence scan then fails with "command not found". Run
lanfence link once (no sudo needed up front - it re-execs itself under
sudo and prompts for your password) to symlink the launcher onto root's
PATH; after that, a bare sudo lanfence scan / sudo lanfence monitor
works. lanfence link --remove undoes it.
Example: an unknown device joins
$ sudo lanfence scan
Devices seen (4)
┌───────────────────┬──────────────┬──────────────┬────────────────────┬────────┬─────────┐
│ MAC │ IP │ Hostname │ Vendor │ Status │ Trusted │
├───────────────────┼──────────────┼──────────────┼────────────────────┼────────┼─────────┤
│ b8:27:eb:12:34:56 │ 192.168.1.10 │ nas.local │ Raspberry Pi │ online │ yes (NAS)│
│ 52:8a:1c:99:f4:2d │ 192.168.1.47 │ [unknown] │ [unknown] │ online │ no │
└───────────────────┴──────────────┴──────────────┴────────────────────┴────────┴─────────┘
Findings (1)
MEDIUM Unknown device connected
MAC: 52:8a:1c:99:f4:2d
The vendor bit pattern indicates a locally administered address rather
than one assigned by a hardware vendor. Common causes: MAC-randomization
privacy features on modern phones/laptops, virtual machines/containers,
or a device deliberately spoofing its address.
Recommendation: Verify this device belongs on your network. If it's
yours, run `lanfence allow 52:8a:1c:99:f4:2d` to stop future alerts.
• MAC: 52:8a:1c:99:f4:2d
• IP: 192.168.1.47
• Hostname: [unknown]
• Vendor: [unknown]
• mac = 52:8a:1c:99:f4:2d (U/L bit set, no vendor OUI match)
Overall: 1 finding(s), highest severity: medium
Running unattended
LAN Fence does not ship its own scheduler; use systemd (recommended on a
Pi) or cron.
Continuous monitoring - /etc/systemd/system/lanfence.service:
[Unit]
Description=LAN Fence continuous monitoring
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=/usr/local/bin/lanfence monitor --config /etc/lanfence/config.yaml
Restart=on-failure
User=root
[Install]
WantedBy=multi-user.target
sudo systemctl enable --now lanfence
Daily report - a cron entry (sudo crontab -e):
0 7 * * * /usr/local/bin/lanfence report --since 24h --format json > /var/log/lanfence/daily.json
Configuration
All settings are optional; everything has a sensible default. Pass
--config path/to/config.yaml to any command.
scan:
interface: null # null = auto-detect
subnet: null # null = derive from the interface's own address
scan_interval_seconds: 60 # how often `monitor` repeats an active sweep
active_scan_timeout_seconds: 3
passive: true # also sniff ARP traffic between sweeps
resolve_hostnames: true # try reverse DNS for each device
dns_timeout_seconds: 1
alerts:
min_severity: medium # info | medium | high - dispatch threshold
syslog:
enabled: false
address: /dev/log
facility: user
email:
enabled: false
smtp_host: localhost
smtp_port: 587
use_tls: true
username: null
password: null
from_addr: null
to_addrs: []
webhook:
enabled: false
url: null
timeout_seconds: 5
db_path: ~/.local/share/lanfence/lanfence.db
allowlist_file: ~/.config/lanfence/allowlist.yaml
vendor_file: null # extra OUI table, merged with the packaged one
rogue_signatures_file: null # extra signatures, merged with the packaged ones
Exit codes (--fail-on-findings)
scan and report accept --fail-on-findings for CI/scripting use:
| Highest severity in the result | Exit code |
|---|---|
| none / info | 0 |
| medium | 10 |
| high | 20 |
Privacy and security
- No telemetry, no external calls. LAN Fence never phones home. The only network destinations it ever contacts are ones you configure: your own syslog daemon, your own SMTP relay, or your own webhook URL.
- The vendor and signature databases are bundled, offline, and static - nothing is fetched to "keep them fresh".
- The device database and allowlist are written atomically and are owner-readable only where the platform supports it.
Development
python3 -m venv .venv && . .venv/bin/activate
pip install -e ".[dev,scan]"
pytest
See CONTRIBUTING.md for scope and pull-request guidelines,
and DISTRIBUTING.md for licensing notes on the optional
scapy (GPL-2.0) dependency.
License
MIT - see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lanfence-0.3.4.tar.gz.
File metadata
- Download URL: lanfence-0.3.4.tar.gz
- Upload date:
- Size: 56.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
59728b505b0a7d2a2b5021298ba931f21ab785308fddc7e4ff22d20cbc9c3620
|
|
| MD5 |
8797f3c1cc55d5142cbaa00ff0122db0
|
|
| BLAKE2b-256 |
1784d6faad09b2cd9ce4c076e8762aa1e622fd652f0ea5a65c0e8fecd667cc7e
|
Provenance
The following attestation bundles were made for lanfence-0.3.4.tar.gz:
Publisher:
python-publish.yml on rosscooney/LanFence
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lanfence-0.3.4.tar.gz -
Subject digest:
59728b505b0a7d2a2b5021298ba931f21ab785308fddc7e4ff22d20cbc9c3620 - Sigstore transparency entry: 2794115018
- Sigstore integration time:
-
Permalink:
rosscooney/LanFence@2fc43e1176068d7de95884d6083e0d7b17726bfd -
Branch / Tag:
refs/tags/v0.3.4 - Owner: https://github.com/rosscooney
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@2fc43e1176068d7de95884d6083e0d7b17726bfd -
Trigger Event:
release
-
Statement type:
File details
Details for the file lanfence-0.3.4-py3-none-any.whl.
File metadata
- Download URL: lanfence-0.3.4-py3-none-any.whl
- Upload date:
- Size: 44.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
04c9fc85d560cf979e3308e16bbf25d3f48663f08bb8419906659a4719694f51
|
|
| MD5 |
ec772170715979b4dc1d8316234d1f2f
|
|
| BLAKE2b-256 |
ca4051c960e59678d7ffd7c99c1d2c37567a0b03af5d53903c51fa2a8a6985ee
|
Provenance
The following attestation bundles were made for lanfence-0.3.4-py3-none-any.whl:
Publisher:
python-publish.yml on rosscooney/LanFence
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lanfence-0.3.4-py3-none-any.whl -
Subject digest:
04c9fc85d560cf979e3308e16bbf25d3f48663f08bb8419906659a4719694f51 - Sigstore transparency entry: 2794115090
- Sigstore integration time:
-
Permalink:
rosscooney/LanFence@2fc43e1176068d7de95884d6083e0d7b17726bfd -
Branch / Tag:
refs/tags/v0.3.4 - Owner: https://github.com/rosscooney
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@2fc43e1176068d7de95884d6083e0d7b17726bfd -
Trigger Event:
release
-
Statement type: