forked from External/mediamtx
This commit is contained in:
parent
caa9fa6ca0
commit
39ae1269ad
3 changed files with 134 additions and 7 deletions
42
README.md
42
README.md
|
|
@ -134,7 +134,8 @@ _rtsp-simple-server_ has been rebranded as _MediaMTX_. The reason is pretty obvi
|
||||||
* [SRT-specific features](#srt-specific-features)
|
* [SRT-specific features](#srt-specific-features)
|
||||||
* [Standard stream ID syntax](#standard-stream-id-syntax)
|
* [Standard stream ID syntax](#standard-stream-id-syntax)
|
||||||
* [WebRTC-specific features](#webrtc-specific-features)
|
* [WebRTC-specific features](#webrtc-specific-features)
|
||||||
* [Connectivity issues](#connectivity-issues)
|
* [Authenticating with WHIP/WHEP](#authenticating-with-whipwhep)
|
||||||
|
* [Solving WebRTC connectivity issues](#solving-webrtc-connectivity-issues)
|
||||||
* [RTSP-specific features](#rtsp-specific-features)
|
* [RTSP-specific features](#rtsp-specific-features)
|
||||||
* [Transport protocols](#transport-protocols)
|
* [Transport protocols](#transport-protocols)
|
||||||
* [Encryption](#encryption)
|
* [Encryption](#encryption)
|
||||||
|
|
@ -338,6 +339,7 @@ Latest versions of OBS Studio can publish to the server with the [WebRTC / WHIP
|
||||||
|
|
||||||
* Service: `WHIP`
|
* Service: `WHIP`
|
||||||
* Server: `http://localhost:8889/mystream/whip`
|
* Server: `http://localhost:8889/mystream/whip`
|
||||||
|
* Bearer Token: `myuser:mypass` (if internal authentication is enabled) or JWT (if JWT-based authentication is enabled)
|
||||||
|
|
||||||
Save the configuration and click `Start streaming`.
|
Save the configuration and click `Start streaming`.
|
||||||
|
|
||||||
|
|
@ -610,7 +612,9 @@ WHIP is a WebRTC extensions that allows to publish streams by using a URL, witho
|
||||||
http://localhost:8889/mystream/whip
|
http://localhost:8889/mystream/whip
|
||||||
```
|
```
|
||||||
|
|
||||||
Depending on the network it may be difficult to establish a connection between server and clients, see [WebRTC-specific features](#webrtc-specific-features) for remediations.
|
Regarding authentication, read [Authenticating with WHIP/WHEP](#authenticating-with-whipwhep).
|
||||||
|
|
||||||
|
Depending on the network it may be difficult to establish a connection between server and clients, read [Solving WebRTC connectivity issues](#solving-webrtc-connectivity-issues).
|
||||||
|
|
||||||
Known clients that can publish with WebRTC and WHIP are [FFmpeg](#ffmpeg), [GStreamer](#gstreamer), [OBS Studio](#obs-studio).
|
Known clients that can publish with WebRTC and WHIP are [FFmpeg](#ffmpeg), [GStreamer](#gstreamer), [OBS Studio](#obs-studio).
|
||||||
|
|
||||||
|
|
@ -876,7 +880,9 @@ WHEP is a WebRTC extensions that allows to read streams by using a URL, without
|
||||||
http://localhost:8889/mystream/whep
|
http://localhost:8889/mystream/whep
|
||||||
```
|
```
|
||||||
|
|
||||||
Depending on the network it may be difficult to establish a connection between server and clients, see [WebRTC-specific features](#webrtc-specific-features) for remediations.
|
Regarding authentication, read [Authenticating with WHIP/WHEP](#authenticating-with-whipwhep).
|
||||||
|
|
||||||
|
Depending on the network it may be difficult to establish a connection between server and clients, read [Solving WebRTC connectivity issues](#solving-webrtc-connectivity-issues).
|
||||||
|
|
||||||
Known clients that can read with WebRTC and WHEP are [FFmpeg](#ffmpeg-1), [GStreamer](#gstreamer-1) and [web browsers](#web-browsers-1).
|
Known clients that can read with WebRTC and WHEP are [FFmpeg](#ffmpeg-1), [GStreamer](#gstreamer-1) and [web browsers](#web-browsers-1).
|
||||||
|
|
||||||
|
|
@ -1838,7 +1844,35 @@ Where:
|
||||||
|
|
||||||
### WebRTC-specific features
|
### WebRTC-specific features
|
||||||
|
|
||||||
#### Connectivity issues
|
#### Authenticating with WHIP/WHEP
|
||||||
|
|
||||||
|
When using WHIP or WHEP to establish a WebRTC connection, there are multiple ways to provide credentials.
|
||||||
|
|
||||||
|
If internal authentication or HTTP-based authentication is enabled, username and password can be passed through the `Authentication: Basic` header:
|
||||||
|
|
||||||
|
```
|
||||||
|
Authentication: Basic [base64_encoded_credentials]
|
||||||
|
```
|
||||||
|
|
||||||
|
Username and password can be also passed through the `Authentication: Bearer` header (since it's mandated by the specification):
|
||||||
|
|
||||||
|
```
|
||||||
|
Authentication: Bearer username:password
|
||||||
|
```
|
||||||
|
|
||||||
|
If JWT-based authentication is enabled, JWT can be passed through the `Authentication: Bearer` header:
|
||||||
|
|
||||||
|
```
|
||||||
|
Authentication: Bearer [jwt]
|
||||||
|
```
|
||||||
|
|
||||||
|
The JWT can also be passed through query parameters:
|
||||||
|
|
||||||
|
```
|
||||||
|
http://localhost:8889/mystream/whip?jwt=[jwt]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Solving WebRTC connectivity issues
|
||||||
|
|
||||||
If the server is hosted inside a container or is behind a NAT, additional configuration is required in order to allow the two WebRTC parts (server and client) to establish a connection.
|
If the server is hosted inside a container or is behind a NAT, additional configuration is required in order to allow the two WebRTC parts (server and client) to establish a connection.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -121,10 +121,17 @@ func (s *httpServer) close() {
|
||||||
|
|
||||||
func (s *httpServer) checkAuthOutsideSession(ctx *gin.Context, pathName string, publish bool) bool {
|
func (s *httpServer) checkAuthOutsideSession(ctx *gin.Context, pathName string, publish bool) bool {
|
||||||
user, pass, hasCredentials := ctx.Request.BasicAuth()
|
user, pass, hasCredentials := ctx.Request.BasicAuth()
|
||||||
|
|
||||||
q := ctx.Request.URL.RawQuery
|
q := ctx.Request.URL.RawQuery
|
||||||
|
|
||||||
if h := ctx.Request.Header.Get("Authorization"); strings.HasPrefix(h, "Bearer ") {
|
if h := ctx.Request.Header.Get("Authorization"); strings.HasPrefix(h, "Bearer ") {
|
||||||
|
// JWT in authorization bearer -> JWT in query parameters
|
||||||
q = addJWTFromAuthorization(q, h)
|
q = addJWTFromAuthorization(q, h)
|
||||||
|
|
||||||
|
// credentials in authorization bearer -> credentials in authorization basic
|
||||||
|
if parts := strings.Split(strings.TrimPrefix(h, "Bearer "), ":"); len(parts) == 2 {
|
||||||
|
user = parts[0]
|
||||||
|
pass = parts[1]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := s.pathManager.FindPathConf(defs.PathFindPathConfReq{
|
_, err := s.pathManager.FindPathConf(defs.PathFindPathConfReq{
|
||||||
|
|
@ -194,10 +201,17 @@ func (s *httpServer) onWHIPPost(ctx *gin.Context, pathName string, publish bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
user, pass, _ := ctx.Request.BasicAuth()
|
user, pass, _ := ctx.Request.BasicAuth()
|
||||||
|
|
||||||
q := ctx.Request.URL.RawQuery
|
q := ctx.Request.URL.RawQuery
|
||||||
|
|
||||||
if h := ctx.Request.Header.Get("Authorization"); strings.HasPrefix(h, "Bearer ") {
|
if h := ctx.Request.Header.Get("Authorization"); strings.HasPrefix(h, "Bearer ") {
|
||||||
|
// JWT in authorization bearer -> JWT in query parameters
|
||||||
q = addJWTFromAuthorization(q, h)
|
q = addJWTFromAuthorization(q, h)
|
||||||
|
|
||||||
|
// credentials in authorization bearer -> credentials in authorization basic
|
||||||
|
if parts := strings.Split(strings.TrimPrefix(h, "Bearer "), ":"); len(parts) == 2 {
|
||||||
|
user = parts[0]
|
||||||
|
pass = parts[1]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res := s.parent.newSession(webRTCNewSessionReq{
|
res := s.parent.newSession(webRTCNewSessionReq{
|
||||||
|
|
|
||||||
|
|
@ -603,7 +603,7 @@ func TestServerRead(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServerReadAuthorizationHeader(t *testing.T) {
|
func TestServerReadAuthorizationBearerJWT(t *testing.T) {
|
||||||
desc := &description.Session{Medias: []*description.Media{test.MediaH264}}
|
desc := &description.Session{Medias: []*description.Media{test.MediaH264}}
|
||||||
|
|
||||||
str, err := stream.New(
|
str, err := stream.New(
|
||||||
|
|
@ -680,6 +680,85 @@ func TestServerReadAuthorizationHeader(t *testing.T) {
|
||||||
require.Equal(t, http.StatusCreated, res.StatusCode)
|
require.Equal(t, http.StatusCreated, res.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestServerReadAuthorizationUserPass(t *testing.T) {
|
||||||
|
desc := &description.Session{Medias: []*description.Media{test.MediaH264}}
|
||||||
|
|
||||||
|
str, err := stream.New(
|
||||||
|
1460,
|
||||||
|
desc,
|
||||||
|
true,
|
||||||
|
test.NilLogger,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
path := &dummyPath{stream: str}
|
||||||
|
|
||||||
|
pm := &dummyPathManager{
|
||||||
|
findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) {
|
||||||
|
require.Equal(t, "myuser", req.AccessRequest.User)
|
||||||
|
require.Equal(t, "mypass", req.AccessRequest.Pass)
|
||||||
|
return &conf.Path{}, nil
|
||||||
|
},
|
||||||
|
addReader: func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) {
|
||||||
|
require.Equal(t, "myuser", req.AccessRequest.User)
|
||||||
|
require.Equal(t, "mypass", req.AccessRequest.Pass)
|
||||||
|
return path, str, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &Server{
|
||||||
|
Address: "127.0.0.1:8886",
|
||||||
|
Encryption: false,
|
||||||
|
ServerKey: "",
|
||||||
|
ServerCert: "",
|
||||||
|
AllowOrigin: "",
|
||||||
|
TrustedProxies: conf.IPNetworks{},
|
||||||
|
ReadTimeout: conf.StringDuration(10 * time.Second),
|
||||||
|
WriteQueueSize: 512,
|
||||||
|
LocalUDPAddress: "127.0.0.1:8887",
|
||||||
|
LocalTCPAddress: "127.0.0.1:8887",
|
||||||
|
IPsFromInterfaces: true,
|
||||||
|
IPsFromInterfacesList: []string{},
|
||||||
|
AdditionalHosts: []string{},
|
||||||
|
ICEServers: []conf.WebRTCICEServer{},
|
||||||
|
HandshakeTimeout: conf.StringDuration(10 * time.Second),
|
||||||
|
TrackGatherTimeout: conf.StringDuration(2 * time.Second),
|
||||||
|
ExternalCmdPool: nil,
|
||||||
|
PathManager: pm,
|
||||||
|
Parent: test.NilLogger,
|
||||||
|
}
|
||||||
|
err = s.Initialize()
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
tr := &http.Transport{}
|
||||||
|
defer tr.CloseIdleConnections()
|
||||||
|
hc := &http.Client{Transport: tr}
|
||||||
|
|
||||||
|
pc, err := pwebrtc.NewPeerConnection(pwebrtc.Configuration{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer pc.Close() //nolint:errcheck
|
||||||
|
|
||||||
|
_, err = pc.AddTransceiverFromKind(pwebrtc.RTPCodecTypeVideo)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
offer, err := pc.CreateOffer(nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPost,
|
||||||
|
"http://localhost:8886/teststream/whep", bytes.NewReader([]byte(offer.SDP)))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/sdp")
|
||||||
|
req.Header.Set("Authorization", "Bearer myuser:mypass")
|
||||||
|
|
||||||
|
res, err := hc.Do(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusCreated, res.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
func TestServerReadNotFound(t *testing.T) {
|
func TestServerReadNotFound(t *testing.T) {
|
||||||
pm := &dummyPathManager{
|
pm := &dummyPathManager{
|
||||||
findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) {
|
findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue