Files
free-turn-proxy/mobile

Интеграция free-turn-proxy через gomobile

Пакет mobile предоставляет API для встраивания ядра прокси в нативные мобильные приложения (iOS/Android) через gomobile bind.

Сборка

iOS

Сборка универсального XCFramework (dist/Mobile.xcframework) с поддержкой iOS и симулятора:

task build:ios

Android

Сборка Android Archive (dist/freeturn.aar и freeturn-sources.jar) с JNI-обертками для Java/Kotlin:

task build:android

API

Хост-приложение передает конфигурацию в формате JSON, а события получает через интерфейс обратного вызова.

func Start(configJSON string) error                 // Запуск в режиме прокси
func StartTunnel(configJSON string, tunFD int) error // Запуск прокси + WireGuard/AmneziaWG
func Restart(configJSON string, tunFD int) error     // Перезапуск с ожиданием остановки
func Stop()                                         // Остановка сессии
func GetState() *Snapshot                           // Метрики сессии (State, Rates, Streams)
func TunnelStats() *TunnelSnapshot                  // Статистика туннеля
func SetEventSink(s EventSink)                      // Установка коллбека событий
func SetProtect(p Protector)                        // Защита сокетов (VpnService.protect)
func DefaultConfigJSON() string                     // Конфиг с дефолтами
func ValidateConfig(configJSON string) string       // Валидация JSON
func ConfigToArgs(configJSON string) (string, error) // Вывод эквивалентной CLI-команды
func DumpLogs() string
func ClearLogs()
func Version() string

В Java класс экспортируется как com.freeturn.core.mobile.Mobile. int в Go транслируется в long на стороне Kotlin/Java.

Конфигурация JSON

Пример схемы JSON:

{
  "peer": "1.2.3.4:56000",
  "clientId": "0123456789abcdef0123456789abcdef",
  "provider": "vk",
  "turn":  {"n": 12, "transport": "tcp", "host": "", "port": ""},
  "proxy": {"mode": "udp", "bond": false, "listen": "127.0.0.1:9000"},
  "vk":    {"links": ["https://vk.ru/call/join/..."], "streamsPerCred": 12,
            "manualCaptcha": false, "platform": "mobile"},
  "obf":   {"profile": "rtpopus3", "key": "<64 hex>", "timingMs": 0},
  "dns":   {"mode": "auto", "servers": ["8.8.8.8"]},
  "log":   {"debug": false},
  "tunnel": {"mode": "none", "config": "", "mtu": 1280},
  "subUrl": ""
}
  • clientId - обязателен. Ядро на мобиле не пишет файлы, ID должен храниться в приложении.

Туннелирование

tunnel.mode принимает значения none, wg (WireGuard) или awg (AmneziaWG).

Текст WireGuard-конфигурации (wg-quick) передается в tunnel.config. В режиме wg параметры обфускации AmneziaWG игнорируются. Поле Endpoint из конфига не используется, так как трафик идет через TURN-релей.

Для wg/awg требуется proxy.mode = "udp" и запуск через StartTunnel:

val fd = vpnBuilder.establish()!!.detachFd()
Mobile.startTunnel(configJson, fd.toLong())

Ядро берет владение дескриптором и закроет его при остановке. Самостоятельно закрывать fd в приложении нельзя во избежание crash.

WireGuard общается с релеем через in-memory netconn.PacketPipe напрямую. Петля 127.0.0.1:9000 не используется, поэтому исключать приложение из VPN-маршрутов не требуется. Защите через SetProtect подлежат только сокеты релея.

Обработка событий

Хост-приложение должно реализовать интерфейс EventSink:

type EventSink interface {
    OnState(state string, streams, total int, errMsg string)
    OnLog(level, msg string, unixMillis int64)
    OnCaptcha(url string)
}

OnState вызывается строго последовательно из одной горутины при изменении статуса. OnLog может вызываться параллельно. Хост обязан удерживать ссылку на объект EventSink в памяти (JNI-ссылка со стороны Go не защищает его от GC).