- 添加完整的 GitHub Actions 构建指南文档 - BUILD_GUIDE.md: Android 详细构建指南 - MULTIPLATFORM_GUIDE.md: 多平台构建指南 - HOW_TO_BUILD.md: 分步操作教程 - QUICKSTART.md: 3步快速开始指南 - INDEX.md: 文档总览索引 - README.md: 基础说明 - 创建 docs/ 目录存放项目文档 - 锁定 libcore 子模块到 f993a57 (v3.1.7) - 防止在线编译时使用最新版本 - 确保构建稳定性和一致性
319 lines
8.2 KiB
Markdown
319 lines
8.2 KiB
Markdown
# Android SELinux chown 权限问题修复方案
|
|
|
|
## 问题背景
|
|
|
|
在 Android 12+ 系统上,由于 SELinux 严格模式的权限限制,sing-box 核心库在启动时会因为 `chown` 操作被拒绝而失败。具体表现为以下三个错误:
|
|
|
|
1. **CommandServer 错误**: `chown: chown command.sock: operation not permitted`
|
|
2. **Logger 错误**: `start logger: chown box.log: operation not permitted`
|
|
3. **Clash Cache 错误**: `pre-start cache file: platform chown: chown clash.db: operation not permitted`
|
|
|
|
## 问题分析
|
|
|
|
### 根本原因
|
|
|
|
sing-box (libbox) 在创建文件后会尝试修改文件所有权(`chown`操作),但在 Android 的 SELinux 环境下,应用无法修改外部存储目录中文件的所有权,导致启动失败。
|
|
|
|
### 涉及的文件
|
|
|
|
1. `command.sock` - CommandServer 的 Unix socket 文件
|
|
2. `box.log` - sing-box 的日志文件
|
|
3. `clash.db` - Clash API 的缓存数据库文件
|
|
|
|
## 修复方案
|
|
|
|
### 方案概述
|
|
|
|
**核心思路**: 在配置层面禁用会触发 chown 操作的功能,或者修改底层代码跳过 chown 操作。
|
|
|
|
由于修改 sing-box 底层库较为复杂,建议采用配置修改方案。
|
|
|
|
---
|
|
|
|
## 具体修复步骤
|
|
|
|
### 修复 1: 禁用文件日志
|
|
|
|
**文件**: `libcore/config/hiddify_option.go`
|
|
|
|
**位置**: `DefaultHiddifyOptions()` 函数
|
|
|
|
**修改前**:
|
|
```go
|
|
LogLevel: "warn",
|
|
// LogFile: "/dev/null",
|
|
LogFile: "box.log",
|
|
```
|
|
|
|
**修改后**:
|
|
```go
|
|
LogLevel: "warn",
|
|
LogFile: "", // 禁用文件日志,避免 Android SELinux chown 权限问题
|
|
// LogFile: "box.log",
|
|
```
|
|
|
|
**说明**:
|
|
- 将 `LogFile` 设置为空字符串,禁用文件日志输出
|
|
- 日志将只输出到 stderr,可以通过 logcat 查看
|
|
- 这不会影响日志功能,只是改变了输出目标
|
|
|
|
---
|
|
|
|
### 修复 2: 禁用 Clash API 缓存
|
|
|
|
**文件**: `libcore/config/config.go`
|
|
|
|
**位置**: `setClashAPI()` 函数
|
|
|
|
**修改前**:
|
|
```go
|
|
func setClashAPI(options *option.Options, opt *HiddifyOptions) {
|
|
if opt.EnableClashApi {
|
|
if opt.ClashApiSecret == "" {
|
|
opt.ClashApiSecret = generateRandomString(16)
|
|
}
|
|
options.Experimental = &option.ExperimentalOptions{
|
|
ClashAPI: &option.ClashAPIOptions{
|
|
ExternalController: fmt.Sprintf("%s:%d", "127.0.0.1", opt.ClashApiPort),
|
|
Secret: opt.ClashApiSecret,
|
|
},
|
|
|
|
CacheFile: &option.CacheFileOptions{
|
|
Enabled: true,
|
|
Path: "clash.db",
|
|
},
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**修改后**:
|
|
```go
|
|
func setClashAPI(options *option.Options, opt *HiddifyOptions) {
|
|
if opt.EnableClashApi {
|
|
if opt.ClashApiSecret == "" {
|
|
opt.ClashApiSecret = generateRandomString(16)
|
|
}
|
|
options.Experimental = &option.ExperimentalOptions{
|
|
ClashAPI: &option.ClashAPIOptions{
|
|
ExternalController: fmt.Sprintf("%s:%d", "127.0.0.1", opt.ClashApiPort),
|
|
Secret: opt.ClashApiSecret,
|
|
},
|
|
|
|
CacheFile: &option.CacheFileOptions{
|
|
Enabled: false, // 禁用缓存以避免 Android SELinux chown 权限问题
|
|
Path: "", // 清空路径
|
|
},
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**说明**:
|
|
- 将 `Enabled` 设置为 `false`,禁用 Clash API 缓存功能
|
|
- 将 `Path` 设置为空字符串
|
|
- Clash API 功能仍然可用,只是不会持久化节点选择等信息
|
|
|
|
---
|
|
|
|
### 修复 3: CommandServer 错误处理 (可选)
|
|
|
|
**说明**: CommandServer 主要用于命令行控制,对 Android VPN 功能不是必需的。
|
|
|
|
#### 方案 A: 在应用层捕获异常 (推荐)
|
|
|
|
**文件**: `android/app/src/main/kotlin/com/hiddify/hiddify/bg/BoxService.kt`
|
|
|
|
**位置**: `onStartCommand()` 函数
|
|
|
|
**修改**:
|
|
```kotlin
|
|
GlobalScope.launch(Dispatchers.IO) {
|
|
Settings.startedByUser = true
|
|
initialize()
|
|
try {
|
|
startCommandServer()
|
|
} catch (e: Exception) {
|
|
// CommandServer 启动失败不是致命错误
|
|
// 在 Android 12+ SELinux 环境下,chown 操作可能被拒绝
|
|
// 但这不影响 VPN 核心功能,因此记录警告但继续启动服务
|
|
Log.w(TAG, "CommandServer failed to start (non-fatal): ${e.message}")
|
|
}
|
|
startService()
|
|
}
|
|
```
|
|
|
|
#### 方案 B: 完全禁用 CommandServer
|
|
|
|
如果不需要 CommandServer 功能,可以直接注释掉启动代码:
|
|
```kotlin
|
|
// startCommandServer() // 在 Android 上禁用
|
|
```
|
|
|
|
---
|
|
|
|
## 验证修复
|
|
|
|
### 编译步骤
|
|
|
|
1. **使用 Docker 编译** (推荐):
|
|
```bash
|
|
cd libcore
|
|
docker run --rm -v "$PWD:/workspace" -w /workspace golang:1.23 bash -c "bash docker-compile.sh"
|
|
```
|
|
|
|
2. **或者本地编译**:
|
|
```bash
|
|
cd libcore
|
|
make android
|
|
```
|
|
|
|
### 部署编译好的库
|
|
|
|
编译完成后,有两种部署方式:
|
|
|
|
#### 方式A: 替换完整AAR (推荐生产环境)
|
|
|
|
```bash
|
|
cd libcore
|
|
# 替换AAR文件
|
|
cp libcore.aar ../android/app/libs/
|
|
|
|
# 删除可能冲突的so文件
|
|
rm -rf ../android/app/src/main/jniLibs/
|
|
|
|
# 重新编译Flutter项目
|
|
cd ..
|
|
flutter clean && flutter build apk
|
|
```
|
|
|
|
**优点**: 支持所有架构,适合发布版本
|
|
|
|
#### 方式B: 只提取arm64 so文件 (推荐测试环境)
|
|
|
|
```bash
|
|
cd libcore
|
|
# 从AAR提取arm64的libbox.so
|
|
unzip -j libcore.aar jni/arm64-v8a/libbox.so -d /tmp/
|
|
mkdir -p ../android/app/src/main/jniLibs/arm64-v8a/
|
|
cp /tmp/libbox.so ../android/app/src/main/jniLibs/arm64-v8a/
|
|
|
|
# 临时禁用AAR(避免冲突)
|
|
mv ../android/app/libs/libcore.aar ../android/app/libs/libcore.aar.old
|
|
|
|
# 编译测试版本
|
|
cd ..
|
|
flutter clean && flutter build apk --debug --split-per-abi
|
|
```
|
|
|
|
**优点**: 编译快速,APK体积小,适合开发调试
|
|
|
|
⚠️ **重要**: 两种方式不能同时使用,会产生冲突!请根据需求选择其一。
|
|
|
|
### 测试验证
|
|
|
|
1. 重新编译 Flutter 应用
|
|
2. 在 Android 设备上安装并启动
|
|
3. 查看 logcat 日志,确认没有 chown 相关错误
|
|
4. 验证 VPN 能正常启动和代理流量
|
|
|
|
---
|
|
|
|
## 预期结果
|
|
|
|
修复后,应用启动日志应该显示:
|
|
|
|
```
|
|
D/A/BoxService: base dir: /data/user/0/app.xxx.com/files
|
|
D/A/BoxService: working dir: /storage/emulated/0/Android/data/app.xxx.com/files
|
|
D/A/BoxService: temp dir: /data/user/0/app.xxx.com/cache
|
|
W/A/BoxService: CommandServer failed to start (non-fatal): chown: chown command.sock: operation not permitted
|
|
D/A/BoxService: starting service
|
|
D/A/BoxService: 配置已修改: 禁用文件日志和Clash缓存
|
|
D/A/EventHandler: new status: Started ✅ 成功启动!
|
|
```
|
|
|
|
---
|
|
|
|
## 影响评估
|
|
|
|
### 功能影响
|
|
|
|
| 功能 | 修复前 | 修复后 | 影响 |
|
|
|------|--------|--------|------|
|
|
| VPN 代理 | ❌ 无法启动 | ✅ 正常工作 | 无影响 |
|
|
| 日志记录 | 文件输出 | logcat 输出 | 日志仍可用,通过 logcat 查看 |
|
|
| Clash API | 带缓存 | 无缓存 | 节点选择不持久化,重启后重置 |
|
|
| CommandServer | ❌ 失败 | 跳过启动 | 命令行控制不可用(非必需) |
|
|
|
|
### 性能影响
|
|
|
|
- **无性能损失**: 禁用缓存和文件日志对性能无负面影响
|
|
- **启动速度**: 可能略微加快(减少文件 I/O 操作)
|
|
|
|
---
|
|
|
|
## 替代方案 (高级)
|
|
|
|
如果需要保留完整功能,可以考虑以下方案:
|
|
|
|
### 方案 1: 修改 sing-box 源码
|
|
|
|
在 sing-box 底层库中跳过 Android 平台的 chown 操作:
|
|
|
|
```go
|
|
// 在文件创建后的 chown 调用处添加平台判断
|
|
if runtime.GOOS != "android" {
|
|
if err := os.Chown(path, uid, gid); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
```
|
|
|
|
**优点**: 保留所有功能
|
|
**缺点**: 需要修改 sing-box 上游代码,维护成本高
|
|
|
|
### 方案 2: 使用内部存储
|
|
|
|
将文件创建在 `/data/user/0/` 目录下而不是外部存储:
|
|
|
|
**优点**: 避免 SELinux 限制
|
|
**缺点**: 空间受限,不适合大文件
|
|
|
|
---
|
|
|
|
## 常见问题
|
|
|
|
### Q1: 禁用文件日志后如何查看日志?
|
|
|
|
**A**: 使用 adb logcat:
|
|
```bash
|
|
adb logcat | grep "A/BoxService\|singbox"
|
|
```
|
|
|
|
### Q2: 禁用 Clash 缓存后有什么影响?
|
|
|
|
**A**: 每次启动 VPN 时,节点选择会重置为默认值,但不影响 VPN 的核心代理功能。
|
|
|
|
### Q3: 为什么 hiddify-app 可以正常运行?
|
|
|
|
**A**: hiddify-app 使用的 libcore 版本已经包含了这些修复,或者使用了不同的配置策略。
|
|
|
|
### Q4: 这个修复是否适用于所有 Android 版本?
|
|
|
|
**A**: 是的,这个修复对 Android 11 及以下版本也兼容,不会产生负面影响。
|
|
|
|
---
|
|
|
|
## 参考资料
|
|
|
|
- [Android SELinux 权限文档](https://source.android.com/docs/security/features/selinux)
|
|
- [sing-box 配置文档](https://sing-box.sagernet.org/configuration/)
|
|
- [Clash API 配置](https://sing-box.sagernet.org/configuration/experimental/clash-api/)
|
|
|
|
---
|
|
|
|
## 更新日志
|
|
|
|
- **2025-10-27**: 初始版本,包含三个核心修复方案
|