mirror of
https://github.com/hanzokms/turn.git
synced 2026-10-30 20:50:36 +00:00
176 lines
4.3 KiB
Go
176 lines
4.3 KiB
Go
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
// Package main implements a TURN client using UDP
|
|
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/pion/logging"
|
|
"github.com/hanzokms/turn/v4"
|
|
)
|
|
|
|
func main() { //nolint:cyclop
|
|
host := flag.String("host", "", "TURN Server name.")
|
|
port := flag.Int("port", 3478, "Listening port.")
|
|
user := flag.String("user", "", "A pair of username and password (e.g. \"user=pass\")")
|
|
realm := flag.String("realm", "pion.ly", "Realm (defaults to \"pion.ly\")")
|
|
ping := flag.Bool("ping", false, "Run ping test")
|
|
flag.Parse()
|
|
|
|
if len(*host) == 0 {
|
|
log.Fatalf("'host' is required")
|
|
}
|
|
|
|
if len(*user) == 0 {
|
|
log.Fatalf("'user' is required")
|
|
}
|
|
|
|
cred := strings.SplitN(*user, "=", 2)
|
|
|
|
// TURN client won't create a local listening socket by itself.
|
|
conn, err := net.ListenPacket("udp4", "0.0.0.0:0")
|
|
if err != nil {
|
|
log.Panicf("Failed to listen: %s", err)
|
|
}
|
|
defer func() {
|
|
if closeErr := conn.Close(); closeErr != nil {
|
|
log.Panicf("Failed to close connection: %s", closeErr)
|
|
}
|
|
}()
|
|
|
|
turnServerAddr := fmt.Sprintf("%s:%d", *host, *port)
|
|
|
|
cfg := &turn.ClientConfig{
|
|
STUNServerAddr: turnServerAddr,
|
|
TURNServerAddr: turnServerAddr,
|
|
Conn: conn,
|
|
Username: cred[0],
|
|
Password: cred[1],
|
|
Realm: *realm,
|
|
LoggerFactory: logging.NewDefaultLoggerFactory(),
|
|
}
|
|
|
|
client, err := turn.NewClient(cfg)
|
|
if err != nil {
|
|
log.Panicf("Failed to create TURN client: %s", err)
|
|
}
|
|
defer client.Close()
|
|
|
|
// Start listening on the conn provided.
|
|
err = client.Listen()
|
|
if err != nil {
|
|
log.Panicf("Failed to listen: %s", err)
|
|
}
|
|
|
|
// Allocate a relay socket on the TURN server. On success, it
|
|
// will return a net.PacketConn which represents the remote
|
|
// socket.
|
|
relayConn, err := client.Allocate()
|
|
if err != nil {
|
|
log.Panicf("Failed to allocate: %s", err)
|
|
}
|
|
defer func() {
|
|
if closeErr := relayConn.Close(); closeErr != nil {
|
|
log.Panicf("Failed to close connection: %s", closeErr)
|
|
}
|
|
}()
|
|
|
|
// The relayConn's local address is actually the transport
|
|
// address assigned on the TURN server.
|
|
log.Printf("relayed-address=%s", relayConn.LocalAddr().String())
|
|
|
|
// If you provided `-ping`, perform a ping test against the
|
|
// relayConn we have just allocated.
|
|
if *ping {
|
|
err = doPingTest(client, relayConn)
|
|
if err != nil {
|
|
log.Panicf("Failed to ping: %s", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func doPingTest(client *turn.Client, relayConn net.PacketConn) error { //nolint:cyclop
|
|
// Send BindingRequest to learn our external IP
|
|
mappedAddr, err := client.SendBindingRequest()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Set up pinger socket (pingerConn)
|
|
pingerConn, err := net.ListenPacket("udp4", "0.0.0.0:0")
|
|
if err != nil {
|
|
log.Panicf("Failed to listen: %s", err)
|
|
}
|
|
defer func() {
|
|
if closeErr := pingerConn.Close(); closeErr != nil {
|
|
log.Panicf("Failed to close connection: %s", closeErr)
|
|
}
|
|
}()
|
|
|
|
// Punch a UDP hole for the relayConn by sending a data to the mappedAddr.
|
|
// This will trigger a TURN client to generate a permission request to the
|
|
// TURN server. After this, packets from the IP address will be accepted by
|
|
// the TURN server.
|
|
_, err = relayConn.WriteTo([]byte("Hello"), mappedAddr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Start read-loop on pingerConn
|
|
go func() {
|
|
buf := make([]byte, 1600)
|
|
for {
|
|
n, from, pingerErr := pingerConn.ReadFrom(buf)
|
|
if pingerErr != nil {
|
|
break
|
|
}
|
|
|
|
msg := string(buf[:n])
|
|
if sentAt, pingerErr := time.Parse(time.RFC3339Nano, msg); pingerErr == nil {
|
|
rtt := time.Since(sentAt)
|
|
log.Printf("%d bytes from from %s time=%d ms\n", n, from.String(), int(rtt.Seconds()*1000))
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Start read-loop on relayConn
|
|
go func() {
|
|
buf := make([]byte, 1600)
|
|
for {
|
|
n, from, readerErr := relayConn.ReadFrom(buf)
|
|
if readerErr != nil {
|
|
break
|
|
}
|
|
|
|
// Echo back
|
|
if _, readerErr = relayConn.WriteTo(buf[:n], from); readerErr != nil {
|
|
break
|
|
}
|
|
}
|
|
}()
|
|
|
|
time.Sleep(500 * time.Millisecond)
|
|
|
|
// Send 10 packets from relayConn to the echo server
|
|
for i := 0; i < 10; i++ {
|
|
msg := time.Now().Format(time.RFC3339Nano)
|
|
_, err = pingerConn.WriteTo([]byte(msg), relayConn.LocalAddr())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// For simplicity, this example does not wait for the pong (reply).
|
|
// Instead, sleep 1 second.
|
|
time.Sleep(time.Second)
|
|
}
|
|
|
|
return nil
|
|
}
|