@ -16,6 +16,7 @@ import (
"net/http"
"os"
"os/signal"
"strings"
"sync"
"sync/atomic"
"syscall"
@ -23,49 +24,53 @@ import (
"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/v4 "
"github.com/pion/turn/v5 "
)
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
}
type getCredsFunc func ( string ) ( string , string , string , error )
func getVkCreds ( link string ) ( string , string , string , error ) {
doRequest := func ( 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 ,
} ,
}
defer client . CloseIdleConnections ( )
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" )
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 ( )
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
}
body , err := io . ReadAll ( httpResp . Body )
if err != nil {
return nil , err
}
err = json . Unmarshal ( body , & resp )
if err != nil {
return nil , err
}
err = json . Unmarshal ( body , & resp )
if err != nil {
return nil , err
}
return resp , nil
}
return resp , nil
}
func getCreds ( link string ) ( string , string , string , error ) {
var resp map [ string ] interface { }
defer func ( ) {
if r := recover ( ) ; r != nil {
@ -135,7 +140,292 @@ func getCreds(link string) (string, string, string, error) {
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
clean := strings . Split ( turn , "?" ) [ 0 ]
address := strings . TrimPrefix ( strings . TrimPrefix ( clean , "turn:" ) , "turns:" )
return user , pass , address , nil
}
func getYandexCreds ( link string ) ( string , string , string , error ) {
const debug = false
const telemostConfHost = "cloud-api.yandex.ru"
telemostConfPath := fmt . Sprintf ( "%s%s%s" , "/telemost_front/v2/telemost/conferences/https%3A%2F%2Ftelemost.yandex.ru%2Fj%2F" , link , "/connection?next_gen_media_platform_allowed=false" )
const userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:144.0) Gecko/20100101 Firefox/144.0"
type ConferenceResponse struct {
URI string ` json:"uri" `
RoomID string ` json:"room_id" `
PeerID string ` json:"peer_id" `
ClientConfiguration struct {
MediaServerURL string ` json:"media_server_url" `
} ` json:"client_configuration" `
Credentials string ` json:"credentials" `
}
type PartMeta struct {
Name string ` json:"name" `
Role string ` json:"role" `
Description string ` json:"description" `
SendAudio bool ` json:"sendAudio" `
SendVideo bool ` json:"sendVideo" `
}
type PartAttrs struct {
Name string ` json:"name" `
Role string ` json:"role" `
Description string ` json:"description" `
}
type SdkInfo struct {
Implementation string ` json:"implementation" `
Version string ` json:"version" `
UserAgent string ` json:"userAgent" `
HwConcurrency int ` json:"hwConcurrency" `
}
type Capabilities struct {
OfferAnswerMode [ ] string ` json:"offerAnswerMode" `
InitialSubscriberOffer [ ] string ` json:"initialSubscriberOffer" `
SlotsMode [ ] string ` json:"slotsMode" `
SimulcastMode [ ] string ` json:"simulcastMode" `
SelfVadStatus [ ] string ` json:"selfVadStatus" `
DataChannelSharing [ ] string ` json:"dataChannelSharing" `
VideoEncoderConfig [ ] string ` json:"videoEncoderConfig" `
DataChannelVideoCodec [ ] string ` json:"dataChannelVideoCodec" `
BandwidthLimitationReason [ ] string ` json:"bandwidthLimitationReason" `
SdkDefaultDeviceManagement [ ] string ` json:"sdkDefaultDeviceManagement" `
JoinOrderLayout [ ] string ` json:"joinOrderLayout" `
PinLayout [ ] string ` json:"pinLayout" `
SendSelfViewVideoSlot [ ] string ` json:"sendSelfViewVideoSlot" `
ServerLayoutTransition [ ] string ` json:"serverLayoutTransition" `
SdkPublisherOptimizeBitrate [ ] string ` json:"sdkPublisherOptimizeBitrate" `
SdkNetworkLostDetection [ ] string ` json:"sdkNetworkLostDetection" `
SdkNetworkPathMonitor [ ] string ` json:"sdkNetworkPathMonitor" `
PublisherVp9 [ ] string ` json:"publisherVp9" `
SvcMode [ ] string ` json:"svcMode" `
SubscriberOfferAsyncAck [ ] string ` json:"subscriberOfferAsyncAck" `
SvcModes [ ] string ` json:"svcModes" `
ReportTelemetryModes [ ] string ` json:"reportTelemetryModes" `
KeepDefaultDevicesModes [ ] string ` json:"keepDefaultDevicesModes" `
}
type HelloPayload struct {
ParticipantMeta PartMeta ` json:"participantMeta" `
ParticipantAttributes PartAttrs ` json:"participantAttributes" `
SendAudio bool ` json:"sendAudio" `
SendVideo bool ` json:"sendVideo" `
SendSharing bool ` json:"sendSharing" `
ParticipantID string ` json:"participantId" `
RoomID string ` json:"roomId" `
ServiceName string ` json:"serviceName" `
Credentials string ` json:"credentials" `
CapabilitiesOffer Capabilities ` json:"capabilitiesOffer" `
SdkInfo SdkInfo ` json:"sdkInfo" `
SdkInitializationID string ` json:"sdkInitializationId" `
DisablePublisher bool ` json:"disablePublisher" `
DisableSubscriber bool ` json:"disableSubscriber" `
DisableSubscriberAudio bool ` json:"disableSubscriberAudio" `
}
type HelloRequest struct {
UID string ` json:"uid" `
Hello HelloPayload ` json:"hello" `
}
type FlexUrls [ ] string
type WSSResponse struct {
UID string ` json:"uid" `
ServerHello struct {
RtcConfiguration struct {
IceServers [ ] struct {
Urls FlexUrls ` json:"urls" `
Username string ` json:"username,omitempty" `
Credential string ` json:"credential,omitempty" `
} ` json:"iceServers" `
} ` json:"rtcConfiguration" `
} ` json:"serverHello" `
}
type WSSAck struct {
Uid string ` json:"uid" `
Ack struct {
Status struct {
Code string ` json:"code" `
} ` json:"status" `
} ` json:"ack" `
}
type WSSData struct {
ParticipantId string
RoomId string
Credentials string
Wss string
}
endpoint := "https://" + telemostConfHost + telemostConfPath
client := & http . Client {
Timeout : 20 * time . Second ,
Transport : & http . Transport {
MaxIdleConns : 100 ,
MaxIdleConnsPerHost : 100 ,
IdleConnTimeout : 90 * time . Second ,
} ,
}
defer client . CloseIdleConnections ( )
req , err := http . NewRequest ( "GET" , endpoint , nil )
if err != nil {
return "" , "" , "" , err
}
req . Header . Set ( "User-Agent" , userAgent )
req . Header . Set ( "Content-Type" , "application/json" )
req . Header . Set ( "Referer" , "https://telemost.yandex.ru/" )
req . Header . Set ( "Origin" , "https://telemost.yandex.ru" )
req . Header . Set ( "Client-Instance-Id" , uuid . New ( ) . String ( ) )
resp , err := client . Do ( req )
if err != nil {
return "" , "" , "" , err
}
defer resp . Body . Close ( )
if resp . StatusCode != http . StatusOK {
body , _ := io . ReadAll ( resp . Body )
return "" , "" , "" , fmt . Errorf ( "GetConference: status=%s body=%s" , resp . Status , string ( body ) )
}
var result ConferenceResponse
if err = json . NewDecoder ( resp . Body ) . Decode ( & result ) ; err != nil {
return "" , "" , "" , fmt . Errorf ( "decode conf: %v" , err )
}
data := WSSData {
ParticipantId : result . PeerID ,
RoomId : result . RoomID ,
Credentials : result . Credentials ,
Wss : result . ClientConfiguration . MediaServerURL ,
}
h := http . Header { }
h . Set ( "Origin" , "https://telemost.yandex.ru" )
h . Set ( "User-Agent" , userAgent )
ctx , cancel := context . WithTimeout ( context . Background ( ) , 15 * time . Second )
defer cancel ( )
dialer := websocket . Dialer { }
conn , _ , err := dialer . DialContext ( ctx , data . Wss , h )
if err != nil {
return "" , "" , "" , fmt . Errorf ( "ws dial: %w" , err )
}
defer conn . Close ( )
req1 := HelloRequest {
UID : uuid . New ( ) . String ( ) ,
Hello : HelloPayload {
ParticipantMeta : PartMeta {
Name : "Гость" ,
Role : "SPEAKER" ,
Description : "" ,
SendAudio : false ,
SendVideo : false ,
} ,
ParticipantAttributes : PartAttrs {
Name : "Гость" ,
Role : "SPEAKER" ,
Description : "" ,
} ,
SendAudio : false ,
SendVideo : false ,
SendSharing : false ,
ParticipantID : data . ParticipantId ,
RoomID : data . RoomId ,
ServiceName : "telemost" ,
Credentials : data . Credentials ,
SdkInfo : SdkInfo {
Implementation : "browser" ,
Version : "5.15.0" ,
UserAgent : userAgent ,
HwConcurrency : 4 ,
} ,
SdkInitializationID : uuid . New ( ) . String ( ) ,
DisablePublisher : false ,
DisableSubscriber : false ,
DisableSubscriberAudio : false ,
CapabilitiesOffer : Capabilities {
OfferAnswerMode : [ ] string { "SEPARATE" } ,
InitialSubscriberOffer : [ ] string { "ON_HELLO" } ,
SlotsMode : [ ] string { "FROM_CONTROLLER" } ,
SimulcastMode : [ ] string { "DISABLED" } ,
SelfVadStatus : [ ] string { "FROM_SERVER" } ,
DataChannelSharing : [ ] string { "TO_RTP" } ,
VideoEncoderConfig : [ ] string { "NO_CONFIG" } ,
DataChannelVideoCodec : [ ] string { "VP8" } ,
BandwidthLimitationReason : [ ] string { "BANDWIDTH_REASON_DISABLED" } ,
SdkDefaultDeviceManagement : [ ] string { "SDK_DEFAULT_DEVICE_MANAGEMENT_DISABLED" } ,
JoinOrderLayout : [ ] string { "JOIN_ORDER_LAYOUT_DISABLED" } ,
PinLayout : [ ] string { "PIN_LAYOUT_DISABLED" } ,
SendSelfViewVideoSlot : [ ] string { "SEND_SELF_VIEW_VIDEO_SLOT_DISABLED" } ,
ServerLayoutTransition : [ ] string { "SERVER_LAYOUT_TRANSITION_DISABLED" } ,
SdkPublisherOptimizeBitrate : [ ] string { "SDK_PUBLISHER_OPTIMIZE_BITRATE_DISABLED" } ,
SdkNetworkLostDetection : [ ] string { "SDK_NETWORK_LOST_DETECTION_DISABLED" } ,
SdkNetworkPathMonitor : [ ] string { "SDK_NETWORK_PATH_MONITOR_DISABLED" } ,
PublisherVp9 : [ ] string { "PUBLISH_VP9_DISABLED" } ,
SvcMode : [ ] string { "SVC_MODE_DISABLED" } ,
SubscriberOfferAsyncAck : [ ] string { "SUBSCRIBER_OFFER_ASYNC_ACK_DISABLED" } ,
SvcModes : [ ] string { "FALSE" } ,
ReportTelemetryModes : [ ] string { "TRUE" } ,
KeepDefaultDevicesModes : [ ] string { "TRUE" } ,
} ,
} ,
}
if debug {
b , _ := json . MarshalIndent ( req1 , "" , " " )
log . Printf ( "Sending HELLO:\n%s" , string ( b ) )
}
if err := conn . WriteJSON ( req1 ) ; err != nil {
return "" , "" , "" , fmt . Errorf ( "ws write: %w" , err )
}
conn . SetReadDeadline ( time . Now ( ) . Add ( 15 * time . Second ) )
for {
_ , msg , err := conn . ReadMessage ( )
if err != nil {
return "" , "" , "" , fmt . Errorf ( "ws read: %w" , err )
}
if debug {
s := string ( msg )
if len ( s ) > 800 {
s = s [ : 800 ] + "...(truncated)"
}
log . Printf ( "WSS recv: %s" , s )
}
var ack WSSAck
if err := json . Unmarshal ( msg , & ack ) ; err == nil && ack . Ack . Status . Code != "" {
continue
}
var resp WSSResponse
if err := json . Unmarshal ( msg , & resp ) ; err == nil {
ice := resp . ServerHello . RtcConfiguration . IceServers
for _ , s := range ice {
for _ , u := range s . Urls {
if ! strings . HasPrefix ( u , "turn:" ) && ! strings . HasPrefix ( u , "turns:" ) {
continue
}
if strings . Contains ( u , "transport=tcp" ) {
continue
}
clean := strings . Split ( u , "?" ) [ 0 ]
address := strings . TrimPrefix ( strings . TrimPrefix ( clean , "turn:" ) , "turns:" )
return s . Username , s . Credential , address , nil
}
}
}
}
}
func dtlsFunc ( ctx context . Context , conn net . PacketConn , peer * net . UDPAddr ) ( net . Conn , error ) {
@ -163,9 +453,9 @@ func dtlsFunc(ctx context.Context, conn net.PacketConn, peer *net.UDPAddr) (net.
return dtlsConn , nil
}
func oneDtlsConnection ( ctx context . Context , peer * net . UDPAddr , listenConn net . PacketConn , connchan chan <- net . PacketConn , okchan chan <- struct { } , c1 chan <- error ) {
func oneDtlsConnection ( ctx context . Context , peer * net . UDPAddr , listenConn net . PacketConn , connchan chan <- net . PacketConn , okchan chan <- struct { } , c chan <- error ) {
var err error = nil
defer func ( ) { c1 <- err } ( )
defer func ( ) { c <- err } ( )
dtlsctx , dtlscancel := context . WithCancel ( ctx )
defer dtlscancel ( )
var conn1 , conn2 net . PacketConn
@ -271,20 +561,43 @@ func oneDtlsConnection(ctx context.Context, peer *net.UDPAddr, listenConn net.Pa
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 ) {
type connectedUDPConn struct {
* net . UDPConn
}
func ( c * connectedUDPConn ) WriteTo ( p [ ] byte , _ net . Addr ) ( int , error ) {
return c . Write ( p )
}
type turnParams struct {
host string
port string
link string
udp bool
getCreds getCredsFunc
}
func oneTurnConnection ( ctx context . Context , turnParams * turnParams , peer * net . UDPAddr , conn2 net . PacketConn , c chan <- error ) {
var err error = nil
defer func ( ) { c <- err } ( )
user , pass , url , err1 := getCreds ( link )
user , pass , url , err1 := turnParams . getCreds ( turnParams . 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 )
urlhost , urlport , err1 := net . SplitHostPort ( url )
if err1 != nil {
err = fmt . Errorf ( "failed to parse TURN server address: %s" , err1 )
return
}
if turnParams . host != "" {
urlhost = turnParams . host
}
if turnParams . port != "" {
urlport = turnParams . port
}
var turnServerAddr string
turnServerAddr = net . JoinHostPort ( urlhost , urlport )
turnServerUdpAddr , err1 := net . ResolveUDPAddr ( "udp" , turnServerAddr )
if err1 != nil {
err = fmt . Errorf ( "failed to resolve TURN server address: %s" , err1 )
@ -298,20 +611,21 @@ func oneTurnConnection(ctx context.Context, host string, port string, link strin
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 )
if turnParams . udp {
conn , err2 := net . DialUDP ( "udp4 " , nil , turnServerUdpAddr ) // nolint: noctx
if err2 != nil {
err = fmt . Errorf ( "failed to connect to TURN server: %s" , err2 )
return
}
defer func ( ) {
if err1 = turnC onn. Close ( ) ; err1 != nil {
if err1 = c onn. Close ( ) ; err1 != nil {
err = fmt . Errorf ( "failed to close TURN server connection: %s" , err1 )
return
}
} ( )
turnConn = & connectedUDPConn { conn }
} else {
conn , err2 := d . DialContext ( ctx1 , "tcp" , turnServerAddr ) // nolint: noctx
conn , err2 := d . DialContext ( ctx1 , "tcp4 " , turnServerAddr ) // nolint: noctx
if err2 != nil {
err = fmt . Errorf ( "failed to connect to TURN server: %s" , err2 )
return
@ -332,7 +646,6 @@ func oneTurnConnection(ctx context.Context, host string, port string, link strin
Conn : turnConn ,
Username : user ,
Password : pass ,
Realm : realm ,
LoggerFactory : logging . NewDefaultLoggerFactory ( ) ,
}
@ -375,6 +688,7 @@ func oneTurnConnection(ctx context.Context, host string, port string, link strin
relayConn . SetDeadline ( time . Now ( ) )
conn2 . SetDeadline ( time . Now ( ) )
} )
var addr atomic . Value
// Start read-loop on conn2 (output of DTLS)
go func ( ) {
defer wg . Done ( )
@ -386,12 +700,14 @@ func oneTurnConnection(ctx context.Context, host string, port string, link strin
return
default :
}
n , _ , err1 := conn2 . ReadFrom ( buf )
n , addr1 , err1 := conn2 . ReadFrom ( buf )
if err1 != nil {
log . Printf ( "Failed: %s" , err1 )
return
}
addr . Store ( addr1 ) // store peer
_ , err1 = relayConn . WriteTo ( buf [ : n ] , peer )
if err1 != nil {
log . Printf ( "Failed: %s" , err1 )
@ -416,8 +732,13 @@ func oneTurnConnection(ctx context.Context, host string, port string, link strin
log . Printf ( "Failed: %s" , err1 )
return
}
addr1 , ok := addr . Load ( ) . ( net . Addr )
if ! ok {
log . Printf ( "Failed: no listener ip" )
return
}
_ , err1 = conn2 . WriteTo ( buf [ : n ] , nil )
_ , err1 = conn2 . WriteTo ( buf [ : n ] , addr1 )
if err1 != nil {
log . Printf ( "Failed: %s" , err1 )
return
@ -430,6 +751,40 @@ func oneTurnConnection(ctx context.Context, host string, port string, link strin
conn2 . SetDeadline ( time . Time { } )
}
func oneDtlsConnectionLoop ( ctx context . Context , peer * net . UDPAddr , listenConnChan <- chan net . PacketConn , connchan chan <- net . PacketConn , okchan chan <- struct { } ) {
for {
select {
case <- ctx . Done ( ) :
return
case listenConn := <- listenConnChan :
c := make ( chan error )
go oneDtlsConnection ( ctx , peer , listenConn , connchan , okchan , c )
if err := <- c ; err != nil {
log . Printf ( "%s" , err )
}
}
}
}
func oneTurnConnectionLoop ( ctx context . Context , turnParams * turnParams , peer * net . UDPAddr , connchan <- chan net . PacketConn , t <- chan time . Time ) {
for {
select {
case <- ctx . Done ( ) :
return
case conn2 := <- connchan :
select {
case <- t :
c := make ( chan error )
go oneTurnConnection ( ctx , turnParams , peer , conn2 , c )
if err := <- c ; err != nil {
log . Printf ( "%s" , err )
}
default :
}
}
}
}
func main ( ) { //nolint:cyclop
ctx , cancel := context . WithCancel ( context . Background ( ) )
defer cancel ( )
@ -447,99 +802,101 @@ func main() { //nolint:cyclop
} ( )
host := flag . String ( "turn" , "" , "override TURN server ip" )
port := flag . String ( "port" , "19302 " , "override TURN port" )
port := flag . String ( "port" , "" , "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 /...\"" )
vklink := flag . String ( "vk-link " , "" , "VK calls invite link \"https://vk.com/call/join/...\" " )
ya link := flag . String ( "yandex- link" , "" , "Yandex telemost invite link \"https://telemost.yandex.ru/j /...\"" )
peerAddr := flag . String ( "peer" , "" , "peer server address (host:port)" )
n := flag . Int ( "n" , 16 , "connections to TURN" )
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" )
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 )
}
if ( * vklink == "" ) == ( * yalink == "" ) {
log . Panicf ( "Need either vk-link or yandex-link!" )
}
var link string
var getCreds getCredsFunc
if * vklink != "" {
link = ( * vklink ) [ len ( * vklink ) - 43 : ]
getCreds = getVkCreds
if * n <= 0 {
* n = 16
}
} else {
link = ( * yalink ) [ len ( * yalink ) - 10 : ]
getCreds = getYandexCreds
if * n <= 0 {
* n = 1
}
}
params := & turnParams {
* host ,
* port ,
link ,
* udp ,
getCreds ,
}
* link = ( * link ) [ len ( * link ) - 43 : ]
listenConnChan := make ( chan net . PacketConn )
listenConn , err := net . ListenPacket ( "udp" , * listen ) // nolint: noctx
if err != nil {
log . Panicf ( "Failed to listen: %s" , err )
}
defer func ( ) {
context . AfterFunc ( ctx , 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 ( ) {
} )
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 :
case listenConnChan <- listenConn :
}
}
} )
} ( )
wg1 := sync . WaitGroup { }
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 :
}
}
if * direct {
for i := 0 ; i < * n ; i ++ {
wg1 . Go ( func ( ) {
oneTurnConnectionLoop ( ctx , params , peer , listenConnChan , t )
} )
}
} )
} else {
okchan := make ( chan struct { } )
connchan := make ( chan net . PacketConn )
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 :
}
}
}
oneDtlsConnectionLoop ( ctx , peer , listenConnChan , connchan , okchan )
} )
wg1 . Go ( func ( ) {
oneTurnConnectionLoop ( ctx , params , peer , connchan , t )
} )
select {
case <- okchan :
case <- ctx . Done ( ) :
}
for i := 0 ; i < * n - 1 ; i ++ {
connchan := make ( chan net . PacketConn )
wg1 . Go ( func ( ) {
oneDtlsConnectionLoop ( ctx , peer , listenConnChan , connchan , nil )
} )
wg1 . Go ( func ( ) {
oneTurnConnectionLoop ( ctx , params , peer , connchan , t )
} )
}
}
wg1 . Wait ( )