Skip to content

Commit

Permalink
Support stand alone STUN server mode
Browse files Browse the repository at this point in the history
Running in stand alone STUN server does not start
due to packet conn config requiring relay address config
during validation.

To enable that mode, bypass validation if relay address config
in packet conn config is nil.

Note that it is still validated in `ListenerConfig`.
  • Loading branch information
boks1971 authored and Sean-Der committed Apr 4, 2024
1 parent 33c87de commit ed59abf
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 3 deletions.
1 change: 1 addition & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ var (
errNonSTUNMessage = errors.New("non-STUN message from STUN server")
errFailedToDecodeSTUN = errors.New("failed to decode STUN message")
errUnexpectedSTUNRequestMessage = errors.New("unexpected STUN request message")
errRelayAddressGeneratorNil = errors.New("RelayAddressGenerator is nil")
)
56 changes: 56 additions & 0 deletions examples/stun-only-server/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

// Package main implements a simple TURN server
package main

import (
"flag"
"log"
"net"
"os"
"os/signal"
"strconv"
"syscall"

"github.com/pion/turn/v3"
)

func main() {
publicIP := flag.String("public-ip", "", "IP Address that STUN can be contacted by.")
port := flag.Int("port", 3478, "Listening port.")
flag.Parse()

if len(*publicIP) == 0 {
log.Fatalf("'public-ip' is required")
}

// Create a UDP listener to pass into pion/turn
// pion/turn itself doesn't allocate any UDP sockets, but lets the user pass them in
// this allows us to add logging, storage or modify inbound/outbound traffic
udpListener, err := net.ListenPacket("udp4", "0.0.0.0:"+strconv.Itoa(*port))
if err != nil {
log.Panicf("Failed to create STUN server listener: %s", err)
}

s, err := turn.NewServer(turn.ServerConfig{
// PacketConnConfigs is a list of UDP Listeners and the configuration around them
PacketConnConfigs: []turn.PacketConnConfig{
{
PacketConn: udpListener,
},
},
})
if err != nil {
log.Panic(err)
}

// Block until user sends SIGINT or SIGTERM
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
<-sigs

if err = s.Close(); err != nil {
log.Panic(err)
}
}
7 changes: 7 additions & 0 deletions internal/server/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ func authenticateRequest(r Request, m *stun.Message, callingMethod stun.Method)
realmAttr := &stun.Realm{}
badRequestMsg := buildMsg(m.TransactionID, stun.NewType(callingMethod, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeBadRequest})

// No Auth handler is set, server is running in STUN only mode
// Respond with 400 so clients don't retry
if r.AuthHandler == nil {
sendErr := buildAndSend(r.Conn, r.SrcAddr, badRequestMsg...)
return nil, false, sendErr
}

if err := nonceAttr.GetFrom(m); err != nil {
return nil, false, buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...)
}
Expand Down
15 changes: 15 additions & 0 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,25 @@ func (s *Server) readListener(l net.Listener, am *allocation.Manager) {
}
}

type nilAddressGenerator struct{}

func (n *nilAddressGenerator) Validate() error { return errRelayAddressGeneratorNil }

func (n *nilAddressGenerator) AllocatePacketConn(string, int) (net.PacketConn, net.Addr, error) {
return nil, nil, errRelayAddressGeneratorNil
}

func (n *nilAddressGenerator) AllocateConn(string, int) (net.Conn, net.Addr, error) {
return nil, nil, errRelayAddressGeneratorNil
}

func (s *Server) createAllocationManager(addrGenerator RelayAddressGenerator, handler PermissionHandler) (*allocation.Manager, error) {
if handler == nil {
handler = DefaultPermissionHandler
}
if addrGenerator == nil {
addrGenerator = &nilAddressGenerator{}
}

am, err := allocation.NewManager(allocation.ManagerConfig{
AllocatePacketConn: addrGenerator.AllocatePacketConn,
Expand Down
9 changes: 6 additions & 3 deletions server_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,14 @@ func (c *PacketConnConfig) validate() error {
if c.PacketConn == nil {
return errConnUnset
}
if c.RelayAddressGenerator == nil {
return errRelayAddressGeneratorUnset

if c.RelayAddressGenerator != nil {
if err := c.RelayAddressGenerator.Validate(); err != nil {
return err
}
}

return c.RelayAddressGenerator.Validate()
return nil
}

// ListenerConfig is a single net.Listener to accept connections on. This will be used for TCP, TLS and DTLS listeners
Expand Down
46 changes: 46 additions & 0 deletions server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,52 @@ func TestConsumeSingleTURNFrame(t *testing.T) {
}
}

func TestSTUNOnly(t *testing.T) {
serverAddr, err := net.ResolveUDPAddr("udp4", "0.0.0.0:3478")
assert.NoError(t, err)

serverConn, err := net.ListenPacket(serverAddr.Network(), serverAddr.String())
assert.NoError(t, err)

defer serverConn.Close() //nolint:errcheck

server, err := NewServer(ServerConfig{
PacketConnConfigs: []PacketConnConfig{{
PacketConn: serverConn,
}},
Realm: "pion.ly",
LoggerFactory: logging.NewDefaultLoggerFactory(),
})
assert.NoError(t, err)

defer server.Close() //nolint:errcheck

conn, err := net.ListenPacket("udp4", "0.0.0.0:0")
assert.NoError(t, err)

client, err := NewClient(&ClientConfig{
Conn: conn,
STUNServerAddr: "127.0.0.1:3478",
TURNServerAddr: "127.0.0.1:3478",
Username: "user",
Password: "pass",
Realm: "pion.ly",
LoggerFactory: logging.NewDefaultLoggerFactory(),
})
assert.NoError(t, err)
assert.NoError(t, client.Listen())
defer client.Close()

reflAddr, err := client.SendBindingRequest()
assert.NoError(t, err)

_, ok := reflAddr.(*net.UDPAddr)
assert.True(t, ok)

_, err = client.Allocate()
assert.Equal(t, err.Error(), "Allocate error response (error 400: )")
}

func RunBenchmarkServer(b *testing.B, clientNum int) {
loggerFactory := logging.NewDefaultLoggerFactory()
credMap := map[string][]byte{
Expand Down

0 comments on commit ed59abf

Please sign in to comment.