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 and IPv6 neighbor discovery, 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 or IPv6 multicast ping (the same things 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 IPv4 subnet
and, unless disabled, pings the IPv6 all-nodes multicast address to reach
every IPv6-enabled host on the link too (
lanfence scanfor one sweep, or on an interval insidelanfence monitor) - so a device that's IPv6-only, or deliberately configured off IPv4 to dodge an ARP-only monitor, doesn't go unseen. Every discovered MAC/IP pairing feeds the same pipeline regardless of address family. - Passive monitoring - between active sweeps,
lanfence monitoralso listens for ARP and IPv6 neighbor-discovery 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 / Ai-Thinker | 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: Orange Pi (Shenzhen Xunlong) | orange_pi |
Same rationale as Raspberry Pi - a legitimate SBC also common in DIY implant projects. |
| Vendor: Allwinner | allwinner_sbc_or_camera |
Common in budget SBCs, Android TV boxes, and cheap white-label Wi-Fi cameras. |
| Vendor: HiSilicon | hisilicon_camera_soc |
The Hi3516/Hi3518-family SoC behind huge numbers of cheap white-label IP cameras/DVRs - and the hardware base widely reported behind the Mirai botnet and its successors. |
| 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 own default hostname (main.name in its default config) - confirmed from the project's source. |
Hostname contains bunny / turtle / pineapple |
Hak5 tooling | Commonly-reported default hostnames for Bash Bunny / LAN Turtle / WiFi Pineapple - corroborated from Hak5's own community forum and docs, not an official spec page, and short enough to occasionally match an unrelated device. |
| 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.
Vendor lookups
The bundled lanfence/data/oui_vendors.txt is a full snapshot (~40,000
entries) of the IEEE's public MA-L OUI registry, taken when this version was
built. LAN Fence makes no network calls on its own and does not auto-update
it; run lanfence vendor-refresh to pull a current copy on demand:
lanfence vendor-refresh # -> ~/.config/lanfence/oui_vendors.txt
lanfence vendor-refresh --output my_ouis.txt # or choose where to save it
It's saved as an extra table (never overwriting the packaged one) - add
vendor_file: <path it printed> to your config to have scan/monitor
merge it on top of the built-in table. This is the one deliberate,
operator-triggered exception to "no network calls", the same as lanfence upgrade checking PyPI - it only ever runs when you type the command.
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)
lanfence vendor-refresh # pull a current copy of the IEEE OUI registry
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/ND traffic between sweeps
ipv6: true # also discover devices via IPv6 neighbor discovery
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 automatic external calls. LAN Fence never phones home
on its own. Alert destinations are ones you configure (your own syslog
daemon, SMTP relay, or webhook URL), and the only other network access is
two commands that exist purely to fetch something you asked for, only
when you run them:
lanfence upgrade(checks/installs from PyPI) andlanfence vendor-refresh(downloads the IEEE OUI registry). Every other command touches only your local network (ARP) and disk. - The bundled vendor and signature tables are static snapshots taken when
this version was built; nothing is fetched automatically to "keep them
fresh" - that's what
vendor-refreshis for, on request. - 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.5.tar.gz.
File metadata
- Download URL: lanfence-0.3.5.tar.gz
- Upload date:
- Size: 469.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
002166fd784db334f565c193f2622ac2e5d7d741700ee3ba4d5a297e75f206c7
|
|
| MD5 |
1503ef7208ef18da5acc6870da0da73c
|
|
| BLAKE2b-256 |
36aeb842cb8658ecacfd81b5d10ff0d4fcfbb164c32e573e3fad39e704be7bc7
|
Provenance
The following attestation bundles were made for lanfence-0.3.5.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.5.tar.gz -
Subject digest:
002166fd784db334f565c193f2622ac2e5d7d741700ee3ba4d5a297e75f206c7 - Sigstore transparency entry: 2794805689
- Sigstore integration time:
-
Permalink:
rosscooney/LanFence@2b1dc4b09c2991aefc498c129f625b21bfb50fb5 -
Branch / Tag:
refs/tags/v0.3.5 - Owner: https://github.com/rosscooney
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@2b1dc4b09c2991aefc498c129f625b21bfb50fb5 -
Trigger Event:
release
-
Statement type:
File details
Details for the file lanfence-0.3.5-py3-none-any.whl.
File metadata
- Download URL: lanfence-0.3.5-py3-none-any.whl
- Upload date:
- Size: 451.2 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 |
22ae55f4f27a232839b67fb56309d4633b7a6ef79ccb3250740042e60da7ae26
|
|
| MD5 |
5c3614a4fb88ce18e399cca875ae8559
|
|
| BLAKE2b-256 |
19b46f0c92b83fd5797dfc0da5cd0968ef7dbb728a9f400c7f26a272d2cbb847
|
Provenance
The following attestation bundles were made for lanfence-0.3.5-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.5-py3-none-any.whl -
Subject digest:
22ae55f4f27a232839b67fb56309d4633b7a6ef79ccb3250740042e60da7ae26 - Sigstore transparency entry: 2794805786
- Sigstore integration time:
-
Permalink:
rosscooney/LanFence@2b1dc4b09c2991aefc498c129f625b21bfb50fb5 -
Branch / Tag:
refs/tags/v0.3.5 - Owner: https://github.com/rosscooney
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@2b1dc4b09c2991aefc498c129f625b21bfb50fb5 -
Trigger Event:
release
-
Statement type: