直播:后台 JWT 推流、前台画中画;WebRTC 服务与 Nginx WebSocket 代理

Made-with: Cursor
This commit is contained in:
whm
2026-03-25 15:00:14 +08:00
parent b83ec91b1a
commit 7811adca66
1050 changed files with 146524 additions and 37 deletions

View File

@@ -0,0 +1,68 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package proto
import (
"fmt"
"net"
)
// Addr is ip:port.
type Addr struct {
IP net.IP
Port int
}
// Network implements net.Addr.
func (Addr) Network() string { return "turn" }
// FromUDPAddr sets addr to UDPAddr.
func (a *Addr) FromUDPAddr(n *net.UDPAddr) {
a.IP = n.IP
a.Port = n.Port
}
// Equal returns true if b == a.
func (a Addr) Equal(b Addr) bool {
if a.Port != b.Port {
return false
}
return a.IP.Equal(b.IP)
}
// EqualIP returns true if a and b have equal IP addresses.
func (a Addr) EqualIP(b Addr) bool {
return a.IP.Equal(b.IP)
}
func (a Addr) String() string {
return fmt.Sprintf("%s:%d", a.IP, a.Port)
}
// FiveTuple represents 5-TUPLE value.
type FiveTuple struct {
Client Addr
Server Addr
Proto Protocol
}
func (t FiveTuple) String() string {
return fmt.Sprintf("%s->%s (%s)",
t.Client, t.Server, t.Proto,
)
}
// Equal returns true if b == t.
func (t FiveTuple) Equal(b FiveTuple) bool {
if t.Proto != b.Proto {
return false
}
if !t.Client.Equal(b.Client) {
return false
}
if !t.Server.Equal(b.Server) {
return false
}
return true
}

View File

@@ -0,0 +1,143 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package proto
import (
"bytes"
"encoding/binary"
"errors"
"io"
)
// ChannelData represents The ChannelData Message.
//
// See RFC 5766 Section 11.4
type ChannelData struct {
Data []byte // Can be sub slice of Raw
Length int // Ignored while encoding, len(Data) is used
Number ChannelNumber
Raw []byte
}
// Equal returns true if b == c.
func (c *ChannelData) Equal(b *ChannelData) bool {
if c == nil && b == nil {
return true
}
if c == nil || b == nil {
return false
}
if c.Number != b.Number {
return false
}
if len(c.Data) != len(b.Data) {
return false
}
return bytes.Equal(c.Data, b.Data)
}
// Grow ensures that internal buffer will fit v more bytes and
// increases it capacity if necessary.
//
// Similar to stun.Message.grow method.
func (c *ChannelData) grow(v int) {
n := len(c.Raw) + v
for cap(c.Raw) < n {
c.Raw = append(c.Raw, 0)
}
c.Raw = c.Raw[:n]
}
// Reset resets Length, Data and Raw length.
func (c *ChannelData) Reset() {
c.Raw = c.Raw[:0]
c.Length = 0
c.Data = c.Data[:0]
}
// Encode encodes ChannelData Message to Raw.
func (c *ChannelData) Encode() {
c.Raw = c.Raw[:0]
c.WriteHeader()
c.Raw = append(c.Raw, c.Data...)
padded := nearestPaddedValueLength(len(c.Raw))
if bytesToAdd := padded - len(c.Raw); bytesToAdd > 0 {
for i := 0; i < bytesToAdd; i++ {
c.Raw = append(c.Raw, 0)
}
}
}
const padding = 4
func nearestPaddedValueLength(l int) int {
n := padding * (l / padding)
if n < l {
n += padding
}
return n
}
// WriteHeader writes channel number and length.
func (c *ChannelData) WriteHeader() {
if len(c.Raw) < channelDataHeaderSize {
// Making WriteHeader call valid even when c.Raw
// is nil or len(c.Raw) is less than needed for header.
c.grow(channelDataHeaderSize)
}
// Early bounds check to guarantee safety of writes below.
_ = c.Raw[:channelDataHeaderSize]
binary.BigEndian.PutUint16(c.Raw[:channelDataNumberSize], uint16(c.Number))
binary.BigEndian.PutUint16(c.Raw[channelDataNumberSize:channelDataHeaderSize],
uint16(len(c.Data)),
)
}
// ErrBadChannelDataLength means that channel data length is not equal
// to actual data length.
var ErrBadChannelDataLength = errors.New("channelData length != len(Data)")
// Decode decodes The ChannelData Message from Raw.
func (c *ChannelData) Decode() error {
buf := c.Raw
if len(buf) < channelDataHeaderSize {
return io.ErrUnexpectedEOF
}
num := binary.BigEndian.Uint16(buf[:channelDataNumberSize])
c.Number = ChannelNumber(num)
l := binary.BigEndian.Uint16(buf[channelDataNumberSize:channelDataHeaderSize])
c.Data = buf[channelDataHeaderSize:]
c.Length = int(l)
if !c.Number.Valid() {
return ErrInvalidChannelNumber
}
if int(l) < len(c.Data) {
c.Data = c.Data[:int(l)]
}
if int(l) > len(buf[channelDataHeaderSize:]) {
return ErrBadChannelDataLength
}
return nil
}
const (
channelDataLengthSize = 2
channelDataNumberSize = channelDataLengthSize
channelDataHeaderSize = channelDataLengthSize + channelDataNumberSize
)
// IsChannelData returns true if buf looks like the ChannelData Message.
func IsChannelData(buf []byte) bool {
if len(buf) < channelDataHeaderSize {
return false
}
if int(binary.BigEndian.Uint16(buf[channelDataNumberSize:channelDataHeaderSize])) > len(buf[channelDataHeaderSize:]) {
return false
}
// Quick check for channel number.
num := binary.BigEndian.Uint16(buf[0:channelNumberSize])
return isChannelNumberValid(num)
}

