gooserelayvpn-android-client
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGooseRelayVPN Android Client Skill
GooseRelayVPN Android客户端技能
Overview
概述
GooseRelayVPN Android Client is an Android application that provides a VPN service tunneling TCP traffic through Google Apps Script to a VPS exit server. It uses:
- Local SOCKS5 proxy for app/browser traffic
- AES-256-GCM encryption for tunnel security
- Domain fronting via Google infrastructure (Apps Script)
- Android VpnService integration with tun2socks
- Profile-based configuration with JSON import/export
Architecture flow:
- Android app traffic → SOCKS5 (127.0.0.1:1080)
- GooseRelay core encrypts with AES key
- HTTPS transport through Google Apps Script deployment
- VPS exit server decrypts and forwards to target
GooseRelayVPN Android客户端是一款提供VPN服务的Android应用,可通过Google Apps Script将TCP流量隧道传输至VPS出口服务器。它采用以下技术:
- 本地SOCKS5代理:用于应用/浏览器流量
- AES-256-GCM加密:保障隧道安全
- 域名前置:通过Google基础设施(Apps Script)实现
- Android VpnService集成:搭配tun2socks使用
- 基于配置文件的设置:支持JSON导入/导出
架构流程:
- Android应用流量 → SOCKS5(127.0.0.1:1080)
- GooseRelay核心模块使用AES密钥加密流量
- 通过Google Apps Script部署的HTTPS传输通道
- VPS出口服务器解密并转发至目标地址
Prerequisites
前提条件
Before using the Android client, you must set up upstream infrastructure:
- VPS server running (from main GooseRelayVPN project)
goose-server - Google Apps Script deployment with
apps_script/Code.gs - Tunnel encryption key generated via
scripts/gen-key.sh - Deployment ID(s) from Apps Script
Upstream project: https://github.com/kianmhz/GooseRelayVPN
使用Android客户端前,您必须先搭建上游基础设施:
- VPS服务器:运行(来自GooseRelayVPN主项目)
goose-server - Google Apps Script部署:配置
apps_script/Code.gs - 隧道加密密钥:通过生成
scripts/gen-key.sh - 部署ID:来自Apps Script的部署标识
Building the Android Client
构建Android客户端
Requirements
环境要求
- Android Studio
- JDK 17
- Go 1.22+
- Android SDK with NDK
- tool
gomobile
- Android Studio
- JDK 17
- Go 1.22+
- 包含NDK的Android SDK
- 工具
gomobile
Build Go Mobile AAR
构建Go Mobile AAR
The core GooseRelay logic is compiled to an Android AAR library:
bash
undefinedGooseRelay核心逻辑会被编译为Android AAR库:
bash
undefinedFrom project root
从项目根目录执行
bash android/build_go_mobile.sh
This script:
- Installs `gomobile` if needed
- Compiles Go code to AAR for arm64-v8a, armeabi-v7a, x86, x86_64
- Outputs to `android/app/libs/gooserelay.aar`
**Manual AAR build:**
```bash
go install golang.org/x/mobile/cmd/gomobile@latest
gomobile init
gomobile bind \
-target=android \
-androidapi=21 \
-o android/app/libs/gooserelay.aar \
-v \
./mobilebash android/build_go_mobile.sh
该脚本会:
- 按需安装`gomobile`
- 将Go代码编译为支持arm64-v8a、armeabi-v7a、x86、x86_64架构的AAR
- 输出至`android/app/libs/gooserelay.aar`
**手动构建AAR:**
```bash
go install golang.org/x/mobile/cmd/gomobile@latest
gomobile init
gomobile bind \
-target=android \
-androidapi=21 \
-o android/app/libs/gooserelay.aar \
-v \
./mobileBuild Debug APK
构建Debug APK
bash
cd android
./gradlew :app:assembleDebugbash
cd android
./gradlew :app:assembleDebugOutput: android/app/build/outputs/apk/debug/app-debug.apk
输出路径: android/app/build/outputs/apk/debug/app-debug.apk
undefinedundefinedBuild Release APK
构建Release APK
bash
cd android
./gradlew :app:assembleReleasebash
cd android
./gradlew :app:assembleReleaseRequires signing configuration in android/app/build.gradle
需要在android/app/build.gradle中配置签名信息
undefinedundefinedProfile Configuration
配置文件设置
Profile JSON Structure
配置文件JSON结构
json
{
"debug_timing": false,
"socks_host": "127.0.0.1",
"socks_port": 1080,
"google_host": "216.239.38.120",
"sni": [
"www.google.com",
"mail.google.com",
"accounts.google.com"
],
"script_keys": [
"DEPLOYMENT_ID_FROM_APPS_SCRIPT",
"OPTIONAL_SECOND_DEPLOYMENT_ID"
],
"tunnel_key": "BASE64_ENCODED_AES_KEY_FROM_GEN_KEY_SCRIPT"
}json
{
"debug_timing": false,
"socks_host": "127.0.0.1",
"socks_port": 1080,
"google_host": "216.239.38.120",
"sni": [
"www.google.com",
"mail.google.com",
"accounts.google.com"
],
"script_keys": [
"DEPLOYMENT_ID_FROM_APPS_SCRIPT",
"OPTIONAL_SECOND_DEPLOYMENT_ID"
],
"tunnel_key": "BASE64_ENCODED_AES_KEY_FROM_GEN_KEY_SCRIPT"
}Field Descriptions
字段说明
| Field | Type | Description |
|---|---|---|
| bool | Enable timing debug logs |
| string | Local SOCKS5 bind address (usually 127.0.0.1) |
| int | Local SOCKS5 port (default 1080) |
| string | Google IP for domain fronting (216.239.38.120 is common) |
| []string | SNI hostnames for TLS handshake rotation |
| []string | Apps Script deployment IDs (one or more for redundancy) |
| string | Base64 AES-256 key (must match server-side key) |
| 字段 | 类型 | 描述 |
|---|---|---|
| bool | 启用计时调试日志 |
| string | 本地SOCKS5绑定地址(通常为127.0.0.1) |
| int | 本地SOCKS5端口(默认1080) |
| string | 用于域名前置的Google IP(常用216.239.38.120) |
| []string | TLS握手轮换使用的SNI主机名 |
| []string | Apps Script部署ID(可配置多个实现冗余) |
| string | Base64编码的AES-256密钥(必须与服务器端密钥一致) |
Generating Tunnel Key
生成隧道密钥
Use the upstream script to generate a secure key:
bash
undefined使用上游脚本生成安全密钥:
bash
undefinedFrom GooseRelayVPN main repo
从GooseRelayVPN主仓库执行
bash scripts/gen-key.sh
bash scripts/gen-key.sh
Outputs base64-encoded key
输出Base64编码的密钥
Example: a3d7f9e2b1c4...
示例: a3d7f9e2b1c4...
**Important:** The same `tunnel_key` must be used on both the Android client and the VPS `goose-server`.
**重要提示:** Android客户端和VPS上的`goose-server`必须使用相同的`tunnel_key`。In-App Profile Management
应用内配置文件管理
Create new profile:
- Open app → Profiles tab
- Tap "+" button
- Enter profile name and configuration
- Save
Import profile from JSON:
- Profiles tab → menu → Import
- Select JSON file from storage
- Profile is added to list
Export profile to JSON:
- Profiles tab → long-press profile
- Select Export
- JSON saved to Downloads
创建新配置文件:
- 打开应用 → 配置文件标签页
- 点击"+"按钮
- 输入配置文件名称和参数
- 保存
从JSON导入配置文件:
- 配置文件标签页 → 菜单 → 导入
- 从存储中选择JSON文件
- 配置文件将被添加至列表
导出配置文件为JSON:
- 配置文件标签页 → 长按目标配置文件
- 选择导出
- JSON文件将保存至下载目录
Android VPN Service Integration
Android VPN服务集成
VpnService Implementation
VpnService实现
The app uses Android's API to capture device traffic:
VpnServicekotlin
// Simplified example from Android codebase
class GooseVpnService : VpnService() {
private val socksHost = "127.0.0.1"
private val socksPort = 1080
fun startVpn(profile: Profile) {
// 1. Start GooseRelay core (via JNI to Go AAR)
GooseRelay.start(profile.toJson())
// 2. Configure VPN interface
val builder = Builder()
.setSession("GooseRelayVPN")
.addAddress("10.0.0.2", 24)
.addRoute("0.0.0.0", 0)
.addDnsServer("8.8.8.8")
// 3. Exclude apps if split tunneling enabled
if (profile.splitTunnel) {
profile.allowedApps.forEach { pkg ->
builder.addAllowedApplication(pkg)
}
}
val vpnInterface = builder.establish()
// 4. Start tun2socks to forward traffic to SOCKS5
Tun2Socks.start(
vpnInterface.fd,
socksHost,
socksPort
)
}
}应用使用Android的 API捕获设备流量:
VpnServicekotlin
// 来自Android代码库的简化示例
class GooseVpnService : VpnService() {
private val socksHost = "127.0.0.1"
private val socksPort = 1080
fun startVpn(profile: Profile) {
// 1. 启动GooseRelay核心(通过JNI调用Go AAR)
GooseRelay.start(profile.toJson())
// 2. 配置VPN接口
val builder = Builder()
.setSession("GooseRelayVPN")
.addAddress("10.0.0.2", 24)
.addRoute("0.0.0.0", 0)
.addDnsServer("8.8.8.8")
// 3. 如果启用拆分隧道则排除指定应用
if (profile.splitTunnel) {
profile.allowedApps.forEach { pkg ->
builder.addAllowedApplication(pkg)
}
}
val vpnInterface = builder.establish()
// 4. 启动tun2socks将流量转发至SOCKS5
Tun2Socks.start(
vpnInterface.fd,
socksHost,
socksPort
)
}
}Permissions Required
所需权限
AndroidManifest.xml:
xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application>
<service
android:name=".service.GooseVpnService"
android:permission="android.permission.BIND_VPN_SERVICE">
<intent-filter>
<action android:name="android.net.VpnService" />
</intent-filter>
</service>
</application>
</manifest>AndroidManifest.xml:
xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application>
<service
android:name=".service.GooseVpnService"
android:permission="android.permission.BIND_VPN_SERVICE">
<intent-filter>
<action android:name="android.net.VpnService" />
</intent-filter>
</service>
</application>
</manifest>Go Mobile Bridge
Go Mobile桥接层
The Go core is exposed to Android via :
gomobileGo核心逻辑通过暴露给Android:
gomobileMobile Package Interface
移动包接口
mobile/mobile.go:
go
package mobile
import (
"encoding/json"
"github.com/Hidden-Node/GooseRelayVPN/client"
)
// Config matches Android profile JSON structure
type Config struct {
DebugTiming bool `json:"debug_timing"`
SocksHost string `json:"socks_host"`
SocksPort int `json:"socks_port"`
GoogleHost string `json:"google_host"`
SNI []string `json:"sni"`
ScriptKeys []string `json:"script_keys"`
TunnelKey string `json:"tunnel_key"`
}
var relayClient *client.Client
// Start initializes and starts the GooseRelay client
func Start(configJSON string) error {
var cfg Config
if err := json.Unmarshal([]byte(configJSON), &cfg); err != nil {
return err
}
relayClient = client.NewClient(client.Config{
DebugTiming: cfg.DebugTiming,
SocksAddr: fmt.Sprintf("%s:%d", cfg.SocksHost, cfg.SocksPort),
GoogleHost: cfg.GoogleHost,
SNI: cfg.SNI,
ScriptKeys: cfg.ScriptKeys,
TunnelKey: cfg.TunnelKey,
})
return relayClient.Start()
}
// Stop gracefully stops the relay client
func Stop() error {
if relayClient != nil {
return relayClient.Stop()
}
return nil
}
// GetStats returns JSON statistics
func GetStats() string {
if relayClient == nil {
return "{}"
}
stats := relayClient.GetStats()
data, _ := json.Marshal(stats)
return string(data)
}mobile/mobile.go:
go
package mobile
import (
"encoding/json"
"github.com/Hidden-Node/GooseRelayVPN/client"
)
// Config与Android配置文件JSON结构匹配
type Config struct {
DebugTiming bool `json:"debug_timing"`
SocksHost string `json:"socks_host"`
SocksPort int `json:"socks_port"`
GoogleHost string `json:"google_host"`
SNI []string `json:"sni"`
ScriptKeys []string `json:"script_keys"`
TunnelKey string `json:"tunnel_key"`
}
var relayClient *client.Client
// Start初始化并启动GooseRelay客户端
func Start(configJSON string) error {
var cfg Config
if err := json.Unmarshal([]byte(configJSON), &cfg); err != nil {
return err
}
relayClient = client.NewClient(client.Config{
DebugTiming: cfg.DebugTiming,
SocksAddr: fmt.Sprintf("%s:%d", cfg.SocksHost, cfg.SocksPort),
GoogleHost: cfg.GoogleHost,
SNI: cfg.SNI,
ScriptKeys: cfg.ScriptKeys,
TunnelKey: cfg.TunnelKey,
})
return relayClient.Start()
}
// Stop优雅停止中继客户端
func Stop() error {
if relayClient != nil {
return relayClient.Stop()
}
return nil
}
// GetStats返回JSON格式的统计数据
func GetStats() string {
if relayClient == nil {
return "{}"
}
stats := relayClient.GetStats()
data, _ := json.Marshal(stats)
return string(data)
}Calling from Android (Kotlin)
Android端调用(Kotlin)
kotlin
import gooserelay.Gooserelay // Generated from AAR
class RelayManager {
fun startRelay(profile: Profile) {
val configJson = profile.toJson()
try {
Gooserelay.start(configJson)
Log.i("GooseRelay", "Started successfully")
} catch (e: Exception) {
Log.e("GooseRelay", "Start failed: ${e.message}")
}
}
fun stopRelay() {
try {
Gooserelay.stop()
} catch (e: Exception) {
Log.e("GooseRelay", "Stop failed: ${e.message}")
}
}
fun getStats(): Stats? {
return try {
val json = Gooserelay.getStats()
Json.decodeFromString<Stats>(json)
} catch (e: Exception) {
null
}
}
}kotlin
import gooserelay.Gooserelay // 从AAR生成
class RelayManager {
fun startRelay(profile: Profile) {
val configJson = profile.toJson()
try {
Gooserelay.start(configJson)
Log.i("GooseRelay", "启动成功")
} catch (e: Exception) {
Log.e("GooseRelay", "启动失败: ${e.message}")
}
}
fun stopRelay() {
try {
Gooserelay.stop()
} catch (e: Exception) {
Log.e("GooseRelay", "停止失败: ${e.message}")
}
}
fun getStats(): Stats? {
return try {
val json = Gooserelay.getStats()
Json.decodeFromString<Stats>(json)
} catch (e: Exception) {
null
}
}
}Common Usage Patterns
常见使用场景
Full Device VPN
全设备VPN
Route all device traffic through the tunnel:
kotlin
// In profile configuration
val profile = Profile(
name = "Full VPN",
socksPort = 1080,
scriptKeys = listOf(System.getenv("APPS_SCRIPT_DEPLOYMENT_ID")),
tunnelKey = System.getenv("GOOSE_TUNNEL_KEY"),
splitTunnel = false, // All apps
excludeLocalNetwork = true
)
vpnService.startVpn(profile)将所有设备流量通过隧道传输:
kotlin
// 配置文件设置
val profile = Profile(
name = "全设备VPN",
socksPort = 1080,
scriptKeys = listOf(System.getenv("APPS_SCRIPT_DEPLOYMENT_ID")),
tunnelKey = System.getenv("GOOSE_TUNNEL_KEY"),
splitTunnel = false, // 所有应用
excludeLocalNetwork = true
)
vpnService.startVpn(profile)Split Tunneling
拆分隧道
Route only specific apps through the tunnel:
kotlin
val profile = Profile(
name = "Split Tunnel",
socksPort = 1080,
scriptKeys = listOf(System.getenv("APPS_SCRIPT_DEPLOYMENT_ID")),
tunnelKey = System.getenv("GOOSE_TUNNEL_KEY"),
splitTunnel = true,
allowedApps = listOf(
"com.android.chrome",
"org.telegram.messenger"
)
)
vpnService.startVpn(profile)仅让指定应用通过隧道传输:
kotlin
val profile = Profile(
name = "拆分隧道",
socksPort = 1080,
scriptKeys = listOf(System.getenv("APPS_SCRIPT_DEPLOYMENT_ID")),
tunnelKey = System.getenv("GOOSE_TUNNEL_KEY"),
splitTunnel = true,
allowedApps = listOf(
"com.android.chrome",
"org.telegram.messenger"
)
)
vpnService.startVpn(profile)Manual SOCKS5 Proxy
手动SOCKS5代理
Use without VpnService (manual app configuration):
kotlin
// Start only the SOCKS5 server, no VPN
fun startSocksOnly(profile: Profile) {
GooseRelay.start(profile.toJson())
// Apps must be configured to use 127.0.0.1:1080 as SOCKS5 proxy
}不使用VpnService(需手动配置应用):
kotlin
// 仅启动SOCKS5服务器,不启用VPN
fun startSocksOnly(profile: Profile) {
GooseRelay.start(profile.toJson())
// 应用需手动配置使用127.0.0.1:1080作为SOCKS5代理
}Logging and Telemetry
日志与遥测
Real-time Logs
实时日志
The app provides a Logs tab showing Android and Go core logs:
kotlin
// Android side logger forwarding to UI
object LogCollector {
private val logs = mutableListOf<LogEntry>()
fun addLog(level: String, tag: String, message: String) {
logs.add(LogEntry(
timestamp = System.currentTimeMillis(),
level = level,
tag = tag,
message = message
))
// Notify UI observers
notifyLogListeners()
}
}
// Go side: logs are captured via custom writer应用提供日志标签页,展示Android和Go核心模块的日志:
kotlin
// Android端日志收集器,转发至UI
object LogCollector {
private val logs = mutableListOf<LogEntry>()
fun addLog(level: String, tag: String, message: String) {
logs.add(LogEntry(
timestamp = System.currentTimeMillis(),
level = level,
tag = tag,
message = message
))
// 通知UI观察者
notifyLogListeners()
}
}
// Go端:通过自定义写入器捕获日志Telemetry Stats
遥测统计
Real-time connection statistics:
kotlin
data class TelemetryStats(
val bytesUploaded: Long,
val bytesDownloaded: Long,
val activeConnections: Int,
val successRate: Float,
val avgLatency: Long
)
fun updateTelemetry() {
val statsJson = Gooserelay.getStats()
val stats = Json.decodeFromString<TelemetryStats>(statsJson)
// Update UI cards
uploadCard.text = formatBytes(stats.bytesUploaded)
downloadCard.text = formatBytes(stats.bytesDownloaded)
latencyCard.text = "${stats.avgLatency}ms"
}实时连接统计数据:
kotlin
data class TelemetryStats(
val bytesUploaded: Long,
val bytesDownloaded: Long,
val activeConnections: Int,
val successRate: Float,
val avgLatency: Long
)
fun updateTelemetry() {
val statsJson = Gooserelay.getStats()
val stats = Json.decodeFromString<TelemetryStats>(statsJson)
// 更新UI卡片
uploadCard.text = formatBytes(stats.bytesUploaded)
downloadCard.text = formatBytes(stats.bytesDownloaded)
latencyCard.text = "${stats.avgLatency}ms"
}CI/CD and Release
CI/CD与发布
GitHub Actions Workflows
GitHub Actions工作流
Debug CI (.github/workflows/android-ci.yml):
yaml
name: Android CI
on:
push:
branches: [main]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.22'
- name: Build Go Mobile AAR
run: bash android/build_go_mobile.sh
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Build Debug APK
run: |
cd android
./gradlew assembleDebug
- name: Upload APK
uses: actions/upload-artifact@v3
with:
name: app-debug
path: android/app/build/outputs/apk/debug/app-debug.apkRelease Workflow (.github/workflows/release.yml):
yaml
name: Release Build
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build AAR
run: bash android/build_go_mobile.sh
- name: Decode Keystore
run: |
echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d > android/keystore.jks
- name: Build Release APK
env:
KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
run: |
cd android
./gradlew assembleRelease
- name: Create Release
uses: softprops/action-gh-release@v1
with:
files: android/app/build/outputs/apk/release/app-release.apkRequired secrets:
- : Base64-encoded keystore file
ANDROID_KEYSTORE_BASE64 - : Keystore password
ANDROID_KEYSTORE_PASSWORD - : Key alias in keystore
ANDROID_KEY_ALIAS - : Key password
ANDROID_KEY_PASSWORD
Debug CI (.github/workflows/android-ci.yml):
yaml
name: Android CI
on:
push:
branches: [main]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.22'
- name: Build Go Mobile AAR
run: bash android/build_go_mobile.sh
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Build Debug APK
run: |
cd android
./gradlew assembleDebug
- name: Upload APK
uses: actions/upload-artifact@v3
with:
name: app-debug
path: android/app/build/outputs/apk/debug/app-debug.apk发布工作流 (.github/workflows/release.yml):
yaml
name: Release Build
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build AAR
run: bash android/build_go_mobile.sh
- name: Decode Keystore
run: |
echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d > android/keystore.jks
- name: Build Release APK
env:
KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
run: |
cd android
./gradlew assembleRelease
- name: Create Release
uses: softprops/action-gh-release@v1
with:
files: android/app/build/outputs/apk/release/app-release.apk所需密钥:
- :Base64编码的密钥库文件
ANDROID_KEYSTORE_BASE64 - :密钥库密码
ANDROID_KEYSTORE_PASSWORD - :密钥库中的密钥别名
ANDROID_KEY_ALIAS - :密钥密码
ANDROID_KEY_PASSWORD
Troubleshooting
故障排除
Connection Stuck in "Preparing" State
连接卡在“准备中”状态
Symptoms: VPN shows "Preparing" but never connects.
Causes:
- Invalid (deployment IDs)
script_keys - Mismatched between client and server
tunnel_key - Apps Script deployment not accessible
Solution:
bash
undefined症状: VPN显示“准备中”但始终无法连接。
原因:
- 无效(部署ID错误)
script_keys - 客户端与服务器的不匹配
tunnel_key - Apps Script部署无法访问
解决方案:
bash
undefinedCheck logs tab in app for specific errors
查看应用内日志标签页获取具体错误
Common log patterns:
常见日志模式:
"Failed to connect to script" → Check deployment ID
"Failed to connect to script" → 检查部署ID
"Decryption failed" → Check tunnel_key matches server
"Decryption failed" → 确认tunnel_key与服务器一致
"Connection timeout" → Verify VPS server is running
"Connection timeout" → 验证VPS服务器是否运行
Verify server-side key matches:
验证服务器端密钥是否匹配:
On VPS: cat /etc/goose-server/config.json | jq .tunnel_key
在VPS上执行: cat /etc/goose-server/config.json | jq .tunnel_key
In Android profile: check tunnel_key field
在Android配置文件中检查tunnel_key字段
undefinedundefinedSOCKS Port Busy Error
SOCKS端口占用错误
Symptoms: "bind: address already in use" in logs.
Solution:
kotlin
// Ensure clean disconnect before reconnect
fun reconnect() {
// Stop VPN service
vpnService.stop()
// Wait for port release
Thread.sleep(2000)
// Check no other app uses port 1080
// Change profile socks_port if needed
profile.socksPort = 1081
vpnService.start()
}症状: 日志中出现“bind: address already in use”。
解决方案:
kotlin
undefinedNo Traffic Flowing Through VPN
确保重新连接前先干净断开
Symptoms: VPN connected but no internet access.
Checklist:
- VPN permission granted
- Split tunnel app selection correct
- DNS servers configured
- Local network excluded if needed
Solution:
kotlin
// Verify VPN builder configuration
val builder = Builder()
.addAddress("10.0.0.2", 24)
.addRoute("0.0.0.0", 0)
.addDnsServer("8.8.8.8")
.addDnsServer("1.1.1.1")
// Exclude local network
if (profile.excludeLocalNetwork) {
builder.addRoute("0.0.0.0", 5)
builder.addRoute("8.0.0.0", 7)
builder.addRoute("11.0.0.0", 8)
// ... exclude 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
}
val vpnInterface = builder.establish()fun reconnect() {
// 停止VPN服务
vpnService.stop()
// 等待端口释放
Thread.sleep(2000)
// 检查是否有其他应用占用1080端口
// 若需要则修改配置文件的socks_port
profile.socksPort = 1081
vpnService.start()}
undefinedApps Script Deployment Issues
VPN连接后无流量
Symptoms: "Script execution failed" in logs.
Solution:
bash
undefined症状: VPN已连接但无法访问互联网。
检查清单:
- 是否已授予VPN权限
- 拆分隧道的应用选择是否正确
- 是否已配置DNS服务器
- 是否需要排除本地网络
解决方案:
kotlin
// 验证VPN构建器配置
val builder = Builder()
.addAddress("10.0.0.2", 24)
.addRoute("0.0.0.0", 0)
.addDnsServer("8.8.8.8")
.addDnsServer("1.1.1.1")
// 排除本地网络
if (profile.excludeLocalNetwork) {
builder.addRoute("0.0.0.0", 5)
builder.addRoute("8.0.0.0", 7)
builder.addRoute("11.0.0.0", 8)
// ... 排除10.0.0.0/8、172.16.0.0/12、192.168.0.0/16等网段
}
val vpnInterface = builder.establish()Verify deployment ID format (should be long alphanumeric string)
Apps Script部署问题
Example: AKfycbzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
—
Test deployment manually:
—
症状: 日志中出现“Script execution failed”。
解决方案:
bash
undefinedEnsure Apps Script has correct VPS endpoint in Code.gs:
验证部署ID格式(应为长字符串)
const VPS_ENDPOINT = "https://your-vps-ip:8443";
示例: AKfycbzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
—
手动测试部署:
undefinedHigh Latency or Slow Speeds
确保Apps Script的Code.gs中配置了正确的VPS端点:
—
const VPS_ENDPOINT = "https://your-vps-ip:8443";
Symptoms: Connected but slow performance.
Optimization:
json
{
"debug_timing": false,
"sni": [
"www.google.com"
],
"script_keys": [
"PRIMARY_DEPLOYMENT_ID",
"BACKUP_DEPLOYMENT_ID"
]
}Tips:
- Use multiple for load balancing
script_keys - Choose closer Google IP in
google_host - Optimize VPS server configuration
- Enable temporarily to identify bottlenecks
debug_timing
undefinedAAR Build Failures
高延迟或速度缓慢
Symptoms: fails.
build_go_mobile.shSolution:
bash
undefined症状: 已连接但性能低下。
优化配置:
json
{
"debug_timing": false,
"sni": [
"www.google.com"
],
"script_keys": [
"PRIMARY_DEPLOYMENT_ID",
"BACKUP_DEPLOYMENT_ID"
]
}提示:
- 使用多个实现负载均衡
script_keys - 在中选择更近的Google IP
google_host - 优化VPS服务器配置
- 临时启用以识别瓶颈
debug_timing
Ensure gomobile is installed
AAR构建失败
go install golang.org/x/mobile/cmd/gomobile@latest
gomobile init
症状: 执行失败。
build_go_mobile.sh解决方案:
bash
undefinedCheck Android NDK is installed
确保已安装gomobile
In Android Studio: Tools → SDK Manager → SDK Tools → NDK
—
Set environment variables
—
export ANDROID_HOME=$HOME/Android/Sdk
export ANDROID_NDK_HOME=$ANDROID_HOME/ndk/25.1.8937393
go install golang.org/x/mobile/cmd/gomobile@latest
gomobile init
Retry build
检查Android NDK是否已安装
—
在Android Studio中: Tools → SDK Manager → SDK Tools → NDK
—
设置环境变量
bash android/build_go_mobile.sh
undefinedexport ANDROID_HOME=$HOME/Android/Sdk
export ANDROID_NDK_HOME=$ANDROID_HOME/ndk/25.1.8937393
Certificate/TLS Errors
重新尝试构建
Symptoms: "x509: certificate signed by unknown authority"
Solution:
go
// If using self-signed cert on VPS, Apps Script must trust it
// Better: Use Let's Encrypt on VPS
// In Code.gs, configure:
const ALLOW_SELF_SIGNED = false; // Set true only for testingbash android/build_go_mobile.sh
undefinedBest Practices
证书/TLS错误
- Key Management: Store in Android Keystore, not plaintext
tunnel_key - Multiple Deployments: Use 2-3 for redundancy
script_keys - Battery Optimization: Exclude VPN service from battery optimization
- Testing: Test with during setup
debug_timing: true - Backup Profiles: Export profiles before app updates
- Server Monitoring: Monitor VPS logs alongside Android logs
goose-server
症状: 出现"x509: certificate signed by unknown authority"
解决方案:
go
// 如果VPS使用自签名证书,Apps Script必须信任该证书
// 更优方案:在VPS上使用Let's Encrypt证书
// 在Code.gs中配置:
const ALLOW_SELF_SIGNED = false; // 仅测试环境设置为trueAdditional Resources
最佳实践
- Main project: https://github.com/kianmhz/GooseRelayVPN
- Apps Script setup: See upstream
apps_script/Code.gs - Server setup: See upstream directory
server/ - Issues: Check open issues at https://github.com/Hidden-Node/GooseRelayVPN-AndroidClient/issues
- 密钥管理: 将存储在Android密钥库中,避免明文存储
tunnel_key - 多部署冗余: 使用2-3个实现故障转移
script_keys - 电池优化: 将VPN服务排除在电池优化之外
- 测试建议: 搭建阶段启用
debug_timing: true - 配置备份: 应用更新前导出配置文件
- 服务器监控: 同时监控VPS 日志和Android日志
goose-server
—
额外资源
—
- 主项目:https://github.com/kianmhz/GooseRelayVPN
- Apps Script配置:查看上游项目的
apps_script/Code.gs - 服务器配置:查看上游项目的目录
server/ - 问题反馈:访问https://github.com/Hidden-Node/GooseRelayVPN-AndroidClient/issues查看公开问题