Skip to content

Commit

Permalink
Add option for ICE servers to be client only (#3164)
Browse files Browse the repository at this point in the history
* Add option for ICE servers to be client only

* add clientOnly to configuration file and API docs

---------

Co-authored-by: aler9 <46489434+aler9@users.noreply.github.com>
  • Loading branch information
dbason and aler9 committed Apr 6, 2024
1 parent 60e7d77 commit 87c0535
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 25 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions apidocs/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ components:
type: string
password:
type: string
clientOnly:
type: boolean

# SRT server
srt:
Expand Down
7 changes: 4 additions & 3 deletions internal/conf/webrtc_ice_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}
4 changes: 2 additions & 2 deletions internal/servers/webrtc/http_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
38 changes: 20 additions & 18 deletions internal/servers/webrtc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
}
}

Expand Down
37 changes: 37 additions & 0 deletions internal/servers/webrtc/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
4 changes: 2 additions & 2 deletions internal/servers/webrtc/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down
1 change: 1 addition & 0 deletions mediamtx.yml
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ webrtcICEServers2: []
# the secret must be inserted into the password field.
# username: ''
# password: ''
# clientOnly: false

###############################################
# Global settings -> SRT server
Expand Down

0 comments on commit 87c0535

Please sign in to comment.