Browse Source

Add files via upload

ish_v.1.2.1
cacggghp 5 months ago
committed by GitHub
parent
commit
6580f84357
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 4
      README.md
  2. 546
      client/main.go
  3. 20
      go.mod
  4. 34
      go.sum
  5. 27
      routes.ps1
  6. 5
      routes.sh
  7. 189
      server/main.go

4
README.md

@ -1,5 +1,5 @@
# vk-turn-proxy # vk-turn-proxy
Проброс трафика WireGuard через TURN сервера VK звонков. Пакеты шифруются DTLS 1.2, затем параллельными потоками через TCP или UDP отправляются на TURN сервер по протоколу STUN ChannelData. Оттуда по UDP отправляются на ваш сервер, где расшифровываются и передаются в WireGuard. Логин/пароль от TURN генерируются из ссылки на звонок. Проброс трафика WireGuard через TURN сервера VK звонков. Пакеты шифруются DTLS 1.2, затем параллельными потоками через TCP или UDP отправляются на TURN сервер по протоколу STUN Datachannel. Оттуда по UDP отправляются на ваш сервер, где расшифровываются и передаются в WireGuard. Логин/пароль от TURN генерируются из ссылки на звонок.
Только для учебных целей! Только для учебных целей!
## Настройка ## Настройка
@ -40,4 +40,4 @@
Если не работает TCP, попробуйте добавить флаг `-udp`. Если не работает TCP, попробуйте добавить флаг `-udp`.
Добавьте флаг `-n 1` для более стабильного подключения в 1 поток (ограничение 5 Мбит/с) Добавьте флаг `-n 1` для более стабильного подключения в 1 поток (ограничение 5 Мбит/с)

546
client/main.go

