Browse Source

feat: TCP mode for VLESS support via KCP+smux over TURN tunnel

Add -tcp flag to client and server for forwarding TCP connections
(VLESS, etc.) through the TURN tunnel instead of UDP packets (WireGuard).
Stack: TCP → smux (mux) → KCP (reliability) → DTLS (encryption) → TURN
New dependencies: xtaci/kcp-go/v5, xtaci/smux
pull/74/head
Moroka8 2 months ago
parent
commit
b798ec5744
  1. 115
      README.md
  2. 260
      client/main.go
  3. 7
      docker-entrypoint.sh
  4. 10
      go.mod
  5. 91
      go.sum
  6. 271
      server/main.go
  7. 99
      tcputil/tcputil.go

115
README.md

@ -227,5 +227,120 @@ chmod 777 ./client-android
</details>
## VLESS (TCP-режим)
Если WireGuard блокируется DPI, можно использовать VLESS через флаг `-tcp`. В этом режиме вместо UDP-пакетов пробрасываются TCP-соединения через TURN-туннель с помощью KCP и smux.
### Настройка
1. На VPS установить Xray с VLESS inbound
2. Запустить `server` с флагом `-tcp`
3. На клиенте запустить `client` с флагом `-tcp`
4. Настроить Xray/v2rayN клиент с VLESS outbound на `127.0.0.1:9000`
### Сервер (VPS)
```
./server -listen 0.0.0.0:56000 -connect 127.0.0.1:443 -tcp
```
#### Docker
```
docker run -p 56000:56000/udp -e CONNECT_ADDR=127.0.0.1:443 -e TCP_MODE=true vk-turn-proxy
```
### Клиент
```
./client -peer <ip сервера>:56000 -vk-link <VK ссылка> -listen 127.0.0.1:9000 -tcp
```
<details>
<summary>
Xray клиент (config.json)
</summary>
```json
{
"inbounds": [
{
"protocol": "socks",
"listen": "127.0.0.1",
"port": 1080,
"settings": {
"udp": true
},
"sniffing": {
"enabled": true,
"destOverride": ["http", "tls"]
}
}
],
"outbounds": [
{
"protocol": "vless",
"settings": {
"vnext": [
{
"address": "127.0.0.1",
"port": 9000,
"users": [
{
"id": "<UUID>",
"encryption": "none"
}
]
}
]
},
"streamSettings": {
"network": "tcp",
"security": "none"
}
}
]
}
```
</details>
<details>
<summary>
Xray сервер (config.json)
</summary>
```json
{
"inbounds": [
{
"protocol": "vless",
"listen": "127.0.0.1",
"port": 443,
"settings": {
"clients": [
{
"id": "<тот же UUID>",
"level": 0
}
],
"decryption": "none"
}
}
],
"outbounds": [
{
"protocol": "freedom",
"settings": {
"domainStrategy": "UseIPv4"
}
}
]
}
```
</details>
> **Важно:** В TCP-режиме используется один TURN-поток. Для VK это ограничивает скорость ~5 Мбит/с.
## Direct mode
С флагом `-no-dtls` можно отправлять пакеты без обфускации DTLS и подключаться к обычным серверам Wireguard. Может привести к бану от вк/яндекса.

260
client/main.go

