diff --git a/webrtc/p2p/base/port.cc b/webrtc/p2p/base/port.cc index 675d39afc7..559fe72549 100644 --- a/webrtc/p2p/base/port.cc +++ b/webrtc/p2p/base/port.cc @@ -88,7 +88,7 @@ const int64_t kForgetPacketAfter = 30000; // 30 seconds namespace cricket { -// TODO(ronghuawu): Use "host", "srflx", "prflx" and "relay". But this requires +// TODO(ronghuawu): Use "local", "srflx", "prflx" and "relay". But this requires // the signaling part be updated correspondingly as well. const char LOCAL_PORT_TYPE[] = "local"; const char STUN_PORT_TYPE[] = "stun"; diff --git a/webrtc/p2p/base/sessiondescription.h b/webrtc/p2p/base/sessiondescription.h index a20f7b7132..5a750efff6 100644 --- a/webrtc/p2p/base/sessiondescription.h +++ b/webrtc/p2p/base/sessiondescription.h @@ -14,8 +14,8 @@ #include #include -#include "webrtc/p2p/base/transportinfo.h" #include "webrtc/base/constructormagic.h" +#include "webrtc/p2p/base/transportinfo.h" namespace cricket { diff --git a/webrtc/pc/jsepsessiondescription.cc b/webrtc/pc/jsepsessiondescription.cc index 02919b6bf8..457faafd91 100644 --- a/webrtc/pc/jsepsessiondescription.cc +++ b/webrtc/pc/jsepsessiondescription.cc @@ -38,6 +38,74 @@ static bool IsTypeSupported(const std::string& type) { return type_supported; } +// RFC 5245 +// It is RECOMMENDED that default candidates be chosen based on the +// likelihood of those candidates to work with the peer that is being +// contacted. It is RECOMMENDED that relayed > reflexive > host. +static const int kPreferenceUnknown = 0; +static const int kPreferenceHost = 1; +static const int kPreferenceReflexive = 2; +static const int kPreferenceRelayed = 3; + +static const char kDummyAddress[] = "0.0.0.0"; +static const int kDummyPort = 9; + +static int GetCandidatePreferenceFromType(const std::string& type) { + int preference = kPreferenceUnknown; + if (type == cricket::LOCAL_PORT_TYPE) { + preference = kPreferenceHost; + } else if (type == cricket::STUN_PORT_TYPE) { + preference = kPreferenceReflexive; + } else if (type == cricket::RELAY_PORT_TYPE) { + preference = kPreferenceRelayed; + } else { + RTC_NOTREACHED(); + } + return preference; +} + +// Update the connection address for the MediaContentDescription based on the +// candidates. +static void UpdateConnectionAddress( + const JsepCandidateCollection& candidate_collection, + cricket::ContentDescription* content_description) { + int port = kDummyPort; + std::string ip = kDummyAddress; + int current_preference = kPreferenceUnknown; + int current_family = AF_UNSPEC; + for (size_t i = 0; i < candidate_collection.count(); ++i) { + const IceCandidateInterface* jsep_candidate = candidate_collection.at(i); + if (jsep_candidate->candidate().component() != + cricket::ICE_CANDIDATE_COMPONENT_RTP) { + continue; + } + // Default destination should be UDP only. + if (jsep_candidate->candidate().protocol() != cricket::UDP_PROTOCOL_NAME) { + continue; + } + const int preference = + GetCandidatePreferenceFromType(jsep_candidate->candidate().type()); + const int family = jsep_candidate->candidate().address().ipaddr().family(); + // See if this candidate is more preferable then the current one if it's the + // same family. Or if the current family is IPv4 already so we could safely + // ignore all IPv6 ones. WebRTC bug 4269. + // http://code.google.com/p/webrtc/issues/detail?id=4269 + if ((preference <= current_preference && current_family == family) || + (current_family == AF_INET && family == AF_INET6)) { + continue; + } + current_preference = preference; + current_family = family; + port = jsep_candidate->candidate().address().port(); + ip = jsep_candidate->candidate().address().ipaddr().ToString(); + } + rtc::SocketAddress connection_addr; + connection_addr.SetIP(ip); + connection_addr.SetPort(port); + static_cast(content_description) + ->set_connection_address(connection_addr); +} + const char SessionDescriptionInterface::kOffer[] = "offer"; const char SessionDescriptionInterface::kPrAnswer[] = "pranswer"; const char SessionDescriptionInterface::kAnswer[] = "answer"; @@ -116,9 +184,13 @@ bool JsepSessionDescription::AddCandidate( static_cast(mediasection_index), updated_candidate)); if (!candidate_collection_[mediasection_index].HasCandidate( - updated_candidate_wrapper.get())) + updated_candidate_wrapper.get())) { candidate_collection_[mediasection_index].add( updated_candidate_wrapper.release()); + UpdateConnectionAddress( + candidate_collection_[mediasection_index], + description_->contents()[mediasection_index].description); + } return true; } @@ -133,6 +205,9 @@ size_t JsepSessionDescription::RemoveCandidates( continue; } num_removed += candidate_collection_[mediasection_index].remove(candidate); + UpdateConnectionAddress( + candidate_collection_[mediasection_index], + description_->contents()[mediasection_index].description); } return num_removed; } diff --git a/webrtc/pc/jsepsessiondescription_unittest.cc b/webrtc/pc/jsepsessiondescription_unittest.cc index f692457f7e..c406ad8249 100644 --- a/webrtc/pc/jsepsessiondescription_unittest.cc +++ b/webrtc/pc/jsepsessiondescription_unittest.cc @@ -34,6 +34,9 @@ static const char kCandidateUfragVoice[] = "ufrag_voice"; static const char kCandidatePwdVoice[] = "pwd_voice"; static const char kCandidateUfragVideo[] = "ufrag_video"; static const char kCandidatePwdVideo[] = "pwd_video"; +static const char kCandidateFoundation[] = "a0+B/1"; +static const uint32_t kCandidatePriority = 2130706432U; // pref = 1.0 +static const uint32_t kCandidateGeneration = 2; // This creates a session description with both audio and video media contents. // In SDP this is described by two m lines, one audio and one video. @@ -228,3 +231,177 @@ TEST_F(JsepSessionDescriptionTest, SerializeDeserializeWithCandidates) { EXPECT_EQ(sdp_with_candidate, parsed_sdp_with_candidate); } + +// TODO(zhihuang): Modify these tests. These are used to verify that after +// adding the candidates, the connection_address field is set correctly. Modify +// those so that the "connection address" is tested directly. +// Tests serialization of SDP with only IPv6 candidates and verifies that IPv6 +// is used as default address in c line according to preference. +TEST_F(JsepSessionDescriptionTest, SerializeSessionDescriptionWithIPv6Only) { + // Stun has a high preference than local host. + cricket::Candidate candidate1( + cricket::ICE_CANDIDATE_COMPONENT_RTP, "udp", + rtc::SocketAddress("::1", 1234), kCandidatePriority, "", "", + cricket::STUN_PORT_TYPE, kCandidateGeneration, kCandidateFoundation); + cricket::Candidate candidate2( + cricket::ICE_CANDIDATE_COMPONENT_RTP, "udp", + rtc::SocketAddress("::2", 1235), kCandidatePriority, "", "", + cricket::LOCAL_PORT_TYPE, kCandidateGeneration, kCandidateFoundation); + + JsepIceCandidate jice1("audio", 0, candidate1); + JsepIceCandidate jice2("audio", 0, candidate2); + JsepIceCandidate jice3("video", 0, candidate1); + JsepIceCandidate jice4("video", 0, candidate2); + ASSERT_TRUE(jsep_desc_->AddCandidate(&jice1)); + ASSERT_TRUE(jsep_desc_->AddCandidate(&jice2)); + ASSERT_TRUE(jsep_desc_->AddCandidate(&jice3)); + ASSERT_TRUE(jsep_desc_->AddCandidate(&jice4)); + std::string message = Serialize(jsep_desc_.get()); + + // Should have a c line like this one. + EXPECT_NE(message.find("c=IN IP6 ::1"), std::string::npos); + // Shouldn't have a IP4 c line. + EXPECT_EQ(message.find("c=IN IP4"), std::string::npos); +} + +// Tests serialization of SDP with both IPv4 and IPv6 candidates and +// verifies that IPv4 is used as default address in c line even if the +// preference of IPv4 is lower. +TEST_F(JsepSessionDescriptionTest, + SerializeSessionDescriptionWithBothIPFamilies) { + cricket::Candidate candidate_v4( + cricket::ICE_CANDIDATE_COMPONENT_RTP, "udp", + rtc::SocketAddress("192.168.1.5", 1234), kCandidatePriority, "", "", + cricket::STUN_PORT_TYPE, kCandidateGeneration, kCandidateFoundation); + cricket::Candidate candidate_v6( + cricket::ICE_CANDIDATE_COMPONENT_RTP, "udp", + rtc::SocketAddress("::1", 1234), kCandidatePriority, "", "", + cricket::LOCAL_PORT_TYPE, kCandidateGeneration, kCandidateFoundation); + + JsepIceCandidate jice_v4("audio", 0, candidate_v4); + JsepIceCandidate jice_v6("audio", 0, candidate_v6); + JsepIceCandidate jice_v4_video("video", 0, candidate_v4); + JsepIceCandidate jice_v6_video("video", 0, candidate_v6); + ASSERT_TRUE(jsep_desc_->AddCandidate(&jice_v4)); + ASSERT_TRUE(jsep_desc_->AddCandidate(&jice_v6)); + ASSERT_TRUE(jsep_desc_->AddCandidate(&jice_v4_video)); + ASSERT_TRUE(jsep_desc_->AddCandidate(&jice_v6_video)); + std::string message = Serialize(jsep_desc_.get()); + + // Should have a c line like this one. + EXPECT_NE(message.find("c=IN IP4 192.168.1.5"), std::string::npos); + // Shouldn't have a IP6 c line. + EXPECT_EQ(message.find("c=IN IP6"), std::string::npos); +} + +// Tests serialization of SDP with both UDP and TCP candidates and +// verifies that UDP is used as default address in c line even if the +// preference of UDP is lower. +TEST_F(JsepSessionDescriptionTest, + SerializeSessionDescriptionWithBothProtocols) { + // Stun has a high preference than local host. + cricket::Candidate candidate1( + cricket::ICE_CANDIDATE_COMPONENT_RTP, "tcp", + rtc::SocketAddress("::1", 1234), kCandidatePriority, "", "", + cricket::STUN_PORT_TYPE, kCandidateGeneration, kCandidateFoundation); + cricket::Candidate candidate2( + cricket::ICE_CANDIDATE_COMPONENT_RTP, "udp", + rtc::SocketAddress("fe80::1234:5678:abcd:ef12", 1235), kCandidatePriority, + "", "", cricket::LOCAL_PORT_TYPE, kCandidateGeneration, + kCandidateFoundation); + + JsepIceCandidate jice1("audio", 0, candidate1); + JsepIceCandidate jice2("audio", 0, candidate2); + JsepIceCandidate jice3("video", 0, candidate1); + JsepIceCandidate jice4("video", 0, candidate2); + ASSERT_TRUE(jsep_desc_->AddCandidate(&jice1)); + ASSERT_TRUE(jsep_desc_->AddCandidate(&jice2)); + ASSERT_TRUE(jsep_desc_->AddCandidate(&jice3)); + ASSERT_TRUE(jsep_desc_->AddCandidate(&jice4)); + std::string message = Serialize(jsep_desc_.get()); + + // Should have a c line like this one. + EXPECT_NE(message.find("c=IN IP6 fe80::1234:5678:abcd:ef12"), + std::string::npos); + // Shouldn't have a IP4 c line. + EXPECT_EQ(message.find("c=IN IP4"), std::string::npos); +} + +// Tests serialization of SDP with only TCP candidates and verifies that +// null IPv4 is used as default address in c line. +TEST_F(JsepSessionDescriptionTest, SerializeSessionDescriptionWithTCPOnly) { + // Stun has a high preference than local host. + cricket::Candidate candidate1( + cricket::ICE_CANDIDATE_COMPONENT_RTP, "tcp", + rtc::SocketAddress("::1", 1234), kCandidatePriority, "", "", + cricket::STUN_PORT_TYPE, kCandidateGeneration, kCandidateFoundation); + cricket::Candidate candidate2( + cricket::ICE_CANDIDATE_COMPONENT_RTP, "tcp", + rtc::SocketAddress("::2", 1235), kCandidatePriority, "", "", + cricket::LOCAL_PORT_TYPE, kCandidateGeneration, kCandidateFoundation); + + JsepIceCandidate jice1("audio", 0, candidate1); + JsepIceCandidate jice2("audio", 0, candidate2); + JsepIceCandidate jice3("video", 0, candidate1); + JsepIceCandidate jice4("video", 0, candidate2); + ASSERT_TRUE(jsep_desc_->AddCandidate(&jice1)); + ASSERT_TRUE(jsep_desc_->AddCandidate(&jice2)); + ASSERT_TRUE(jsep_desc_->AddCandidate(&jice3)); + ASSERT_TRUE(jsep_desc_->AddCandidate(&jice4)); + + std::string message = Serialize(jsep_desc_.get()); + EXPECT_EQ(message.find("c=IN IP6 ::3"), std::string::npos); + // Should have a c line like this one when no any default exists. + EXPECT_NE(message.find("c=IN IP4 0.0.0.0"), std::string::npos); +} + +// Tests that the connection address will be correctly set when the Candidate is +// removed. +TEST_F(JsepSessionDescriptionTest, RemoveCandidateAndSetConnectionAddress) { + cricket::Candidate candidate1( + cricket::ICE_CANDIDATE_COMPONENT_RTP, "udp", + rtc::SocketAddress("::1", 1234), kCandidatePriority, "", "", + cricket::LOCAL_PORT_TYPE, kCandidateGeneration, kCandidateFoundation); + candidate1.set_transport_name("audio"); + + cricket::Candidate candidate2( + cricket::ICE_CANDIDATE_COMPONENT_RTP, "tcp", + rtc::SocketAddress("::2", 1235), kCandidatePriority, "", "", + cricket::LOCAL_PORT_TYPE, kCandidateGeneration, kCandidateFoundation); + candidate2.set_transport_name("audio"); + + cricket::Candidate candidate3( + cricket::ICE_CANDIDATE_COMPONENT_RTP, "udp", + rtc::SocketAddress("192.168.1.1", 1236), kCandidatePriority, "", "", + cricket::LOCAL_PORT_TYPE, kCandidateGeneration, kCandidateFoundation); + candidate3.set_transport_name("audio"); + + JsepIceCandidate jice1("audio", 0, candidate1); + JsepIceCandidate jice2("audio", 0, candidate2); + JsepIceCandidate jice3("audio", 0, candidate3); + + size_t audio_index = 0; + auto media_desc = static_cast( + jsep_desc_->description()->contents()[audio_index].description); + + ASSERT_TRUE(jsep_desc_->AddCandidate(&jice1)); + ASSERT_TRUE(jsep_desc_->AddCandidate(&jice2)); + ASSERT_TRUE(jsep_desc_->AddCandidate(&jice3)); + + std::vector candidates; + EXPECT_EQ("192.168.1.1:1236", media_desc->connection_address().ToString()); + + candidates.push_back(candidate3); + ASSERT_TRUE(jsep_desc_->RemoveCandidates(candidates)); + EXPECT_EQ("[::1]:1234", media_desc->connection_address().ToString()); + + candidates.clear(); + candidates.push_back(candidate2); + ASSERT_TRUE(jsep_desc_->RemoveCandidates(candidates)); + EXPECT_EQ("[::1]:1234", media_desc->connection_address().ToString()); + + candidates.clear(); + candidates.push_back(candidate1); + ASSERT_TRUE(jsep_desc_->RemoveCandidates(candidates)); + EXPECT_EQ("0.0.0.0:9", media_desc->connection_address().ToString()); +} diff --git a/webrtc/pc/mediasession.h b/webrtc/pc/mediasession.h index a901e1d75a..9474256ee3 100644 --- a/webrtc/pc/mediasession.h +++ b/webrtc/pc/mediasession.h @@ -309,6 +309,16 @@ class MediaContentDescription : public ContentDescription { } int buffered_mode_latency() const { return buffered_mode_latency_; } + // https://tools.ietf.org/html/rfc4566#section-5.7 + // May be present at the media or session level of SDP. If present at both + // levels, the media-level attribute overwrites the session-level one. + void set_connection_address(const rtc::SocketAddress& address) { + connection_address_ = address; + } + const rtc::SocketAddress& connection_address() const { + return connection_address_; + } + protected: bool rtcp_mux_ = false; bool rtcp_reduced_size_ = false; @@ -324,6 +334,7 @@ class MediaContentDescription : public ContentDescription { bool partial_ = false; int buffered_mode_latency_ = kBufferedModeDisabled; MediaContentDirection direction_ = MD_SENDRECV; + rtc::SocketAddress connection_address_; }; template diff --git a/webrtc/pc/webrtcsdp.cc b/webrtc/pc/webrtcsdp.cc index 13d09a6ee9..3953773d83 100644 --- a/webrtc/pc/webrtcsdp.cc +++ b/webrtc/pc/webrtcsdp.cc @@ -249,11 +249,13 @@ static void BuildIceOptions(const std::vector& transport_options, std::string* message); static bool IsRtp(const std::string& protocol); static bool IsDtlsSctp(const std::string& protocol); -static bool ParseSessionDescription(const std::string& message, size_t* pos, +static bool ParseSessionDescription(const std::string& message, + size_t* pos, std::string* session_id, std::string* session_version, TransportDescription* session_td, RtpHeaderExtensions* session_extmaps, + rtc::SocketAddress* connection_addr, cricket::SessionDescription* desc, SdpParseError* error); static bool ParseGroupAttribute(const std::string& line, @@ -263,7 +265,9 @@ static bool ParseMediaDescription( const std::string& message, const TransportDescription& session_td, const RtpHeaderExtensions& session_extmaps, - size_t* pos, cricket::SessionDescription* desc, + size_t* pos, + const rtc::SocketAddress& session_connection_addr, + cricket::SessionDescription* desc, std::vector* candidates, SdpParseError* error); static bool ParseContent(const std::string& message, @@ -713,47 +717,6 @@ static void GetDefaultDestination( } } -// Update |mline|'s default destination and append a c line after it. -static void UpdateMediaDefaultDestination( - const std::vector& candidates, - const std::string& mline, - std::string* message) { - std::string new_lines; - AddLine(mline, &new_lines); - // RFC 4566 - // m= ... - std::vector fields; - rtc::split(mline, kSdpDelimiterSpace, &fields); - if (fields.size() < 3) { - return; - } - - std::ostringstream os; - std::string rtp_port, rtp_ip, addr_type; - GetDefaultDestination(candidates, ICE_CANDIDATE_COMPONENT_RTP, - &rtp_port, &rtp_ip, &addr_type); - // Found default RTP candidate. - // RFC 5245 - // The default candidates are added to the SDP as the default - // destination for media. For streams based on RTP, this is done by - // placing the IP address and port of the RTP candidate into the c and m - // lines, respectively. - // Update the port in the m line. - // If this is a m-line with port equal to 0, we don't change it. - if (fields[1] != kMediaPortRejected) { - new_lines.replace(fields[0].size() + 1, - fields[1].size(), - rtp_port); - } - // Add the c line. - // RFC 4566 - // c= - InitLine(kLineTypeConnection, kConnectionNettype, &os); - os << " " << addr_type << " " << rtp_ip; - AddLine(os.str(), &new_lines); - message->append(new_lines); -} - // Gets "a=rtcp" line if found default RTCP candidate from |candidates|. static std::string GetRtcpLine(const std::vector& candidates) { std::string rtcp_line, rtcp_port, rtcp_ip, addr_type; @@ -902,6 +865,7 @@ bool SdpDeserialize(const std::string& message, std::string session_version; TransportDescription session_td("", ""); RtpHeaderExtensions session_extmaps; + rtc::SocketAddress session_connection_addr; cricket::SessionDescription* desc = new cricket::SessionDescription(); std::vector candidates; size_t current_pos = 0; @@ -909,14 +873,15 @@ bool SdpDeserialize(const std::string& message, // Session Description if (!ParseSessionDescription(message, ¤t_pos, &session_id, &session_version, &session_td, &session_extmaps, - desc, error)) { + &session_connection_addr, desc, error)) { delete desc; return false; } // Media Description if (!ParseMediaDescription(message, session_td, session_extmaps, ¤t_pos, - desc, &candidates, error)) { + session_connection_addr, desc, &candidates, + error)) { delete desc; for (std::vector::const_iterator it = candidates.begin(); it != candidates.end(); ++it) { @@ -1315,9 +1280,12 @@ void BuildMediaDescription(const ContentInfo* content_info, // // However, the BUNDLE draft adds a new meaning to port zero, when used along // with a=bundle-only. - const std::string& port = - (content_info->rejected || content_info->bundle_only) ? kMediaPortRejected - : kDummyPort; + std::string port = kDummyPort; + if (content_info->rejected || content_info->bundle_only) { + port = kMediaPortRejected; + } else if (!media_desc->connection_address().IsNil()) { + port = rtc::ToString(media_desc->connection_address().port()); + } rtc::SSLFingerprint* fp = (transport_info) ? transport_info->description.identity_fingerprint.get() : NULL; @@ -1325,8 +1293,19 @@ void BuildMediaDescription(const ContentInfo* content_info, // Add the m and c lines. InitLine(kLineTypeMedia, type, &os); os << " " << port << " " << media_desc->protocol() << fmt; - std::string mline = os.str(); - UpdateMediaDefaultDestination(candidates, mline, message); + AddLine(os.str(), message); + + InitLine(kLineTypeConnection, kConnectionNettype, &os); + if (media_desc->connection_address().IsNil()) { + os << " " << kConnectionIpv4Addrtype << " " << kDummyAddress; + } else if (media_desc->connection_address().family() == AF_INET) { + os << " " << kConnectionIpv4Addrtype << " " + << media_desc->connection_address().ipaddr().ToString(); + } else { + os << " " << kConnectionIpv6Addrtype << " " + << media_desc->connection_address().ipaddr().ToString(); + } + AddLine(os.str(), message); // RFC 4566 // b=AS: @@ -1900,11 +1879,62 @@ bool IsDtlsSctp(const std::string& protocol) { return protocol.find(cricket::kMediaProtocolDtlsSctp) != std::string::npos; } -bool ParseSessionDescription(const std::string& message, size_t* pos, +bool ParseConnectionData(const std::string& line, + rtc::SocketAddress* addr, + SdpParseError* error) { + // Parse the line from left to right. + std::string token; + std::string rightpart; + // RFC 4566 + // c= + // Skip the "c=" + if (!rtc::tokenize_first(line, kSdpDelimiterEqual, &token, &rightpart)) { + return ParseFailed(line, "Failed to parse the network type.", error); + } + + // Extract and verify the + if (!rtc::tokenize_first(rightpart, kSdpDelimiterSpace, &token, &rightpart) || + token != kConnectionNettype) { + return ParseFailed(line, + "Failed to parse the connection data. The network type " + "is not currently supported.", + error); + } + + // Extract the "" and "". + if (!rtc::tokenize_first(rightpart, kSdpDelimiterSpace, &token, &rightpart)) { + return ParseFailed(line, "Failed to parse the address type.", error); + } + + // The rightpart part should be the IP address without the slash which is used + // for multicast. + if (rightpart.find('/') != std::string::npos) { + return ParseFailed(line, + "Failed to parse the connection data. Multicast is not " + "currently supported.", + error); + } + addr->SetIP(rightpart); + + // Verify that the addrtype matches the type of the parsed address. + if ((addr->family() == AF_INET && token != "IP4") || + (addr->family() == AF_INET6 && token != "IP6")) { + addr->Clear(); + return ParseFailed( + line, + "Failed to parse the connection data. The address type is mismatching.", + error); + } + return true; +} + +bool ParseSessionDescription(const std::string& message, + size_t* pos, std::string* session_id, std::string* session_version, TransportDescription* session_td, RtpHeaderExtensions* session_extmaps, + rtc::SocketAddress* connection_addr, cricket::SessionDescription* desc, SdpParseError* error) { std::string line; @@ -1962,7 +1992,11 @@ bool ParseSessionDescription(const std::string& message, size_t* pos, // RFC 4566 // c=* (connection information -- not required if included in // all media) - GetLineWithType(message, pos, &line, kLineTypeConnection); + if (GetLineWithType(message, pos, &line, kLineTypeConnection)) { + if (!ParseConnectionData(line, connection_addr, error)) { + return false; + } + } // RFC 4566 // b=* (zero or more bandwidth information lines) @@ -2266,7 +2300,7 @@ static C* ParseContentDescription(const std::string& message, pos, content_name, bundle_only, media_desc, transport, candidates, error)) { delete media_desc; - return NULL; + return nullptr; } // Sort the codecs according to the m-line fmt list. std::unordered_map payload_type_preferences; @@ -2291,6 +2325,7 @@ bool ParseMediaDescription(const std::string& message, const TransportDescription& session_td, const RtpHeaderExtensions& session_extmaps, size_t* pos, + const rtc::SocketAddress& session_connection_addr, cricket::SessionDescription* desc, std::vector* candidates, SdpParseError* error) { @@ -2320,6 +2355,10 @@ bool ParseMediaDescription(const std::string& message, port_rejected = true; } + int port = 0; + if (!rtc::FromString(fields[1], &port) || !IsValidPort(port)) { + return ParseFailed(line, "The port number is invalid", error); + } std::string protocol = fields[2]; // @@ -2420,6 +2459,16 @@ bool ParseMediaDescription(const std::string& message, } } content->set_protocol(protocol); + + // Use the session level connection address if the media level addresses are + // not specified. + rtc::SocketAddress address; + address = content->connection_address().IsNil() + ? session_connection_addr + : content->connection_address(); + address.SetPort(port); + content->set_connection_address(address); + desc->AddContent(content_name, IsDtlsSctp(protocol) ? cricket::NS_JINGLE_DRAFT_SCTP : cricket::NS_JINGLE_RTP, @@ -2668,6 +2717,16 @@ bool ParseContent(const std::string& message, continue; } + // Parse the media level connection data. + if (IsLineType(line, kLineTypeConnection)) { + rtc::SocketAddress addr; + if (!ParseConnectionData(line, &addr, error)) { + return false; + } + media_desc->set_connection_address(addr); + continue; + } + if (!IsLineType(line, kLineTypeAttributes)) { // TODO: Handle other lines if needed. LOG(LS_INFO) << "Ignored line: " << line; diff --git a/webrtc/pc/webrtcsdp_unittest.cc b/webrtc/pc/webrtcsdp_unittest.cc index f5cac8921f..60620918b2 100644 --- a/webrtc/pc/webrtcsdp_unittest.cc +++ b/webrtc/pc/webrtcsdp_unittest.cc @@ -875,6 +875,8 @@ class WebRtcSdpTest : public testing::Test { audio_stream.sync_label = kStreamLabel1; audio_stream.ssrcs.push_back(kAudioTrack1Ssrc); audio_desc_->AddStream(audio_stream); + rtc::SocketAddress audio_addr("74.125.127.126", 2345); + audio_desc_->set_connection_address(audio_addr); desc_.AddContent(kAudioContentName, NS_JINGLE_RTP, audio_desc_); // VideoContentDescription @@ -888,6 +890,8 @@ class WebRtcSdpTest : public testing::Test { cricket::SsrcGroup ssrc_group(kFecSsrcGroupSemantics, video_stream.ssrcs); video_stream.ssrc_groups.push_back(ssrc_group); video_desc_->AddStream(video_stream); + rtc::SocketAddress video_addr("74.125.224.39", 3457); + video_desc_->set_connection_address(video_addr); desc_.AddContent(kVideoContentName, NS_JINGLE_RTP, video_desc_); // TransportInfo @@ -1090,7 +1094,6 @@ class WebRtcSdpTest : public testing::Test { desc_.AddContent(kAudioContentName2, NS_JINGLE_RTP, audio_desc_2); EXPECT_TRUE(desc_.AddTransportInfo(TransportInfo( kAudioContentName2, TransportDescription(kUfragVoice2, kPwdVoice2)))); - // Video track 2, in stream 2. VideoContentDescription* video_desc_2 = CreateVideoContentDescription(); StreamParams video_track_2; @@ -1470,6 +1473,7 @@ class WebRtcSdpTest : public testing::Test { audio_desc_->Copy()); video_desc_ = static_cast( video_desc_->Copy()); + desc_.RemoveContentByName(kAudioContentName); desc_.RemoveContentByName(kVideoContentName); desc_.AddContent(kAudioContentName, NS_JINGLE_RTP, audio_rejected, @@ -1485,10 +1489,7 @@ class WebRtcSdpTest : public testing::Test { ReplaceRejected(audio_rejected, video_rejected, &new_sdp); JsepSessionDescription jdesc_no_candidates(kDummyString); - if (!jdesc_no_candidates.Initialize(desc_.Copy(), kSessionId, - kSessionVersion)) { - return false; - } + MakeDescriptionWithoutCandidates(&jdesc_no_candidates); std::string message = webrtc::SdpSerialize(jdesc_no_candidates, false); EXPECT_EQ(new_sdp, message); return true; @@ -1766,6 +1767,18 @@ class WebRtcSdpTest : public testing::Test { EXPECT_TRUE(CompareSessionDescription(jdesc, jdesc_output_des)); } + // Calling 'Initialize' with a copy of the inner SessionDescription will + // create a copy of the JsepSessionDescription without candidates. The + // 'connection address' field, previously set from the candidates, must also + // be reset. + void MakeDescriptionWithoutCandidates(JsepSessionDescription* jdesc) { + rtc::SocketAddress audio_addr("0.0.0.0", 9); + rtc::SocketAddress video_addr("0.0.0.0", 9); + audio_desc_->set_connection_address(audio_addr); + video_desc_->set_connection_address(video_addr); + ASSERT_TRUE(jdesc->Initialize(desc_.Copy(), kSessionId, kSessionVersion)); + } + protected: SessionDescription desc_; AudioContentDescription* audio_desc_; @@ -1801,134 +1814,12 @@ TEST_F(WebRtcSdpTest, SerializeSessionDescriptionEmpty) { EXPECT_EQ("", webrtc::SdpSerialize(jdesc_empty, false)); } -// This tests serialization of SDP with only IPv6 candidates and verifies that -// IPv6 is used as default address in c line according to preference. -TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithIPv6Only) { - // Only test 1 m line. - desc_.RemoveContentByName("video_content_name"); - // Stun has a high preference than local host. - cricket::Candidate candidate1( - cricket::ICE_CANDIDATE_COMPONENT_RTP, "udp", - rtc::SocketAddress("::1", 1234), kCandidatePriority, "", "", - cricket::STUN_PORT_TYPE, kCandidateGeneration, kCandidateFoundation1); - cricket::Candidate candidate2( - cricket::ICE_CANDIDATE_COMPONENT_RTP, "udp", - rtc::SocketAddress("::2", 1235), kCandidatePriority, "", "", - cricket::LOCAL_PORT_TYPE, kCandidateGeneration, kCandidateFoundation1); - JsepSessionDescription jdesc(kDummyString); - ASSERT_TRUE(jdesc.Initialize(desc_.Copy(), kSessionId, kSessionVersion)); - - // Only add the candidates to audio m line. - JsepIceCandidate jice1("audio_content_name", 0, candidate1); - JsepIceCandidate jice2("audio_content_name", 0, candidate2); - ASSERT_TRUE(jdesc.AddCandidate(&jice1)); - ASSERT_TRUE(jdesc.AddCandidate(&jice2)); - std::string message = webrtc::SdpSerialize(jdesc, false); - - // Audio line should have a c line like this one. - EXPECT_NE(message.find("c=IN IP6 ::1"), std::string::npos); - // Shouldn't have a IP4 c line. - EXPECT_EQ(message.find("c=IN IP4"), std::string::npos); -} - -// This tests serialization of SDP with both IPv4 and IPv6 candidates and -// verifies that IPv4 is used as default address in c line even if the -// preference of IPv4 is lower. -TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithBothIPFamilies) { - // Only test 1 m line. - desc_.RemoveContentByName("video_content_name"); - cricket::Candidate candidate_v4( - cricket::ICE_CANDIDATE_COMPONENT_RTP, "udp", - rtc::SocketAddress("192.168.1.5", 1234), kCandidatePriority, "", "", - cricket::STUN_PORT_TYPE, kCandidateGeneration, kCandidateFoundation1); - cricket::Candidate candidate_v6( - cricket::ICE_CANDIDATE_COMPONENT_RTP, "udp", - rtc::SocketAddress("::1", 1234), kCandidatePriority, "", "", - cricket::LOCAL_PORT_TYPE, kCandidateGeneration, kCandidateFoundation1); - JsepSessionDescription jdesc(kDummyString); - ASSERT_TRUE(jdesc.Initialize(desc_.Copy(), kSessionId, kSessionVersion)); - - // Only add the candidates to audio m line. - JsepIceCandidate jice_v4("audio_content_name", 0, candidate_v4); - JsepIceCandidate jice_v6("audio_content_name", 0, candidate_v6); - ASSERT_TRUE(jdesc.AddCandidate(&jice_v4)); - ASSERT_TRUE(jdesc.AddCandidate(&jice_v6)); - std::string message = webrtc::SdpSerialize(jdesc, false); - - // Audio line should have a c line like this one. - EXPECT_NE(message.find("c=IN IP4 192.168.1.5"), std::string::npos); - // Shouldn't have a IP6 c line. - EXPECT_EQ(message.find("c=IN IP6"), std::string::npos); -} - -// This tests serialization of SDP with both UDP and TCP candidates and -// verifies that UDP is used as default address in c line even if the -// preference of UDP is lower. -TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithBothProtocols) { - // Only test 1 m line. - desc_.RemoveContentByName("video_content_name"); - // Stun has a high preference than local host. - cricket::Candidate candidate1( - cricket::ICE_CANDIDATE_COMPONENT_RTP, "tcp", - rtc::SocketAddress("::1", 1234), kCandidatePriority, "", "", - cricket::STUN_PORT_TYPE, kCandidateGeneration, kCandidateFoundation1); - cricket::Candidate candidate2( - cricket::ICE_CANDIDATE_COMPONENT_RTP, "udp", - rtc::SocketAddress("fe80::1234:5678:abcd:ef12", 1235), kCandidatePriority, - "", "", cricket::LOCAL_PORT_TYPE, kCandidateGeneration, - kCandidateFoundation1); - JsepSessionDescription jdesc(kDummyString); - ASSERT_TRUE(jdesc.Initialize(desc_.Copy(), kSessionId, kSessionVersion)); - - // Only add the candidates to audio m line. - JsepIceCandidate jice1("audio_content_name", 0, candidate1); - JsepIceCandidate jice2("audio_content_name", 0, candidate2); - ASSERT_TRUE(jdesc.AddCandidate(&jice1)); - ASSERT_TRUE(jdesc.AddCandidate(&jice2)); - std::string message = webrtc::SdpSerialize(jdesc, false); - - // Audio line should have a c line like this one. - EXPECT_NE(message.find("c=IN IP6 fe80::1234:5678:abcd:ef12"), - std::string::npos); - // Shouldn't have a IP4 c line. - EXPECT_EQ(message.find("c=IN IP4"), std::string::npos); -} - -// This tests serialization of SDP with only TCP candidates and verifies that -// null IPv4 is used as default address in c line. -TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithTCPOnly) { - // Only test 1 m line. - desc_.RemoveContentByName("video_content_name"); - // Stun has a high preference than local host. - cricket::Candidate candidate1( - cricket::ICE_CANDIDATE_COMPONENT_RTP, "tcp", - rtc::SocketAddress("::1", 1234), kCandidatePriority, "", "", - cricket::STUN_PORT_TYPE, kCandidateGeneration, kCandidateFoundation1); - cricket::Candidate candidate2( - cricket::ICE_CANDIDATE_COMPONENT_RTP, "tcp", - rtc::SocketAddress("::2", 1235), kCandidatePriority, "", "", - cricket::LOCAL_PORT_TYPE, kCandidateGeneration, kCandidateFoundation1); - JsepSessionDescription jdesc(kDummyString); - ASSERT_TRUE(jdesc.Initialize(desc_.Copy(), kSessionId, kSessionVersion)); - - // Only add the candidates to audio m line. - JsepIceCandidate jice1("audio_content_name", 0, candidate1); - JsepIceCandidate jice2("audio_content_name", 0, candidate2); - ASSERT_TRUE(jdesc.AddCandidate(&jice1)); - ASSERT_TRUE(jdesc.AddCandidate(&jice2)); - std::string message = webrtc::SdpSerialize(jdesc, false); - - // Audio line should have a c line like this one when no any default exists. - EXPECT_NE(message.find("c=IN IP4 0.0.0.0"), std::string::npos); -} - // This tests serialization of SDP with a=crypto and a=fingerprint, as would be // the case in a DTLS offer. TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithFingerprint) { AddFingerprint(); JsepSessionDescription jdesc_with_fingerprint(kDummyString); - ASSERT_TRUE(jdesc_with_fingerprint.Initialize(desc_.Copy(), - kSessionId, kSessionVersion)); + MakeDescriptionWithoutCandidates(&jdesc_with_fingerprint); std::string message = webrtc::SdpSerialize(jdesc_with_fingerprint, false); std::string sdp_with_fingerprint = kSdpString; @@ -1946,8 +1837,7 @@ TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithFingerprintNoCryptos) { AddFingerprint(); RemoveCryptos(); JsepSessionDescription jdesc_with_fingerprint(kDummyString); - ASSERT_TRUE(jdesc_with_fingerprint.Initialize(desc_.Copy(), - kSessionId, kSessionVersion)); + MakeDescriptionWithoutCandidates(&jdesc_with_fingerprint); std::string message = webrtc::SdpSerialize(jdesc_with_fingerprint, false); std::string sdp_with_fingerprint = kSdpString; @@ -1964,8 +1854,7 @@ TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithFingerprintNoCryptos) { TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithoutCandidates) { // JsepSessionDescription with desc but without candidates. JsepSessionDescription jdesc_no_candidates(kDummyString); - ASSERT_TRUE(jdesc_no_candidates.Initialize(desc_.Copy(), kSessionId, - kSessionVersion)); + MakeDescriptionWithoutCandidates(&jdesc_no_candidates); std::string message = webrtc::SdpSerialize(jdesc_no_candidates, false); EXPECT_EQ(std::string(kSdpString), message); } @@ -2058,7 +1947,7 @@ TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithRtpDataChannel) { AddRtpDataChannel(); JsepSessionDescription jsep_desc(kDummyString); - ASSERT_TRUE(jsep_desc.Initialize(desc_.Copy(), kSessionId, kSessionVersion)); + MakeDescriptionWithoutCandidates(&jsep_desc); std::string message = webrtc::SdpSerialize(jsep_desc, false); std::string expected_sdp = kSdpString; @@ -2071,7 +1960,7 @@ TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithSctpDataChannel) { AddSctpDataChannel(use_sctpmap); JsepSessionDescription jsep_desc(kDummyString); - ASSERT_TRUE(jsep_desc.Initialize(desc_.Copy(), kSessionId, kSessionVersion)); + MakeDescriptionWithoutCandidates(&jsep_desc); std::string message = webrtc::SdpSerialize(jsep_desc, false); std::string expected_sdp = kSdpString; @@ -2083,8 +1972,7 @@ TEST_F(WebRtcSdpTest, SerializeWithSctpDataChannelAndNewPort) { bool use_sctpmap = true; AddSctpDataChannel(use_sctpmap); JsepSessionDescription jsep_desc(kDummyString); - - ASSERT_TRUE(jsep_desc.Initialize(desc_.Copy(), kSessionId, kSessionVersion)); + MakeDescriptionWithoutCandidates(&jsep_desc); DataContentDescription* dcdesc = static_cast( jsep_desc.description()->GetContentDescriptionByName(kDataContentName)); @@ -2112,11 +2000,10 @@ TEST_F(WebRtcSdpTest, SerializeWithSctpDataChannelAndNewPort) { } TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithDataChannelAndBandwidth) { + JsepSessionDescription jsep_desc(kDummyString); AddRtpDataChannel(); data_desc_->set_bandwidth(100*1000); - JsepSessionDescription jsep_desc(kDummyString); - - ASSERT_TRUE(jsep_desc.Initialize(desc_.Copy(), kSessionId, kSessionVersion)); + MakeDescriptionWithoutCandidates(&jsep_desc); std::string message = webrtc::SdpSerialize(jsep_desc, false); std::string expected_sdp = kSdpString; @@ -2131,8 +2018,7 @@ TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithDataChannelAndBandwidth) { TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithExtmap) { AddExtmap(); JsepSessionDescription desc_with_extmap("dummy"); - ASSERT_TRUE(desc_with_extmap.Initialize(desc_.Copy(), - kSessionId, kSessionVersion)); + MakeDescriptionWithoutCandidates(&desc_with_extmap); std::string message = webrtc::SdpSerialize(desc_with_extmap, false); std::string sdp_with_extmap = kSdpString; @@ -3504,3 +3390,121 @@ TEST_F(WebRtcSdpTest, DeserializeMsidAttributeWithMissingStreamId) { JsepSessionDescription jdesc_output(kDummyString); EXPECT_FALSE(SdpDeserialize(kSdpWithMissingStreamId, &jdesc_output)); } + +// Tests that if both session-level address and media-level address exist, use +// the media-level address. +TEST_F(WebRtcSdpTest, ParseConnectionData) { + JsepSessionDescription jsep_desc(kDummyString); + + // Sesssion-level address. + std::string sdp = kSdpFullString; + InjectAfter("s=-\r\n", "c=IN IP4 192.168.0.3\r\n", &sdp); + EXPECT_TRUE(SdpDeserialize(sdp, &jsep_desc)); + + const auto& content1 = jsep_desc.description()->contents()[0]; + EXPECT_EQ("74.125.127.126:2345", + static_cast(content1.description) + ->connection_address() + .ToString()); + const auto& content2 = jsep_desc.description()->contents()[1]; + EXPECT_EQ("74.125.224.39:3457", + static_cast(content2.description) + ->connection_address() + .ToString()); +} + +// Tests that the session-level connection address will be used if the media +// level-addresses are not specified. +TEST_F(WebRtcSdpTest, ParseConnectionDataSessionLevelOnly) { + JsepSessionDescription jsep_desc(kDummyString); + + // Sesssion-level address. + std::string sdp = kSdpString; + InjectAfter("s=-\r\n", "c=IN IP4 192.168.0.3\r\n", &sdp); + // Remove the media level addresses. + Replace("c=IN IP4 0.0.0.0\r\n", "", &sdp); + Replace("c=IN IP4 0.0.0.0\r\n", "", &sdp); + EXPECT_TRUE(SdpDeserialize(sdp, &jsep_desc)); + + const auto& content1 = jsep_desc.description()->contents()[0]; + EXPECT_EQ("192.168.0.3:9", + static_cast(content1.description) + ->connection_address() + .ToString()); + const auto& content2 = jsep_desc.description()->contents()[1]; + EXPECT_EQ("192.168.0.3:9", + static_cast(content2.description) + ->connection_address() + .ToString()); +} + +TEST_F(WebRtcSdpTest, ParseConnectionDataIPv6) { + JsepSessionDescription jsep_desc(kDummyString); + + std::string sdp = kSdpString; + EXPECT_TRUE(SdpDeserialize(sdp, &jsep_desc)); + Replace("m=audio 9 RTP/SAVPF 111 103 104\r\nc=IN IP4 0.0.0.0\r\n", + "m=audio 9 RTP/SAVPF 111 103 104\r\nc=IN IP6 " + "2001:0db8:85a3:0000:0000:8a2e:0370:7335\r\n", + &sdp); + Replace("m=video 9 RTP/SAVPF 120\r\nc=IN IP4 0.0.0.0\r\n", + "m=video 9 RTP/SAVPF 120\r\nc=IN IP6 " + "2001:0db8:85a3:0000:0000:8a2e:0370:7336\r\n", + &sdp); + EXPECT_TRUE(SdpDeserialize(sdp, &jsep_desc)); + const auto& content1 = jsep_desc.description()->contents()[0]; + EXPECT_EQ("[2001:db8:85a3::8a2e:370:7335]:9", + static_cast(content1.description) + ->connection_address() + .ToString()); + const auto& content2 = jsep_desc.description()->contents()[1]; + EXPECT_EQ("[2001:db8:85a3::8a2e:370:7336]:9", + static_cast(content2.description) + ->connection_address() + .ToString()); +} + +// Test that the invalid or unsupprted connection data cannot be parsed. +TEST_F(WebRtcSdpTest, ParseConnectionDataFailure) { + JsepSessionDescription jsep_desc(kDummyString); + std::string sdp = kSdpString; + EXPECT_TRUE(SdpDeserialize(sdp, &jsep_desc)); + + // Unsupported multicast IPv4 address. + sdp = kSdpFullString; + Replace("c=IN IP4 74.125.224.39\r\n", "c=IN IP4 74.125.224.39/127\r\n", &sdp); + EXPECT_FALSE(SdpDeserialize(sdp, &jsep_desc)); + + // Unsupported multicast IPv6 address. + sdp = kSdpFullString; + Replace("c=IN IP4 74.125.224.39\r\n", "c=IN IP6 ::1/3\r\n", &sdp); + EXPECT_FALSE(SdpDeserialize(sdp, &jsep_desc)); + + // Mismatched address type. + sdp = kSdpFullString; + Replace("c=IN IP4 74.125.224.39\r\n", "c=IN IP6 74.125.224.39\r\n", &sdp); + EXPECT_FALSE(SdpDeserialize(sdp, &jsep_desc)); + + sdp = kSdpFullString; + Replace("c=IN IP4 74.125.224.39\r\n", + "c=IN IP4 2001:0db8:85a3:0000:0000:8a2e:0370:7334\r\n", &sdp); + EXPECT_FALSE(SdpDeserialize(sdp, &jsep_desc)); +} + +TEST_F(WebRtcSdpTest, SerializeAndDeserializeWithConnectionAddress) { + JsepSessionDescription expected_jsep(kDummyString); + MakeDescriptionWithoutCandidates(&expected_jsep); + // Serialization. + std::string message = webrtc::SdpSerialize(expected_jsep, false); + // Deserialization. + JsepSessionDescription jdesc(kDummyString); + EXPECT_TRUE(SdpDeserialize(message, &jdesc)); + auto audio_desc = static_cast( + jdesc.description()->GetContentByName(kAudioContentName)->description); + auto video_desc = static_cast( + jdesc.description()->GetContentByName(kVideoContentName)->description); + EXPECT_EQ(audio_desc_->connection_address().ToString(), + audio_desc->connection_address().ToString()); + EXPECT_EQ(video_desc_->connection_address().ToString(), + video_desc->connection_address().ToString()); +} diff --git a/webrtc/pc/webrtcsession_unittest.cc b/webrtc/pc/webrtcsession_unittest.cc index b80949ce10..d19f26a6b3 100644 --- a/webrtc/pc/webrtcsession_unittest.cc +++ b/webrtc/pc/webrtcsession_unittest.cc @@ -2168,7 +2168,7 @@ TEST_F(WebRtcSessionTest, TestAddAndRemoveRemoteCandidates) { SendAudioVideoStream1(); cricket::Candidate candidate(1, "udp", rtc::SocketAddress("1.1.1.1", 5000), 0, - "", "", "host", 0, ""); + "", "", "local", 0, ""); candidate.set_transport_name("audio"); JsepIceCandidate ice_candidate1(kMediaContentName0, 0, candidate); @@ -3077,6 +3077,7 @@ TEST_F(WebRtcSessionTest, TestIgnoreCandidatesForUnusedTransportWhenBundling) { candidate0.set_address(rtc::SocketAddress("1.1.1.1", 5000)); candidate0.set_component(1); candidate0.set_protocol("udp"); + candidate0.set_type("local"); JsepIceCandidate ice_candidate0(kMediaContentName0, kMediaContentIndex0, candidate0); EXPECT_TRUE(session_->ProcessIceMessage(&ice_candidate0)); @@ -3086,6 +3087,7 @@ TEST_F(WebRtcSessionTest, TestIgnoreCandidatesForUnusedTransportWhenBundling) { candidate1.set_address(rtc::SocketAddress("1.1.1.1", 6000)); candidate1.set_component(1); candidate1.set_protocol("udp"); + candidate1.set_type("local"); JsepIceCandidate ice_candidate1(kMediaContentName1, kMediaContentIndex1, candidate1); EXPECT_TRUE(session_->ProcessIceMessage(&ice_candidate1)); @@ -3095,6 +3097,7 @@ TEST_F(WebRtcSessionTest, TestIgnoreCandidatesForUnusedTransportWhenBundling) { candidate2.set_address(rtc::SocketAddress("1.1.1.1", 5001)); candidate2.set_component(1); candidate2.set_protocol("udp"); + candidate2.set_type("local"); JsepIceCandidate ice_candidate2(kMediaContentName0, kMediaContentIndex0, candidate2); EXPECT_TRUE(session_->ProcessIceMessage(&ice_candidate2));