# mac-route

一个用 [ratatui](https://ratatui.rs) 实现的 macOS 静态路由 TUI 管理器。
通过 macOS 自带的 `networksetup` 命令查看与修改网卡的「附加路由（additional routes）」，
写操作经 `osascript` 弹出系统级图形授权对话框提权，**TUI 进程本身不需要 root**。

## 功能

- 扫描所有网络服务（网卡），聚合**三类来源**的路由：
  - `manual` 手动附加路由（`networksetup -getadditionalroutes`）—— **可在 TUI 内增删**
  - `default` 默认路由（`netstat -rn` 的 default，决定「其他流量走哪」）—— 只读展示
  - `direct` 直连网段（每张网卡 IP/掩码对应的本地网段）—— 只读展示
- 左侧：IP 段列表，**按优先级低→高排序**，每条标注来源标记 `[m]`/`[d]`/`[.]`。
- 右侧：选中 IP 段的全部命中（来源 + 网卡 + 网关 + 优先级/禁用标记）。
- 添加路由：输入 CIDR（如 `10.0.0.0/8`）→ 选择目标网卡 → 提权写入（成为 manual）。
- 删除路由：仅在 manual 命中上可用 → 确认 → 提权移除。
- 自动过滤路由噪声：loopback / link-local / multicast / broadcast / `/32` 主机条目、
  以及 utun/lo0 等非服务网卡上的条目均不展示。

## 运行

```bash
cargo run
```

需要：macOS + 已安装 `networksetup`（系统自带）+ Rust 工具链。

## 快捷键

| 按键        | 作用                                  |
| ----------- | ------------------------------------- |
| `↑`/`k`     | 上一个 IP 段                          |
| `↓`/`j`     | 下一个 IP 段                          |
| `←`/`h`     | 上一个绑定网卡（右侧）                |
| `→`/`l`     | 下一个绑定网卡（右侧）                |
| `r`         | 刷新（重新读取 networksetup）         |
| `a`         | 添加路由                              |
| `d`         | 删除当前选中的绑定                    |
| `?`         | 帮助                                  |
| `q`/`Esc`   | 退出                                  |

添加路由弹窗内：`Tab` 在「CIDR 输入框」和「网卡列表」之间切换焦点，`Enter` 确认。

## 提权说明

`networksetup -setadditionalroutes` 写路由需要 root。本程序在写操作时通过：

```text
osascript -e 'do shell script "networksetup ..." with administrator privileges'
```

弹出 macOS 系统级用户名/密码授权对话框（独立于终端，不干扰 TUI 屏幕）。
取消授权或密码错误会以错误弹窗展示，并附上原始命令文本供手动重试。

读操作（`-listnetworkserviceorder` / `-getadditionalroutes`）不需要提权。

## 实现要点

- `networksetup -setadditionalroutes` 是**全量覆盖**语义：传入一个服务的全部路由。
  因此增/删内部都是「读现有全部路由 → 加/减一条 → 整体回写」，封装在
  `net::networksetup::set_routes`，对上层只暴露「增/删一条」的语义。
- 内部统一用 `{dest, prefix_len}`（CIDR）表示路由，调用命令前再做 netmask↔prefix 转换。

## 项目结构

```
src/
├── main.rs              # 入口：terminal setup/restore (Drop guard)、事件循环
├── app.rs               # App 状态、Mode 枚举、update reducer
├── event.rs             # crossterm 键盘事件循环
├── ui/
│   ├── mod.rs           # draw：整体双面板布局 + 底栏
│   ├── sidebar.rs       # 左侧 IP 段列表
│   ├── detail.rs        # 右侧：选中 IP 段绑定的网卡列表
│   └── popup.rs         # 添加路由 / 删除确认 / 帮助 / 错误 弹窗
├── net/
│   ├── mod.rs           # 重新导出
│   ├── service.rs       # NetworkService + listnetworkserviceorder 解析
│   ├── route.rs         # Route + RouteSource + CIDR/netmask 转换
│   ├── getinfo.rs       # networksetup -getinfo 解析（IP/掩码/网关）
│   ├── netstat.rs       # netstat -rn 解析（默认路由 + 直连网段，去噪）
│   └── networksetup.rs  # list_services / get_routes / set_routes / netstat_routes / run_elevated
├── macros.rs            # key! / cmd! / log! 宏
└── error.rs             # AppError (thiserror)
```

### 宏

- `key!`：压缩 crossterm `KeyEvent` 的匹配，如 `key!(ev, 'j')`、`key!(ev, Esc)`、`key!(ev, ctrl => 'c')`。
- `cmd!`：构造 `Command` 并压入参数，如 `cmd!(networksetup -getadditionalroutes service)`，自动把 `-flag` 拼成字符串。
- `log!`：向应用内部日志缓冲写入（TUI 里不能用 `println!`）。

## 测试

```bash
cargo test
```

覆盖：CIDR 解析与 netmask 互转、`listnetworkserviceorder` / `getadditionalroutes` 输出解析（含真实样例 fixture）、AppleScript/shell 转义、宏行为。
