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{