Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option for ICE servers to be client only #3164

Merged
merged 2 commits into from
Apr 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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()

Check warning on line 438 in internal/servers/webrtc/server.go

View check run for this annotation

Codecov / codecov/patch

internal/servers/webrtc/server.go#L438

Added line #L438 was not covered by tests

user, err := randomTurnUser()
if err != nil {
return nil, err
}
user, err := randomTurnUser()
if err != nil {
return nil, err
}

Check warning on line 443 in internal/servers/webrtc/server.go

View check run for this annotation

Codecov / codecov/patch

internal/servers/webrtc/server.go#L440-L443

Added lines #L440 - L443 were not covered by tests

server.Username = strconv.FormatInt(expireDate, 10) + ":" + user
server.Username = strconv.FormatInt(expireDate, 10) + ":" + user

Check warning on line 445 in internal/servers/webrtc/server.go

View check run for this annotation

Codecov / codecov/patch

internal/servers/webrtc/server.go#L445

Added line #L445 was not covered by tests

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))

Check warning on line 448 in internal/servers/webrtc/server.go

View check run for this annotation

Codecov / codecov/patch

internal/servers/webrtc/server.go#L447-L448

Added lines #L447 - L448 were not covered by tests

server.Password = base64.StdEncoding.EncodeToString(h.Sum(nil))
}
server.Password = base64.StdEncoding.EncodeToString(h.Sum(nil))

Check warning on line 450 in internal/servers/webrtc/server.go

View check run for this annotation

Codecov / codecov/patch

internal/servers/webrtc/server.go#L450

Added line #L450 was not covered by tests
}

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
Loading