View File

@@ -0,0 +1,70 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package proto
import (
"encoding/binary"
"errors"
"strconv"
"github.com/pion/stun"
)
// ChannelNumber represents CHANNEL-NUMBER attribute.
//
// The CHANNEL-NUMBER attribute contains the number of the channel.
//
// RFC 5766 Section 14.1
type ChannelNumber uint16 // Encoded as uint16
func (n ChannelNumber) String() string { return strconv.Itoa(int(n)) }
// 16 bits of uint + 16 bits of RFFU = 0.
const channelNumberSize = 4
// AddTo adds CHANNEL-NUMBER to message.
func (n ChannelNumber) AddTo(m *stun.Message) error {
v := make([]byte, channelNumberSize)
binary.BigEndian.PutUint16(v[:2], uint16(n))
// v[2:4] are zeroes (RFFU = 0)
m.Add(stun.AttrChannelNumber, v)
return nil
}
// GetFrom decodes CHANNEL-NUMBER from message.
func (n *ChannelNumber) GetFrom(m *stun.Message) error {
v, err := m.Get(stun.AttrChannelNumber)
if err != nil {
return err
}
if err = stun.CheckSize(stun.AttrChannelNumber, len(v), channelNumberSize); err != nil {
return err
}
_ = v[channelNumberSize-1] // Asserting length
*n = ChannelNumber(binary.BigEndian.Uint16(v[:2]))
// v[2:4] is RFFU and equals to 0.
return nil
}
// See https://tools.ietf.org/html/rfc5766#section-11:
//
// 0x4000 through 0x7FFF: These values are the allowed channel
// numbers (16,383 possible values).
const (
MinChannelNumber = 0x4000
MaxChannelNumber = 0x7FFF
)
// ErrInvalidChannelNumber means that channel number is not valid as by RFC 5766 Section 11.
var ErrInvalidChannelNumber = errors.New("channel number not in [0x4000, 0x7FFF]")
// isChannelNumberValid returns true if c in [0x4000, 0x7FFF].
func isChannelNumberValid(c uint16) bool {
return c >= MinChannelNumber && c <= MaxChannelNumber
}
// Valid returns true if channel number has correct value that complies RFC 5766 Section 11 range.
func (n ChannelNumber) Valid() bool {
return isChannelNumberValid(uint16(n))
}

View File

@@ -0,0 +1,42 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package proto
import (
"encoding/binary"
"github.com/pion/stun"
)
// ConnectionID represents CONNECTION-ID attribute.
//
// The CONNECTION-ID attribute uniquely identifies a peer data
// connection. It is a 32-bit unsigned integral value.
//
// RFC 6062 Section 6.2.1
type ConnectionID uint32
const connectionIDSize = 4 // uint32: 4 bytes, 32 bits
// AddTo adds CONNECTION-ID to message.
func (c ConnectionID) AddTo(m *stun.Message) error {
v := make([]byte, lifetimeSize)
binary.BigEndian.PutUint32(v, uint32(c))
m.Add(stun.AttrConnectionID, v)
return nil
}
// GetFrom decodes CONNECTION-ID from message.
func (c *ConnectionID) GetFrom(m *stun.Message) error {
v, err := m.Get(stun.AttrConnectionID)
if err != nil {
return err
}
if err = stun.CheckSize(stun.AttrConnectionID, len(v), connectionIDSize); err != nil {
return err
}
_ = v[connectionIDSize-1] // Asserting length
*(*uint32)(c) = binary.BigEndian.Uint32(v)
return nil
}

