From 14ed6799d72c7807467456808aa08a6f376ebe14 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 23 Sep 2014 17:16:13 +0100 Subject: Add support for TURN servers as per the TURN REST API (http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00) --- synapse/rest/__init__.py | 3 ++- synapse/rest/voip.py | 59 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 synapse/rest/voip.py (limited to 'synapse/rest') diff --git a/synapse/rest/__init__.py b/synapse/rest/__init__.py index ed785cfbd5..3b9aa59733 100644 --- a/synapse/rest/__init__.py +++ b/synapse/rest/__init__.py @@ -15,7 +15,7 @@ from . import ( - room, events, register, login, profile, presence, initial_sync, directory + room, events, register, login, profile, presence, initial_sync, directory, voip ) @@ -42,3 +42,4 @@ class RestServletFactory(object): presence.register_servlets(hs, client_resource) initial_sync.register_servlets(hs, client_resource) directory.register_servlets(hs, client_resource) + voip.register_servlets(hs, client_resource) diff --git a/synapse/rest/voip.py b/synapse/rest/voip.py new file mode 100644 index 0000000000..cba9b27e3b --- /dev/null +++ b/synapse/rest/voip.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# Copyright 2014 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from twisted.internet import defer + +from base import RestServlet, client_path_pattern + +from syutil.base64util import encode_base64 + +import hmac +import hashlib + + +class VoipRestServlet(RestServlet): + PATTERN = client_path_pattern("/voip/turnuris$") + + @defer.inlineCallbacks + def on_GET(self, request): + auth_user = yield self.auth.get_user_by_req(request) + + turnUri = self.hs.config.voip.turn_uri + turnSecret = self.hs.config.voip.turn_shared_secret + userLifetime = self.hs.config.voip.turn_user_lifetime + if not turnUri or not turnSecret or not userLifetime: + defer.returnValue( (200, {"uris": []}) ) + + expiry = self.hs.get_clock().time_msec() + userLifetime + username = "%d:%s" % (expiry, auth_user.to_string()) + + mac = hmac.new(turnSecret, msg=username, digestmod=hashlib.sha1) + password = encode_base64(mac.digest()) + + defer.returnValue( (200, { + 'username': username, + 'password': password, + 'ttl': userLifetime / 1000, + 'uris': [ + turnUri, + ] + }) ) + + def on_OPTIONS(self, request): + return (200, {}) + + +def register_servlets(hs, http_server): + VoipRestServlet(hs).register(http_server) -- cgit 1.4.1 From c96ab4fcbb8b7cb61fffe46ef010ea2766d1dc63 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 23 Sep 2014 19:17:24 +0200 Subject: The config is not hierarchical --- synapse/rest/voip.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'synapse/rest') diff --git a/synapse/rest/voip.py b/synapse/rest/voip.py index cba9b27e3b..1989a322cf 100644 --- a/synapse/rest/voip.py +++ b/synapse/rest/voip.py @@ -30,9 +30,9 @@ class VoipRestServlet(RestServlet): def on_GET(self, request): auth_user = yield self.auth.get_user_by_req(request) - turnUri = self.hs.config.voip.turn_uri - turnSecret = self.hs.config.voip.turn_shared_secret - userLifetime = self.hs.config.voip.turn_user_lifetime + turnUri = self.hs.config.turn_uri + turnSecret = self.hs.config.turn_shared_secret + userLifetime = self.hs.config.turn_user_lifetime if not turnUri or not turnSecret or not userLifetime: defer.returnValue( (200, {"uris": []}) ) -- cgit 1.4.1 From b42b0d3fe527313f7885ec3ac5e582a59c2c07fb Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 24 Sep 2014 15:29:24 +0200 Subject: Use standard base64 encoding with padding to get the same result as coturn. --- synapse/rest/voip.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'synapse/rest') diff --git a/synapse/rest/voip.py b/synapse/rest/voip.py index 1989a322cf..bb0108cbd1 100644 --- a/synapse/rest/voip.py +++ b/synapse/rest/voip.py @@ -17,10 +17,10 @@ from twisted.internet import defer from base import RestServlet, client_path_pattern -from syutil.base64util import encode_base64 import hmac import hashlib +import base64 class VoipRestServlet(RestServlet): @@ -40,7 +40,10 @@ class VoipRestServlet(RestServlet): username = "%d:%s" % (expiry, auth_user.to_string()) mac = hmac.new(turnSecret, msg=username, digestmod=hashlib.sha1) - password = encode_base64(mac.digest()) + # We need to use standard base64 encoding here, *not* syutil's encode_base64 + # because we need to add the standard padding to get the same result as the + # TURN server. + password = base64.b64encode(mac.digest()) defer.returnValue( (200, { 'username': username, -- cgit 1.4.1 From 5383ba55870079076277ee6e83458f6cd7ceee85 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 24 Sep 2014 16:01:36 +0100 Subject: rename endpoint to better reflect what it is and allow specifying multiple uris --- synapse/config/voip.py | 6 +++--- synapse/rest/voip.py | 8 +++----- 2 files changed, 6 insertions(+), 8 deletions(-) (limited to 'synapse/rest') diff --git a/synapse/config/voip.py b/synapse/config/voip.py index a47e81037a..3a211ae6b6 100644 --- a/synapse/config/voip.py +++ b/synapse/config/voip.py @@ -19,7 +19,7 @@ class VoipConfig(Config): def __init__(self, args): super(VoipConfig, self).__init__(args) - self.turn_uri = args.turn_uri + self.turn_uris = args.turn_uris.split(",") self.turn_shared_secret = args.turn_shared_secret self.turn_user_lifetime = args.turn_user_lifetime @@ -28,8 +28,8 @@ class VoipConfig(Config): super(VoipConfig, cls).add_arguments(parser) group = parser.add_argument_group("voip") group.add_argument( - "--turn-uri", type=str, default=None, - help="The public URI of the TURN server to give to clients" + "--turn-uris", type=str, default=None, + help="The public URIs of the TURN server to give to clients" ) group.add_argument( "--turn-shared-secret", type=str, default=None, diff --git a/synapse/rest/voip.py b/synapse/rest/voip.py index bb0108cbd1..31f3fd100d 100644 --- a/synapse/rest/voip.py +++ b/synapse/rest/voip.py @@ -24,13 +24,13 @@ import base64 class VoipRestServlet(RestServlet): - PATTERN = client_path_pattern("/voip/turnuris$") + PATTERN = client_path_pattern("/voip/turnServers$") @defer.inlineCallbacks def on_GET(self, request): auth_user = yield self.auth.get_user_by_req(request) - turnUri = self.hs.config.turn_uri + turnUris = self.hs.config.turn_uris turnSecret = self.hs.config.turn_shared_secret userLifetime = self.hs.config.turn_user_lifetime if not turnUri or not turnSecret or not userLifetime: @@ -49,9 +49,7 @@ class VoipRestServlet(RestServlet): 'username': username, 'password': password, 'ttl': userLifetime / 1000, - 'uris': [ - turnUri, - ] + 'uris': turnUris, }) ) def on_OPTIONS(self, request): -- cgit 1.4.1 From 455365113878632774d12039cb6ab362d6d16416 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 24 Sep 2014 17:04:33 +0200 Subject: Oops --- synapse/rest/voip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'synapse/rest') diff --git a/synapse/rest/voip.py b/synapse/rest/voip.py index 31f3fd100d..a3a8842cb6 100644 --- a/synapse/rest/voip.py +++ b/synapse/rest/voip.py @@ -33,7 +33,7 @@ class VoipRestServlet(RestServlet): turnUris = self.hs.config.turn_uris turnSecret = self.hs.config.turn_shared_secret userLifetime = self.hs.config.turn_user_lifetime - if not turnUri or not turnSecret or not userLifetime: + if not turnUris or not turnSecret or not userLifetime: defer.returnValue( (200, {"uris": []}) ) expiry = self.hs.get_clock().time_msec() + userLifetime -- cgit 1.4.1 From 7dc7c53029fccbccf291ca4c299fccfdeb8e19fb Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 24 Sep 2014 17:28:47 +0200 Subject: The REST API spec only alows for returning a single server so name the endpoint appropriately. --- synapse/rest/voip.py | 2 +- webclient/components/matrix/matrix-service.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'synapse/rest') diff --git a/synapse/rest/voip.py b/synapse/rest/voip.py index a3a8842cb6..7260ff0e8e 100644 --- a/synapse/rest/voip.py +++ b/synapse/rest/voip.py @@ -24,7 +24,7 @@ import base64 class VoipRestServlet(RestServlet): - PATTERN = client_path_pattern("/voip/turnServers$") + PATTERN = client_path_pattern("/voip/turnServer$") @defer.inlineCallbacks def on_GET(self, request): diff --git a/webclient/components/matrix/matrix-service.js b/webclient/components/matrix/matrix-service.js index 69e6caccd3..cb827a0b4d 100644 --- a/webclient/components/matrix/matrix-service.js +++ b/webclient/components/matrix/matrix-service.js @@ -765,7 +765,7 @@ angular.module('matrixService', []) }, getTurnServer: function() { - return doRequest("GET", "/voip/turnServers"); + return doRequest("GET", "/voip/turnServer"); } }; -- cgit 1.4.1 From a31bf7777694d794d8e861c2bfede4a8ebb8849e Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 25 Sep 2014 11:24:49 +0200 Subject: Make turn server endpoint return an empty object if no turn servers to match the normal response. Don't break if the turn_uris option isn't present. --- synapse/config/voip.py | 2 +- synapse/rest/voip.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'synapse/rest') diff --git a/synapse/config/voip.py b/synapse/config/voip.py index 3a211ae6b6..c5131d9bcd 100644 --- a/synapse/config/voip.py +++ b/synapse/config/voip.py @@ -19,7 +19,7 @@ class VoipConfig(Config): def __init__(self, args): super(VoipConfig, self).__init__(args) - self.turn_uris = args.turn_uris.split(",") + self.turn_uris = args.turn_uris.split(",") if args.turn_uris else None self.turn_shared_secret = args.turn_shared_secret self.turn_user_lifetime = args.turn_user_lifetime diff --git a/synapse/rest/voip.py b/synapse/rest/voip.py index 7260ff0e8e..2e4627606f 100644 --- a/synapse/rest/voip.py +++ b/synapse/rest/voip.py @@ -34,7 +34,7 @@ class VoipRestServlet(RestServlet): turnSecret = self.hs.config.turn_shared_secret userLifetime = self.hs.config.turn_user_lifetime if not turnUris or not turnSecret or not userLifetime: - defer.returnValue( (200, {"uris": []}) ) + defer.returnValue( (200, {}) ) expiry = self.hs.get_clock().time_msec() + userLifetime username = "%d:%s" % (expiry, auth_user.to_string()) -- cgit 1.4.1