| /* | |
| * Copyright 2019 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/connection.h" | |
| #include <algorithm> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <memory> | |
| #include <optional> | |
| #include <span> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/algorithm/container.h" | |
| #include "absl/functional/any_invocable.h" | |
| #include "absl/strings/string_view.h" | |
| #include "api/candidate.h" | |
| #include "api/environment/environment.h" | |
| #include "api/rtc_error.h" | |
| #include "api/sequence_checker.h" | |
| #include "api/task_queue/task_queue_base.h" | |
| #include "api/transport/stun.h" | |
| #include "api/units/data_rate.h" | |
| #include "api/units/time_delta.h" | |
| #include "api/units/timestamp.h" | |
| #include "logging/rtc_event_log/events/rtc_event_ice_candidate_pair.h" | |
| #include "logging/rtc_event_log/events/rtc_event_ice_candidate_pair_config.h" | |
| #include "logging/rtc_event_log/ice_logger.h" | |
| #include "p2p/base/connection_info.h" | |
| #include "p2p/base/p2p_constants.h" | |
| #include "p2p/base/p2p_transport_channel_ice_field_trials.h" | |
| #include "p2p/base/port_interface.h" | |
| #include "p2p/base/stun_request.h" | |
| #include "p2p/base/transport_description.h" | |
| #include "p2p/dtls/dtls_stun_piggyback_callbacks.h" | |
| #include "p2p/dtls/dtls_utils.h" | |
| #include "rtc_base/async_packet_socket.h" | |
| #include "rtc_base/byte_buffer.h" | |
| #include "rtc_base/checks.h" | |
| #include "rtc_base/crypto_random.h" | |
| #include "rtc_base/logging.h" | |
| #include "rtc_base/net_helper.h" | |
| #include "rtc_base/net_helpers.h" | |
| #include "rtc_base/network.h" | |
| #include "rtc_base/network/received_packet.h" | |
| #include "rtc_base/network/sent_packet.h" | |
| #include "rtc_base/network_constants.h" | |
| #include "rtc_base/socket.h" | |
| #include "rtc_base/socket_address.h" | |
| #include "rtc_base/string_encode.h" | |
| #include "rtc_base/string_utils.h" | |
| #include "rtc_base/strings/string_builder.h" | |
| #include "rtc_base/time_utils.h" | |
| #include "rtc_base/weak_ptr.h" | |
| namespace webrtc { | |
| namespace { | |
| // Determines whether we have seen at least the given maximum number of | |
| // pings fail to have a response. | |
| inline bool TooManyFailures( | |
| const std::vector<Connection::SentPing>& pings_since_last_response, | |
| uint32_t maximum_failures, | |
| TimeDelta rtt_estimate, | |
| Timestamp now) { | |
| // If we haven't sent that many pings, then we can't have failed that many. | |
| if (pings_since_last_response.size() < maximum_failures) | |
| return false; | |
| // Check if the window in which we would expect a response to the ping has | |
| // already elapsed. | |
| Timestamp expected_response_time = | |
| pings_since_last_response[maximum_failures - 1].sent_time + rtt_estimate; | |
| return now > expected_response_time; | |
| } | |
| // Determines whether we have gone too long without seeing any response. | |
| inline bool TooLongWithoutResponse( | |
| const std::vector<Connection::SentPing>& pings_since_last_response, | |
| TimeDelta maximum_time, | |
| Timestamp now) { | |
| if (pings_since_last_response.empty()) | |
| return false; | |
| auto first = pings_since_last_response[0]; | |
| return now > (first.sent_time + maximum_time); | |
| } | |
| // Helper methods for converting string values of log description fields to | |
| // enum. | |
| IceCandidateType GetRtcEventLogCandidateType(const Candidate& c) { | |
| if (c.is_local()) { | |
| return IceCandidateType::kHost; | |
| } else if (c.is_stun()) { | |
| return IceCandidateType::kSrflx; | |
| } else if (c.is_prflx()) { | |
| return IceCandidateType::kPrflx; | |
| } | |
| RTC_DCHECK(c.is_relay()); | |
| return IceCandidateType::kRelay; | |
| } | |
| IceCandidatePairProtocol GetProtocolByString(absl::string_view protocol) { | |
| if (protocol == UDP_PROTOCOL_NAME) { | |
| return IceCandidatePairProtocol::kUdp; | |
| } else if (protocol == TCP_PROTOCOL_NAME) { | |
| return IceCandidatePairProtocol::kTcp; | |
| } else if (protocol == SSLTCP_PROTOCOL_NAME) { | |
| return IceCandidatePairProtocol::kSsltcp; | |
| } else if (protocol == TLS_PROTOCOL_NAME) { | |
| return IceCandidatePairProtocol::kTls; | |
| } | |
| return IceCandidatePairProtocol::kUnknown; | |
| } | |
| IceCandidatePairAddressFamily GetAddressFamilyByInt(int address_family) { | |
| if (address_family == AF_INET) { | |
| return IceCandidatePairAddressFamily::kIpv4; | |
| } else if (address_family == AF_INET6) { | |
| return IceCandidatePairAddressFamily::kIpv6; | |
| } | |
| return IceCandidatePairAddressFamily::kUnknown; | |
| } | |
| IceCandidateNetworkType ConvertNetworkType(AdapterType type) { | |
| switch (type) { | |
| case ADAPTER_TYPE_ETHERNET: | |
| return IceCandidateNetworkType::kEthernet; | |
| case ADAPTER_TYPE_LOOPBACK: | |
| return IceCandidateNetworkType::kLoopback; | |
| case ADAPTER_TYPE_WIFI: | |
| return IceCandidateNetworkType::kWifi; | |
| case ADAPTER_TYPE_VPN: | |
| return IceCandidateNetworkType::kVpn; | |
| case ADAPTER_TYPE_CELLULAR: | |
| case ADAPTER_TYPE_CELLULAR_2G: | |
| case ADAPTER_TYPE_CELLULAR_3G: | |
| case ADAPTER_TYPE_CELLULAR_4G: | |
| case ADAPTER_TYPE_CELLULAR_5G: | |
| return IceCandidateNetworkType::kCellular; | |
| default: | |
| return IceCandidateNetworkType::kUnknown; | |
| } | |
| } | |
| // When we don't have any RTT data, we have to pick something reasonable. We | |
| // use a large value just in case the connection is really slow. | |
| constexpr TimeDelta kDefaultRtt = TimeDelta::Seconds(3); | |
| // We will restrict RTT estimates (when used for determining state) to be | |
| // within a reasonable range. | |
| constexpr TimeDelta kMinimumRtt = TimeDelta::Millis(100); | |
| constexpr TimeDelta kMaximumRtt = TimeDelta::Seconds(60); | |
| constexpr int kDefaultRttEstimateHalfTimeMs = 500; | |
| // Weighting of the old rtt value to new data. | |
| constexpr int kRttRatio = 3; // 3 : 1 | |
| constexpr TimeDelta kMinExtraPingDelay = TimeDelta::Millis(100); | |
| // Default field trials. | |
| const IceFieldTrials kDefaultFieldTrials; | |
| constexpr int kSupportGoogPingVersionRequestIndex = static_cast<int>( | |
| IceGoogMiscInfoBindingRequestAttributeIndex::SUPPORT_GOOG_PING_VERSION); | |
| constexpr int kSupportGoogPingVersionResponseIndex = static_cast<int>( | |
| IceGoogMiscInfoBindingResponseAttributeIndex::SUPPORT_GOOG_PING_VERSION); | |
| } // namespace | |
| // A ConnectionRequest is a STUN binding used to determine writability. | |
| class Connection::ConnectionRequest : public StunRequest { | |
| public: | |
| ConnectionRequest(const Environment& env, | |
| StunRequestManager& manager, | |
| Connection* connection, | |
| std::unique_ptr<IceMessage> message); | |
| void OnResponse(StunMessage* response) override; | |
| void OnErrorResponse(StunMessage* response) override; | |
| void OnTimeout() override; | |
| void OnSent() override; | |
| int resend_delay() override; | |
| private: | |
| Connection* const connection_; | |
| }; | |
| Connection::ConnectionRequest::ConnectionRequest( | |
| const Environment& env, | |
| StunRequestManager& manager, | |
| Connection* connection, | |
| std::unique_ptr<IceMessage> message) | |
| : StunRequest(env, manager, std::move(message)), connection_(connection) {} | |
| void Connection::ConnectionRequest::OnResponse(StunMessage* response) { | |
| RTC_DCHECK_RUN_ON(connection_->network_thread_); | |
| connection_->OnConnectionRequestResponse(this, response); | |
| } | |
| void Connection::ConnectionRequest::OnErrorResponse(StunMessage* response) { | |
| RTC_DCHECK_RUN_ON(connection_->network_thread_); | |
| connection_->OnConnectionRequestErrorResponse(this, response); | |
| } | |
| void Connection::ConnectionRequest::OnTimeout() { | |
| RTC_DCHECK_RUN_ON(connection_->network_thread_); | |
| connection_->OnConnectionRequestTimeout(this); | |
| } | |
| void Connection::ConnectionRequest::OnSent() { | |
| RTC_DCHECK_RUN_ON(connection_->network_thread_); | |
| connection_->OnConnectionRequestSent(this); | |
| // Each request is sent only once. After a single delay , the request will | |
| // time out. | |
| set_timed_out(); | |
| } | |
| int Connection::ConnectionRequest::resend_delay() { | |
| return kConnectionResponseTimeout.ms(); | |
| } | |
| Connection::Connection(const Environment& env, | |
| WeakPtr<PortInterface> port, | |
| size_t index, | |
| const Candidate& remote_candidate) | |
| : env_(env), | |
| network_thread_(port->thread()), | |
| id_(CreateRandomId()), | |
| port_(std::move(port)), | |
| local_candidate_(port_->Candidates()[index]), | |
| remote_candidate_(remote_candidate), | |
| recv_rate_tracker_(/*max_window_size=*/TimeDelta::Seconds(1)), | |
| send_rate_tracker_(/*max_window_size=*/TimeDelta::Seconds(1)), | |
| last_send_data_(Timestamp::Zero()), | |
| write_state_(STATE_WRITE_INIT), | |
| receiving_(false), | |
| connected_(true), | |
| pruned_(false), | |
| use_candidate_attr_(false), | |
| requests_(port_->thread(), | |
| [this](const void* data, size_t size, StunRequest* request) { | |
| OnSendStunPacket(data, size, request); | |
| }), | |
| rtt_(kDefaultRtt), | |
| last_ping_sent_(Timestamp::Zero()), | |
| last_ping_received_(Timestamp::Zero()), | |
| last_data_received_(Timestamp::Zero()), | |
| last_ping_response_received_(Timestamp::Zero()), | |
| receiving_unchanged_since_(Timestamp::Zero()), | |
| state_(IceCandidatePairState::WAITING), | |
| time_created_(env_.clock().CurrentTime()), | |
| delta_internal_unix_epoch_(Timestamp::Millis(TimeUTCMillis()) - | |
| time_created_), | |
| field_trials_(&kDefaultFieldTrials), | |
| rtt_estimate_(kDefaultRttEstimateHalfTimeMs) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| RTC_DCHECK(port_); | |
| RTC_LOG(LS_INFO) << ToString() << ": Connection created"; | |
| } | |
| Connection::~Connection() { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| RTC_DCHECK(!port_); | |
| RTC_DCHECK(!received_packet_callback_); | |
| } | |
| TaskQueueBase* Connection::network_thread() const { | |
| return network_thread_; | |
| } | |
| const Candidate& Connection::local_candidate() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return local_candidate_; | |
| } | |
| const Candidate& Connection::remote_candidate() const { | |
| return remote_candidate_; | |
| } | |
| const Network* Connection::network() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| RTC_DCHECK(port_) << ToDebugId() << ": port_ null in network()"; | |
| return port()->Network(); | |
| } | |
| int Connection::generation() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| RTC_DCHECK(port_) << ToDebugId() << ": port_ null in generation()"; | |
| return port()->generation(); | |
| } | |
| uint64_t Connection::priority() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| RTC_DCHECK(port_) << ToDebugId() << ": port_ null in priority()"; | |
| if (!port_) | |
| return 0; | |
| uint64_t priority = 0; | |
| // RFC 5245 - 5.7.2. Computing Pair Priority and Ordering Pairs | |
| // Let G be the priority for the candidate provided by the controlling | |
| // agent. Let D be the priority for the candidate provided by the | |
| // controlled agent. | |
| // pair priority = 2^32*MIN(G,D) + 2*MAX(G,D) + (G>D?1:0) | |
| IceRole role = port_->GetIceRole(); | |
| if (role != ICEROLE_UNKNOWN) { | |
| uint32_t g = 0; | |
| uint32_t d = 0; | |
| if (role == ICEROLE_CONTROLLING) { | |
| g = local_candidate().priority(); | |
| d = remote_candidate_.priority(); | |
| } else { | |
| g = remote_candidate_.priority(); | |
| d = local_candidate().priority(); | |
| } | |
| priority = std::min(g, d); | |
| priority = priority << 32; | |
| priority += 2 * std::max(g, d) + (g > d ? 1 : 0); | |
| } | |
| return priority; | |
| } | |
| void Connection::set_write_state(WriteState value) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| WriteState old_value = write_state_; | |
| write_state_ = value; | |
| if (value != old_value) { | |
| RTC_LOG(LS_VERBOSE) << ToString() << ": set_write_state from: " << old_value | |
| << " to " << value; | |
| NotifyStateChange(this); | |
| } | |
| } | |
| void Connection::UpdateReceiving(Timestamp now) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| bool receiving; | |
| if (LastPingSent() < LastPingResponseReceived()) { | |
| // We consider any candidate pair that has its last connectivity check | |
| // acknowledged by a response as receiving, particularly for backup | |
| // candidate pairs that send checks at a much slower pace than the selected | |
| // one. Otherwise, a backup candidate pair constantly becomes not receiving | |
| // as a side effect of a long ping interval, since we do not have a separate | |
| // receiving timeout for backup candidate pairs. See | |
| // IceConfig.ice_backup_candidate_pair_ping_interval, | |
| // IceConfig.ice_connection_receiving_timeout and their default value. | |
| receiving = true; | |
| } else { | |
| receiving = LastReceived() > Timestamp::Zero() && | |
| now <= LastReceived() + ReceivingTimeout(); | |
| } | |
| if (receiving_ == receiving) { | |
| return; | |
| } | |
| RTC_LOG(LS_VERBOSE) << ToString() << ": set_receiving to " << receiving; | |
| receiving_ = receiving; | |
| receiving_unchanged_since_ = now; | |
| NotifyStateChange(this); | |
| } | |
| void Connection::set_state(IceCandidatePairState state) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| IceCandidatePairState old_state = state_; | |
| state_ = state; | |
| if (state != old_state) { | |
| RTC_LOG(LS_VERBOSE) << ToString() << ": set_state"; | |
| } | |
| } | |
| void Connection::set_connected(bool value) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| bool old_value = connected_; | |
| connected_ = value; | |
| if (value != old_value) { | |
| RTC_LOG(LS_VERBOSE) << ToString() << ": Change connected_ to " << value; | |
| NotifyStateChange(this); | |
| } | |
| } | |
| bool Connection::use_candidate_attr() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return use_candidate_attr_; | |
| } | |
| void Connection::set_use_candidate_attr(bool enable) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| use_candidate_attr_ = enable; | |
| } | |
| void Connection::set_nomination(uint32_t value) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| nomination_ = value; | |
| } | |
| uint32_t Connection::remote_nomination() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return remote_nomination_; | |
| } | |
| bool Connection::nominated() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return acked_nomination_ || remote_nomination_; | |
| } | |
| TimeDelta Connection::UnwritableTimeout() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return unwritable_timeout_.value_or(kConnectionWriteConnectTimeout); | |
| } | |
| void Connection::SetUnwritableTimeout(std::optional<TimeDelta> value) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| unwritable_timeout_ = value; | |
| } | |
| int Connection::unwritable_min_checks() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return unwritable_min_checks_.value_or(kConnectionWriteConnectFailures); | |
| } | |
| void Connection::set_unwritable_min_checks(const std::optional<int>& value) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| unwritable_min_checks_ = value; | |
| } | |
| TimeDelta Connection::InactiveTimeout() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return inactive_timeout_.value_or(kConnectionWriteTimeout); | |
| } | |
| void Connection::SetInactiveTimeout(std::optional<TimeDelta> value) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| inactive_timeout_ = value; | |
| } | |
| TimeDelta Connection::ReceivingTimeout() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return receiving_timeout_.value_or(kWeakConnectionReceiveTimeout); | |
| } | |
| void Connection::SetReceivingTimeout( | |
| std::optional<TimeDelta> receiving_timeout) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| receiving_timeout_ = receiving_timeout; | |
| } | |
| void Connection::SetIceFieldTrials(const IceFieldTrials* field_trials) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| field_trials_ = field_trials; | |
| rtt_estimate_.SetHalfTime(field_trials->rtt_estimate_halftime_ms); | |
| } | |
| void Connection::OnSendStunPacket(const void* data, | |
| size_t size, | |
| StunRequest* req) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| AsyncSocketPacketOptions options(port_->StunDscpValue()); | |
| options.info_signaled_after_sent.packet_type = | |
| PacketType::kIceConnectivityCheck; | |
| auto err = | |
| port_->SendTo(data, size, remote_candidate_.address(), options, false); | |
| if (err < 0) { | |
| RTC_LOG(LS_WARNING) << ToString() | |
| << ": Failed to send STUN ping " | |
| " err=" | |
| << err << " id=" << hex_encode(req->id()); | |
| } | |
| } | |
| void Connection::RegisterReceivedPacketCallback( | |
| absl::AnyInvocable<void(Connection*, const ReceivedIpPacket&)> | |
| received_packet_callback) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| RTC_CHECK(!received_packet_callback_); | |
| received_packet_callback_ = std::move(received_packet_callback); | |
| } | |
| void Connection::DeregisterReceivedPacketCallback() { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| received_packet_callback_ = nullptr; | |
| } | |
| void Connection::OnReadPacket(const char* data, | |
| size_t size, | |
| int64_t packet_time_us) { | |
| OnReadPacket(ReceivedIpPacket::CreateFromLegacy(data, size, packet_time_us)); | |
| } | |
| void Connection::OnReadPacket(const ReceivedIpPacket& packet) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| std::unique_ptr<IceMessage> msg; | |
| std::string remote_ufrag; | |
| const SocketAddress& addr(remote_candidate_.address()); | |
| if (!port_->GetStunMessage( | |
| reinterpret_cast<const char*>(packet.payload().data()), | |
| packet.payload().size(), addr, &msg, &remote_ufrag)) { | |
| // The packet did not parse as a valid STUN message | |
| // This is a data packet, pass it along. | |
| last_data_received_ = env_.clock().CurrentTime(); | |
| UpdateReceiving(last_data_received_); | |
| recv_rate_tracker_.Update(packet.payload().size(), last_data_received_); | |
| stats_.recv_total_bytes += packet.payload().size(); | |
| stats_.packets_received++; | |
| if (received_packet_callback_) { | |
| received_packet_callback_(this, packet); | |
| } | |
| // If timed out sending writability checks, start up again | |
| if (!pruned_ && (write_state_ == STATE_WRITE_TIMEOUT)) { | |
| RTC_LOG(LS_WARNING) | |
| << "Received a data packet on a timed-out Connection. " | |
| "Resetting state to STATE_WRITE_INIT."; | |
| set_write_state(STATE_WRITE_INIT); | |
| } | |
| return; | |
| } else if (!msg) { | |
| // The packet was STUN, but failed a check and was handled internally. | |
| return; | |
| } | |
| // The packet is STUN and passed the Port checks. | |
| // Perform our own checks to ensure this packet is valid. | |
| // If this is a STUN request, then update the receiving bit and respond. | |
| // If this is a STUN response, then update the writable bit. | |
| // Log at LS_INFO if we receive a ping on an unwritable connection. | |
| // REQUESTs have msg->integrity() already checked in Port | |
| // RESPONSEs have msg->integrity() checked below. | |
| // INDICATION does not have any integrity. | |
| if (IsStunRequestType(msg->type())) { | |
| if (msg->integrity() != StunMessage::IntegrityStatus::kIntegrityOk) { | |
| // "silently" discard the request. | |
| RTC_LOG(LS_VERBOSE) << ToString() << ": Discarding " | |
| << StunMethodToString(msg->type()) | |
| << ", id=" << hex_encode(msg->transaction_id()) | |
| << " with invalid message integrity: " | |
| << static_cast<int>(msg->integrity()); | |
| return; | |
| } | |
| // fall-through | |
| } else if (IsStunSuccessResponseType(msg->type()) || | |
| IsStunErrorResponseType(msg->type())) { | |
| RTC_DCHECK(msg->integrity() == StunMessage::IntegrityStatus::kNotSet); | |
| if (msg->ValidateMessageIntegrity(remote_candidate().password()) != | |
| StunMessage::IntegrityStatus::kIntegrityOk) { | |
| // "silently" discard the response. | |
| RTC_LOG(LS_VERBOSE) << ToString() << ": Discarding " | |
| << StunMethodToString(msg->type()) | |
| << ", id=" << hex_encode(msg->transaction_id()) | |
| << " with invalid message integrity: " | |
| << static_cast<int>(msg->integrity()); | |
| return; | |
| } | |
| } else { | |
| RTC_DCHECK(IsStunIndicationType(msg->type())); | |
| // No message integrity. | |
| } | |
| LoggingSeverity sev = (!writable() ? LS_INFO : LS_VERBOSE); | |
| switch (msg->type()) { | |
| case STUN_BINDING_REQUEST: | |
| RTC_LOG_V(sev) << ToString() << ": Received " | |
| << StunMethodToString(msg->type()) | |
| << ", id=" << hex_encode(msg->transaction_id()); | |
| if (remote_ufrag == remote_candidate_.username()) { | |
| HandleStunBindingOrGoogPingRequest(msg.get()); | |
| } else { | |
| // The packet had the right local username, but the remote username | |
| // was not the right one for the remote address. | |
| RTC_LOG(LS_ERROR) << ToString() | |
| << ": Received STUN request with bad remote username " | |
| << remote_ufrag; | |
| port_->SendBindingErrorResponse(msg.get(), addr, | |
| STUN_ERROR_UNAUTHORIZED, | |
| STUN_ERROR_REASON_UNAUTHORIZED); | |
| } | |
| break; | |
| // Response from remote peer. Does it match request sent? | |
| // This doesn't just check, it makes callbacks if transaction | |
| // id's match. | |
| case STUN_BINDING_RESPONSE: | |
| case STUN_BINDING_ERROR_RESPONSE: | |
| requests_.CheckResponse(msg.get()); | |
| break; | |
| // Remote end point sent an STUN indication instead of regular binding | |
| // request. In this case `last_ping_received_` will be updated but no | |
| // response will be sent. | |
| case STUN_BINDING_INDICATION: | |
| ReceivedPing(msg->transaction_id()); | |
| break; | |
| case GOOG_PING_REQUEST: | |
| // Checked in Port::GetStunMessage. | |
| HandleStunBindingOrGoogPingRequest(msg.get()); | |
| break; | |
| case GOOG_PING_RESPONSE: | |
| case GOOG_PING_ERROR_RESPONSE: | |
| requests_.CheckResponse(msg.get()); | |
| break; | |
| default: | |
| RTC_DCHECK_NOTREACHED(); | |
| break; | |
| } | |
| } | |
| void Connection::MaybeAddDtlsPiggybackingAttributes(StunMessage* msg) { | |
| if (dtls_stun_piggyback_callbacks_.empty()) { | |
| return; | |
| } | |
| const auto& [attr, ack] = dtls_stun_piggyback_callbacks_.send_data( | |
| static_cast<StunMessageType>(msg->type())); | |
| if (ack) { | |
| size_t msg_length = msg->length(); | |
| size_t need_length = | |
| kStunAttributeHeaderSize + ack->size() * sizeof(uint32_t); | |
| if (msg_length + need_length <= kMaxStunBindingLength) { | |
| msg->AddAttribute(std::make_unique<StunByteStringAttribute>( | |
| STUN_ATTR_META_DTLS_IN_STUN_ACK, *ack)); | |
| } else if (msg_length + kStunAttributeHeaderSize <= kMaxStunBindingLength) { | |
| // Add en empty ACK. | |
| std::string empty; | |
| msg->AddAttribute(std::make_unique<StunByteStringAttribute>( | |
| STUN_ATTR_META_DTLS_IN_STUN_ACK, empty)); | |
| } else { | |
| return; | |
| } | |
| } | |
| if (attr) { | |
| size_t need_length = attr->length() + kStunAttributeHeaderSize; | |
| if (msg->length() + need_length <= kMaxStunBindingLength) { | |
| msg->AddAttribute(std::make_unique<StunByteStringAttribute>( | |
| STUN_ATTR_META_DTLS_IN_STUN, *attr)); | |
| } | |
| } | |
| } | |
| void Connection::MaybeHandleDtlsPiggybackingAttributes( | |
| const StunMessage* msg, | |
| const StunRequest* original_request) { | |
| if (dtls_stun_piggyback_callbacks_.empty()) { | |
| return; | |
| } | |
| const StunByteStringAttribute* dtls_piggyback_attr = | |
| msg->GetByteString(STUN_ATTR_META_DTLS_IN_STUN); | |
| const StunByteStringAttribute* dtls_piggyback_ack = | |
| msg->GetByteString(STUN_ATTR_META_DTLS_IN_STUN_ACK); | |
| std::optional<std::span<uint8_t>> piggyback_data; | |
| if (dtls_piggyback_attr != nullptr) { | |
| piggyback_data = dtls_piggyback_attr->array_view(); | |
| } | |
| std::optional<std::vector<uint32_t>> piggyback_acks; | |
| if (dtls_piggyback_ack != nullptr) { | |
| piggyback_acks = dtls_piggyback_ack->GetUInt32Vector(); | |
| } | |
| // A response implicitly acknowledges the original embedded packet | |
| // when the ack attribute is included. | |
| if (dtls_piggyback_ack != nullptr && original_request != nullptr) { | |
| const StunByteStringAttribute* request_dtls_piggyback = | |
| original_request->msg()->GetByteString(STUN_ATTR_META_DTLS_IN_STUN); | |
| if (request_dtls_piggyback) { | |
| uint32_t sent_hash = | |
| ComputeDtlsPacketHash(request_dtls_piggyback->array_view()); | |
| if (!piggyback_acks) { | |
| piggyback_acks = {}; | |
| } | |
| piggyback_acks->push_back(sent_hash); | |
| } | |
| } | |
| dtls_stun_piggyback_callbacks_.recv_data(piggyback_data, piggyback_acks); | |
| } | |
| void Connection::HandleStunBindingOrGoogPingRequest(IceMessage* msg) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| // This connection should now be receiving. | |
| ReceivedPing(msg->transaction_id()); | |
| if (field_trials_->extra_ice_ping && | |
| last_ping_response_received_ <= Timestamp::Zero()) { | |
| if (local_candidate().is_relay() || local_candidate().is_prflx() || | |
| remote_candidate().is_relay() || remote_candidate().is_prflx()) { | |
| const Timestamp now = env_.clock().CurrentTime(); | |
| if (last_ping_sent_ + kMinExtraPingDelay <= now) { | |
| RTC_LOG(LS_INFO) << ToString() | |
| << "WebRTC-ExtraICEPing/Sending extra ping" | |
| " last_ping_sent_: " | |
| << last_ping_sent_ << " now: " << now | |
| << " (diff: " << (now - last_ping_sent_) << ")"; | |
| Ping(now); | |
| } else { | |
| RTC_LOG(LS_INFO) << ToString() | |
| << "WebRTC-ExtraICEPing/Not sending extra ping" | |
| " last_ping_sent_: " | |
| << last_ping_sent_ << " now: " << now | |
| << " (diff: " << (now - last_ping_sent_) << ")"; | |
| } | |
| } | |
| } | |
| const SocketAddress& remote_addr = remote_candidate_.address(); | |
| if (msg->type() == STUN_BINDING_REQUEST) { | |
| // Check for role conflicts. | |
| const std::string& remote_ufrag = remote_candidate_.username(); | |
| if (!port_->MaybeIceRoleConflict(remote_addr, msg, remote_ufrag)) { | |
| // Received conflicting role from the peer. | |
| RTC_LOG(LS_INFO) << "Received conflicting role from the peer."; | |
| return; | |
| } | |
| } | |
| stats_.recv_ping_requests++; | |
| LogCandidatePairEvent(IceCandidatePairEventType::kCheckReceived, | |
| msg->reduced_transaction_id()); | |
| // This is a validated stun request from remote peer. | |
| if (msg->type() == STUN_BINDING_REQUEST) { | |
| MaybeHandleDtlsPiggybackingAttributes(msg, /*original_request=*/nullptr); | |
| SendStunBindingResponse(msg); | |
| } else { | |
| RTC_DCHECK(msg->type() == GOOG_PING_REQUEST); | |
| SendGoogPingResponse(msg); | |
| } | |
| // If it timed out on writing check, start up again | |
| if (!pruned_ && write_state_ == STATE_WRITE_TIMEOUT) { | |
| set_write_state(STATE_WRITE_INIT); | |
| } | |
| if (port_->GetIceRole() == ICEROLE_CONTROLLED) { | |
| const StunUInt32Attribute* nomination_attr = | |
| msg->GetUInt32(STUN_ATTR_NOMINATION); | |
| uint32_t nomination = 0; | |
| if (nomination_attr) { | |
| nomination = nomination_attr->value(); | |
| if (nomination == 0) { | |
| RTC_LOG(LS_ERROR) << "Invalid nomination: " << nomination; | |
| } | |
| } else { | |
| const StunByteStringAttribute* use_candidate_attr = | |
| msg->GetByteString(STUN_ATTR_USE_CANDIDATE); | |
| if (use_candidate_attr) { | |
| nomination = 1; | |
| } | |
| } | |
| // We don't un-nominate a connection, so we only keep a larger nomination. | |
| if (nomination > remote_nomination_) { | |
| set_remote_nomination(nomination); | |
| NotifyNominated(this); | |
| } | |
| } | |
| // Set the remote cost if the network_info attribute is available. | |
| // Note: If packets are re-ordered, we may get incorrect network cost | |
| // temporarily, but it should get the correct value shortly after that. | |
| const StunUInt32Attribute* network_attr = | |
| msg->GetUInt32(STUN_ATTR_GOOG_NETWORK_INFO); | |
| if (network_attr) { | |
| uint32_t network_info = network_attr->value(); | |
| uint16_t network_cost = static_cast<uint16_t>(network_info); | |
| if (network_cost != remote_candidate_.network_cost()) { | |
| remote_candidate_.set_network_cost(network_cost); | |
| // Network cost change will affect the connection ranking, so signal | |
| // state change to force a re-sort in P2PTransportChannel. | |
| NotifyStateChange(this); | |
| } | |
| } | |
| if (field_trials_->piggyback_ice_check_acknowledgement) { | |
| HandlePiggybackCheckAcknowledgementIfAny(msg); | |
| } | |
| } | |
| void Connection::SendStunBindingResponse(const StunMessage* message) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| RTC_DCHECK_EQ(message->type(), STUN_BINDING_REQUEST); | |
| // Retrieve the username from the `message`. | |
| const StunByteStringAttribute* username_attr = | |
| message->GetByteString(STUN_ATTR_USERNAME); | |
| RTC_DCHECK(username_attr != nullptr); | |
| if (username_attr == nullptr) { | |
| // No valid username, skip the response. | |
| return; | |
| } | |
| // Fill in the response. | |
| StunMessage response(STUN_BINDING_RESPONSE, message->transaction_id()); | |
| const StunUInt32Attribute* retransmit_attr = | |
| message->GetUInt32(STUN_ATTR_RETRANSMIT_COUNT); | |
| if (retransmit_attr) { | |
| // Inherit the incoming retransmit value in the response so the other side | |
| // can see our view of lost pings. | |
| response.AddAttribute(std::make_unique<StunUInt32Attribute>( | |
| STUN_ATTR_RETRANSMIT_COUNT, retransmit_attr->value())); | |
| if (retransmit_attr->value() > kConnectionWriteConnectFailures) { | |
| RTC_LOG(LS_INFO) | |
| << ToString() | |
| << ": Received a remote ping with high retransmit count: " | |
| << retransmit_attr->value(); | |
| } | |
| } | |
| response.AddAttribute(std::make_unique<StunXorAddressAttribute>( | |
| STUN_ATTR_XOR_MAPPED_ADDRESS, remote_candidate_.address())); | |
| if (field_trials_->announce_goog_ping) { | |
| // Check if message contains a announce-request. | |
| auto goog_misc = message->GetUInt16List(STUN_ATTR_GOOG_MISC_INFO); | |
| if (goog_misc != nullptr && | |
| goog_misc->Size() >= kSupportGoogPingVersionRequestIndex && | |
| // Which version can we handle...currently any >= 1 | |
| goog_misc->GetType(kSupportGoogPingVersionRequestIndex) >= 1) { | |
| auto list = | |
| StunAttribute::CreateUInt16ListAttribute(STUN_ATTR_GOOG_MISC_INFO); | |
| list->AddTypeAtIndex(kSupportGoogPingVersionResponseIndex, | |
| kGoogPingVersion); | |
| response.AddAttribute(std::move(list)); | |
| } | |
| } | |
| const StunByteStringAttribute* delta = | |
| message->GetByteString(STUN_ATTR_GOOG_DELTA); | |
| if (delta) { | |
| if (field_trials_->answer_goog_delta && goog_delta_consumer_) { | |
| auto ack = (*goog_delta_consumer_)(delta); | |
| if (ack) { | |
| RTC_LOG(LS_INFO) << "Sending GOOG_DELTA_ACK" | |
| << " delta len: " << delta->length(); | |
| response.AddAttribute(std::move(ack)); | |
| } else { | |
| RTC_LOG(LS_ERROR) << "GOOG_DELTA consumer did not return ack!"; | |
| } | |
| } else { | |
| RTC_LOG(LS_WARNING) << "Ignore GOOG_DELTA" << " len: " << delta->length() | |
| << " answer_goog_delta = " | |
| << field_trials_->answer_goog_delta | |
| << " goog_delta_consumer_ = " | |
| << goog_delta_consumer_.has_value(); | |
| } | |
| } | |
| MaybeAddDtlsPiggybackingAttributes(&response); | |
| response.AddMessageIntegrity(local_candidate().password()); | |
| response.AddFingerprint(); | |
| SendResponseMessage(response); | |
| } | |
| void Connection::SendGoogPingResponse(const StunMessage* message) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| RTC_DCHECK(message->type() == GOOG_PING_REQUEST); | |
| // Fill in the response. | |
| StunMessage response(GOOG_PING_RESPONSE, message->transaction_id()); | |
| response.AddMessageIntegrity32(local_candidate().password()); | |
| SendResponseMessage(response); | |
| } | |
| void Connection::SendResponseMessage(const StunMessage& response) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| // Where I send the response. | |
| const SocketAddress& addr = remote_candidate_.address(); | |
| // Send the response. | |
| ByteBufferWriter buf; | |
| response.Write(&buf); | |
| AsyncSocketPacketOptions options(port_->StunDscpValue()); | |
| options.info_signaled_after_sent.packet_type = | |
| PacketType::kIceConnectivityCheckResponse; | |
| auto err = port_->SendTo(buf.Data(), buf.Length(), addr, options, false); | |
| if (err < 0) { | |
| RTC_LOG(LS_ERROR) << ToString() << ": Failed to send " | |
| << StunMethodToString(response.type()) | |
| << ", to=" << addr.ToSensitiveString() << ", err=" << err | |
| << ", id=" << hex_encode(response.transaction_id()); | |
| } else { | |
| // Log at LS_INFO if we send a stun ping response on an unwritable | |
| // connection. | |
| LoggingSeverity sev = (!writable()) ? LS_INFO : LS_VERBOSE; | |
| RTC_LOG_V(sev) << ToString() << ": Sent " | |
| << StunMethodToString(response.type()) | |
| << ", to=" << addr.ToSensitiveString() | |
| << ", id=" << hex_encode(response.transaction_id()); | |
| stats_.sent_ping_responses++; | |
| LogCandidatePairEvent(IceCandidatePairEventType::kCheckResponseSent, | |
| response.reduced_transaction_id()); | |
| } | |
| } | |
| uint32_t Connection::acked_nomination() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return acked_nomination_; | |
| } | |
| void Connection::set_remote_nomination(uint32_t remote_nomination) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| remote_nomination_ = remote_nomination; | |
| } | |
| void Connection::OnReadyToSend() { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| NotifyReadyToSend(this); | |
| } | |
| bool Connection::pruned() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return pruned_; | |
| } | |
| void Connection::Prune() { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| if (!pruned_ || active()) { | |
| RTC_LOG(LS_INFO) << ToString() << ": Connection pruned"; | |
| pruned_ = true; | |
| requests_.Clear(); | |
| set_write_state(STATE_WRITE_TIMEOUT); | |
| } | |
| } | |
| void Connection::Destroy() { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| RTC_DCHECK(port_) << ToDebugId() << ": port_ null in Destroy()"; | |
| if (port_) | |
| port_->DestroyConnection(this); | |
| } | |
| bool Connection::Shutdown() { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| RTC_DCHECK(port_) << ToDebugId() << ": Calling Shutdown() twice?"; | |
| if (!port_) | |
| return false; // already shut down. | |
| RTC_DLOG(LS_VERBOSE) << ToString() << ": Connection destroyed"; | |
| // Fire the 'destroyed' event before deleting the object. This is done | |
| // intentionally to avoid a situation whereby the signal might have dangling | |
| // pointers to objects that have been deleted by the time the async task | |
| // that deletes the connection object runs. | |
| // Note: With CallbackList, there's no way of clearing all callbacks. | |
| // It would be cleaner if all callbacks used a safety flag on objects | |
| // whose existence they care about. | |
| // TODO: issues.webrtc.org/42222066 - figure out if this matters. | |
| NotifyDestroyed(this); | |
| LogCandidatePairConfig(IceCandidatePairConfigType::kDestroyed); | |
| // Reset the `port_` after logging and firing the destroyed signal since | |
| // information required for logging needs access to `port_`. | |
| port_.reset(); | |
| // Clear any pending requests (or responses). | |
| requests_.Clear(); | |
| return true; | |
| } | |
| void Connection::FailAndPrune() { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| // TODO(bugs.webrtc.org/13865): There's a circular dependency between Port | |
| // and Connection. In some cases (Port dtor), a Connection object is deleted | |
| // without using the `Destroy` method (port_ won't be nulled and some | |
| // functionality won't run as expected), while in other cases | |
| // the Connection object is deleted asynchronously and in that case `port_` | |
| // will be nulled. | |
| // In such a case, there's a chance that the Port object gets | |
| // deleted before the Connection object ends up being deleted. | |
| RTC_DCHECK(port_) << ToDebugId() << ": port_ null in FailAndPrune()"; | |
| if (!port_) | |
| return; | |
| set_state(IceCandidatePairState::FAILED); | |
| Prune(); | |
| } | |
| void Connection::PrintPingsSinceLastResponse(std::string* s, size_t max) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| StringBuilder oss; | |
| if (pings_since_last_response_.size() > max) { | |
| for (size_t i = 0; i < max; i++) { | |
| const SentPing& ping = pings_since_last_response_[i]; | |
| oss << hex_encode(ping.id) << " "; | |
| } | |
| oss << "... " << (pings_since_last_response_.size() - max) << " more"; | |
| } else { | |
| for (const SentPing& ping : pings_since_last_response_) { | |
| oss << hex_encode(ping.id) << " "; | |
| } | |
| } | |
| *s = oss.Release(); | |
| } | |
| bool Connection::selected() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return selected_; | |
| } | |
| void Connection::set_selected(bool selected) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| selected_ = selected; | |
| } | |
| void Connection::UpdateState(Timestamp now) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| RTC_DCHECK(port_) << ToDebugId() << ": port_ null in UpdateState()"; | |
| if (!port_) | |
| return; | |
| // Computes our estimate of the RTT given the current estimate. | |
| TimeDelta rtt = std::clamp(2 * rtt_, kMinimumRtt, kMaximumRtt); | |
| if (RTC_LOG_CHECK_LEVEL(LS_VERBOSE)) { | |
| std::string pings; | |
| PrintPingsSinceLastResponse(&pings, 5); | |
| RTC_LOG(LS_VERBOSE) << ToString() | |
| << ": UpdateState()" | |
| ", ms since last received response=" | |
| << now - last_ping_response_received_ | |
| << ", ms since last received data=" | |
| << now - last_data_received_ << ", rtt=" << rtt | |
| << ", pings_since_last_response=" << pings; | |
| } | |
| // Check the writable state. (The order of these checks is important.) | |
| // | |
| // Before becoming unwritable, we allow for a fixed number of pings to fail | |
| // (i.e., receive no response). We also have to give the response time to | |
| // get back, so we include a conservative estimate of this. | |
| // | |
| // Before timing out writability, we give a fixed amount of time. This is to | |
| // allow for changes in network conditions. | |
| if ((write_state_ == STATE_WRITABLE) && | |
| TooManyFailures(pings_since_last_response_, unwritable_min_checks(), rtt, | |
| now) && | |
| TooLongWithoutResponse(pings_since_last_response_, UnwritableTimeout(), | |
| now)) { | |
| uint32_t max_pings = unwritable_min_checks(); | |
| RTC_LOG(LS_INFO) << ToString() << ": Unwritable after " << max_pings | |
| << " ping failures and " | |
| << now - pings_since_last_response_[0].sent_time | |
| << " ms without a response," | |
| " ms since last received ping=" | |
| << now - last_ping_received_ | |
| << " ms since last received data=" | |
| << now - last_data_received_ << " rtt=" << rtt; | |
| set_write_state(STATE_WRITE_UNRELIABLE); | |
| } | |
| if ((write_state_ == STATE_WRITE_UNRELIABLE || | |
| write_state_ == STATE_WRITE_INIT) && | |
| TooLongWithoutResponse(pings_since_last_response_, InactiveTimeout(), | |
| now)) { | |
| RTC_LOG(LS_INFO) << ToString() << ": Timed out after " | |
| << now - pings_since_last_response_[0].sent_time | |
| << " ms without a response, rtt=" << rtt; | |
| set_write_state(STATE_WRITE_TIMEOUT); | |
| } | |
| // Update the receiving state. | |
| UpdateReceiving(now); | |
| if (dead(now)) { | |
| port_->DestroyConnectionAsync(this); | |
| } | |
| } | |
| void Connection::UpdateLocalIceParameters(int component, | |
| absl::string_view username_fragment, | |
| absl::string_view password) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| local_candidate_.set_component(component); | |
| local_candidate_.set_username(username_fragment); | |
| local_candidate_.set_password(password); | |
| } | |
| Timestamp Connection::LastPingSent() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return last_ping_sent_; | |
| } | |
| void Connection::Ping() { | |
| Ping(env_.clock().CurrentTime()); | |
| } | |
| void Connection::Ping(Timestamp now, | |
| std::unique_ptr<StunByteStringAttribute> delta) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| RTC_DCHECK(port_) << ToDebugId() << ": port_ null in Ping()"; | |
| if (!port_) | |
| return; | |
| last_ping_sent_ = now; | |
| // If not using renomination, we use "1" to mean "nominated" and "0" to mean | |
| // "not nominated". If using renomination, values greater than 1 are used for | |
| // re-nominated pairs. | |
| int nomination = use_candidate_attr_ ? 1 : 0; | |
| if (nomination_ > 0) { | |
| nomination = nomination_; | |
| } | |
| bool has_delta = delta != nullptr; | |
| auto req = std::make_unique<ConnectionRequest>( | |
| env_, requests_, this, BuildPingRequest(std::move(delta))); | |
| if (!has_delta && ShouldSendGoogPing(req->msg())) { | |
| auto message = std::make_unique<IceMessage>(GOOG_PING_REQUEST, req->id()); | |
| message->AddMessageIntegrity32(remote_candidate_.password()); | |
| req = std::make_unique<ConnectionRequest>(env_, requests_, this, | |
| std::move(message)); | |
| } | |
| pings_since_last_response_.push_back(SentPing(req->id(), now, nomination)); | |
| RTC_LOG(LS_VERBOSE) << ToString() | |
| << ": Sending STUN ping, id=" << hex_encode(req->id()) | |
| << ", nomination=" << nomination_; | |
| requests_.Send(std::move(req)); | |
| state_ = IceCandidatePairState::IN_PROGRESS; | |
| num_pings_sent_++; | |
| } | |
| std::unique_ptr<IceMessage> Connection::BuildPingRequest( | |
| std::unique_ptr<StunByteStringAttribute> delta) { | |
| auto message = std::make_unique<IceMessage>(STUN_BINDING_REQUEST); | |
| // Note that the order of attributes does not impact the parsing on the | |
| // receiver side. The attribute is retrieved then by iterating and matching | |
| // over all parsed attributes. See StunMessage::GetAttribute. | |
| message->AddAttribute(std::make_unique<StunByteStringAttribute>( | |
| STUN_ATTR_USERNAME, | |
| port()->CreateStunUsername(remote_candidate_.username()))); | |
| message->AddAttribute(std::make_unique<StunUInt32Attribute>( | |
| STUN_ATTR_GOOG_NETWORK_INFO, | |
| (port_->Network()->id() << 16) | port_->network_cost())); | |
| if (field_trials_->piggyback_ice_check_acknowledgement && | |
| last_ping_id_received_) { | |
| message->AddAttribute(std::make_unique<StunByteStringAttribute>( | |
| STUN_ATTR_GOOG_LAST_ICE_CHECK_RECEIVED, *last_ping_id_received_)); | |
| } | |
| // Adding ICE_CONTROLLED or ICE_CONTROLLING attribute based on the role. | |
| IceRole ice_role = port_->GetIceRole(); | |
| RTC_DCHECK(ice_role == ICEROLE_CONTROLLING || ice_role == ICEROLE_CONTROLLED); | |
| message->AddAttribute(std::make_unique<StunUInt64Attribute>( | |
| ice_role == ICEROLE_CONTROLLING ? STUN_ATTR_ICE_CONTROLLING | |
| : STUN_ATTR_ICE_CONTROLLED, | |
| port_->IceTiebreaker())); | |
| if (ice_role == ICEROLE_CONTROLLING) { | |
| // We should have either USE_CANDIDATE attribute or ICE_NOMINATION | |
| // attribute but not both. That was enforced in p2ptransportchannel. | |
| if (use_candidate_attr()) { | |
| message->AddAttribute( | |
| std::make_unique<StunByteStringAttribute>(STUN_ATTR_USE_CANDIDATE)); | |
| } | |
| if (nomination_ && nomination_ != acked_nomination()) { | |
| message->AddAttribute(std::make_unique<StunUInt32Attribute>( | |
| STUN_ATTR_NOMINATION, nomination_)); | |
| } | |
| } | |
| message->AddAttribute(std::make_unique<StunUInt32Attribute>( | |
| STUN_ATTR_PRIORITY, prflx_priority())); | |
| if (port()->send_retransmit_count_attribute()) { | |
| message->AddAttribute(std::make_unique<StunUInt32Attribute>( | |
| STUN_ATTR_RETRANSMIT_COUNT, pings_since_last_response_.size())); | |
| } | |
| if (field_trials_->enable_goog_ping && | |
| !remote_support_goog_ping_.has_value()) { | |
| // Check if remote supports GOOG PING by announcing which version we | |
| // support. This is sent on all STUN_BINDING_REQUEST until we get a | |
| // STUN_BINDING_RESPONSE. | |
| auto list = | |
| StunAttribute::CreateUInt16ListAttribute(STUN_ATTR_GOOG_MISC_INFO); | |
| list->AddTypeAtIndex(kSupportGoogPingVersionRequestIndex, kGoogPingVersion); | |
| message->AddAttribute(std::move(list)); | |
| } | |
| if (delta) { | |
| RTC_DCHECK(delta->type() == STUN_ATTR_GOOG_DELTA); | |
| size_t msg_length = message->length(); | |
| if (msg_length + kStunAttributeHeaderSize + delta->length() < | |
| kMaxStunBindingLength) { | |
| RTC_LOG(LS_INFO) << "Sending GOOG_DELTA: len: " << delta->length(); | |
| message->AddAttribute(std::move(delta)); | |
| } else { | |
| RTC_LOG(LS_WARNING) << "Not sending GOOG_DELTA, request full: len: " | |
| << delta->length() << " msg_length: " << msg_length; | |
| } | |
| } | |
| MaybeAddDtlsPiggybackingAttributes(message.get()); | |
| message->AddMessageIntegrity(remote_candidate_.password()); | |
| message->AddFingerprint(); | |
| return message; | |
| } | |
| Timestamp Connection::LastPingResponseReceived() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return last_ping_response_received_; | |
| } | |
| const std::optional<std::string>& Connection::last_ping_id_received() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return last_ping_id_received_; | |
| } | |
| // Used to check if any STUN ping response has been received. | |
| int Connection::rtt_samples() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return rtt_samples_; | |
| } | |
| // Called whenever a valid ping is received on this connection. This is | |
| // public because the connection intercepts the first ping for us. | |
| Timestamp Connection::LastPingReceived() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return last_ping_received_; | |
| } | |
| void Connection::ReceivedPing(const std::optional<std::string>& request_id) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| last_ping_received_ = env_.clock().CurrentTime(); | |
| last_ping_id_received_ = request_id; | |
| UpdateReceiving(last_ping_received_); | |
| } | |
| void Connection::HandlePiggybackCheckAcknowledgementIfAny(StunMessage* msg) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| RTC_DCHECK(msg->type() == STUN_BINDING_REQUEST || | |
| msg->type() == GOOG_PING_REQUEST); | |
| const StunByteStringAttribute* last_ice_check_received_attr = | |
| msg->GetByteString(STUN_ATTR_GOOG_LAST_ICE_CHECK_RECEIVED); | |
| if (last_ice_check_received_attr) { | |
| const absl::string_view request_id = | |
| last_ice_check_received_attr->string_view(); | |
| auto iter = absl::c_find_if( | |
| pings_since_last_response_, | |
| [&request_id](const SentPing& ping) { return ping.id == request_id; }); | |
| if (iter != pings_since_last_response_.end()) { | |
| LoggingSeverity sev = !writable() ? LS_INFO : LS_VERBOSE; | |
| RTC_LOG_V(sev) << ToString() | |
| << ": Received piggyback STUN ping response, id=" | |
| << hex_encode(request_id); | |
| const TimeDelta rtt = env_.clock().CurrentTime() - iter->sent_time; | |
| ReceivedPingResponse(rtt, request_id, iter->nomination); | |
| } | |
| } | |
| } | |
| Timestamp Connection::LastSendData() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return last_send_data_; | |
| } | |
| Timestamp Connection::LastDataReceived() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return last_data_received_; | |
| } | |
| void Connection::ReceivedPingResponse( | |
| TimeDelta rtt, | |
| absl::string_view /* request_id */, | |
| const std::optional<uint32_t>& nomination) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| RTC_DCHECK_GE(rtt, TimeDelta::Zero()); | |
| // We've already validated that this is a STUN binding response with | |
| // the correct local and remote username for this connection. | |
| // So if we're not already, become writable. We may be bringing a pruned | |
| // connection back to life, but if we don't really want it, we can always | |
| // prune it again. | |
| if (nomination && nomination.value() > acked_nomination_) { | |
| acked_nomination_ = nomination.value(); | |
| } | |
| Timestamp now = env_.clock().CurrentTime(); | |
| total_round_trip_time_ += rtt; | |
| current_round_trip_time_ = rtt; | |
| rtt_estimate_.AddSample(now.ms(), rtt.ms()); | |
| pings_since_last_response_.clear(); | |
| last_ping_response_received_ = now; | |
| UpdateReceiving(last_ping_response_received_); | |
| set_write_state(STATE_WRITABLE); | |
| set_state(IceCandidatePairState::SUCCEEDED); | |
| // Smooth the RTT estimate using a moving average. | |
| if (rtt_samples_ > 0) { | |
| rtt_ = (kRttRatio * rtt_ + rtt) / (kRttRatio + 1); | |
| } else { | |
| rtt_ = rtt; | |
| } | |
| rtt_samples_++; | |
| } | |
| Connection::WriteState Connection::write_state() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return write_state_; | |
| } | |
| bool Connection::writable() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return write_state_ == STATE_WRITABLE; | |
| } | |
| bool Connection::receiving() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return receiving_; | |
| } | |
| // Determines whether the connection has finished connecting. This can only | |
| // be false for TCP connections. | |
| bool Connection::connected() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return connected_; | |
| } | |
| bool Connection::weak() const { | |
| return !(writable() && receiving() && connected()); | |
| } | |
| bool Connection::active() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return write_state_ != STATE_WRITE_TIMEOUT; | |
| } | |
| bool Connection::dead(Timestamp now) const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| if (LastReceived() > Timestamp::Zero()) { | |
| // If it has ever received anything, we keep it alive | |
| // - if it has recevied last DEAD_CONNECTION_RECEIVE_TIMEOUT (30s) | |
| // - if it has a ping outstanding shorter than | |
| // DEAD_CONNECTION_RECEIVE_TIMEOUT (30s) | |
| // - else if IDLE let it live field_trials_->dead_connection_timeout_ms | |
| // | |
| // This covers the normal case of a successfully used connection that stops | |
| // working. This also allows a remote peer to continue pinging over a | |
| // locally inactive (pruned) connection. This also allows the local agent to | |
| // ping with longer interval than 30s as long as it shorter than | |
| // `dead_connection_timeout_ms`. | |
| if (now <= (LastReceived() + kDeadConnectionReceiveTimeout)) { | |
| // Not dead since we have received the last 30s. | |
| return false; | |
| } | |
| if (!pings_since_last_response_.empty()) { | |
| // Outstanding pings: let it live until the ping is unreplied for | |
| // DEAD_CONNECTION_RECEIVE_TIMEOUT. | |
| return now > (pings_since_last_response_[0].sent_time + | |
| kDeadConnectionReceiveTimeout); | |
| } | |
| // No outstanding pings: let it live until | |
| // field_trials_->dead_connection_timeout_ms has passed. | |
| return now > | |
| LastReceived() + | |
| TimeDelta::Millis(field_trials_->dead_connection_timeout_ms); | |
| } | |
| if (active()) { | |
| // If it has never received anything, keep it alive as long as it is | |
| // actively pinging and not pruned. Otherwise, the connection might be | |
| // deleted before it has a chance to ping. This is the normal case for a | |
| // new connection that is pinging but hasn't received anything yet. | |
| return false; | |
| } | |
| // If it has never received anything and is not actively pinging (pruned), we | |
| // keep it around for at least MIN_CONNECTION_LIFETIME to prevent connections | |
| // from being pruned too quickly during a network change event when two | |
| // networks would be up simultaneously but only for a brief period. | |
| return now > (time_created_ + kMinConnectionLifetime); | |
| } | |
| TimeDelta Connection::Rtt() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return rtt_; | |
| } | |
| bool Connection::stable(Timestamp now) const { | |
| // A connection is stable if it's RTT has converged and it isn't missing any | |
| // responses. We should send pings at a higher rate until the RTT converges | |
| // and whenever a ping response is missing (so that we can detect | |
| // unwritability faster) | |
| return rtt_converged() && !missing_responses(now); | |
| } | |
| std::string Connection::ToDebugId() const { | |
| return ToHex(reinterpret_cast<uintptr_t>(this)); | |
| } | |
| uint32_t Connection::ComputeNetworkCost() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| // TODO(honghaiz): Will add rtt as part of the network cost. | |
| RTC_DCHECK(port_) << ToDebugId() << ": port_ null in ComputeNetworkCost()"; | |
| return port()->network_cost() + remote_candidate_.network_cost(); | |
| } | |
| std::string Connection::ToString() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| RTC_DCHECK(port_) << ToDebugId() << ": port_ null in ToString()"; | |
| constexpr absl::string_view CONNECT_STATE_ABBREV[2] = { | |
| "-", // not connected (false) | |
| "C", // connected (true) | |
| }; | |
| constexpr absl::string_view RECEIVE_STATE_ABBREV[2] = { | |
| "-", // not receiving (false) | |
| "R", // receiving (true) | |
| }; | |
| constexpr absl::string_view WRITE_STATE_ABBREV[4] = { | |
| "W", // STATE_WRITABLE | |
| "w", // STATE_WRITE_UNRELIABLE | |
| "-", // STATE_WRITE_INIT | |
| "x", // STATE_WRITE_TIMEOUT | |
| }; | |
| constexpr absl::string_view ICESTATE[4] = { | |
| "W", // STATE_WAITING | |
| "I", // STATE_INPROGRESS | |
| "S", // STATE_SUCCEEDED | |
| "F" // STATE_FAILED | |
| }; | |
| constexpr absl::string_view SELECTED_STATE_ABBREV[2] = { | |
| "-", // candidate pair not selected (false) | |
| "S", // selected (true) | |
| }; | |
| StringBuilder ss; | |
| ss << "Conn[" << ToDebugId(); | |
| if (!port_) { | |
| // No content or network names for pending delete. Temporarily substitute | |
| // the names with a hash (rhyming with trash). | |
| ss << ":#:#:"; | |
| } else { | |
| ss << ":" << port_->content_name() << ":" << port_->Network()->ToString() | |
| << ":"; | |
| } | |
| const Candidate& local = local_candidate(); | |
| const Candidate& remote = remote_candidate(); | |
| ss << local.id() << ":" << local.component() << ":" << local.generation() | |
| << ":" << local.type_name() << ":" << local.protocol() << ":" | |
| << local.address().ToSensitiveString() << "->" << remote.id() << ":" | |
| << remote.component() << ":" << remote.priority() << ":" | |
| << remote.type_name() << ":" << remote.protocol() << ":" | |
| << remote.address().ToSensitiveString() << "|"; | |
| ss << CONNECT_STATE_ABBREV[connected_] << RECEIVE_STATE_ABBREV[receiving_] | |
| << WRITE_STATE_ABBREV[write_state_] << ICESTATE[static_cast<int>(state_)] | |
| << "|" << SELECTED_STATE_ABBREV[selected_] << "|" << remote_nomination_ | |
| << "|" << nomination_ << "|"; | |
| if (port_) | |
| ss << priority() << "|"; | |
| if (rtt_ < kDefaultRtt) { | |
| ss << rtt_.ms() << "]"; | |
| } else { | |
| ss << "-]"; | |
| } | |
| return ss.Release(); | |
| } | |
| std::string Connection::ToSensitiveString() const { | |
| return ToString(); | |
| } | |
| const IceCandidatePairDescription& Connection::ToLogDescription() { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| if (log_description_.has_value()) { | |
| return log_description_.value(); | |
| } | |
| const Candidate& local = local_candidate(); | |
| const Candidate& remote = remote_candidate(); | |
| const Network* network = port()->Network(); | |
| log_description_ = IceCandidatePairDescription( | |
| GetRtcEventLogCandidateType(local), GetRtcEventLogCandidateType(remote)); | |
| log_description_->local_relay_protocol = | |
| GetProtocolByString(local.relay_protocol()); | |
| log_description_->local_network_type = ConvertNetworkType(network->type()); | |
| log_description_->local_address_family = | |
| GetAddressFamilyByInt(local.address().family()); | |
| log_description_->remote_address_family = | |
| GetAddressFamilyByInt(remote.address().family()); | |
| log_description_->candidate_pair_protocol = | |
| GetProtocolByString(local.protocol()); | |
| return log_description_.value(); | |
| } | |
| void Connection::set_ice_event_log(IceEventLog* ice_event_log) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| ice_event_log_ = ice_event_log; | |
| } | |
| void Connection::LogCandidatePairConfig(IceCandidatePairConfigType type) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| if (ice_event_log_ == nullptr) { | |
| return; | |
| } | |
| ice_event_log_->LogCandidatePairConfig(type, id(), ToLogDescription()); | |
| } | |
| void Connection::LogCandidatePairEvent(IceCandidatePairEventType type, | |
| uint32_t transaction_id) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| if (ice_event_log_ == nullptr) { | |
| return; | |
| } | |
| ice_event_log_->LogCandidatePairEvent(type, id(), transaction_id); | |
| } | |
| void Connection::OnConnectionRequestResponse(StunRequest* request, | |
| StunMessage* response) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| // Log at LS_INFO if we receive a ping response on an unwritable | |
| // connection. | |
| LoggingSeverity sev = !writable() ? LS_INFO : LS_VERBOSE; | |
| TimeDelta rtt = request->Elapsed(); | |
| if (RTC_LOG_CHECK_LEVEL_V(sev)) { | |
| std::string pings; | |
| PrintPingsSinceLastResponse(&pings, 5); | |
| RTC_LOG_V(sev) << ToString() << ": Received " | |
| << StunMethodToString(response->type()) | |
| << ", id=" << hex_encode(request->id()) | |
| << ", code=0" // Makes logging easier to parse. | |
| ", rtt=" | |
| << rtt << ", pings_since_last_response=" << pings; | |
| } | |
| std::optional<uint32_t> nomination; | |
| const std::string request_id = request->id(); | |
| auto iter = absl::c_find_if( | |
| pings_since_last_response_, | |
| [&request_id](const SentPing& ping) { return ping.id == request_id; }); | |
| if (iter != pings_since_last_response_.end()) { | |
| nomination.emplace(iter->nomination); | |
| } | |
| ReceivedPingResponse(rtt, request_id, nomination); | |
| stats_.recv_ping_responses++; | |
| LogCandidatePairEvent(IceCandidatePairEventType::kCheckResponseReceived, | |
| response->reduced_transaction_id()); | |
| if (request->msg()->type() == STUN_BINDING_REQUEST) { | |
| if (!remote_support_goog_ping_.has_value()) { | |
| auto goog_misc = response->GetUInt16List(STUN_ATTR_GOOG_MISC_INFO); | |
| if (goog_misc != nullptr && | |
| goog_misc->Size() >= kSupportGoogPingVersionResponseIndex) { | |
| // The remote peer has indicated that it {does/does not} supports | |
| // GOOG_PING. | |
| remote_support_goog_ping_ = | |
| goog_misc->GetType(kSupportGoogPingVersionResponseIndex) >= | |
| kGoogPingVersion; | |
| } else { | |
| remote_support_goog_ping_ = false; | |
| } | |
| } | |
| MaybeUpdateLocalCandidate(request, response); | |
| if (field_trials_->enable_goog_ping && remote_support_goog_ping_) { | |
| cached_stun_binding_ = request->msg()->Clone(); | |
| } | |
| } | |
| // Did we send a delta ? | |
| const bool sent_goog_delta = | |
| request->msg()->GetByteString(STUN_ATTR_GOOG_DELTA) != nullptr; | |
| // Did we get a GOOG_DELTA_ACK ? | |
| const StunUInt64Attribute* delta_ack = | |
| response->GetUInt64(STUN_ATTR_GOOG_DELTA_ACK); | |
| if (goog_delta_ack_consumer_) { | |
| if (sent_goog_delta && delta_ack) { | |
| RTC_LOG(LS_VERBOSE) << "Got GOOG_DELTA_ACK len: " << delta_ack->length(); | |
| (*goog_delta_ack_consumer_)(delta_ack); | |
| } else if (sent_goog_delta) { | |
| // We sent DELTA but did not get a DELTA_ACK. | |
| // This means that remote does not support GOOG_DELTA | |
| RTC_LOG(LS_INFO) << "NO DELTA ACK => disable GOOG_DELTA"; | |
| (*goog_delta_ack_consumer_)( | |
| RTCError(RTCErrorType::UNSUPPORTED_OPERATION)); | |
| } else if (delta_ack) { | |
| // We did NOT send DELTA but got a DELTA_ACK. | |
| // That is internal error. | |
| RTC_LOG(LS_ERROR) << "DELTA ACK w/o DELTA => disable GOOG_DELTA"; | |
| (*goog_delta_ack_consumer_)(RTCError(RTCErrorType::INTERNAL_ERROR)); | |
| } | |
| } else if (delta_ack) { | |
| RTC_LOG(LS_ERROR) << "Discard GOOG_DELTA_ACK, no consumer"; | |
| } | |
| const bool sent_dtls_piggyback = | |
| request->msg()->GetByteString(STUN_ATTR_META_DTLS_IN_STUN) != nullptr; | |
| const bool sent_dtls_piggyback_ack = | |
| request->msg()->GetByteString(STUN_ATTR_META_DTLS_IN_STUN_ACK) != nullptr; | |
| if (sent_dtls_piggyback || sent_dtls_piggyback_ack) { | |
| MaybeHandleDtlsPiggybackingAttributes(response, request); | |
| } | |
| } | |
| void Connection::OnConnectionRequestErrorResponse(ConnectionRequest* request, | |
| StunMessage* response) { | |
| RTC_DCHECK(port_) << ToDebugId() | |
| << ": port_ null in OnConnectionRequestErrorResponse"; | |
| if (!port_) | |
| return; | |
| int error_code = response->GetErrorCodeValue(); | |
| RTC_LOG(LS_WARNING) << ToString() << ": Received " | |
| << StunMethodToString(response->type()) | |
| << " id=" << hex_encode(request->id()) | |
| << " code=" << error_code | |
| << " rtt=" << request->Elapsed(); | |
| cached_stun_binding_.reset(); | |
| if (error_code == STUN_ERROR_UNKNOWN_ATTRIBUTE || | |
| error_code == STUN_ERROR_SERVER_ERROR || | |
| error_code == STUN_ERROR_UNAUTHORIZED) { | |
| // Recoverable error, retry | |
| } else if (error_code == STUN_ERROR_ROLE_CONFLICT) { | |
| port_->NotifyRoleConflict(); | |
| } else if (request->msg()->type() == GOOG_PING_REQUEST) { | |
| // Race, retry. | |
| } else { | |
| // This is not a valid connection. | |
| RTC_LOG(LS_ERROR) << ToString() | |
| << ": Received STUN error response, code=" << error_code | |
| << "; killing connection"; | |
| set_state(IceCandidatePairState::FAILED); | |
| port_->DestroyConnectionAsync(this); | |
| } | |
| } | |
| void Connection::OnConnectionRequestTimeout(ConnectionRequest* request) { | |
| // Log at LS_INFO if we miss a ping on a writable connection. | |
| LoggingSeverity sev = writable() ? LS_INFO : LS_VERBOSE; | |
| RTC_LOG_V(sev) << ToString() << ": Timing-out STUN ping " | |
| << hex_encode(request->id()) << " after " << request->Elapsed() | |
| << " ms"; | |
| } | |
| void Connection::OnConnectionRequestSent(ConnectionRequest* request) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| // Log at LS_INFO if we send a ping on an unwritable connection. | |
| LoggingSeverity sev = !writable() ? LS_INFO : LS_VERBOSE; | |
| RTC_LOG_V(sev) << ToString() << ": Sent " | |
| << StunMethodToString(request->msg()->type()) | |
| << ", id=" << hex_encode(request->id()) | |
| << ", use_candidate=" << use_candidate_attr() | |
| << ", nomination=" << nomination_; | |
| stats_.sent_ping_requests_total++; | |
| LogCandidatePairEvent(IceCandidatePairEventType::kCheckSent, | |
| request->reduced_transaction_id()); | |
| if (stats_.recv_ping_responses == 0) { | |
| stats_.sent_ping_requests_before_first_response++; | |
| } | |
| } | |
| IceCandidatePairState Connection::state() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return state_; | |
| } | |
| int Connection::num_pings_sent() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return num_pings_sent_; | |
| } | |
| void Connection::MaybeSetRemoteIceParametersAndGeneration( | |
| const IceParameters& ice_params, | |
| int generation) { | |
| if (remote_candidate_.username() == ice_params.ufrag && | |
| remote_candidate_.password().empty()) { | |
| remote_candidate_.set_password(ice_params.pwd); | |
| } | |
| // TODO(deadbeef): A value of '0' for the generation is used for both | |
| // generation 0 and "generation unknown". It should be changed to an | |
| // std::optional to fix this. | |
| if (remote_candidate_.username() == ice_params.ufrag && | |
| remote_candidate_.password() == ice_params.pwd && | |
| remote_candidate_.generation() == 0) { | |
| remote_candidate_.set_generation(generation); | |
| } | |
| } | |
| void Connection::MaybeUpdatePeerReflexiveCandidate( | |
| const Candidate& new_candidate) { | |
| if (remote_candidate_.is_prflx() && !new_candidate.is_prflx() && | |
| remote_candidate_.protocol() == new_candidate.protocol() && | |
| remote_candidate_.address() == new_candidate.address() && | |
| remote_candidate_.username() == new_candidate.username() && | |
| remote_candidate_.password() == new_candidate.password() && | |
| remote_candidate_.generation() == new_candidate.generation()) { | |
| remote_candidate_ = new_candidate; | |
| } | |
| } | |
| Timestamp Connection::LastReceived() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return std::max( | |
| {last_data_received_, last_ping_received_, last_ping_response_received_}); | |
| } | |
| Timestamp Connection::ReceivingUnchangedSince() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return receiving_unchanged_since_; | |
| } | |
| uint32_t Connection::prflx_priority() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| // PRIORITY Attribute. | |
| // Changing the type preference to Peer Reflexive and local preference | |
| // and component id information is unchanged from the original priority. | |
| // priority = (2^24)*(type preference) + | |
| // (2^8)*(local preference) + | |
| // (2^0)*(256 - component ID) | |
| IcePriorityValue type_preference = | |
| (local_candidate_.protocol() == TCP_PROTOCOL_NAME) | |
| ? ICE_TYPE_PREFERENCE_PRFLX_TCP | |
| : ICE_TYPE_PREFERENCE_PRFLX; | |
| return type_preference << 24 | (local_candidate_.priority() & 0x00FFFFFF); | |
| } | |
| ConnectionInfo Connection::stats() { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| Timestamp now = env_.clock().CurrentTime(); | |
| stats_.recv_bytes_second = | |
| recv_rate_tracker_.Rate(now).value_or(DataRate::Zero()).bps<size_t>(); | |
| stats_.sent_bytes_second = | |
| send_rate_tracker_.Rate(now).value_or(DataRate::Zero()).bps<size_t>(); | |
| stats_.receiving = receiving_; | |
| stats_.writable = write_state_ == STATE_WRITABLE; | |
| stats_.timeout = write_state_ == STATE_WRITE_TIMEOUT; | |
| stats_.rtt = rtt_.ms(); | |
| stats_.key = this; | |
| stats_.state = state_; | |
| if (port_) { | |
| stats_.priority = priority(); | |
| stats_.local_candidate = local_candidate(); | |
| } | |
| stats_.nominated = nominated(); | |
| stats_.total_round_trip_time_ms = total_round_trip_time_.ms(); | |
| stats_.current_round_trip_time_ms = | |
| current_round_trip_time_.has_value() | |
| ? std::optional(current_round_trip_time_->ms<uint32_t>()) | |
| : std::nullopt; | |
| stats_.remote_candidate = remote_candidate(); | |
| if (last_data_received_ > Timestamp::Zero()) { | |
| stats_.last_data_received = | |
| last_data_received_ + delta_internal_unix_epoch_; | |
| } | |
| if (last_send_data_ > Timestamp::Zero()) { | |
| stats_.last_data_sent = last_send_data_ + delta_internal_unix_epoch_; | |
| } | |
| return stats_; | |
| } | |
| void Connection::MaybeUpdateLocalCandidate(StunRequest* request, | |
| StunMessage* response) { | |
| RTC_DCHECK(port_) << ToDebugId() | |
| << ": port_ null in MaybeUpdateLocalCandidate"; | |
| if (!port_) | |
| return; | |
| // RFC 5245 | |
| // The agent checks the mapped address from the STUN response. If the | |
| // transport address does not match any of the local candidates that the | |
| // agent knows about, the mapped address represents a new candidate -- a | |
| // peer reflexive candidate. | |
| const StunAddressAttribute* addr = | |
| response->GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS); | |
| if (!addr) { | |
| RTC_LOG(LS_WARNING) | |
| << "Connection::OnConnectionRequestResponse - " | |
| "No MAPPED-ADDRESS or XOR-MAPPED-ADDRESS found in the " | |
| "stun response message"; | |
| return; | |
| } | |
| for (const Candidate& candidate : port_->Candidates()) { | |
| if (candidate.address() == addr->GetAddress()) { | |
| if (local_candidate_ != candidate) { | |
| RTC_LOG(LS_INFO) << ToString() | |
| << ": Updating local candidate type to srflx."; | |
| local_candidate_ = candidate; | |
| // NotifyStateChange to force a re-sort in P2PTransportChannel as this | |
| // Connection's local candidate has changed. | |
| NotifyStateChange(this); | |
| } | |
| return; | |
| } | |
| } | |
| // RFC 5245 | |
| // Its priority is set equal to the value of the PRIORITY attribute | |
| // in the Binding request. | |
| const StunUInt32Attribute* priority_attr = | |
| request->msg()->GetUInt32(STUN_ATTR_PRIORITY); | |
| if (!priority_attr) { | |
| RTC_LOG(LS_WARNING) << "Connection::OnConnectionRequestResponse - " | |
| "No STUN_ATTR_PRIORITY found in the " | |
| "stun response message"; | |
| return; | |
| } | |
| const uint32_t priority = priority_attr->value(); | |
| // Create a peer-reflexive candidate based on the local candidate. | |
| local_candidate_.generate_id(); | |
| local_candidate_.set_type(IceCandidateType::kPrflx); | |
| // Set the related address and foundation attributes before changing the | |
| // address. | |
| local_candidate_.set_related_address(local_candidate_.address()); | |
| local_candidate_.ComputeFoundation(local_candidate_.address(), | |
| port_->IceTiebreaker()); | |
| local_candidate_.set_priority(priority); | |
| local_candidate_.set_address(addr->GetAddress()); | |
| // Change the local candidate of this Connection to the new prflx candidate. | |
| RTC_LOG(LS_INFO) << ToString() << ": Updating local candidate type to prflx."; | |
| port_->AddPrflxCandidate(local_candidate_); | |
| // NotifyStateChange to force a re-sort in P2PTransportChannel as this | |
| // Connection's local candidate has changed. | |
| NotifyStateChange(this); | |
| } | |
| bool Connection::rtt_converged() const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return rtt_samples_ > (kRttRatio + 1); | |
| } | |
| bool Connection::missing_responses(Timestamp now) const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| if (pings_since_last_response_.empty()) { | |
| return false; | |
| } | |
| TimeDelta waiting = now - pings_since_last_response_[0].sent_time; | |
| return waiting > 2 * Rtt(); | |
| } | |
| bool Connection::TooManyOutstandingPings( | |
| const std::optional<int>& max_outstanding_pings) const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| if (!max_outstanding_pings.has_value()) { | |
| return false; | |
| } | |
| if (static_cast<int>(pings_since_last_response_.size()) < | |
| *max_outstanding_pings) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| void Connection::SetLocalCandidateNetworkCost(uint16_t cost) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| if (cost == local_candidate_.network_cost()) | |
| return; | |
| local_candidate_.set_network_cost(cost); | |
| // Network cost change will affect the connection selection criteria. | |
| // Signal the connection state change to force a re-sort in | |
| // P2PTransportChannel. | |
| NotifyStateChange(this); | |
| } | |
| bool Connection::ShouldSendGoogPing(const StunMessage* message) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| if (remote_support_goog_ping_ == true && cached_stun_binding_ && | |
| cached_stun_binding_->EqualAttributes(message, [](int type) { | |
| // Ignore these attributes. | |
| // NOTE: Consider what to do if adding more content to | |
| // STUN_ATTR_GOOG_MISC_INFO | |
| return type != STUN_ATTR_FINGERPRINT && | |
| type != STUN_ATTR_MESSAGE_INTEGRITY && | |
| type != STUN_ATTR_RETRANSMIT_COUNT && | |
| type != STUN_ATTR_GOOG_MISC_INFO; | |
| })) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| void Connection::ForgetLearnedState() { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| RTC_LOG(LS_INFO) << ToString() << ": Connection forget learned state"; | |
| requests_.Clear(); | |
| receiving_ = false; | |
| write_state_ = STATE_WRITE_INIT; | |
| rtt_estimate_.Reset(); | |
| pings_since_last_response_.clear(); | |
| } | |
| ProxyConnection::ProxyConnection(const Environment& env, | |
| WeakPtr<PortInterface> port, | |
| size_t index, | |
| const Candidate& remote_candidate) | |
| : Connection(env, std::move(port), index, remote_candidate) {} | |
| int ProxyConnection::Send(const void* data, | |
| size_t size, | |
| const AsyncSocketPacketOptions& options) { | |
| RTC_DCHECK(port() != nullptr) << ToDebugId() << ": port_ null in Send()"; | |
| if (port() == nullptr) | |
| return SOCKET_ERROR; | |
| mutable_stats().sent_total_packets++; | |
| int sent = | |
| port()->SendTo(data, size, remote_candidate().address(), options, true); | |
| Timestamp now = env().clock().CurrentTime(); | |
| if (sent <= 0) { | |
| RTC_DCHECK(sent < 0); | |
| error_ = port()->GetError(); | |
| mutable_stats().sent_discarded_packets++; | |
| mutable_stats().sent_discarded_bytes += size; | |
| } else { | |
| AddSentBytesToStats(sent, now); | |
| } | |
| set_last_send_data(now); | |
| return sent; | |
| } | |
| int ProxyConnection::GetError() { | |
| return error_; | |
| } | |
| // This method is used by the FakeIceLiteAgent | |
| // to pretend that a Connection is writable so | |
| // P2PTransportChannel/FakeIceLiteAgent can be used | |
| // to simulate a ICE Lite agent. | |
| bool Connection::set_writable_for_fake_ice_lite() const { | |
| if (write_state() != STATE_WRITABLE) { | |
| Timestamp now = env_.clock().CurrentTime(); | |
| auto con = const_cast<Connection*>(this); | |
| con->UpdateReceiving(now); | |
| con->set_write_state(STATE_WRITABLE); | |
| con->set_state(IceCandidatePairState::SUCCEEDED); | |
| return true; | |
| } | |
| return false; | |
| } | |
| } // namespace webrtc |