View File

@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package proto
import "github.com/pion/stun"
// Data represents DATA attribute.
//
// The DATA attribute is present in all Send and Data indications. The
// value portion of this attribute is variable length and consists of
// the application data (that is, the data that would immediately follow
// the UDP header if the data was been sent directly between the client
// and the peer).
//
// RFC 5766 Section 14.4
type Data []byte
// AddTo adds DATA to message.
func (d Data) AddTo(m *stun.Message) error {
m.Add(stun.AttrData, d)
return nil
}
// GetFrom decodes DATA from message.
func (d *Data) GetFrom(m *stun.Message) error {
v, err := m.Get(stun.AttrData)
if err != nil {
return err
}
*d = v
return nil
}

View File

@@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package proto
import (
"github.com/pion/stun"
)
// DontFragmentAttr is a deprecated alias for DontFragment
// Deprecated: Please use DontFragment
type DontFragmentAttr = DontFragment
// DontFragment represents DONT-FRAGMENT attribute.
//
// This attribute is used by the client to request that the server set
// the DF (Don't Fragment) bit in the IP header when relaying the
// application data onward to the peer. This attribute has no value
// part and thus the attribute length field is 0.
//
// RFC 5766 Section 14.8
type DontFragment struct{}
const dontFragmentSize = 0
// AddTo adds DONT-FRAGMENT attribute to message.
func (DontFragment) AddTo(m *stun.Message) error {
m.Add(stun.AttrDontFragment, nil)
return nil
}
// GetFrom decodes DONT-FRAGMENT from message.
func (d *DontFragment) GetFrom(m *stun.Message) error {
v, err := m.Get(stun.AttrDontFragment)
if err != nil {
return err
}
return stun.CheckSize(stun.AttrDontFragment, len(v), dontFragmentSize)
}
// IsSet returns true if DONT-FRAGMENT attribute is set.
func (DontFragment) IsSet(m *stun.Message) bool {
_, err := m.Get(stun.AttrDontFragment)
return err == nil
}

View File

@@ -0,0 +1,58 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package proto
import "github.com/pion/stun"
// EvenPort represents EVEN-PORT attribute.
//
// This attribute allows the client to request that the port in the
// relayed transport address be even, and (optionally) that the server
// reserve the next-higher port number.
//
// RFC 5766 Section 14.6
type EvenPort struct {
// ReservePort means that the server is requested to reserve
// the next-higher port number (on the same IP address)
// for a subsequent allocation.
ReservePort bool
}
func (p EvenPort) String() string {
if p.ReservePort {
return "reserve: true"
}
return "reserve: false"
}
const (
evenPortSize = 1
firstBitSet = (1 << 8) - 1 // 0b100000000
)
// AddTo adds EVEN-PORT to message.
func (p EvenPort) AddTo(m *stun.Message) error {
v := make([]byte, evenPortSize)
if p.ReservePort {
// Set first bit to 1.
v[0] = firstBitSet
}
m.Add(stun.AttrEvenPort, v)
return nil
}
// GetFrom decodes EVEN-PORT from message.
func (p *EvenPort) GetFrom(m *stun.Message) error {
v, err := m.Get(stun.AttrEvenPort)
if err != nil {
return err
}
if err = stun.CheckSize(stun.AttrEvenPort, len(v), evenPortSize); err != nil {
return err
}
if v[0]&firstBitSet > 0 {
p.ReservePort = true
}
return nil
}

View File

@@ -0,0 +1,55 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package proto
import (
"encoding/binary"
"time"
"github.com/pion/stun"
)
// DefaultLifetime in RFC 5766 is 10 minutes.
//
// RFC 5766 Section 2.2
const DefaultLifetime = time.Minute * 10
// Lifetime represents LIFETIME attribute.
//
// The LIFETIME attribute represents the duration for which the server
// will maintain an allocation in the absence of a refresh. The value
// portion of this attribute is 4-bytes long and consists of a 32-bit
// unsigned integral value representing the number of seconds remaining
// until expiration.
//
// RFC 5766 Section 14.2
type Lifetime struct {
time.Duration
}
// Seconds in uint32
const lifetimeSize = 4 // 4 bytes, 32 bits
// AddTo adds LIFETIME to message.
func (l Lifetime) AddTo(m *stun.Message) error {
v := make([]byte, lifetimeSize)
binary.BigEndian.PutUint32(v, uint32(l.Seconds()))
m.Add(stun.AttrLifetime, v)
return nil
}
// GetFrom decodes LIFETIME from message.
func (l *Lifetime) GetFrom(m *stun.Message) error {
v, err := m.Get(stun.AttrLifetime)
if err != nil {
return err
}
if err = stun.CheckSize(stun.AttrLifetime, len(v), lifetimeSize); err != nil {
return err
}
_ = v[lifetimeSize-1] // Asserting length
seconds := binary.BigEndian.Uint32(v)
l.Duration = time.Second * time.Duration(seconds)
return nil
}

