diff --git a/README.md b/README.md index 3e80d490..1174ffa0 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 f8acc197..a1258f6e 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 95bddf2b..cd0f15bd 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 4ac0d7da..43a05763 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 93b7af19..fce4bf78 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 96d0f15e..cf6cc42e 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 }