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
|
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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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=
|
||||||
|
|||||||
@@ -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"`
|
||||||
|
|||||||
@@ -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 ||
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
"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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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
|
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)
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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/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 {
|
||||||
|
|||||||
@@ -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¶m=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¶m=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¶m=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¶m=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¶m=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¶m=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)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user