View File

@@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package proto
import (
"net"
"github.com/pion/stun"
)
// PeerAddress implements XOR-PEER-ADDRESS attribute.
//
// The XOR-PEER-ADDRESS specifies the address and port of the peer as
// seen from the TURN server. (For example, the peer's server-reflexive
// transport address if the peer is behind a NAT.)
//
// RFC 5766 Section 14.3
type PeerAddress struct {
IP net.IP
Port int
}
func (a PeerAddress) String() string {
return stun.XORMappedAddress(a).String()
}
// AddTo adds XOR-PEER-ADDRESS to message.
func (a PeerAddress) AddTo(m *stun.Message) error {
return stun.XORMappedAddress(a).AddToAs(m, stun.AttrXORPeerAddress)
}
// GetFrom decodes XOR-PEER-ADDRESS from message.
func (a *PeerAddress) GetFrom(m *stun.Message) error {
return (*stun.XORMappedAddress)(a).GetFromAs(m, stun.AttrXORPeerAddress)
}
// XORPeerAddress implements XOR-PEER-ADDRESS attribute.
//
// The XOR-PEER-ADDRESS specifies the address and port of the peer as
// seen from the TURN server. (For example, the peer's server-reflexive
// transport address if the peer is behind a NAT.)
//
// RFC 5766 Section 14.3
type XORPeerAddress = PeerAddress

View File

@@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package proto implements RFC 5766 Traversal Using Relays around NAT.
package proto
import (
"github.com/pion/stun"
)
// Default ports for TURN from RFC 5766 Section 4.
const (
// DefaultPort for TURN is same as STUN.
DefaultPort = stun.DefaultPort
// DefaultTLSPort is for TURN over TLS and is same as STUN.
DefaultTLSPort = stun.DefaultTLSPort
)
// CreatePermissionRequest is shorthand for create permission request type.
func CreatePermissionRequest() stun.MessageType {
return stun.NewType(stun.MethodCreatePermission, stun.ClassRequest)
}
// AllocateRequest is shorthand for allocation request message type.
func AllocateRequest() stun.MessageType { return stun.NewType(stun.MethodAllocate, stun.ClassRequest) }
// SendIndication is shorthand for send indication message type.
func SendIndication() stun.MessageType { return stun.NewType(stun.MethodSend, stun.ClassIndication) }
// RefreshRequest is shorthand for refresh request message type.
func RefreshRequest() stun.MessageType { return stun.NewType(stun.MethodRefresh, stun.ClassRequest) }

View File

@@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package proto
import (
"net"
"github.com/pion/stun"
)
// RelayedAddress implements XOR-RELAYED-ADDRESS attribute.
//
// It specifies the address and port that the server allocated to the
// client. It is encoded in the same way as XOR-MAPPED-ADDRESS.
//
// RFC 5766 Section 14.5
type RelayedAddress struct {
IP net.IP
Port int
}
func (a RelayedAddress) String() string {
return stun.XORMappedAddress(a).String()
}
// AddTo adds XOR-PEER-ADDRESS to message.
func (a RelayedAddress) AddTo(m *stun.Message) error {
return stun.XORMappedAddress(a).AddToAs(m, stun.AttrXORRelayedAddress)
}
// GetFrom decodes XOR-PEER-ADDRESS from message.
func (a *RelayedAddress) GetFrom(m *stun.Message) error {
return (*stun.XORMappedAddress)(a).GetFromAs(m, stun.AttrXORRelayedAddress)
}
// XORRelayedAddress implements XOR-RELAYED-ADDRESS attribute.
//
// It specifies the address and port that the server allocated to the
// client. It is encoded in the same way as XOR-MAPPED-ADDRESS.
//
// RFC 5766 Section 14.5
type XORRelayedAddress = RelayedAddress

View File

