Skip to content

Commit

Permalink
Add dedicated TCP TURN Client
Browse files Browse the repository at this point in the history
Also update .goreleaser binary names to be more
self documenting

Resolves #87
  • Loading branch information
Sean-Der committed Jan 14, 2020
1 parent 630c6d9 commit 52a83f9
Show file tree
Hide file tree
Showing 5 changed files with 238 additions and 79 deletions.
9 changes: 5 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
*.sw[poe]
examples/turn-client/turn-client
examples/turn-server/simple/add-software-attribute
examples/turn-server/simple/log
examples/turn-client/tcp/tcp
examples/turn-client/udp/udp
examples/turn-server/add-software-attribute/add-software-attribute
examples/turn-server/log/log
examples/turn-server/simple/simple
examples/turn-server/simple/tcp
examples/turn-server/tcp/tcp
36 changes: 25 additions & 11 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ changelog:
- '^test:'

builds:
- binary: turn-client
id: turn-client
- binary: turn-client-tcp
id: turn-client-tcp
goos:
- darwin
- windows
Expand All @@ -36,10 +36,24 @@ builds:
- 386
env:
- CGO_ENABLED=0
main: ./examples/turn-client/
main: ./examples/turn-client/tcp

- binary: add-software-attribute
id: add-software-attribute
- binary: turn-client-udp
id: turn-client-udp
goos:
- darwin
- windows
- linux
- freebsd
goarch:
- amd64
- 386
env:
- CGO_ENABLED=0
main: ./examples/turn-client/udp

- binary: turn-server-add-software-attribute
id: turn-server-add-software-attribute
goos:
- darwin
- windows
Expand All @@ -52,8 +66,8 @@ builds:
- CGO_ENABLED=0
main: ./examples/turn-server/add-software-attribute

- binary: log
id: log
- binary: turn-server-log
id: turn-server-log
goos:
- darwin
- windows
Expand All @@ -66,8 +80,8 @@ builds:
- CGO_ENABLED=0
main: ./examples/turn-server/log

- binary: simple
id: simple
- binary: turn-server-simple
id: turn-server-simple
goos:
- darwin
- windows
Expand All @@ -80,8 +94,8 @@ builds:
- CGO_ENABLED=0
main: ./examples/turn-server/simple/

- binary: tcp
id: tcp
- binary: turn-server-tcp
id: turn-server-tcp
goos:
- darwin
- windows
Expand Down
167 changes: 167 additions & 0 deletions examples/turn-client/tcp/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package main

import (
"flag"
"fmt"
"log"
"net"
"strings"
"time"

"github.com/pion/logging"
"github.com/pion/turn/v2"
)

func main() {
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")
}

// Dial TURN Server
turnServerAddr := fmt.Sprintf("%s:%d", *host, *port)
conn, err := net.Dial("tcp", turnServerAddr)
if err != nil {
panic(err)
}

cred := strings.Split(*user, "=")

// 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: turn.NewSTUNConn(conn),
Username: cred[0],
Password: cred[1],
Realm: *realm,
LoggerFactory: logging.NewDefaultLoggerFactory(),
}

client, err := turn.NewClient(cfg)
if err != nil {
panic(err)
}
defer client.Close()

