Bug 1548841 - Obfuscate mDNS ICE candidate addresses; r=bwc
authorDan Minor <dminor@mozilla.com>
Wed, 15 May 2019 22:55:55 +0000
changeset 475751 173b03319a4694ae530f30effbff5148e31bf5af
parent 475750 d909d196e7a9502568e02b4d5af79dc65c5c94de
child 475752 15ec66f24d058464e5661742c15daeff1d81e1a4
push id86458
push userdminor@mozilla.com
push dateMon, 27 May 2019 16:57:14 +0000
treeherderautoland@3c4fb056f40c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbwc
bugs1548841
milestone69.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1548841 - Obfuscate mDNS ICE candidate addresses; r=bwc This adds a mdns_addr field to nICEr ICE candidates to track the mDNS address associated with a candidate, if any. This is used to hide the real address when generating ICE stats. This potentially could be handled at the MediaTransportHandler level, but we need to know if a candidate is mDNS to prevent pairing it with relay candidates, which is part of the next commit. This adds a unit tests to check that the mDNS addresses are handled properly. As part of doing this, TestBogusCandidate was fixed. As written, it was never parsing the bogus candidate because it was in the wrong ICE state. Differential Revision: https://phabricator.services.mozilla.com/D30935
media/mtransport/nricemediastream.cpp
media/mtransport/nricemediastream.h
media/mtransport/test/ice_unittest.cpp
media/mtransport/third_party/nICEr/src/ice/ice_candidate.c
media/mtransport/third_party/nICEr/src/ice/ice_candidate.h
media/mtransport/third_party/nICEr/src/ice/ice_peer_ctx.c
media/mtransport/third_party/nICEr/src/ice/ice_peer_ctx.h
media/webrtc/signaling/src/peerconnection/MediaTransportHandler.cpp
--- a/media/mtransport/nricemediastream.cpp
+++ b/media/mtransport/nricemediastream.cpp
@@ -109,16 +109,20 @@ static bool ToNrIceCandidate(const nr_ic
                              NrIceCandidate* out) {
   MOZ_ASSERT(out);
   int r;
   // Const-cast because the internal nICEr code isn't const-correct.
   nr_ice_candidate* cand = const_cast<nr_ice_candidate*>(&candc);
 
   if (!ToNrIceAddr(cand->addr, &out->cand_addr)) return false;
 
+  if (cand->mdns_addr) {
+    out->mdns_addr = cand->mdns_addr;
+  }
+
   if (cand->isock) {
     nr_transport_addr addr;
     r = nr_socket_getaddr(cand->isock->sock, &addr);
     if (r) return false;
 
     if (!ToNrIceAddr(addr, &out->local_addr)) return false;
   }
 
@@ -265,28 +269,29 @@ nsresult NrIceMediaStream::SetIceCredent
   }
 
   state_ = ICE_CONNECTING;
   return NS_OK;
 }
 
 // Parse trickle ICE candidate
 nsresult NrIceMediaStream::ParseTrickleCandidate(const std::string& candidate,
-                                                 const std::string& ufrag) {
+                                                 const std::string& ufrag,
+                                                 const std::string& mdns_addr) {
   nr_ice_media_stream* stream = GetStreamForRemoteUfrag(ufrag);
   if (!stream) {
     return NS_ERROR_FAILURE;
   }
 
   MOZ_MTLOG(ML_DEBUG, "NrIceCtx(" << ctx_->label << ")/STREAM(" << name()
                                   << ") : parsing trickle candidate "
                                   << candidate);
 
   int r = nr_ice_peer_ctx_parse_trickle_candidate(
-      ctx_peer_, stream, const_cast<char*>(candidate.c_str()));
+      ctx_peer_, stream, const_cast<char*>(candidate.c_str()), mdns_addr.c_str());
 
   if (r) {
     if (r == R_ALREADY) {
       MOZ_MTLOG(ML_INFO, "Trickle candidate is redundant for stream '"
                              << name_
                              << "' because it is completed: " << candidate);
     } else if (r == R_REJECTED) {
       MOZ_MTLOG(ML_INFO,
--- a/media/mtransport/nricemediastream.h
+++ b/media/mtransport/nricemediastream.h
@@ -75,16 +75,17 @@ struct NrIceAddr {
    pair is active */
 struct NrIceCandidate {
   enum Type { ICE_HOST, ICE_SERVER_REFLEXIVE, ICE_PEER_REFLEXIVE, ICE_RELAYED };
 
   enum TcpType { ICE_NONE, ICE_ACTIVE, ICE_PASSIVE, ICE_SO };
 
   NrIceAddr cand_addr;
   NrIceAddr local_addr;
+  std::string mdns_addr;
   Type type;
   TcpType tcp_type;
   std::string codeword;
   std::string label;
   bool trickled;
   uint32_t priority;
 };
 
@@ -148,17 +149,18 @@ class NrIceMediaStream {
   // Get all candidate pairs, whether in the check list or triggered check
   // queue, in priority order. |out_pairs| is cleared before being filled.
   nsresult GetCandidatePairs(std::vector<NrIceCandidatePair>* out_pairs) const;
 
   nsresult GetDefaultCandidate(int component, NrIceCandidate* candidate) const;
 
   // Parse trickle ICE candidate
   nsresult ParseTrickleCandidate(const std::string& candidate,
-                                 const std::string& ufrag);
+                                 const std::string& ufrag,
+                                 const std::string& mdns_addr);
 
   // Disable a component
   nsresult DisableComponent(int component);
 
   // Get the candidate pair currently active. It's the
   // caller's responsibility to free these.
   nsresult GetActivePair(int component, UniquePtr<NrIceCandidate>* local,
                          UniquePtr<NrIceCandidate>* remote);
--- a/media/mtransport/test/ice_unittest.cpp
+++ b/media/mtransport/test/ice_unittest.cpp
@@ -804,17 +804,17 @@ class IceTestPeer : public sigslot::has_
 
   nsresult TrickleCandidate_s(const std::string& candidate,
                               const std::string& ufrag, size_t index) {
     auto stream = GetStream_s(index);
     if (!stream) {
       // stream might have gone away before the trickle timer popped
       return NS_OK;
     }
-    return stream->ParseTrickleCandidate(candidate, ufrag);
+    return stream->ParseTrickleCandidate(candidate, ufrag, "");
   }
 
   void DumpCandidate(std::string which, const NrIceCandidate& cand) {
     std::string type;
     std::string tcp_type;
 
     std::string addr;
     int port;
@@ -996,17 +996,18 @@ class IceTestPeer : public sigslot::has_
     // If we are connected, then try to trickle to the other side.
     if (remote_ && remote_->remote_ && (trickle_mode_ != TRICKLE_SIMULATE)) {
       // first, find the index of the stream we've been given so
       // we can get the corresponding stream on the remote side
       for (size_t i = 0; i < stream_counter_; ++i) {
         if (GetStream_s(i) == stream) {
           ASSERT_GT(remote_->stream_counter_, i);
           nsresult res =
-              remote_->GetStream_s(i)->ParseTrickleCandidate(candidate, ufrag);
+              remote_->GetStream_s(i)->ParseTrickleCandidate(candidate, ufrag,
+                                                             "");
           ASSERT_TRUE(NS_SUCCEEDED(res));
           return;
         }
       }
       ADD_FAILURE() << "No matching stream found for " << stream;
     }
   }
 
@@ -1214,26 +1215,29 @@ class IceTestPeer : public sigslot::has_
 
     std::cerr << name_ << ": send failed as expected" << std::endl;
   }
 
   void SetCandidateFilter(CandidateFilter filter) {
     candidate_filter_ = filter;
   }
 
-  void ParseCandidate_s(size_t i, const std::string& candidate) {
+  void ParseCandidate_s(size_t i, const std::string& candidate,
+                        const std::string& mdns_addr) {
     auto media_stream = GetStream_s(i);
     ASSERT_TRUE(media_stream.get())
     << "No such stream " << i;
-    media_stream->ParseTrickleCandidate(candidate, "");
+    media_stream->ParseTrickleCandidate(candidate, "", mdns_addr);
   }
 
-  void ParseCandidate(size_t i, const std::string& candidate) {
+  void ParseCandidate(size_t i, const std::string& candidate,
+                      const std::string &mdns_addr) {
     test_utils_->sts_target()->Dispatch(
-        WrapRunnable(this, &IceTestPeer::ParseCandidate_s, i, candidate),
+        WrapRunnable(this, &IceTestPeer::ParseCandidate_s, i, candidate,
+                     mdns_addr),
         NS_DISPATCH_SYNC);
   }
 
   void DisableComponent_s(size_t index, int component_id) {
     ASSERT_LT(index, stream_counter_);
     auto stream = GetStream_s(index);
     ASSERT_TRUE(stream.get())
     << "No such stream " << index;
@@ -2269,23 +2273,16 @@ TEST_F(WebRtcIceGatherTest, TestGatherTc
   // Set up peer with tcp disabled.
   peer_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, false, false);
   peer_->AddStream(1);
   Gather();
   ASSERT_FALSE(StreamHasMatchingCandidate(0, " TCP "));
   ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP "));
 }
 
-// Verify that a bogus candidate doesn't cause crashes on the
-// main thread. See bug 856433.
-TEST_F(WebRtcIceGatherTest, TestBogusCandidate) {
-  Gather();
-  peer_->ParseCandidate(0, kBogusIceCandidate);
-}
-
 TEST_F(WebRtcIceGatherTest, VerifyTestStunServer) {
   UseFakeStunUdpServerWithResponse("192.0.2.133", 3333);
   Gather();
   ASSERT_TRUE(StreamHasMatchingCandidate(0, " 192.0.2.133 3333 "));
 }
 
 TEST_F(WebRtcIceGatherTest, VerifyTestStunTcpServer) {
   UseFakeStunTcpServerWithResponse("192.0.2.233", 3333);
@@ -3605,16 +3602,56 @@ TEST_F(WebRtcIceConnectTest, TestRLogCon
     std::deque<std::string> logs;
     std::string substring("CAND-PAIR(");
     substring += p.codeword;
     RLogConnector::GetInstance()->Filter(substring, 0, &logs);
     ASSERT_NE(0U, logs.size());
   }
 }
 
+// Verify that a bogus candidate doesn't cause crashes on the
+// main thread. See bug 856433.
+TEST_F(WebRtcIceConnectTest, TestBogusCandidate) {
+  AddStream(1);
+  Gather();
+  ConnectTrickle();
+  p1_->ParseCandidate(0, kBogusIceCandidate, "");
+
+  std::vector<NrIceCandidatePair> pairs;
+  nsresult res = p1_->GetCandidatePairs(0, &pairs);
+  ASSERT_EQ(NS_OK, res);
+  ASSERT_EQ(0U, pairs.size());
+}
+
+TEST_F(WebRtcIceConnectTest, TestNonMDNSCandidate) {
+  AddStream(1);
+  Gather();
+  ConnectTrickle();
+  p1_->ParseCandidate(0, kUnreachableHostIceCandidate, "");
+
+  std::vector<NrIceCandidatePair> pairs;
+  nsresult res = p1_->GetCandidatePairs(0, &pairs);
+  ASSERT_EQ(NS_OK, res);
+  ASSERT_EQ(1U, pairs.size());
+  ASSERT_EQ(pairs[0].remote.mdns_addr, "");
+}
+
+TEST_F(WebRtcIceConnectTest, TestMDNSCandidate) {
+  AddStream(1);
+  Gather();
+  ConnectTrickle();
+  p1_->ParseCandidate(0, kUnreachableHostIceCandidate, "host.local");
+
+  std::vector<NrIceCandidatePair> pairs;
+  nsresult res = p1_->GetCandidatePairs(0, &pairs);
+  ASSERT_EQ(NS_OK, res);
+  ASSERT_EQ(1U, pairs.size());
+  ASSERT_EQ(pairs[0].remote.mdns_addr, "host.local");
+}
+
 TEST_F(WebRtcIcePrioritizerTest, TestPrioritizer) {
   SetPriorizer(::mozilla::CreateInterfacePrioritizer());
 
   AddInterface("0", NR_INTERFACE_TYPE_VPN, 100);  // unknown vpn
   AddInterface("1", NR_INTERFACE_TYPE_VPN | NR_INTERFACE_TYPE_WIRED,
                100);  // wired vpn
   AddInterface("2", NR_INTERFACE_TYPE_VPN | NR_INTERFACE_TYPE_WIFI,
                100);  // wifi vpn
--- a/media/mtransport/third_party/nICEr/src/ice/ice_candidate.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_candidate.c
@@ -341,16 +341,17 @@ int nr_ice_candidate_destroy(nr_ice_cand
         if (cand->u.srvrflx.relay_candidate)
           cand->u.srvrflx.relay_candidate->u.relayed.srvflx_candidate=0;
         nr_stun_client_ctx_destroy(&cand->u.srvrflx.stun);
         break;
       default:
         break;
     }
 
+    RFREE(cand->mdns_addr);
     RFREE(cand->foundation);
     RFREE(cand->label);
     RFREE(cand);
 
     return(0);
   }
 
 void nr_ice_candidate_stop_gathering(nr_ice_candidate *cand)
--- a/media/mtransport/third_party/nICEr/src/ice/ice_candidate.h
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_candidate.h
@@ -60,16 +60,17 @@ struct nr_ice_candidate_ {
   nr_ice_media_stream *stream;        /* The media stream this is associated with */
   nr_ice_component *component;        /* The component this is associated with */
   nr_ice_candidate_type type;         /* The type of the candidate (S 4.1.1) */
   nr_socket_tcp_type tcp_type;
   UCHAR component_id;                 /* The component id (S 4.1.2.1) */
   nr_transport_addr addr;             /* The advertised address;
                                          JDR calls this the candidate */
   nr_transport_addr base;             /* The base address (S 2.1)*/
+  char *mdns_addr;                    /* MDNS address, if any */
   char *foundation;                   /* Foundation for the candidate (S 4) */
   UINT4 priority;                     /* The priority value (S 5.4 */
   nr_ice_stun_server *stun_server;
   nr_transport_addr stun_server_addr; /* Resolved STUN server address */
   void *delay_timer;
   void *resolver_handle;
 
   /* Holding data for STUN and TURN */
--- a/media/mtransport/third_party/nICEr/src/ice/ice_peer_ctx.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_peer_ctx.c
@@ -39,17 +39,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 #include "ice_media_stream.h"
 #include "ice_util.h"
 #include "nr_crypto.h"
 #include "async_timer.h"
 #include "ice_reg.h"
 
 static void nr_ice_peer_ctx_destroy_cb(NR_SOCKET s, int how, void *cb_arg);
 static int nr_ice_peer_ctx_parse_stream_attributes_int(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, nr_ice_media_stream *pstream, char **attrs, int attr_ct);
-static int nr_ice_ctx_parse_candidate(nr_ice_peer_ctx *pctx, nr_ice_media_stream *pstream, char *candidate, int trickled);
+static int nr_ice_ctx_parse_candidate(nr_ice_peer_ctx *pctx, nr_ice_media_stream *pstream, char *candidate, int trickled, const char *mdns_addr);
 static void nr_ice_peer_ctx_start_trickle_timer(nr_ice_peer_ctx *pctx);
 
 int nr_ice_peer_ctx_create(nr_ice_ctx *ctx, nr_ice_handler *handler,char *label, nr_ice_peer_ctx **pctxp)
   {
     int r,_status;
     nr_ice_peer_ctx *pctx=0;
 
     if(!(pctx=RCALLOC(sizeof(nr_ice_peer_ctx))))
@@ -153,43 +153,50 @@ static int nr_ice_peer_ctx_parse_stream_
     for(i=0;i<attr_ct;i++){
       if(!strncmp(attrs[i],"ice-",4)){
         if(r=nr_ice_peer_ctx_parse_media_stream_attribute(pctx,pstream,attrs[i])) {
           r_log(LOG_ICE,LOG_WARNING,"ICE(%s): peer (%s) specified bogus ICE attribute",pctx->ctx->label,pctx->label);
           continue;
         }
       }
       else if (!strncmp(attrs[i],"candidate",9)){
-        if(r=nr_ice_ctx_parse_candidate(pctx,pstream,attrs[i],0)) {
+        if(r=nr_ice_ctx_parse_candidate(pctx,pstream,attrs[i],0,0)) {
           r_log(LOG_ICE,LOG_WARNING,"ICE(%s): peer (%s) specified bogus candidate",pctx->ctx->label,pctx->label);
           continue;
         }
       }
       else {
         r_log(LOG_ICE,LOG_WARNING,"ICE(%s): peer (%s) specified bogus attribute: %s",pctx->ctx->label,pctx->label,attrs[i]);
       }
     }
 
     /* Doesn't fail because we just skip errors */
     return(0);
   }
 
-static int nr_ice_ctx_parse_candidate(nr_ice_peer_ctx *pctx, nr_ice_media_stream *pstream, char *candidate, int trickled)
+static int nr_ice_ctx_parse_candidate(nr_ice_peer_ctx *pctx, nr_ice_media_stream *pstream, char *candidate, int trickled, const char *mdns_addr)
   {
     nr_ice_candidate *cand=0;
     nr_ice_component *comp;
     int j;
     int r, _status;
 
     if(r=nr_ice_peer_candidate_from_attribute(pctx->ctx,candidate,pstream,&cand))
       ABORT(r);
 
     /* set the trickled flag on the candidate */
     cand->trickled = trickled;
 
+    if (mdns_addr) {
+      cand->mdns_addr = r_strdup(mdns_addr);
+      if (!cand->mdns_addr) {
+        ABORT(R_NO_MEMORY);
+      }
+    }
+
     /* Not the fastest way to find a component, but it's what we got */
     j=1;
     for(comp=STAILQ_FIRST(&pstream->components);comp;comp=STAILQ_NEXT(comp,entry)){
       if(j==cand->component_id)
         break;
 
       j++;
     }
@@ -260,17 +267,17 @@ int nr_ice_peer_ctx_remove_pstream(nr_ic
       ABORT(r);
     }
 
     _status=0;
  abort:
     return(_status);
   }
 
-int nr_ice_peer_ctx_parse_trickle_candidate(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, char *candidate)
+int nr_ice_peer_ctx_parse_trickle_candidate(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, char *candidate, const char *mdns_addr)
   {
     nr_ice_media_stream *pstream;
     int r,_status;
     int needs_pairing = 0;
 
     r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): peer (%s) parsing trickle ICE candidate %s",pctx->ctx->label,pctx->label,candidate);
     r = nr_ice_peer_ctx_find_pstream(pctx, stream, &pstream);
     if (r)
@@ -284,17 +291,17 @@ int nr_ice_peer_ctx_parse_trickle_candid
         needs_pairing = 1;
         break;
       default:
         r_log(LOG_ICE,LOG_ERR,"ICE(%s): peer (%s), stream(%s) tried to trickle ICE in inappropriate state %d",pctx->ctx->label,pctx->label,stream->label,pstream->ice_state);
         ABORT(R_ALREADY);
         break;
     }
 
-    if(r=nr_ice_ctx_parse_candidate(pctx,pstream,candidate,1)){
+    if(r=nr_ice_ctx_parse_candidate(pctx,pstream,candidate,1,mdns_addr)){
       ABORT(r);
     }
 
     /* If ICE is running (i.e., we are in FROZEN or ACTIVE states)
        then we need to pair this new candidate. For now we
        just re-pair the stream which is inefficient but still
        fine because we suppress duplicate pairing */
     if (needs_pairing) {
--- a/media/mtransport/third_party/nICEr/src/ice/ice_peer_ctx.h
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_peer_ctx.h
@@ -69,17 +69,17 @@ struct nr_ice_peer_ctx_ {
 
 typedef STAILQ_HEAD(nr_ice_peer_ctx_head_, nr_ice_peer_ctx_) nr_ice_peer_ctx_head;
 
 int nr_ice_peer_ctx_create(nr_ice_ctx *ctx, nr_ice_handler *handler,char *label, nr_ice_peer_ctx **pctxp);
 int nr_ice_peer_ctx_destroy(nr_ice_peer_ctx **pctxp);
 int nr_ice_peer_ctx_parse_stream_attributes(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, char **attrs, int attr_ct);
 int nr_ice_peer_ctx_find_pstream(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, nr_ice_media_stream **pstreamp);
 int nr_ice_peer_ctx_remove_pstream(nr_ice_peer_ctx *pctx, nr_ice_media_stream **pstreamp);
-int nr_ice_peer_ctx_parse_trickle_candidate(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, char *cand);
+int nr_ice_peer_ctx_parse_trickle_candidate(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, char *cand, const char *mdns_addr);
 
 int nr_ice_peer_ctx_pair_candidates(nr_ice_peer_ctx *pctx);
 int nr_ice_peer_ctx_parse_global_attributes(nr_ice_peer_ctx *pctx, char **attrs, int attr_ct);
 int nr_ice_peer_ctx_start_checks(nr_ice_peer_ctx *pctx);
 int nr_ice_peer_ctx_start_checks2(nr_ice_peer_ctx *pctx, int allow_non_first);
 void nr_ice_peer_ctx_stream_started_checks(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream);
 void nr_ice_peer_ctx_refresh_consent_all_streams(nr_ice_peer_ctx *pctx);
 void nr_ice_peer_ctx_disconnect_all_streams(nr_ice_peer_ctx *pctx);
--- a/media/webrtc/signaling/src/peerconnection/MediaTransportHandler.cpp
+++ b/media/webrtc/signaling/src/peerconnection/MediaTransportHandler.cpp
@@ -736,17 +736,17 @@ void MediaTransportHandlerSTS::AddIceCan
 
   RefPtr<NrIceMediaStream> stream(mIceCtx->GetStream(aTransportId));
   if (!stream) {
     CSFLogError(LOGTAG, "No ICE stream for candidate with transport id %s: %s",
                 aTransportId.c_str(), aCandidate.c_str());
     return;
   }
 
-  nsresult rv = stream->ParseTrickleCandidate(aCandidate, aUfrag);
+  nsresult rv = stream->ParseTrickleCandidate(aCandidate, aUfrag, "");
   if (NS_FAILED(rv)) {
     CSFLogError(LOGTAG,
                 "Couldn't process ICE candidate with transport id %s: "
                 "%s",
                 aTransportId.c_str(), aCandidate.c_str());
   }
 }
 
@@ -1071,18 +1071,25 @@ static void ToRTCIceCandidateStats(
     dom::RTCIceCandidateStats cand;
     cand.mType.Construct(candidateType);
     NS_ConvertASCIItoUTF16 codeword(candidate.codeword.c_str());
     cand.mTransportId.Construct(transportId);
     cand.mId.Construct(codeword);
     cand.mTimestamp.Construct(now);
     cand.mCandidateType.Construct(dom::RTCIceCandidateType(candidate.type));
     cand.mPriority.Construct(candidate.priority);
-    cand.mAddress.Construct(
-        NS_ConvertASCIItoUTF16(candidate.cand_addr.host.c_str()));
+    // https://tools.ietf.org/html/draft-ietf-rtcweb-mdns-ice-candidates-03#section-3.3.1
+    // This obfuscates the address with the mDNS address if one exists
+    if (!candidate.mdns_addr.empty()) {
+      cand.mAddress.Construct(
+          NS_ConvertASCIItoUTF16(candidate.mdns_addr.c_str()));
+    } else {
+      cand.mAddress.Construct(
+          NS_ConvertASCIItoUTF16(candidate.cand_addr.host.c_str()));
+    }
     cand.mPort.Construct(candidate.cand_addr.port);
     cand.mProtocol.Construct(
         NS_ConvertASCIItoUTF16(candidate.cand_addr.transport.c_str()));
     if (candidateType == dom::RTCStatsType::Local_candidate &&
         dom::RTCIceCandidateType(candidate.type) ==
             dom::RTCIceCandidateType::Relay) {
       cand.mRelayProtocol.Construct(
           NS_ConvertASCIItoUTF16(candidate.local_addr.transport.c_str()));