@@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package proto
import (
"errors"
"github.com/pion/stun"
)
// RequestedAddressFamily represents the REQUESTED-ADDRESS-FAMILY Attribute as
// defined in RFC 6156 Section 4.1.1.
type RequestedAddressFamily byte
const requestedFamilySize = 4
var errInvalidRequestedFamilyValue = errors.New("invalid value for requested family attribute")
// GetFrom decodes REQUESTED-ADDRESS-FAMILY from message.
func (f *RequestedAddressFamily) GetFrom(m *stun.Message) error {
v, err := m.Get(stun.AttrRequestedAddressFamily)
if err != nil {
return err
}
if err = stun.CheckSize(stun.AttrRequestedAddressFamily, len(v), requestedFamilySize); err != nil {
return err
}
switch v[0] {
case byte(RequestedFamilyIPv4), byte(RequestedFamilyIPv6):
*f = RequestedAddressFamily(v[0])
default:
return errInvalidRequestedFamilyValue
}
return nil
}
func (f RequestedAddressFamily) String() string {
switch f {
case RequestedFamilyIPv4:
return "IPv4"
case RequestedFamilyIPv6:
return "IPv6"
default:
return "unknown"
}
}
// AddTo adds REQUESTED-ADDRESS-FAMILY to message.
func (f RequestedAddressFamily) AddTo(m *stun.Message) error {
v := make([]byte, requestedFamilySize)
v[0] = byte(f)
// b[1:4] is RFFU = 0.
// The RFFU field MUST be set to zero on transmission and MUST be
// ignored on reception. It is reserved for future uses.
m.Add(stun.AttrRequestedAddressFamily, v)
return nil
}
// Values for RequestedAddressFamily as defined in RFC 6156 Section 4.1.1.
const (
RequestedFamilyIPv4 RequestedAddressFamily = 0x01
RequestedFamilyIPv6 RequestedAddressFamily = 0x02
)

View File

@@ -0,0 +1,72 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package proto
import (
"strconv"
"github.com/pion/stun"
)
// Protocol is IANA assigned protocol number.
type Protocol byte
const (
// ProtoTCP is IANA assigned protocol number for TCP.
ProtoTCP Protocol = 6
// ProtoUDP is IANA assigned protocol number for UDP.
ProtoUDP Protocol = 17
)
func (p Protocol) String() string {
switch p {
case ProtoTCP:
return "TCP"
case ProtoUDP:
return "UDP"
default:
return strconv.Itoa(int(p))
}
}
// RequestedTransport represents REQUESTED-TRANSPORT attribute.
//
// This attribute is used by the client to request a specific transport
// protocol for the allocated transport address. RFC 5766 only allows the use of
// code point 17 (User Datagram Protocol).
//
// RFC 5766 Section 14.7
type RequestedTransport struct {
Protocol Protocol
}
func (t RequestedTransport) String() string {
return "protocol: " + t.Protocol.String()
}
const requestedTransportSize = 4
// AddTo adds REQUESTED-TRANSPORT to message.
func (t RequestedTransport) AddTo(m *stun.Message) error {
v := make([]byte, requestedTransportSize)
v[0] = byte(t.Protocol)
// b[1:4] is RFFU = 0.
// The RFFU field MUST be set to zero on transmission and MUST be
// ignored on reception. It is reserved for future uses.
m.Add(stun.AttrRequestedTransport, v)
return nil
}
// GetFrom decodes REQUESTED-TRANSPORT from message.
func (t *RequestedTransport) GetFrom(m *stun.Message) error {
v, err := m.Get(stun.AttrRequestedTransport)
if err != nil {
return err
}
if err = stun.CheckSize(stun.AttrRequestedTransport, len(v), requestedTransportSize); err != nil {
return err
}
t.Protocol = Protocol(v[0])
return nil
}

View File

@@ -0,0 +1,42 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package proto
import "github.com/pion/stun"
// ReservationToken represents RESERVATION-TOKEN attribute.
//
// The RESERVATION-TOKEN attribute contains a token that uniquely
// identifies a relayed transport address being held in reserve by the
// server. The server includes this attribute in a success response to
// tell the client about the token, and the client includes this
// attribute in a subsequent Allocate request to request the server use
// that relayed transport address for the allocation.
//
// RFC 5766 Section 14.9
type ReservationToken []byte
const reservationTokenSize = 8 // 8 bytes
// AddTo adds RESERVATION-TOKEN to message.
func (t ReservationToken) AddTo(m *stun.Message) error {
if err := stun.CheckSize(stun.AttrReservationToken, len(t), reservationTokenSize); err != nil {
return err
}
m.Add(stun.AttrReservationToken, t)
return nil
}
// GetFrom decodes RESERVATION-TOKEN from message.
func (t *ReservationToken) GetFrom(m *stun.Message) error {
v, err := m.Get(stun.AttrReservationToken)
if err != nil {
return err
}
if err = stun.CheckSize(stun.AttrReservationToken, len(v), reservationTokenSize); err != nil {
return err
}
*t = v
return nil
}