Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support stand alone STUN server mode #383

Merged
merged 1 commit into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
}
}

type nilAddressGenerator struct{}

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

Check warning on line 172 in server.go

View check run for this annotation

Codecov / codecov/patch

server.go#L172

Added line #L172 was not covered by tests

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

Check warning on line 175 in server.go

View check run for this annotation

Codecov / codecov/patch

server.go#L174-L175

Added lines #L174 - L175 were not covered by tests
}

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

Check warning on line 179 in server.go

View check run for this annotation

Codecov / codecov/patch

server.go#L178-L179

Added lines #L178 - L179 were not covered by tests
}

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 @@
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
}

Check warning on line 64 in server_config.go

View check run for this annotation

Codecov / codecov/patch

server_config.go#L63-L64

Added lines #L63 - L64 were not covered by tests
}

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
Loading