rtmp, rtsp: support PROXY protocol (#5754)

Support PROXY protocol v1/v2 on RTMP, RTMPS, RTSP, and RTSPS TCP
listeners so real client IPs are visible when running behind L4 proxies
(nginx stream, HAProxy, AWS NLB).

---------

Co-authored-by: aler9 <46489434+aler9@users.noreply.github.com>
This commit is contained in:
Alex McKenzie
2026-06-05 21:37:53 +02:00
committed by GitHub
co-authored by aler9
parent f5d7ed3138
commit b2dc62e13c
27 changed files with 1009 additions and 489 deletions
+8
View File
@@ -449,6 +449,10 @@ components:
type: array type: array
items: items:
$ref: "#/components/schemas/RTSPAuthMethod" $ref: "#/components/schemas/RTSPAuthMethod"
rtspTrustedProxies:
type: array
items:
type: string
rtspUDPReadBufferSize: rtspUDPReadBufferSize:
type: integer type: integer
format: uint64 format: uint64
@@ -472,6 +476,10 @@ components:
type: string type: string
rtmpServerCert: rtmpServerCert:
type: string type: string
rtmpTrustedProxies:
type: array
items:
type: string
# HLS server # HLS server
hls: hls:
+1
View File
@@ -37,6 +37,7 @@ require (
github.com/pion/sdp/v3 v3.0.18 github.com/pion/sdp/v3 v3.0.18
github.com/pion/transport/v4 v4.0.2 github.com/pion/transport/v4 v4.0.2
github.com/pion/webrtc/v4 v4.2.14 github.com/pion/webrtc/v4 v4.2.14
github.com/pires/go-proxyproto v0.12.0
github.com/quic-go/quic-go v0.59.1 github.com/quic-go/quic-go v0.59.1
github.com/quic-go/webtransport-go v0.10.0 github.com/quic-go/webtransport-go v0.10.0
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
+2
View File
@@ -199,6 +199,8 @@ github.com/pion/turn/v5 v5.0.8 h1:pZUCtmwWCMkrRKqh/8pL3WoGADXBe0/lOPkN7oqFjK8=
github.com/pion/turn/v5 v5.0.8/go.mod h1:1VwvxElZaOdJU0liJ/WUSm/Tsh+n2OxS5ISSDxgOWxU= github.com/pion/turn/v5 v5.0.8/go.mod h1:1VwvxElZaOdJU0liJ/WUSm/Tsh+n2OxS5ISSDxgOWxU=
github.com/pion/webrtc/v4 v4.2.14 h1:Q6zMs+fSDsYuhZcNlvFGBxCOMHVV9oYcDa6O9/HIGTc= github.com/pion/webrtc/v4 v4.2.14 h1:Q6zMs+fSDsYuhZcNlvFGBxCOMHVV9oYcDa6O9/HIGTc=
github.com/pion/webrtc/v4 v4.2.14/go.mod h1:87NVKP86+g4OMrRxWhjWfUjeXP4JrV6RTlUrIW+/Jak= github.com/pion/webrtc/v4 v4.2.14/go.mod h1:87NVKP86+g4OMrRxWhjWfUjeXP4JrV6RTlUrIW+/Jak=
github.com/pires/go-proxyproto v0.12.0 h1:TTCxD66dU898tahivkqc3hoceZp7P44FnorWyo9d5vM=
github.com/pires/go-proxyproto v0.12.0/go.mod h1:qUvfqUMEoX7T8g0q7TQLDnhMjdTrxnG0hvpMn+7ePNI=
github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU= github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU=
github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+9 -7
View File
@@ -335,16 +335,18 @@ type Conf struct {
RTSPServerCert string `json:"rtspServerCert"` RTSPServerCert string `json:"rtspServerCert"`
AuthMethods *RTSPAuthMethods `json:"authMethods,omitempty" deprecated:"true"` AuthMethods *RTSPAuthMethods `json:"authMethods,omitempty" deprecated:"true"`
RTSPAuthMethods RTSPAuthMethods `json:"rtspAuthMethods"` RTSPAuthMethods RTSPAuthMethods `json:"rtspAuthMethods"`
RTSPTrustedProxies IPNetworks `json:"rtspTrustedProxies"`
RTSPUDPReadBufferSize *uint `json:"rtspUDPReadBufferSize,omitempty" deprecated:"true"` RTSPUDPReadBufferSize *uint `json:"rtspUDPReadBufferSize,omitempty" deprecated:"true"`
// RTMP server // RTMP server
RTMP bool `json:"rtmp"` RTMP bool `json:"rtmp"`
RTMPDisable *bool `json:"rtmpDisable,omitempty" deprecated:"true"` RTMPDisable *bool `json:"rtmpDisable,omitempty" deprecated:"true"`
RTMPEncryption Encryption `json:"rtmpEncryption"` RTMPEncryption Encryption `json:"rtmpEncryption"`
RTMPAddress string `json:"rtmpAddress"` RTMPAddress string `json:"rtmpAddress"`
RTMPSAddress string `json:"rtmpsAddress"` RTMPSAddress string `json:"rtmpsAddress"`
RTMPServerKey string `json:"rtmpServerKey"` RTMPServerKey string `json:"rtmpServerKey"`
RTMPServerCert string `json:"rtmpServerCert"` RTMPServerCert string `json:"rtmpServerCert"`
RTMPTrustedProxies IPNetworks `json:"rtmpTrustedProxies"`
// HLS server // HLS server
HLS bool `json:"hls"` HLS bool `json:"hls"`
+8
View File
@@ -491,6 +491,7 @@ func (p *Core) createResources(initial bool) error {
ServerCert: "", ServerCert: "",
ServerKey: "", ServerKey: "",
RTSPAddress: p.conf.RTSPAddress, RTSPAddress: p.conf.RTSPAddress,
TrustedProxies: p.conf.RTSPTrustedProxies,
Transports: p.conf.RTSPTransports, Transports: p.conf.RTSPTransports,
RunOnConnect: p.conf.RunOnConnect, RunOnConnect: p.conf.RunOnConnect,
RunOnConnectRestart: p.conf.RunOnConnectRestart, RunOnConnectRestart: p.conf.RunOnConnectRestart,
@@ -534,6 +535,7 @@ func (p *Core) createResources(initial bool) error {
ServerCert: p.conf.RTSPServerCert, ServerCert: p.conf.RTSPServerCert,
ServerKey: p.conf.RTSPServerKey, ServerKey: p.conf.RTSPServerKey,
RTSPAddress: p.conf.RTSPAddress, RTSPAddress: p.conf.RTSPAddress,
TrustedProxies: p.conf.RTSPTrustedProxies,
Transports: p.conf.RTSPTransports, Transports: p.conf.RTSPTransports,
RunOnConnect: p.conf.RunOnConnect, RunOnConnect: p.conf.RunOnConnect,
RunOnConnectRestart: p.conf.RunOnConnectRestart, RunOnConnectRestart: p.conf.RunOnConnectRestart,
@@ -563,6 +565,7 @@ func (p *Core) createResources(initial bool) error {
ServerCert: "", ServerCert: "",
ServerKey: "", ServerKey: "",
RTSPAddress: p.conf.RTSPAddress, RTSPAddress: p.conf.RTSPAddress,
TrustedProxies: p.conf.RTMPTrustedProxies,
RunOnConnect: p.conf.RunOnConnect, RunOnConnect: p.conf.RunOnConnect,
RunOnConnectRestart: p.conf.RunOnConnectRestart, RunOnConnectRestart: p.conf.RunOnConnectRestart,
RunOnDisconnect: p.conf.RunOnDisconnect, RunOnDisconnect: p.conf.RunOnDisconnect,
@@ -591,6 +594,7 @@ func (p *Core) createResources(initial bool) error {
ServerKey: p.conf.RTMPServerKey, ServerKey: p.conf.RTMPServerKey,
DumpPackets: p.conf.DumpPackets, DumpPackets: p.conf.DumpPackets,
RTSPAddress: p.conf.RTSPAddress, RTSPAddress: p.conf.RTSPAddress,
TrustedProxies: p.conf.RTMPTrustedProxies,
RunOnConnect: p.conf.RunOnConnect, RunOnConnect: p.conf.RunOnConnect,
RunOnConnectRestart: p.conf.RunOnConnectRestart, RunOnConnectRestart: p.conf.RunOnConnectRestart,
RunOnDisconnect: p.conf.RunOnDisconnect, RunOnDisconnect: p.conf.RunOnDisconnect,
@@ -876,6 +880,7 @@ func (p *Core) closeResources(newConf *conf.Conf, calledByAPI bool) {
newConf.MulticastRTPPort != p.conf.MulticastRTPPort || newConf.MulticastRTPPort != p.conf.MulticastRTPPort ||
newConf.MulticastRTCPPort != p.conf.MulticastRTCPPort || newConf.MulticastRTCPPort != p.conf.MulticastRTCPPort ||
!reflect.DeepEqual(newConf.RTSPTransports, p.conf.RTSPTransports) || !reflect.DeepEqual(newConf.RTSPTransports, p.conf.RTSPTransports) ||
!reflect.DeepEqual(newConf.RTSPTrustedProxies, p.conf.RTSPTrustedProxies) ||
newConf.RunOnConnect != p.conf.RunOnConnect || newConf.RunOnConnect != p.conf.RunOnConnect ||
newConf.RunOnConnectRestart != p.conf.RunOnConnectRestart || newConf.RunOnConnectRestart != p.conf.RunOnConnectRestart ||
newConf.RunOnDisconnect != p.conf.RunOnDisconnect || newConf.RunOnDisconnect != p.conf.RunOnDisconnect ||
@@ -898,6 +903,7 @@ func (p *Core) closeResources(newConf *conf.Conf, calledByAPI bool) {
newConf.RTSPServerKey != p.conf.RTSPServerKey || newConf.RTSPServerKey != p.conf.RTSPServerKey ||
newConf.RTSPAddress != p.conf.RTSPAddress || newConf.RTSPAddress != p.conf.RTSPAddress ||
!reflect.DeepEqual(newConf.RTSPTransports, p.conf.RTSPTransports) || !reflect.DeepEqual(newConf.RTSPTransports, p.conf.RTSPTransports) ||
!reflect.DeepEqual(newConf.RTSPTrustedProxies, p.conf.RTSPTrustedProxies) ||
newConf.RunOnConnect != p.conf.RunOnConnect || newConf.RunOnConnect != p.conf.RunOnConnect ||
newConf.RunOnConnectRestart != p.conf.RunOnConnectRestart || newConf.RunOnConnectRestart != p.conf.RunOnConnectRestart ||
newConf.RunOnDisconnect != p.conf.RunOnDisconnect || newConf.RunOnDisconnect != p.conf.RunOnDisconnect ||
@@ -913,6 +919,7 @@ func (p *Core) closeResources(newConf *conf.Conf, calledByAPI bool) {
newConf.ReadTimeout != p.conf.ReadTimeout || newConf.ReadTimeout != p.conf.ReadTimeout ||
newConf.WriteTimeout != p.conf.WriteTimeout || newConf.WriteTimeout != p.conf.WriteTimeout ||
newConf.RTSPAddress != p.conf.RTSPAddress || newConf.RTSPAddress != p.conf.RTSPAddress ||
!reflect.DeepEqual(newConf.RTMPTrustedProxies, p.conf.RTMPTrustedProxies) ||
newConf.RunOnConnect != p.conf.RunOnConnect || newConf.RunOnConnect != p.conf.RunOnConnect ||
newConf.RunOnConnectRestart != p.conf.RunOnConnectRestart || newConf.RunOnConnectRestart != p.conf.RunOnConnectRestart ||
newConf.RunOnDisconnect != p.conf.RunOnDisconnect || newConf.RunOnDisconnect != p.conf.RunOnDisconnect ||
@@ -930,6 +937,7 @@ func (p *Core) closeResources(newConf *conf.Conf, calledByAPI bool) {
newConf.RTMPServerCert != p.conf.RTMPServerCert || newConf.RTMPServerCert != p.conf.RTMPServerCert ||
newConf.RTMPServerKey != p.conf.RTMPServerKey || newConf.RTMPServerKey != p.conf.RTMPServerKey ||
newConf.RTSPAddress != p.conf.RTSPAddress || newConf.RTSPAddress != p.conf.RTSPAddress ||
!reflect.DeepEqual(newConf.RTMPTrustedProxies, p.conf.RTMPTrustedProxies) ||
newConf.RunOnConnect != p.conf.RunOnConnect || newConf.RunOnConnect != p.conf.RunOnConnect ||
newConf.RunOnConnectRestart != p.conf.RunOnConnectRestart || newConf.RunOnConnectRestart != p.conf.RunOnConnectRestart ||
newConf.RunOnDisconnect != p.conf.RunOnDisconnect || newConf.RunOnDisconnect != p.conf.RunOnDisconnect ||
+4 -4
View File
@@ -8,6 +8,7 @@ import (
"net" "net"
"os" "os"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/google/gopacket" "github.com/google/gopacket"
@@ -72,7 +73,7 @@ type conn struct {
Conn net.Conn Conn net.Conn
ServerSide bool ServerSide bool
expectingSecrets int expectingSecrets atomic.Int32
f *os.File f *os.File
pw *pcapgo.NgWriter pw *pcapgo.NgWriter
once sync.Once once sync.Once
@@ -157,7 +158,7 @@ func (c *conn) run() {
} }
func (c *conn) processEntry(e dumpEntry) { func (c *conn) processEntry(e dumpEntry) {
if c.expectingSecrets > 0 && e.direction != dirSecret { if c.expectingSecrets.Load() > 0 && e.direction != dirSecret {
c.delayed = append(c.delayed, e) c.delayed = append(c.delayed, e)
return return
} }
@@ -189,8 +190,7 @@ func (c *conn) processEntry(e dumpEntry) {
c.pw.Flush() //nolint:errcheck c.pw.Flush() //nolint:errcheck
writeDecryptionSecretsBlock(c.f, e.data) writeDecryptionSecretsBlock(c.f, e.data)
c.expectingSecrets-- if c.expectingSecrets.Add(-1) == 0 {
if c.expectingSecrets == 0 {
for _, e2 := range c.delayed { for _, e2 := range c.delayed {
c.processEntry(e2) c.processEntry(e2)
} }
+1 -1
View File
@@ -36,7 +36,7 @@ func (t *DialTLSContext) Do(ctx context.Context, network, addr string) (net.Conn
} }
pdConn := netConn.(*conn) pdConn := netConn.(*conn)
pdConn.expectingSecrets = 4 pdConn.expectingSecrets.Store(4)
tlsConfig.KeyLogWriter = &connKeyLogWriter{c: pdConn} tlsConfig.KeyLogWriter = &connKeyLogWriter{c: pdConn}
return tls.Client(netConn, tlsConfig), nil return tls.Client(netConn, tlsConfig), nil
-23
View File
@@ -1,23 +0,0 @@
package packetdumper
import (
"net"
)
// Listen is a wrapper around net.Listen that dumps packets to disk.
type Listen struct {
Prefix string
}
// Do mimics net.Listen.
func (l *Listen) Do(network, address string) (net.Listener, error) {
netListener, err := net.Listen(network, address)
if err != nil {
return nil, err
}
return &listener{
Prefix: l.Prefix,
Listener: netListener,
}, nil
}
-30
View File
@@ -1,30 +0,0 @@
package packetdumper
import (
"net"
)
// ListenPacket is a wrapper around net.ListenPacket that dumps packets to disk.
type ListenPacket struct {
Prefix string
}
// Do mimics net.ListenPacket.
func (l *ListenPacket) Do(network, address string) (net.PacketConn, error) {
netPacketConn, err := net.ListenPacket(network, address)
if err != nil {
return nil, err
}
pdPacketConn := &packetConn{
Prefix: l.Prefix,
PacketConn: netPacketConn,
}
err = pdPacketConn.Initialize()
if err != nil {
netPacketConn.Close()
return nil, err
}
return pdPacketConn, nil
}
+11 -11
View File
@@ -2,17 +2,17 @@ package packetdumper
import "net" import "net"
var _ net.Listener = (*listener)(nil) var _ net.Listener = (*Listener)(nil)
// listener is a wrapper around a net.Listener that dumps packets to disk. // Listener is a wrapper around a net.Listener that dumps packets to disk.
type listener struct { type Listener struct {
Prefix string Wrapped net.Listener
Listener net.Listener Prefix string
} }
// Accept implements net.Listener. // Accept implements net.Listener.
func (l *listener) Accept() (net.Conn, error) { func (l *Listener) Accept() (net.Conn, error) {
netConn, err := l.Listener.Accept() netConn, err := l.Wrapped.Accept()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -32,11 +32,11 @@ func (l *listener) Accept() (net.Conn, error) {
} }
// Close implements net.Listener. // Close implements net.Listener.
func (l *listener) Close() error { func (l *Listener) Close() error {
return l.Listener.Close() return l.Wrapped.Close()
} }
// Addr implements net.Listener. // Addr implements net.Listener.
func (l *listener) Addr() net.Addr { func (l *Listener) Addr() net.Addr {
return l.Listener.Addr() return l.Wrapped.Addr()
} }
+44
View File
@@ -0,0 +1,44 @@
package packetdumper
import (
"net"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
)
func TestListener(t *testing.T) {
innerLn, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
prefix := filepath.Join(t.TempDir(), "capture")
ln := &Listener{
Wrapped: innerLn,
Prefix: prefix,
}
require.Equal(t, innerLn.Addr(), ln.Addr())
clientConn, err := net.Dial("tcp", ln.Addr().String())
require.NoError(t, err)
defer clientConn.Close()
serverConn, err := ln.Accept()
require.NoError(t, err)
defer serverConn.Close()
_, err = clientConn.Write([]byte("ping"))
require.NoError(t, err)
buf := make([]byte, 4)
_, err = serverConn.Read(buf)
require.NoError(t, err)
require.Equal(t, []byte("ping"), buf)
serverConn.Close()
clientConn.Close()
ln.Close() //nolint:errcheck
checkPcapngPresence(t, prefix)
}
+25 -25
View File
@@ -14,7 +14,7 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
) )
var _ net.PacketConn = (*packetConn)(nil) var _ net.PacketConn = (*PacketConn)(nil)
type extendedPacketConn interface { type extendedPacketConn interface {
net.PacketConn net.PacketConn
@@ -28,10 +28,10 @@ type packetDumpEntry struct {
src, dst *net.UDPAddr src, dst *net.UDPAddr
} }
// packetConn is a wrapper around net.PacketConn that dumps packets to disk. // PacketConn is a wrapper around net.PacketConn that dumps packets to disk.
type packetConn struct { type PacketConn struct {
Prefix string Prefix string
PacketConn net.PacketConn Wrapped net.PacketConn
f *os.File f *os.File
pw *pcapgo.NgWriter pw *pcapgo.NgWriter
@@ -43,7 +43,7 @@ type packetConn struct {
} }
// Initialize initializes packetConn. // Initialize initializes packetConn.
func (c *packetConn) Initialize() error { func (c *PacketConn) Initialize() error {
var err error var err error
c.f, err = os.Create(fmt.Sprintf("%s_%d_%s.pcapng", c.Prefix, time.Now().UnixNano(), uuid.New().String())) c.f, err = os.Create(fmt.Sprintf("%s_%d_%s.pcapng", c.Prefix, time.Now().UnixNano(), uuid.New().String()))
if err != nil { if err != nil {
@@ -66,15 +66,15 @@ func (c *packetConn) Initialize() error {
} }
// Close implements net.PacketConn. // Close implements net.PacketConn.
func (c *packetConn) Close() error { func (c *PacketConn) Close() error {
c.once.Do(func() { c.once.Do(func() {
close(c.terminated) close(c.terminated)
}) })
<-c.done <-c.done
return c.PacketConn.Close() return c.Wrapped.Close()
} }
func (c *packetConn) run() { func (c *PacketConn) run() {
defer close(c.done) defer close(c.done)
defer c.f.Close() defer c.f.Close()
@@ -97,7 +97,7 @@ func (c *packetConn) run() {
} }
} }
func (c *packetConn) writePacket(ntp time.Time, src, dst *net.UDPAddr, payload []byte) { func (c *PacketConn) writePacket(ntp time.Time, src, dst *net.UDPAddr, payload []byte) {
eth := &layers.Ethernet{ eth := &layers.Ethernet{
SrcMAC: net.HardwareAddr{0, 0, 0, 0, 0, 0}, SrcMAC: net.HardwareAddr{0, 0, 0, 0, 0, 0},
DstMAC: net.HardwareAddr{0, 0, 0, 0, 0, 0}, DstMAC: net.HardwareAddr{0, 0, 0, 0, 0, 0},
@@ -130,7 +130,7 @@ func (c *packetConn) writePacket(ntp time.Time, src, dst *net.UDPAddr, payload [
}, raw) }, raw)
} }
func (c *packetConn) enqueue(e packetDumpEntry) { func (c *PacketConn) enqueue(e packetDumpEntry) {
select { select {
case c.queue <- e: case c.queue <- e:
case <-c.terminated: case <-c.terminated:
@@ -138,11 +138,11 @@ func (c *packetConn) enqueue(e packetDumpEntry) {
} }
// ReadFrom implements net.PacketConn. // ReadFrom implements net.PacketConn.
func (c *packetConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { func (c *PacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
n, addr, err = c.PacketConn.ReadFrom(p) n, addr, err = c.Wrapped.ReadFrom(p)
if n != 0 { if n != 0 {
local := c.PacketConn.LocalAddr().(*net.UDPAddr) local := c.Wrapped.LocalAddr().(*net.UDPAddr)
remote := addr.(*net.UDPAddr) remote := addr.(*net.UDPAddr)
c.enqueue(packetDumpEntry{ c.enqueue(packetDumpEntry{
@@ -157,11 +157,11 @@ func (c *packetConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
} }
// WriteTo implements net.PacketConn. // WriteTo implements net.PacketConn.
func (c *packetConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { func (c *PacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
n, err = c.PacketConn.WriteTo(p, addr) n, err = c.Wrapped.WriteTo(p, addr)
if err == nil { if err == nil {
local := c.PacketConn.LocalAddr().(*net.UDPAddr) local := c.Wrapped.LocalAddr().(*net.UDPAddr)
remote := addr.(*net.UDPAddr) remote := addr.(*net.UDPAddr)
c.enqueue(packetDumpEntry{ c.enqueue(packetDumpEntry{
@@ -176,23 +176,23 @@ func (c *packetConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
} }
// LocalAddr implements net.PacketConn. // LocalAddr implements net.PacketConn.
func (c *packetConn) LocalAddr() net.Addr { return c.PacketConn.LocalAddr() } func (c *PacketConn) LocalAddr() net.Addr { return c.Wrapped.LocalAddr() }
// SetDeadline implements net.PacketConn. // SetDeadline implements net.PacketConn.
func (c *packetConn) SetDeadline(t time.Time) error { return c.PacketConn.SetDeadline(t) } func (c *PacketConn) SetDeadline(t time.Time) error { return c.Wrapped.SetDeadline(t) }
// SetReadDeadline implements net.PacketConn. // SetReadDeadline implements net.PacketConn.
func (c *packetConn) SetReadDeadline(t time.Time) error { return c.PacketConn.SetReadDeadline(t) } func (c *PacketConn) SetReadDeadline(t time.Time) error { return c.Wrapped.SetReadDeadline(t) }
// SetWriteDeadline implements net.PacketConn. // SetWriteDeadline implements net.PacketConn.
func (c *packetConn) SetWriteDeadline(t time.Time) error { return c.PacketConn.SetWriteDeadline(t) } func (c *PacketConn) SetWriteDeadline(t time.Time) error { return c.Wrapped.SetWriteDeadline(t) }
// SetReadBuffer implements extendedPacketConn. // SetReadBuffer implements extendedPacketConn.
func (c *packetConn) SetReadBuffer(bytes int) error { func (c *PacketConn) SetReadBuffer(bytes int) error {
return c.PacketConn.(extendedPacketConn).SetReadBuffer(bytes) return c.Wrapped.(extendedPacketConn).SetReadBuffer(bytes)
} }
// SyscallConn implements extendedPacketConn. // SyscallConn implements extendedPacketConn.
func (c *packetConn) SyscallConn() (syscall.RawConn, error) { func (c *PacketConn) SyscallConn() (syscall.RawConn, error) {
return c.PacketConn.(extendedPacketConn).SyscallConn() return c.Wrapped.(extendedPacketConn).SyscallConn()
} }
+7 -7
View File
@@ -37,7 +37,7 @@ func TestPacketConnInitialize_CreatesFile(t *testing.T) {
client, server := startUDPPair(t) client, server := startUDPPair(t)
prefix := filepath.Join(t.TempDir(), "capture") prefix := filepath.Join(t.TempDir(), "capture")
c := &packetConn{Prefix: prefix, PacketConn: client} c := &PacketConn{Prefix: prefix, Wrapped: client}
require.NoError(t, c.Initialize()) require.NoError(t, c.Initialize())
c.Close() //nolint:errcheck c.Close() //nolint:errcheck
@@ -50,7 +50,7 @@ func TestPacketConnWriteTo(t *testing.T) {
client, server := startUDPPair(t) client, server := startUDPPair(t)
prefix := filepath.Join(t.TempDir(), "capture") prefix := filepath.Join(t.TempDir(), "capture")
c := &packetConn{Prefix: prefix, PacketConn: client} c := &PacketConn{Prefix: prefix, Wrapped: client}
require.NoError(t, c.Initialize()) require.NoError(t, c.Initialize())
n, err := c.WriteTo([]byte("hello world"), server.LocalAddr()) n, err := c.WriteTo([]byte("hello world"), server.LocalAddr())
@@ -73,7 +73,7 @@ func TestPacketConnReadFrom(t *testing.T) {
client, server := startUDPPair(t) client, server := startUDPPair(t)
prefix := filepath.Join(t.TempDir(), "capture") prefix := filepath.Join(t.TempDir(), "capture")
c := &packetConn{Prefix: prefix, PacketConn: client} c := &PacketConn{Prefix: prefix, Wrapped: client}
require.NoError(t, c.Initialize()) require.NoError(t, c.Initialize())
_, err := server.WriteTo([]byte("incoming data"), client.LocalAddr()) _, err := server.WriteTo([]byte("incoming data"), client.LocalAddr())
@@ -96,7 +96,7 @@ func TestPacketConnMultipleWriteRead(t *testing.T) {
client, server := startUDPPair(t) client, server := startUDPPair(t)
prefix := filepath.Join(t.TempDir(), "capture") prefix := filepath.Join(t.TempDir(), "capture")
c := &packetConn{Prefix: prefix, PacketConn: client} c := &PacketConn{Prefix: prefix, Wrapped: client}
require.NoError(t, c.Initialize()) require.NoError(t, c.Initialize())
serverAddr := server.LocalAddr() serverAddr := server.LocalAddr()
@@ -140,7 +140,7 @@ func TestPacketConnCloseIdempotent(t *testing.T) {
client, server := startUDPPair(t) client, server := startUDPPair(t)
prefix := filepath.Join(t.TempDir(), "capture") prefix := filepath.Join(t.TempDir(), "capture")
c := &packetConn{Prefix: prefix, PacketConn: client} c := &PacketConn{Prefix: prefix, Wrapped: client}
require.NoError(t, c.Initialize()) require.NoError(t, c.Initialize())
c.Close() //nolint:errcheck c.Close() //nolint:errcheck
@@ -154,7 +154,7 @@ func TestPacketConnDelegatesAddrMethods(t *testing.T) {
client, server := startUDPPair(t) client, server := startUDPPair(t)
prefix := filepath.Join(t.TempDir(), "capture") prefix := filepath.Join(t.TempDir(), "capture")
c := &packetConn{Prefix: prefix, PacketConn: client} c := &PacketConn{Prefix: prefix, Wrapped: client}
require.NoError(t, c.Initialize()) require.NoError(t, c.Initialize())
require.Equal(t, client.LocalAddr(), c.LocalAddr()) require.Equal(t, client.LocalAddr(), c.LocalAddr())
@@ -173,7 +173,7 @@ func TestPacketConnReadFromRecordsSource(t *testing.T) {
client, server := startUDPPair(t) client, server := startUDPPair(t)
prefix := filepath.Join(t.TempDir(), "capture") prefix := filepath.Join(t.TempDir(), "capture")
c := &packetConn{Prefix: prefix, PacketConn: client} c := &PacketConn{Prefix: prefix, Wrapped: client}
require.NoError(t, c.Initialize()) require.NoError(t, c.Initialize())
_, err := server.WriteTo([]byte("ping"), client.LocalAddr()) _, err := server.WriteTo([]byte("ping"), client.LocalAddr())
-24
View File
@@ -1,24 +0,0 @@
package packetdumper
import (
"crypto/tls"
"net"
)
// TLSListen provides a tls.Listen that also dumps TLS master secrets to disk.
type TLSListen struct {
Listen func(network string, address string) (net.Listener, error)
}
// Do mimics tls.Listen.
func (l *TLSListen) Do(network string, laddr string, tlsConfig *tls.Config) (net.Listener, error) {
netListener, err := l.Listen(network, laddr)
if err != nil {
return nil, err
}
return &tlsListener{
Listener: netListener,
TLSConfig: tlsConfig,
}, nil
}
+15 -9
View File
@@ -20,28 +20,34 @@ func (w *connKeyLogWriter) Write(p []byte) (int, error) {
return len(p), nil return len(p), nil
} }
type tlsListener struct { var _ net.Listener = (*TLSListener)(nil)
Listener net.Listener
// TLSListener is a wrapper around a net.Listener that dumps TLS master secrets to disk.
type TLSListener struct {
Wrapped net.Listener
TLSConfig *tls.Config TLSConfig *tls.Config
} }
func (l *tlsListener) Close() error { // Close implements net.Listener.
return l.Listener.Close() func (l *TLSListener) Close() error {
return l.Wrapped.Close()
} }
func (l *tlsListener) Addr() net.Addr { // Addr implements net.Listener.
return l.Listener.Addr() func (l *TLSListener) Addr() net.Addr {
return l.Wrapped.Addr()
} }
func (l *tlsListener) Accept() (net.Conn, error) { // Accept implements net.Listener.
netConn, err := l.Listener.Accept() func (l *TLSListener) Accept() (net.Conn, error) {
netConn, err := l.Wrapped.Accept()
if err != nil { if err != nil {
return nil, err return nil, err
} }
tlsConfig := l.TLSConfig.Clone() tlsConfig := l.TLSConfig.Clone()
pdConn := netConn.(*conn) pdConn := netConn.(*conn)
pdConn.expectingSecrets = 4 pdConn.expectingSecrets.Store(4)
tlsConfig.KeyLogWriter = &connKeyLogWriter{c: pdConn} tlsConfig.KeyLogWriter = &connKeyLogWriter{c: pdConn}
return tls.Server(netConn, tlsConfig), nil return tls.Server(netConn, tlsConfig), nil
@@ -0,0 +1,64 @@
package packetdumper
import (
"crypto/tls"
"net"
"path/filepath"
"testing"
"github.com/bluenviron/mediamtx/internal/test"
"github.com/stretchr/testify/require"
)
func TestTLSListener(t *testing.T) {
cert, err := tls.X509KeyPair(test.TLSCertPub, test.TLSCertKey)
require.NoError(t, err)
serverTLSConfig := &tls.Config{Certificates: []tls.Certificate{cert}}
innerLn, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
prefix := filepath.Join(t.TempDir(), "capture")
pdLn := &Listener{
Wrapped: innerLn,
Prefix: prefix,
}
tlsLn := &TLSListener{
Wrapped: pdLn,
TLSConfig: serverTLSConfig,
}
require.Equal(t, innerLn.Addr(), tlsLn.Addr())
clientDone := make(chan error, 1)
go func() {
clientConn, err2 := tls.Dial("tcp", tlsLn.Addr().String(), &tls.Config{InsecureSkipVerify: true}) //nolint:gosec
if err2 != nil {
clientDone <- err2
return
}
defer clientConn.Close() //nolint:errcheck
_, err2 = clientConn.Write([]byte("ping"))
clientDone <- err2
}()
serverConn, err := tlsLn.Accept()
require.NoError(t, err)
defer serverConn.Close()
buf := make([]byte, 4)
_, err = serverConn.Read(buf)
require.NoError(t, err)
require.Equal(t, []byte("ping"), buf)
require.NoError(t, <-clientDone)
serverConn.Close()
tlsLn.Close() //nolint:errcheck
checkPcapngPresence(t, prefix)
}
+30 -12
View File
@@ -130,20 +130,38 @@ func (s *Server) Initialize() error {
} }
} }
var listen func(network string, address string) (net.Listener, error) listen := func(network string, address string) (net.Listener, error) {
var tlsListen func(network string, laddr string, config *tls.Config) (net.Listener, error) ln, err := net.Listen(network, address)
if err != nil {
return nil, err
}
if s.DumpPackets { if s.DumpPackets {
listen = (&packetdumper.Listen{ ln = &packetdumper.Listener{
Prefix: s.DumpPacketsPrefix, Wrapped: ln,
}).Do Prefix: s.DumpPacketsPrefix,
}
}
tlsListen = (&packetdumper.TLSListen{ return ln, nil
Listen: listen, }
}).Do
} else { tlsListen := func(network string, laddr string, config *tls.Config) (net.Listener, error) {
listen = net.Listen ln, err := listen(network, laddr)
tlsListen = tls.Listen if err != nil {
return nil, err
}
if s.DumpPackets {
ln = &packetdumper.TLSListener{
Wrapped: ln,
TLSConfig: config,
}
} else {
ln = tls.NewListener(ln, config)
}
return ln, nil
} }
if tlsConfig != nil { if tlsConfig != nil {
+49
View File
@@ -0,0 +1,49 @@
// Package proxy provides PROXY protocol support for net.Listener.
package proxy
import (
"net"
"github.com/pires/go-proxyproto"
"github.com/bluenviron/mediamtx/internal/conf"
)
var _ net.Listener = (*Listener)(nil)
// Listener is a net.Listener that supports PROXY protocol.
type Listener struct {
Wrapped net.Listener
TrustedProxies conf.IPNetworks
inner *proxyproto.Listener
}
// Initialize initializes the listener.
func (l *Listener) Initialize() {
l.inner = &proxyproto.Listener{
Listener: l.Wrapped,
Policy: func(upstream net.Addr) (proxyproto.Policy, error) {
tcpAddr, ok := upstream.(*net.TCPAddr)
if ok && l.TrustedProxies.Contains(tcpAddr.IP) {
return proxyproto.USE, nil
}
return proxyproto.IGNORE, nil
},
}
}
// Close implements net.Listener.
func (l *Listener) Close() error {
return l.Wrapped.Close()
}
// Accept implements net.Listener.
func (l *Listener) Accept() (net.Conn, error) {
return l.inner.Accept()
}
// Addr implements net.Listener.
func (l *Listener) Addr() net.Addr {
return l.Wrapped.Addr()
}
+135
View File
@@ -0,0 +1,135 @@
package proxy
import (
"fmt"
"net"
"testing"
"github.com/pires/go-proxyproto"
"github.com/stretchr/testify/require"
"github.com/bluenviron/mediamtx/internal/conf"
)
func trustedProxies(cidrs ...string) conf.IPNetworks {
networks := make(conf.IPNetworks, 0, len(cidrs))
for _, cidr := range cidrs {
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
panic(err)
}
networks = append(networks, conf.IPNetwork(*ipnet))
}
return networks
}
func TestListenerTrustedWithHeader(t *testing.T) {
for _, version := range []byte{1, 2} {
t.Run(fmt.Sprintf("v%d", version), func(t *testing.T) {
ln, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
defer ln.Close()
wrapped := &Listener{Wrapped: ln, TrustedProxies: trustedProxies("127.0.0.1/32")}
wrapped.Initialize()
done := make(chan struct{})
go func() {
defer close(done)
conn, err2 := wrapped.Accept()
require.NoError(t, err2)
defer conn.Close()
require.Equal(t, "192.168.1.100:1234", conn.RemoteAddr().String())
}()
clientConn, err := net.Dial("tcp", ln.Addr().String())
require.NoError(t, err)
defer clientConn.Close()
header := &proxyproto.Header{
Version: version,
Command: proxyproto.PROXY,
TransportProtocol: proxyproto.TCPv4,
SourceAddr: &net.TCPAddr{IP: net.ParseIP("192.168.1.100"), Port: 1234},
DestinationAddr: &net.TCPAddr{IP: net.ParseIP("10.0.0.1"), Port: 1935},
}
_, err = header.WriteTo(clientConn)
require.NoError(t, err)
<-done
})
}
}
func TestListenerTrustedWithoutHeader(t *testing.T) {
ln, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
defer ln.Close()
wrapped := &Listener{Wrapped: ln, TrustedProxies: trustedProxies("127.0.0.1/32")}
wrapped.Initialize()
done := make(chan struct{})
go func() {
defer close(done)
conn, err2 := wrapped.Accept()
require.NoError(t, err2)
defer conn.Close()
buf := make([]byte, 7)
_, err2 = conn.Read(buf)
require.NoError(t, err2)
require.Equal(t, []byte("testing"), buf)
}()
clientConn, err := net.Dial("tcp", ln.Addr().String())
require.NoError(t, err)
defer clientConn.Close()
_, err = clientConn.Write([]byte("testing"))
require.NoError(t, err)
<-done
}
func TestListenerUntrustedIgnoresHeader(t *testing.T) {
ln, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
defer ln.Close()
wrapped := &Listener{Wrapped: ln, TrustedProxies: trustedProxies("10.0.0.0/8")}
wrapped.Initialize()
done := make(chan struct{})
go func() {
defer close(done)
conn, err2 := wrapped.Accept()
require.NoError(t, err2)
defer conn.Close()
require.Equal(t, "127.0.0.1", conn.RemoteAddr().(*net.TCPAddr).IP.String())
}()
clientConn, err := net.Dial("tcp", ln.Addr().String())
require.NoError(t, err)
defer clientConn.Close()
header := &proxyproto.Header{
Version: 1,
Command: proxyproto.PROXY,
TransportProtocol: proxyproto.TCPv4,
SourceAddr: &net.TCPAddr{IP: net.ParseIP("192.168.1.100"), Port: 1234},
DestinationAddr: &net.TCPAddr{IP: net.ParseIP("10.0.0.1"), Port: 1935},
}
_, err = header.WriteTo(clientConn)
require.NoError(t, err)
<-done
}
+47 -18
View File
@@ -19,6 +19,7 @@ import (
"github.com/bluenviron/mediamtx/internal/externalcmd" "github.com/bluenviron/mediamtx/internal/externalcmd"
"github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/packetdumper" "github.com/bluenviron/mediamtx/internal/packetdumper"
"github.com/bluenviron/mediamtx/internal/protocols/proxy"
"github.com/bluenviron/mediamtx/internal/restrictnetwork" "github.com/bluenviron/mediamtx/internal/restrictnetwork"
) )
@@ -81,6 +82,7 @@ type Server struct {
ServerCert string ServerCert string
ServerKey string ServerKey string
RTSPAddress string RTSPAddress string
TrustedProxies conf.IPNetworks
RunOnConnect string RunOnConnect string
RunOnConnectRestart bool RunOnConnectRestart bool
RunOnDisconnect string RunOnDisconnect string
@@ -107,27 +109,54 @@ type Server struct {
// Initialize initializes the server. // Initialize initializes the server.
func (s *Server) Initialize() error { func (s *Server) Initialize() error {
var listen func(network string, address string) (net.Listener, error) listen := func(network, address string) (net.Listener, error) {
var tlsListen func(network string, laddr string, config *tls.Config) (net.Listener, error) ln, err := net.Listen(network, address)
if err != nil {
if s.DumpPackets { return nil, err
var proto string
if s.Encryption {
proto = "rtmps"
} else {
proto = "rtmp"
} }
listen = (&packetdumper.Listen{ if s.DumpPackets {
Prefix: proto + "_server_conn", var proto string
}).Do if s.Encryption {
proto = "rtmps"
} else {
proto = "rtmp"
}
tlsListen = (&packetdumper.TLSListen{ ln = &packetdumper.Listener{
Listen: listen, Wrapped: ln,
}).Do Prefix: proto + "_server_conn",
} else { }
listen = net.Listen }
tlsListen = tls.Listen
if len(s.TrustedProxies) > 0 {
pl := &proxy.Listener{
Wrapped: ln,
TrustedProxies: s.TrustedProxies,
}
pl.Initialize()
ln = pl
}
return ln, nil
}
tlsListen := func(network string, laddr string, config *tls.Config) (net.Listener, error) {
ln, err := listen(network, laddr)
if err != nil {
return nil, err
}
if s.DumpPackets {
ln = &packetdumper.TLSListener{
Wrapped: ln,
TLSConfig: config,
}
} else {
ln = tls.NewListener(ln, config)
}
return ln, nil
} }
if s.Encryption { if s.Encryption {
+185 -149
View File
@@ -3,6 +3,7 @@ package rtmp
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"net"
"net/url" "net/url"
"testing" "testing"
"time" "time"
@@ -10,6 +11,8 @@ import (
"github.com/bluenviron/gortmplib" "github.com/bluenviron/gortmplib"
"github.com/bluenviron/gortmplib/pkg/codecs" "github.com/bluenviron/gortmplib/pkg/codecs"
"github.com/bluenviron/gortsplib/v5/pkg/description" "github.com/bluenviron/gortsplib/v5/pkg/description"
"github.com/pires/go-proxyproto"
"github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/conf"
"github.com/bluenviron/mediamtx/internal/defs" "github.com/bluenviron/mediamtx/internal/defs"
"github.com/bluenviron/mediamtx/internal/externalcmd" "github.com/bluenviron/mediamtx/internal/externalcmd"
@@ -44,168 +47,201 @@ func TestServerPublish(t *testing.T) {
"plain", "plain",
"tls", "tls",
} { } {
t.Run(encrypt, func(t *testing.T) { for _, proxy := range []string{
var serverCertFpath string "no_proxy",
var serverKeyFpath string "proxy",
} {
t.Run(encrypt+"_"+proxy, func(t *testing.T) {
var serverCertFpath string
var serverKeyFpath string
if encrypt == "tls" { if encrypt == "tls" {
serverCertFpath = test.CreateTempFile(t, test.TLSCertPub) serverCertFpath = test.CreateTempFile(t, test.TLSCertPub)
serverKeyFpath = test.CreateTempFile(t, test.TLSCertKey) serverKeyFpath = test.CreateTempFile(t, test.TLSCertKey)
} }
var strm *stream.Stream _, ipnet, err := net.ParseCIDR("127.0.0.1/32")
var reader *stream.Reader require.NoError(t, err)
defer func() { trustedProxies := conf.IPNetworks{conf.IPNetwork(*ipnet)}
strm.RemoveReader(reader)
}()
dataReceived := make(chan struct{})
n := 0
pathManager := &test.PathManager{ var strm *stream.Stream
AddPublisherImpl: func(req defs.PathAddPublisherReq) (*defs.PathAddPublisherRes, error) { var reader *stream.Reader
require.Equal(t, "teststream", req.AccessRequest.Name) defer func() {
require.Equal(t, "user=myuser&pass=mypass&param=value", req.AccessRequest.Query) strm.RemoveReader(reader)
require.Equal(t, "myuser", req.AccessRequest.Credentials.User) }()
require.Equal(t, "mypass", req.AccessRequest.Credentials.Pass) dataReceived := make(chan struct{})
n := 0
strm = &stream.Stream{ pathManager := &test.PathManager{
Desc: req.Desc, AddPublisherImpl: func(req defs.PathAddPublisherReq) (*defs.PathAddPublisherRes, error) {
WriteQueueSize: 512, require.Equal(t, "teststream", req.AccessRequest.Name)
RTPMaxPayloadSize: 1450, require.Equal(t, "user=myuser&pass=mypass&param=value", req.AccessRequest.Query)
Parent: test.NilLogger, require.Equal(t, "myuser", req.AccessRequest.Credentials.User)
} require.Equal(t, "mypass", req.AccessRequest.Credentials.Pass)
err := strm.Initialize()
require.NoError(t, err)
subStream := &stream.SubStream{ strm = &stream.Stream{
Stream: strm, Desc: req.Desc,
UseRTPPackets: false, WriteQueueSize: 512,
} RTPMaxPayloadSize: 1450,
err = subStream.Initialize() Parent: test.NilLogger,
require.NoError(t, err) }
err2 := strm.Initialize()
require.NoError(t, err2)
reader = &stream.Reader{Parent: test.NilLogger} subStream := &stream.SubStream{
Stream: strm,
UseRTPPackets: false,
}
err2 = subStream.Initialize()
require.NoError(t, err2)
reader.OnData( reader = &stream.Reader{Parent: test.NilLogger}
strm.Desc.Medias[0],
strm.Desc.Medias[0].Formats[0],
func(u *unit.Unit) error {
switch n {
case 0:
require.Equal(t, unit.PayloadH264(nil), u.Payload)
case 1: reader.OnData(
require.Equal(t, unit.PayloadH264{ strm.Desc.Medias[0],
test.FormatH264.SPS, strm.Desc.Medias[0].Formats[0],
test.FormatH264.PPS, func(u *unit.Unit) error {
{5, 2, 3, 4}, switch n {
}, u.Payload) case 0:
close(dataReceived) require.Equal(t, unit.PayloadH264(nil), u.Payload)
default: case 1:
t.Errorf("should not happen") require.Equal(t, unit.PayloadH264{
} test.FormatH264.SPS,
n++ test.FormatH264.PPS,
return nil {5, 2, 3, 4},
}) }, u.Payload)
close(dataReceived)
strm.AddReader(reader) default:
t.Errorf("should not happen")
}
n++
return nil
})
return &defs.PathAddPublisherRes{ strm.AddReader(reader)
Path: &dummyPath{},
User: req.AccessRequest.Credentials.User,
SubStream: subStream,
}, nil
},
}
s := &Server{ return &defs.PathAddPublisherRes{
Address: "127.0.0.1:1939", Path: &dummyPath{},
ReadTimeout: conf.Duration(10 * time.Second), User: req.AccessRequest.Credentials.User,
WriteTimeout: conf.Duration(10 * time.Second), SubStream: subStream,
Encryption: encrypt == "tls", }, nil
ServerCert: serverCertFpath,
ServerKey: serverKeyFpath,
RTSPAddress: "",
RunOnConnect: "",
RunOnConnectRestart: false,
RunOnDisconnect: "",
ExternalCmdPool: nil,
PathManager: pathManager,
Parent: test.NilLogger,
}
err := s.Initialize()
require.NoError(t, err)
defer s.Close()
var rawURL string
if encrypt == "tls" {
rawURL += "rtmps://"
} else {
rawURL += "rtmp://"
}
rawURL += "127.0.0.1:1939/teststream?user=myuser&pass=mypass&param=value"
u, err := url.Parse(rawURL)
require.NoError(t, err)
conn := &gortmplib.Client{
URL: u,
TLSConfig: &tls.Config{InsecureSkipVerify: true},
Publish: true,
}
err = conn.Initialize(context.Background())
require.NoError(t, err)
defer conn.Close()
w := &gortmplib.Writer{
Conn: conn,
Tracks: []*gortmplib.Track{
{Codec: &codecs.H264{
SPS: test.FormatH264.SPS,
PPS: test.FormatH264.PPS,
}},
{Codec: &codecs.MPEG4Audio{
Config: test.FormatMPEG4Audio.Config,
}},
},
}
err = w.Initialize()
require.NoError(t, err)
err = w.WriteH264(
w.Tracks[0],
2*time.Second, 2*time.Second, [][]byte{
{5, 2, 3, 4},
})
require.NoError(t, err)
<-dataReceived
list, err := s.APIConnsList()
require.NoError(t, err)
require.Equal(t, &defs.APIRTMPConnList{
Items: []defs.APIRTMPConn{
{
ID: list.Items[0].ID,
Created: list.Items[0].Created,
RemoteAddr: list.Items[0].RemoteAddr,
State: "publish",
Path: "teststream",
Query: "user=myuser&pass=mypass&param=value",
User: "myuser",
UserAgent: list.Items[0].UserAgent,
InboundBytes: list.Items[0].InboundBytes,
OutboundBytes: list.Items[0].OutboundBytes,
OutboundFramesDiscarded: list.Items[0].OutboundFramesDiscarded,
BytesReceived: list.Items[0].BytesReceived,
BytesSent: list.Items[0].BytesSent,
}, },
}, }
}, list)
}) s := &Server{
Address: "127.0.0.1:1939",
ReadTimeout: conf.Duration(10 * time.Second),
WriteTimeout: conf.Duration(10 * time.Second),
Encryption: encrypt == "tls",
ServerCert: serverCertFpath,
ServerKey: serverKeyFpath,
RTSPAddress: "",
TrustedProxies: trustedProxies,
RunOnConnect: "",
RunOnConnectRestart: false,
RunOnDisconnect: "",
ExternalCmdPool: nil,
PathManager: pathManager,
Parent: test.NilLogger,
}
err = s.Initialize()
require.NoError(t, err)
defer s.Close()
var rawURL string
if encrypt == "tls" {
rawURL += "rtmps://"
} else {
rawURL += "rtmp://"
}
rawURL += "127.0.0.1:1939/teststream?user=myuser&pass=mypass&param=value"
u, err := url.Parse(rawURL)
require.NoError(t, err)
var dialContext func(ctx context.Context, network, address string) (net.Conn, error)
if proxy == "proxy" {
dialContext = func(ctx context.Context, network, address string) (net.Conn, error) {
c, err2 := (&net.Dialer{}).DialContext(ctx, network, address)
if err2 != nil {
return nil, err2
}
header := &proxyproto.Header{
Version: 1,
Command: proxyproto.PROXY,
TransportProtocol: proxyproto.TCPv4,
SourceAddr: &net.TCPAddr{IP: net.ParseIP("192.168.1.100"), Port: 1234},
DestinationAddr: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 1939},
}
_, err2 = header.WriteTo(c)
if err2 != nil {
return nil, err2
}
return c, nil
}
}
conn := &gortmplib.Client{
URL: u,
TLSConfig: &tls.Config{InsecureSkipVerify: true},
Publish: true,
DialContext: dialContext,
}
err = conn.Initialize(context.Background())
require.NoError(t, err)
defer conn.Close()
w := &gortmplib.Writer{
Conn: conn,
Tracks: []*gortmplib.Track{
{Codec: &codecs.H264{
SPS: test.FormatH264.SPS,
PPS: test.FormatH264.PPS,
}},
{Codec: &codecs.MPEG4Audio{
Config: test.FormatMPEG4Audio.Config,
}},
},
}
err = w.Initialize()
require.NoError(t, err)
err = w.WriteH264(
w.Tracks[0],
2*time.Second, 2*time.Second, [][]byte{
{5, 2, 3, 4},
})
require.NoError(t, err)
<-dataReceived
list, err := s.APIConnsList()
require.NoError(t, err)
require.Equal(t, &defs.APIRTMPConnList{
Items: []defs.APIRTMPConn{
{
ID: list.Items[0].ID,
Created: list.Items[0].Created,
RemoteAddr: list.Items[0].RemoteAddr,
State: "publish",
Path: "teststream",
Query: "user=myuser&pass=mypass&param=value",
User: "myuser",
UserAgent: list.Items[0].UserAgent,
InboundBytes: list.Items[0].InboundBytes,
OutboundBytes: list.Items[0].OutboundBytes,
OutboundFramesDiscarded: list.Items[0].OutboundFramesDiscarded,
BytesReceived: list.Items[0].BytesReceived,
BytesSent: list.Items[0].BytesSent,
},
},
}, list)
})
}
} }
} }
+77 -15
View File
@@ -6,6 +6,7 @@ import (
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
"net"
"reflect" "reflect"
"sort" "sort"
"strings" "strings"
@@ -24,6 +25,7 @@ import (
"github.com/bluenviron/mediamtx/internal/externalcmd" "github.com/bluenviron/mediamtx/internal/externalcmd"
"github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/packetdumper" "github.com/bluenviron/mediamtx/internal/packetdumper"
"github.com/bluenviron/mediamtx/internal/protocols/proxy"
) )
// ErrConnNotFound is returned when a connection is not found. // ErrConnNotFound is returned when a connection is not found.
@@ -105,6 +107,7 @@ type Server struct {
ServerCert string ServerCert string
ServerKey string ServerKey string
RTSPAddress string RTSPAddress string
TrustedProxies conf.IPNetworks
Transports conf.RTSPTransports Transports conf.RTSPTransports
RunOnConnect string RunOnConnect string
RunOnConnectRestart bool RunOnConnectRestart bool
@@ -166,25 +169,84 @@ func (s *Server) Initialize() error {
s.srv.TLSConfig = &tls.Config{GetCertificate: s.loader.GetCertificate()} s.srv.TLSConfig = &tls.Config{GetCertificate: s.loader.GetCertificate()}
} }
if s.DumpPackets { s.srv.Listen = func(network, address string) (net.Listener, error) {
var proto string ln, err := net.Listen(network, address)
if s.Encryption { if err != nil {
proto = "rtsps" return nil, err
} else {
proto = "rtsp"
} }
s.srv.Listen = (&packetdumper.Listen{ if s.DumpPackets {
Prefix: proto + "_server_conn", var proto string
}).Do if s.Encryption {
proto = "rtsps"
} else {
proto = "rtsp"
}
s.srv.ListenPacket = (&packetdumper.ListenPacket{ ln = &packetdumper.Listener{
Prefix: proto + "_server_packet_conn", Wrapped: ln,
}).Do Prefix: proto + "_server_conn",
}
}
s.srv.TLSListen = (&packetdumper.TLSListen{ if len(s.TrustedProxies) > 0 {
Listen: s.srv.Listen, pl := &proxy.Listener{
}).Do Wrapped: ln,
TrustedProxies: s.TrustedProxies,
}
pl.Initialize()
ln = pl
}
return ln, nil
}
s.srv.TLSListen = func(network, laddr string, config *tls.Config) (net.Listener, error) {
ln, err := s.srv.Listen(network, laddr)
if err != nil {
return nil, err
}
if s.DumpPackets {
ln = &packetdumper.TLSListener{
Wrapped: ln,
TLSConfig: config,
}
} else {
ln = tls.NewListener(ln, config)
}
return ln, nil
}
s.srv.ListenPacket = func(network, address string) (net.PacketConn, error) {
pc, err := net.ListenPacket(network, address)
if err != nil {
return nil, err
}
if s.DumpPackets {
var proto string
if s.Encryption {
proto = "rtsps"
} else {
proto = "rtsp"
}
pc2 := &packetdumper.PacketConn{
Wrapped: pc,
Prefix: proto + "_server_packet_conn",
}
err = pc2.Initialize()
if err != nil {
pc.Close() //nolint:errcheck
return nil, err
}
pc = pc2
}
return pc, nil
} }
err := s.srv.Start() err := s.srv.Start()
+201 -139
View File
@@ -3,7 +3,10 @@ package rtsp
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"context"
"crypto/tls"
"fmt" "fmt"
"net"
"sync/atomic" "sync/atomic"
"testing" "testing"
"time" "time"
@@ -15,6 +18,8 @@ import (
"github.com/bluenviron/gortsplib/v5/pkg/format" "github.com/bluenviron/gortsplib/v5/pkg/format"
mpegts "github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts" mpegts "github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts"
tscodecs "github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts/codecs" tscodecs "github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts/codecs"
"github.com/pires/go-proxyproto"
"github.com/bluenviron/mediamtx/internal/auth" "github.com/bluenviron/mediamtx/internal/auth"
"github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/conf"
"github.com/bluenviron/mediamtx/internal/defs" "github.com/bluenviron/mediamtx/internal/defs"
@@ -49,159 +54,216 @@ func (p *dummyPath) RemoveReader(_ defs.PathRemoveReaderReq) {
func TestServerPublish(t *testing.T) { func TestServerPublish(t *testing.T) {
for _, ca := range []string{"basic", "digest", "basic+digest"} { for _, ca := range []string{"basic", "digest", "basic+digest"} {
t.Run(ca, func(t *testing.T) { for _, encrypt := range []string{"plain", "tls"} {
var strm *stream.Stream for _, proxy := range []string{"no_proxy", "proxy"} {
var reader *stream.Reader t.Run(ca+"_"+encrypt+"_"+proxy, func(t *testing.T) {
defer func() { var serverCertFpath string
strm.RemoveReader(reader) var serverKeyFpath string
}()
dataReceived := make(chan struct{})
n := 0 if encrypt == "tls" {
serverCertFpath = test.CreateTempFile(t, test.TLSCertPub)
serverKeyFpath = test.CreateTempFile(t, test.TLSCertKey)
}
pathManager := &test.PathManager{ _, ipnet, err := net.ParseCIDR("127.0.0.1/32")
FindPathConfImpl: func(req defs.PathFindPathConfReq) (*defs.PathFindPathConfRes, error) { require.NoError(t, err)
require.Equal(t, "teststream", req.AccessRequest.Name) trustedProxies := conf.IPNetworks{conf.IPNetwork(*ipnet)}
require.Equal(t, "param=value", req.AccessRequest.Query)
if ca == "basic" { var strm *stream.Stream
require.Nil(t, req.AccessRequest.CustomVerifyFunc) var reader *stream.Reader
defer func() {
strm.RemoveReader(reader)
}()
dataReceived := make(chan struct{})
if req.AccessRequest.Credentials.User == "" && req.AccessRequest.Credentials.Pass == "" { n := 0
return nil, &auth.Error{AskCredentials: true, Wrapped: fmt.Errorf("auth error")}
}
require.Equal(t, "myuser", req.AccessRequest.Credentials.User) pathManager := &test.PathManager{
require.Equal(t, "mypass", req.AccessRequest.Credentials.Pass) FindPathConfImpl: func(req defs.PathFindPathConfReq) (*defs.PathFindPathConfRes, error) {
require.Equal(t, "teststream", req.AccessRequest.Name)
require.Equal(t, "param=value", req.AccessRequest.Query)
if ca == "basic" {
require.Nil(t, req.AccessRequest.CustomVerifyFunc)
if req.AccessRequest.Credentials.User == "" && req.AccessRequest.Credentials.Pass == "" {
return nil, &auth.Error{AskCredentials: true, Wrapped: fmt.Errorf("auth error")}
}
require.Equal(t, "myuser", req.AccessRequest.Credentials.User)
require.Equal(t, "mypass", req.AccessRequest.Credentials.Pass)
} else {
ok := req.AccessRequest.CustomVerifyFunc("myuser", "mypass")
if n == 0 {
require.False(t, ok)
n++
return nil, &auth.Error{AskCredentials: true, Wrapped: fmt.Errorf("auth error")}
}
require.True(t, ok)
}
return &defs.PathFindPathConfRes{Conf: &conf.Path{}, User: req.AccessRequest.Credentials.User}, nil
},
AddPublisherImpl: func(req defs.PathAddPublisherReq) (*defs.PathAddPublisherRes, error) {
require.Equal(t, "teststream", req.AccessRequest.Name)
require.Equal(t, "param=value", req.AccessRequest.Query)
require.True(t, req.AccessRequest.SkipAuth)
strm = &stream.Stream{
Desc: req.Desc,
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Parent: test.NilLogger,
}
err2 := strm.Initialize()
require.NoError(t, err2)
subStream := &stream.SubStream{
Stream: strm,
UseRTPPackets: true,
}
err2 = subStream.Initialize()
require.NoError(t, err2)
reader = &stream.Reader{Parent: test.NilLogger}
reader.OnData(
strm.Desc.Medias[0],
strm.Desc.Medias[0].Formats[0],
func(u *unit.Unit) error {
require.Equal(t, unit.PayloadH264{
test.FormatH264.SPS,
test.FormatH264.PPS,
{5, 2, 3, 4},
}, u.Payload)
close(dataReceived)
return nil
})
strm.AddReader(reader)
return &defs.PathAddPublisherRes{Path: &dummyPath{}, SubStream: subStream}, nil
},
}
var authMethods []rtspauth.VerifyMethod
switch ca {
case "basic":
authMethods = []rtspauth.VerifyMethod{rtspauth.VerifyMethodBasic}
case "digest":
authMethods = []rtspauth.VerifyMethod{rtspauth.VerifyMethodDigestMD5}
default:
authMethods = []rtspauth.VerifyMethod{rtspauth.VerifyMethodBasic, rtspauth.VerifyMethodDigestMD5}
}
s := &Server{
Address: "127.0.0.1:8557",
AuthMethods: authMethods,
ReadTimeout: conf.Duration(10 * time.Second),
WriteTimeout: conf.Duration(10 * time.Second),
WriteQueueSize: 512,
Transports: conf.RTSPTransports{gortsplib.ProtocolTCP: {}},
Encryption: encrypt == "tls",
ServerCert: serverCertFpath,
ServerKey: serverKeyFpath,
TrustedProxies: trustedProxies,
PathManager: pathManager,
Parent: test.NilLogger,
}
err = s.Initialize()
require.NoError(t, err)
defer s.Close()
var scheme string
if encrypt == "tls" {
scheme = "rtsps"
} else { } else {
ok := req.AccessRequest.CustomVerifyFunc("myuser", "mypass") scheme = "rtsp"
if n == 0 { }
require.False(t, ok)
n++ var dialContext func(ctx context.Context, network, address string) (net.Conn, error)
return nil, &auth.Error{AskCredentials: true, Wrapped: fmt.Errorf("auth error")} if proxy == "proxy" {
dialContext = func(ctx context.Context, network, address string) (net.Conn, error) {
c, err2 := (&net.Dialer{}).DialContext(ctx, network, address)
if err2 != nil {
return nil, err2
}
header := &proxyproto.Header{
Version: 1,
Command: proxyproto.PROXY,
TransportProtocol: proxyproto.TCPv4,
SourceAddr: &net.TCPAddr{IP: net.ParseIP("192.168.1.100"), Port: 1234},
DestinationAddr: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8557},
}
_, err2 = header.WriteTo(c)
if err2 != nil {
return nil, err2
}
return c, nil
} }
require.True(t, ok)
} }
return &defs.PathFindPathConfRes{Conf: &conf.Path{}, User: req.AccessRequest.Credentials.User}, nil source := gortsplib.Client{
}, TLSConfig: &tls.Config{InsecureSkipVerify: true},
AddPublisherImpl: func(req defs.PathAddPublisherReq) (*defs.PathAddPublisherRes, error) { DialContext: dialContext,
require.Equal(t, "teststream", req.AccessRequest.Name)
require.Equal(t, "param=value", req.AccessRequest.Query)
require.True(t, req.AccessRequest.SkipAuth)
strm = &stream.Stream{
Desc: req.Desc,
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Parent: test.NilLogger,
} }
err := strm.Initialize()
media0 := test.UniqueMediaH264()
err = source.StartRecording(
scheme+"://myuser:mypass@127.0.0.1:8557/teststream?param=value",
&description.Session{Medias: []*description.Media{media0}})
require.NoError(t, err)
defer source.Close()
err = source.WritePacketRTP(media0, &rtp.Packet{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 96,
SequenceNumber: 123,
Timestamp: 45343,
SSRC: 563423,
},
Payload: []byte{5, 2, 3, 4},
})
require.NoError(t, err) require.NoError(t, err)
subStream := &stream.SubStream{ <-dataReceived
Stream: strm,
UseRTPPackets: true, list, err := s.APISessionsList()
}
err = subStream.Initialize()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, &defs.APIRTSPSessionList{
reader = &stream.Reader{Parent: test.NilLogger} Items: []defs.APIRTSPSession{
{
reader.OnData( ID: list.Items[0].ID,
strm.Desc.Medias[0], Created: list.Items[0].Created,
strm.Desc.Medias[0].Formats[0], RemoteAddr: list.Items[0].RemoteAddr,
func(u *unit.Unit) error { State: "publish",
require.Equal(t, unit.PayloadH264{ Path: "teststream",
test.FormatH264.SPS, Query: "param=value",
test.FormatH264.PPS, User: "myuser",
{5, 2, 3, 4}, UserAgent: list.Items[0].UserAgent,
}, u.Payload) InboundBytes: list.Items[0].InboundBytes,
close(dataReceived) InboundRTPPackets: list.Items[0].InboundRTPPackets,
return nil OutboundBytes: list.Items[0].OutboundBytes,
}) BytesReceived: list.Items[0].BytesReceived,
BytesSent: list.Items[0].BytesSent,
strm.AddReader(reader) Conns: list.Items[0].Conns,
RTPPacketsReceived: list.Items[0].RTPPacketsReceived,
return &defs.PathAddPublisherRes{Path: &dummyPath{}, SubStream: subStream}, nil Transport: new("TCP"),
}, Profile: func() *string {
if encrypt == "tls" {
return new("SAVP")
}
return new("AVP")
}(),
},
},
}, list)
})
} }
}
var authMethods []rtspauth.VerifyMethod
switch ca {
case "basic":
authMethods = []rtspauth.VerifyMethod{rtspauth.VerifyMethodBasic}
case "digest":
authMethods = []rtspauth.VerifyMethod{rtspauth.VerifyMethodDigestMD5}
default:
authMethods = []rtspauth.VerifyMethod{rtspauth.VerifyMethodBasic, rtspauth.VerifyMethodDigestMD5}
}
s := &Server{
Address: "127.0.0.1:8557",
AuthMethods: authMethods,
ReadTimeout: conf.Duration(10 * time.Second),
WriteTimeout: conf.Duration(10 * time.Second),
WriteQueueSize: 512,
Transports: conf.RTSPTransports{gortsplib.ProtocolTCP: {}},
PathManager: pathManager,
Parent: test.NilLogger,
}
err := s.Initialize()
require.NoError(t, err)
defer s.Close()
source := gortsplib.Client{}
media0 := test.UniqueMediaH264()
err = source.StartRecording(
"rtsp://myuser:mypass@127.0.0.1:8557/teststream?param=value",
&description.Session{Medias: []*description.Media{media0}})
require.NoError(t, err)
defer source.Close()
err = source.WritePacketRTP(media0, &rtp.Packet{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 96,
SequenceNumber: 123,
Timestamp: 45343,
SSRC: 563423,
},
Payload: []byte{5, 2, 3, 4},
})
require.NoError(t, err)
<-dataReceived
list, err := s.APISessionsList()
require.NoError(t, err)
require.Equal(t, &defs.APIRTSPSessionList{
Items: []defs.APIRTSPSession{
{
ID: list.Items[0].ID,
Created: list.Items[0].Created,
RemoteAddr: list.Items[0].RemoteAddr,
State: "publish",
Path: "teststream",
Query: "param=value",
User: "myuser",
UserAgent: list.Items[0].UserAgent,
InboundBytes: list.Items[0].InboundBytes,
InboundRTPPackets: list.Items[0].InboundRTPPackets,
OutboundBytes: list.Items[0].OutboundBytes,
BytesReceived: list.Items[0].BytesReceived,
BytesSent: list.Items[0].BytesSent,
Conns: list.Items[0].Conns,
RTPPacketsReceived: list.Items[0].RTPPacketsReceived,
Transport: new("TCP"),
Profile: new("AVP"),
},
},
}, list)
})
} }
} }
+33 -7
View File
@@ -58,9 +58,17 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error {
} }
if s.DumpPackets { if s.DumpPackets {
l.Listen = (&packetdumper.Listen{ l.Listen = func(network, address string) (net.Listener, error) {
Prefix: "mpegts_source_unix_conn", ln, err2 := net.Listen(network, address)
}).Do if err2 != nil {
return nil, err2
}
return &packetdumper.Listener{
Wrapped: ln,
Prefix: "mpegts_source_unix_conn",
}, nil
}
} }
err = l.Initialize() err = l.Initialize()
@@ -76,6 +84,7 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error {
} }
params := udp.URLToParams(u) params := udp.URLToParams(u)
l := &udp.Listener{ l := &udp.Listener{
Address: params.Address, Address: params.Address,
Source: params.Source, Source: params.Source,
@@ -83,10 +92,27 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error {
UDPReadBufferSize: int(udpReadBufferSize), UDPReadBufferSize: int(udpReadBufferSize),
} }
if s.DumpPackets { l.ListenPacket = func(network, address string) (net.PacketConn, error) {
l.ListenPacket = (&packetdumper.ListenPacket{ pc, err2 := net.ListenPacket(network, address)
Prefix: "mpegts_source_packet_conn", if err2 != nil {
}).Do return nil, err2
}
if s.DumpPackets {
pc2 := &packetdumper.PacketConn{
Wrapped: pc,
Prefix: "mpegts_source_packet_conn",
}
err2 = pc2.Initialize()
if err2 != nil {
pc.Close() //nolint:errcheck
return nil, err2
}
pc = pc2
}
return pc, nil
} }
err = l.Initialize() err = l.Initialize()
+21 -4
View File
@@ -92,10 +92,27 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error {
UDPReadBufferSize: int(udpReadBufferSize), UDPReadBufferSize: int(udpReadBufferSize),
} }
if s.DumpPackets { l.ListenPacket = func(network, address string) (net.PacketConn, error) {
l.ListenPacket = (&packetdumper.ListenPacket{ pc, err2 := net.ListenPacket(network, address)
Prefix: "rtp_source_packet_conn", if err2 != nil {
}).Do return nil, err2
}
if s.DumpPackets {
pc2 := &packetdumper.PacketConn{
Wrapped: pc,
Prefix: "rtp_source_packet_conn",
}
err2 = pc2.Initialize()
if err2 != nil {
pc.Close() //nolint:errcheck
return nil, err2
}
pc = pc2
}
return pc, nil
} }
err = l.Initialize() err = l.Initialize()
+24 -4
View File
@@ -3,6 +3,7 @@ package rtsp
import ( import (
"fmt" "fmt"
"net"
"net/url" "net/url"
"time" "time"
@@ -187,10 +188,6 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error {
Prefix: u.Scheme + "_source_conn", Prefix: u.Scheme + "_source_conn",
}).Do }).Do
c.ListenPacket = (&packetdumper.ListenPacket{
Prefix: u.Scheme + "_source_packet_conn",
}).Do
c.DialTLSContext = (&packetdumper.DialTLSContext{ c.DialTLSContext = (&packetdumper.DialTLSContext{
DialContext: c.DialContext, DialContext: c.DialContext,
TLSConfig: tlsConfig, TLSConfig: tlsConfig,
@@ -199,6 +196,29 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error {
c.TLSConfig = tlsConfig c.TLSConfig = tlsConfig
} }
c.ListenPacket = func(network, address string) (net.PacketConn, error) {
pc, err2 := net.ListenPacket(network, address)
if err2 != nil {
return nil, err2
}
if s.DumpPackets {
pc2 := &packetdumper.PacketConn{
Wrapped: pc,
Prefix: u.Scheme + "_source_packet_conn",
}
err2 = pc2.Initialize()
if err2 != nil {
pc.Close() //nolint:errcheck
return nil, err2
}
pc = pc2
}
return pc, nil
}
err = c.Start() err = c.Start()
if err != nil { if err != nil {
return err return err
+8
View File
@@ -281,6 +281,10 @@ rtspServerCert: server.crt
# Authentication methods. Available are "basic" and "digest". # Authentication methods. Available are "basic" and "digest".
# "digest" doesn't provide any additional security and is available for compatibility only. # "digest" doesn't provide any additional security and is available for compatibility only.
rtspAuthMethods: [basic] rtspAuthMethods: [basic]
# IPs or CIDRs of proxies placed before the RTSP server.
# If the server receives a request from one of these entries, IP in logs
# and authentication will be taken from the PROXY protocol header.
rtspTrustedProxies: []
############################################### ###############################################
# Global settings -> RTMP server # Global settings -> RTMP server
@@ -301,6 +305,10 @@ rtmpsAddress: :1936
rtmpServerKey: server.key rtmpServerKey: server.key
# Path to the server certificate. This is needed only when encryption is "strict" or "optional". # Path to the server certificate. This is needed only when encryption is "strict" or "optional".
rtmpServerCert: server.crt rtmpServerCert: server.crt
# IPs or CIDRs of proxies placed before the RTMP server.
# If the server receives a request from one of these entries, IP in logs
# and authentication will be taken from the PROXY protocol header.
rtmpTrustedProxies: []
############################################### ###############################################
# Global settings -> HLS server # Global settings -> HLS server