refactor: вынести разбор CLI в internal/config

ParseClient/ParseServer возвращают валидированные структуры; main становится
связкой. Валидация без сайд-эффектов (без DNS/сети); main держит ResolveUDPAddr,
установки dnsdial.* и вывод gen-wrap-key. Сервер теперь переиспользует
wrap.GenKeyHex/wrap.DecodeKey вместо своего hex+rand пути.

client/main.go: 319 → 272. server/main.go: 424 → 402.
19 тестов покрывают дефолты, санитизацию vk-link, отсутствующие флаги,
валидацию -dns, конфликт wrap+no-dtls, декод wrap-key, положительность
streams-per-cred, clamp n, split dns-servers и short-circuit gen-wrap-key.

Флаги CLI, дефолты и поведение не изменены. Две строки ошибок
("Need peer address!" / "Need vk-link!") переведены в нижний регистр под
revive; не часть wire-контракта.
This commit is contained in:
samosvalishe
2026-05-19 16:26:36 +03:00
parent 75471c6fdc
commit 817def3e01
4 changed files with 454 additions and 116 deletions
+29 -76
View File
@@ -5,13 +5,11 @@ package main
import (
"context"
"flag"
"fmt"
"log"
"net"
"os"
"os/signal"
"strings"
"sync"
"sync/atomic"
"syscall"
@@ -21,6 +19,7 @@ import (
"github.com/cacggghp/vk-turn-proxy/client/internal/dnsdial"
"github.com/cacggghp/vk-turn-proxy/client/internal/vkauth"
bondclient "github.com/cacggghp/vk-turn-proxy/internal/bond/client"
"github.com/cacggghp/vk-turn-proxy/internal/config"
"github.com/cacggghp/vk-turn-proxy/internal/dtlsdial"
udpproxy "github.com/cacggghp/vk-turn-proxy/internal/proxy/udp"
"github.com/cacggghp/vk-turn-proxy/internal/proxy/vless"
@@ -91,102 +90,56 @@ func main() {
log.Fatalf("Exit...\n")
}()
host := flag.String("turn", "", "override TURN server ip")
port := flag.String("port", "", "override TURN port")
listen := flag.String("listen", "127.0.0.1:9000", "listen on ip:port")
vklink := flag.String("vk-link", "", "VK calls invite link \"https://vk.com/call/join/...\"")
peerAddr := flag.String("peer", "", "peer server address (host:port)")
n := flag.Int("n", 10, "connections to TURN")
udp := flag.Bool("udp", false, "connect to TURN with UDP")
direct := flag.Bool("no-dtls", false, "connect without obfuscation. DO NOT USE")
vlessMode := flag.Bool("vless", false, "VLESS mode: forward TCP connections (for VLESS) instead of UDP packets")
vlessBond := flag.Bool("vless-bond", false, "bond one VLESS TCP connection across all active smux sessions")
wrapMode := flag.Bool("wrap", false, "WRAP mode: ChaCha20-XOR obfuscate DTLS packets before they reach TURN ChannelData")
wrapKeyHex := flag.String("wrap-key", "", "32-byte hex-encoded shared key for -wrap (64 hex chars)")
genWrapKey := flag.Bool("gen-wrap-key", false, "print a fresh 64-character hex key for -wrap-key and exit")
streamsPerCredFlag := flag.Int("streams-per-cred", vkauth.DefaultStreamsPerCache, "number of TURN streams sharing one VK credential cache")
debugFlag := flag.Bool("debug", false, "enable debug logging")
manualCaptchaFlag := flag.Bool("manual-captcha", false, "skip auto captcha solving, use manual mode immediately")
dnsFlag := flag.String("dns", dnsdial.DNSModeAuto, "DNS resolution mode: udp | doh | auto (auto tries UDP/53 first, sticky-fallback to DoH on total failure)")
dnsServersFlag := flag.String("dns-servers", "", "comma-separated UDP/53 DNS servers to use instead of built-in defaults (e.g. carrier resolvers from Android LinkProperties). Format: ip[:port][,ip[:port]...].")
flag.Parse()
cfg, err := config.ParseClient(os.Args[1:], os.Stderr)
if err != nil {
log.Panicf("%v", err)
}
switch *dnsFlag {
case dnsdial.DNSModeUDP, dnsdial.DNSModeDoH, dnsdial.DNSModeAuto:
default:
log.Panicf("invalid -dns value %q: must be udp | doh | auto", *dnsFlag)
if cfg.DNSServers != nil {
dnsdial.SetUDPDNSServers(cfg.DNSServers)
log.Printf("[DNS] using custom UDP servers: %v", cfg.DNSServers)
}
if *dnsServersFlag != "" {
servers := strings.Split(*dnsServersFlag, ",")
dnsdial.SetUDPDNSServers(servers)
log.Printf("[DNS] using custom UDP servers: %v", servers)
}
appDialer = dnsdial.AppDialer(*dnsFlag)
dnsdial.InstallGlobalResolver(*dnsFlag)
if *genWrapKey {
key, err := wrap.GenKeyHex()
if err != nil {
log.Panicf("%v", err)
appDialer = dnsdial.AppDialer(cfg.DNSMode)
dnsdial.InstallGlobalResolver(cfg.DNSMode)
if cfg.GenWrapKey {
key, gerr := wrap.GenKeyHex()
if gerr != nil {
log.Panicf("%v", gerr)
}
fmt.Println(key)
return
}
if *peerAddr == "" {
log.Panicf("Need peer address!")
}
peer, err := net.ResolveUDPAddr("udp", *peerAddr)
peer, err := net.ResolveUDPAddr("udp", cfg.Peer)
if err != nil {
panic(err)
}
if *vklink == "" {
log.Panicf("Need vk-link!")
}
if *wrapMode && *direct {
log.Panicf("-wrap requires DTLS; remove -no-dtls")
}
wrapKey, err := wrap.DecodeKey(*wrapMode, *wrapKeyHex)
if err != nil {
log.Panicf("%v", err)
}
if *wrapMode {
if cfg.WrapMode {
log.Printf("WRAP mode enabled: peer server must use matching -wrap-key")
}
if *streamsPerCredFlag <= 0 {
log.Panicf("-streams-per-cred must be positive")
}
isDebug = *debugFlag
isDebug = cfg.Debug
vkAuth = vkauth.New(vkauth.Config{
Dialer: appDialer,
ManualOnly: *manualCaptchaFlag,
StreamsPerCache: *streamsPerCredFlag,
ManualOnly: cfg.ManualCaptcha,
StreamsPerCache: cfg.StreamsPerCred,
StreamsAlive: func() int32 { return connectedStreams.Load() },
ManualSolver: manualCaptchaSolver,
Debugf: debugf,
})
parts := strings.Split(*vklink, "join/")
link := parts[len(parts)-1]
getCreds := getCredsFunc(vkAuth.GetCredentials)
if *n <= 0 {
*n = 10
}
if idx := strings.IndexAny(link, "/?#"); idx != -1 {
link = link[:idx]
}
params := &turnParams{
host: *host,
port: *port,
link: link,
udp: *udp,
wrapKey: wrapKey,
host: cfg.Host,
port: cfg.Port,
link: cfg.VKLink,
udp: cfg.UDP,
wrapKey: cfg.WrapKey,
getCreds: getCreds,
}
if *vlessMode {
if cfg.VLESSMode {
bondH := &bondclient.Handler{Deps: bondclient.Deps{Debug: isDebug, Debugf: debugf}}
vlessDeps := &vless.Deps{
DTLSDialer: vlessDtlsDialer,
@@ -202,11 +155,11 @@ func main() {
WrapKey: params.wrapKey,
GetCreds: vless.GetCredsFunc(params.getCreds),
}
vless.Run(ctx, vlessDeps, vlessParams, peer, *listen, *n, *vlessBond)
vless.Run(ctx, vlessDeps, vlessParams, peer, cfg.Listen, cfg.N, cfg.VLESSBond)
return
}
listenConn, err := net.ListenPacket("udp", *listen)
listenConn, err := net.ListenPacket("udp", cfg.Listen)
if err != nil {
log.Panicf("Failed to listen: %s", err)
}
@@ -216,7 +169,7 @@ func main() {
}
})
numStreams := *n
numStreams := cfg.N
if numStreams <= 0 {
numStreams = 1
}
@@ -268,7 +221,7 @@ func main() {
wg1 := sync.WaitGroup{}
t := time.Tick(200 * time.Millisecond)
if *direct {
if cfg.Direct {
log.Panicf("Direct mode not supported with dispatcher")
}
+200
View File
@@ -0,0 +1,200 @@
// Package config parses CLI flags for the client and server binaries.
//
// Parse* functions are side-effect free: they validate inputs and decode the
// wrap key, but do not touch the network, DNS, or process state. main() is
// responsible for wiring those side effects after Parse* returns.
package config
import (
"errors"
"flag"
"fmt"
"io"
"strings"
"github.com/cacggghp/vk-turn-proxy/internal/wrap"
)
// Mirrors of constants defined in client/internal packages, duplicated here
// because internal/config cannot import client/internal/*.
const (
dnsModeUDP = "udp"
dnsModeDoH = "doh"
dnsModeAuto = "auto"
defaultStreamsPerCache = 10
)
// Client holds parsed and validated client CLI options.
type Client struct {
Host string
Port string
Listen string
VKLink string // sanitized: stripped past "join/" and trimmed at /?#
Peer string
N int
UDP bool
Direct bool
VLESSMode bool
VLESSBond bool
WrapMode bool
WrapKey []byte // nil unless WrapMode
GenWrapKey bool
StreamsPerCred int
Debug bool
ManualCaptcha bool
DNSMode string
DNSServers []string // nil when -dns-servers empty
}
// Server holds parsed and validated server CLI options.
type Server struct {
Listen string
Connect string
VLESSMode bool
VLESSBond bool
WrapMode bool
WrapKey []byte
GenWrapKey bool
Debug bool
}
// ParseClient parses args (excluding program name) into a Client.
// On flag.ErrHelp it returns (nil, flag.ErrHelp) so the caller can exit cleanly.
func ParseClient(args []string, errOut io.Writer) (*Client, error) {
fs := flag.NewFlagSet("client", flag.ContinueOnError)
if errOut != nil {
fs.SetOutput(errOut)
}
host := fs.String("turn", "", "override TURN server ip")
port := fs.String("port", "", "override TURN port")
listen := fs.String("listen", "127.0.0.1:9000", "listen on ip:port")
vklink := fs.String("vk-link", "", "VK calls invite link \"https://vk.com/call/join/...\"")
peerAddr := fs.String("peer", "", "peer server address (host:port)")
n := fs.Int("n", 10, "connections to TURN")
udp := fs.Bool("udp", false, "connect to TURN with UDP")
direct := fs.Bool("no-dtls", false, "connect without obfuscation. DO NOT USE")
vlessMode := fs.Bool("vless", false, "VLESS mode: forward TCP connections (for VLESS) instead of UDP packets")
vlessBond := fs.Bool("vless-bond", false, "bond one VLESS TCP connection across all active smux sessions")
wrapMode := fs.Bool("wrap", false, "WRAP mode: ChaCha20-XOR obfuscate DTLS packets before they reach TURN ChannelData")
wrapKeyHex := fs.String("wrap-key", "", "32-byte hex-encoded shared key for -wrap (64 hex chars)")
genWrapKey := fs.Bool("gen-wrap-key", false, "print a fresh 64-character hex key for -wrap-key and exit")
streamsPerCredFlag := fs.Int("streams-per-cred", defaultStreamsPerCache, "number of TURN streams sharing one VK credential cache")
debugFlag := fs.Bool("debug", false, "enable debug logging")
manualCaptchaFlag := fs.Bool("manual-captcha", false, "skip auto captcha solving, use manual mode immediately")
dnsFlag := fs.String("dns", dnsModeAuto, "DNS resolution mode: udp | doh | auto (auto tries UDP/53 first, sticky-fallback to DoH on total failure)")
dnsServersFlag := fs.String("dns-servers", "", "comma-separated UDP/53 DNS servers to use instead of built-in defaults (e.g. carrier resolvers from Android LinkProperties). Format: ip[:port][,ip[:port]...].")
if err := fs.Parse(args); err != nil {
return nil, err
}
c := &Client{
Host: *host,
Port: *port,
Listen: *listen,
Peer: *peerAddr,
N: *n,
UDP: *udp,
Direct: *direct,
VLESSMode: *vlessMode,
VLESSBond: *vlessBond,
WrapMode: *wrapMode,
GenWrapKey: *genWrapKey,
StreamsPerCred: *streamsPerCredFlag,
Debug: *debugFlag,
ManualCaptcha: *manualCaptchaFlag,
DNSMode: *dnsFlag,
}
switch c.DNSMode {
case dnsModeUDP, dnsModeDoH, dnsModeAuto:
default:
return nil, fmt.Errorf("invalid -dns value %q: must be udp | doh | auto", c.DNSMode)
}
if *dnsServersFlag != "" {
c.DNSServers = strings.Split(*dnsServersFlag, ",")
}
// gen-wrap-key short-circuits: caller emits the key and exits, so skip
// the rest of validation (no peer / vk-link needed for key gen).
if c.GenWrapKey {
return c, nil
}
if c.Peer == "" {
return nil, errors.New("need peer address")
}
if *vklink == "" {
return nil, errors.New("need vk-link")
}
if c.WrapMode && c.Direct {
return nil, fmt.Errorf("-wrap requires DTLS; remove -no-dtls")
}
key, err := wrap.DecodeKey(c.WrapMode, *wrapKeyHex)
if err != nil {
return nil, err
}
c.WrapKey = key
if c.StreamsPerCred <= 0 {
return nil, fmt.Errorf("-streams-per-cred must be positive")
}
if c.N <= 0 {
c.N = 10
}
parts := strings.Split(*vklink, "join/")
link := parts[len(parts)-1]
if idx := strings.IndexAny(link, "/?#"); idx != -1 {
link = link[:idx]
}
c.VKLink = link
return c, nil
}
// ParseServer parses args (excluding program name) into a Server.
func ParseServer(args []string, errOut io.Writer) (*Server, error) {
fs := flag.NewFlagSet("server", flag.ContinueOnError)
if errOut != nil {
fs.SetOutput(errOut)
}
listen := fs.String("listen", "0.0.0.0:56000", "listen on ip:port")
connect := fs.String("connect", "", "connect to ip:port")
vlessMode := fs.Bool("vless", false, "VLESS mode: forward TCP connections (for VLESS) instead of UDP packets")
vlessBond := fs.Bool("vless-bond", false, "bond one VLESS TCP connection across all active smux sessions")
wrapMode := fs.Bool("wrap", false, "WRAP mode: ChaCha20-XOR obfuscate DTLS packets before they reach TURN ChannelData")
wrapKeyHex := fs.String("wrap-key", "", "32-byte hex-encoded shared key for -wrap (64 hex chars)")
genWrapKey := fs.Bool("gen-wrap-key", false, "print a fresh 64-character hex key for -wrap-key and exit")
debugFlag := fs.Bool("debug", false, "enable debug logging")
if err := fs.Parse(args); err != nil {
return nil, err
}
s := &Server{
Listen: *listen,
Connect: *connect,
VLESSMode: *vlessMode,
VLESSBond: *vlessBond,
WrapMode: *wrapMode,
GenWrapKey: *genWrapKey,
Debug: *debugFlag,
}
if s.GenWrapKey {
return s, nil
}
if s.Connect == "" {
return nil, fmt.Errorf("server address is required")
}
key, err := wrap.DecodeKey(s.WrapMode, *wrapKeyHex)
if err != nil {
return nil, err
}
s.WrapKey = key
return s, nil
}
+207
View File
@@ -0,0 +1,207 @@
package config
import (
"bytes"
"errors"
"flag"
"io"
"strings"
"testing"
)
func validClientArgs() []string {
return []string{
"-peer", "1.2.3.4:5000",
"-vk-link", "https://vk.com/call/join/abcdef",
}
}
func TestParseClient_Defaults(t *testing.T) {
c, err := ParseClient(validClientArgs(), io.Discard)
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
if c.Listen != "127.0.0.1:9000" {
t.Errorf("Listen default: %q", c.Listen)
}
if c.N != 10 {
t.Errorf("N default: %d", c.N)
}
if c.DNSMode != "auto" {
t.Errorf("DNSMode default: %q", c.DNSMode)
}
if c.StreamsPerCred != defaultStreamsPerCache {
t.Errorf("StreamsPerCred default: %d", c.StreamsPerCred)
}
if c.VKLink != "abcdef" {
t.Errorf("VKLink: %q (expected abcdef)", c.VKLink)
}
if c.WrapKey != nil {
t.Errorf("WrapKey should be nil when -wrap absent")
}
}
func TestParseClient_VKLinkStrip(t *testing.T) {
args := []string{
"-peer", "1.2.3.4:5000",
"-vk-link", "https://vk.com/call/join/CODE123?foo=bar",
}
c, err := ParseClient(args, io.Discard)
if err != nil {
t.Fatal(err)
}
if c.VKLink != "CODE123" {
t.Errorf("VKLink: %q (expected CODE123)", c.VKLink)
}
}
func TestParseClient_MissingPeer(t *testing.T) {
_, err := ParseClient([]string{"-vk-link", "https://vk.com/call/join/X"}, io.Discard)
if err == nil || !strings.Contains(err.Error(), "peer") {
t.Errorf("expected peer error, got %v", err)
}
}
func TestParseClient_MissingVKLink(t *testing.T) {
_, err := ParseClient([]string{"-peer", "1.2.3.4:5000"}, io.Discard)
if err == nil || !strings.Contains(err.Error(), "vk-link") {
t.Errorf("expected vk-link error, got %v", err)
}
}
func TestParseClient_InvalidDNS(t *testing.T) {
args := append(validClientArgs(), "-dns", "garbage")
_, err := ParseClient(args, io.Discard)
if err == nil || !strings.Contains(err.Error(), "invalid -dns") {
t.Errorf("expected dns error, got %v", err)
}
}
func TestParseClient_WrapDirectConflict(t *testing.T) {
args := append(validClientArgs(), "-wrap", "-no-dtls", "-wrap-key", strings.Repeat("aa", 32))
_, err := ParseClient(args, io.Discard)
if err == nil || !strings.Contains(err.Error(), "-wrap requires DTLS") {
t.Errorf("expected wrap/direct conflict, got %v", err)
}
}
func TestParseClient_WrapMissingKey(t *testing.T) {
args := append(validClientArgs(), "-wrap")
_, err := ParseClient(args, io.Discard)
if err == nil || !strings.Contains(err.Error(), "-wrap requires -wrap-key") {
t.Errorf("expected wrap-key error, got %v", err)
}
}
func TestParseClient_WrapKeyOK(t *testing.T) {
args := append(validClientArgs(), "-wrap", "-wrap-key", strings.Repeat("ab", 32))
c, err := ParseClient(args, io.Discard)
if err != nil {
t.Fatal(err)
}
if len(c.WrapKey) != 32 {
t.Errorf("WrapKey len: %d", len(c.WrapKey))
}
}
func TestParseClient_StreamsPerCredNonPositive(t *testing.T) {
args := append(validClientArgs(), "-streams-per-cred", "0")
_, err := ParseClient(args, io.Discard)
if err == nil || !strings.Contains(err.Error(), "streams-per-cred") {
t.Errorf("expected streams-per-cred error, got %v", err)
}
}
func TestParseClient_NClampedToTen(t *testing.T) {
args := append(validClientArgs(), "-n", "-5")
c, err := ParseClient(args, io.Discard)
if err != nil {
t.Fatal(err)
}
if c.N != 10 {
t.Errorf("N: %d (expected 10)", c.N)
}
}
func TestParseClient_DNSServersSplit(t *testing.T) {
args := append(validClientArgs(), "-dns-servers", "1.1.1.1,8.8.8.8:53")
c, err := ParseClient(args, io.Discard)
if err != nil {
t.Fatal(err)
}
if len(c.DNSServers) != 2 || c.DNSServers[0] != "1.1.1.1" || c.DNSServers[1] != "8.8.8.8:53" {
t.Errorf("DNSServers: %v", c.DNSServers)
}
}
func TestParseClient_GenWrapKeySkipsPeerCheck(t *testing.T) {
c, err := ParseClient([]string{"-gen-wrap-key"}, io.Discard)
if err != nil {
t.Fatalf("gen-wrap-key should not require peer/vk-link: %v", err)
}
if !c.GenWrapKey {
t.Errorf("GenWrapKey not set")
}
}
func TestParseClient_HelpReturnsErrHelp(t *testing.T) {
var buf bytes.Buffer
_, err := ParseClient([]string{"-h"}, &buf)
if !errors.Is(err, flag.ErrHelp) {
t.Errorf("expected flag.ErrHelp, got %v", err)
}
}
func TestParseServer_Defaults(t *testing.T) {
s, err := ParseServer([]string{"-connect", "backend:1234"}, io.Discard)
if err != nil {
t.Fatal(err)
}
if s.Listen != "0.0.0.0:56000" {
t.Errorf("Listen default: %q", s.Listen)
}
if s.Connect != "backend:1234" {
t.Errorf("Connect: %q", s.Connect)
}
}
func TestParseServer_MissingConnect(t *testing.T) {
_, err := ParseServer(nil, io.Discard)
if err == nil || !strings.Contains(err.Error(), "server address") {
t.Errorf("expected connect error, got %v", err)
}
}
func TestParseServer_WrapMissingKey(t *testing.T) {
_, err := ParseServer([]string{"-connect", "x:1", "-wrap"}, io.Discard)
if err == nil || !strings.Contains(err.Error(), "-wrap requires -wrap-key") {
t.Errorf("expected wrap-key error, got %v", err)
}
}
func TestParseServer_WrapKeyBadHex(t *testing.T) {
_, err := ParseServer([]string{"-connect", "x:1", "-wrap", "-wrap-key", "zz"}, io.Discard)
if err == nil || !strings.Contains(err.Error(), "invalid hex") {
t.Errorf("expected hex error, got %v", err)
}
}
func TestParseServer_WrapKeyOK(t *testing.T) {
s, err := ParseServer([]string{"-connect", "x:1", "-wrap", "-wrap-key", strings.Repeat("cd", 32)}, io.Discard)
if err != nil {
t.Fatal(err)
}
if len(s.WrapKey) != 32 {
t.Errorf("WrapKey len: %d", len(s.WrapKey))
}
}
func TestParseServer_GenWrapKeySkipsConnectCheck(t *testing.T) {
s, err := ParseServer([]string{"-gen-wrap-key"}, io.Discard)
if err != nil {
t.Fatalf("gen-wrap-key should not require -connect: %v", err)
}
if !s.GenWrapKey {
t.Errorf("GenWrapKey not set")
}
}
+18 -40
View File
@@ -2,9 +2,6 @@ package main
import (
"context"
"crypto/rand"
"encoding/hex"
"flag"
"fmt"
"io"
"log"
@@ -17,6 +14,7 @@ import (
"github.com/cacggghp/vk-turn-proxy/internal/bond"
bondserver "github.com/cacggghp/vk-turn-proxy/internal/bond/server"
"github.com/cacggghp/vk-turn-proxy/internal/config"
"github.com/cacggghp/vk-turn-proxy/internal/stats"
"github.com/cacggghp/vk-turn-proxy/internal/wrap"
"github.com/cacggghp/vk-turn-proxy/tcputil"
@@ -34,23 +32,18 @@ func debugf(format string, v ...any) {
}
func main() {
listen := flag.String("listen", "0.0.0.0:56000", "listen on ip:port")
connect := flag.String("connect", "", "connect to ip:port")
vlessMode := flag.Bool("vless", false, "VLESS mode: forward TCP connections (for VLESS) instead of UDP packets")
vlessBond := flag.Bool("vless-bond", false, "bond one VLESS TCP connection across all active smux sessions")
wrapMode := flag.Bool("wrap", false, "WRAP mode: ChaCha20-XOR obfuscate DTLS packets before they reach TURN ChannelData")
wrapKeyHex := flag.String("wrap-key", "", "32-byte hex-encoded shared key for -wrap (64 hex chars)")
genWrapKey := flag.Bool("gen-wrap-key", false, "print a fresh 64-character hex key for -wrap-key and exit")
debugFlag := flag.Bool("debug", false, "enable debug logging")
flag.Parse()
isDebug = *debugFlag
cfg, err := config.ParseServer(os.Args[1:], os.Stderr)
if err != nil {
log.Panicf("%v", err)
}
isDebug = cfg.Debug
if *genWrapKey {
key := make([]byte, wrap.KeyLen)
if _, err := rand.Read(key); err != nil {
log.Panicf("gen-wrap-key: rand.Read: %v", err)
if cfg.GenWrapKey {
key, gerr := wrap.GenKeyHex()
if gerr != nil {
log.Panicf("gen-wrap-key: %v", gerr)
}
fmt.Println(hex.EncodeToString(key))
fmt.Println(key)
return
}
@@ -66,27 +59,12 @@ func main() {
log.Fatalf("Exit...\n")
}()
addr, err := net.ResolveUDPAddr("udp", *listen)
addr, err := net.ResolveUDPAddr("udp", cfg.Listen)
if err != nil {
panic(err)
}
if len(*connect) == 0 {
log.Panicf("server address is required")
}
var wrapKey []byte
if *wrapMode {
if *wrapKeyHex == "" {
log.Panicf("-wrap requires -wrap-key")
}
wrapKey, err = hex.DecodeString(*wrapKeyHex)
if err != nil {
log.Panicf("-wrap-key invalid hex: %v", err)
}
if len(wrapKey) != wrap.KeyLen {
log.Panicf("-wrap-key must decode to %d bytes (got %d)", wrap.KeyLen, len(wrapKey))
}
}
log.Printf("Starting server listen=%s connect=%s vless=%t vless-bond=%t wrap=%t bond-autodetect=true", *listen, *connect, *vlessMode, *vlessBond, *wrapMode)
wrapKey := cfg.WrapKey
log.Printf("Starting server listen=%s connect=%s vless=%t vless-bond=%t wrap=%t bond-autodetect=true", cfg.Listen, cfg.Connect, cfg.VLESSMode, cfg.VLESSBond, cfg.WrapMode)
// Generate a certificate and private key to secure the connection
certificate, genErr := selfsign.GenerateSelfSigned()
if genErr != nil {
@@ -104,7 +82,7 @@ func main() {
dtls.WithConnectionIDGenerator(dtls.RandomCIDGenerator(8)),
}
var listener net.Listener
if *wrapMode {
if cfg.WrapMode {
log.Printf("WRAP mode enabled: listener only accepts clients with matching -wrap-key")
wrapListener, werr := wrap.Listen(addr, wrapKey)
if werr != nil {
@@ -163,10 +141,10 @@ func main() {
}
debugf("Handshake done")
if *vlessMode {
handleVLESSConnection(ctx, dtlsConn, *connect, *vlessBond)
if cfg.VLESSMode {
handleVLESSConnection(ctx, dtlsConn, cfg.Connect, cfg.VLESSBond)
} else {
handleUDPConnection(ctx, conn, *connect)
handleUDPConnection(ctx, conn, cfg.Connect)
}
debugf("Connection closed: %s\n", conn.RemoteAddr())