mirror of
https://github.com/hackdiaz-dev/free-turn-proxy.git
synced 2026-10-12 09:41:53 +02:00
a7798cd851
- Добавлен пакет `internal/routemgr` для автоматического создания host-маршрутов (/32) через шлюз ОС (Windows, Linux, macOS). - В `config.Client` и CLI добавлен флаг `-routes` (по умолчанию отключен). - Интегрирован callback `OnTURNServer` в `udprelay` и `tcpfwd` для динамического добавления маршрутов при старте стримов. - Для Android (и переименованных JNI-бинарей Linux) добавлено graceful-отключение менеджера маршрутов. - Обновлена документация (`quickstart.md`): добавлены примеры с использованием `-routes` и `-client-id`.
213 lines
6.2 KiB
Go
213 lines
6.2 KiB
Go
package config
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/json"
|
||
"fmt"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/samosvalishe/free-turn-proxy/internal/uri"
|
||
)
|
||
|
||
// ClientJSON - схема конфига для хостов, которые не собирают командную строку
|
||
// (мобильное приложение через gomobile). Группы зеркалят Client, поэтому
|
||
// добавление опции не требует изобретать, где ей место.
|
||
//
|
||
// Декодирование строгое: неизвестное поле - ошибка. AAR и приложение собираются
|
||
// вместе, поэтому опечатка в хосте должна падать сразу, а не молча теряться.
|
||
// Отсутствующее поле сохраняет значение из Defaults(): DTO перед разбором
|
||
// заполняется дефолтами, а json.Unmarshal не трогает то, чего нет во входе.
|
||
type ClientJSON struct {
|
||
Peer string `json:"peer"`
|
||
ClientID string `json:"clientId"`
|
||
SubURL string `json:"subUrl"`
|
||
Provider string `json:"provider"`
|
||
Routes bool `json:"routes"`
|
||
|
||
TURN turnJSON `json:"turn"`
|
||
Proxy proxyJSON `json:"proxy"`
|
||
VK vkJSON `json:"vk"`
|
||
Obf obfJSON `json:"obf"`
|
||
DNS dnsJSON `json:"dns"`
|
||
Log logJSON `json:"log"`
|
||
Tunnel tunnelJSON `json:"tunnel"`
|
||
}
|
||
|
||
// tunnelJSON - userspace-туннель внутри процесса. config передаётся текстом в
|
||
// формате wg-quick: параметры AmneziaWG (Jc, S1, H1...) хост берёт из него же,
|
||
// дублировать их отдельными полями незачем.
|
||
type tunnelJSON struct {
|
||
Mode string `json:"mode"`
|
||
Config string `json:"config"`
|
||
MTU int `json:"mtu"`
|
||
}
|
||
|
||
type turnJSON struct {
|
||
N int `json:"n"`
|
||
Transport string `json:"transport"`
|
||
Host string `json:"host"`
|
||
Port string `json:"port"`
|
||
}
|
||
|
||
type proxyJSON struct {
|
||
Mode string `json:"mode"`
|
||
Bond bool `json:"bond"`
|
||
Listen string `json:"listen"`
|
||
}
|
||
|
||
type vkJSON struct {
|
||
Links []string `json:"links"`
|
||
StreamsPerCred int `json:"streamsPerCred"`
|
||
ManualCaptcha bool `json:"manualCaptcha"`
|
||
Platform string `json:"platform"`
|
||
}
|
||
|
||
type obfJSON struct {
|
||
Profile string `json:"profile"`
|
||
Key string `json:"key"`
|
||
TimingMs int `json:"timingMs"`
|
||
}
|
||
|
||
type dnsJSON struct {
|
||
Mode string `json:"mode"`
|
||
Servers []string `json:"servers"`
|
||
}
|
||
|
||
type logJSON struct {
|
||
Debug bool `json:"debug"`
|
||
}
|
||
|
||
// DefaultClientJSON - дефолты в виде JSON. Хост строит из них стартовое
|
||
// состояние формы и не повторяет литералы у себя.
|
||
func DefaultClientJSON() string {
|
||
b, err := json.MarshalIndent(defaultClientJSON(), "", " ")
|
||
if err != nil {
|
||
// Схема состоит из строк, чисел и срезов строк - маршалиться обязана.
|
||
panic("config: default JSON must marshal: " + err.Error())
|
||
}
|
||
return string(b)
|
||
}
|
||
|
||
// PeekSubURLJSON достаёт subUrl из конфига, не разбирая остального. Нужен до
|
||
// ParseClientJSON: подписка отдаёт peer, без которого валидация падает.
|
||
// Некорректный JSON - не ошибка здесь, о ней сообщит полноценный разбор.
|
||
func PeekSubURLJSON(data []byte) string {
|
||
var peek struct {
|
||
SubURL string `json:"subUrl"`
|
||
}
|
||
if json.Unmarshal(data, &peek) != nil {
|
||
return ""
|
||
}
|
||
return peek.SubURL
|
||
}
|
||
|
||
// ParseClientJSON разбирает конфиг хоста тем же путём, что и CLI: DTO -> raw ->
|
||
// assemble -> Validate.
|
||
//
|
||
// overlayURI (если не пуст) накладывается поверх, как позиционный freeturn:// у
|
||
// CLI: так узел из подписки применяется тем же кодом, что и вручную вставленная
|
||
// ссылка.
|
||
func ParseClientJSON(data []byte, overlayURI string) (*Client, error) {
|
||
dto := defaultClientJSON()
|
||
dec := json.NewDecoder(bytes.NewReader(data))
|
||
dec.DisallowUnknownFields()
|
||
if err := dec.Decode(&dto); err != nil {
|
||
return nil, fmt.Errorf("config json: %w", err)
|
||
}
|
||
|
||
r := dto.toRaw()
|
||
if overlayURI != "" {
|
||
u, err := uri.Parse(overlayURI)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to parse freeturn:// URI: %w", err)
|
||
}
|
||
r.applyURI(u)
|
||
}
|
||
|
||
c, err := assemble(r)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if err := Validate(c); err != nil {
|
||
return nil, err
|
||
}
|
||
return c, nil
|
||
}
|
||
|
||
func defaultClientJSON() ClientJSON {
|
||
r := defaultRaw()
|
||
return ClientJSON{
|
||
Peer: r.Peer,
|
||
ClientID: r.ClientID,
|
||
SubURL: r.SubURL,
|
||
Provider: r.Provider,
|
||
Routes: r.Routes,
|
||
TURN: turnJSON{
|
||
N: r.N,
|
||
Transport: r.Transport,
|
||
Host: r.Turn,
|
||
Port: r.Port,
|
||
},
|
||
Proxy: proxyJSON{
|
||
Mode: r.Mode,
|
||
Bond: r.Bond,
|
||
Listen: r.Listen,
|
||
},
|
||
VK: vkJSON{
|
||
StreamsPerCred: r.StreamsPerCred,
|
||
ManualCaptcha: r.ManualCaptcha,
|
||
Platform: r.Platform,
|
||
},
|
||
Obf: obfJSON{
|
||
Profile: r.ObfProfile,
|
||
Key: r.ObfKey,
|
||
TimingMs: int(r.ObfTiming / time.Millisecond),
|
||
},
|
||
DNS: dnsJSON{Mode: r.DNSMode},
|
||
Log: logJSON{Debug: r.Debug},
|
||
Tunnel: tunnelJSON{
|
||
Mode: r.TunnelMode,
|
||
Config: r.TunnelConfig,
|
||
MTU: r.TunnelMTU,
|
||
},
|
||
}
|
||
}
|
||
|
||
func (j ClientJSON) toRaw() raw {
|
||
return raw{
|
||
Turn: j.TURN.Host,
|
||
Port: j.TURN.Port,
|
||
Listen: j.Proxy.Listen,
|
||
Peer: j.Peer,
|
||
|
||
Provider: j.Provider,
|
||
Links: strings.Join(j.VK.Links, ","),
|
||
|
||
N: j.TURN.N,
|
||
StreamsPerCred: j.VK.StreamsPerCred,
|
||
Transport: j.TURN.Transport,
|
||
Mode: j.Proxy.Mode,
|
||
Bond: j.Proxy.Bond,
|
||
|
||
ObfProfile: j.Obf.Profile,
|
||
ObfKey: j.Obf.Key,
|
||
ObfTiming: time.Duration(j.Obf.TimingMs) * time.Millisecond,
|
||
|
||
ManualCaptcha: j.VK.ManualCaptcha,
|
||
Platform: j.VK.Platform,
|
||
|
||
DNSMode: j.DNS.Mode,
|
||
DNSServers: strings.Join(j.DNS.Servers, ","),
|
||
|
||
Debug: j.Log.Debug,
|
||
ClientID: j.ClientID,
|
||
SubURL: j.SubURL,
|
||
Routes: j.Routes,
|
||
|
||
TunnelMode: j.Tunnel.Mode,
|
||
TunnelConfig: j.Tunnel.Config,
|
||
TunnelMTU: j.Tunnel.MTU,
|
||
}
|
||
}
|