// Start listening on the conn provided.
err = client.Listen()
if err != nil {
panic(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 {
panic(err)
}
defer func() {
if closeErr := relayConn.Close(); closeErr != nil {
panic(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 agaist the
// relayConn we have just allocated.
if *ping {
err = doPingTest(client, relayConn)
if err != nil {
panic(err)
}
}
}

func doPingTest(client *turn.Client, relayConn net.PacketConn) error {
// 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 {
panic(err)
}
defer func() {
if closeErr := pingerConn.Close(); closeErr != nil {
panic(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, 1500)
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, 1500)
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
}
File renamed without changes.
105 changes: 41 additions & 64 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,53 @@ func NewServer(config ServerConfig) (*Server, error) {
s.channelBindTimeout = proto.DefaultLifetime
}

for _, p := range s.packetConnConfigs {
go s.packetConnReadLoop(p.PacketConn, p.RelayAddressGenerator)
for i := range s.packetConnConfigs {
go func(p PacketConnConfig) {
allocationManager, err := allocation.NewManager(allocation.ManagerConfig{
AllocatePacketConn: p.RelayAddressGenerator.AllocatePacketConn,
AllocateConn: p.RelayAddressGenerator.AllocateConn,
LeveledLogger: s.log,
})
if err != nil {
s.log.Errorf("exit read loop on error: %s", err.Error())
return
}
defer func() {
if err := allocationManager.Close(); err != nil {
s.log.Errorf("Failed to close AllocationManager: %s", err.Error())
}
}()

s.readLoop(p.PacketConn, allocationManager)
}(s.packetConnConfigs[i])
}

for _, listener := range s.listenerConfigs {
go func(l ListenerConfig) {
conn, err := l.Listener.Accept()
allocationManager, err := allocation.NewManager(allocation.ManagerConfig{
AllocatePacketConn: l.RelayAddressGenerator.AllocatePacketConn,
AllocateConn: l.RelayAddressGenerator.AllocateConn,
LeveledLogger: s.log,
})
if err != nil {
s.log.Debugf("exit accept loop on error: %s", err.Error())
s.log.Errorf("exit read loop on error: %s", err.Error())
return
}

go s.connReadLoop(conn, l.RelayAddressGenerator)
defer func() {
if err := allocationManager.Close(); err != nil {
s.log.Errorf("Failed to close AllocationManager: %s", err.Error())
}
}()

for {
conn, err := l.Listener.Accept()
if err != nil {
s.log.Debugf("exit accept loop on error: %s", err.Error())
return
}

go s.readLoop(NewSTUNConn(conn), allocationManager)
}
}(listener)
}

Expand Down Expand Up @@ -98,67 +132,10 @@ func (s *Server) Close() error {
return err
}

func (s *Server) connReadLoop(c net.Conn, r RelayAddressGenerator) {
allocationManager, err := allocation.NewManager(allocation.ManagerConfig{
AllocatePacketConn: r.AllocatePacketConn,
AllocateConn: r.AllocateConn,
LeveledLogger: s.log,
})
if err != nil {
s.log.Errorf("exit read loop on error: %s", err.Error())
return
}
defer func() {
if err := allocationManager.Close(); err != nil {
s.log.Errorf("Failed to close AllocationManager: %s", err.Error())
}
}()

stunConn := NewSTUNConn(c)
buf := make([]byte, inboundMTU)
for {
n, addr, err := stunConn.ReadFrom(buf)

if err != nil {
s.log.Debugf("exit read loop on error: %s", err.Error())
return
}

if err := server.HandleRequest(server.Request{
Conn: stunConn,
SrcAddr: addr,
Buff: buf[:n],
Log: s.log,
AuthHandler: s.authHandler,
Realm: s.realm,
AllocationManager: allocationManager,
ChannelBindTimeout: s.channelBindTimeout,
}); err != nil {
s.log.Errorf("error when handling datagram: %v", err)
}
}
}

func (s *Server) packetConnReadLoop(p net.PacketConn, r RelayAddressGenerator) {
allocationManager, err := allocation.NewManager(allocation.ManagerConfig{
AllocatePacketConn: r.AllocatePacketConn,
AllocateConn: r.AllocateConn,
LeveledLogger: s.log,
})
if err != nil {
s.log.Errorf("exit read loop on error: %s", err.Error())
return
}
defer func() {
if err := allocationManager.Close(); err != nil {
s.log.Errorf("Failed to close AllocationManager: %s", err.Error())
}
}()

func (s *Server) readLoop(p net.PacketConn, allocationManager *allocation.Manager) {
buf := make([]byte, inboundMTU)
for {
n, addr, err := p.ReadFrom(buf)

if err != nil {
s.log.Debugf("exit read loop on error: %s", err.Error())
return
Expand Down

0 comments on commit 52a83f9

Please sign in to comment.