From 94efd0fa528b6c7f5ad8c041cb64f1e2e16ae6a5 Mon Sep 17 00:00:00 2001 From: Dan Bason Date: Mon, 25 Mar 2024 13:20:55 +1300 Subject: [PATCH 1/2] Add option for ICE servers to be client only --- README.md | 10 +++++++ internal/conf/webrtc_ice_server.go | 7 +++-- internal/servers/webrtc/http_server.go | 4 +-- internal/servers/webrtc/server.go | 38 ++++++++++++++------------ internal/servers/webrtc/server_test.go | 37 +++++++++++++++++++++++++ internal/servers/webrtc/session.go | 4 +-- 6 files changed, 75 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 3e80d490305..1174ffa03b2 100644 --- a/README.md +++ b/README.md @@ -1879,6 +1879,16 @@ webrtcICEServers2: where secret is the secret of the TURN server. MediaMTX will generate a set of credentials by using the secret, and credentials will be sent to clients before the WebRTC/ICE connection is established. +In some cases you may want the browser to connect using TURN servers but have mediamtx not using TURN (for example if the TURN server is on the same network as mediamtx). To allow this you can configure the TURN server to be client only: + +```yml +webrtcICEServers2: +- url: turn:host:port + username: user + password: password + clientOnly: true +``` + ### RTSP-specific features #### Transport protocols diff --git a/internal/conf/webrtc_ice_server.go b/internal/conf/webrtc_ice_server.go index f8acc1975f1..a1258f6e5fc 100644 --- a/internal/conf/webrtc_ice_server.go +++ b/internal/conf/webrtc_ice_server.go @@ -2,7 +2,8 @@ package conf // WebRTCICEServer is a WebRTC ICE Server. type WebRTCICEServer struct { - URL string `json:"url"` - Username string `json:"username"` - Password string `json:"password"` + URL string `json:"url"` + Username string `json:"username"` + Password string `json:"password"` + ClientOnly bool `json:"clientOnly"` } diff --git a/internal/servers/webrtc/http_server.go b/internal/servers/webrtc/http_server.go index 95bddf2b483..cd0f15bd2e5 100644 --- a/internal/servers/webrtc/http_server.go +++ b/internal/servers/webrtc/http_server.go @@ -151,7 +151,7 @@ func (s *httpServer) onWHIPOptions(ctx *gin.Context, path string, publish bool) return } - servers, err := s.parent.generateICEServers() + servers, err := s.parent.generateICEServers(true) if err != nil { writeError(ctx, http.StatusInternalServerError, err) return @@ -191,7 +191,7 @@ func (s *httpServer) onWHIPPost(ctx *gin.Context, path string, publish bool) { return } - servers, err := s.parent.generateICEServers() + servers, err := s.parent.generateICEServers(true) if err != nil { writeError(ctx, http.StatusInternalServerError, err) return diff --git a/internal/servers/webrtc/server.go b/internal/servers/webrtc/server.go index 4ac0d7da964..43a05763370 100644 --- a/internal/servers/webrtc/server.go +++ b/internal/servers/webrtc/server.go @@ -429,30 +429,32 @@ func (s *Server) findSessionByUUID(uuid uuid.UUID) *session { return nil } -func (s *Server) generateICEServers() ([]pwebrtc.ICEServer, error) { - ret := make([]pwebrtc.ICEServer, len(s.ICEServers)) +func (s *Server) generateICEServers(clientConfig bool) ([]pwebrtc.ICEServer, error) { + ret := make([]pwebrtc.ICEServer, 0, len(s.ICEServers)) - for i, server := range s.ICEServers { - if server.Username == "AUTH_SECRET" { - expireDate := time.Now().Add(webrtcTurnSecretExpiration).Unix() + for _, server := range s.ICEServers { + if !server.ClientOnly || clientConfig { + if server.Username == "AUTH_SECRET" { + expireDate := time.Now().Add(webrtcTurnSecretExpiration).Unix() - user, err := randomTurnUser() - if err != nil { - return nil, err - } + user, err := randomTurnUser() + if err != nil { + return nil, err + } - server.Username = strconv.FormatInt(expireDate, 10) + ":" + user + server.Username = strconv.FormatInt(expireDate, 10) + ":" + user - h := hmac.New(sha1.New, []byte(server.Password)) - h.Write([]byte(server.Username)) + h := hmac.New(sha1.New, []byte(server.Password)) + h.Write([]byte(server.Username)) - server.Password = base64.StdEncoding.EncodeToString(h.Sum(nil)) - } + server.Password = base64.StdEncoding.EncodeToString(h.Sum(nil)) + } - ret[i] = pwebrtc.ICEServer{ - URLs: []string{server.URL}, - Username: server.Username, - Credential: server.Password, + ret = append(ret, pwebrtc.ICEServer{ + URLs: []string{server.URL}, + Username: server.Username, + Credential: server.Password, + }) } } diff --git a/internal/servers/webrtc/server_test.go b/internal/servers/webrtc/server_test.go index 93b7af19a79..fce4bf78581 100644 --- a/internal/servers/webrtc/server_test.go +++ b/internal/servers/webrtc/server_test.go @@ -408,3 +408,40 @@ func TestServerReadNotFound(t *testing.T) { require.Equal(t, http.StatusNotFound, res.StatusCode) } + +func TestICEServerNoClientOnly(t *testing.T) { + s := &Server{ + ICEServers: []conf.WebRTCICEServer{ + { + URL: "turn:turn.example.com:1234", + Username: "user", + Password: "passwrd", + }, + }, + } + clientICEServers, err := s.generateICEServers(true) + require.NoError(t, err) + require.Equal(t, len(s.ICEServers), len(clientICEServers)) + serverICEServers, err := s.generateICEServers(false) + require.NoError(t, err) + require.Equal(t, len(s.ICEServers), len(serverICEServers)) +} + +func TestICEServerClientOnly(t *testing.T) { + s := &Server{ + ICEServers: []conf.WebRTCICEServer{ + { + URL: "turn:turn.example.com:1234", + Username: "user", + Password: "passwrd", + ClientOnly: true, + }, + }, + } + clientICEServers, err := s.generateICEServers(true) + require.NoError(t, err) + require.Equal(t, len(s.ICEServers), len(clientICEServers)) + serverICEServers, err := s.generateICEServers(false) + require.NoError(t, err) + require.Equal(t, 0, len(serverICEServers)) +} diff --git a/internal/servers/webrtc/session.go b/internal/servers/webrtc/session.go index 96d0f15ea50..cf6cc42ee64 100644 --- a/internal/servers/webrtc/session.go +++ b/internal/servers/webrtc/session.go @@ -393,7 +393,7 @@ func (s *session) runPublish() (int, error) { defer path.RemovePublisher(defs.PathRemovePublisherReq{Author: s}) - iceServers, err := s.parent.generateICEServers() + iceServers, err := s.parent.generateICEServers(false) if err != nil { return http.StatusInternalServerError, err } @@ -528,7 +528,7 @@ func (s *session) runRead() (int, error) { defer path.RemoveReader(defs.PathRemoveReaderReq{Author: s}) - iceServers, err := s.parent.generateICEServers() + iceServers, err := s.parent.generateICEServers(false) if err != nil { return http.StatusInternalServerError, err } From 0c72f7bb08f91285ab41d3bd8f72fed35e483f33 Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Sat, 6 Apr 2024 18:25:06 +0200 Subject: [PATCH 2/2] add clientOnly to configuration file and API docs --- apidocs/openapi.yaml | 2 ++ mediamtx.yml | 1 + 2 files changed, 3 insertions(+) diff --git a/apidocs/openapi.yaml b/apidocs/openapi.yaml index 54c243d0523..26f3973ccc5 100644 --- a/apidocs/openapi.yaml +++ b/apidocs/openapi.yaml @@ -190,6 +190,8 @@ components: type: string password: type: string + clientOnly: + type: boolean # SRT server srt: diff --git a/mediamtx.yml b/mediamtx.yml index b4a8a7dc8ee..f2c53be9f6c 100644 --- a/mediamtx.yml +++ b/mediamtx.yml @@ -311,6 +311,7 @@ webrtcICEServers2: [] # the secret must be inserted into the password field. # username: '' # password: '' + # clientOnly: false ############################################### # Global settings -> SRT server