/* * Copyright 2017 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "api/jsep.h" #include #include #include #include #include #include #include #include #include "absl/memory/memory.h" #include "absl/strings/string_view.h" #include "api/candidate.h" #include "api/sequence_checker.h" #include "api/webrtc_sdp.h" #include "p2p/base/p2p_constants.h" #include "p2p/base/transport_info.h" #include "pc/session_description.h" #include "rtc_base/checks.h" #include "rtc_base/ip_address.h" #include "rtc_base/net_helper.h" #include "rtc_base/net_helpers.h" #include "rtc_base/socket_address.h" namespace webrtc { namespace { constexpr char kDummyAddress[] = "0.0.0.0"; constexpr int kDummyPort = 9; // Update the connection address for the MediaContentDescription based on the // candidates. void UpdateConnectionAddress(const IceCandidateCollection& candidate_collection, MediaContentDescription* media_desc) { int port = kDummyPort; std::string ip = kDummyAddress; std::string hostname; int current_preference = 0; // Start with lowest preference. int current_family = AF_UNSPEC; for (size_t i = 0; i < candidate_collection.count(); ++i) { const IceCandidate* jsep_candidate = candidate_collection.at(i); if (jsep_candidate->candidate().component() != ICE_CANDIDATE_COMPONENT_RTP) { continue; } // Default destination should be UDP only. if (jsep_candidate->candidate().protocol() != UDP_PROTOCOL_NAME) { continue; } const int preference = jsep_candidate->candidate().type_preference(); 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; const SocketAddress& candidate_addr = jsep_candidate->candidate().address(); port = candidate_addr.port(); ip = candidate_addr.ipaddr().ToString(); hostname = candidate_addr.hostname(); } SocketAddress connection_addr(ip, port); if (IPIsUnspec(connection_addr.ipaddr()) && !hostname.empty()) { // When a hostname candidate becomes the (default) connection address, // we use the dummy address 0.0.0.0 and port 9 in the c= and the m= lines. // // We have observed in deployment that with a FQDN in a c= line, SDP parsing // could fail in other JSEP implementations. We note that the wildcard // addresses (0.0.0.0 or ::) with port 9 are given the exception as the // connection address that will not result in an ICE mismatch // (draft-ietf-mmusic-ice-sip-sdp). Also, 0.0.0.0 or :: can be used as the // connection address in the initial offer or answer with trickle ICE // if the offerer or answerer does not want to include the host IP address // (draft-ietf-mmusic-trickle-ice-sip), and in particular 0.0.0.0 has been // widely deployed for this use without outstanding compatibility issues. // Combining the above considerations, we use 0.0.0.0 with port 9 to // populate the c= and the m= lines. See `BuildMediaDescription` in // webrtc_sdp.cc for the SDP generation with // `media_desc->connection_address()`. connection_addr = SocketAddress(kDummyAddress, kDummyPort); } media_desc->set_connection_address(connection_addr); } std::vector CloneCandidateCollection( const std::vector& original) { std::vector ret; ret.reserve(original.size()); for (const auto& collection : original) { ret.push_back(collection.Clone()); } return ret; } } // namespace const char SessionDescriptionInterface::kOffer[] = "offer"; const char SessionDescriptionInterface::kPrAnswer[] = "pranswer"; const char SessionDescriptionInterface::kAnswer[] = "answer"; const char SessionDescriptionInterface::kRollback[] = "rollback"; const char* SdpTypeToString(SdpType type) { switch (type) { case SdpType::kOffer: return SessionDescriptionInterface::kOffer; case SdpType::kPrAnswer: return SessionDescriptionInterface::kPrAnswer; case SdpType::kAnswer: return SessionDescriptionInterface::kAnswer; case SdpType::kRollback: return SessionDescriptionInterface::kRollback; } return ""; } std::optional SdpTypeFromString(const std::string& type_str) { if (type_str == SessionDescriptionInterface::kOffer) { return SdpType::kOffer; } else if (type_str == SessionDescriptionInterface::kPrAnswer) { return SdpType::kPrAnswer; } else if (type_str == SessionDescriptionInterface::kAnswer) { return SdpType::kAnswer; } else if (type_str == SessionDescriptionInterface::kRollback) { return SdpType::kRollback; } else { return std::nullopt; } } std::unique_ptr CreateIceCandidate(absl::string_view sdp_mid, int sdp_mline_index, const Candidate& candidate) { return std::make_unique(sdp_mid, sdp_mline_index, candidate); } IceCandidate* CreateIceCandidate(absl::string_view sdp_mid, int sdp_mline_index, const std::string& sdp, SdpParseError* error) { return IceCandidate::Create(sdp_mid, sdp_mline_index, sdp, error).release(); } std::unique_ptr CreateSessionDescription( SdpType type, absl::string_view sdp) { return CreateSessionDescription(type, sdp, nullptr); } std::unique_ptr CreateSessionDescription( SdpType type, absl::string_view sdp, SdpParseError* error_out) { if (type == SdpType::kRollback) { return CreateRollbackSessionDescription(); } return SdpDeserialize(type, sdp, error_out); } std::unique_ptr CreateSessionDescription( SdpType type, absl::string_view session_id, absl::string_view session_version, std::unique_ptr description) { return SessionDescriptionInterface::Create(type, std::move(description), session_id, session_version); } std::unique_ptr CreateRollbackSessionDescription( absl::string_view session_id, absl::string_view session_version) { return SessionDescriptionInterface::Create( SdpType::kRollback, /*description=*/nullptr, session_id, session_version); } // static std::unique_ptr SessionDescriptionInterface::Create( SdpType type, std::unique_ptr description, absl::string_view id, absl::string_view version, std::vector candidates) { if (!description && type != SdpType::kRollback) return nullptr; return absl::WrapUnique(new SessionDescriptionInterface( type, std::move(description), id, version, std::move(candidates))); } SessionDescriptionInterface::~SessionDescriptionInterface() = default; void SessionDescriptionInterface::RelinquishThreadOwnership() { // Ideally we should require that the method can only be called from the // thread that the sequence checker is currently attached to. However that's // not compatible with some cases outside of webrtc where initializations // happens on one thread and then the object is moved to a second thread (e.g. // signaling) where a call is made into webrtc. At that point we'd hit a // dcheck like this in webrtc: RTC_DCHECK_RUN_ON(&sequence_checker_); sequence_checker_.Detach(); // Tie the checker to the current thread, which permits iterating // `candidate_collection_` RTC_DCHECK_RUN_ON(&sequence_checker_); for (IceCandidateCollection& collection : candidate_collection_) { collection.RelinquishThreadOwnership(); } sequence_checker_.Detach(); // Unties the checker from the current thread. } SessionDescriptionInterface::SessionDescriptionInterface( SdpType type, std::unique_ptr desc, absl::string_view id, absl::string_view version, std::vector candidates) : sdp_type_(type), id_(id), version_(version), description_(std::move(desc)), candidate_collection_(std::move(candidates)) { RTC_DCHECK(description() || type == SdpType::kRollback); RTC_DCHECK(candidate_collection_.empty() || candidate_collection_.size() == number_of_mediasections()); candidate_collection_.resize(number_of_mediasections()); } size_t SessionDescriptionInterface::number_of_mediasections() const { return description_ ? description_->contents().size() : 0u; } std::unique_ptr SessionDescriptionInterface::Clone() const { RTC_DCHECK_RUN_ON(&sequence_checker_); return SessionDescriptionInterface::Create( sdp_type_, description() ? description()->Clone() : nullptr, id(), version(), CloneCandidateCollection(candidate_collection_)); } bool SessionDescriptionInterface::AddCandidate(const IceCandidate* candidate) { RTC_DCHECK_RUN_ON(&sequence_checker_); if (!candidate) return false; size_t index = 0; if (!GetMediasectionIndex(candidate, &index)) { return false; } ContentInfo& content = description()->contents()[index]; const TransportInfo* transport_info = description()->GetTransportInfoByName(content.mid()); if (!transport_info) { return false; } Candidate updated_candidate = candidate->candidate(); if (updated_candidate.username().empty()) { updated_candidate.set_username(transport_info->description.ice_ufrag); } if (updated_candidate.password().empty()) { updated_candidate.set_password(transport_info->description.ice_pwd); } // Use `content.mid()` as the mid for the updated candidate. The // `candidate->sdp_mid()` property *should* be the same. However, in some // cases specifying an empty mid but a valid index is a way to add a candidate // without knowing (or caring about) the mid. This is done in several tests. RTC_DCHECK(candidate->sdp_mid().empty() || candidate->sdp_mid() == content.mid()) << "sdp_mid='" << candidate->sdp_mid() << "' content.mid()='" << content.mid() << "'"; auto updated_candidate_wrapper = std::make_unique( content.mid(), static_cast(index), updated_candidate); IceCandidateCollection& candidates = candidate_collection_[index]; if (!candidates.HasCandidate(updated_candidate_wrapper.get())) { candidates.add(std::move(updated_candidate_wrapper)); UpdateConnectionAddress(candidates, content.media_description()); } return true; } bool SessionDescriptionInterface::RemoveCandidate( const IceCandidate* candidate) { RTC_DCHECK_RUN_ON(&sequence_checker_); size_t index = 0u; if (!GetMediasectionIndex(candidate, &index)) { return false; } IceCandidateCollection& candidates = candidate_collection_[index]; if (!candidates.remove(candidate)) { return false; } UpdateConnectionAddress(candidates, description()->contents()[index].media_description()); return true; } const IceCandidateCollection* SessionDescriptionInterface::candidates( size_t mediasection_index) const { RTC_DCHECK_RUN_ON(&sequence_checker_); if (mediasection_index >= candidate_collection_.size()) return nullptr; return &candidate_collection_[mediasection_index]; } std::string SessionDescriptionInterface::ToString() const { return SdpSerialize(*this); } bool SessionDescriptionInterface::IsValidMLineIndex(int index) const { RTC_DCHECK(description()); return index >= 0 && index < static_cast(description()->contents().size()); } bool SessionDescriptionInterface::GetMediasectionIndex( const IceCandidate* candidate, size_t* index) const { if (!candidate || !index || !description()) { return false; } auto mid = candidate->sdp_mid(); if (!mid.empty()) { *index = GetMediasectionIndex(mid); } else { // An sdp_mline_index of -1 will be treated as invalid. *index = static_cast(candidate->sdp_mline_index()); } return IsValidMLineIndex(*index); } int SessionDescriptionInterface::GetMediasectionIndex( absl::string_view mid) const { const auto& contents = description()->contents(); auto it = std::find_if(contents.begin(), contents.end(), [&](const auto& content) { return mid == content.mid(); }); return it == contents.end() ? -1 : std::distance(contents.begin(), it); } } // namespace webrtc