rtsp: support encrypting UDP and UDP-multicast streams (#4690)

This commit is contained in:
Alessandro Ros 2025-07-05 13:46:59 +02:00 committed by GitHub
parent 81af4075f0
commit c475f84e5d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 131 additions and 90 deletions

View file

@ -301,7 +301,14 @@ The RTSP protocol supports multiple underlying transport protocols, each with it
```sh
gst-launch-1.0 filesrc location=file.mp4 ! qtdemux name=d \
d.video_0 ! rtspclientsink protocols=tcp name=s location=rtsp://localhost:8554/mystream
d.video_0 ! rtspclientsink location=rtsp://localhost:8554/mystream protocols=tcp
```
If encryption is enabled, the `tls-validation-flags` and `profiles` options must be specified too:
```sh
gst-launch-1.0 filesrc location=file.mp4 ! qtdemux name=d \
d.video_0 ! rtspclientsink location=rtsp://localhost:8554/mystream tls-validation-flags=0 profiles=GST_RTSP_PROFILE_SAVP
```
The resulting stream is available in path `/mystream`.
@ -2407,9 +2414,9 @@ ffmpeg -i rtsp://original-source \
The RTSP protocol supports different underlying transport protocols, that are chosen by clients during the handshake with the server:
* UDP: the most performant, but doesn't work when there's a NAT/firewall between server and clients. It doesn't support encryption.
* UDP-multicast: allows to save bandwidth when clients are all in the same LAN, by sending packets once to a fixed multicast IP. It doesn't support encryption.
* TCP: the most versatile, does support encryption.
* UDP: the most performant, but doesn't work when there's a NAT/firewall between server and clients.
* UDP-multicast: allows to save bandwidth when clients are all in the same LAN, by sending packets once to a fixed multicast IP.
* TCP: the most versatile.
The default transport protocol is UDP. To change the transport protocol, you have to tune the configuration of your client of choice.
@ -2422,10 +2429,9 @@ openssl genrsa -out server.key 2048
openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
```
Edit `mediamtx.yml` and set the `rtspTransports`, `encryption`, `serverKey` and serverCert parameters:
Edit `mediamtx.yml` and set the `encryption`, `serverKey` and serverCert parameters:
```yml
rtspTransports: [tcp]
rtspEncryption: optional
rtspServerKey: server.key
rtspServerCert: server.crt

View file

@ -195,6 +195,14 @@ components:
type: integer
multicastRTCPPort:
type: integer
srtpAddress:
type: string
srtcpAddress:
type: string
multicastSRTPPort:
type: integer
multicastSRTCPPort:
type: integer
rtspServerKey:
type: string
rtspServerCert:

4
go.mod
View file

@ -10,7 +10,7 @@ require (
github.com/alecthomas/kong v1.12.0
github.com/asticode/go-astits v1.13.0
github.com/bluenviron/gohlslib/v2 v2.2.0
github.com/bluenviron/gortsplib/v4 v4.14.1
github.com/bluenviron/gortsplib/v4 v4.14.2-0.20250705110245-9c1011567a97
github.com/bluenviron/mediacommon/v2 v2.2.0
github.com/datarhei/gosrt v0.9.0
github.com/fsnotify/fsnotify v1.9.0
@ -73,7 +73,7 @@ require (
github.com/pion/mdns/v2 v2.0.7 // indirect
github.com/pion/randutil v0.1.0 // indirect
github.com/pion/sctp v1.8.36 // indirect
github.com/pion/srtp/v3 v3.0.4 // indirect
github.com/pion/srtp/v3 v3.0.6 // indirect
github.com/pion/stun/v3 v3.0.0 // indirect
github.com/pion/transport/v3 v3.0.7 // indirect
github.com/pion/turn/v4 v4.0.0 // indirect

8
go.sum
View file

@ -35,8 +35,8 @@ github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c h1:8XZeJrs4+ZYh
github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c/go.mod h1:x1vxHcL/9AVzuk5HOloOEPrtJY0MaalYr78afXZ+pWI=
github.com/bluenviron/gohlslib/v2 v2.2.0 h1:eIsCai3IHP0F538h2tCPCRkhQ7XSOaxeceMyPns0o1k=
github.com/bluenviron/gohlslib/v2 v2.2.0/go.mod h1:sLyKB5iM6Su1kucNHuDUU9aeN/Hw4WxsV2y9k2IHMGs=
github.com/bluenviron/gortsplib/v4 v4.14.1 h1:v99NmXeeJFfbrO+ipPzPxYGibQaR5ZOUESOA9UQZhsI=
github.com/bluenviron/gortsplib/v4 v4.14.1/go.mod h1:3LaEcg0d47+kfXju5KSlsSxCiZ3IKBI/sqIrBPcsS64=
github.com/bluenviron/gortsplib/v4 v4.14.2-0.20250705110245-9c1011567a97 h1:V8m1pyQOYVEJK5RBy1SLg/Y+hgXYFFiMZOd7NhWWLAE=
github.com/bluenviron/gortsplib/v4 v4.14.2-0.20250705110245-9c1011567a97/go.mod h1:rur2QGh1wRU6KINZn8LwU8qTPFt1XafJGtsfs0KYzRo=
github.com/bluenviron/mediacommon/v2 v2.2.0 h1:fGXEX0OEvv5VhGHOv3Q2ABzOtSkIpl9UbwOHrnKWNTk=
github.com/bluenviron/mediacommon/v2 v2.2.0/go.mod h1:a6MbPmXtYda9mKibKVMZlW20GYLLrX2R7ZkUE+1pwV0=
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
@ -173,8 +173,8 @@ github.com/pion/sctp v1.8.36 h1:owNudmnz1xmhfYje5L/FCav3V9wpPRePHle3Zi+P+M0=
github.com/pion/sctp v1.8.36/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=
github.com/pion/sdp/v3 v3.0.14 h1:1h7gBr9FhOWH5GjWWY5lcw/U85MtdcibTyt/o6RxRUI=
github.com/pion/sdp/v3 v3.0.14/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E=
github.com/pion/srtp/v3 v3.0.4 h1:2Z6vDVxzrX3UHEgrUyIGM4rRouoC7v+NiF1IHtp9B5M=
github.com/pion/srtp/v3 v3.0.4/go.mod h1:1Jx3FwDoxpRaTh1oRV8A/6G1BnFL+QI82eK4ms8EEJQ=
github.com/pion/srtp/v3 v3.0.6 h1:E2gyj1f5X10sB/qILUGIkL4C2CqK269Xq167PbGCc/4=
github.com/pion/srtp/v3 v3.0.6/go.mod h1:BxvziG3v/armJHAaJ87euvkhHqWe9I7iiOy50K2QkhY=
github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw=
github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU=
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=

View file

@ -233,6 +233,10 @@ type Conf struct {
MulticastIPRange string `json:"multicastIPRange"`
MulticastRTPPort int `json:"multicastRTPPort"`
MulticastRTCPPort int `json:"multicastRTCPPort"`
SRTPAddress string `json:"srtpAddress"`
SRTCPAddress string `json:"srtcpAddress"`
MulticastSRTPPort int `json:"multicastSRTPPort"`
MulticastSRTCPPort int `json:"multicastSRTCPPort"`
ServerKey *string `json:"serverKey,omitempty"`
ServerCert *string `json:"serverCert,omitempty"`
RTSPServerKey string `json:"rtspServerKey"`
@ -376,6 +380,10 @@ func (conf *Conf) setDefaults() {
conf.MulticastIPRange = "224.1.0.0/16"
conf.MulticastRTPPort = 8002
conf.MulticastRTCPPort = 8003
conf.SRTPAddress = ":8004"
conf.SRTCPAddress = ":8005"
conf.MulticastSRTPPort = 8006
conf.MulticastSRTCPPort = 8007
conf.RTSPServerKey = "server.key"
conf.RTSPServerCert = "server.crt"
conf.RTSPAuthMethods = RTSPAuthMethods{auth.VerifyMethodBasic}
@ -619,14 +627,6 @@ func (conf *Conf) Validate(l logger.Writer) error {
l.Log(logger.Warn, "parameter 'encryption' is deprecated and has been replaced with 'rtspEncryption'")
conf.RTSPEncryption = *conf.Encryption
}
if conf.RTSPEncryption == EncryptionStrict {
if _, ok := conf.RTSPTransports[gortsplib.TransportUDP]; ok {
return fmt.Errorf("strict encryption cannot be used with the UDP transport protocol")
}
if _, ok := conf.RTSPTransports[gortsplib.TransportUDPMulticast]; ok {
return fmt.Errorf("strict encryption cannot be used with the UDP-multicast transport protocol")
}
}
if conf.AuthMethods != nil {
l.Log(logger.Warn, "parameter 'authMethods' is deprecated and has been replaced with 'rtspAuthMethods'")
conf.RTSPAuthMethods = *conf.AuthMethods

View file

@ -291,18 +291,6 @@ func TestConfErrors(t *testing.T) {
"udpMaxPayloadSize: 5000\n",
"'udpMaxPayloadSize' must be less than 1472",
},
{
"invalid strict encryption 1",
"rtspEncryption: strict\n" +
"rtspTransports: [udp]\n",
"strict encryption cannot be used with the UDP transport protocol",
},
{
"invalid strict encryption 2",
"rtspEncryption: strict\n" +
"rtspTransports: [multicast]\n",
"strict encryption cannot be used with the UDP-multicast transport protocol",
},
{
"invalid ICE server",
"webrtcICEServers: [testing]\n",

View file

@ -410,19 +410,22 @@ func (p *Core) createResources(initial bool) error {
(p.conf.RTSPEncryption == conf.EncryptionStrict ||
p.conf.RTSPEncryption == conf.EncryptionOptional) &&
p.rtspsServer == nil {
_, useUDP := p.conf.RTSPTransports[gortsplib.TransportUDP]
_, useMulticast := p.conf.RTSPTransports[gortsplib.TransportUDPMulticast]
i := &rtsp.Server{
Address: p.conf.RTSPSAddress,
AuthMethods: p.conf.RTSPAuthMethods,
ReadTimeout: p.conf.ReadTimeout,
WriteTimeout: p.conf.WriteTimeout,
WriteQueueSize: p.conf.WriteQueueSize,
UseUDP: false,
UseMulticast: false,
RTPAddress: "",
RTCPAddress: "",
MulticastIPRange: "",
MulticastRTPPort: 0,
MulticastRTCPPort: 0,
UseUDP: useUDP,
UseMulticast: useMulticast,
RTPAddress: p.conf.SRTPAddress,
RTCPAddress: p.conf.SRTCPAddress,
MulticastIPRange: p.conf.MulticastIPRange,
MulticastRTPPort: p.conf.MulticastSRTPPort,
MulticastRTCPPort: p.conf.MulticastSRTCPPort,
IsTLS: true,
ServerCert: p.conf.RTSPServerCert,
ServerKey: p.conf.RTSPServerKey,

View file

@ -398,12 +398,15 @@ func TestPathRunOnRead(t *testing.T) {
switch ca {
case "rtsp":
reader := gortsplib.Client{}
u, err := base.ParseURL("rtsp://127.0.0.1:8554/test?query=value")
require.NoError(t, err)
err = reader.Start(u.Scheme, u.Host)
reader := gortsplib.Client{
Scheme: u.Scheme,
Host: u.Host,
}
err = reader.Start2()
require.NoError(t, err)
defer reader.Close()
@ -417,12 +420,16 @@ func TestPathRunOnRead(t *testing.T) {
require.NoError(t, err)
case "rtsps":
reader := gortsplib.Client{TLSConfig: &tls.Config{InsecureSkipVerify: true}}
u, err := base.ParseURL("rtsps://127.0.0.1:8322/test?query=value")
require.NoError(t, err)
err = reader.Start(u.Scheme, u.Host)
reader := gortsplib.Client{
Scheme: u.Scheme,
Host: u.Host,
TLSConfig: &tls.Config{InsecureSkipVerify: true},
}
err = reader.Start2()
require.NoError(t, err)
defer reader.Close()
@ -649,12 +656,15 @@ func TestPathMaxReaders(t *testing.T) {
defer source.Close()
for i := 0; i < 2; i++ {
reader := gortsplib.Client{}
u, err := base.ParseURL("rtsp://127.0.0.1:8554/mystream")
require.NoError(t, err)
err = reader.Start(u.Scheme, u.Host)
reader := gortsplib.Client{
Scheme: u.Scheme,
Host: u.Host,
}
err = reader.Start2()
require.NoError(t, err)
defer reader.Close()
@ -796,8 +806,12 @@ func TestPathFallback(t *testing.T) {
u, err := base.ParseURL("rtsp://localhost:8554/path1")
require.NoError(t, err)
dest := gortsplib.Client{}
err = dest.Start(u.Scheme, u.Host)
dest := gortsplib.Client{
Scheme: u.Scheme,
Host: u.Host,
}
err = dest.Start2()
require.NoError(t, err)
defer dest.Close()
@ -856,12 +870,15 @@ func TestPathResolveSource(t *testing.T) {
require.Equal(t, true, ok)
defer p.Close()
reader := gortsplib.Client{}
u, err := base.ParseURL("rtsp://127.0.0.1:8554/test_a?key=val")
require.NoError(t, err)
err = reader.Start(u.Scheme, u.Host)
reader := gortsplib.Client{
Scheme: u.Scheme,
Host: u.Host,
}
err = reader.Start2()
require.NoError(t, err)
defer reader.Close()
@ -909,12 +926,15 @@ func TestPathOverridePublisher(t *testing.T) {
frameRecv := make(chan struct{})
c := gortsplib.Client{}
u, err := base.ParseURL("rtsp://localhost:8554/teststream")
require.NoError(t, err)
err = c.Start(u.Scheme, u.Host)
c := gortsplib.Client{
Scheme: u.Scheme,
Host: u.Host,
}
err = c.Start2()
require.NoError(t, err)
defer c.Close()

View file

@ -253,12 +253,15 @@ func TestServerRead(t *testing.T) {
require.NoError(t, err)
defer s.Close()
reader := gortsplib.Client{}
u, err := base.ParseURL("rtsp://myuser:mypass@127.0.0.1:8557/teststream?param=value")
require.NoError(t, err)
err = reader.Start(u.Scheme, u.Host)
reader := gortsplib.Client{
Scheme: u.Scheme,
Host: u.Host,
}
err = reader.Start2()
require.NoError(t, err)
defer reader.Close()
@ -370,12 +373,15 @@ func TestServerRedirect(t *testing.T) {
require.NoError(t, err)
defer s.Close()
reader := gortsplib.Client{}
u, err := base.ParseURL("rtsp://myuser:mypass@127.0.0.1:8557/path1?param=value")
require.NoError(t, err)
err = reader.Start(u.Scheme, u.Host)
reader := gortsplib.Client{
Scheme: u.Scheme,
Host: u.Host,
}
err = reader.Start2()
require.NoError(t, err)
defer reader.Close()

View file

@ -110,7 +110,14 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error {
decodeErrors.Start()
defer decodeErrors.Stop()
u, err := base.ParseURL(params.ResolvedSource)
if err != nil {
return err
}
c := &gortsplib.Client{
Scheme: u.Scheme,
Host: u.Host,
Transport: params.Conf.RTSPTransport.Transport,
TLSConfig: tls.ConfigForFingerprint(params.Conf.SourceFingerprint),
ReadTimeout: time.Duration(s.ReadTimeout),
@ -134,12 +141,7 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error {
},
}
u, err := base.ParseURL(params.ResolvedSource)
if err != nil {
return err
}
err = c.Start(u.Scheme, u.Host)
err = c.Start2()
if err != nil {
return err
}

View file

@ -237,12 +237,12 @@ playbackTrustedProxies: []
rtsp: yes
# List of enabled RTSP transport protocols.
# UDP is the most performant, but doesn't work when there's a NAT/firewall between
# server and clients, and doesn't support encryption.
# server and clients.
# UDP-multicast allows to save bandwidth when clients are all in the same LAN.
# TCP is the most versatile, and does support encryption.
# TCP is the most versatile.
# The handshake is always performed with TCP.
rtspTransports: [udp, multicast, tcp]
# Encrypt handshakes and TCP streams with TLS (RTSPS).
# Use secure protocol variants (RTSPS, TLS, SRTP).
# Available values are "no", "strict", "optional".
rtspEncryption: "no"
# Address of the TCP/RTSP listener. This is needed only when encryption is "no" or "optional".
@ -259,6 +259,14 @@ multicastIPRange: 224.1.0.0/16
multicastRTPPort: 8002
# Port of all UDP-multicast/RTCP listeners. This is needed only when "multicast" is in rtspTransports.
multicastRTCPPort: 8003
# Address of the UDP/SRTP listener. This is needed only when "udp" is in rtspTransports and encryption is enabled.
srtpAddress: :8004
# Address of the UDP/SRTCP listener. This is needed only when "udp" is in rtspTransports and encryption is enabled.
srtcpAddress: :8005
# Port of all UDP-multicast/SRTP listeners. This is needed only when "multicast" is in rtspTransports and encryption is enabled.
multicastSRTPPort: 8006
# Port of all UDP-multicast/SRTCP listeners. This is needed only when "multicast" is in rtspTransports and encryption is enabled.
multicastSRTCPPort: 8007
# Path to the server key. This is needed only when encryption is "strict" or "optional".
# This can be generated with:
# openssl genrsa -out server.key 2048