// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

// Package turn contains the public API for pion/turn, a toolkit for building TURN clients and servers
package turn import ( ) const ( defaultInboundMTU = 1600 ) // Server is an instance of the Pion TURN Server. type Server struct { log logging.LeveledLogger authHandler AuthHandler realm string channelBindTimeout time.Duration nonceHash *server.NonceHash packetConnConfigs []PacketConnConfig listenerConfigs []ListenerConfig allocationManagers []*allocation.Manager inboundMTU int } // NewServer creates the Pion TURN server. func ( ServerConfig) (*Server, error) { //nolint:gocognit,cyclop if := .validate(); != nil { return nil, } := .LoggerFactory if == nil { = logging.NewDefaultLoggerFactory() } := defaultInboundMTU if .InboundMTU != 0 { = .InboundMTU } , := server.NewNonceHash() if != nil { return nil, } := &Server{ log: .NewLogger("turn"), authHandler: .AuthHandler, realm: .Realm, channelBindTimeout: .ChannelBindTimeout, packetConnConfigs: .PacketConnConfigs, listenerConfigs: .ListenerConfigs, nonceHash: , inboundMTU: , } if .channelBindTimeout == 0 { .channelBindTimeout = proto.DefaultLifetime } for , := range .packetConnConfigs { , := .createAllocationManager(.RelayAddressGenerator, .PermissionHandler) if != nil { return nil, fmt.Errorf("failed to create AllocationManager: %w", ) } go func( PacketConnConfig, *allocation.Manager) { .readLoop(.PacketConn, ) if := .Close(); != nil { .log.Errorf("Failed to close AllocationManager: %s", ) } }(, ) } for , := range .listenerConfigs { , := .createAllocationManager(.RelayAddressGenerator, .PermissionHandler) if != nil { return nil, fmt.Errorf("failed to create AllocationManager: %w", ) } go func( ListenerConfig, *allocation.Manager) { .readListener(.Listener, ) if := .Close(); != nil { .log.Errorf("Failed to close AllocationManager: %s", ) } }(, ) } return , nil } // AllocationCount returns the number of active allocations. // It can be used to drain the server before closing. func ( *Server) () int { := 0 for , := range .allocationManagers { += .AllocationCount() } return } // Close stops the TURN Server. // It cleans up any associated state and closes all connections it is managing. func ( *Server) () error { var []error for , := range .packetConnConfigs { if := .PacketConn.Close(); != nil { = append(, ) } } for , := range .listenerConfigs { if := .Listener.Close(); != nil { = append(, ) } } if len() == 0 { return nil } := errFailedToClose for , := range { = fmt.Errorf("%s; close error (%w) ", , ) //nolint:errorlint } return } func ( *Server) ( net.Listener, *allocation.Manager) { for { , := .Accept() if != nil { .log.Debugf("Failed to accept: %s", ) return } go func() { .readLoop(NewSTUNConn(), ) // Delete allocation .DeleteAllocation(&allocation.FiveTuple{ Protocol: allocation.UDP, // fixed UDP SrcAddr: .RemoteAddr(), DstAddr: .LocalAddr(), }) if := .Close(); != nil && !errors.Is(, net.ErrClosed) { .log.Errorf("Failed to close conn: %s", ) } }() } } type nilAddressGenerator struct{} func ( *nilAddressGenerator) () error { return errRelayAddressGeneratorNil } func ( *nilAddressGenerator) (string, int) (net.PacketConn, net.Addr, error) { return nil, nil, errRelayAddressGeneratorNil } func ( *nilAddressGenerator) (string, int) (net.Conn, net.Addr, error) { return nil, nil, errRelayAddressGeneratorNil } func ( *Server) ( RelayAddressGenerator, PermissionHandler, ) (*allocation.Manager, error) { if == nil { = DefaultPermissionHandler } if == nil { = &nilAddressGenerator{} } , := allocation.NewManager(allocation.ManagerConfig{ AllocatePacketConn: .AllocatePacketConn, AllocateConn: .AllocateConn, PermissionHandler: , LeveledLogger: .log, }) if != nil { return , } .allocationManagers = append(.allocationManagers, ) return , } func ( *Server) ( net.PacketConn, *allocation.Manager) { := make([]byte, .inboundMTU) for { , , := .ReadFrom() switch { case != nil: .log.Debugf("Exit read loop on error: %s", ) return case >= .inboundMTU: .log.Debugf("Read bytes exceeded MTU, packet is possibly truncated") continue } if := server.HandleRequest(server.Request{ Conn: , SrcAddr: , Buff: [:], Log: .log, AuthHandler: .authHandler, Realm: .realm, AllocationManager: , ChannelBindTimeout: .channelBindTimeout, NonceHash: .nonceHash, }); != nil { .log.Debugf("Failed to handle datagram: %v", ) } } }