@ -0,0 +1,546 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package main
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"flag"
"fmt"
"io"
"log"
"net"
"net/http"
"os"
"os/signal"
"sync"
"sync/atomic"
"syscall"
"time"
"github.com/cbeuw/connutil"
"github.com/google/uuid"
"github.com/pion/dtls/v3"
"github.com/pion/dtls/v3/pkg/crypto/selfsign"
"github.com/pion/logging"
"github.com/pion/turn/v4"
)
func doRequest(data string, url string) (resp map[string]interface{}, err error) {
client := &http.Client{
Timeout: 20 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 90 * time.Second,
},
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(data)))
if err != nil {
return nil, err
}
req.Header.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:144.0) Gecko/20100101 Firefox/144.0")
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
httpResp, err := client.Do(req)
if err != nil {
return nil, err
}
defer httpResp.Body.Close()
body, err := io.ReadAll(httpResp.Body)
if err != nil {
return nil, err
}
err = json.Unmarshal(body, &resp)
if err != nil {
return nil, err
}
return resp, nil
}
func getCreds(link string) (string, string, string, error) {
var resp map[string]interface{}
defer func() {
if r := recover(); r != nil {
log.Panicf("get TURN creds error: %v\n\n", resp)
}
}()
data := "client_secret=QbYic1K3lEV5kTGiqlq2&client_id=6287487&scopes=audio_anonymous%2Cvideo_anonymous%2Cphotos_anonymous%2Cprofile_anonymous&isApiOauthAnonymEnabled=false&version=1&app_id=6287487"
url := "https://login.vk.ru/?act=get_anonym_token"
resp, err := doRequest(data, url)
if err != nil {
return "", "", "", fmt.Errorf("request error:%s", err)
}
token1 := resp["data"].(map[string]interface{})["access_token"].(string)
data = fmt.Sprintf("access_token=%s", token1)
url = "https://api.vk.ru/method/calls.getAnonymousAccessTokenPayload?v=5.264&client_id=6287487"
resp, err = doRequest(data, url)
if err != nil {
return "", "", "", fmt.Errorf("request error:%s", err)
}
token2 := resp["response"].(map[string]interface{})["payload"].(string)
data = fmt.Sprintf("client_id=6287487&token_type=messages&payload=%s&client_secret=QbYic1K3lEV5kTGiqlq2&version=1&app_id=6287487", token2)
url = "https://login.vk.ru/?act=get_anonym_token"
resp, err = doRequest(data, url)
if err != nil {
return "", "", "", fmt.Errorf("request error:%s", err)
}
token3 := resp["data"].(map[string]interface{})["access_token"].(string)
data = fmt.Sprintf("vk_join_link=https://vk.com/call/join/%s&name=123&access_token=%s", link, token3)
url = "https://api.vk.ru/method/calls.getAnonymousToken?v=5.264"
resp, err = doRequest(data, url)
if err != nil {
return "", "", "", fmt.Errorf("request error:%s", err)
}
token4 := resp["response"].(map[string]interface{})["token"].(string)
data = fmt.Sprintf("%s%s%s", "session_data=%7B%22version%22%3A2%2C%22device_id%22%3A%22", uuid.New(), "%22%2C%22client_version%22%3A1.1%2C%22client_type%22%3A%22SDK_JS%22%7D&method=auth.anonymLogin&format=JSON&application_key=CGMMEJLGDIHBABABA")
url = "https://calls.okcdn.ru/fb.do"
resp, err = doRequest(data, url)
if err != nil {
return "", "", "", fmt.Errorf("request error:%s", err)
}
token5 := resp["session_key"].(string)
data = fmt.Sprintf("joinLink=%s&isVideo=false&protocolVersion=5&anonymToken=%s&method=vchat.joinConversationByLink&format=JSON&application_key=CGMMEJLGDIHBABABA&session_key=%s", link, token4, token5)
url = "https://calls.okcdn.ru/fb.do"
resp, err = doRequest(data, url)
if err != nil {
return "", "", "", fmt.Errorf("request error:%s", err)
}
user := resp["turn_server"].(map[string]interface{})["username"].(string)
pass := resp["turn_server"].(map[string]interface{})["credential"].(string)
turn := resp["turn_server"].(map[string]interface{})["urls"].([]interface{})[0].(string)
return user, pass, turn, nil
}
func dtlsFunc(ctx context.Context, conn net.PacketConn, peer *net.UDPAddr) (net.Conn, error) {
certificate, err := selfsign.GenerateSelfSigned()
if err != nil {
return nil, err
}
config := &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(),
}
ctx1, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
dtlsConn, err := dtls.Client(conn, peer, config)
if err != nil {
return nil, err
}
if err := dtlsConn.HandshakeContext(ctx1); err != nil {
return nil, err
}
return dtlsConn, nil
}
func oneDtlsConnection(ctx context.Context, peer *net.UDPAddr, listenConn net.PacketConn, connchan chan<- net.PacketConn, okchan chan<- struct{}, c1 chan<- error) {
var err error = nil
defer func() { c1 <- err }()
dtlsctx, dtlscancel := context.WithCancel(ctx)
defer dtlscancel()
var conn1, conn2 net.PacketConn
conn1, conn2 = connutil.AsyncPacketPipe()
go func() {
for {
select {
case <-dtlsctx.Done():
return
case connchan <- conn2:
}
}
}()
dtlsConn, err1 := dtlsFunc(dtlsctx, conn1, peer)
if err1 != nil {
err = fmt.Errorf("failed to connect DTLS: %s", err1)
return
}
defer func() {
if closeErr := dtlsConn.Close(); closeErr != nil {
err = fmt.Errorf("failed to close DTLS connection: %s", closeErr)
return
}
log.Printf("Closed DTLS connection\n")
}()
log.Printf("Established DTLS connection!\n")
go func() {
for {
select {
case <-dtlsctx.Done():
return
case okchan <- struct{}{}:
}
}
}()
wg := sync.WaitGroup{}
wg.Add(2)
context.AfterFunc(dtlsctx, func() {
listenConn.SetDeadline(time.Now())
dtlsConn.SetDeadline(time.Now())
})
var addr atomic.Value
// Start read-loop on listenConn
go func() {
defer wg.Done()
defer dtlscancel()
buf := make([]byte, 1600)
for {
select {
case <-dtlsctx.Done():
return
default:
}
n, addr1, err1 := listenConn.ReadFrom(buf)
if err1 != nil {
log.Printf("Failed: %s", err1)
return
}
addr.Store(addr1) // store peer
_, err1 = dtlsConn.Write(buf[:n])
if err1 != nil {
log.Printf("Failed: %s", err1)
return
}
}
}()
// Start read-loop on dtlsConn
go func() {
defer wg.Done()
defer dtlscancel()
buf := make([]byte, 1600)
for {
select {
case <-dtlsctx.Done():
return
default:
}
n, err1 := dtlsConn.Read(buf)
if err1 != nil {
log.Printf("Failed: %s", err1)
return
}
addr1, ok := addr.Load().(net.Addr)
if !ok {
log.Printf("Failed: no listener ip")
return
}
_, err1 = listenConn.WriteTo(buf[:n], addr1)
if err1 != nil {
log.Printf("Failed: %s", err1)
return
}
}
}()
wg.Wait()
listenConn.SetDeadline(time.Time{})
dtlsConn.SetDeadline(time.Time{})
}
func oneTurnConnection(ctx context.Context, host string, port string, link string, udp bool, realm string, peer net.Addr, conn2 net.PacketConn, c chan<- error) {
var err error = nil
defer func() { c <- err }()
user, pass, url, err1 := getCreds(link)
if err1 != nil {
err = fmt.Errorf("failed to get TURN credentials: %s", err1)
return
}
var turnServerAddr string
if host == "" || port == "" {
turnServerAddr = url[5:]
} else {
turnServerAddr = net.JoinHostPort(host, port)
}
turnServerUdpAddr, err1 := net.ResolveUDPAddr("udp", turnServerAddr)
if err1 != nil {
err = fmt.Errorf("failed to resolve TURN server address: %s", err1)
return
}
turnServerAddr = turnServerUdpAddr.String()
fmt.Println(turnServerUdpAddr.IP)
// Dial TURN Server
var cfg *turn.ClientConfig
var turnConn net.PacketConn
var d net.Dialer
ctx1, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
if udp {
turnConn, err1 = net.ListenPacket("udp", "") // nolint: noctx
if err1 != nil {
err = fmt.Errorf("failed to connect to TURN server: %s", err1)
return
}
defer func() {
if err1 = turnConn.Close(); err1 != nil {
err = fmt.Errorf("failed to close TURN server connection: %s", err1)
return
}
}()
} else {
conn, err2 := d.DialContext(ctx1, "tcp", turnServerAddr) // nolint: noctx
if err2 != nil {
err = fmt.Errorf("failed to connect to TURN server: %s", err2)
return
}
defer func() {
if err1 = conn.Close(); err1 != nil {
err = fmt.Errorf("failed to close TURN server connection: %s", err1)
return
}
}()
turnConn = turn.NewSTUNConn(conn)
}
// Start a new TURN Client and wrap our net.Conn in a STUNConn
// This allows us to simulate datagram based communication over a net.Conn
cfg = &turn.ClientConfig{
STUNServerAddr: turnServerAddr,
TURNServerAddr: turnServerAddr,
Conn: turnConn,
Username: user,
Password: pass,
Realm: realm,
LoggerFactory: logging.NewDefaultLoggerFactory(),
}
client, err1 := turn.NewClient(cfg)
if err1 != nil {
err = fmt.Errorf("failed to create TURN client: %s", err1)
return
}
defer client.Close()
// Start listening on the conn provided.
err1 = client.Listen()
if err1 != nil {
err = fmt.Errorf("failed to listen: %s", err1)
return
}
// Allocate a relay socket on the TURN server. On success, it
// will return a net.PacketConn which represents the remote
// socket.
relayConn, err1 := client.Allocate()
if err1 != nil {
err = fmt.Errorf("failed to allocate: %s", err1)
return
}
defer func() {
if err1 := relayConn.Close(); err1 != nil {
err = fmt.Errorf("failed to close TURN allocated connection: %s", err1)
}
}()
// The relayConn's local address is actually the transport
// address assigned on the TURN server.
log.Printf("relayed-address=%s", relayConn.LocalAddr().String())
wg := sync.WaitGroup{}
wg.Add(2)
turnctx, turncancel := context.WithCancel(context.Background())
context.AfterFunc(turnctx, func() {
relayConn.SetDeadline(time.Now())
conn2.SetDeadline(time.Now())
})
// Start read-loop on conn2 (output of DTLS)
go func() {
defer wg.Done()
defer turncancel()
buf := make([]byte, 1600)
for {
select {
case <-turnctx.Done():
return
default:
}
n, _, err1 := conn2.ReadFrom(buf)
if err1 != nil {
log.Printf("Failed: %s", err1)
return
}
_, err1 = relayConn.WriteTo(buf[:n], peer)
if err1 != nil {
log.Printf("Failed: %s", err1)
return
}
}
}()
// Start read-loop on relayConn
go func() {
defer wg.Done()
defer turncancel()
buf := make([]byte, 1600)
for {
select {
case <-turnctx.Done():
return
default:
}
n, _, err1 := relayConn.ReadFrom(buf)
if err1 != nil {
log.Printf("Failed: %s", err1)
return
}
_, err1 = conn2.WriteTo(buf[:n], nil)
if err1 != nil {
log.Printf("Failed: %s", err1)
return
}
}
}()
wg.Wait()
relayConn.SetDeadline(time.Time{})
conn2.SetDeadline(time.Time{})
}
func main() { //nolint:cyclop
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, syscall.SIGTERM, syscall.SIGINT)
go func() {
<-signalChan
log.Printf("Terminating...\n")
cancel()
select {
case <-signalChan:
case <-time.After(5 * time.Second):
}
log.Fatalf("Exit...\n")
}()
host := flag.String("turn", "", "override TURN server ip")
port := flag.String("port", "19302", "override TURN port")
listen := flag.String("listen", "127.0.0.1:9000", "listen on ip:port")
realm := flag.String("realm", "call6-7.vkuser.net", "TURN Realm")
link := flag.String("link", "", "VK calls invite link \"https://vk.com/call/join/...\"")
peerAddr := flag.String("peer", "", "peer server address (host:port)")
n := flag.Int("n", 16, "connections to TURN")
udp := flag.Bool("udp", false, "connect to TURN with UDP")
flag.Parse()
if *peerAddr == "" {
log.Panicf("Need peer address!")
}
if *link == "" {
log.Panicf("Need invite link!")
}
peer, err := net.ResolveUDPAddr("udp", *peerAddr)
if err != nil {
panic(err)
}
*link = (*link)[len(*link)-43:]
listenConn, err := net.ListenPacket("udp", *listen) // nolint: noctx
if err != nil {
log.Panicf("Failed to listen: %s", err)
}
defer func() {
if closeErr := listenConn.Close(); closeErr != nil {
log.Panicf("Failed to close local connection: %s", closeErr)
}
}()
okchan := make(chan struct{})
connchan := make(chan net.PacketConn)
wg1 := sync.WaitGroup{}
wg1.Go(func() {
for {
c1 := make(chan error)
go oneDtlsConnection(ctx, peer, listenConn, connchan, okchan, c1)
if err := <-c1; err != nil {
log.Printf("%s", err)
}
select {
case <-ctx.Done():
return
default:
}
}
})
t := time.Tick(100 * time.Millisecond)
wg1.Go(func() {
for {
select {
case <-ctx.Done():
return
case conn2 := <-connchan:
select {
case <-t:
c := make(chan error)
go oneTurnConnection(ctx, *host, *port, *link, *udp, *realm, peer, conn2, c)
if err := <-c; err != nil {
log.Printf("%s", err)
}
default:
}
}
}
})
for i := 0; i < *n-1; i++ {
wg1.Go(func() {
for {
select {
case <-ctx.Done():
return
case <-okchan:
select {
case conn2 := <-connchan:
select {
case <-t:
c2 := make(chan error)
go oneTurnConnection(ctx, *host, *port, *link, *udp, *realm, peer, conn2, c2)
if err := <-c2; err != nil {
log.Printf("%s", err)
}
default:
}
default:
}
}
}
})
}
wg1.Wait()
}

