Ehsan200/em-wall
GitHub: Ehsan200/em-wall
macOS 平台的 DNS 层防火墙,可按域名阻断、放行或将流量路由到指定的 VPN 隧道和代理出口。
Stars: 6 | Forks: 0
# em-wall

一款工作在 DNS 层的 macOS 防火墙。你的机器上发生的每一次域名查询,在建立任何连接之前都会经过 em-wall —— 由规则决定将其阻断、放行,还是通过特定的网络接口、SOCKS/HTTP 代理或内嵌的 Xray outbound 进行路由。
- 阻断域名和通配符(`*.example.com` 会匹配根域名及其所有子域名)。
- 将特定域名路由到所选目标出去:原生接口(`utun3`)、已配置的上游代理(`proxy:work`),或是内嵌的 Xray outbound(`xray:home`)。多目标绑定(`xray:home,backup`)会回退到第一个可达的成员,并优先按最低延迟排序。
- 精心策划的域名分组(OpenAI、Anthropic、Google、Meta、X、Telegram、JetBrains 等),支持一键批量规则,以及针对整个分组的批量启用/禁用/删除。
- 可选的开关,用于阻断加密的 DNS(DoH/DoT),否则这些流量将完全绕过防火墙。
- 实时记录每一次 DNS 查询及其应用的处理决策。
- 内嵌 [xray-core](https://github.com/XTLS/Xray-core):管理 outbound、导入 VLESS/VMess/Trojan 链接、在 Monaco 编辑器中编辑原始 JSON、针对每个条目进行延迟测试,以及设有上限的滚动日志。
- **订阅**:添加 base64 分享链接的订阅 URL;em-wall 会按计划获取它们并存入节点池(直连,当 URL 被审查时通过一个可用节点作为回退),同时支持针对单个节点的禁用,且该状态在刷新后依然保持。
- **主配置拨号器 (Master-config dialer)**:将任何 Xray 条目转变为“主节点”,其自身的传输通道将通过所选节点/订阅集合中*最快*的一个进行隧道转发 —— 这是一个实时的 `leastPing` 均衡器,它会重新挑选最优节点并吸收节点列表的变更,**而无需重启 Xray**。
## 工作原理
两个二进制程序通过 Unix socket 进行通信:
```
em-wall.app (Wails + Vue, user-space)
│
│ /var/run/em-wall.sock (newline-framed JSON-RPC)
│
em-walld (LaunchDaemon, root)
├─ core/rules GORM + SQLite rule store
├─ core/decision rule engine, in-memory cache
├─ core/dnsproxy UDP + TCP server on 127.0.0.1:53
├─ core/routing per-host route installer via /sbin/route; utun owner labels (lsof)
├─ core/proxy SOCKS/HTTP upstream proxies (proxy:NAME target)
├─ core/proxytun userspace TUN that funnels matched traffic into a proxy
├─ core/xray embedded xray-core lifecycle + config builder
└─ core/pfctl pf anchor for DoH/DoT blocking
```
守护进程管理一切 —— UI 只是一个轻量级客户端,通过 IPC 转发调用。每次进行 DNS 查询时,引擎会找到最具体的匹配规则(在相同深度下,精确匹配优先于通配符)并进行分发处理:
- **阻断 (block)** → 返回带有负缓存 TTL 的 NXDOMAIN。
- **放行 (allow)** 且无接口 → 正常向上游转发。
- **放行/路由 (allow/route)** 且指定 `Interface = "utunN"` → 转发请求,然后针对解析结果中的每一个 A/AAAA 记录安装 `route -host -interface utunN` 路由。
- **路由 (route)** 且指定 `Interface = "proxy:NAME"` 或 `"xray:NAME"` → 安装指向 em-wall 用户态 TUN 的路由,该 TUN 会将匹配的流量转发到已配置的 SOCKS/HTTP 代理或 Xray outbound 中。以逗号分隔的列表(`proxy:work,backup`)会回退到第一个可达的成员,并优先按最低延迟排序。
### 订阅与主配置拨号器
**订阅**是一个返回 base64 分享链接列表的远程 URL。守护进程会按计划(默认 12 小时,外加手动触发和启动时触发)拉取该 URL,将链接解析为节点池,并对它们进行指纹记录,从而确保被手动禁用的节点在刷新后依然保持禁用状态。订阅本身永远不会作为直接的规则目标。
**主节点**是一个普通的 Xray 条目,带有一个 `Dialer` 字段 —— 这是一个由 `xraysub:NAME` / `xray:NAME` / `proxy:NAME` 引用组成的列表。每一个被引用的节点(每个订阅中的所有活跃节点,加上任何独立的条目/代理)都会被投入到同一个 Xray `leastPing` 均衡器中,而该主节点的 outbound 则通过 `streamSettings.sockopt.dialerProxy` 与其相连。因此,主节点自身的服务器连接将通过当前最快的节点进行隧道转发 —— 这在主节点的服务器本身只能通过可用的中继节点访问时非常有用。
“最快”的选择完全由 Xray 的均衡器和 observatory 内部处理,因此它会重新挑选最优节点,并能应对连接过程中的节点失效情况。节点更迭(刷新、启用/禁用、上限变更)会**通过运行中的 Xray 进程的 gRPC API**(`ado`/`rmo`)进行实时应用,绝不通过重启 —— 这样长期连接就不会被中断。规则可以通过常规方式(即使用 `xray:MASTER`)触达主节点。
## 仓库结构
```
core/ Go library — fully testable without root
rules/ SQLite store + wildcard matcher
decision/ rule evaluation engine
dnsproxy/ DNS server + multi-upstream forwarder
routing/ per-host route installer + utun owner labels
proxy/ SOCKS/HTTP upstream proxy registry
proxytun/ userspace TUN that funnels matched flows into a proxy
xray/ embedded xray-core lifecycle + JSON config builder
pfctl/ pf anchor manager
ipc/ Unix-socket JSON-RPC (protocol.go is the wire contract)
groups/ curated domain group definitions
version/ build-time version stamp
daemon/ em-walld — wires core/* together, runs as LaunchDaemon
app/ Wails + Vue 3 UI (separate Go module via go.work)
app.go thin IPC client; every method forwards one RPC call
internal/installer/ in-app install / uninstall logic
frontend/ Vite + Vue 3 + TypeScript (Rules / Logs / Network / Proxies / Xray / Settings)
assets/ source assets (app icon, screenshots)
launchd/ LaunchDaemon plist template
```
## 开发
**前置要求:** Go 1.21+,Node 18+,以及位于 `~/go/bin/wails` 的 [Wails v2](https://wails.io)。
```
make test # core unit tests — no root, no port 53 needed
make run-daemon # local daemon on :5353 with ./tmp/dev.db (no root)
make run-app # wails dev UI — run in a second terminal
```
`make run-daemon` 会针对本地的数据库和 socket 启动 em-walld,这样 UI 就可以在不干预系统 DNS 的情况下连接。安装面板会显示 `not packaged` —— 这在开发模式下是预期行为;规则/日志/网络标签页均可正常工作。
### 添加功能
IPC 协议是唯一的真实来源。添加一个方法:
1. 在 [core/ipc/protocol.go](core/ipc/protocol.go) 中定义 DTO 和方法常量。
2. 在 `daemon/main.go` → `registerHandlers` 中注册处理程序。
3. 在 `app/app.go` 中暴露一个调用 `a.call(ipc.MethodXxx, ...)` 的方法。
4. 在 `app/` 目录下运行 `wails generate module` 以重新生成 TypeScript 绑定。
### 构建可分发的应用程序
```
make app-bundle # builds app/build/bin/em-wall.app (fully self-contained)
```
Makefile 在打包前总是会从源码重新构建守护进程,以确保内嵌的二进制文件绝不会过时。
## 安装
打开 `em-wall.app`。首次启动时会出现一个**安装**界面 —— 点击它会触发标准的 macOS 管理员授权提示,并将守护进程、LaunchDaemon plist 和 pf anchor 写入到各自的系统路径中。安装完成后,从**设置**标签页中激活 DNS 劫持。
## 卸载
在运行的应用程序内点击 **Settings → Uninstall em-wall**。该流程会首先停用 DNS 劫持(从已保存的备份中恢复你原始的各服务 DNS),移除守护进程及所有系统文件,并运行一次安全扫描,以确保没有任何网络服务会继续指向 `127.0.0.1`。
标签:DNS防火墙, EVTX分析, Vue, Xray, 代理订阅, 安全合规, 文件系统扫描, 日志审计, 网络代理, 网络路由