From ed59abf3d773f4f9d7a37f1d7805c5988215e2cf Mon Sep 17 00:00:00 2001 From: boks1971 Date: Thu, 4 Apr 2024 12:42:25 +0530 Subject: [PATCH] Support stand alone STUN server mode 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`. --- errors.go | 1 + examples/stun-only-server/main.go | 56 +++++++++++++++++++++++++++++++ internal/server/util.go | 7 ++++ server.go | 15 +++++++++ server_config.go | 9 +++-- server_test.go | 46 +++++++++++++++++++++++++ 6 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 examples/stun-only-server/main.go diff --git a/errors.go b/errors.go index 50065e27..3ebd26ae 100644 --- a/errors.go +++ b/errors.go @@ -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") ) diff --git a/examples/stun-only-server/main.go b/examples/stun-only-server/main.go new file mode 100644 index 00000000..5cba2997 --- /dev/null +++ b/examples/stun-only-server/main.go @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// 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) + } +} diff --git a/internal/server/util.go b/internal/server/util.go index d34d7b15..97f01c01 100644 --- a/internal/server/util.go +++ b/internal/server/util.go @@ -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...) } diff --git a/server.go b/server.go index db1c1852..91d7b1d5 100644 --- a/server.go +++ b/server.go @@ -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, diff --git a/server_config.go b/server_config.go index ee808fe9..eaa7a258 100644 --- a/server_config.go +++ b/server_config.go @@ -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 diff --git a/server_test.go b/server_test.go index ba307511..d3ad693c 100644 --- a/server_test.go +++ b/server_test.go @@ -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{