20
go.mod

@ -0,0 +1,20 @@
module github.com/cacggghp/vk-turn-proxy
go 1.25.5
require (
github.com/cbeuw/connutil v1.0.1
github.com/google/uuid v1.6.0
github.com/pion/dtls/v3 v3.0.10
github.com/pion/logging v0.2.4
github.com/pion/turn/v4 v4.1.4
)
require (
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/wlynxg/anet v0.0.5 // indirect
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/sys v0.40.0 // indirect
)

34
go.sum

@ -0,0 +1,34 @@
github.com/cbeuw/connutil v1.0.1 h1:LWuNYjwm7JEDYG/ISAO1TfU4G+q2dA5NhR97eq2roCA=
github.com/cbeuw/connutil v1.0.1/go.mod h1:lKofNtrW7Atmosgp1eNnTt2j2NjA2IkifapgLVI1QtA=
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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/pion/dtls/v3 v3.0.10 h1:k9ekkq1kaZoxnNEbyLKI8DI37j/Nbk1HWmMuywpQJgg=
github.com/pion/dtls/v3 v3.0.10/go.mod h1:YEmmBYIoBsY3jmG56dsziTv/Lca9y4Om83370CXfqJ8=
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/stun/v3 v3.1.1 h1:CkQxveJ4xGQjulGSROXbXq94TAWu8gIX2dT+ePhUkqw=
github.com/pion/stun/v3 v3.1.1/go.mod h1:qC1DfmcCTQjl9PBaMa5wSn3x9IPmKxSdcCsxBcDBndM=
github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o=
github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM=
github.com/pion/turn/v4 v4.1.4 h1:EU11yMXKIsK43FhcUnjLlrhE4nboHZq+TXBIi3QpcxQ=
github.com/pion/turn/v4 v4.1.4/go.mod h1:ES1DXVFKnOhuDkqn9hn5VJlSWmZPaRJLyBXoOeO/BmQ=
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/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

