Files

403 lines
27 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ROS-DPI
**给 MikroTik RouterOS 用的终端流量、应用识别与上网审计面板。**
不换路由器、不做旁路串接:RouterOS 把流量统计(IPFIX)和握手包镜像(TZSP)发给 ROS-DPI,它算出**每台设备、每个人用了多少流量、用在了哪些应用上**,并按规则记录访问、推送告警。界面参照维盟 / Panabit,审计参照深信服 AC。
[中文](#中文) · [English](#english) · [更新日志](CHANGELOG.md) · [RouterOS 配置详解](docs/RouterOS配置.md)
![全网概览](docs/images/overview-light.png)
---
## 中文
### 功能
| 页面 | 能看到 / 能做什么 |
|---|---|
| **全网概览** | 路由器状态、网络健康指数(逐项扣分和改进建议)、在线终端、DHCP 地址池、每条 WAN 线路的状态和速率;流量构成饼图;实时 / 历史流量;Top 应用与 Top 终端 |
| **主机监控** | 每台设备的类型(手机、电脑、电视、摄像头……自动识别)、IP、MAC、主机名、归属人、在线时长、实时速率、今日 / 本周 / 本月流量;按 MAC 或 IP 命名;单台设备的应用构成、访问过的域名、访问日志、上网时间线;导出 CSV |
| **应用统计** | 三级下钻:应用大类 → 小类 → 应用。大类照维盟的 17 类,共 50 个小类、约 2970 个应用(抖音、B 站、微信、Steam、银行、券商、ToDesk、DeepSeek……) |
| **流量流向** | 实时连接、访问目标排行、流向世界地图、代理分流(sing-box 的策略组 / 出口节点) |
| **家庭成员 / 员工** | 把一个人的设备归到一起按人统计。**家庭**场景看孩子上网健康(屏幕时间、游戏和短视频、熬夜),**企业**场景看效率与合规 |
| **审计日志** | 访问日志;审计规则(应用、域名、时段、流量阈值、大流量外发、私接路由、新设备);14 种通知渠道(Telegram、飞书、企业微信、钉钉、Bark、邮件、Server酱、Pushplus、Discord、Slack、ntfy、Gotify、QQ、Webhook);日报 / 周报;操作日志 |
| **管控命令** | 按应用、时段生成 RouterOS 命令(封禁应用、定时断网、限速)并附撤销命令。**只生成,不会改路由器** |
| **识别规则** | 按域名 / IP 段 / 端口自定义规则;未识别域名 Top 一键归类;各识别库的规模与更新状态 |
中英双语,浅色 / 深色主题,手机上也能用。
![应用统计](docs/images/apps-light.png)
![流量流向](docs/images/flow-light.png)
![家庭成员](docs/images/people-light.png)
### 三步上手
1. **部署 ROS-DPI**:Linux 一条命令,或 Docker Compose(见[部署](#部署))。
2. **配置 RouterOS**:在 **系统设置 → 接入与自检** 复制命令到路由器执行(见[RouterOS 配置](#routeros-配置))。
3. **打开网页**:约 1 分钟后概览页开始有数据。再选场景(家庭 / 企业)、把设备分给成员、配好通知渠道和审计规则。
### 部署
| | 方式一:Linux 一键安装 | 方式二:Docker Compose |
|---|---|---|
| 适合 | 一台 Linux 小主机 / 虚拟机 | 已有 Docker、NAS 或 MySQL |
| 数据库 | 默认 SQLite,零配置;可改 MySQL | MySQL 5.7 / 8.x(可一起启动) |
| 升级 | 网页上点「一键更新」 | `docker compose pull && docker compose up -d` |
| 架构 | x86_64、aarch64 | amd64、arm64 |
#### 方式一:Linux 一键安装
```bash
curl -fsSL https://raw.githubusercontent.com/suyijun8182/ros-dpi/main/install.sh | sudo bash
```
脚本会下载最新版单文件二进制(含前端)并校验 SHA256,创建专用账号 `ros-dpi`,注册并启动 systemd 服务。装完浏览器打开 `http://<本机IP>:8080`。
| 位置 | 用途 |
|---|---|
| `/opt/ros-dpi/bin/ros-dpi` | 程序(`/usr/local/bin/ros-dpi` 是指向它的软链接) |
| `/opt/ros-dpi/data/` | 数据:SQLite 库、识别库文件 |
| `/etc/ros-dpi.env` | 你的配置(`DATABASE_URL`、`ADMIN_PASSWORD`、`LAN_CIDRS`……),重装 / 升级都不会覆盖 |
- 改配置后执行 `sudo systemctl restart ros-dpi`;看日志用 `journalctl -u ros-dpi -f`。
- 安装时可以用 `ROS_DPI_PORT`(默认 8080)和 `ROS_DPI_DIR`(默认 `/opt/ros-dpi`)指定端口和目录,例如 `curl … | sudo ROS_DPI_PORT=9000 bash`。
- 服务以 `ros-dpi` 账号运行,不是 root,只能写 `bin/` 和 `data/`。**0.6.23 及以前装的是 root 服务**,重新执行一次安装命令即可迁移,数据和配置原样保留。
#### 方式二:Docker Compose
**还没有 MySQL**:用 [`docker-compose.with-mysql.yml`](docker-compose.with-mysql.yml),会一起启动 MySQL 8.4 并自动建库、写好连接,网页上不会出现数据库设置。
```bash
mkdir ros-dpi && cd ros-dpi
curl -o docker-compose.yml https://raw.githubusercontent.com/suyijun8182/ros-dpi/main/docker-compose.with-mysql.yml
cat > .env <<'EOF'
MYSQL_PASSWORD=改成强密码
MYSQL_ROOT_PASSWORD=改成另一个强密码
EOF
# 把 docker-compose.yml 里的 LAN_CIDRS 改成你的内网网段
docker compose up -d
```
**已有 MySQL**:先建库和账号(命令行用 `mysql -u root -p`,也可以在 phpMyAdmin、Navicat、NAS 的数据库管理页里操作;表会自动创建):
```sql
CREATE DATABASE rosdpi CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'rosdpi'@'%' IDENTIFIED BY 'your-password';
GRANT ALL PRIVILEGES ON rosdpi.* TO 'rosdpi'@'%';
FLUSH PRIVILEGES;
```
再保存下面的 `docker-compose.yml`(或 `curl -O https://raw.githubusercontent.com/suyijun8182/ros-dpi/main/docker-compose.yml`),改好 `LAN_CIDRS` 后 `docker compose up -d`。首次打开网页时在安装向导里填 MySQL 信息。
```yaml
services:
ros-dpi:
image: suyijun8182/ros-dpi:latest # 也可用 ghcr.io/suyijun8182/ros-dpi:latest
container_name: ros-dpi
restart: unless-stopped
ports:
- "8080:8080/tcp" # Web 界面
- "2055:2055/udp" # IPFIX:RouterOS Traffic Flow 发到这里
- "37008:37008/udp" # TZSP:RouterOS 镜像的 DNS 应答和 HTTPS / QUIC 握手包发到这里
environment:
TZ: Asia/Shanghai
LAN_CIDRS: 192.168.88.0/24 # 必改:内网网段,逗号分隔
ADMIN_PASSWORD: "" # 留空则首次打开网页时设置
volumes:
- ./data:/app/data # 保存数据库连接配置和识别库,务必保留
```
镜像发布在 Docker Hub(`suyijun8182/ros-dpi`)和 GHCR(`ghcr.io/suyijun8182/ros-dpi`),每次发版都有版本号标签。群晖 / 飞牛 / 威联通在 Container Manager 里新建“项目”,粘贴 compose 即可。
### 首次使用
打开 `http://<主机IP>:8080`:
1. **连接数据库**(只有 Docker 且没用自带 MySQL 时才有这一步):填 MySQL 信息,可以先测试连接。MySQL 和 ROS-DPI 在同一台机器上时填局域网 IP,**不能填 127.0.0.1**。
2. **设置管理员密码**,设置后自动登录。
3. 到 **系统设置 → 接入与自检** 复制 RouterOS 命令去路由器执行。自检会检查规则是否齐全、端口是否写错、FastTrack 是否挡住了握手包。
只读账号(只能看、不能改)在 **系统设置 → 用户管理** 添加。
| 现象 | 处理 |
|---|---|
| 连不上 MySQL | 地址 / 端口不通,或填了 `127.0.0.1`;MySQL 要允许远程连接(`bind-address`),防火墙放行 3306 |
| 账号或密码错误 | 检查密码;账号的主机要是 `%` |
| 重启后显示“正在连接数据库” | MySQL 还没就绪,会自动重试,不用重填 |
| 要换数据库 | 删掉 `data/db_config.json` 后重启,重新出现向导 |
| 忘记管理员密码 | 让其他管理员在 用户管理 里重置;都忘了就执行 `DELETE FROM rosdpi.users;` 后重新设置(统计数据不受影响) |
| 概览一直没数据 | 看 接入与自检 的结果;确认路由器能访问本机的 udp/2055、udp/37008 |
### RouterOS 配置
把 `192.168.88.10` 换成 ROS-DPI 主机的 IP。**系统设置 → 接入与自检** 里有替换好地址的版本,可以直接复制。
```routeros
# 1. 流量统计(IPFIX)
/ip traffic-flow set enabled=yes interfaces=all active-flow-timeout=1m inactive-flow-timeout=15s
/ip traffic-flow target add dst-address=192.168.88.10 port=2055 version=ipfix
# 2. DNS 嗅探:远端 IP 是哪个域名解析出来的
/ip firewall mangle add chain=output protocol=udp src-port=53 action=sniff-tzsp sniff-target=192.168.88.10 sniff-target-port=37008 comment=ROSDPI-DNS
/ip firewall mangle add chain=forward protocol=udp src-port=53 action=sniff-tzsp sniff-target=192.168.88.10 sniff-target-port=37008 comment=ROSDPI-DNS
# 3. SNI 识别(推荐):只镜像每条 HTTPS / QUIC 连接开头的握手,终端用加密 DNS 也能识别
/ip firewall mangle add chain=forward protocol=tcp dst-port=443 connection-bytes=0-4000 action=sniff-tzsp sniff-target=192.168.88.10 sniff-target-port=37008 comment=ROSDPI-SNI
/ip firewall mangle add chain=forward protocol=udp dst-port=443 connection-bytes=0-4000 action=sniff-tzsp sniff-target=192.168.88.10 sniff-target-port=37008 comment=ROSDPI-SNI
# 4. 开了 IPv6 时再加一份(国内宽带大多有 IPv6,视频、下载优先走 IPv6)
/ipv6 firewall mangle add chain=output protocol=udp src-port=53 action=sniff-tzsp sniff-target=192.168.88.10 sniff-target-port=37008 comment=ROSDPI-DNS
/ipv6 firewall mangle add chain=forward protocol=udp src-port=53 action=sniff-tzsp sniff-target=192.168.88.10 sniff-target-port=37008 comment=ROSDPI-DNS
/ipv6 firewall mangle add chain=forward protocol=tcp dst-port=443 connection-bytes=0-4000 action=sniff-tzsp sniff-target=192.168.88.10 sniff-target-port=37008 comment=ROSDPI-SNI
/ipv6 firewall mangle add chain=forward protocol=udp dst-port=443 connection-bytes=0-4000 action=sniff-tzsp sniff-target=192.168.88.10 sniff-target-port=37008 comment=ROSDPI-SNI
# 5. 开了 FastTrack 时:每条连接的前 10KB 不走 FastTrack,否则握手包镜像不到
/ip firewall filter set [find action=fasttrack-connection] connection-bytes=10000-0
/ipv6 firewall filter set [find action=fasttrack-connection] connection-bytes=10000-0
# 6. 只读 API 账号(可选):主机名、IP↔MAC、WAN 口速率、DHCP 地址池
/user group add name=rosdpi policy=read,rest-api,api,!write,!policy,!sensitive
/user add name=rosdpi group=rosdpi password=<强密码> address=192.168.88.10
```
第 6 步的地址和账号填在 **系统设置 → 接入与自检**,或者用环境变量 `ROS_URL` / `ROS_USER` / `ROS_PASSWORD`。SNI 的开销、双层 NAT、排错等见 [docs/RouterOS配置.md](docs/RouterOS配置.md)。
### 应用是怎么识别出来的
```
RouterOS ──IPFIX udp/2055 ──▶ 每条连接:哪台终端、远端 IP、端口、字节数
RouterOS ──TZSP udp/37008──▶ DNS 应答:远端 IP ← 哪个域名
HTTPS / QUIC 握手:这条连接的目标域名(SNI)
RouterOS ◀── REST 只读(可选)── 主机名、IP↔MAC、WAN 速率
↓
域名 / IP → 应用 → 按分钟、小时、天汇总入库
```
**先确定域名**,由准到粗:这条连接的 SNI → DNS 嗅探 → 这台终端最近的 DNS / SNI 记录 → 全网 IP→域名 表。终端按 MAC 区分,IP 变了不影响统计。
**再把域名 / IP 对应到应用**,四层依次查:
1. **公开识别库**(自动更新):内置手工库、[v2fly](https://github.com/v2fly/domain-list-community)、[blackmatrix7](https://github.com/blackmatrix7/ios_rule_script)、Hagezi / UT1 分类列表、ASN、国内域名列表、云厂商官方 IP 段。上游有新目录时,先下载再用现有识别库投票决定它属于哪个应用,不用人工整理。
2. **本地自学习库**:你的网络里实测到的「IP → 应用」「域名 → 应用」会长期记住,DNS 缓存过期、用 DoH 的终端、直接连 IP 的流量都能按记忆归类,每小时重建一次。
3. **社区共享库**:各个部署实例匿名汇总的识别结论,本地查不到时兜底(见下节)。
4. 都查不到,才归到「HTTPS」「国内网络」这类笼统分类。
识别库更新后,或者每天过了零点,会自动把以前的历史流量按新规则重算一遍(只会从笼统改成具体,不会反过来);**识别规则** 页也可以手动重算。
**局限**:没开 SNI、只开 DNS 嗅探时,用 DoH / DoT 的终端只能识别成 HTTPS / QUIC;启用了 ECH 的少数网站只能看到外层的公共域名。
### 社区共享库
每个部署实例学到的东西不一样,汇总起来识别会更准。项目自带一个社区主站,不依赖第三方存储。
- **上报什么**:远端 IP / 域名、应用 ID、票数。**不含**本机终端、访问记录、自定义应用;私网地址会被剔除。
- **怎么防作假**:主站按**来源网络**计票(IPv4 /24、IPv6 /56,只存加盐哈希),至少 2 个不同网络的实例投给同一个应用、而且票数领先才会发布;一个人开再多实例也只算一票。每个实例有主站发的令牌,别人冒充不了。
- **不需要公网 IP**:各实例都是主动连主站,能上外网就行。社区库缓存在本地,主站暂时连不上也照常用。
- **在哪里看、怎么关**:**系统设置 → 外部数据源 → 社区共享库**,能看到连接状态、社区库规模、全球已部署 / 在线 / 离线数量。默认打开,关掉后不再上报,也不再使用社区库。
- **自建私有社区**:[`deploy/hub/`](deploy/hub/) 有 VPS 部署模板(Docker + Caddy 自动 HTTPS;或已有 Lucky + MySQL 的版本),步骤见 [docs/社区主站部署.md](docs/社区主站部署.md)。各实例在上面的页面填写“社区主站地址”即可。
### 外部数据源(可选)
用了旁路代理或自建 DNS 时,可以把它们接进来补全识别。都在 **系统设置 → 外部数据源** 填写,不填就不启用。
| 来源 | 补上什么 |
|---|---|
| **sing-box**(Clash API) | 走代理的流量:哪台终端、哪个域名、多少字节,以及策略组和出口节点 |
| **MosDNS**(审计 API) | 每台终端查了哪个域名、解析出哪些 IP |
| **AdGuard Home** | 双层 NAT(RouterOS 下面还挂了一台路由器)时,下级终端的 DNS 查询路由器看不到,从 AdGuard Home 的查询日志补上 |
### 升级
- **Linux 一键安装**:**系统设置 → 版本更新** 出现新版本时点「一键更新」,程序会下载新版、校验 SHA256、替换后自动重启,旧版本留一份 `ros-dpi.bak`。也可以重新执行安装命令。
- **Docker**:`docker compose pull && docker compose up -d`。想固定版本,就把镜像标签从 `latest` 改成具体版本号(如 `0.6.24`)。
每个版本改了什么见 [CHANGELOG.md](CHANGELOG.md)。
### 接入 Home Assistant 等外部程序
在 **系统设置 → 用户管理 → API 令牌** 新建一个令牌(只读、不过期,删除后立即失效;原文只在创建时显示一次),请求时放在请求头里:
```
Authorization: Bearer rdp_xxxxxxxx
GET http://<ROS-DPI 地址>:8080/api/ha/summary?limit=30
```
`/api/ha/summary` 一次返回首页常用的数:今日 / 本周 / 本月流量、实时速率、在线终端数、识别率、健康指数、应用大类构成、Top 应用,以及按今日流量排序的前 `limit` 台终端(名称、IP、MAC、类型、实时速率、今日 / 本周 / 本月流量、今日前 3 个应用)。字节数单位是 bytes,速率是 bytes/s。令牌也能读其它只读接口(等同普通用户),但不能修改任何设置。
Home Assistant 里用 RESTful 传感器读取即可,例如:
```yaml
rest:
- resource: http://192.168.88.10:8080/api/ha/summary
headers:
Authorization: !secret rosdpi_token # secrets.yaml 里写 "Bearer rdp_..."
scan_interval: 60
sensor:
- name: rosdpi_summary
value_template: "{{ value_json.online }}"
json_attributes: [totals, rate, hosts, groups, top_apps, identified, health]
```
### 安全建议
- 内网有不可信设备时,设 `COLLECTOR_ALLOWED=<路由器IP>`,采集端口只接收路由器发来的包,防止别人伪造流量或 DNS 记录。
- 不要把 8080 端口直接暴露到公网;需要外网访问时放在反向代理后面并启用 HTTPS。从公网打开安装向导时,需要填写启动日志里打印的安装码。
- RouterOS 账号只给只读权限(上面第 6 步的用户组);管控页生成的命令要你自己检查后再执行。
### 环境变量
Linux 一键安装写在 `/etc/ros-dpi.env`,Docker 写在 compose 的 `environment`。标了“网页可改”的,网页上的设置优先。
**基础**
| 变量 | 默认值 | 说明 |
|---|---|---|
| `DATABASE_URL` | 空 | 留空则用网页安装向导(Linux 一键安装默认 SQLite)。MySQL 格式:`mysql+pymysql://user:pass@host:3306/rosdpi?charset=utf8mb4` |
| `LAN_CIDRS` | 私网三段 + `fd00::/8` | 内网网段,逗号分隔(网页可改) |
| `ADMIN_PASSWORD` | 空 | 首次启动时创建 `admin` 的密码;留空则在网页上设置 |
| `TZ` | `Asia/Shanghai` | 统计用的时区;每周从周一开始 |
| `HTTP_PORT` / `IPFIX_PORT` / `TZSP_PORT` | 8080 / 2055 / 37008 | 端口 |
| `DATA_DIR` | `./data` | 数据目录;Docker 里是 `/app/data` |
| `SECRET_KEY` | 自动生成 | 登录令牌的签名密钥 |
**RouterOS 与采集**
| 变量 | 默认值 | 说明 |
|---|---|---|
| `ROS_URL` / `ROS_USER` / `ROS_PASSWORD` | 空 | RouterOS REST 地址和只读账号(网页可改) |
| `ROS_VERIFY_TLS` | `false` | 是否校验 RouterOS 的证书 |
| `ROS_POLL_SECONDS` / `ROS_RATE_SECONDS` | 30 / 5 | 完整轮询间隔 / WAN 口速率采样间隔(秒) |
| `WAN_INTERFACE` | 自动 | WAN 接口名,默认按默认路由判断 |
| `COLLECTOR_ALLOWED` | 空 | 采集端口只收这些地址的包(一般填路由器 IP);留空不限制 |
**外部数据源**(都可在网页填写)
| 变量 | 默认值 | 说明 |
|---|---|---|
| `SINGBOX_URL` / `SINGBOX_SECRET` | 空 | sing-box 的 Clash API 地址和密钥 |
| `PROXY_IPS` | 空 | 旁路代理自己的 IP;留空时取 `SINGBOX_URL` 里的地址 |
| `FAKEIP_CIDRS` | `198.18.0.0/15,fc00::/18` | FakeIP 地址段 |
| `MOSDNS_URL` | 空 | MosDNS 审计 API 地址 |
| `ADGUARD_URL` / `ADGUARD_USERNAME` / `ADGUARD_PASSWORD` | 空 | AdGuard Home 地址和账号 |
**识别库与社区**
| 变量 | 默认值 | 说明 |
|---|---|---|
| `ASN_DB` / `CHINA_DB` / `IPLIST_DB` / `BM7_DB` / `HAGEZI_DB` / `UT1_DB` / `OUI_DB` | `true` | 各识别库是否自动下载;机器上不了外网时设 `false` |
| `SHARE_ENABLED` | `true` | 社区共享库总开关(网页可改) |
| `SHARE_HUB_URL` | `https://rosdpi.vviip.dpdns.org:8443` | 社区主站地址,一般不用改(网页可改) |
| `SHARE_VERIFY_TLS` | `true` | 校验主站证书;私有社区用自签证书时才设 `false` |
| `SHARE_HUB` | `false` | 把本实例当作社区主站(只有主站部署才设 `true`) |
| `SHARE_TRUSTED_PROXIES` | 空 | 主站用:只信任这些反向代理传来的 `X-Forwarded-For`;留空 = 内网和本机地址 |
**数据保留**(网页可改)
| 变量 | 默认值(天) |
|---|---|
| `RETENTION_MINUTE_DAYS` / `RETENTION_HOUR_DAYS` / `RETENTION_DAY_DAYS` | 2 / 45 / 730(分钟、小时、每日汇总) |
| `RETENTION_DOMAIN_DAYS` / `RETENTION_LOG_DAYS` / `RETENTION_EVENT_DAYS` | 31 / 30 / 180(域名明细、访问日志、审计事件) |
### 开发
技术栈:后端 FastAPI + SQLAlchemy(MySQL / SQLite),前端 Vue 3 + Vite + ECharts。
```bash
# 后端
cd backend && python -m venv .venv && .venv/bin/pip install -r requirements.txt pytest
DATABASE_URL=sqlite:///./dev.db .venv/bin/uvicorn app.main:app --port 8080
.venv/bin/python -m pytest
# 前端:http://localhost:5173,/api 代理到 8080
cd frontend && npm install && npm run dev
# 灌一些模拟流量
backend/.venv/bin/python scripts/simulate.py --minutes 5
# 打包 Linux 单文件二进制(产物 dist/ros-dpi)
cd frontend && npm run build && cd ..
pip install -r backend/requirements.txt pyinstaller && pyinstaller ros-dpi.spec
```
补充应用规则:改 [backend/app/classify/apps.yaml](backend/app/classify/apps.yaml)(手工维护,优先级最高,欢迎 PR)。`apps_v2fly.yaml` 由 `scripts/import_v2fly.py` 生成,不要手改。
---
## English
**ROS-DPI** is a traffic, application and access-audit dashboard for networks behind a MikroTik RouterOS router. RouterOS exports flow records (IPFIX) and mirrors DNS answers and TLS/QUIC handshakes (TZSP); ROS-DPI turns them into per-device and per-person traffic by application, with access logs and alert rules. No inline box, no router replacement.
### Features
- **Overview**: router status, network health score with fixes, online hosts, DHCP pool, per-WAN status and rate, traffic mix, live and historical traffic, top apps and hosts.
- **Hosts**: device type detection (phone, PC, TV, camera…), IP / MAC / hostname / owner, online time, live rate, daily / weekly / monthly totals, per-device app mix, domains, access log and timeline; CSV export.
- **Applications**: three-level drill-down (group → category → app) — 17 groups, 50 categories, ~2,970 apps.
- **Flows**: live connections, top destinations, world flow map, proxy routing per sing-box policy group and outbound.
- **People**: group devices per person. *Home* scenario focuses on children's screen time and habits; *Business* on productivity and compliance.
- **Audit**: access log, rules (app, domain, time window, traffic threshold, large upload, rogue router, new device), 14 notification channels, daily / weekly reports, operation log.
- **Control commands**: generates RouterOS commands (block an app, scheduled cut-off, rate limit) with undo commands. It never changes the router by itself.
- Chinese and English UI, light and dark themes, mobile friendly.
### Quick start
**Linux (single binary, SQLite by default, one-click updates from the UI):**
```bash
curl -fsSL https://raw.githubusercontent.com/suyijun8182/ros-dpi/main/install.sh | sudo bash
```
It verifies the download's SHA256, creates a dedicated `ros-dpi` user and starts a systemd service on port 8080. The binary lives in `/opt/ros-dpi/bin`, data in `/opt/ros-dpi/data`, and your settings in `/etc/ros-dpi.env` (kept across reinstalls). Installs from 0.6.23 or earlier ran as root — re-run the command to migrate; data and settings are kept.
**Docker, with a bundled MySQL:**
```bash
curl -o docker-compose.yml https://raw.githubusercontent.com/suyijun8182/ros-dpi/main/docker-compose.with-mysql.yml
printf 'MYSQL_PASSWORD=change-me\nMYSQL_ROOT_PASSWORD=change-me-too\n' > .env
# set LAN_CIDRS in docker-compose.yml, then:
docker compose up -d
```
**Docker, with your own MySQL:** create the database and user with the SQL in the Chinese section, use [`docker-compose.yml`](docker-compose.yml), and enter the MySQL details in the setup wizard on first launch (use the host's LAN IP, not `127.0.0.1`). Images: `suyijun8182/ros-dpi` on Docker Hub and `ghcr.io/suyijun8182/ros-dpi`, amd64 / arm64, tagged per release.
Then open `http://<host>:8080`, set the admin password, and copy the RouterOS commands from **Settings → Setup & check** (also listed in the [RouterOS section](#routeros-配置) above and in [docs/RouterOS配置.md](docs/RouterOS配置.md)). The self-check there flags missing rules, wrong ports and FastTrack bypassing the sniffer.
### How apps are identified
The domain of each connection comes from its TLS/QUIC SNI, then sniffed DNS answers, then the host's recent lookups, then a network-wide IP→domain table. Domains and IPs map to apps through four layers: (1) public libraries, updated automatically — built-in rules, v2fly, blackmatrix7, Hagezi / UT1 categories, ASN, China domain lists and official cloud IP ranges; (2) a **local self-learning library** of mappings observed on your own network; (3) the **community library**; (4) generic fallbacks such as "HTTPS". When the libraries change, past traffic is reclassified automatically.
With DNS sniffing alone, DoH/DoT clients show up as generic HTTPS/QUIC — enable the SNI rules to cover them. Sites using ECH only expose their outer public name.
### Community library
Instances share anonymous votes (remote IP / domain → app) through the built-in community hub — no hosts, no access logs and no private addresses are sent. A mapping is published only when instances on at least two different networks (IPv4 /24, IPv6 /56, stored as salted hashes) agree, so one person running many instances still counts once. Instances need no public IP. Toggle it and see its status under **Settings → External sources**; to run a private hub, see [`deploy/hub/`](deploy/hub/) and [docs/社区主站部署.md](docs/社区主站部署.md).
### Optional sources
sing-box (Clash API) adds per-host, per-domain byte counts for proxied traffic; MosDNS adds per-host DNS lookups; AdGuard Home fills in domains behind a second NAT. Configure them under **Settings → External sources**.
### Home Assistant and other programs
Create a read-only API token under **Settings → Users → API tokens** (it never expires and stops working when deleted; the token is shown only once). Send it as `Authorization: Bearer rdp_…`. `GET /api/ha/summary?limit=30` returns today / week / month totals, live rate, online hosts, identification rate, health score, app groups, top apps and the top hosts by today's traffic with their top three apps (bytes and bytes/s). See the Chinese section for a Home Assistant RESTful sensor example.
### Security notes
Set `COLLECTOR_ALLOWED=<router IP>` if untrusted devices share the LAN. Don't expose port 8080 to the internet directly — put it behind an HTTPS reverse proxy. Give RouterOS a read-only user only.
All environment variables are listed in the [Chinese section](#环境变量).
## License
[MIT](LICENSE)
`backend/app/classify/apps_v2fly.yaml` is generated from [v2fly/domain-list-community](https://github.com/v2fly/domain-list-community) (MIT License, Copyright (c) 2019-present V2Fly Community). World map data from [Natural Earth](https://www.naturalearthdata.com/) (public domain).
These data sources are **not** included in this repository or the Docker image; ROS-DPI downloads them at runtime into its data directory, and each keeps its own license / 以下数据不在仓库和镜像里,运行时下载,各自沿用原许可证:
- [blackmatrix7/ios_rule_script](https://github.com/blackmatrix7/ios_rule_script) — GPL-2.0 (`BM7_DB`). `backend/app/classify/bm7_map.yaml` only maps its directory names to ROS-DPI apps.
- [Hagezi DNS blocklists](https://github.com/hagezi/dns-blocklists) — GPL-3.0 (`HAGEZI_DB`).
- [UT1 blacklists](https://dsi.ut-capitole.fr/blacklists/), Université Toulouse Capitole, via the [olbat/ut1-blacklists](https://github.com/olbat/ut1-blacklists) mirror — CC BY-SA 4.0 (`UT1_DB`).
- [dnsmasq-china-list](https://github.com/felixonmars/dnsmasq-china-list) — WTFPL; [chnroutes2](https://github.com/misakaio/chnroutes2) — CC BY-SA 4.0; [iptoasn](https://iptoasn.com) — PDDL; IEEE OUI; official Google / Cloudflare / AWS / GitHub IP range feeds.