DreadpiratePickles/lantern-lan
GitHub: DreadpiratePickles/lantern-lan
一款局域网设备发现与清单管理工具,通过 MAC 厂商识别和随机化感知帮助防御者快速发现网络中的未知设备。
Stars: 0 | Forks: 0
# 🏮 Lantern LAN
### 映射局域网 · 识别供应商 · 揪出陌生设备
     
*你的网络上最可怕的设备,就是你叫不出名字的那些。*
Lantern LAN 构建并维护本地 IPv4 网络上的设备清单,通过供应商提示丰富每个 MAC 地址的信息,记录它之前检测到的设备,并将*未知*设备放在你最显眼的地方——无论是在带有颜色区分的终端视图中,还是在本地 Web 仪表板中。
它是一个**用于你拥有或明确授权评估的网络的防御性工具。**它只做一件切实有效的事:帮助家庭实验室或小型办公室的防御者回答“谁在我的 Wi-Fi 上,其中是否有新出现或意外的设备?”——相比于假设那个神秘的 MAC 地址可能没什么问题,这是迈向网络卫生管理成本更低的第一步。
## 为什么开发它
家庭和小型办公网络会在不知不觉中积累设备:一个新的智能插座、客人的手机、室友的游戏机,偶尔还会出现一些你并没有放置的设备。单纯的 `arp -a` 转储就像是一堵十六进制的墙。Lantern LAN 将其转化为一份运行记录,包含防御者真正需要的三样东西:
1. **供应商归属** — OUI 查询将 `b8:27:eb:…` 转换为“Raspberry Pi”。
2. **随机化感知** — 现代手机为了保护隐私会轮换*随机* MAC 地址。Lantern LAN 会检测到这些(本地管理位)并将它们归入独立的平静的 `randomized` 状态,这样你的手机就不会在“unknown”视图中引发虚警淹没。剩下被标记为 **unknown** 的才是真正无法识别的、全球分配的地址——这才是值得关注的情况。
3. **记忆功能** — 首次发现/最后发现的时间戳和信任标志会在多次扫描中持久化存储于 SQLite 中,因此“刚刚出现”是一个可见的信号,而不是你需要自己去重建的东西。
## 工作原理(数据流)
```
┌──────────────┐
scan ───────► │ scanner.py │ active ARP sweep (Scapy) of a private CIDR
│ │ └─ falls back to passive `ip neigh` cache
└──────┬───────┘ (Linux) if Scapy/permissions unavailable
│ Device(ip, mac, vendor, source)
▼
┌──────────────┐
│ oui.py │ MAC → vendor + classify_mac():
│ │ universal / randomized / multicast / invalid
└──────┬───────┘
▼
┌──────────────┐
│ store.py │ SQLite upsert. Preserves trust marks and
│ │ first_seen across re-scans; computes a
└──────┬───────┘ display status per device.
│
┌─────────┴──────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ cli.py │ │ app.py │ Flask dashboard (127.0.0.1),
│ rich tables │ │ templates/ │ strangers float to the top.
└──────────────┘ └──────────────┘
```
**状态是推导出来的,而不是存储的** — 信任级别最高,其次是带有未知供应商的隐私随机化 MAC,状态为 `randomized`,然后是无法识别的全局 OUI 为 `unknown`,否则为 `known`:
| 状态 | 颜色 | 含义 |
| ------------ | ------- | ------------------------------------------------------------- |
| `unknown` | 红色 | 无法识别的**全球分配** OUI。请注意这些。 |
| `randomized` | 黄色 | 本地管理/隐私随机化 MAC(通常是手机)。 |
| `known` | 青色 | 从本地 OUI 表中识别出的供应商。 |
| `trusted` | 绿色 | 你明确标记为受信任的设备。 |
`•new` 徽章标记仅被检测到过一次(未再次观察到)的设备。
**内置的发现保护机制:** 扫描范围被限制在 `/20`(4096 个主机),仅限 IPv4,并且**默认拒绝非私有地址空间** — 防御性清单工具不应该对公共互联网进行 ARP 扫描。
## 设置
```
cd lantern-lan
python -m venv .venv
# Linux/macOS: source .venv/bin/activate
# Windows: .venv\Scripts\activate
pip install -r requirements.txt
pip install -e .
cp .env.example .env # optional: defaults for DB path, CIDR, host/port
```
在 Kali / Parrot 上,主动 ARP 扫描需要 packet 工具,并且通常需要 root 权限:
```
sudo apt install -y python3-scapy tcpdump
```
将 `src` 添加到路径中即可直接运行,无需安装:
```
PYTHONPATH=src python -m lantern_lan.cli --help
```
## 使用方法
全局标志(位于子命令之前):`--db PATH`(SQLite 清单,环境变量 `LANTERN_LAN_DB`)和 `--plain` / `--no-color`(同时也会自动响应 `NO_COLOR` 和非 TTY 输出)。
### `scan` — 发现设备并保存
主动 ARP 扫描**私有** CIDR 并保存结果。因为这会发送数据包,所以需要经过授权确认才能执行。
```
# 提示进行 YES 确认,然后进行扫描:
sudo lantern-lan scan --cidr 192.168.1.0/24
# 非交互式(scripts/cron):预先断言授权:
sudo lantern-lan scan --cidr 192.168.1.0/24 --yes
# ...或者在环境中设置 LANTERN_LAN_ASSUME_AUTHORIZED=1。
# 被动模式:读取主机的 neighbor cache 而不是发送 ARP(无需同意):
lantern-lan scan --ip-neigh-only
# 机器可读,stdout 上无额外修饰:
lantern-lan scan --cidr 192.168.1.0/24 --yes --json
```
标志:`--cidr`, `--timeout`, `--interface`, `--ip-neigh-only`,
`--yes/--i-am-authorized`, `--allow-public-range`, `--json`.
示例输出:
```
┌───────────────────── 🏮 lantern-lan ─────────────────────┐
│ map the LAN · name the vendors · surface the strangers │
└────────────────────────── scan ──────────────────────────┘
scan · 192.168.1.0/24 · via scapy
┌──────────────┬──────────────┬───────────────────┬──────────────┬────────┐
│ Status │ IP │ MAC │ Vendor │ Source │
├──────────────┼──────────────┼───────────────────┼──────────────┼────────┤
│ UNKNOWN •new │ 192.168.1.47 │ 00:11:22:33:44:55 │ Unknown │ scapy │
│ RANDOM •new │ 192.168.1.23 │ de:ad:be:ef:00:01 │ Unknown │ scapy │
│ OK │ 192.168.1.10 │ b8:27:eb:aa:bb:cc │ Raspberry Pi │ scapy │
│ TRUSTED │ 192.168.1.1 │ 44:d9:e7:11:22:33 │ Ubiquiti │ scapy │
└──────────────┴──────────────┴───────────────────┴──────────────┴────────┘
┌─────────────────── inventory ────────────────────┐
│ 1 unknown 1 randomized 1 trusted 4 total │
└──────────────────────────────────────────────────┘
saved 4 device(s) → lantern_lan.sqlite3 1 new
```
### `devices` — 显示已保存的清单
陌生设备优先(unknown → randomized → known → trusted),在每个类别内按 IP 排序。
```
lantern-lan devices # pretty table + summary
lantern-lan devices --unknown-only # only red/unknown rows
lantern-lan devices --watch # live-refreshing view (Ctrl-C to exit)
lantern-lan devices --json # pure JSON for scripting
```
标志:`--json`, `--unknown-only`, `--watch`, `--interval SECONDS`.
### `trust` — 将设备标记为受信任(或撤销信任)
接受任何常见的 MAC 格式(`:`, `-`, `.`,任何大小写)。报告是否实际匹配到了记录。
```
lantern-lan trust b8:27:eb:aa:bb:cc
lantern-lan trust B8-27-EB-AA-BB-CC --remove
```
### `forget` — 从清单中移除设备
适用于那些已经彻底离开、你不想让它继续残留的设备。
```
lantern-lan forget de:ad:be:ef:00:01
```
### `serve` — Web 仪表板
默认绑定到 `127.0.0.1:5063`。具有相同的三态视图,并为每个设备提供一个 Trust/Untrust 按钮。
```
lantern-lan serve # http://127.0.0.1:5063
lantern-lan serve --host 127.0.0.1 --port 8080
```
标志:`--host`, `--port`, `--debug`. 如果你绑定到 localhost 之外(仪表板没有身份验证)或启用了 `--debug`(如果可以被访问到,Werkzeug 调试器会导致远程代码执行),CLI 会发出强烈的警告。
**退出代码:** `0` 成功 · `1` 错误 · `2` 拒绝执行(未经授权的扫描)。
## 安全与授权
- **仅限已授权目标。** 主动扫描会向范围内的每个主机发送 ARP 数据包。仅对你拥有或明确授权评估的网络运行此操作。如果没有明确的 `--yes` / 交互式 `YES` / `LANTERN_LAN_ASSUME_AUTHORIZED=1`,`scan` 子命令将拒绝执行。
- **默认为私有范围。** 除非你添加 `--allow-public-range`(并且你仍需获得授权),否则将拒绝公共/非 RFC1918 的 CIDR。
- **扫描大小限制。** 任何大于 `/20` 的范围都会作为可能的误操作被拒绝。
- **被动回退即为被动。** `--ip-neigh-only` 仅读取你本机现有的邻居缓存;它不发送任何内容,因此无需授权。
- **仪表板默认仅限本地。** 绑定到 `127.0.0.1`;它没有登录功能,所以请保持在此地址。更改状态的 POST 请求会进行 same-origin 检查,输入会被验证,并且设置了保守的响应头——这是安全带,而不是一把锁。
- **输出中无敏感信息。** 该工具仅存储 MAC/IP/供应商/时间戳;不会记录任何敏感信息。
## 开发与测试
快速、确定、无网络依赖、无 sleeps:
```
cd lantern-lan
PYTHONPATH=src python -m pytest -q
```
测试套件涵盖了 MAC 分类(randomized vs. universal vs. multicast)、私有范围/大小 CIDR 保护、基于 CIDR 过滤的邻居缓存回退、清单状态推导、跨多次重新扫描的信任/首次发现的持久性、CLI 授权确认和 JSON 纯度,以及 Flask 的输入验证 / same-origin / 响应头加固。
### 项目结构
```
lantern-lan/
src/lantern_lan/
scanner.py # Scapy ARP sweep + passive `ip neigh` fallback, CIDR guards
oui.py # local MAC→vendor table + MAC classification
store.py # SQLite inventory, trust flags, status derivation
cli.py # scan · devices · trust · forget · serve (+ rich rendering)
app.py # Flask dashboard (hardened)
ui.py # shared theme / console / banner
templates/
tests/
```
### 扩展供应商表
`oui.py` 提供了一组精选的常见 OUI。将你环境的前缀添加到 `OUI_PREFIXES` 中。如需完整覆盖,请接入下载好的 IEEE `oui.txt` — `lookup_vendor` 边界已经将调用者与数据所在的具体位置隔离开来(有关升级路径,请参阅文件中的 `# ponytail:` 注释)。
```
---
## 🏷️ 为什么叫 "Lantern LAN"?
Hold up a lantern, see who's in the room. Finding devices is the easy half — any scanner does that. The half that actually matters is telling a genuinely unknown host apart from an iPhone doing MAC-address randomisation, because to a naive scanner those look identical, and a tool that cries wolf about your colleague's phone every morning is a tool you will stop reading by Thursday.
---
## 🔬 这个工具是如何构建的
> One of ten defensive tools rebuilt to a single standard — *understand, reimagine,
> build the CLI, harden, document, verify*. The goal was never to change what these
> tools **are**, but to take each one as far as it could honestly go on correctness,
> safety and the experience of the person actually running it at 2am.
**Purpose.** A LAN device mapper that no longer just lists MACs but *reasons* about
them: it tells a genuinely-unknown globally-assigned device apart from an expected
privacy-randomized phone, remembers what it has seen, and puts the strangers
front-and-center — all behind an authorized-networks-only gate.
**How it works.** An active ARP scan (or a passive neighbor-cache read) discovers
devices; each MAC is classified by its OUI and the locally-administered bit, then
reconciled against a persistent SQLite inventory that tracks first-seen and
trusted state. Unknowns sort to the top.
**Standout CLI.** A color-coded status column (unknown=red, randomized=yellow,
known=cyan, trusted=green), an inventory summary panel whose border turns red when
unknowns are present, a `•new` badge for first-seen devices, and a `--watch`
live-refreshing table via `rich.Live`.
**Key improvements.** *Functional:* MAC-randomization detection (the
locally-administered bit) splits expected privacy MACs from genuinely-unknown
global OUIs, cutting false positives; a `forget` subcommand prunes stale devices;
`trust` now validates the MAC in any format and reports whether a row actually
changed; the CIDR-filtered neighbor-cache fallback no longer leaks devices from
other subnets; fixed leaking SQLite connections (matters for the long-lived Flask
process); hostname is preserved across re-scans. *Security:* the consent gate on
active ARP scans (`--yes` / interactive / `LANTERN_LAN_ASSUME_AUTHORIZED=1`, exit
2); private-range-only by default (`--allow-public-range` to override) with a /20
scan-size cap; the passive `--ip-neigh-only` path needs no consent (it sends
nothing); strict MAC validation and a same-origin Origin/Referer check on
`POST /trust` to block cross-site trust flips.
**Example.**
```bash
PYTHONPATH=src python -m lantern_lan.cli --db demo.sqlite3 devices
```
## ⚖️ 授权使用与安全准则
**这些是用于你拥有或明确授权评估的系统、文件、网络和人员的防御性工具。** 在运行任何内容之前,请阅读以下声明:
- **授权不是可选项。** 钓鱼演练、网络扫描、IP 信誉查询和 Web 应用探测都会触及他人的系统或数据。请先获得书面授权。一些工具在确认授权之前*拒绝执行任何操作*(`--yes`, `--authorized-training`, `--i-have-authorization`, `--i-am-authorized` 等)。
- **默认安全。** 每个 Web UI 都绑定到 `127.0.0.1` (loopback)。出站流量、实时发送和主动扫描都需要显式的标志才能执行——试运行、被动模式,或者拒绝并警告始终是默认设置。
- **防止意外自损。** Flask `--debug`(Werkzeug 交互式调试器在任何 traceback 上均可被用于远程代码执行)会受到强烈的警告,并且在 loopback 之外被完全拒绝。不受信任的输入会有大小限制、经过验证并被无效化处理,因此带有恶意的文件或日志行既不会导致你的终端崩溃,也不会被接管。
- **输出中无敏感信息。** API key 保留在 request header 中,密码来源于环境变量,并且不会记录或打印任何敏感信息。
这些不是攻击性工具。它们不包含任何漏洞利用、凭证收集器或 payload。如果某个工具*可能*被滥用,它的构造设计也会使其能够抵抗这种滥用。
## 🧪 开发
```
python -m venv .venv && source .venv/bin/activate
pip install -e .
pytest -q # 30 tests, no network, no sleeps
lantern-lan --help
```
## 📄 许可证
基于 **MIT License** 发布。详见 [LICENSE](LICENSE)。
防御性工具。无漏洞利用,无 payload,无凭证收集器。
标签:Docker 部署, Python, Scapy, 局域网发现, 插件系统, 无后门, 网络设备监控, 网络运维, 资产盘点, 逆向工具