Bug 1297150: Use ICE server for ICE and TURN unit tests draft
authorNils Ohlmeier [:drno] <drno@ohlmeier.org>
Sat, 20 Aug 2016 23:04:02 -0700
changeset 412709 62889de075a8e1eba893b8b2d2e3b40b0965835c
parent 411277 ab70808cd4b6c6ad9a57a9f71cfa495fcea0aecd
child 531058 946d0c66a46bc338df8c4a23a781a031fe086573
push id29247
push userdrno@ohlmeier.org
push dateMon, 12 Sep 2016 18:40:11 +0000
bugs1297150
milestone51.0a1
Bug 1297150: Use ICE server for ICE and TURN unit tests MozReview-Commit-ID: L8ZykPKoWLh
media/mtransport/test/gtest_utils.h
media/mtransport/test/ice_unittest.cpp
media/mtransport/test/turn_unittest.cpp
python/mozbuild/mozbuild/mach_commands.py
testing/tools/iceserver/iceserver.py
--- a/media/mtransport/test/gtest_utils.h
+++ b/media/mtransport/test/gtest_utils.h
@@ -95,56 +95,63 @@ extern "C" {
 #define ASSERT_EQ_WAIT(expected, actual, timeout) \
   do { \
     WAIT(expected == actual, timeout); \
     ASSERT_EQ(expected, actual); \
   } while(0)
 
 using test::RingbufferDumper;
 
+const std::string kDefaultStunServerHostname(
+    (char *)"localhost");
+const std::string kDefaultStunServerAddress(
+    (char *)"10.252.26.185");
+const uint16_t kDefaultStunServerPort=3478;
+const uint16_t kDefaultStunServerReflexivePort=3479;
+const std::string kBogusStunServerHostname(
+    (char *)"stun-server-nonexistent.invalid");
+
+const std::string kDefaultTurnServerHostname(
+    (char *)"localhost");
+const std::string kDefaultTurnServerAddress(
+    (char *)"10.252.26.185");
+const uint16_t kDefaultTurnServerPort=3478;
+const std::string kDefaultTurnUsername(
+    (char *)"foo");
+const std::string kDefaultTurnPassword(
+    (char *)"bar");
+
 class MtransportTest : public ::testing::Test {
 public:
   MtransportTest()
     : test_utils_(nullptr)
     , dumper_(nullptr)
   {
+    stun_server_hostname_ = kDefaultStunServerHostname;
+    stun_server_address_ = kDefaultStunServerAddress;
+    turn_server_address_ = kDefaultTurnServerAddress;
+    turn_user_ = kDefaultTurnUsername;
+    turn_password_ = kDefaultTurnPassword;
   }
 
   void SetUp() override {
     test_utils_ = new MtransportTestUtils();
     NSS_NoDB_Init(nullptr);
     NSS_SetDomesticPolicy();
 
     NR_reg_init(NR_REG_MODE_LOCAL);
 
     // Attempt to load env vars used by tests.
-    GetEnvironment("TURN_SERVER_ADDRESS", turn_server_);
+    GetEnvironment("TURN_SERVER_ADDRESS", turn_server_address_);
+    GetEnvironment("TURN_SERVER_HOSTNAME", turn_server_hostname_);
     GetEnvironment("TURN_SERVER_USER", turn_user_);
     GetEnvironment("TURN_SERVER_PASSWORD", turn_password_);
     GetEnvironment("STUN_SERVER_ADDRESS", stun_server_address_);
     GetEnvironment("STUN_SERVER_HOSTNAME", stun_server_hostname_);
 
-    std::string disable_non_local;
-    GetEnvironment("MOZ_DISABLE_NONLOCAL_CONNECTIONS", disable_non_local);
-    std::string upload_dir;
-    GetEnvironment("MOZ_UPLOAD_DIR", upload_dir);
-
-    if ((!disable_non_local.empty() && disable_non_local != "0") ||
-        !upload_dir.empty()) {
-      // We're assuming that MOZ_UPLOAD_DIR is only set on tbpl;
-      // MOZ_DISABLE_NONLOCAL_CONNECTIONS probably should be set when running the
-      // cpp unit-tests, but is not presently.
-      stun_server_address_ = "";
-      stun_server_hostname_ = "";
-      turn_server_ = "";
-    }
-
-    // Some tests are flaky and need to check if they're supposed to run.
-    webrtc_enabled_ = CheckEnvironmentFlag("MOZ_WEBRTC_TESTS");
-
     ::testing::TestEventListeners& listeners =
         ::testing::UnitTest::GetInstance()->listeners();
 
     dumper_ = new RingbufferDumper(test_utils_);
     listeners.Append(dumper_);
   }
 
   void TearDown() override {
@@ -155,49 +162,19 @@ public:
 
   void GetEnvironment(const char* aVar, std::string& out) {
     char* value = getenv(aVar);
     if (value) {
       out = value;
     }
   }
 
-  bool CheckEnvironmentFlag(const char* aVar) {
-    std::string value;
-    GetEnvironment(aVar, value);
-    return value == "1";
-  }
-
-  bool WarnIfTurnNotConfigured() const {
-    bool configured =
-        !turn_server_.empty() &&
-        !turn_user_.empty() &&
-        !turn_password_.empty();
-
-    if (configured) {
-      nr_transport_addr addr;
-      if (nr_str_port_to_transport_addr(turn_server_.c_str(), 3478,
-                                        IPPROTO_UDP, &addr)) {
-        printf("Invalid TURN_SERVER_ADDRESS \"%s\". Only IP numbers supported.\n",
-               turn_server_.c_str());
-        configured = false;
-      }
-    } else {
-      printf(
-        "Set TURN_SERVER_ADDRESS, TURN_SERVER_USER, and TURN_SERVER_PASSWORD\n"
-        "environment variables to run this test\n");
-    }
-
-    return !configured;
-  }
-
   MtransportTestUtils* test_utils_;
   RingbufferDumper* dumper_;
 
-  std::string turn_server_;
+  std::string turn_server_address_;
+  std::string turn_server_hostname_;
   std::string turn_user_;
   std::string turn_password_;
   std::string stun_server_address_;
   std::string stun_server_hostname_;
-
-  bool webrtc_enabled_;
 };
 #endif
--- a/media/mtransport/test/ice_unittest.cpp
+++ b/media/mtransport/test/ice_unittest.cpp
@@ -55,23 +55,16 @@ extern "C" {
 #include "gtest/gtest.h"
 #include "gtest_utils.h"
 
 
 using namespace mozilla;
 
 static unsigned int kDefaultTimeout = 7000;
 
-//TODO(nils@mozilla.com): This should get replaced with some non-external
-//solution like discussed in bug 860775.
-const std::string kDefaultStunServerHostname(
-    (char *)"global.stun.twilio.com");
-const std::string kBogusStunServerHostname(
-    (char *)"stun-server-nonexistent.invalid");
-const uint16_t kDefaultStunServerPort=3478;
 const std::string kBogusIceCandidate(
     (char *)"candidate:0 2 UDP 2113601790 192.168.178.20 50769 typ");
 
 const std::string kUnreachableHostIceCandidate(
     (char *)"candidate:0 1 UDP 2113601790 192.168.178.20 50769 typ host");
 
 namespace {
 
@@ -119,27 +112,28 @@ Resolve(const std::string& fqdn, int add
 
   freeaddrinfo(res);
   return str_addr;
 }
 
 class StunTest : public MtransportTest {
 public:
   StunTest() : MtransportTest() {
-    stun_server_hostname_ = kDefaultStunServerHostname;
   }
 
   void SetUp() override {
     MtransportTest::SetUp();
 
     // If only a STUN server FQDN was provided, look up its IP address for the
     // address-only tests.
+    /*
     if (stun_server_address_.empty() && !stun_server_hostname_.empty()) {
       stun_server_address_ = Resolve(stun_server_hostname_, AF_INET);
     }
+    */
 
     // Make sure NrIceCtx is in a testable state.
     test_utils_->sts_target()->Dispatch(
         WrapRunnableNM(&NrIceCtx::internal_DeinitializeGlobal),
         NS_DISPATCH_SYNC);
 
     // NB: NrIceCtx::internal_DeinitializeGlobal destroys the RLogRingBuffer
     // singleton.
@@ -1654,25 +1648,28 @@ class WebRtcIceConnectTest : public Stun
   }
 
   void InitPeer(IceTestPeer* peer, bool setup_stun_servers = true) {
     if (use_nat_) {
       // If we enable nat simulation, but still use a real STUN server somewhere
       // on the internet, we will see failures if there is a real NAT in
       // addition to our simulated one, particularly if it disallows
       // hairpinning.
+      /*
       if (setup_stun_servers) {
         InitTestStunServer();
         peer->UseTestStunServer();
       }
+      */
       peer->UseNat();
       peer->SetFilteringType(filtering_type_);
       peer->SetMappingType(mapping_type_);
       peer->SetBlockUdp(block_udp_);
-    } else if (setup_stun_servers) {
+    }
+    if (setup_stun_servers) {
       std::vector<NrIceStunServer> stun_servers;
 
       stun_servers.push_back(*NrIceStunServer::Create(stun_server_address_,
                                                       kDefaultStunServerPort, kNrIceTransportUdp));
       stun_servers.push_back(*NrIceStunServer::Create(stun_server_address_,
                                                       kDefaultStunServerPort, kNrIceTransportTcp));
 
       peer->SetStunServers(stun_servers);
@@ -2176,17 +2173,17 @@ TEST_F(WebRtcIceGatherTest, TestGatherFa
 }
 
 TEST_F(WebRtcIceGatherTest, TestGatherDNSStunServerIpAddress) {
   if (stun_server_address_.empty()) {
     return;
   }
 
   EnsurePeer();
-  peer_->SetStunServer(stun_server_address_, kDefaultStunServerPort);
+  peer_->SetStunServer(stun_server_address_, kDefaultStunServerReflexivePort);
   peer_->SetDNSResolver();
   Gather();
   ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP "));
   ASSERT_TRUE(StreamHasMatchingCandidate(0, "typ srflx raddr"));
 }
 
 TEST_F(WebRtcIceGatherTest, TestGatherDNSStunServerIpAddressTcp) {
   if (stun_server_address_.empty()) {
@@ -2206,24 +2203,28 @@ TEST_F(WebRtcIceGatherTest, TestGatherDN
 }
 
 TEST_F(WebRtcIceGatherTest, TestGatherDNSStunServerHostname) {
   if (stun_server_hostname_.empty()) {
     return;
   }
 
   EnsurePeer();
-  peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort);
+  peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerReflexivePort);
   peer_->SetDNSResolver();
   Gather();
   ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP "));
   ASSERT_TRUE(StreamHasMatchingCandidate(0, "typ srflx raddr"));
 }
 
 TEST_F(WebRtcIceGatherTest, TestGatherDNSStunServerHostnameTcp) {
+  if (stun_server_hostname_.empty()) {
+    return;
+  }
+
   EnsurePeer(ICE_TEST_PEER_OFFERER | ICE_TEST_PEER_ENABLED_TCP);
   peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort,
     kNrIceTransportTcp);
   peer_->SetDNSResolver();
   Gather();
   ASSERT_TRUE(StreamHasMatchingCandidate(0, "tcptype passive"));
   ASSERT_FALSE(StreamHasMatchingCandidate(0, "tcptype passive", " 9 "));
   ASSERT_TRUE(StreamHasMatchingCandidate(0, "tcptype so"));
@@ -2283,47 +2284,39 @@ TEST_F(WebRtcIceGatherTest, TestGatherDN
     kNrIceTransportTcp);
   peer_->SetDNSResolver();
   Gather();
   ASSERT_TRUE(StreamHasMatchingCandidate(0, " TCP "));
 }
 
 TEST_F(WebRtcIceGatherTest, TestDefaultCandidate) {
   EnsurePeer();
-  peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort);
+  peer_->SetStunServer(stun_server_address_, kDefaultStunServerPort);
   Gather();
   NrIceCandidate default_candidate;
   ASSERT_TRUE(NS_SUCCEEDED(peer_->GetDefaultCandidate(0, &default_candidate)));
 }
 
 TEST_F(WebRtcIceGatherTest, TestGatherTurn) {
   EnsurePeer();
-  if (turn_server_.empty())
-    return;
-  peer_->SetTurnServer(turn_server_, kDefaultStunServerPort,
+  peer_->SetTurnServer(turn_server_address_, kDefaultTurnServerPort,
                        turn_user_, turn_password_, kNrIceTransportUdp);
   Gather();
 }
 
 TEST_F(WebRtcIceGatherTest, TestGatherTurnTcp) {
   EnsurePeer();
-  if (turn_server_.empty())
-    return;
-  peer_->SetTurnServer(turn_server_, kDefaultStunServerPort,
+  peer_->SetTurnServer(turn_server_address_, kDefaultTurnServerPort,
                        turn_user_, turn_password_, kNrIceTransportTcp);
   Gather();
 }
 
 TEST_F(WebRtcIceGatherTest, TestGatherDisableComponent) {
-  if (stun_server_hostname_.empty()) {
-    return;
-  }
-
   EnsurePeer();
-  peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort);
+  peer_->SetStunServer(stun_server_address_, kDefaultStunServerPort);
   peer_->AddStream(2);
   peer_->DisableComponent(1, 2);
   Gather();
   std::vector<std::string> candidates =
     peer_->GetCandidates(1);
 
   for (size_t i=0; i<candidates.size(); ++i) {
     size_t sp1 = candidates[i].find(' ');
@@ -2854,69 +2847,60 @@ TEST_F(WebRtcIceConnectTest, TestGatherS
   UseNat();
   SetFilteringType(TestNat::PORT_DEPENDENT);
   SetMappingType(TestNat::PORT_DEPENDENT);
   AddStream(1);
   ASSERT_TRUE(Gather());
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectSymmetricNat) {
-  if (turn_server_.empty())
-    return;
-
   UseNat();
   SetFilteringType(TestNat::PORT_DEPENDENT);
   SetMappingType(TestNat::PORT_DEPENDENT);
   AddStream(1);
   p1_->SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
                         NrIceCandidate::Type::ICE_RELAYED);
   p2_->SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
                         NrIceCandidate::Type::ICE_RELAYED);
-  SetTurnServer(turn_server_, kDefaultStunServerPort,
+  SetTurnServer(turn_server_address_, kDefaultTurnServerPort,
                 turn_user_, turn_password_);
   ASSERT_TRUE(Gather());
   Connect();
 }
 
 TEST_F(WebRtcIceConnectTest, TestGatherNatBlocksUDP) {
-  if (turn_server_.empty())
-    return;
-
   UseNat();
   BlockUdp();
   AddStream(1);
   std::vector<NrIceTurnServer> turn_servers;
   std::vector<unsigned char> password_vec(turn_password_.begin(),
                                           turn_password_.end());
   turn_servers.push_back(
-      *NrIceTurnServer::Create(turn_server_, kDefaultStunServerPort,
+      *NrIceTurnServer::Create(turn_server_address_, kDefaultTurnServerPort,
                                turn_user_, password_vec, kNrIceTransportTcp));
   turn_servers.push_back(
-      *NrIceTurnServer::Create(turn_server_, kDefaultStunServerPort,
+      *NrIceTurnServer::Create(turn_server_address_, kDefaultTurnServerPort,
                                turn_user_, password_vec, kNrIceTransportUdp));
   SetTurnServers(turn_servers);
   // We have to wait for the UDP-based stuff to time out.
   ASSERT_TRUE(Gather(kDefaultTimeout * 3));
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectNatBlocksUDP) {
-  if (turn_server_.empty())
-    return;
-
   UseNat();
   BlockUdp();
   AddStream(1);
   std::vector<NrIceTurnServer> turn_servers;
   std::vector<unsigned char> password_vec(turn_password_.begin(),
                                           turn_password_.end());
   turn_servers.push_back(
-      *NrIceTurnServer::Create(turn_server_, kDefaultStunServerPort,
+      *NrIceTurnServer::Create(turn_server_address_, kDefaultTurnServerPort,
                                turn_user_, password_vec, kNrIceTransportTcp));
   turn_servers.push_back(
-      *NrIceTurnServer::Create(turn_server_, kDefaultStunServerPort,
+      *NrIceTurnServer::Create(turn_server_address_, kDefaultTurnServerPort,
                                turn_user_, password_vec, kNrIceTransportUdp));
   SetTurnServers(turn_servers);
   p1_->SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
                         NrIceCandidate::Type::ICE_RELAYED,
                         kNrIceTransportTcp);
   p2_->SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
                         NrIceCandidate::Type::ICE_RELAYED,
                         kNrIceTransportTcp);
@@ -3300,179 +3284,146 @@ TEST_F(WebRtcIceConnectTest, TestConsent
   p1_->SetStunResponseDelay(300);
   p2_->SetStunResponseDelay(300);
   PR_Sleep(1000);
   AssertConsentRefresh();
   SendReceive();
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectTurn) {
-  if (turn_server_.empty())
-    return;
-
   AddStream(1);
-  SetTurnServer(turn_server_, kDefaultStunServerPort,
+  SetTurnServer(turn_server_address_, kDefaultTurnServerPort,
                 turn_user_, turn_password_);
   ASSERT_TRUE(Gather());
   Connect();
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectTurnWithDelay) {
-  if (turn_server_.empty())
-    return;
-
   AddStream(1);
-  SetTurnServer(turn_server_, kDefaultStunServerPort,
+  SetTurnServer(turn_server_address_, kDefaultTurnServerPort,
                 turn_user_, turn_password_);
   SetCandidateFilter(SabotageHostCandidateAndDropReflexive);
   p1_->Gather();
   PR_Sleep(500);
   p2_->Gather();
   ConnectTrickle(TRICKLE_REAL);
   WaitForGather();
   WaitForComplete();
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectTurnWithNormalTrickleDelay) {
-  if (turn_server_.empty())
-    return;
-
   AddStream(1);
-  SetTurnServer(turn_server_, kDefaultStunServerPort,
+  SetTurnServer(turn_server_address_, kDefaultTurnServerPort,
                 turn_user_, turn_password_);
   ASSERT_TRUE(Gather());
   ConnectTrickle();
   RealisticTrickleDelay(p1_->ControlTrickle(0));
   RealisticTrickleDelay(p2_->ControlTrickle(0));
 
   ASSERT_TRUE_WAIT(p1_->ice_complete(), kDefaultTimeout);
   ASSERT_TRUE_WAIT(p2_->ice_complete(), kDefaultTimeout);
   AssertCheckingReached();
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectTurnWithNormalTrickleDelayOneSided) {
-  if (turn_server_.empty())
-    return;
-
   AddStream(1);
-  SetTurnServer(turn_server_, kDefaultStunServerPort,
+  SetTurnServer(turn_server_address_, kDefaultTurnServerPort,
                 turn_user_, turn_password_);
   ASSERT_TRUE(Gather());
   ConnectTrickle();
   RealisticTrickleDelay(p1_->ControlTrickle(0));
   p2_->SimulateTrickle(0);
 
   ASSERT_TRUE_WAIT(p1_->ice_complete(), kDefaultTimeout);
   ASSERT_TRUE_WAIT(p2_->ice_complete(), kDefaultTimeout);
   AssertCheckingReached();
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectTurnWithLargeTrickleDelay) {
-  if (turn_server_.empty())
-    return;
-
   AddStream(1);
-  SetTurnServer(turn_server_, kDefaultStunServerPort,
+  SetTurnServer(turn_server_address_, kDefaultTurnServerPort,
                 turn_user_, turn_password_);
   SetCandidateFilter(SabotageHostCandidateAndDropReflexive);
   ASSERT_TRUE(Gather());
   ConnectTrickle();
   // Trickle host candidates immediately, but delay relay candidates
   DelayRelayCandidates(p1_->ControlTrickle(0), 3700);
   DelayRelayCandidates(p2_->ControlTrickle(0), 3700);
 
   ASSERT_TRUE_WAIT(p1_->ice_complete(), kDefaultTimeout);
   ASSERT_TRUE_WAIT(p2_->ice_complete(), kDefaultTimeout);
   AssertCheckingReached();
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectTurnTcp) {
-  if (turn_server_.empty())
-    return;
-
   AddStream(1);
-  SetTurnServer(turn_server_, kDefaultStunServerPort,
+  SetTurnServer(turn_server_address_, kDefaultTurnServerPort,
                 turn_user_, turn_password_, kNrIceTransportTcp);
   ASSERT_TRUE(Gather());
   Connect();
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectTurnOnly) {
-  if (turn_server_.empty())
-    return;
-
   AddStream(1);
-  SetTurnServer(turn_server_, kDefaultStunServerPort,
+  SetTurnServer(turn_server_address_, kDefaultTurnServerPort,
                 turn_user_, turn_password_);
   ASSERT_TRUE(Gather());
   SetCandidateFilter(IsRelayCandidate);
   SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
                    NrIceCandidate::Type::ICE_RELAYED);
   Connect();
 }
 
 TEST_F(WebRtcIceConnectTest, TestConnectTurnTcpOnly) {
-  if (turn_server_.empty())
-    return;
-
   AddStream(1);
-  SetTurnServer(turn_server_, kDefaultStunServerPort,
+  SetTurnServer(turn_server_address_, kDefaultTurnServerPort,
                 turn_user_, turn_password_, kNrIceTransportTcp);
   ASSERT_TRUE(Gather());
   SetCandidateFilter(IsRelayCandidate);
   SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
                    NrIceCandidate::Type::ICE_RELAYED,
                    kNrIceTransportTcp);
   Connect();
 }
 
 TEST_F(WebRtcIceConnectTest, TestSendReceiveTurnOnly) {
-  if (turn_server_.empty())
-    return;
-
   AddStream(1);
-  SetTurnServer(turn_server_, kDefaultStunServerPort,
+  SetTurnServer(turn_server_address_, kDefaultTurnServerPort,
                 turn_user_, turn_password_);
   ASSERT_TRUE(Gather());
   SetCandidateFilter(IsRelayCandidate);
   SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
                    NrIceCandidate::Type::ICE_RELAYED);
   Connect();
   SendReceive();
 }
 
 TEST_F(WebRtcIceConnectTest, TestSendReceiveTurnTcpOnly) {
-  if (turn_server_.empty())
-    return;
-
   AddStream(1);
-  SetTurnServer(turn_server_, kDefaultStunServerPort,
+  SetTurnServer(turn_server_address_, kDefaultTurnServerPort,
                 turn_user_, turn_password_, kNrIceTransportTcp);
   ASSERT_TRUE(Gather());
   SetCandidateFilter(IsRelayCandidate);
   SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
                    NrIceCandidate::Type::ICE_RELAYED,
                    kNrIceTransportTcp);
   Connect();
   SendReceive();
 }
 
 TEST_F(WebRtcIceConnectTest, TestSendReceiveTurnBothOnly) {
-  if (turn_server_.empty())
-    return;
-
   AddStream(1);
   std::vector<NrIceTurnServer> turn_servers;
   std::vector<unsigned char> password_vec(turn_password_.begin(),
                                           turn_password_.end());
   turn_servers.push_back(*NrIceTurnServer::Create(
-                           turn_server_, kDefaultStunServerPort,
+                           turn_server_address_, kDefaultTurnServerPort,
                            turn_user_, password_vec, kNrIceTransportTcp));
   turn_servers.push_back(*NrIceTurnServer::Create(
-                           turn_server_, kDefaultStunServerPort,
+                           turn_server_address_, kDefaultTurnServerPort,
                            turn_user_, password_vec, kNrIceTransportUdp));
   SetTurnServers(turn_servers);
   ASSERT_TRUE(Gather());
   SetCandidateFilter(IsRelayCandidate);
   // UDP is preferred.
   SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
                    NrIceCandidate::Type::ICE_RELAYED,
                    kNrIceTransportUdp);
--- a/media/mtransport/test/turn_unittest.cpp
+++ b/media/mtransport/test/turn_unittest.cpp
@@ -79,35 +79,37 @@ extern "C" {
 #include "nricemediastream.h"
 #include "nricectxhandler.h"
 
 
 using namespace mozilla;
 
 static std::string kDummyTurnServer("192.0.2.1");  // From RFC 5737
 
-class TurnClient : public MtransportTest {
+class WebRtcTurn: public MtransportTest {
  public:
-  TurnClient()
+  WebRtcTurn()
       : MtransportTest(),
         real_socket_(nullptr),
         net_socket_(nullptr),
         buffered_socket_(nullptr),
         net_fd_(nullptr),
         turn_ctx_(nullptr),
         allocated_(false),
         received_(0),
         protocol_(IPPROTO_UDP) {
   }
 
-  ~TurnClient() {
+  ~WebRtcTurn() {
   }
 
   static void SetUpTestCase() {
-    NrIceCtx::InitializeGlobals(false, false, false);
+    NrIceCtx::InitializeGlobals(true,    // loopback
+                                false,   // tcp
+                                false);  // link local
   }
 
   void SetTcp() {
     protocol_ = IPPROTO_TCP;
   }
 
   void Init_s() {
     int r;
@@ -123,18 +125,18 @@ class TurnClient : public MtransportTest
           nr_socket_buffered_stun_create(real_socket_, 100000, TURN_TCP_FRAMING,
                                          &buffered_socket_);
       ASSERT_EQ(0, r);
       net_socket_ = buffered_socket_;
     } else {
       net_socket_ = real_socket_;
     }
 
-    r = nr_str_port_to_transport_addr(turn_server_.c_str(), 3478,
-      protocol_, &addr);
+    r = nr_str_port_to_transport_addr(turn_server_address_.c_str(),
+      kDefaultTurnServerPort, protocol_, &addr);
     ASSERT_EQ(0, r);
 
     std::vector<unsigned char> password_vec(
         turn_password_.begin(), turn_password_.end());
     Data password;
     INIT_DATA(password, &password_vec[0], password_vec.size());
     r = nr_turn_client_ctx_create("test", net_socket_,
                                   turn_user_.c_str(),
@@ -155,33 +157,33 @@ class TurnClient : public MtransportTest
       NR_ASYNC_CANCEL(net_fd_, NR_ASYNC_WAIT_READ);
     }
 
     nr_socket_destroy(&buffered_socket_);
   }
 
   void TearDown() {
     RUN_ON_THREAD(test_utils_->sts_target(),
-                  WrapRunnable(this, &TurnClient::TearDown_s),
+                  WrapRunnable(this, &WebRtcTurn::TearDown_s),
                   NS_DISPATCH_SYNC);
   }
 
   void Allocate_s() {
     Init_s();
     ASSERT_TRUE(turn_ctx_);
 
     int r = nr_turn_client_allocate(turn_ctx_,
                                     allocate_success_cb,
                                     this);
     ASSERT_EQ(0, r);
   }
 
   void Allocate(bool expect_success=true) {
     RUN_ON_THREAD(test_utils_->sts_target(),
-                  WrapRunnable(this, &TurnClient::Allocate_s),
+                  WrapRunnable(this, &WebRtcTurn::Allocate_s),
                   NS_DISPATCH_SYNC);
 
     if (expect_success) {
       ASSERT_TRUE_WAIT(allocated_, 5000);
     }
     else {
       PR_Sleep(10000);
       ASSERT_FALSE(allocated_);
@@ -211,17 +213,17 @@ class TurnClient : public MtransportTest
 
     std::cerr << "De-Allocating..." << std::endl;
     int r = nr_turn_client_deallocate(turn_ctx_);
     ASSERT_EQ(0, r);
   }
 
   void Deallocate() {
     RUN_ON_THREAD(test_utils_->sts_target(),
-                  WrapRunnable(this, &TurnClient::Deallocate_s),
+                  WrapRunnable(this, &WebRtcTurn::Deallocate_s),
                   NS_DISPATCH_SYNC);
   }
 
   void RequestPermission_s(const std::string& target) {
     nr_transport_addr addr;
     int r;
 
     // Expected pattern here is "IP4:127.0.0.1:3487"
@@ -240,17 +242,17 @@ class TurnClient : public MtransportTest
     ASSERT_EQ(0, r);
 
     r = nr_turn_client_ensure_perm(turn_ctx_, &addr);
     ASSERT_EQ(0, r);
   }
 
   void RequestPermission(const std::string& target) {
     RUN_ON_THREAD(test_utils_->sts_target(),
-                  WrapRunnable(this, &TurnClient::RequestPermission_s, target),
+                  WrapRunnable(this, &WebRtcTurn::RequestPermission_s, target),
                   NS_DISPATCH_SYNC);
 
   }
 
   void Readable(NR_SOCKET s, int how, void *arg) {
     // Re-arm
     std::cerr << "Socket is readable" << std::endl;
     NR_ASYNC_WAIT(s, how, socket_readable_cb, arg);
@@ -338,96 +340,77 @@ class TurnClient : public MtransportTest
                                             &addr);
     if (expect_return >= 0) {
       ASSERT_EQ(expect_return, r);
     }
   }
 
   void SendTo(const std::string& target, int expect_return=0) {
     RUN_ON_THREAD(test_utils_->sts_target(),
-                  WrapRunnable(this, &TurnClient::SendTo_s, target,
+                  WrapRunnable(this, &WebRtcTurn::SendTo_s, target,
                                expect_return),
                   NS_DISPATCH_SYNC);
   }
 
   int received() const { return received_; }
 
   static void socket_readable_cb(NR_SOCKET s, int how, void *arg) {
-    static_cast<TurnClient *>(arg)->Readable(s, how, arg);
+    static_cast<WebRtcTurn*>(arg)->Readable(s, how, arg);
   }
 
   static void allocate_success_cb(NR_SOCKET s, int how, void *arg){
-    static_cast<TurnClient *>(arg)->Allocated();
+    static_cast<WebRtcTurn*>(arg)->Allocated();
   }
 
  protected:
-  std::string turn_server_;
   nr_socket *real_socket_;
   nr_socket *net_socket_;
   nr_socket *buffered_socket_;
   NR_SOCKET net_fd_;
   nr_turn_client_ctx *turn_ctx_;
   std::string relay_addr_;
   bool allocated_;
   int received_;
   int protocol_;
 };
 
-TEST_F(TurnClient, Allocate) {
-  if (WarnIfTurnNotConfigured())
-    return;
-
+TEST_F(WebRtcTurn, Allocate) {
   Allocate();
 }
 
-TEST_F(TurnClient, AllocateTcp) {
-  if (WarnIfTurnNotConfigured())
-    return;
-
+TEST_F(WebRtcTurn, AllocateTcp) {
   SetTcp();
   Allocate();
 }
 
-TEST_F(TurnClient, AllocateAndHold) {
-  if (WarnIfTurnNotConfigured())
-    return;
-
+TEST_F(WebRtcTurn, AllocateAndHold) {
   Allocate();
   PR_Sleep(20000);
   ASSERT_TRUE(turn_ctx_->state == NR_TURN_CLIENT_STATE_ALLOCATED);
 }
 
-TEST_F(TurnClient, SendToSelf) {
-  if (WarnIfTurnNotConfigured())
-    return;
-
+TEST_F(WebRtcTurn, SendToSelf) {
   Allocate();
   SendTo(relay_addr_);
   ASSERT_TRUE_WAIT(received() == 100, 5000);
   SendTo(relay_addr_);
   ASSERT_TRUE_WAIT(received() == 200, 1000);
 }
 
 
-TEST_F(TurnClient, SendToSelfTcp) {
-  if (WarnIfTurnNotConfigured())
-    return;
-
+TEST_F(WebRtcTurn, SendToSelfTcp) {
   SetTcp();
   Allocate();
   SendTo(relay_addr_);
   ASSERT_TRUE_WAIT(received() == 100, 5000);
   SendTo(relay_addr_);
   ASSERT_TRUE_WAIT(received() == 200, 1000);
 }
 
-TEST_F(TurnClient, PermissionDenied) {
-  if (WarnIfTurnNotConfigured())
-    return;
-
+TEST_F(WebRtcTurn, PermissionDenied) {
   Allocate();
   RequestPermission(relay_addr_);
   PR_Sleep(1000);
 
   /* Fake a 403 response */
   nr_turn_permission *perm;
   perm = STAILQ_FIRST(&turn_ctx_->permissions);
   ASSERT_TRUE(perm);
@@ -440,48 +423,39 @@ TEST_F(TurnClient, PermissionDenied) {
   SendTo(relay_addr_, R_NOT_PERMITTED);
   ASSERT_TRUE(received() == 0);
 
   //TODO: We should check if we can still send to a second destination, but
   //      we would need a second TURN client as one client can only handle one
   //      allocation (maybe as part of bug 1128128 ?).
 }
 
-TEST_F(TurnClient, DeallocateReceiveFailure) {
-  if (WarnIfTurnNotConfigured())
-    return;
-
+TEST_F(WebRtcTurn, DeallocateReceiveFailure) {
   Allocate();
   SendTo(relay_addr_);
   ASSERT_TRUE_WAIT(received() == 100, 5000);
   Deallocate();
   turn_ctx_->state = NR_TURN_CLIENT_STATE_ALLOCATED;
   SendTo(relay_addr_);
   PR_Sleep(1000);
   ASSERT_TRUE(received() == 100);
 }
 
-TEST_F(TurnClient, DeallocateReceiveFailureTcp) {
-  if (WarnIfTurnNotConfigured())
-    return;
-
+TEST_F(WebRtcTurn, DeallocateReceiveFailureTcp) {
   SetTcp();
   Allocate();
   SendTo(relay_addr_);
   ASSERT_TRUE_WAIT(received() == 100, 5000);
   Deallocate();
   turn_ctx_->state = NR_TURN_CLIENT_STATE_ALLOCATED;
   /* Either the connection got closed by the TURN server already, then the send
    * is going to fail, which we simply ignore. Or the connection is still alive
    * and we cand send the data, but it should not get forwarded to us. In either
    * case we should not receive more data. */
   SendTo(relay_addr_, -1);
   PR_Sleep(1000);
   ASSERT_TRUE(received() == 100);
 }
 
-TEST_F(TurnClient, AllocateDummyServer) {
-  if (WarnIfTurnNotConfigured())
-    return;
-
-  turn_server_ = kDummyTurnServer;
+TEST_F(WebRtcTurn, AllocateDummyServer) {
+  turn_server_address_ = kDummyTurnServer;
   Allocate(false);
 }
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -990,16 +990,161 @@ class GTestCommands(MachCommandBase):
                 print("(We can't handle the %r character.)" % e.char)
                 return 1
 
         # Prepend the debugger args.
         args = [debuggerInfo.path] + debuggerInfo.args + args
         return args
 
 @CommandProvider
+class GTestWebRtcCommands(MachCommandBase):
+    @Command('gtest-webrtc', category='testing',
+        description='Run WebRTC GTest unit tests (C++ tests).')
+    @CommandArgument('gtest_filter', default=b"*", nargs='?', metavar='gtest_filter',
+        help="test_filter is a ':'-separated list of wildcard patterns (called the positive patterns),"
+             "optionally followed by a '-' and another ':'-separated pattern list (called the negative patterns).")
+    @CommandArgument('--jobs', '-j', default='1', nargs='?', metavar='jobs', type=int,
+        help='Run the tests in parallel using multiple processes.')
+    @CommandArgument('--tbpl-parser', '-t', action='store_true',
+        help='Output test results in a format that can be parsed by TBPL.')
+    @CommandArgument('--shuffle', '-s', action='store_true',
+        help='Randomize the execution order of tests.')
+
+    @CommandArgumentGroup('debugging')
+    @CommandArgument('--debug', action='store_true', group='debugging',
+        help='Enable the debugger. Not specifying a --debugger option will result in the default debugger being used.')
+    @CommandArgument('--debugger', default=None, type=str, group='debugging',
+        help='Name of debugger to use.')
+    @CommandArgument('--debugger-args', default=None, metavar='params', type=str,
+        group='debugging',
+        help='Command-line arguments to pass to the debugger itself; split as the Bourne shell would.')
+
+
+    def gtest_webrtc(self, shuffle, jobs, gtest_filter, tbpl_parser, debug, debugger,
+                     debugger_args):
+
+        # We lazy build gtest because it's slow to link
+        self._run_make(directory="testing/gtest", target='gtest',
+                       print_directory=False, ensure_exit_code=True)
+
+        app_path = self.get_binary_path('app')
+        args = [app_path, '-unittest'];
+
+        if debug or debugger or debugger_args:
+            args = self.prepend_debugger_args(args, debugger, debugger_args)
+
+        cwd = os.path.join(self.topobjdir, '_tests', 'gtest')
+
+        if not os.path.isdir(cwd):
+            os.makedirs(cwd)
+
+        # Use GTest environment variable to control test execution
+        # For details see:
+        # https://code.google.com/p/googletest/wiki/AdvancedGuide#Running_Test_Programs:_Advanced_Options
+        if (gtest_filter == '*'):
+            gtest_env = {b'GTEST_FILTER': 'WebRtcIce*:WebRtcTurn.*'}
+        else:
+            gtest_env = {b'GTEST_FILTER': gtest_filter}
+
+        # Note: we must normalize the path here so that gtest on Windows sees
+        # a MOZ_GMP_PATH which has only Windows dir seperators, because
+        # nsILocalFile cannot open the paths with non-Windows dir seperators.
+        xre_path = os.path.join(os.path.normpath(self.topobjdir), "dist", "bin")
+        gtest_env["MOZ_XRE_DIR"] = xre_path
+        gtest_env["MOZ_GMP_PATH"] = os.pathsep.join(
+            os.path.join(xre_path, p, "1.0")
+            for p in ('gmp-fake', 'gmp-fakeopenh264')
+        )
+
+        gtest_env[b"MOZ_RUN_GTEST"] = b"True"
+
+        if shuffle:
+            gtest_env[b"GTEST_SHUFFLE"] = b"True"
+
+        if tbpl_parser:
+            gtest_env[b"MOZ_TBPL_PARSER"] = b"True"
+
+        if jobs == 1:
+            return self.run_process(args=args,
+                                    append_env=gtest_env,
+                                    cwd=cwd,
+                                    ensure_exit_code=False,
+                                    pass_thru=True)
+
+        from mozprocess import ProcessHandlerMixin
+        import functools
+        def handle_line(job_id, line):
+            # Prepend the jobId
+            line = '[%d] %s' % (job_id + 1, line.strip())
+            self.log(logging.INFO, "GTest", {'line': line}, '{line}')
+
+        gtest_env["GTEST_TOTAL_SHARDS"] = str(jobs)
+        processes = {}
+        for i in range(0, jobs):
+            gtest_env["GTEST_SHARD_INDEX"] = str(i)
+            processes[i] = ProcessHandlerMixin([app_path, "-unittest"],
+                             cwd=cwd,
+                             env=gtest_env,
+                             processOutputLine=[functools.partial(handle_line, i)],
+                             universal_newlines=True)
+            processes[i].run()
+
+        exit_code = 0
+        for process in processes.values():
+            status = process.wait()
+            if status:
+                exit_code = status
+
+        # Clamp error code to 255 to prevent overflowing multiple of
+        # 256 into 0
+        if exit_code > 255:
+            exit_code = 255
+
+        return exit_code
+
+    def prepend_debugger_args(self, args, debugger, debugger_args):
+        '''
+        Given an array with program arguments, prepend arguments to run it under a
+        debugger.
+
+        :param args: The executable and arguments used to run the process normally.
+        :param debugger: The debugger to use, or empty to use the default debugger.
+        :param debugger_args: Any additional parameters to pass to the debugger.
+        '''
+
+        import mozdebug
+
+        if not debugger:
+            # No debugger name was provided. Look for the default ones on
+            # current OS.
+            debugger = mozdebug.get_default_debugger_name(mozdebug.DebuggerSearch.KeepLooking)
+
+        if debugger:
+            debuggerInfo = mozdebug.get_debugger_info(debugger, debugger_args)
+            if not debuggerInfo:
+                print("Could not find a suitable debugger in your PATH.")
+                return 1
+
+        # Parameters come from the CLI. We need to convert them before
+        # their use.
+        if debugger_args:
+            from mozbuild import shellutil
+            try:
+                debugger_args = shellutil.split(debugger_args)
+            except shellutil.MetaCharacterException as e:
+                print("The --debugger_args you passed require a real shell to parse them.")
+                print("(We can't handle the %r character.)" % e.char)
+                return 1
+
+        # Prepend the debugger args.
+        args = [debuggerInfo.path] + debuggerInfo.args + args
+        return args
+
+
+@CommandProvider
 class ClangCommands(MachCommandBase):
     @Command('clang-complete', category='devenv',
         description='Generate a .clang_complete file.')
     def clang_complete(self):
         import shlex
 
         build_vars = {}
 
--- a/testing/tools/iceserver/iceserver.py
+++ b/testing/tools/iceserver/iceserver.py
@@ -396,28 +396,28 @@ class StunHandler(object):
     server, along with Allocation.
     """
 
     def __init__(self, transport_handler):
         self.client_address = None
         self.data = str()
         self.transport_handler = transport_handler
 
-    def data_received(self, data, address):
+    def data_received(self, data, reply_address, address):
         self.data += bytearray(data)
         while True:
             stun_message = StunMessage()
             parsed_len = stun_message.parse(self.data)
             if parsed_len > len(self.data):
                 break
             self.data = self.data[parsed_len:]
 
             response = self.handle_stun(stun_message, address)
             if response:
-                self.transport_handler.write(response, address)
+                self.transport_handler.write(response, reply_address)
 
     def handle_stun(self, stun_message, address):
         self.client_address = address
         if stun_message.msg_class == INDICATION:
             if stun_message.method == SEND:
                 self.handle_send_indication(stun_message)
             else:
                 print("Dropping unknown indication method: {}"
@@ -557,16 +557,24 @@ class StunHandler(object):
     def handle_send_indication(self, indication):
         try:
             allocation = allocations[self.get_allocation_tuple()]
         except KeyError:
             print("Dropping send indication; no allocation for tuple {}"
                   .format(self.get_allocation_tuple()))
             return
 
+        if allocation.expiry <= time.time():
+            print("Rejecting send indication, binding expired")
+            return self.make_error_response(
+                    indication,
+                    437,
+                    ("Refresh request for non-existing allocation, tuple {}"
+                     .format(self.get_allocation_tuple())))
+
         peer_address = indication.get_xor_address(XOR_PEER_ADDRESS)
         if not peer_address:
             print("Dropping send indication, missing XOR-PEER-ADDRESS")
             return
 
         data_attr = indication.find(DATA_ATTR)
         if not data_attr:
             print("Dropping send indication, missing DATA")
@@ -636,48 +644,61 @@ class StunHandler(object):
         return self.make_success_response(request)
 
 
 class UdpStunHandler(protocol.DatagramProtocol):
     """
     Represents a UDP listen port for TURN.
     """
 
+    def __init__(self, fakePort=False):
+        self.fakePort = fakePort
+
     def datagramReceived(self, data, address):
         stun_handler = StunHandler(self)
-        stun_handler.data_received(data,
-                                   IPv4Address('UDP', address[0], address[1]))
+        addr = IPv4Address('UDP', address[0], address[1])
+        stun_addr = addr
+        if (self.fakePort):
+            stun_addr = IPv4Address('UDP', address[0], address[1] + 5)
+        stun_handler.data_received(data, addr, stun_addr)
 
     def write(self, data, address):
         self.transport.write(str(data), (address.host, address.port))
 
 
 class TcpStunHandlerFactory(protocol.Factory):
     """
     Represents a TCP listen port for TURN.
     """
 
+    def __init__(self, fakePort=False):
+        self.fakePort = fakePort
+
     def buildProtocol(self, addr):
-        return TcpStunHandler(addr)
+        return TcpStunHandler(addr, self.fakePort)
 
 
 class TcpStunHandler(protocol.Protocol):
     """
     Represents a connected TCP port for TURN.
     """
 
-    def __init__(self, addr):
+    def __init__(self, addr, fakePort):
         self.address = addr
+        self.fakePort = fakePort
         self.stun_handler = None
 
     def dataReceived(self, data):
         # This needs to persist, since it handles framing
         if not self.stun_handler:
             self.stun_handler = StunHandler(self)
-        self.stun_handler.data_received(data, self.address)
+        stun_addr = self.address
+        if (self.fakePort):
+            stun_addr = IPv4Address('TCP', self.address[0], self.address[1] + 5)
+        self.stun_handler.data_received(data, self.address, stun_addr)
 
     def connectionLost(self, reason):
         print("Lost connection from {}".format(self.address))
         # Destroy allocations that this connection made
         for key, allocation in allocations.items():
             if allocation.other_transport_handler == self:
                 print("Closing allocation due to dropped connection: {}"
                       .format(key))
@@ -702,58 +723,66 @@ turn_user="foo"
 turn_pass="bar"
 turn_realm="mozilla.invalid"
 allocations = {}
 v4_address = get_default_route(socket.AF_INET)
 try:
     v6_address = get_default_route(socket.AF_INET6)
 except:
     v6_address = ""
+v4_address_loopback = "127.0.0.1"
+v6_address_loopback = "::1"
+hostname = socket.gethostname()
 
 def prune_allocations():
     now = time.time()
     for key, allocation in allocations.items():
         if allocation.expiry < now:
             print("Allocation expired: {}".format(key))
             del allocations[key]
             allocation.close()
 
 if __name__ == "__main__":
     random.seed()
 
-    if platform.system() is "Windows":
-      # Windows is finicky about allowing real interfaces to talk to loopback.
-      interface_4 = v4_address
-      interface_6 = v6_address
-      hostname = socket.gethostname()
-    else:
-      # Our linux builders do not have a hostname that resolves to the real
-      # interface.
-      interface_4 = "127.0.0.1"
-      interface_6 = "::1"
-      hostname = "localhost"
-
-    reactor.listenUDP(3478, UdpStunHandler(), interface=interface_4)
-    reactor.listenTCP(3478, TcpStunHandlerFactory(), interface=interface_4)
+    reactor.listenUDP(3478, UdpStunHandler(), interface=v4_address)
+    reactor.listenUDP(3478, UdpStunHandler(), interface=v4_address_loopback)
+    reactor.listenTCP(3478, TcpStunHandlerFactory(), interface=v4_address)
+    reactor.listenTCP(3478, TcpStunHandlerFactory(), interface=v4_address_loopback)
 
     try:
-        reactor.listenUDP(3478, UdpStunHandler(), interface=interface_6)
-        reactor.listenTCP(3478, TcpStunHandlerFactory(), interface=interface_6)
+        reactor.listenUDP(3478, UdpStunHandler(), interface=v6_address)
+        reactor.listenUDP(3478, UdpStunHandler(), interface=v6_address_loopback)
+        reactor.listenTCP(3478, TcpStunHandlerFactory(), interface=v6_address)
+        reactor.listenTCP(3478, TcpStunHandlerFactory(), interface=v6_address_loopback)
+
     except:
         pass
 
+    try:
+        reactor.listenUDP(3479, UdpStunHandler(fakePort=True), interface=v4_address)
+        reactor.listenUDP(3479, UdpStunHandler(fakePort=True), interface=v4_address_loopback)
+        reactor.listenTCP(3479, TcpStunHandlerFactory(fakePort=True), interface=v4_address)
+        reactor.listenTCP(3479, TcpStunHandlerFactory(fakePort=True), interface=v4_address_loopback)
+        reactor.listenUDP(3479, UdpStunHandler(fakePort=True), interface=v6_address)
+        reactor.listenUDP(3479, UdpStunHandler(fakePort=True), interface=v6_address_loopback)
+        reactor.listenTCP(3479, TcpStunHandlerFactory(fakePort=True), interface=v6_address)
+        reactor.listenTCP(3479, TcpStunHandlerFactory(fakePort=True), interface=v6_address_loopback)
+    except:
+        print('failed')
+
     allocation_pruner = LoopingCall(prune_allocations)
     allocation_pruner.start(1)
 
     template = Template(
 '[\
 {"url":"stun:$hostname"}, \
 {"url":"stun:$hostname?transport=tcp"}, \
 {"username":"$user","credential":"$pwd","url":"turn:$hostname"}, \
-{"username":"$user","credential":"$pwd","url":"turn:$hostname?transport=tcp"}]'
-)
+{"username":"$user","credential":"$pwd","url":"turn:$hostname?transport=tcp"}\
+]')
 
     print(template.substitute(user=turn_user,
                               pwd=turn_pass,
                               hostname=hostname))
 
     reactor.run()