@ -10,14 +10,6 @@ import (
"encoding/json"
"flag"
"fmt"
"github.com/bschaatsbergen/dnsdialer"
"github.com/cbeuw/connutil"
"github.com/google/uuid"
"github.com/gorilla/websocket"
"github.com/pion/dtls/v3"
"github.com/pion/dtls/v3/pkg/crypto/selfsign"
"github.com/pion/logging"
"github.com/pion/turn/v5"
"io"
"log"
"net"
@ -29,6 +21,17 @@ import (
"sync/atomic"
"syscall"
"time"
"github.com/bschaatsbergen/dnsdialer"
"github.com/cacggghp/vk-turn-proxy/tcputil"
"github.com/cbeuw/connutil"
"github.com/google/uuid"
"github.com/gorilla/websocket"
"github.com/pion/dtls/v3"
"github.com/pion/dtls/v3/pkg/crypto/selfsign"
"github.com/pion/logging"
"github.com/pion/turn/v5"
"github.com/xtaci/smux"
)
type getCredsFunc func(string) (string, string, string, error)
@ -830,6 +833,7 @@ func main() { //nolint:cyclop
n := flag.Int("n", 0, "connections to TURN (default 16 for VK, 1 for Yandex)")
udp := flag.Bool("udp", false, "connect to TURN with UDP")
direct := flag.Bool("no-dtls", false, "connect without obfuscation. DO NOT USE")
tcpMode := flag.Bool("tcp", false, "TCP mode: forward TCP connections (for VLESS) instead of UDP packets")
flag.Parse()
if *peerAddr == "" {
log.Panicf("Need peer address!")
@ -879,6 +883,11 @@ func main() { //nolint:cyclop
getCreds,
}
if *tcpMode {
runTCPMode(ctx, params, peer, *listen)
return
}
listenConnChan := make(chan net.PacketConn)
listenConn, err := net.ListenPacket("udp", *listen) // nolint: noctx
if err != nil {
@ -936,3 +945,238 @@ func main() { //nolint:cyclop
wg1.Wait()
}
// runTCPMode implements TCP forwarding mode for VLESS.
// It establishes a DTLS tunnel through TURN, then creates a KCP+smux session
// on top, and forwards incoming TCP connections as smux streams.
func runTCPMode(ctx context.Context, tp *turnParams, peer *net.UDPAddr, listenAddr string) {
for {
select {
case <-ctx.Done():
return
default:
}
err := runTCPSession(ctx, tp, peer, listenAddr)
if err != nil {
log.Printf("TCP session error: %s, reconnecting...", err)
}
select {
case <-ctx.Done():
return
case <-time.After(2 * time.Second):
}
}
}
func runTCPSession(ctx context.Context, tp *turnParams, peer *net.UDPAddr, listenAddr string) error {
// 1. Get TURN credentials
user, pass, url, err := tp.getCreds(tp.link)
if err != nil {
return fmt.Errorf("get TURN creds: %w", err)
}
urlhost, urlport, err := net.SplitHostPort(url)
if err != nil {
return fmt.Errorf("parse TURN addr: %w", err)
}
if tp.host != "" {
urlhost = tp.host
}
if tp.port != "" {
urlport = tp.port
}
turnServerAddr := net.JoinHostPort(urlhost, urlport)
turnServerUdpAddr, err := net.ResolveUDPAddr("udp", turnServerAddr)
if err != nil {
return fmt.Errorf("resolve TURN addr: %w", err)
}
turnServerAddr = turnServerUdpAddr.String()
fmt.Println(turnServerUdpAddr.IP)
// 2. Connect to TURN server
var turnConn net.PacketConn
ctx1, cancel1 := context.WithTimeout(ctx, 5*time.Second)
defer cancel1()
if tp.udp {
conn, err := net.DialUDP("udp", nil, turnServerUdpAddr)
if err != nil {
return fmt.Errorf("dial TURN (udp): %w", err)
}
defer conn.Close()
turnConn = &connectedUDPConn{conn}
} else {
var d net.Dialer
conn, err := d.DialContext(ctx1, "tcp", turnServerAddr)
if err != nil {
return fmt.Errorf("dial TURN (tcp): %w", err)
}
defer conn.Close()
turnConn = turn.NewSTUNConn(conn)
}
// 3. Allocate TURN relay
var addrFamily turn.RequestedAddressFamily
if peer.IP.To4() != nil {
addrFamily = turn.RequestedAddressFamilyIPv4
} else {
addrFamily = turn.RequestedAddressFamilyIPv6
}
cfg := &turn.ClientConfig{
STUNServerAddr: turnServerAddr,
TURNServerAddr: turnServerAddr,
Conn: turnConn,
Username: user,
Password: pass,
RequestedAddressFamily: addrFamily,
LoggerFactory: logging.NewDefaultLoggerFactory(),
}
turnClient, err := turn.NewClient(cfg)
if err != nil {
return fmt.Errorf("create TURN client: %w", err)
}
defer turnClient.Close()
if err = turnClient.Listen(); err != nil {
return fmt.Errorf("TURN listen: %w", err)
}
relayConn, err := turnClient.Allocate()
if err != nil {
return fmt.Errorf("TURN allocate: %w", err)
}
defer relayConn.Close()
log.Printf("relayed-address=%s", relayConn.LocalAddr().String())
// 4. Establish DTLS over TURN relay
certificate, err := selfsign.GenerateSelfSigned()
if err != nil {
return fmt.Errorf("generate cert: %w", err)
}
// Create a connected PacketConn for DTLS: relay writes go to peer
dtlsPC := &relayPacketConn{relay: relayConn, peer: peer}
dtlsConfig := &dtls.Config{
Certificates: []tls.Certificate{certificate},
InsecureSkipVerify: true,
ExtendedMasterSecret: dtls.RequireExtendedMasterSecret,
CipherSuites: []dtls.CipherSuiteID{dtls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
ConnectionIDGenerator: dtls.OnlySendCIDGenerator(),
}
dtlsConn, err := dtls.Client(dtlsPC, peer, dtlsConfig)
if err != nil {
return fmt.Errorf("DTLS client create: %w", err)
}
ctx2, cancel2 := context.WithTimeout(ctx, 30*time.Second)
defer cancel2()
if err = dtlsConn.HandshakeContext(ctx2); err != nil {
dtlsConn.Close()
return fmt.Errorf("DTLS handshake: %w", err)
}
defer dtlsConn.Close()
log.Printf("DTLS connection established")
// 5. Create KCP session over DTLS
kcpSess, err := tcputil.NewKCPOverDTLS(dtlsConn, false)
if err != nil {
return fmt.Errorf("KCP session: %w", err)
}
defer kcpSess.Close()
log.Printf("KCP session established")
// 6. Create smux client session over KCP
smuxSess, err := smux.Client(kcpSess, tcputil.DefaultSmuxConfig())
if err != nil {
return fmt.Errorf("smux client: %w", err)
}
defer smuxSess.Close()
log.Printf("smux session established")
// 7. Listen for TCP connections and forward through smux
listener, err := net.Listen("tcp", listenAddr)
if err != nil {
return fmt.Errorf("TCP listen: %w", err)
}
context.AfterFunc(ctx, func() { listener.Close() })
log.Printf("TCP mode: listening on %s", listenAddr)
var wg sync.WaitGroup
for {
tcpConn, err := listener.Accept()
if err != nil {
select {
case <-ctx.Done():
wg.Wait()
return nil
default:
}
if smuxSess.IsClosed() {
wg.Wait()
return fmt.Errorf("smux session closed")
}
log.Printf("TCP accept error: %s", err)
continue
}
wg.Add(1)
go func(tc net.Conn) {
defer wg.Done()
defer tc.Close()
stream, err := smuxSess.OpenStream()
if err != nil {
log.Printf("smux open stream error: %s", err)
return
}
defer stream.Close()
pipe(ctx, tc, stream)
}(tcpConn)
}
}
// relayPacketConn wraps a TURN relay PacketConn to direct all writes to the peer.
type relayPacketConn struct {
relay net.PacketConn
peer net.Addr
}
func (r *relayPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
return r.relay.ReadFrom(b)
}
func (r *relayPacketConn) WriteTo(b []byte, _ net.Addr) (int, error) {
return r.relay.WriteTo(b, r.peer)
}
func (r *relayPacketConn) Close() error { return r.relay.Close() }
func (r *relayPacketConn) LocalAddr() net.Addr { return r.relay.LocalAddr() }
func (r *relayPacketConn) SetDeadline(t time.Time) error { return r.relay.SetDeadline(t) }
func (r *relayPacketConn) SetReadDeadline(t time.Time) error { return r.relay.SetReadDeadline(t) }
func (r *relayPacketConn) SetWriteDeadline(t time.Time) error { return r.relay.SetWriteDeadline(t) }
// pipe copies data bidirectionally between two connections.
func pipe(ctx context.Context, c1, c2 net.Conn) {
ctx2, cancel := context.WithCancel(ctx)
context.AfterFunc(ctx2, func() {
c1.SetDeadline(time.Now())
c2.SetDeadline(time.Now())
})
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
defer cancel()
io.Copy(c1, c2)
}()
go func() {
defer wg.Done()
defer cancel()
io.Copy(c2, c1)
}()
wg.Wait()
c1.SetDeadline(time.Time{})
c2.SetDeadline(time.Time{})
}

7
docker-entrypoint.sh

@ -3,4 +3,9 @@ set -e
CONNECT="${CONNECT_ADDR:?CONNECT_ADDR is required}"
exec ./vk-turn-proxy -listen 0.0.0.0:56000 -connect "$CONNECT"
TCP_FLAG=""
if [ "${TCP_MODE}" = "true" ]; then
TCP_FLAG="-tcp"
fi
exec ./vk-turn-proxy -listen 0.0.0.0:56000 -connect "$CONNECT" $TCP_FLAG

10
go.mod

@ -10,14 +10,23 @@ require (
github.com/pion/dtls/v3 v3.0.10
github.com/pion/logging v0.2.4
github.com/pion/turn/v5 v5.0.2
github.com/xtaci/kcp-go/v5 v5.6.18
github.com/xtaci/smux v1.5.34
)
require (
github.com/google/go-cmp v0.7.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/klauspost/reedsolomon v1.12.4 // indirect
github.com/miekg/dns v1.1.69 // indirect
github.com/pion/randutil v0.1.0 // indirect
github.com/pion/stun/v3 v3.1.1 // indirect
github.com/pion/transport/v4 v4.0.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/templexxx/cpu v0.1.1 // indirect
github.com/templexxx/xorsimd v0.4.3 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/wlynxg/anet v0.0.5 // indirect
golang.org/x/crypto v0.47.0 // indirect
golang.org/x/mod v0.30.0 // indirect
@ -25,4 +34,5 @@ require (
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/tools v0.39.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect
)

91
go.sum

@ -1,17 +1,44 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/bschaatsbergen/dnsdialer v0.0.0-20251225104348-3e7610e8ea45 h1:0b2i5TvZm8FVcuHP1288k+DEu1XM26DtRjcidOxpGXs=
github.com/bschaatsbergen/dnsdialer v0.0.0-20251225104348-3e7610e8ea45/go.mod h1:NU7MdmhQD8Ounc0760w90fL6nxI2lxjlnIaN6qWzNIU=
github.com/cbeuw/connutil v1.0.1 h1:LWuNYjwm7JEDYG/ISAO1TfU4G+q2dA5NhR97eq2roCA=
github.com/cbeuw/connutil v1.0.1/go.mod h1:lKofNtrW7Atmosgp1eNnTt2j2NjA2IkifapgLVI1QtA=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/klauspost/reedsolomon v1.12.4 h1:5aDr3ZGoJbgu/8+j45KtUJxzYm8k08JGtB9Wx1VQ4OA=
github.com/klauspost/reedsolomon v1.12.4/go.mod h1:d3CzOMOt0JXGIFZm1StgkyF14EYr3xneR2rNWo7NcMU=
github.com/miekg/dns v1.1.69 h1:Kb7Y/1Jo+SG+a2GtfoFUfDkG//csdRPwRLkCsxDG9Sc=
github.com/miekg/dns v1.1.69/go.mod h1:7OyjD9nEba5OkqQ/hB4fy3PIoxafSZJtducccIelz3g=
github.com/pion/dtls/v3 v3.0.10 h1:k9ekkq1kaZoxnNEbyLKI8DI37j/Nbk1HWmMuywpQJgg=
@ -26,33 +53,93 @@ github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k
github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM=
github.com/pion/turn/v5 v5.0.2 h1:GHlDk+fiegz+yibb3ch+tK+iPFokoVWiM+aVJakySqA=
github.com/pion/turn/v5 v5.0.2/go.mod h1:cumcsSEF2ytAtDhDwkYgYhv1uJ3AOP7a4pFt0NL/snY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/templexxx/cpu v0.1.1 h1:isxHaxBXpYFWnk2DReuKkigaZyrjs2+9ypIdGP4h+HI=
github.com/templexxx/cpu v0.1.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
github.com/templexxx/xorsimd v0.4.3 h1:9AQTFHd7Bhk3dIT7Al2XeBX5DWOvsUPZCuhyAtNbHjU=
github.com/templexxx/xorsimd v0.4.3/go.mod h1:oZQcD6RFDisW2Am58dSAGwwL6rHjbzrlu25VDqfWkQg=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
github.com/xtaci/kcp-go/v5 v5.6.18 h1:7oV4mc272pcnn39/13BB11Bx7hJM4ogMIEokJYVWn4g=
github.com/xtaci/kcp-go/v5 v5.6.18/go.mod h1:75S1AKYYzNUSXIv30h+jPKJYZUwqpfvLshu63nCNSOM=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
github.com/xtaci/smux v1.5.34 h1:OUA9JaDFHJDT8ZT3ebwLWPAgEfE6sWo2LaTy3anXqwg=
github.com/xtaci/smux v1.5.34/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

271
server/main.go

@ -5,6 +5,7 @@ import (
"crypto/tls"
"flag"
"fmt"
"io"
"log"
"net"
"os"
@ -13,13 +14,16 @@ import (
"syscall"
"time"
"github.com/cacggghp/vk-turn-proxy/tcputil"
"github.com/pion/dtls/v3"
"github.com/pion/dtls/v3/pkg/crypto/selfsign"
"github.com/xtaci/smux"
)
func main() {
listen := flag.String("listen", "0.0.0.0:56000", "listen on ip:port")
connect := flag.String("connect", "", "connect to ip:port")
tcpMode := flag.Bool("tcp", false, "TCP mode: forward TCP connections (for VLESS) instead of UDP packets")
flag.Parse()
ctx, cancel := context.WithCancel(context.Background())
@ -47,10 +51,6 @@ func main() {
panic(err)
}
//
// Everything below is the pion-DTLS API! Thanks for using it ❤️.
//
// Prepare the configuration of the DTLS connection
config := &dtls.Config{
Certificates: []tls.Certificate{certificate},
@ -59,7 +59,7 @@ func main() {
ConnectionIDGenerator: dtls.RandomCIDGenerator(8),
}
// Connect to a DTLS server
// Listen for DTLS connections
listener, err := dtls.Listen("udp", addr, config)
if err != nil {
panic(err)
@ -94,11 +94,7 @@ func main() {
log.Printf("failed to close incoming connection: %s", closeErr)
}
}()
var err error = nil
log.Printf("Connection from %s\n", conn.RemoteAddr())
// `conn` is of type `net.Conn` but may be casted to `dtls.Conn`
// using `dtlsConn := conn.(*dtls.Conn)` in order to to expose
// functions like `ConnectionState` etc.
// Perform the handshake with a 30-second timeout
ctx1, cancel1 := context.WithTimeout(ctx, 30*time.Second)
@ -109,7 +105,7 @@ func main() {
return
}
log.Println("Start handshake")
if err = dtlsConn.HandshakeContext(ctx1); err != nil {
if err := dtlsConn.HandshakeContext(ctx1); err != nil {
log.Println(err)
cancel1()
return
@ -117,93 +113,180 @@ func main() {
cancel1()
log.Println("Handshake done")
serverConn, err := net.Dial("udp", *connect)
if *tcpMode {
handleTCPConnection(ctx, dtlsConn, *connect)
} else {
handleUDPConnection(ctx, conn, *connect)
}
log.Printf("Connection closed: %s\n", conn.RemoteAddr())
}(conn)
}
}
// handleUDPConnection forwards DTLS packets to a UDP backend (WireGuard).
func handleUDPConnection(ctx context.Context, conn net.Conn, connectAddr string) {
serverConn, err := net.Dial("udp", connectAddr)
if err != nil {
log.Println(err)
return
}
defer func() {
if err = serverConn.Close(); err != nil {
log.Printf("failed to close outgoing connection: %s", err)
}
}()
var wg sync.WaitGroup
wg.Add(2)
ctx2, cancel2 := context.WithCancel(ctx)
context.AfterFunc(ctx2, func() {
if err := conn.SetDeadline(time.Now()); err != nil {
log.Printf("failed to set incoming deadline: %s", err)
}
if err := serverConn.SetDeadline(time.Now()); err != nil {
log.Printf("failed to set outgoing deadline: %s", err)
}
})
go func() {
defer wg.Done()
defer cancel2()
buf := make([]byte, 1600)
for {
select {
case <-ctx2.Done():
return
default:
}
if err1 := conn.SetReadDeadline(time.Now().Add(time.Minute * 30)); err1 != nil {
log.Printf("Failed: %s", err1)
return
}
n, err1 := conn.Read(buf)
if err1 != nil {
log.Printf("Failed: %s", err1)
return
}
if err1 := serverConn.SetWriteDeadline(time.Now().Add(time.Minute * 30)); err1 != nil {
log.Printf("Failed: %s", err1)
return
}
_, err1 = serverConn.Write(buf[:n])
if err1 != nil {
log.Printf("Failed: %s", err1)
return
}
}
}()
go func() {
defer wg.Done()
defer cancel2()
buf := make([]byte, 1600)
for {
select {
case <-ctx2.Done():
return
default:
}
if err1 := serverConn.SetReadDeadline(time.Now().Add(time.Minute * 30)); err1 != nil {
log.Printf("Failed: %s", err1)
return
}
n, err1 := serverConn.Read(buf)
if err1 != nil {
log.Printf("Failed: %s", err1)
return
}
if err1 := conn.SetWriteDeadline(time.Now().Add(time.Minute * 30)); err1 != nil {
log.Printf("Failed: %s", err1)
return
}
_, err1 = conn.Write(buf[:n])
if err1 != nil {
log.Printf("Failed: %s", err1)
return
}
}
}()
wg.Wait()
}
// handleTCPConnection creates a KCP+smux session over DTLS and forwards
// each smux stream as a TCP connection to the backend (Xray/VLESS).
func handleTCPConnection(ctx context.Context, dtlsConn net.Conn, connectAddr string) {
// 1. Create KCP session over DTLS
kcpSess, err := tcputil.NewKCPOverDTLS(dtlsConn, true)
if err != nil {
log.Printf("KCP session error: %s", err)
return
}
defer kcpSess.Close()
log.Printf("KCP session established (server)")
// 2. Create smux server session over KCP
smuxSess, err := smux.Server(kcpSess, tcputil.DefaultSmuxConfig())
if err != nil {
log.Printf("smux server error: %s", err)
return
}
defer smuxSess.Close()
log.Printf("smux session established (server)")
// 3. Accept smux streams and forward to backend via TCP
var wg sync.WaitGroup
for {
stream, err := smuxSess.AcceptStream()
if err != nil {
select {
case <-ctx.Done():
default:
log.Printf("smux accept error: %s", err)
}
break
}
wg.Add(1)
go func(s *smux.Stream) {
defer wg.Done()
defer s.Close()
// Connect to backend (Xray/VLESS)
backendConn, err := net.DialTimeout("tcp", connectAddr, 10*time.Second)
if err != nil {
log.Println(err)
log.Printf("backend dial error: %s", err)
return
}
defer func() {
if err = serverConn.Close(); err != nil {
log.Printf("failed to close outgoing connection: %s", err)
return
}
}()
defer backendConn.Close()
var wg sync.WaitGroup
wg.Add(2)
ctx2, cancel2 := context.WithCancel(ctx)
context.AfterFunc(ctx2, func() {
if err := conn.SetDeadline(time.Now()); err != nil {
log.Printf("failed to set incoming deadline: %s", err)
}
if err := serverConn.SetDeadline(time.Now()); err != nil {
log.Printf("failed to set outgoing deadline: %s", err)
}
})
go func() {
defer wg.Done()
defer cancel2()
buf := make([]byte, 1600)
for {
select {
case <-ctx2.Done():
return
default:
}
if err1 := conn.SetReadDeadline(time.Now().Add(time.Minute * 30)); err1 != nil {
log.Printf("Failed: %s", err1)
return
}
n, err1 := conn.Read(buf)
if err1 != nil {
log.Printf("Failed: %s", err1)
return
}
if err1 := serverConn.SetWriteDeadline(time.Now().Add(time.Minute * 30)); err1 != nil {
log.Printf("Failed: %s", err1)
return
}
_, err1 = serverConn.Write(buf[:n])
if err1 != nil {
log.Printf("Failed: %s", err1)
return
}
}
}()
go func() {
defer wg.Done()
defer cancel2()
buf := make([]byte, 1600)
for {
select {
case <-ctx2.Done():
return
default:
}
if err1 := serverConn.SetReadDeadline(time.Now().Add(time.Minute * 30)); err1 != nil {
log.Printf("Failed: %s", err1)
return
}
n, err1 := serverConn.Read(buf)
if err1 != nil {
log.Printf("Failed: %s", err1)
return
}
if err1 := conn.SetWriteDeadline(time.Now().Add(time.Minute * 30)); err1 != nil {
log.Printf("Failed: %s", err1)
return
}
_, err1 = conn.Write(buf[:n])
if err1 != nil {
log.Printf("Failed: %s", err1)
return
}
}
}()
wg.Wait()
log.Printf("Connection closed: %s\n", conn.RemoteAddr())
}(conn)
// Bidirectional copy
pipeConn(ctx, s, backendConn)
}(stream)
}
wg.Wait()
}
// pipeConn copies data bidirectionally between two connections.
func pipeConn(ctx context.Context, c1, c2 net.Conn) {
ctx2, cancel := context.WithCancel(ctx)
context.AfterFunc(ctx2, func() {
c1.SetDeadline(time.Now())
c2.SetDeadline(time.Now())
})
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
defer cancel()
io.Copy(c1, c2)
}()
go func() {
defer wg.Done()
defer cancel()
io.Copy(c2, c1)
}()
wg.Wait()
c1.SetDeadline(time.Time{})
c2.SetDeadline(time.Time{})
}

99
tcputil/tcputil.go

@ -0,0 +1,99 @@
package tcputil
import (
"net"
"time"
"github.com/xtaci/kcp-go/v5"
"github.com/xtaci/smux"
)
// DtlsPacketConn wraps a net.Conn (DTLS) as a net.PacketConn for KCP.
// Each DTLS Read/Write preserves message boundaries (datagram semantics).
type DtlsPacketConn struct {
conn net.Conn
}
func NewDtlsPacketConn(conn net.Conn) *DtlsPacketConn {
return &DtlsPacketConn{conn: conn}
}
func (d *DtlsPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, err := d.conn.Read(b)
return n, d.conn.RemoteAddr(), err
}
func (d *DtlsPacketConn) WriteTo(b []byte, _ net.Addr) (int, error) {
return d.conn.Write(b)
}
func (d *DtlsPacketConn) Close() error {
return d.conn.Close()
}
func (d *DtlsPacketConn) LocalAddr() net.Addr {
return d.conn.LocalAddr()
}
func (d *DtlsPacketConn) SetDeadline(t time.Time) error {
return d.conn.SetDeadline(t)
}
func (d *DtlsPacketConn) SetReadDeadline(t time.Time) error {
return d.conn.SetReadDeadline(t)
}
func (d *DtlsPacketConn) SetWriteDeadline(t time.Time) error {
return d.conn.SetWriteDeadline(t)
}
// NewKCPOverDTLS creates a KCP session over a DTLS connection.
// isServer: true for server-side (listener), false for client-side (dialer).
func NewKCPOverDTLS(dtlsConn net.Conn, isServer bool) (*kcp.UDPSession, error) {
pc := NewDtlsPacketConn(dtlsConn)
block, _ := kcp.NewNoneBlockCrypt(nil) // DTLS already encrypts
var sess *kcp.UDPSession
var err error
if isServer {
// Server: listen on the PacketConn and accept one session
listener, err := kcp.ServeConn(block, 0, 0, pc)
if err != nil {
return nil, err
}
listener.SetDeadline(time.Now().Add(30 * time.Second))
sess, err = listener.AcceptKCP()
if err != nil {
return nil, err
}
} else {
// Client: dial through the PacketConn
sess, err = kcp.NewConn2(dtlsConn.RemoteAddr(), block, 0, 0, pc)
if err != nil {
return nil, err
}
}
// Tune KCP for TURN tunnel:
// - NoDelay mode for lower latency
// - Window sizes suitable for ~5Mbit/s
sess.SetNoDelay(1, 20, 2, 1) // nodelay, interval(ms), resend, nc
sess.SetWindowSize(256, 256)
sess.SetMtu(1200) // conservative MTU to fit inside DTLS+TURN
sess.SetACKNoDelay(true)
sess.SetStreamMode(true)
return sess, nil
}
// DefaultSmuxConfig returns smux config tuned for TURN tunnel.
func DefaultSmuxConfig() *smux.Config {
cfg := smux.DefaultConfig()
cfg.MaxReceiveBuffer = 4 * 1024 * 1024
cfg.MaxStreamBuffer = 1 * 1024 * 1024
cfg.KeepAliveInterval = 10 * time.Second
cfg.KeepAliveTimeout = 30 * time.Second
return cfg
}
Loading…
Cancel
Save