Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 159 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,178 @@
# Magicdian's static route helper
# StaticRouteHelper

<p align="center">
<img src="./StaticRouter/Assets.xcassets/AppIcon.appiconset/icon_256x256.png" width="120" alt="StaticRouteHelper icon">
</p>

<p align="center">
A macOS static route manager built with SwiftUI + privileged helper (XPC).
</p>

<p align="center">
<a href="https://github.com/jdjingdian/StaticRouteHelper/releases"><img src="https://img.shields.io/github/v/release/jdjingdian/StaticRouteHelper?display_name=tag&sort=semver" alt="Latest Release"></a>
<a href="https://github.com/jdjingdian/StaticRouteHelper/actions/workflows/release.yml"><img src="https://github.com/jdjingdian/StaticRouteHelper/actions/workflows/release.yml/badge.svg" alt="Release Workflow"></a>
<img src="https://img.shields.io/badge/macOS-12%2B-111111?logo=apple" alt="macOS 12+">
<a href="./LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License"></a>
</p>

English | [简体中文](./README_CN.md)

## Table of Contents

- [Overview](#overview)
- [Feature Highlights](#feature-highlights)
- [Compatibility](#compatibility)
- [Download & Installation](#download--installation)
- [Usage Flow](#usage-flow)
- [Usage Screenshots](#usage-screenshots)
- [Architecture](#architecture)
- [Build from Source](#build-from-source)
- [Repository Layout](#repository-layout)
- [Security Notes & Limitations](#security-notes--limitations)
- [License](#license)

## Overview

StaticRouteHelper helps you manage IPv4 static routes on macOS with a desktop UI.

- Frontend: SwiftUI app (`StaticRouter`)
- Privileged operations: helper daemon (`RouteHelper`)
- IPC: typed XPC messages (`RouteWriteRequest` / `RouteWriteReply`)
- Route write path: PF_ROUTE socket (`RTM_ADD` / `RTM_DELETE`)

## Feature Highlights

- Add, edit, delete static routes
- Enable/disable routes individually
- Support both gateway modes:
- IPv4 gateway address
- Network interface (for example `utun3`, `en0`)
- System route table viewer:
- Search
- Refresh
- "Show only my routes" filter
- Route groups (macOS 14+):
- Create, rename, reorder, delete groups
- Assign route to multiple groups
- Startup route-state calibration (sync saved state with actual system route table)
- Helper install status banner and guided recovery for SMAppService XPC failures
- English + Simplified Chinese localization

## Compatibility

| macOS | Data layer | UI mode | Helper install method |
| --- | --- | --- | --- |
| 12-13 | Core Data | Legacy navigation | SMJobBless |
| 14+ | SwiftData (with legacy migration) | NavigationSplitView + sidebar groups | SMAppService (recommended) or SMJobBless |

Current project version in Xcode settings: `2.2.3` (build `73`).

## Download & Installation

Pre-built binaries are available on the [GitHub Releases](../../releases) page.
Pre-built binaries are available on [GitHub Releases](https://github.com/jdjingdian/StaticRouteHelper/releases).

Because this project is not signed with a paid Apple Developer certificate, the app is distributed with **ad-hoc code signing** and is **not notarized**. macOS Gatekeeper will block it from opening after download. To remove the restriction, run the following command in Terminal after unzipping:
1. Download and unzip the release package.
2. Move `Static Router.app` to your preferred location (for example `~/Applications/`).
3. Run the following command once in Terminal:

```bash
xattr -cr /path/to/Static\ Router.app
```

Replace `/path/to/Static\ Router.app` with the actual path where you placed the app (e.g., `~/Applications/Static\ Router.app`). This command removes the `com.apple.quarantine` flag that Gatekeeper sets on downloaded files, allowing the app to launch normally.
4. Launch the app, open **Settings -> General**, and install the helper.

Why step 3 is required:

- The project uses **ad-hoc code signing** (no paid Apple Developer certificate).
- The app is **not notarized**.
- Gatekeeper adds a quarantine flag to downloaded apps; `xattr -cr` removes it.

## Usage Flow

1. Open app and install helper from **Settings -> General**.
2. Add a route (`destination/prefix`, `gateway type`, `gateway`).
3. Toggle route activation in route list.
4. Open **System Route Table** to verify actual kernel routes.
5. Optionally group routes for organization (macOS 14+).

## Usage Screenshots

### 1) Route list with group organization and activation toggle

![Route list](./docs/images/workflow-01-route-list.png)

### 2) System route table with search and "Only My Routes" filter

![System route table](./docs/images/workflow-02-system-route-table.png)

### 3) Add route dialog (network, gateway mode, group assignment)

![Add route dialog](./docs/images/workflow-03-add-route-dialog.png)

## Architecture

```mermaid
flowchart LR
A["StaticRouter (SwiftUI App)"] --> B["RouterService"]
B --> C["PrivilegedHelperManager"]
C --> D["RouteHelper (XPC Server)"]
D --> E["PF_ROUTE Socket (Kernel Routing Table)"]
B --> F["SystemRouteReader"]
F --> E
```

## Build from Source

Requirements:

- macOS 12+
- Xcode 15+ recommended

Build Debug:

```bash
xcodebuild \
-project StaticRouteHelper.xcodeproj \
-scheme "Static Router" \
-configuration Debug \
build
```

Build Release package (same direction as CI):

```bash
xcodebuild \
-project StaticRouteHelper.xcodeproj \
-scheme "Static Router" \
-configuration Release \
-derivedDataPath build/DerivedData

ditto -c -k --keepParent \
"build/DerivedData/Build/Products/Release/Static Router.app" \
"StaticRouteHelper-local.zip"
```

## Notes
Useful scripts:

This is a helper tool for macOS written in Swift and SwiftUI. You can use it to manage macOS network route. It use `/sbin/route` to do the job, and it need root privileges. It gains root privileges with official API.
- `scripts/bump-version.sh <X.Y.Z>`: bump marketing/build version in `project.pbxproj`
- `scripts/validate-smappservice-health.sh [service_label]`: quick health check for SMAppService launchd job

## Feature && TODO
## Repository Layout

- [x] show system routes
- [x] Add/Delete route
- [ ] **SMAppService** for macOS 13.0+
- [ ] Rewrite the UI
- [ ] Using Network Extension to replace using `/sbin/route`
- [ ] Localization
- [ ] Dark Mode
- `StaticRouter/`: macOS app (SwiftUI)
- `RouteHelper/`: privileged helper daemon
- `Shared/`: shared XPC contracts/constants
- `.github/workflows/release.yml`: build/sign/package/release workflow
- `openspec/`: spec-driven change history

## Security Notes & Limitations

- Route operations require root privileges and a successfully installed helper.
- Current write/read implementation focuses on IPv4 routes.
- Misconfigured routes can affect host connectivity. Test carefully before applying broad destination ranges.
- If you use SMAppService on macOS 14+, system background-item approval may be required in System Settings.

License
-------
## License

StaticRouteHelper is licensed under the [Apache License 2.0](./LICENSE).
Copyright &copy; 2021, Derek Jing
172 changes: 156 additions & 16 deletions README_CN.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,178 @@
# 典の静态路由小助手
# StaticRouteHelper

<p align="center">
<img src="./StaticRouter/Assets.xcassets/AppIcon.appiconset/icon_256x256.png" width="120" alt="StaticRouteHelper 图标">
</p>

<p align="center">
基于 SwiftUI + 特权 Helper(XPC)的 macOS 静态路由管理工具。
</p>

<p align="center">
<a href="https://github.com/jdjingdian/StaticRouteHelper/releases"><img src="https://img.shields.io/github/v/release/jdjingdian/StaticRouteHelper?display_name=tag&sort=semver" alt="Latest Release"></a>
<a href="https://github.com/jdjingdian/StaticRouteHelper/actions/workflows/release.yml"><img src="https://github.com/jdjingdian/StaticRouteHelper/actions/workflows/release.yml/badge.svg" alt="Release Workflow"></a>
<img src="https://img.shields.io/badge/macOS-12%2B-111111?logo=apple" alt="macOS 12+">
<a href="./LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License"></a>
</p>

[English](./README.md) | 简体中文

## 目录

- [项目概览](#项目概览)
- [功能亮点](#功能亮点)
- [系统兼容性](#系统兼容性)
- [下载与安装](#下载与安装)
- [使用流程](#使用流程)
- [使用示意图](#使用示意图)
- [架构说明](#架构说明)
- [源码构建](#源码构建)
- [仓库结构](#仓库结构)
- [安全提示与限制](#安全提示与限制)
- [开源协议](#开源协议)

## 项目概览

StaticRouteHelper 用于在 macOS 上管理 IPv4 静态路由,提供桌面 UI 与受控提权能力。

- 前端应用:SwiftUI(`StaticRouter`)
- 特权操作:Helper 守护进程(`RouteHelper`)
- 通信方式:类型安全 XPC 消息(`RouteWriteRequest` / `RouteWriteReply`)
- 路由写入:PF_ROUTE socket(`RTM_ADD` / `RTM_DELETE`)

## 功能亮点

- 静态路由新增、编辑、删除
- 路由单条启用/停用
- 支持两种网关模式:
- IPv4 网关地址
- 网络接口名(例如 `utun3`、`en0`)
- 系统路由表查看:
- 搜索
- 刷新
- 仅显示“我的路由”过滤
- 路由分组(macOS 14+):
- 新增、重命名、排序、删除分组
- 单条路由可归属多个分组
- 启动时自动校准路由激活状态(持久化状态与系统真实路由对齐)
- SMAppService XPC 异常时提供引导与自动恢复流程
- 中英文本地化支持

## 系统兼容性

| macOS | 数据层 | 界面模式 | Helper 安装方式 |
| --- | --- | --- | --- |
| 12-13 | Core Data | 兼容模式导航 | SMJobBless |
| 14+ | SwiftData(含旧数据迁移) | NavigationSplitView + 分组侧边栏 | SMAppService(推荐)或 SMJobBless |

当前 Xcode 工程版本:`2.2.3`(build `73`)。

## 下载与安装

编译好的二进制文件可以在 [GitHub Releases](../../releases) 页面下载
预编译包可在 [GitHub Releases](https://github.com/jdjingdian/StaticRouteHelper/releases) 下载

由于本项目未申请付费的 Apple 开发者证书,发布的应用采用 **Ad-hoc 方式签名**,**未经 Apple 公证**。在 macOS 上首次打开时,Gatekeeper 会阻止其运行。解压后,请在终端执行以下命令移除系统的隔离限制:
1. 下载并解压发布包。
2. 将 `Static Router.app` 移动到你常用目录(例如 `~/Applications/`)。
3. 在终端执行一次以下命令:

```bash
xattr -cr /path/to/Static\ Router.app
```

将 `/path/to/Static\ Router.app` 替换为应用的实际路径(例如 `~/Applications/Static\ Router.app`)。该命令会移除 macOS 在下载文件时自动添加的 `com.apple.quarantine` 隔离属性,之后即可正常启动应用
4. 打开应用,在 **Settings -> General** 中安装 Helper

## 应用说明
为什么必须执行第 3 步:

这是一个用Swift和SwiftUI写的macOS下的静态路由管理助手,可以方便地添加自己需要的路由,因为route命令需要超级用户权限,所以应用第一次运行的时候会需要输入密码。程序使用CoreData保存了用户添加的静态路由信息,再此后启动的时候就可以自动加载,避免重复输入。点击退出按钮可以安全退出应用,退出的时候会清空手动添加过的路由表,如果是意外退出,电脑重启后也会清空手动添加的路由表。
- 项目当前使用 **Ad-hoc 签名**(未使用付费 Apple Developer 证书)。
- 应用 **未经过 Apple 公证(Notarization)**。
- 下载后 Gatekeeper 会添加隔离标记,`xattr -cr` 用于移除该标记。

在工作状态下,状态图标为绿色,并且此时无法从列表中删除该路由,点击图标后会清除该路由在系统中的设置,此时会有一个按钮出现,可以用来从列表中删除该路由,从而下次启动的时候不会加载。
## 使用流程

## 学到的一些知识点:
1. 启动应用,在 **Settings -> General** 安装 Helper。
2. 新建路由(目标网段/前缀、网关类型、网关值)。
3. 在路由列表中切换启用状态。
4. 打开 **System Route Table** 校验系统实际路由。
5. 如有需要,可通过分组组织规则(macOS 14+)。

慢慢补充ing……
## 使用示意图

### 1)路由列表:分组管理 + 单条激活开关

![路由列表](./docs/images/workflow-01-route-list.png)

## TODO
### 2)系统路由表:搜索 + 仅我的路由过滤

- 重写SwiftUI,MVVM
- 使用Network Extension的方式添加路由(比较遥远,网上似乎没什么教程,而且要开通付费开发者账号……但这样的话iOS也可以使用了(V2)
- 想办法复用Views
- 添加汉语支持
![系统路由表](./docs/images/workflow-02-system-route-table.png)

License
-------
### 3)添加路由弹窗:目标网段、路由方式、分组归属

![添加路由弹窗](./docs/images/workflow-03-add-route-dialog.png)

## 架构说明

```mermaid
flowchart LR
A["StaticRouter (SwiftUI App)"] --> B["RouterService"]
B --> C["PrivilegedHelperManager"]
C --> D["RouteHelper (XPC Server)"]
D --> E["PF_ROUTE Socket (Kernel Routing Table)"]
B --> F["SystemRouteReader"]
F --> E
```

## 源码构建

环境要求:

- macOS 12+
- 建议 Xcode 15+

Debug 构建:

```bash
xcodebuild \
-project StaticRouteHelper.xcodeproj \
-scheme "Static Router" \
-configuration Debug \
build
```

Release 打包(与 CI 方向一致):

```bash
xcodebuild \
-project StaticRouteHelper.xcodeproj \
-scheme "Static Router" \
-configuration Release \
-derivedDataPath build/DerivedData

ditto -c -k --keepParent \
"build/DerivedData/Build/Products/Release/Static Router.app" \
"StaticRouteHelper-local.zip"
```

常用脚本:

- `scripts/bump-version.sh <X.Y.Z>`:更新 `project.pbxproj` 中的版本号与构建号
- `scripts/validate-smappservice-health.sh [service_label]`:快速检查 SMAppService 的 launchd 健康状态

## 仓库结构

- `StaticRouter/`:macOS 客户端(SwiftUI)
- `RouteHelper/`:特权 Helper 守护进程
- `Shared/`:共享常量与 XPC 消息定义
- `.github/workflows/release.yml`:构建/签名/打包/发布流水线
- `openspec/`:规格驱动的变更历史

## 安全提示与限制

- 路由操作依赖 root 权限与可用的 Helper。
- 当前路由读写能力聚焦 IPv4。
- 错误路由规则可能影响主机网络连通性,请先小范围验证。
- 在 macOS 14+ 使用 SMAppService 时,可能需要在系统设置中手动允许后台项目。

## 开源协议

StaticRouteHelper 基于 [Apache License 2.0](./LICENSE) 协议开源。
Copyright &copy; 2021, Derek Jing
Loading
Loading