| /* |
| * 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/strings/match.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 "system_wrappers/include/field_trial.h" |
| |
| namespace { |
| |
| 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 |
| |
| namespace cricket { |
| |
| using webrtc::RTCError; |
| using webrtc::RTCErrorType; |
| |
| // 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]; |
| } |
| |
| bool StringToProto(const char* value, ProtocolType* proto) { |
| for (size_t i = 0; i <= PROTO_LAST; ++i) { |
| if (absl::EqualsIgnoreCase(PROTO_NAMES[i], value)) { |
| *proto = static_cast<ProtocolType>(i); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // 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(const std::string& type, |
| const std::string& protocol, |
| const std::string& relay_protocol, |
| const rtc::SocketAddress& base_address) { |
| rtc::StringBuilder sb; |
| sb << type << base_address.ipaddr().ToString() << protocol << relay_protocol; |
| return rtc::ToString(rtc::ComputeCrc32(sb.Release())); |
| } |
| |
| CandidateStats::CandidateStats() = default; |
| |
| CandidateStats::CandidateStats(const CandidateStats&) = default; |
| |
| CandidateStats::CandidateStats(Candidate candidate) { |
| this->candidate = candidate; |
| } |
| |
| CandidateStats::~CandidateStats() = default; |
| |
| Port::Port(rtc::Thread* thread, |
| const std::string& type, |
| rtc::PacketSocketFactory* factory, |
| rtc::Network* network, |
| const std::string& username_fragment, |
| const std::string& password) |
| : 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) { |
| RTC_DCHECK(factory_ != NULL); |
| Construct(); |
| } |
| |
| Port::Port(rtc::Thread* thread, |
| const std::string& type, |
| rtc::PacketSocketFactory* factory, |
| rtc::Network* network, |
| uint16_t min_port, |
| uint16_t max_port, |
| const std::string& username_fragment, |
| const std::string& password) |
| : 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) { |
| 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(); |
| |
| thread_->PostDelayed(RTC_FROM_HERE, timeout_delay_, this, |
| MSG_DESTROY_IF_DEAD); |
| RTC_LOG(LS_INFO) << ToString() << ": Port created with network cost " |
| << network_cost_; |
| } |
| |
| Port::~Port() { |
| RTC_DCHECK_RUN_ON(thread_); |
| CancelPendingTasks(); |
| |
| // Delete all of the remaining connections. We copy the list up front |
| // because each deletion will cause it to be modified. |
| |
| std::vector<Connection*> list; |
| |
| AddressMap::iterator iter = connections_.begin(); |
| while (iter != connections_.end()) { |
| list.push_back(iter->second); |
| ++iter; |
| } |
| |
| for (uint32_t i = 0; i < list.size(); i++) |
| delete list[i]; |
| } |
| |
| const std::string& Port::Type() const { |
| return type_; |
| } |
| 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, |
| const std::string& username_fragment, |
| const std::string& password) { |
| component_ = component; |
| ice_username_fragment_ = username_fragment; |
| password_ = password; |
| for (Candidate& c : candidates_) { |
| c.set_component(component); |
| c.set_username(username_fragment); |
| c.set_password(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, |
| const std::string& protocol, |
| const std::string& relay_protocol, |
| const std::string& tcptype, |
| const std::string& type, |
| uint32_t type_preference, |
| uint32_t relay_preference, |
| const std::string& url, |
| bool is_final) { |
| 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_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, |
| const std::string& 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, |
| const std::string& 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) { |
| 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(); |
| ret.first->second->SignalDestroyed.disconnect(this); |
| ret.first->second->Destroy(); |
| ret.first->second = conn; |
| } |
| conn->SignalDestroyed.connect(this, &Port::OnConnectionDestroyed); |
| SignalConnectionCreated(this, conn); |
| } |
| |
| 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(); |
| } |
| } |
| |
| size_t Port::AddPrflxCandidate(const Candidate& local) { |
| candidates_.push_back(local); |
| return (candidates_.size() - 1); |
| } |
| |
| 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::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 std::string username = username_attr->GetString(); |
| size_t colon_pos = username.find(':'); |
| if (colon_pos == std::string::npos) { |
| return false; |
| } |
| |
| *local_ufrag = username.substr(0, colon_pos); |
| *remote_ufrag = username.substr(colon_pos + 1, username.size()); |
| return true; |
| } |
| |
| bool Port::MaybeIceRoleConflict(const rtc::SocketAddress& addr, |
| IceMessage* stun_msg, |
| const std::string& 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_NOTREACHED(); |
| } |
| return ret; |
| } |
| |
| void Port::CreateStunUsername(const std::string& remote_username, |
| std::string* stun_username_attr_str) const { |
| stun_username_attr_str->clear(); |
| *stun_username_attr_str = remote_username; |
| stun_username_attr_str->append(":"); |
| stun_username_attr_str->append(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_NOTREACHED(); |
| return false; |
| } |
| |
| bool Port::CanHandleIncomingPacketsFrom(const rtc::SocketAddress&) const { |
| return false; |
| } |
| |
| void Port::SendBindingErrorResponse(StunMessage* request, |
| const rtc::SocketAddress& addr, |
| int error_code, |
| const std::string& reason) { |
| RTC_DCHECK(request->type() == STUN_BINDING_REQUEST || |
| request->type() == GOOG_PING_REQUEST); |
| |
| // Fill in the response message. |
| StunMessage response; |
| if (request->type() == STUN_BINDING_REQUEST) { |
| response.SetType(STUN_BINDING_ERROR_RESPONSE); |
| } else { |
| response.SetType(GOOG_PING_ERROR_RESPONSE); |
| } |
| response.SetTransactionID(request->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(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 && |
| request->type() != GOOG_PING_REQUEST) { |
| if (request->type() == STUN_BINDING_REQUEST) { |
| response.AddMessageIntegrity(password_); |
| } else { |
| response.AddMessageIntegrity32(password_); |
| } |
| } |
| |
| if (request->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* request, |
| const rtc::SocketAddress& addr, |
| const std::vector<uint16_t>& unknown_types) { |
| RTC_DCHECK(request->type() == STUN_BINDING_REQUEST); |
| |
| // Fill in the response message. |
| StunMessage response; |
| response.SetType(STUN_BINDING_ERROR_RESPONSE); |
| response.SetTransactionID(request->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; |
| thread_->Post(RTC_FROM_HERE, this, MSG_DESTROY_IF_DEAD); |
| } |
| |
| // Call to stop any currently pending operations from running. |
| void Port::CancelPendingTasks() { |
| RTC_DCHECK_RUN_ON(thread_); |
| thread_->Clear(this); |
| } |
| |
| void Port::OnMessage(rtc::Message* pmsg) { |
| RTC_DCHECK_RUN_ON(thread_); |
| RTC_DCHECK(pmsg->message_id == MSG_DESTROY_IF_DEAD); |
| 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() { |
| uint16_t new_cost = network_->GetCost(); |
| 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_); |
| } |
| // Network cost change will affect the connection selection criteria. |
| // Signal the connection state change on each connection to force a |
| // re-sort in P2PTransportChannel. |
| for (const auto& kv : connections_) { |
| Connection* conn = kv.second; |
| conn->SignalStateChange(conn); |
| } |
| } |
| |
| void Port::EnablePortPackets() { |
| enable_port_packets_ = true; |
| } |
| |
| void Port::OnConnectionDestroyed(Connection* conn) { |
| AddressMap::iterator iter = |
| connections_.find(conn->remote_candidate().address()); |
| RTC_DCHECK(iter != connections_.end()); |
| connections_.erase(iter); |
| 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(); |
| thread_->PostDelayed(RTC_FROM_HERE, timeout_delay_, this, |
| MSG_DESTROY_IF_DEAD); |
| } |
| } |
| |
| 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 |