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:
@@ -449,6 +449,10 @@ components:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/RTSPAuthMethod"
|
||||
rtspTrustedProxies:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
rtspUDPReadBufferSize:
|
||||
type: integer
|
||||
format: uint64
|
||||
@@ -472,6 +476,10 @@ components:
|
||||
type: string
|
||||
rtmpServerCert:
|
||||
type: string
|
||||
rtmpTrustedProxies:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
|
||||
# HLS server
|
||||
hls:
|
||||
|
||||
@@ -37,6 +37,7 @@ require (
|
||||
github.com/pion/sdp/v3 v3.0.18
|
||||
github.com/pion/transport/v4 v4.0.2
|
||||
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/webtransport-go v0.10.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
|
||||
@@ -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/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/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/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
|
||||
@@ -335,6 +335,7 @@ type Conf struct {
|
||||
RTSPServerCert string `json:"rtspServerCert"`
|
||||
AuthMethods *RTSPAuthMethods `json:"authMethods,omitempty" deprecated:"true"`
|
||||
RTSPAuthMethods RTSPAuthMethods `json:"rtspAuthMethods"`
|
||||
RTSPTrustedProxies IPNetworks `json:"rtspTrustedProxies"`
|
||||
RTSPUDPReadBufferSize *uint `json:"rtspUDPReadBufferSize,omitempty" deprecated:"true"`
|
||||
|
||||
// RTMP server
|
||||
@@ -345,6 +346,7 @@ type Conf struct {
|
||||
RTMPSAddress string `json:"rtmpsAddress"`
|
||||
RTMPServerKey string `json:"rtmpServerKey"`
|
||||
RTMPServerCert string `json:"rtmpServerCert"`
|
||||
RTMPTrustedProxies IPNetworks `json:"rtmpTrustedProxies"`
|
||||
|
||||
// HLS server
|
||||
HLS bool `json:"hls"`
|
||||
|
||||
@@ -491,6 +491,7 @@ func (p *Core) createResources(initial bool) error {
|
||||
ServerCert: "",
|
||||
ServerKey: "",
|
||||
RTSPAddress: p.conf.RTSPAddress,
|
||||
TrustedProxies: p.conf.RTSPTrustedProxies,
|
||||
Transports: p.conf.RTSPTransports,
|
||||
RunOnConnect: p.conf.RunOnConnect,
|
||||
RunOnConnectRestart: p.conf.RunOnConnectRestart,
|
||||
@@ -534,6 +535,7 @@ func (p *Core) createResources(initial bool) error {
|
||||
ServerCert: p.conf.RTSPServerCert,
|
||||
ServerKey: p.conf.RTSPServerKey,
|
||||
RTSPAddress: p.conf.RTSPAddress,
|
||||
TrustedProxies: p.conf.RTSPTrustedProxies,
|
||||
Transports: p.conf.RTSPTransports,
|
||||
RunOnConnect: p.conf.RunOnConnect,
|
||||
RunOnConnectRestart: p.conf.RunOnConnectRestart,
|
||||
@@ -563,6 +565,7 @@ func (p *Core) createResources(initial bool) error {
|
||||
ServerCert: "",
|
||||
ServerKey: "",
|
||||
RTSPAddress: p.conf.RTSPAddress,
|
||||
TrustedProxies: p.conf.RTMPTrustedProxies,
|
||||
RunOnConnect: p.conf.RunOnConnect,
|
||||
RunOnConnectRestart: p.conf.RunOnConnectRestart,
|
||||
RunOnDisconnect: p.conf.RunOnDisconnect,
|
||||
@@ -591,6 +594,7 @@ func (p *Core) createResources(initial bool) error {
|
||||
ServerKey: p.conf.RTMPServerKey,
|
||||
DumpPackets: p.conf.DumpPackets,
|
||||
RTSPAddress: p.conf.RTSPAddress,
|
||||
TrustedProxies: p.conf.RTMPTrustedProxies,
|
||||
RunOnConnect: p.conf.RunOnConnect,
|
||||
RunOnConnectRestart: p.conf.RunOnConnectRestart,
|
||||
RunOnDisconnect: p.conf.RunOnDisconnect,
|
||||
@@ -876,6 +880,7 @@ func (p *Core) closeResources(newConf *conf.Conf, calledByAPI bool) {
|
||||
newConf.MulticastRTPPort != p.conf.MulticastRTPPort ||
|
||||
newConf.MulticastRTCPPort != p.conf.MulticastRTCPPort ||
|
||||
!reflect.DeepEqual(newConf.RTSPTransports, p.conf.RTSPTransports) ||
|
||||
!reflect.DeepEqual(newConf.RTSPTrustedProxies, p.conf.RTSPTrustedProxies) ||
|
||||
newConf.RunOnConnect != p.conf.RunOnConnect ||
|
||||
newConf.RunOnConnectRestart != p.conf.RunOnConnectRestart ||
|
||||
newConf.RunOnDisconnect != p.conf.RunOnDisconnect ||
|
||||
@@ -898,6 +903,7 @@ func (p *Core) closeResources(newConf *conf.Conf, calledByAPI bool) {
|
||||
newConf.RTSPServerKey != p.conf.RTSPServerKey ||
|
||||
newConf.RTSPAddress != p.conf.RTSPAddress ||
|
||||
!reflect.DeepEqual(newConf.RTSPTransports, p.conf.RTSPTransports) ||
|
||||
!reflect.DeepEqual(newConf.RTSPTrustedProxies, p.conf.RTSPTrustedProxies) ||
|
||||
newConf.RunOnConnect != p.conf.RunOnConnect ||
|
||||
newConf.RunOnConnectRestart != p.conf.RunOnConnectRestart ||
|
||||
newConf.RunOnDisconnect != p.conf.RunOnDisconnect ||
|
||||
@@ -913,6 +919,7 @@ func (p *Core) closeResources(newConf *conf.Conf, calledByAPI bool) {
|
||||
newConf.ReadTimeout != p.conf.ReadTimeout ||
|
||||
newConf.WriteTimeout != p.conf.WriteTimeout ||
|
||||
newConf.RTSPAddress != p.conf.RTSPAddress ||
|
||||
!reflect.DeepEqual(newConf.RTMPTrustedProxies, p.conf.RTMPTrustedProxies) ||
|
||||
newConf.RunOnConnect != p.conf.RunOnConnect ||
|
||||
newConf.RunOnConnectRestart != p.conf.RunOnConnectRestart ||
|
||||
newConf.RunOnDisconnect != p.conf.RunOnDisconnect ||
|
||||
@@ -930,6 +937,7 @@ func (p *Core) closeResources(newConf *conf.Conf, calledByAPI bool) {
|
||||
newConf.RTMPServerCert != p.conf.RTMPServerCert ||
|
||||
newConf.RTMPServerKey != p.conf.RTMPServerKey ||
|
||||
newConf.RTSPAddress != p.conf.RTSPAddress ||
|
||||
!reflect.DeepEqual(newConf.RTMPTrustedProxies, p.conf.RTMPTrustedProxies) ||
|
||||
newConf.RunOnConnect != p.conf.RunOnConnect ||
|
||||
newConf.RunOnConnectRestart != p.conf.RunOnConnectRestart ||
|
||||
newConf.RunOnDisconnect != p.conf.RunOnDisconnect ||
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
@@ -72,7 +73,7 @@ type conn struct {
|
||||
Conn net.Conn
|
||||
ServerSide bool
|
||||
|
||||
expectingSecrets int
|
||||
expectingSecrets atomic.Int32
|
||||
f *os.File
|
||||
pw *pcapgo.NgWriter
|
||||
once sync.Once
|
||||
@@ -157,7 +158,7 @@ func (c *conn) run() {
|
||||
}
|
||||
|
||||
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)
|
||||
return
|
||||
}
|
||||
@@ -189,8 +190,7 @@ func (c *conn) processEntry(e dumpEntry) {
|
||||
c.pw.Flush() //nolint:errcheck
|
||||
writeDecryptionSecretsBlock(c.f, e.data)
|
||||
|
||||
c.expectingSecrets--
|
||||
if c.expectingSecrets == 0 {
|
||||
if c.expectingSecrets.Add(-1) == 0 {
|
||||
for _, e2 := range c.delayed {
|
||||
c.processEntry(e2)
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func (t *DialTLSContext) Do(ctx context.Context, network, addr string) (net.Conn
|
||||
}
|
||||
|
||||
pdConn := netConn.(*conn)
|
||||
pdConn.expectingSecrets = 4
|
||||
pdConn.expectingSecrets.Store(4)
|
||||
tlsConfig.KeyLogWriter = &connKeyLogWriter{c: pdConn}
|
||||
|
||||
return tls.Client(netConn, tlsConfig), nil
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -2,17 +2,17 @@ package packetdumper
|
||||
|
||||
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.
|
||||
type listener struct {
|
||||
// Listener is a wrapper around a net.Listener that dumps packets to disk.
|
||||
type Listener struct {
|
||||
Wrapped net.Listener
|
||||
Prefix string
|
||||
Listener net.Listener
|
||||
}
|
||||
|
||||
// Accept implements net.Listener.
|
||||
func (l *listener) Accept() (net.Conn, error) {
|
||||
netConn, err := l.Listener.Accept()
|
||||
func (l *Listener) Accept() (net.Conn, error) {
|
||||
netConn, err := l.Wrapped.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -32,11 +32,11 @@ func (l *listener) Accept() (net.Conn, error) {
|
||||
}
|
||||
|
||||
// Close implements net.Listener.
|
||||
func (l *listener) Close() error {
|
||||
return l.Listener.Close()
|
||||
func (l *Listener) Close() error {
|
||||
return l.Wrapped.Close()
|
||||
}
|
||||
|
||||
// Addr implements net.Listener.
|
||||
func (l *listener) Addr() net.Addr {
|
||||
return l.Listener.Addr()
|
||||
func (l *Listener) Addr() net.Addr {
|
||||
return l.Wrapped.Addr()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
var _ net.PacketConn = (*packetConn)(nil)
|
||||
var _ net.PacketConn = (*PacketConn)(nil)
|
||||
|
||||
type extendedPacketConn interface {
|
||||
net.PacketConn
|
||||
@@ -28,10 +28,10 @@ type packetDumpEntry struct {
|
||||
src, dst *net.UDPAddr
|
||||
}
|
||||
|
||||
// packetConn is a wrapper around net.PacketConn that dumps packets to disk.
|
||||
type packetConn struct {
|
||||
// PacketConn is a wrapper around net.PacketConn that dumps packets to disk.
|
||||
type PacketConn struct {
|
||||
Prefix string
|
||||
PacketConn net.PacketConn
|
||||
Wrapped net.PacketConn
|
||||
|
||||
f *os.File
|
||||
pw *pcapgo.NgWriter
|
||||
@@ -43,7 +43,7 @@ type packetConn struct {
|
||||
}
|
||||
|
||||
// Initialize initializes packetConn.
|
||||
func (c *packetConn) Initialize() error {
|
||||
func (c *PacketConn) Initialize() error {
|
||||
var err error
|
||||
c.f, err = os.Create(fmt.Sprintf("%s_%d_%s.pcapng", c.Prefix, time.Now().UnixNano(), uuid.New().String()))
|
||||
if err != nil {
|
||||
@@ -66,15 +66,15 @@ func (c *packetConn) Initialize() error {
|
||||
}
|
||||
|
||||
// Close implements net.PacketConn.
|
||||
func (c *packetConn) Close() error {
|
||||
func (c *PacketConn) Close() error {
|
||||
c.once.Do(func() {
|
||||
close(c.terminated)
|
||||
})
|
||||
<-c.done
|
||||
return c.PacketConn.Close()
|
||||
return c.Wrapped.Close()
|
||||
}
|
||||
|
||||
func (c *packetConn) run() {
|
||||
func (c *PacketConn) run() {
|
||||
defer close(c.done)
|
||||
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{
|
||||
SrcMAC: 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)
|
||||
}
|
||||
|
||||
func (c *packetConn) enqueue(e packetDumpEntry) {
|
||||
func (c *PacketConn) enqueue(e packetDumpEntry) {
|
||||
select {
|
||||
case c.queue <- e:
|
||||
case <-c.terminated:
|
||||
@@ -138,11 +138,11 @@ func (c *packetConn) enqueue(e packetDumpEntry) {
|
||||
}
|
||||
|
||||
// ReadFrom implements net.PacketConn.
|
||||
func (c *packetConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
n, addr, err = c.PacketConn.ReadFrom(p)
|
||||
func (c *PacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
n, addr, err = c.Wrapped.ReadFrom(p)
|
||||
|
||||
if n != 0 {
|
||||
local := c.PacketConn.LocalAddr().(*net.UDPAddr)
|
||||
local := c.Wrapped.LocalAddr().(*net.UDPAddr)
|
||||
remote := addr.(*net.UDPAddr)
|
||||
|
||||
c.enqueue(packetDumpEntry{
|
||||
@@ -157,11 +157,11 @@ func (c *packetConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
}
|
||||
|
||||
// WriteTo implements net.PacketConn.
|
||||
func (c *packetConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
n, err = c.PacketConn.WriteTo(p, addr)
|
||||
func (c *PacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
n, err = c.Wrapped.WriteTo(p, addr)
|
||||
|
||||
if err == nil {
|
||||
local := c.PacketConn.LocalAddr().(*net.UDPAddr)
|
||||
local := c.Wrapped.LocalAddr().(*net.UDPAddr)
|
||||
remote := addr.(*net.UDPAddr)
|
||||
|
||||
c.enqueue(packetDumpEntry{
|
||||
@@ -176,23 +176,23 @@ func (c *packetConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
}
|
||||
|
||||
// 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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
func (c *packetConn) SetReadBuffer(bytes int) error {
|
||||
return c.PacketConn.(extendedPacketConn).SetReadBuffer(bytes)
|
||||
func (c *PacketConn) SetReadBuffer(bytes int) error {
|
||||
return c.Wrapped.(extendedPacketConn).SetReadBuffer(bytes)
|
||||
}
|
||||
|
||||
// SyscallConn implements extendedPacketConn.
|
||||
func (c *packetConn) SyscallConn() (syscall.RawConn, error) {
|
||||
return c.PacketConn.(extendedPacketConn).SyscallConn()
|
||||
func (c *PacketConn) SyscallConn() (syscall.RawConn, error) {
|
||||
return c.Wrapped.(extendedPacketConn).SyscallConn()
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ func TestPacketConnInitialize_CreatesFile(t *testing.T) {
|
||||
client, server := startUDPPair(t)
|
||||
|
||||
prefix := filepath.Join(t.TempDir(), "capture")
|
||||
c := &packetConn{Prefix: prefix, PacketConn: client}
|
||||
c := &PacketConn{Prefix: prefix, Wrapped: client}
|
||||
require.NoError(t, c.Initialize())
|
||||
|
||||
c.Close() //nolint:errcheck
|
||||
@@ -50,7 +50,7 @@ func TestPacketConnWriteTo(t *testing.T) {
|
||||
client, server := startUDPPair(t)
|
||||
|
||||
prefix := filepath.Join(t.TempDir(), "capture")
|
||||
c := &packetConn{Prefix: prefix, PacketConn: client}
|
||||
c := &PacketConn{Prefix: prefix, Wrapped: client}
|
||||
require.NoError(t, c.Initialize())
|
||||
|
||||
n, err := c.WriteTo([]byte("hello world"), server.LocalAddr())
|
||||
@@ -73,7 +73,7 @@ func TestPacketConnReadFrom(t *testing.T) {
|
||||
client, server := startUDPPair(t)
|
||||
|
||||
prefix := filepath.Join(t.TempDir(), "capture")
|
||||
c := &packetConn{Prefix: prefix, PacketConn: client}
|
||||
c := &PacketConn{Prefix: prefix, Wrapped: client}
|
||||
require.NoError(t, c.Initialize())
|
||||
|
||||
_, err := server.WriteTo([]byte("incoming data"), client.LocalAddr())
|
||||
@@ -96,7 +96,7 @@ func TestPacketConnMultipleWriteRead(t *testing.T) {
|
||||
client, server := startUDPPair(t)
|
||||
|
||||
prefix := filepath.Join(t.TempDir(), "capture")
|
||||
c := &packetConn{Prefix: prefix, PacketConn: client}
|
||||
c := &PacketConn{Prefix: prefix, Wrapped: client}
|
||||
require.NoError(t, c.Initialize())
|
||||
|
||||
serverAddr := server.LocalAddr()
|
||||
@@ -140,7 +140,7 @@ func TestPacketConnCloseIdempotent(t *testing.T) {
|
||||
client, server := startUDPPair(t)
|
||||
|
||||
prefix := filepath.Join(t.TempDir(), "capture")
|
||||
c := &packetConn{Prefix: prefix, PacketConn: client}
|
||||
c := &PacketConn{Prefix: prefix, Wrapped: client}
|
||||
require.NoError(t, c.Initialize())
|
||||
|
||||
c.Close() //nolint:errcheck
|
||||
@@ -154,7 +154,7 @@ func TestPacketConnDelegatesAddrMethods(t *testing.T) {
|
||||
client, server := startUDPPair(t)
|
||||
|
||||
prefix := filepath.Join(t.TempDir(), "capture")
|
||||
c := &packetConn{Prefix: prefix, PacketConn: client}
|
||||
c := &PacketConn{Prefix: prefix, Wrapped: client}
|
||||
require.NoError(t, c.Initialize())
|
||||
|
||||
require.Equal(t, client.LocalAddr(), c.LocalAddr())
|
||||
@@ -173,7 +173,7 @@ func TestPacketConnReadFromRecordsSource(t *testing.T) {
|
||||
client, server := startUDPPair(t)
|
||||
|
||||
prefix := filepath.Join(t.TempDir(), "capture")
|
||||
c := &packetConn{Prefix: prefix, PacketConn: client}
|
||||
c := &PacketConn{Prefix: prefix, Wrapped: client}
|
||||
require.NoError(t, c.Initialize())
|
||||
|
||||
_, err := server.WriteTo([]byte("ping"), client.LocalAddr())
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -20,28 +20,34 @@ func (w *connKeyLogWriter) Write(p []byte) (int, error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
type tlsListener struct {
|
||||
Listener net.Listener
|
||||
var _ net.Listener = (*TLSListener)(nil)
|
||||
|
||||
// TLSListener is a wrapper around a net.Listener that dumps TLS master secrets to disk.
|
||||
type TLSListener struct {
|
||||
Wrapped net.Listener
|
||||
TLSConfig *tls.Config
|
||||
}
|
||||
|
||||
func (l *tlsListener) Close() error {
|
||||
return l.Listener.Close()
|
||||
// Close implements net.Listener.
|
||||
func (l *TLSListener) Close() error {
|
||||
return l.Wrapped.Close()
|
||||
}
|
||||
|
||||
func (l *tlsListener) Addr() net.Addr {
|
||||
return l.Listener.Addr()
|
||||
// Addr implements net.Listener.
|
||||
func (l *TLSListener) Addr() net.Addr {
|
||||
return l.Wrapped.Addr()
|
||||
}
|
||||
|
||||
func (l *tlsListener) Accept() (net.Conn, error) {
|
||||
netConn, err := l.Listener.Accept()
|
||||
// Accept implements net.Listener.
|
||||
func (l *TLSListener) Accept() (net.Conn, error) {
|
||||
netConn, err := l.Wrapped.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tlsConfig := l.TLSConfig.Clone()
|
||||
pdConn := netConn.(*conn)
|
||||
pdConn.expectingSecrets = 4
|
||||
pdConn.expectingSecrets.Store(4)
|
||||
tlsConfig.KeyLogWriter = &connKeyLogWriter{c: pdConn}
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -130,20 +130,38 @@ func (s *Server) Initialize() error {
|
||||
}
|
||||
}
|
||||
|
||||
var listen func(network string, address string) (net.Listener, error)
|
||||
var tlsListen func(network string, laddr string, config *tls.Config) (net.Listener, error)
|
||||
listen := func(network string, address string) (net.Listener, error) {
|
||||
ln, err := net.Listen(network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if s.DumpPackets {
|
||||
listen = (&packetdumper.Listen{
|
||||
ln = &packetdumper.Listener{
|
||||
Wrapped: ln,
|
||||
Prefix: s.DumpPacketsPrefix,
|
||||
}).Do
|
||||
}
|
||||
}
|
||||
|
||||
tlsListen = (&packetdumper.TLSListen{
|
||||
Listen: listen,
|
||||
}).Do
|
||||
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 {
|
||||
listen = net.Listen
|
||||
tlsListen = tls.Listen
|
||||
ln = tls.NewListener(ln, config)
|
||||
}
|
||||
|
||||
return ln, nil
|
||||
}
|
||||
|
||||
if tlsConfig != nil {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/bluenviron/mediamtx/internal/externalcmd"
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/packetdumper"
|
||||
"github.com/bluenviron/mediamtx/internal/protocols/proxy"
|
||||
"github.com/bluenviron/mediamtx/internal/restrictnetwork"
|
||||
)
|
||||
|
||||
@@ -81,6 +82,7 @@ type Server struct {
|
||||
ServerCert string
|
||||
ServerKey string
|
||||
RTSPAddress string
|
||||
TrustedProxies conf.IPNetworks
|
||||
RunOnConnect string
|
||||
RunOnConnectRestart bool
|
||||
RunOnDisconnect string
|
||||
@@ -107,8 +109,11 @@ type Server struct {
|
||||
|
||||
// Initialize initializes the server.
|
||||
func (s *Server) Initialize() error {
|
||||
var listen func(network string, address string) (net.Listener, error)
|
||||
var tlsListen func(network string, laddr string, config *tls.Config) (net.Listener, error)
|
||||
listen := func(network, address string) (net.Listener, error) {
|
||||
ln, err := net.Listen(network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if s.DumpPackets {
|
||||
var proto string
|
||||
@@ -118,16 +123,40 @@ func (s *Server) Initialize() error {
|
||||
proto = "rtmp"
|
||||
}
|
||||
|
||||
listen = (&packetdumper.Listen{
|
||||
ln = &packetdumper.Listener{
|
||||
Wrapped: ln,
|
||||
Prefix: proto + "_server_conn",
|
||||
}).Do
|
||||
}
|
||||
}
|
||||
|
||||
tlsListen = (&packetdumper.TLSListen{
|
||||
Listen: listen,
|
||||
}).Do
|
||||
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 {
|
||||
listen = net.Listen
|
||||
tlsListen = tls.Listen
|
||||
ln = tls.NewListener(ln, config)
|
||||
}
|
||||
|
||||
return ln, nil
|
||||
}
|
||||
|
||||
if s.Encryption {
|
||||
|
||||
@@ -3,6 +3,7 @@ package rtmp
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -10,6 +11,8 @@ import (
|
||||
"github.com/bluenviron/gortmplib"
|
||||
"github.com/bluenviron/gortmplib/pkg/codecs"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/description"
|
||||
"github.com/pires/go-proxyproto"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/conf"
|
||||
"github.com/bluenviron/mediamtx/internal/defs"
|
||||
"github.com/bluenviron/mediamtx/internal/externalcmd"
|
||||
@@ -44,7 +47,11 @@ func TestServerPublish(t *testing.T) {
|
||||
"plain",
|
||||
"tls",
|
||||
} {
|
||||
t.Run(encrypt, func(t *testing.T) {
|
||||
for _, proxy := range []string{
|
||||
"no_proxy",
|
||||
"proxy",
|
||||
} {
|
||||
t.Run(encrypt+"_"+proxy, func(t *testing.T) {
|
||||
var serverCertFpath string
|
||||
var serverKeyFpath string
|
||||
|
||||
@@ -53,6 +60,10 @@ func TestServerPublish(t *testing.T) {
|
||||
serverKeyFpath = test.CreateTempFile(t, test.TLSCertKey)
|
||||
}
|
||||
|
||||
_, ipnet, err := net.ParseCIDR("127.0.0.1/32")
|
||||
require.NoError(t, err)
|
||||
trustedProxies := conf.IPNetworks{conf.IPNetwork(*ipnet)}
|
||||
|
||||
var strm *stream.Stream
|
||||
var reader *stream.Reader
|
||||
defer func() {
|
||||
@@ -74,15 +85,15 @@ func TestServerPublish(t *testing.T) {
|
||||
RTPMaxPayloadSize: 1450,
|
||||
Parent: test.NilLogger,
|
||||
}
|
||||
err := strm.Initialize()
|
||||
require.NoError(t, err)
|
||||
err2 := strm.Initialize()
|
||||
require.NoError(t, err2)
|
||||
|
||||
subStream := &stream.SubStream{
|
||||
Stream: strm,
|
||||
UseRTPPackets: false,
|
||||
}
|
||||
err = subStream.Initialize()
|
||||
require.NoError(t, err)
|
||||
err2 = subStream.Initialize()
|
||||
require.NoError(t, err2)
|
||||
|
||||
reader = &stream.Reader{Parent: test.NilLogger}
|
||||
|
||||
@@ -127,6 +138,7 @@ func TestServerPublish(t *testing.T) {
|
||||
ServerCert: serverCertFpath,
|
||||
ServerKey: serverKeyFpath,
|
||||
RTSPAddress: "",
|
||||
TrustedProxies: trustedProxies,
|
||||
RunOnConnect: "",
|
||||
RunOnConnectRestart: false,
|
||||
RunOnDisconnect: "",
|
||||
@@ -134,7 +146,7 @@ func TestServerPublish(t *testing.T) {
|
||||
PathManager: pathManager,
|
||||
Parent: test.NilLogger,
|
||||
}
|
||||
err := s.Initialize()
|
||||
err = s.Initialize()
|
||||
require.NoError(t, err)
|
||||
defer s.Close()
|
||||
|
||||
@@ -151,10 +163,33 @@ func TestServerPublish(t *testing.T) {
|
||||
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)
|
||||
@@ -208,6 +243,7 @@ func TestServerPublish(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerRead(t *testing.T) {
|
||||
for _, encrypt := range []string{
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -24,6 +25,7 @@ import (
|
||||
"github.com/bluenviron/mediamtx/internal/externalcmd"
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/packetdumper"
|
||||
"github.com/bluenviron/mediamtx/internal/protocols/proxy"
|
||||
)
|
||||
|
||||
// ErrConnNotFound is returned when a connection is not found.
|
||||
@@ -105,6 +107,7 @@ type Server struct {
|
||||
ServerCert string
|
||||
ServerKey string
|
||||
RTSPAddress string
|
||||
TrustedProxies conf.IPNetworks
|
||||
Transports conf.RTSPTransports
|
||||
RunOnConnect string
|
||||
RunOnConnectRestart bool
|
||||
@@ -166,6 +169,12 @@ func (s *Server) Initialize() error {
|
||||
s.srv.TLSConfig = &tls.Config{GetCertificate: s.loader.GetCertificate()}
|
||||
}
|
||||
|
||||
s.srv.Listen = func(network, address string) (net.Listener, error) {
|
||||
ln, err := net.Listen(network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if s.DumpPackets {
|
||||
var proto string
|
||||
if s.Encryption {
|
||||
@@ -174,17 +183,70 @@ func (s *Server) Initialize() error {
|
||||
proto = "rtsp"
|
||||
}
|
||||
|
||||
s.srv.Listen = (&packetdumper.Listen{
|
||||
ln = &packetdumper.Listener{
|
||||
Wrapped: ln,
|
||||
Prefix: proto + "_server_conn",
|
||||
}).Do
|
||||
}
|
||||
}
|
||||
|
||||
s.srv.ListenPacket = (&packetdumper.ListenPacket{
|
||||
if len(s.TrustedProxies) > 0 {
|
||||
pl := &proxy.Listener{
|
||||
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",
|
||||
}).Do
|
||||
}
|
||||
err = pc2.Initialize()
|
||||
if err != nil {
|
||||
pc.Close() //nolint:errcheck
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.srv.TLSListen = (&packetdumper.TLSListen{
|
||||
Listen: s.srv.Listen,
|
||||
}).Do
|
||||
pc = pc2
|
||||
}
|
||||
|
||||
return pc, nil
|
||||
}
|
||||
|
||||
err := s.srv.Start()
|
||||
|
||||
@@ -3,7 +3,10 @@ package rtsp
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -15,6 +18,8 @@ import (
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
||||
mpegts "github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts"
|
||||
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/conf"
|
||||
"github.com/bluenviron/mediamtx/internal/defs"
|
||||
@@ -49,7 +54,21 @@ func (p *dummyPath) RemoveReader(_ defs.PathRemoveReaderReq) {
|
||||
|
||||
func TestServerPublish(t *testing.T) {
|
||||
for _, ca := range []string{"basic", "digest", "basic+digest"} {
|
||||
t.Run(ca, func(t *testing.T) {
|
||||
for _, encrypt := range []string{"plain", "tls"} {
|
||||
for _, proxy := range []string{"no_proxy", "proxy"} {
|
||||
t.Run(ca+"_"+encrypt+"_"+proxy, func(t *testing.T) {
|
||||
var serverCertFpath string
|
||||
var serverKeyFpath string
|
||||
|
||||
if encrypt == "tls" {
|
||||
serverCertFpath = test.CreateTempFile(t, test.TLSCertPub)
|
||||
serverKeyFpath = test.CreateTempFile(t, test.TLSCertKey)
|
||||
}
|
||||
|
||||
_, ipnet, err := net.ParseCIDR("127.0.0.1/32")
|
||||
require.NoError(t, err)
|
||||
trustedProxies := conf.IPNetworks{conf.IPNetwork(*ipnet)}
|
||||
|
||||
var strm *stream.Stream
|
||||
var reader *stream.Reader
|
||||
defer func() {
|
||||
@@ -96,15 +115,15 @@ func TestServerPublish(t *testing.T) {
|
||||
RTPMaxPayloadSize: 1450,
|
||||
Parent: test.NilLogger,
|
||||
}
|
||||
err := strm.Initialize()
|
||||
require.NoError(t, err)
|
||||
err2 := strm.Initialize()
|
||||
require.NoError(t, err2)
|
||||
|
||||
subStream := &stream.SubStream{
|
||||
Stream: strm,
|
||||
UseRTPPackets: true,
|
||||
}
|
||||
err = subStream.Initialize()
|
||||
require.NoError(t, err)
|
||||
err2 = subStream.Initialize()
|
||||
require.NoError(t, err2)
|
||||
|
||||
reader = &stream.Reader{Parent: test.NilLogger}
|
||||
|
||||
@@ -144,19 +163,55 @@ func TestServerPublish(t *testing.T) {
|
||||
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()
|
||||
err = s.Initialize()
|
||||
require.NoError(t, err)
|
||||
defer s.Close()
|
||||
|
||||
source := gortsplib.Client{}
|
||||
var scheme string
|
||||
if encrypt == "tls" {
|
||||
scheme = "rtsps"
|
||||
} else {
|
||||
scheme = "rtsp"
|
||||
}
|
||||
|
||||
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: 8557},
|
||||
}
|
||||
_, err2 = header.WriteTo(c)
|
||||
if err2 != nil {
|
||||
return nil, err2
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
|
||||
source := gortsplib.Client{
|
||||
TLSConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
DialContext: dialContext,
|
||||
}
|
||||
|
||||
media0 := test.UniqueMediaH264()
|
||||
|
||||
err = source.StartRecording(
|
||||
"rtsp://myuser:mypass@127.0.0.1:8557/teststream?param=value",
|
||||
scheme+"://myuser:mypass@127.0.0.1:8557/teststream?param=value",
|
||||
&description.Session{Medias: []*description.Media{media0}})
|
||||
require.NoError(t, err)
|
||||
defer source.Close()
|
||||
@@ -197,13 +252,20 @@ func TestServerPublish(t *testing.T) {
|
||||
Conns: list.Items[0].Conns,
|
||||
RTPPacketsReceived: list.Items[0].RTPPacketsReceived,
|
||||
Transport: new("TCP"),
|
||||
Profile: new("AVP"),
|
||||
Profile: func() *string {
|
||||
if encrypt == "tls" {
|
||||
return new("SAVP")
|
||||
}
|
||||
return new("AVP")
|
||||
}(),
|
||||
},
|
||||
},
|
||||
}, list)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerPublishMPEGTS(t *testing.T) {
|
||||
var strm *stream.Stream
|
||||
|
||||
@@ -58,9 +58,17 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error {
|
||||
}
|
||||
|
||||
if s.DumpPackets {
|
||||
l.Listen = (&packetdumper.Listen{
|
||||
l.Listen = func(network, address string) (net.Listener, error) {
|
||||
ln, err2 := net.Listen(network, address)
|
||||
if err2 != nil {
|
||||
return nil, err2
|
||||
}
|
||||
|
||||
return &packetdumper.Listener{
|
||||
Wrapped: ln,
|
||||
Prefix: "mpegts_source_unix_conn",
|
||||
}).Do
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
err = l.Initialize()
|
||||
@@ -76,6 +84,7 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error {
|
||||
}
|
||||
|
||||
params := udp.URLToParams(u)
|
||||
|
||||
l := &udp.Listener{
|
||||
Address: params.Address,
|
||||
Source: params.Source,
|
||||
@@ -83,10 +92,27 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error {
|
||||
UDPReadBufferSize: int(udpReadBufferSize),
|
||||
}
|
||||
|
||||
l.ListenPacket = func(network, address string) (net.PacketConn, error) {
|
||||
pc, err2 := net.ListenPacket(network, address)
|
||||
if err2 != nil {
|
||||
return nil, err2
|
||||
}
|
||||
|
||||
if s.DumpPackets {
|
||||
l.ListenPacket = (&packetdumper.ListenPacket{
|
||||
pc2 := &packetdumper.PacketConn{
|
||||
Wrapped: pc,
|
||||
Prefix: "mpegts_source_packet_conn",
|
||||
}).Do
|
||||
}
|
||||
err2 = pc2.Initialize()
|
||||
if err2 != nil {
|
||||
pc.Close() //nolint:errcheck
|
||||
return nil, err2
|
||||
}
|
||||
|
||||
pc = pc2
|
||||
}
|
||||
|
||||
return pc, nil
|
||||
}
|
||||
|
||||
err = l.Initialize()
|
||||
|
||||
@@ -92,10 +92,27 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error {
|
||||
UDPReadBufferSize: int(udpReadBufferSize),
|
||||
}
|
||||
|
||||
l.ListenPacket = func(network, address string) (net.PacketConn, error) {
|
||||
pc, err2 := net.ListenPacket(network, address)
|
||||
if err2 != nil {
|
||||
return nil, err2
|
||||
}
|
||||
|
||||
if s.DumpPackets {
|
||||
l.ListenPacket = (&packetdumper.ListenPacket{
|
||||
pc2 := &packetdumper.PacketConn{
|
||||
Wrapped: pc,
|
||||
Prefix: "rtp_source_packet_conn",
|
||||
}).Do
|
||||
}
|
||||
err2 = pc2.Initialize()
|
||||
if err2 != nil {
|
||||
pc.Close() //nolint:errcheck
|
||||
return nil, err2
|
||||
}
|
||||
|
||||
pc = pc2
|
||||
}
|
||||
|
||||
return pc, nil
|
||||
}
|
||||
|
||||
err = l.Initialize()
|
||||
|
||||
@@ -3,6 +3,7 @@ package rtsp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
@@ -187,10 +188,6 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error {
|
||||
Prefix: u.Scheme + "_source_conn",
|
||||
}).Do
|
||||
|
||||
c.ListenPacket = (&packetdumper.ListenPacket{
|
||||
Prefix: u.Scheme + "_source_packet_conn",
|
||||
}).Do
|
||||
|
||||
c.DialTLSContext = (&packetdumper.DialTLSContext{
|
||||
DialContext: c.DialContext,
|
||||
TLSConfig: tlsConfig,
|
||||
@@ -199,6 +196,29 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error {
|
||||
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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -281,6 +281,10 @@ rtspServerCert: server.crt
|
||||
# Authentication methods. Available are "basic" and "digest".
|
||||
# "digest" doesn't provide any additional security and is available for compatibility only.
|
||||
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
|
||||
@@ -301,6 +305,10 @@ rtmpsAddress: :1936
|
||||
rtmpServerKey: server.key
|
||||
# Path to the server certificate. This is needed only when encryption is "strict" or "optional".
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user