diff options
Diffstat (limited to 'third_party/libwebrtc/p2p/base/port.cc')
-rw-r--r-- | third_party/libwebrtc/p2p/base/port.cc | 969 |
1 files changed, 969 insertions, 0 deletions
diff --git a/third_party/libwebrtc/p2p/base/port.cc b/third_party/libwebrtc/p2p/base/port.cc new file mode 100644 index 0000000000..5c5ac6319c --- /dev/null +++ b/third_party/libwebrtc/p2p/base/port.cc @@ -0,0 +1,969 @@ +/* + * Copyright 2004 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 "p2p/base/port.h" + +#include <math.h> + +#include <algorithm> +#include <memory> +#include <utility> +#include <vector> + +#include "absl/algorithm/container.h" +#include "absl/memory/memory.h" +#include "absl/strings/match.h" +#include "absl/strings/string_view.h" +#include "p2p/base/connection.h" +#include "p2p/base/port_allocator.h" +#include "rtc_base/checks.h" +#include "rtc_base/crc32.h" +#include "rtc_base/helpers.h" +#include "rtc_base/logging.h" +#include "rtc_base/mdns_responder_interface.h" +#include "rtc_base/message_digest.h" +#include "rtc_base/network.h" +#include "rtc_base/numerics/safe_minmax.h" +#include "rtc_base/string_encode.h" +#include "rtc_base/string_utils.h" +#include "rtc_base/strings/string_builder.h" +#include "rtc_base/third_party/base64/base64.h" +#include "rtc_base/trace_event.h" + +namespace cricket { +namespace { + +using ::webrtc::RTCError; +using ::webrtc::RTCErrorType; +using ::webrtc::TaskQueueBase; +using ::webrtc::TimeDelta; + +rtc::PacketInfoProtocolType ConvertProtocolTypeToPacketInfoProtocolType( + cricket::ProtocolType type) { + switch (type) { + case cricket::ProtocolType::PROTO_UDP: + return rtc::PacketInfoProtocolType::kUdp; + case cricket::ProtocolType::PROTO_TCP: + return rtc::PacketInfoProtocolType::kTcp; + case cricket::ProtocolType::PROTO_SSLTCP: + return rtc::PacketInfoProtocolType::kSsltcp; + case cricket::ProtocolType::PROTO_TLS: + return rtc::PacketInfoProtocolType::kTls; + default: + return rtc::PacketInfoProtocolType::kUnknown; + } +} + +// The delay before we begin checking if this port is useless. We set +// it to a little higher than a total STUN timeout. +const int kPortTimeoutDelay = cricket::STUN_TOTAL_TIMEOUT + 5000; + +} // namespace + +// 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"; +const char PRFLX_PORT_TYPE[] = "prflx"; +const char RELAY_PORT_TYPE[] = "relay"; + +static const char* const PROTO_NAMES[] = {UDP_PROTOCOL_NAME, TCP_PROTOCOL_NAME, + SSLTCP_PROTOCOL_NAME, + TLS_PROTOCOL_NAME}; + +const char* ProtoToString(ProtocolType proto) { + return PROTO_NAMES[proto]; +} + +absl::optional<ProtocolType> StringToProto(absl::string_view proto_name) { + for (size_t i = 0; i <= PROTO_LAST; ++i) { + if (absl::EqualsIgnoreCase(PROTO_NAMES[i], proto_name)) { + return static_cast<ProtocolType>(i); + } + } + return absl::nullopt; +} + +// RFC 6544, TCP candidate encoding rules. +const int DISCARD_PORT = 9; +const char TCPTYPE_ACTIVE_STR[] = "active"; +const char TCPTYPE_PASSIVE_STR[] = "passive"; +const char TCPTYPE_SIMOPEN_STR[] = "so"; + +std::string Port::ComputeFoundation(absl::string_view type, + absl::string_view protocol, + absl::string_view relay_protocol, + const rtc::SocketAddress& base_address) { + // TODO(bugs.webrtc.org/14605): ensure IceTiebreaker() is set. + rtc::StringBuilder sb; + sb << type << base_address.ipaddr().ToString() << protocol << relay_protocol + << rtc::ToString(IceTiebreaker()); + return rtc::ToString(rtc::ComputeCrc32(sb.Release())); +} + +Port::Port(TaskQueueBase* thread, + absl::string_view type, + rtc::PacketSocketFactory* factory, + const rtc::Network* network, + absl::string_view username_fragment, + absl::string_view password, + const webrtc::FieldTrialsView* field_trials) + : thread_(thread), + factory_(factory), + type_(type), + send_retransmit_count_attribute_(false), + network_(network), + min_port_(0), + max_port_(0), + component_(ICE_CANDIDATE_COMPONENT_DEFAULT), + generation_(0), + ice_username_fragment_(username_fragment), + password_(password), + timeout_delay_(kPortTimeoutDelay), + enable_port_packets_(false), + ice_role_(ICEROLE_UNKNOWN), + tiebreaker_(0), + shared_socket_(true), + weak_factory_(this), + field_trials_(field_trials) { + RTC_DCHECK(factory_ != NULL); + Construct(); +} + +Port::Port(TaskQueueBase* thread, + absl::string_view type, + rtc::PacketSocketFactory* factory, + const rtc::Network* network, + uint16_t min_port, + uint16_t max_port, + absl::string_view username_fragment, + absl::string_view password, + const webrtc::FieldTrialsView* field_trials) + : thread_(thread), + factory_(factory), + type_(type), + send_retransmit_count_attribute_(false), + network_(network), + min_port_(min_port), + max_port_(max_port), + component_(ICE_CANDIDATE_COMPONENT_DEFAULT), + generation_(0), + ice_username_fragment_(username_fragment), + password_(password), + timeout_delay_(kPortTimeoutDelay), + enable_port_packets_(false), + ice_role_(ICEROLE_UNKNOWN), + tiebreaker_(0), + shared_socket_(false), + weak_factory_(this), + field_trials_(field_trials) { + RTC_DCHECK(factory_ != NULL); + Construct(); +} + +void Port::Construct() { + // TODO(pthatcher): Remove this old behavior once we're sure no one + // relies on it. If the username_fragment and password are empty, + // we should just create one. + if (ice_username_fragment_.empty()) { + RTC_DCHECK(password_.empty()); + ice_username_fragment_ = rtc::CreateRandomString(ICE_UFRAG_LENGTH); + password_ = rtc::CreateRandomString(ICE_PWD_LENGTH); + } + network_->SignalTypeChanged.connect(this, &Port::OnNetworkTypeChanged); + network_cost_ = network_->GetCost(field_trials()); + + PostDestroyIfDead(/*delayed=*/true); + RTC_LOG(LS_INFO) << ToString() << ": Port created with network cost " + << network_cost_; +} + +Port::~Port() { + RTC_DCHECK_RUN_ON(thread_); + CancelPendingTasks(); + DestroyAllConnections(); +} + +const std::string& Port::Type() const { + return type_; +} +const rtc::Network* Port::Network() const { + return network_; +} + +IceRole Port::GetIceRole() const { + return ice_role_; +} + +void Port::SetIceRole(IceRole role) { + ice_role_ = role; +} + +void Port::SetIceTiebreaker(uint64_t tiebreaker) { + tiebreaker_ = tiebreaker; +} + +uint64_t Port::IceTiebreaker() const { + return tiebreaker_; +} + +bool Port::SharedSocket() const { + return shared_socket_; +} + +void Port::SetIceParameters(int component, + absl::string_view username_fragment, + absl::string_view password) { + RTC_DCHECK_RUN_ON(thread_); + component_ = component; + ice_username_fragment_ = std::string(username_fragment); + password_ = std::string(password); + for (Candidate& c : candidates_) { + c.set_component(component); + c.set_username(username_fragment); + c.set_password(password); + } + + // In case any connections exist make sure we update them too. + for (auto& [unused, connection] : connections_) { + connection->UpdateLocalIceParameters(component, username_fragment, + password); + } +} + +const std::vector<Candidate>& Port::Candidates() const { + return candidates_; +} + +Connection* Port::GetConnection(const rtc::SocketAddress& remote_addr) { + AddressMap::const_iterator iter = connections_.find(remote_addr); + if (iter != connections_.end()) + return iter->second; + else + return NULL; +} + +void Port::AddAddress(const rtc::SocketAddress& address, + const rtc::SocketAddress& base_address, + const rtc::SocketAddress& related_address, + absl::string_view protocol, + absl::string_view relay_protocol, + absl::string_view tcptype, + absl::string_view type, + uint32_t type_preference, + uint32_t relay_preference, + absl::string_view url, + bool is_final) { + RTC_DCHECK_RUN_ON(thread_); + if (protocol == TCP_PROTOCOL_NAME && type == LOCAL_PORT_TYPE) { + RTC_DCHECK(!tcptype.empty()); + } + + std::string foundation = + ComputeFoundation(type, protocol, relay_protocol, base_address); + Candidate c(component_, protocol, address, 0U, username_fragment(), password_, + type, generation_, foundation, network_->id(), network_cost_); + c.set_priority( + c.GetPriority(type_preference, network_->preference(), relay_preference)); + c.set_relay_protocol(relay_protocol); + c.set_tcptype(tcptype); + c.set_network_name(network_->name()); + c.set_network_type(network_->type()); + c.set_underlying_type_for_vpn(network_->underlying_type_for_vpn()); + c.set_url(url); + c.set_related_address(related_address); + + bool pending = MaybeObfuscateAddress(&c, type, is_final); + + if (!pending) { + FinishAddingAddress(c, is_final); + } +} + +bool Port::MaybeObfuscateAddress(Candidate* c, + absl::string_view type, + bool is_final) { + // TODO(bugs.webrtc.org/9723): Use a config to control the feature of IP + // handling with mDNS. + if (network_->GetMdnsResponder() == nullptr) { + return false; + } + if (type != LOCAL_PORT_TYPE) { + return false; + } + + auto copy = *c; + auto weak_ptr = weak_factory_.GetWeakPtr(); + auto callback = [weak_ptr, copy, is_final](const rtc::IPAddress& addr, + absl::string_view name) mutable { + RTC_DCHECK(copy.address().ipaddr() == addr); + rtc::SocketAddress hostname_address(name, copy.address().port()); + // In Port and Connection, we need the IP address information to + // correctly handle the update of candidate type to prflx. The removal + // of IP address when signaling this candidate will take place in + // BasicPortAllocatorSession::OnCandidateReady, via SanitizeCandidate. + hostname_address.SetResolvedIP(addr); + copy.set_address(hostname_address); + copy.set_related_address(rtc::SocketAddress()); + if (weak_ptr != nullptr) { + RTC_DCHECK_RUN_ON(weak_ptr->thread_); + weak_ptr->set_mdns_name_registration_status( + MdnsNameRegistrationStatus::kCompleted); + weak_ptr->FinishAddingAddress(copy, is_final); + } + }; + set_mdns_name_registration_status(MdnsNameRegistrationStatus::kInProgress); + network_->GetMdnsResponder()->CreateNameForAddress(copy.address().ipaddr(), + callback); + return true; +} + +void Port::FinishAddingAddress(const Candidate& c, bool is_final) { + candidates_.push_back(c); + SignalCandidateReady(this, c); + + PostAddAddress(is_final); +} + +void Port::PostAddAddress(bool is_final) { + if (is_final) { + SignalPortComplete(this); + } +} + +void Port::AddOrReplaceConnection(Connection* conn) { + auto ret = connections_.insert( + std::make_pair(conn->remote_candidate().address(), conn)); + // If there is a different connection on the same remote address, replace + // it with the new one and destroy the old one. + if (ret.second == false && ret.first->second != conn) { + RTC_LOG(LS_WARNING) + << ToString() + << ": A new connection was created on an existing remote address. " + "New remote candidate: " + << conn->remote_candidate().ToSensitiveString(); + std::unique_ptr<Connection> old_conn = absl::WrapUnique(ret.first->second); + ret.first->second = conn; + HandleConnectionDestroyed(old_conn.get()); + old_conn->Shutdown(); + } +} + +void Port::OnReadPacket(const char* data, + size_t size, + const rtc::SocketAddress& addr, + ProtocolType proto) { + // If the user has enabled port packets, just hand this over. + if (enable_port_packets_) { + SignalReadPacket(this, data, size, addr); + return; + } + + // If this is an authenticated STUN request, then signal unknown address and + // send back a proper binding response. + std::unique_ptr<IceMessage> msg; + std::string remote_username; + if (!GetStunMessage(data, size, addr, &msg, &remote_username)) { + RTC_LOG(LS_ERROR) << ToString() + << ": Received non-STUN packet from unknown address: " + << addr.ToSensitiveString(); + } else if (!msg) { + // STUN message handled already + } else if (msg->type() == STUN_BINDING_REQUEST) { + RTC_LOG(LS_INFO) << "Received " << StunMethodToString(msg->type()) + << " id=" << rtc::hex_encode(msg->transaction_id()) + << " from unknown address " << addr.ToSensitiveString(); + // We need to signal an unknown address before we handle any role conflict + // below. Otherwise there would be no candidate pair and TURN entry created + // to send the error response in case of a role conflict. + SignalUnknownAddress(this, addr, proto, msg.get(), remote_username, false); + // Check for role conflicts. + if (!MaybeIceRoleConflict(addr, msg.get(), remote_username)) { + RTC_LOG(LS_INFO) << "Received conflicting role from the peer."; + return; + } + } else if (msg->type() == GOOG_PING_REQUEST) { + // This is a PING sent to a connection that was destroyed. + // Send back that this is the case and a authenticated BINDING + // is needed. + SendBindingErrorResponse(msg.get(), addr, STUN_ERROR_BAD_REQUEST, + STUN_ERROR_REASON_BAD_REQUEST); + } else { + // NOTE(tschmelcher): STUN_BINDING_RESPONSE is benign. It occurs if we + // pruned a connection for this port while it had STUN requests in flight, + // because we then get back responses for them, which this code correctly + // does not handle. + if (msg->type() != STUN_BINDING_RESPONSE && + msg->type() != GOOG_PING_RESPONSE && + msg->type() != GOOG_PING_ERROR_RESPONSE) { + RTC_LOG(LS_ERROR) << ToString() + << ": Received unexpected STUN message type: " + << msg->type() << " from unknown address: " + << addr.ToSensitiveString(); + } + } +} + +void Port::OnReadyToSend() { + AddressMap::iterator iter = connections_.begin(); + for (; iter != connections_.end(); ++iter) { + iter->second->OnReadyToSend(); + } +} + +void Port::AddPrflxCandidate(const Candidate& local) { + RTC_DCHECK_RUN_ON(thread_); + candidates_.push_back(local); +} + +bool Port::GetStunMessage(const char* data, + size_t size, + const rtc::SocketAddress& addr, + std::unique_ptr<IceMessage>* out_msg, + std::string* out_username) { + // NOTE: This could clearly be optimized to avoid allocating any memory. + // However, at the data rates we'll be looking at on the client side, + // this probably isn't worth worrying about. + RTC_DCHECK(out_msg != NULL); + RTC_DCHECK(out_username != NULL); + out_username->clear(); + + // Don't bother parsing the packet if we can tell it's not STUN. + // In ICE mode, all STUN packets will have a valid fingerprint. + // Except GOOG_PING_REQUEST/RESPONSE that does not send fingerprint. + int types[] = {GOOG_PING_REQUEST, GOOG_PING_RESPONSE, + GOOG_PING_ERROR_RESPONSE}; + if (!StunMessage::IsStunMethod(types, data, size) && + !StunMessage::ValidateFingerprint(data, size)) { + return false; + } + + // Parse the request message. If the packet is not a complete and correct + // STUN message, then ignore it. + std::unique_ptr<IceMessage> stun_msg(new IceMessage()); + rtc::ByteBufferReader buf(data, size); + if (!stun_msg->Read(&buf) || (buf.Length() > 0)) { + return false; + } + + // Get list of attributes in the "comprehension-required" range that were not + // comprehended. If one or more is found, the behavior differs based on the + // type of the incoming message; see below. + std::vector<uint16_t> unknown_attributes = + stun_msg->GetNonComprehendedAttributes(); + + if (stun_msg->type() == STUN_BINDING_REQUEST) { + // Check for the presence of USERNAME and MESSAGE-INTEGRITY (if ICE) first. + // If not present, fail with a 400 Bad Request. + if (!stun_msg->GetByteString(STUN_ATTR_USERNAME) || + !stun_msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY)) { + RTC_LOG(LS_ERROR) << ToString() << ": Received " + << StunMethodToString(stun_msg->type()) + << " without username/M-I from: " + << addr.ToSensitiveString(); + SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_BAD_REQUEST, + STUN_ERROR_REASON_BAD_REQUEST); + return true; + } + + // If the username is bad or unknown, fail with a 401 Unauthorized. + std::string local_ufrag; + std::string remote_ufrag; + if (!ParseStunUsername(stun_msg.get(), &local_ufrag, &remote_ufrag) || + local_ufrag != username_fragment()) { + RTC_LOG(LS_ERROR) << ToString() << ": Received " + << StunMethodToString(stun_msg->type()) + << " with bad local username " << local_ufrag + << " from " << addr.ToSensitiveString(); + SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_UNAUTHORIZED, + STUN_ERROR_REASON_UNAUTHORIZED); + return true; + } + + // If ICE, and the MESSAGE-INTEGRITY is bad, fail with a 401 Unauthorized + if (stun_msg->ValidateMessageIntegrity(password_) != + StunMessage::IntegrityStatus::kIntegrityOk) { + RTC_LOG(LS_ERROR) << ToString() << ": Received " + << StunMethodToString(stun_msg->type()) + << " with bad M-I from " << addr.ToSensitiveString() + << ", password_=" << password_; + SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_UNAUTHORIZED, + STUN_ERROR_REASON_UNAUTHORIZED); + return true; + } + + // If a request contains unknown comprehension-required attributes, reply + // with an error. See RFC5389 section 7.3.1. + if (!unknown_attributes.empty()) { + SendUnknownAttributesErrorResponse(stun_msg.get(), addr, + unknown_attributes); + return true; + } + + out_username->assign(remote_ufrag); + } else if ((stun_msg->type() == STUN_BINDING_RESPONSE) || + (stun_msg->type() == STUN_BINDING_ERROR_RESPONSE)) { + if (stun_msg->type() == STUN_BINDING_ERROR_RESPONSE) { + if (const StunErrorCodeAttribute* error_code = stun_msg->GetErrorCode()) { + RTC_LOG(LS_ERROR) << ToString() << ": Received " + << StunMethodToString(stun_msg->type()) + << ": class=" << error_code->eclass() + << " number=" << error_code->number() << " reason='" + << error_code->reason() << "' from " + << addr.ToSensitiveString(); + // Return message to allow error-specific processing + } else { + RTC_LOG(LS_ERROR) << ToString() << ": Received " + << StunMethodToString(stun_msg->type()) + << " without a error code from " + << addr.ToSensitiveString(); + return true; + } + } + // If a response contains unknown comprehension-required attributes, it's + // simply discarded and the transaction is considered failed. See RFC5389 + // sections 7.3.3 and 7.3.4. + if (!unknown_attributes.empty()) { + RTC_LOG(LS_ERROR) << ToString() + << ": Discarding STUN response due to unknown " + "comprehension-required attribute"; + return true; + } + // NOTE: Username should not be used in verifying response messages. + out_username->clear(); + } else if (stun_msg->type() == STUN_BINDING_INDICATION) { + RTC_LOG(LS_VERBOSE) << ToString() << ": Received " + << StunMethodToString(stun_msg->type()) << ": from " + << addr.ToSensitiveString(); + out_username->clear(); + + // If an indication contains unknown comprehension-required attributes,[] + // it's simply discarded. See RFC5389 section 7.3.2. + if (!unknown_attributes.empty()) { + RTC_LOG(LS_ERROR) << ToString() + << ": Discarding STUN indication due to " + "unknown comprehension-required attribute"; + return true; + } + // No stun attributes will be verified, if it's stun indication message. + // Returning from end of the this method. + } else if (stun_msg->type() == GOOG_PING_REQUEST) { + if (stun_msg->ValidateMessageIntegrity(password_) != + StunMessage::IntegrityStatus::kIntegrityOk) { + RTC_LOG(LS_ERROR) << ToString() << ": Received " + << StunMethodToString(stun_msg->type()) + << " with bad M-I from " << addr.ToSensitiveString() + << ", password_=" << password_; + SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_UNAUTHORIZED, + STUN_ERROR_REASON_UNAUTHORIZED); + return true; + } + RTC_LOG(LS_VERBOSE) << ToString() << ": Received " + << StunMethodToString(stun_msg->type()) << " from " + << addr.ToSensitiveString(); + out_username->clear(); + } else if (stun_msg->type() == GOOG_PING_RESPONSE || + stun_msg->type() == GOOG_PING_ERROR_RESPONSE) { + // note: the MessageIntegrity32 will be verified in Connection.cc + RTC_LOG(LS_VERBOSE) << ToString() << ": Received " + << StunMethodToString(stun_msg->type()) << " from " + << addr.ToSensitiveString(); + out_username->clear(); + } else { + RTC_LOG(LS_ERROR) << ToString() + << ": Received STUN packet with invalid type (" + << stun_msg->type() << ") from " + << addr.ToSensitiveString(); + return true; + } + + // Return the STUN message found. + *out_msg = std::move(stun_msg); + return true; +} + +bool Port::IsCompatibleAddress(const rtc::SocketAddress& addr) { + // Get a representative IP for the Network this port is configured to use. + rtc::IPAddress ip = network_->GetBestIP(); + // We use single-stack sockets, so families must match. + if (addr.family() != ip.family()) { + return false; + } + // Link-local IPv6 ports can only connect to other link-local IPv6 ports. + if (ip.family() == AF_INET6 && + (IPIsLinkLocal(ip) != IPIsLinkLocal(addr.ipaddr()))) { + return false; + } + return true; +} + +rtc::DiffServCodePoint Port::StunDscpValue() const { + // By default, inherit from whatever the MediaChannel sends. + return rtc::DSCP_NO_CHANGE; +} + +void Port::DestroyAllConnections() { + RTC_DCHECK_RUN_ON(thread_); + for (auto& [unused, connection] : connections_) { + connection->Shutdown(); + delete connection; + } + connections_.clear(); +} + +void Port::set_timeout_delay(int delay) { + RTC_DCHECK_RUN_ON(thread_); + // Although this method is meant to only be used by tests, some downstream + // projects have started using it. Ideally we should update our tests to not + // require to modify this state and instead use a testing harness that allows + // adjusting the clock and then just use the kPortTimeoutDelay constant + // directly. + timeout_delay_ = delay; +} + +bool Port::ParseStunUsername(const StunMessage* stun_msg, + std::string* local_ufrag, + std::string* remote_ufrag) const { + // The packet must include a username that either begins or ends with our + // fragment. It should begin with our fragment if it is a request and it + // should end with our fragment if it is a response. + local_ufrag->clear(); + remote_ufrag->clear(); + const StunByteStringAttribute* username_attr = + stun_msg->GetByteString(STUN_ATTR_USERNAME); + if (username_attr == NULL) + return false; + + // RFRAG:LFRAG + const absl::string_view username = username_attr->string_view(); + size_t colon_pos = username.find(':'); + if (colon_pos == absl::string_view::npos) { + return false; + } + + *local_ufrag = std::string(username.substr(0, colon_pos)); + *remote_ufrag = std::string(username.substr(colon_pos + 1, username.size())); + return true; +} + +bool Port::MaybeIceRoleConflict(const rtc::SocketAddress& addr, + IceMessage* stun_msg, + absl::string_view remote_ufrag) { + // Validate ICE_CONTROLLING or ICE_CONTROLLED attributes. + bool ret = true; + IceRole remote_ice_role = ICEROLE_UNKNOWN; + uint64_t remote_tiebreaker = 0; + const StunUInt64Attribute* stun_attr = + stun_msg->GetUInt64(STUN_ATTR_ICE_CONTROLLING); + if (stun_attr) { + remote_ice_role = ICEROLE_CONTROLLING; + remote_tiebreaker = stun_attr->value(); + } + + // If `remote_ufrag` is same as port local username fragment and + // tie breaker value received in the ping message matches port + // tiebreaker value this must be a loopback call. + // We will treat this as valid scenario. + if (remote_ice_role == ICEROLE_CONTROLLING && + username_fragment() == remote_ufrag && + remote_tiebreaker == IceTiebreaker()) { + return true; + } + + stun_attr = stun_msg->GetUInt64(STUN_ATTR_ICE_CONTROLLED); + if (stun_attr) { + remote_ice_role = ICEROLE_CONTROLLED; + remote_tiebreaker = stun_attr->value(); + } + + switch (ice_role_) { + case ICEROLE_CONTROLLING: + if (ICEROLE_CONTROLLING == remote_ice_role) { + if (remote_tiebreaker >= tiebreaker_) { + SignalRoleConflict(this); + } else { + // Send Role Conflict (487) error response. + SendBindingErrorResponse(stun_msg, addr, STUN_ERROR_ROLE_CONFLICT, + STUN_ERROR_REASON_ROLE_CONFLICT); + ret = false; + } + } + break; + case ICEROLE_CONTROLLED: + if (ICEROLE_CONTROLLED == remote_ice_role) { + if (remote_tiebreaker < tiebreaker_) { + SignalRoleConflict(this); + } else { + // Send Role Conflict (487) error response. + SendBindingErrorResponse(stun_msg, addr, STUN_ERROR_ROLE_CONFLICT, + STUN_ERROR_REASON_ROLE_CONFLICT); + ret = false; + } + } + break; + default: + RTC_DCHECK_NOTREACHED(); + } + return ret; +} + +std::string Port::CreateStunUsername(absl::string_view remote_username) const { + return std::string(remote_username) + ":" + username_fragment(); +} + +bool Port::HandleIncomingPacket(rtc::AsyncPacketSocket* socket, + const char* data, + size_t size, + const rtc::SocketAddress& remote_addr, + int64_t packet_time_us) { + RTC_DCHECK_NOTREACHED(); + return false; +} + +bool Port::CanHandleIncomingPacketsFrom(const rtc::SocketAddress&) const { + return false; +} + +void Port::SendBindingErrorResponse(StunMessage* message, + const rtc::SocketAddress& addr, + int error_code, + absl::string_view reason) { + RTC_DCHECK(message->type() == STUN_BINDING_REQUEST || + message->type() == GOOG_PING_REQUEST); + + // Fill in the response message. + StunMessage response(message->type() == STUN_BINDING_REQUEST + ? STUN_BINDING_ERROR_RESPONSE + : GOOG_PING_ERROR_RESPONSE, + message->transaction_id()); + + // When doing GICE, we need to write out the error code incorrectly to + // maintain backwards compatiblility. + auto error_attr = StunAttribute::CreateErrorCode(); + error_attr->SetCode(error_code); + error_attr->SetReason(std::string(reason)); + response.AddAttribute(std::move(error_attr)); + + // Per Section 10.1.2, certain error cases don't get a MESSAGE-INTEGRITY, + // because we don't have enough information to determine the shared secret. + if (error_code != STUN_ERROR_BAD_REQUEST && + error_code != STUN_ERROR_UNAUTHORIZED && + message->type() != GOOG_PING_REQUEST) { + if (message->type() == STUN_BINDING_REQUEST) { + response.AddMessageIntegrity(password_); + } else { + response.AddMessageIntegrity32(password_); + } + } + + if (message->type() == STUN_BINDING_REQUEST) { + response.AddFingerprint(); + } + + // Send the response message. + rtc::ByteBufferWriter buf; + response.Write(&buf); + rtc::PacketOptions options(StunDscpValue()); + options.info_signaled_after_sent.packet_type = + rtc::PacketType::kIceConnectivityCheckResponse; + SendTo(buf.Data(), buf.Length(), addr, options, false); + RTC_LOG(LS_INFO) << ToString() << ": Sending STUN " + << StunMethodToString(response.type()) + << ": reason=" << reason << " to " + << addr.ToSensitiveString(); +} + +void Port::SendUnknownAttributesErrorResponse( + StunMessage* message, + const rtc::SocketAddress& addr, + const std::vector<uint16_t>& unknown_types) { + RTC_DCHECK(message->type() == STUN_BINDING_REQUEST); + + // Fill in the response message. + StunMessage response(STUN_BINDING_ERROR_RESPONSE, message->transaction_id()); + + auto error_attr = StunAttribute::CreateErrorCode(); + error_attr->SetCode(STUN_ERROR_UNKNOWN_ATTRIBUTE); + error_attr->SetReason(STUN_ERROR_REASON_UNKNOWN_ATTRIBUTE); + response.AddAttribute(std::move(error_attr)); + + std::unique_ptr<StunUInt16ListAttribute> unknown_attr = + StunAttribute::CreateUnknownAttributes(); + for (uint16_t type : unknown_types) { + unknown_attr->AddType(type); + } + response.AddAttribute(std::move(unknown_attr)); + + response.AddMessageIntegrity(password_); + response.AddFingerprint(); + + // Send the response message. + rtc::ByteBufferWriter buf; + response.Write(&buf); + rtc::PacketOptions options(StunDscpValue()); + options.info_signaled_after_sent.packet_type = + rtc::PacketType::kIceConnectivityCheckResponse; + SendTo(buf.Data(), buf.Length(), addr, options, false); + RTC_LOG(LS_ERROR) << ToString() << ": Sending STUN binding error: reason=" + << STUN_ERROR_UNKNOWN_ATTRIBUTE << " to " + << addr.ToSensitiveString(); +} + +void Port::KeepAliveUntilPruned() { + // If it is pruned, we won't bring it up again. + if (state_ == State::INIT) { + state_ = State::KEEP_ALIVE_UNTIL_PRUNED; + } +} + +void Port::Prune() { + state_ = State::PRUNED; + PostDestroyIfDead(/*delayed=*/false); +} + +// Call to stop any currently pending operations from running. +void Port::CancelPendingTasks() { + TRACE_EVENT0("webrtc", "Port::CancelPendingTasks"); + RTC_DCHECK_RUN_ON(thread_); + weak_factory_.InvalidateWeakPtrs(); +} + +void Port::PostDestroyIfDead(bool delayed) { + rtc::WeakPtr<Port> weak_ptr = NewWeakPtr(); + auto task = [weak_ptr = std::move(weak_ptr)] { + if (weak_ptr) { + weak_ptr->DestroyIfDead(); + } + }; + if (delayed) { + thread_->PostDelayedTask(std::move(task), + TimeDelta::Millis(timeout_delay_)); + } else { + thread_->PostTask(std::move(task)); + } +} + +void Port::DestroyIfDead() { + RTC_DCHECK_RUN_ON(thread_); + bool dead = + (state_ == State::INIT || state_ == State::PRUNED) && + connections_.empty() && + rtc::TimeMillis() - last_time_all_connections_removed_ >= timeout_delay_; + if (dead) { + Destroy(); + } +} + +void Port::SubscribePortDestroyed( + std::function<void(PortInterface*)> callback) { + port_destroyed_callback_list_.AddReceiver(callback); +} + +void Port::SendPortDestroyed(Port* port) { + port_destroyed_callback_list_.Send(port); +} +void Port::OnNetworkTypeChanged(const rtc::Network* network) { + RTC_DCHECK(network == network_); + + UpdateNetworkCost(); +} + +std::string Port::ToString() const { + rtc::StringBuilder ss; + ss << "Port[" << rtc::ToHex(reinterpret_cast<uintptr_t>(this)) << ":" + << content_name_ << ":" << component_ << ":" << generation_ << ":" << type_ + << ":" << network_->ToString() << "]"; + return ss.Release(); +} + +// TODO(honghaiz): Make the network cost configurable from user setting. +void Port::UpdateNetworkCost() { + RTC_DCHECK_RUN_ON(thread_); + uint16_t new_cost = network_->GetCost(field_trials()); + if (network_cost_ == new_cost) { + return; + } + RTC_LOG(LS_INFO) << "Network cost changed from " << network_cost_ << " to " + << new_cost + << ". Number of candidates created: " << candidates_.size() + << ". Number of connections created: " + << connections_.size(); + network_cost_ = new_cost; + for (cricket::Candidate& candidate : candidates_) + candidate.set_network_cost(network_cost_); + + for (auto& [unused, connection] : connections_) + connection->SetLocalCandidateNetworkCost(network_cost_); +} + +void Port::EnablePortPackets() { + enable_port_packets_ = true; +} + +bool Port::OnConnectionDestroyed(Connection* conn) { + if (connections_.erase(conn->remote_candidate().address()) == 0) { + // This could indicate a programmer error outside of webrtc so while we + // do have this check here to alert external developers, we also need to + // handle it since it might be a corner case not caught in tests. + RTC_DCHECK_NOTREACHED() << "Calling Destroy recursively?"; + return false; + } + + HandleConnectionDestroyed(conn); + + // Ports time out after all connections fail if it is not marked as + // "keep alive until pruned." + // Note: If a new connection is added after this message is posted, but it + // fails and is removed before kPortTimeoutDelay, then this message will + // not cause the Port to be destroyed. + if (connections_.empty()) { + last_time_all_connections_removed_ = rtc::TimeMillis(); + PostDestroyIfDead(/*delayed=*/true); + } + + return true; +} + +void Port::DestroyConnectionInternal(Connection* conn, bool async) { + RTC_DCHECK_RUN_ON(thread_); + if (!OnConnectionDestroyed(conn)) + return; + + conn->Shutdown(); + if (async) { + // Unwind the stack before deleting the object in case upstream callers + // need to refer to the Connection's state as part of teardown. + // NOTE: We move ownership of `conn` into the capture section of the lambda + // so that the object will always be deleted, including if PostTask fails. + // In such a case (only tests), deletion would happen inside of the call + // to `DestroyConnection()`. + thread_->PostTask([conn = absl::WrapUnique(conn)]() {}); + } else { + delete conn; + } +} + +void Port::Destroy() { + RTC_DCHECK(connections_.empty()); + RTC_LOG(LS_INFO) << ToString() << ": Port deleted"; + SendPortDestroyed(this); + delete this; +} + +const std::string Port::username_fragment() const { + return ice_username_fragment_; +} + +void Port::CopyPortInformationToPacketInfo(rtc::PacketInfo* info) const { + info->protocol = ConvertProtocolTypeToPacketInfoProtocolType(GetProtocol()); + info->network_id = Network()->id(); +} + +} // namespace cricket |