27
routes.ps1

@ -0,0 +1,27 @@
# Получаем default gateway (IPv4)
$gateway = Get-NetRoute `
-DestinationPrefix "0.0.0.0/0" `
| Sort-Object RouteMetric `
| Select-Object -First 1 -ExpandProperty NextHop
if (-not $gateway) {
Write-Error "Не удалось определить default gateway"
exit 1
}
Write-Host "Default gateway: $gateway"
# Читаем адреса из stdin
$input | ForEach-Object {
$addr = $_.Trim()
if ($addr -eq "") { return }
Write-Host "Добавляем маршрут к $addr через $gateway"
New-NetRoute `
-DestinationPrefix "$addr/32" `
-NextHop $gateway `
-PolicyStore ActiveStore `
-ErrorAction Stop
}

5
routes.sh

@ -0,0 +1,5 @@
#!/bin/bash
gateway="$(ip -o -4 route show to default | awk '/via/ {print $3}' | head -1)"
while read -r remote; do
sudo ip r add $remote via $gateway
done

189
server/main.go

@ -0,0 +1,189 @@
package main
import (
"context"
"crypto/tls"
"flag"
"fmt"
"log"
"net"
"os"
"os/signal"
"sync"
"syscall"
"time"
"github.com/pion/dtls/v3"
"github.com/pion/dtls/v3/pkg/crypto/selfsign"
)
func main() {
listen := flag.String("listen", "0.0.0.0:56000", "listen on ip:port")
connect := flag.String("connect", "", "connect to ip:port")
flag.Parse()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, syscall.SIGTERM, syscall.SIGINT)
go func() {
<-signalChan
log.Printf("Terminating...\n")
cancel()
<-signalChan
log.Fatalf("Exit...\n")
}()
addr, err := net.ResolveUDPAddr("udp", *listen)
if err != nil {
panic(err)
}
if len(*connect) == 0 {
log.Panicf("server address is required")
}
// Generate a certificate and private key to secure the connection
certificate, genErr := selfsign.GenerateSelfSigned()
if genErr != nil {
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},
ExtendedMasterSecret: dtls.RequireExtendedMasterSecret,
CipherSuites: []dtls.CipherSuiteID{dtls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
ConnectionIDGenerator: dtls.RandomCIDGenerator(8),
}
// Connect to a DTLS server
listener, err := dtls.Listen("udp", addr, config)
if err != nil {
panic(err)
}
context.AfterFunc(ctx, func() {
if err = listener.Close(); err != nil {
panic(err)
}
})
fmt.Println("Listening")
wg1 := sync.WaitGroup{}
for {
select {
case <-ctx.Done():
wg1.Wait()
return
default:
}
// Wait for a connection.
conn, err := listener.Accept()
if err != nil {
log.Println(err)
continue
}
wg1.Add(1)
go func(conn net.Conn) {
defer wg1.Done()
defer conn.Close() // graceful shutdown
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)
dtlsConn, ok := conn.(*dtls.Conn)
if !ok {
log.Println("Type error")
cancel1()
return
}
log.Println("Start handshake")
if err = dtlsConn.HandshakeContext(ctx1); err != nil {
log.Println(err)
cancel1()
return
}
cancel1()
log.Println("Handshake done")
serverConn, err := net.Dial("udp", *connect)
if err != nil {
log.Println(err)
return
}
defer func() {
if err = serverConn.Close(); err != nil {
log.Printf("failed to close outgoing connection: %s", err)
return
}
}()
var wg sync.WaitGroup
wg.Add(2)
ctx2, cancel2 := context.WithCancel(ctx)
context.AfterFunc(ctx2, func() {
conn.SetDeadline(time.Now())
serverConn.SetDeadline(time.Now())
})
go func() {
defer wg.Done()
defer cancel2()
buf := make([]byte, 1600)
for {
select {
case <-ctx2.Done():
return
default:
}
conn.SetReadDeadline(time.Now().Add(time.Minute * 30))
n, err1 := conn.Read(buf)
if err1 != nil {
log.Printf("Failed: %s", err1)
return
}
serverConn.SetWriteDeadline(time.Now().Add(time.Minute * 30))
_, 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:
}
serverConn.SetReadDeadline(time.Now().Add(time.Minute * 30))
n, err1 := serverConn.Read(buf)
if err1 != nil {
log.Printf("Failed: %s", err1)
return
}
conn.SetWriteDeadline(time.Now().Add(time.Minute * 30))
_, 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)
}
}
Loading…
Cancel
Save