summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/p2p/base/connection.cc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libwebrtc/p2p/base/connection.cc')
-rw-r--r--third_party/libwebrtc/p2p/base/connection.cc1720
1 files changed, 1720 insertions, 0 deletions
diff --git a/third_party/libwebrtc/p2p/base/connection.cc b/third_party/libwebrtc/p2p/base/connection.cc
new file mode 100644
index 0000000000..c5e6993c87
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/connection.cc
@@ -0,0 +1,1720 @@
+/*
+ * 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 <math.h>
+
+#include <algorithm>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "absl/algorithm/container.h"
+#include "absl/strings/escaping.h"
+#include "absl/strings/match.h"
+#include "absl/strings/string_view.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"
+
+namespace cricket {
+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,
+ int rtt_estimate,
+ int64_t 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.
+ int64_t 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,
+ int64_t maximum_time,
+ int64_t now) {
+ if (pings_since_last_response.size() == 0)
+ 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.
+webrtc::IceCandidateType GetCandidateTypeByString(absl::string_view type) {
+ if (type == LOCAL_PORT_TYPE) {
+ return webrtc::IceCandidateType::kLocal;
+ } else if (type == STUN_PORT_TYPE) {
+ return webrtc::IceCandidateType::kStun;
+ } else if (type == PRFLX_PORT_TYPE) {
+ return webrtc::IceCandidateType::kPrflx;
+ } else if (type == RELAY_PORT_TYPE) {
+ return webrtc::IceCandidateType::kRelay;
+ }
+ return webrtc::IceCandidateType::kUnknown;
+}
+
+webrtc::IceCandidatePairProtocol GetProtocolByString(
+ absl::string_view protocol) {
+ if (protocol == UDP_PROTOCOL_NAME) {
+ return webrtc::IceCandidatePairProtocol::kUdp;
+ } else if (protocol == TCP_PROTOCOL_NAME) {
+ return webrtc::IceCandidatePairProtocol::kTcp;
+ } else if (protocol == SSLTCP_PROTOCOL_NAME) {
+ return webrtc::IceCandidatePairProtocol::kSsltcp;
+ } else if (protocol == TLS_PROTOCOL_NAME) {
+ return webrtc::IceCandidatePairProtocol::kTls;
+ }
+ return webrtc::IceCandidatePairProtocol::kUnknown;
+}
+
+webrtc::IceCandidatePairAddressFamily GetAddressFamilyByInt(
+ int address_family) {
+ if (address_family == AF_INET) {
+ return webrtc::IceCandidatePairAddressFamily::kIpv4;
+ } else if (address_family == AF_INET6) {
+ return webrtc::IceCandidatePairAddressFamily::kIpv6;
+ }
+ return webrtc::IceCandidatePairAddressFamily::kUnknown;
+}
+
+webrtc::IceCandidateNetworkType ConvertNetworkType(rtc::AdapterType type) {
+ switch (type) {
+ case rtc::ADAPTER_TYPE_ETHERNET:
+ return webrtc::IceCandidateNetworkType::kEthernet;
+ case rtc::ADAPTER_TYPE_LOOPBACK:
+ return webrtc::IceCandidateNetworkType::kLoopback;
+ case rtc::ADAPTER_TYPE_WIFI:
+ return webrtc::IceCandidateNetworkType::kWifi;
+ case rtc::ADAPTER_TYPE_VPN:
+ return webrtc::IceCandidateNetworkType::kVpn;
+ case rtc::ADAPTER_TYPE_CELLULAR:
+ case rtc::ADAPTER_TYPE_CELLULAR_2G:
+ case rtc::ADAPTER_TYPE_CELLULAR_3G:
+ case rtc::ADAPTER_TYPE_CELLULAR_4G:
+ case rtc::ADAPTER_TYPE_CELLULAR_5G:
+ return webrtc::IceCandidateNetworkType::kCellular;
+ default:
+ return webrtc::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.
+const int DEFAULT_RTT = 3000; // 3 seconds
+
+// We will restrict RTT estimates (when used for determining state) to be
+// within a reasonable range.
+const int MINIMUM_RTT = 100; // 0.1 seconds
+const int MAXIMUM_RTT = 60000; // 60 seconds
+
+const int DEFAULT_RTT_ESTIMATE_HALF_TIME_MS = 500;
+
+// Computes our estimate of the RTT given the current estimate.
+inline int ConservativeRTTEstimate(int rtt) {
+ return rtc::SafeClamp(2 * rtt, MINIMUM_RTT, MAXIMUM_RTT);
+}
+
+// Weighting of the old rtt value to new data.
+const int RTT_RATIO = 3; // 3 : 1
+
+constexpr int64_t kMinExtraPingDelayMs = 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(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(
+ StunRequestManager& manager,
+ Connection* connection,
+ std::unique_ptr<IceMessage> message)
+ : StunRequest(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 CONNECTION_RESPONSE_TIMEOUT;
+}
+
+Connection::Connection(rtc::WeakPtr<Port> port,
+ size_t index,
+ const Candidate& remote_candidate)
+ : network_thread_(port->thread()),
+ id_(rtc::CreateRandomId()),
+ port_(std::move(port)),
+ local_candidate_(port_->Candidates()[index]),
+ remote_candidate_(remote_candidate),
+ recv_rate_tracker_(100, 10u),
+ send_rate_tracker_(100, 10u),
+ 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_(DEFAULT_RTT),
+ last_ping_sent_(0),
+ last_ping_received_(0),
+ last_data_received_(0),
+ last_ping_response_received_(0),
+ state_(IceCandidatePairState::WAITING),
+ time_created_ms_(rtc::TimeMillis()),
+ delta_internal_unix_epoch_ms_(rtc::TimeUTCMillis() - rtc::TimeMillis()),
+ field_trials_(&kDefaultFieldTrials),
+ rtt_estimate_(DEFAULT_RTT_ESTIMATE_HALF_TIME_MS) {
+ 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_);
+}
+
+webrtc::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 rtc::Network* Connection::network() const {
+ return port()->Network();
+}
+
+int Connection::generation() const {
+ return port()->generation();
+}
+
+uint64_t Connection::priority() const {
+ 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;
+ SignalStateChange(this);
+ }
+}
+
+void Connection::UpdateReceiving(int64_t now) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ bool receiving;
+ if (last_ping_sent() < last_ping_response_received()) {
+ // 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 =
+ last_received() > 0 && now <= last_received() + receiving_timeout();
+ }
+ if (receiving_ == receiving) {
+ return;
+ }
+ RTC_LOG(LS_VERBOSE) << ToString() << ": set_receiving to " << receiving;
+ receiving_ = receiving;
+ receiving_unchanged_since_ = now;
+ SignalStateChange(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;
+ SignalStateChange(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_;
+}
+
+int Connection::unwritable_timeout() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return unwritable_timeout_.value_or(CONNECTION_WRITE_CONNECT_TIMEOUT);
+}
+
+void Connection::set_unwritable_timeout(const absl::optional<int>& value_ms) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ unwritable_timeout_ = value_ms;
+}
+
+int Connection::unwritable_min_checks() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return unwritable_min_checks_.value_or(CONNECTION_WRITE_CONNECT_FAILURES);
+}
+
+void Connection::set_unwritable_min_checks(const absl::optional<int>& value) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ unwritable_min_checks_ = value;
+}
+
+int Connection::inactive_timeout() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return inactive_timeout_.value_or(CONNECTION_WRITE_TIMEOUT);
+}
+
+void Connection::set_inactive_timeout(const absl::optional<int>& value) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ inactive_timeout_ = value;
+}
+
+int Connection::receiving_timeout() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return receiving_timeout_.value_or(WEAK_CONNECTION_RECEIVE_TIMEOUT);
+}
+
+void Connection::set_receiving_timeout(
+ absl::optional<int> receiving_timeout_ms) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ receiving_timeout_ = receiving_timeout_ms;
+}
+
+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_);
+ rtc::PacketOptions options(port_->StunDscpValue());
+ options.info_signaled_after_sent.packet_type =
+ rtc::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=" << rtc::hex_encode(req->id());
+ }
+}
+
+void Connection::OnReadPacket(const char* data,
+ size_t size,
+ int64_t packet_time_us) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ std::unique_ptr<IceMessage> msg;
+ std::string remote_ufrag;
+ const rtc::SocketAddress& addr(remote_candidate_.address());
+ if (!port_->GetStunMessage(data, 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_ = rtc::TimeMillis();
+ UpdateReceiving(last_data_received_);
+ recv_rate_tracker_.AddSamples(size);
+ stats_.packets_received++;
+ SignalReadPacket(this, data, size, packet_time_us);
+
+ // 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=" << rtc::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=" << rtc::hex_encode(msg->transaction_id())
+ << " with invalid message integrity: "
+ << static_cast<int>(msg->integrity());
+ return;
+ }
+ } else {
+ RTC_DCHECK(IsStunIndicationType(msg->type()));
+ // No message integrity.
+ }
+
+ rtc::LoggingSeverity sev = (!writable() ? rtc::LS_INFO : rtc::LS_VERBOSE);
+ switch (msg->type()) {
+ case STUN_BINDING_REQUEST:
+ RTC_LOG_V(sev) << ToString() << ": Received "
+ << StunMethodToString(msg->type())
+ << ", id=" << rtc::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::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_ == 0) {
+ if (local_candidate().type() == RELAY_PORT_TYPE ||
+ local_candidate().type() == PRFLX_PORT_TYPE ||
+ remote_candidate().type() == RELAY_PORT_TYPE ||
+ remote_candidate().type() == PRFLX_PORT_TYPE) {
+ const int64_t now = rtc::TimeMillis();
+ if (last_ping_sent_ + kMinExtraPingDelayMs <= 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 rtc::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(webrtc::IceCandidatePairEventType::kCheckReceived,
+ msg->reduced_transaction_id());
+
+ // This is a validated stun request from remote peer.
+ if (msg->type() == STUN_BINDING_REQUEST) {
+ 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);
+ SignalNominated(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.
+ SignalStateChange(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 != NULL);
+ if (username_attr == NULL) {
+ // 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() > CONNECTION_WRITE_CONNECT_FAILURES) {
+ 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));
+ }
+ }
+
+ 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 rtc::SocketAddress& addr = remote_candidate_.address();
+
+ // Send the response.
+ rtc::ByteBufferWriter buf;
+ response.Write(&buf);
+ rtc::PacketOptions options(port_->StunDscpValue());
+ options.info_signaled_after_sent.packet_type =
+ rtc::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=" << rtc::hex_encode(response.transaction_id());
+ } else {
+ // Log at LS_INFO if we send a stun ping response on an unwritable
+ // connection.
+ rtc::LoggingSeverity sev = (!writable()) ? rtc::LS_INFO : rtc::LS_VERBOSE;
+ RTC_LOG_V(sev) << ToString() << ": Sent "
+ << StunMethodToString(response.type())
+ << ", to=" << addr.ToSensitiveString()
+ << ", id=" << rtc::hex_encode(response.transaction_id());
+
+ stats_.sent_ping_responses++;
+ LogCandidatePairEvent(webrtc::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_);
+ SignalReadyToSend(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_) << "Calling Destroy() twice?";
+ if (port_)
+ port_->DestroyConnection(this);
+}
+
+bool Connection::Shutdown() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ 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.
+ auto destroyed_signals = SignalDestroyed;
+ SignalDestroyed.disconnect_all();
+ destroyed_signals(this);
+
+ LogCandidatePairConfig(webrtc::IceCandidatePairConfigType::kDestroyed);
+
+ // Reset the `port_` after logging and firing the destroyed signal since
+ // information required for logging needs access to `port_`.
+ port_.reset();
+
+ 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.
+ if (!port_)
+ return;
+
+ set_state(IceCandidatePairState::FAILED);
+ Prune();
+}
+
+void Connection::PrintPingsSinceLastResponse(std::string* s, size_t max) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ rtc::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 << rtc::hex_encode(ping.id) << " ";
+ }
+ oss << "... " << (pings_since_last_response_.size() - max) << " more";
+ } else {
+ for (const SentPing& ping : pings_since_last_response_) {
+ oss << rtc::hex_encode(ping.id) << " ";
+ }
+ }
+ *s = oss.str();
+}
+
+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(int64_t now) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (!port_)
+ return;
+
+ int rtt = ConservativeRTTEstimate(rtt_);
+
+ 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_, unwritable_timeout(),
+ 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_, inactive_timeout(),
+ 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);
+}
+
+int64_t Connection::last_ping_sent() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return last_ping_sent_;
+}
+
+void Connection::Ping(int64_t now) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ 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_;
+ }
+
+ auto req =
+ std::make_unique<ConnectionRequest>(requests_, this, BuildPingRequest());
+
+ if (ShouldSendGoogPing(req->msg())) {
+ auto message = std::make_unique<IceMessage>(GOOG_PING_REQUEST, req->id());
+ message->AddMessageIntegrity32(remote_candidate_.password());
+ req.reset(new ConnectionRequest(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="
+ << rtc::hex_encode(req->id())
+ << ", nomination=" << nomination_;
+ requests_.Send(req.release());
+ state_ = IceCandidatePairState::IN_PROGRESS;
+ num_pings_sent_++;
+}
+
+std::unique_ptr<IceMessage> Connection::BuildPingRequest() {
+ 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));
+ }
+ message->AddMessageIntegrity(remote_candidate_.password());
+ message->AddFingerprint();
+
+ return message;
+}
+
+int64_t Connection::last_ping_response_received() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return last_ping_response_received_;
+}
+
+const absl::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.
+int64_t Connection::last_ping_received() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return last_ping_received_;
+}
+
+void Connection::ReceivedPing(const absl::optional<std::string>& request_id) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ last_ping_received_ = rtc::TimeMillis();
+ 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()) {
+ rtc::LoggingSeverity sev = !writable() ? rtc::LS_INFO : rtc::LS_VERBOSE;
+ RTC_LOG_V(sev) << ToString()
+ << ": Received piggyback STUN ping response, id="
+ << rtc::hex_encode(request_id);
+ const int64_t rtt = rtc::TimeMillis() - iter->sent_time;
+ ReceivedPingResponse(rtt, request_id, iter->nomination);
+ }
+ }
+}
+
+int64_t Connection::last_send_data() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return last_send_data_;
+}
+
+int64_t Connection::last_data_received() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return last_data_received_;
+}
+
+void Connection::ReceivedPingResponse(
+ int rtt,
+ absl::string_view request_id,
+ const absl::optional<uint32_t>& nomination) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ RTC_DCHECK_GE(rtt, 0);
+ // 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();
+ }
+
+ int64_t now = rtc::TimeMillis();
+ total_round_trip_time_ms_ += rtt;
+ current_round_trip_time_ms_ = static_cast<uint32_t>(rtt);
+ rtt_estimate_.AddSample(now, rtt);
+
+ pings_since_last_response_.clear();
+ last_ping_response_received_ = now;
+ UpdateReceiving(last_ping_response_received_);
+ set_write_state(STATE_WRITABLE);
+ set_state(IceCandidatePairState::SUCCEEDED);
+ if (rtt_samples_ > 0) {
+ rtt_ = rtc::GetNextMovingAverage(rtt_, rtt, RTT_RATIO);
+ } 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(int64_t now) const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (last_received() > 0) {
+ // 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 <= (last_received() + DEAD_CONNECTION_RECEIVE_TIMEOUT)) {
+ // 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 +
+ DEAD_CONNECTION_RECEIVE_TIMEOUT);
+ }
+
+ // No outstanding pings: let it live until
+ // field_trials_->dead_connection_timeout_ms has passed.
+ return now > (last_received() + 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_ms_ + MIN_CONNECTION_LIFETIME);
+}
+
+int Connection::rtt() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return rtt_;
+}
+
+bool Connection::stable(int64_t 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 rtc::ToHex(reinterpret_cast<uintptr_t>(this));
+}
+
+uint32_t Connection::ComputeNetworkCost() const {
+ // TODO(honghaiz): Will add rtt as part of the network cost.
+ return port()->network_cost() + remote_candidate_.network_cost();
+}
+
+std::string Connection::ToString() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ 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)
+ };
+ rtc::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() << ":" << local.protocol() << ":"
+ << local.address().ToSensitiveString() << "->" << remote.id() << ":"
+ << remote.component() << ":" << remote.priority() << ":" << remote.type()
+ << ":" << 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_ < DEFAULT_RTT) {
+ ss << rtt_ << "]";
+ } else {
+ ss << "-]";
+ }
+
+ return ss.Release();
+}
+
+std::string Connection::ToSensitiveString() const {
+ return ToString();
+}
+
+const webrtc::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 rtc::Network* network = port()->Network();
+ log_description_ = webrtc::IceCandidatePairDescription();
+ log_description_->local_candidate_type =
+ GetCandidateTypeByString(local.type());
+ 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_candidate_type =
+ GetCandidateTypeByString(remote.type());
+ 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(webrtc::IceEventLog* ice_event_log) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ ice_event_log_ = ice_event_log;
+}
+
+void Connection::LogCandidatePairConfig(
+ webrtc::IceCandidatePairConfigType type) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (ice_event_log_ == nullptr) {
+ return;
+ }
+ ice_event_log_->LogCandidatePairConfig(type, id(), ToLogDescription());
+}
+
+void Connection::LogCandidatePairEvent(webrtc::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.
+ rtc::LoggingSeverity sev = !writable() ? rtc::LS_INFO : rtc::LS_VERBOSE;
+
+ int 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=" << rtc::hex_encode(request->id())
+ << ", code=0" // Makes logging easier to parse.
+ ", rtt="
+ << rtt << ", pings_since_last_response=" << pings;
+ }
+ absl::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(
+ webrtc::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();
+ }
+ }
+}
+
+void Connection::OnConnectionRequestErrorResponse(ConnectionRequest* request,
+ StunMessage* response) {
+ if (!port_)
+ return;
+
+ int error_code = response->GetErrorCodeValue();
+ RTC_LOG(LS_WARNING) << ToString() << ": Received "
+ << StunMethodToString(response->type())
+ << " id=" << rtc::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_->SignalRoleConflict(port_.get());
+ } 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.
+ rtc::LoggingSeverity sev = writable() ? rtc::LS_INFO : rtc::LS_VERBOSE;
+ RTC_LOG_V(sev) << ToString() << ": Timing-out STUN ping "
+ << rtc::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.
+ rtc::LoggingSeverity sev = !writable() ? rtc::LS_INFO : rtc::LS_VERBOSE;
+ RTC_LOG_V(sev) << ToString() << ": Sent "
+ << StunMethodToString(request->msg()->type())
+ << ", id=" << rtc::hex_encode(request->id())
+ << ", use_candidate=" << use_candidate_attr()
+ << ", nomination=" << nomination_;
+ stats_.sent_ping_requests_total++;
+ LogCandidatePairEvent(webrtc::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
+ // absl::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_.type() == PRFLX_PORT_TYPE &&
+ new_candidate.type() != PRFLX_PORT_TYPE &&
+ 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;
+ }
+}
+
+int64_t Connection::last_received() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return std::max(last_data_received_,
+ std::max(last_ping_received_, last_ping_response_received_));
+}
+
+int64_t Connection::receiving_unchanged_since() 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_);
+ stats_.recv_bytes_second = round(recv_rate_tracker_.ComputeRate());
+ stats_.recv_total_bytes = recv_rate_tracker_.TotalSampleCount();
+ stats_.sent_bytes_second = round(send_rate_tracker_.ComputeRate());
+ stats_.sent_total_bytes = send_rate_tracker_.TotalSampleCount();
+ stats_.receiving = receiving_;
+ stats_.writable = write_state_ == STATE_WRITABLE;
+ stats_.timeout = write_state_ == STATE_WRITE_TIMEOUT;
+ stats_.rtt = rtt_;
+ 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_ms_;
+ stats_.remote_candidate = remote_candidate();
+ if (last_data_received_ > 0) {
+ stats_.last_data_received = webrtc::Timestamp::Millis(
+ last_data_received_ + delta_internal_unix_epoch_ms_);
+ }
+ if (last_send_data_ > 0) {
+ stats_.last_data_sent = webrtc::Timestamp::Millis(
+ last_send_data_ + delta_internal_unix_epoch_ms_);
+ }
+ return stats_;
+}
+
+void Connection::MaybeUpdateLocalCandidate(StunRequest* request,
+ StunMessage* response) {
+ 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;
+ // SignalStateChange to force a re-sort in P2PTransportChannel as this
+ // Connection's local candidate has changed.
+ SignalStateChange(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();
+ std::string id = rtc::CreateRandomString(8);
+
+ // Create a peer-reflexive candidate based on the local candidate.
+ local_candidate_.set_id(id);
+ local_candidate_.set_type(PRFLX_PORT_TYPE);
+ // Set the related address and foundation attributes before changing the
+ // address.
+ local_candidate_.set_related_address(local_candidate_.address());
+ local_candidate_.set_foundation(port()->ComputeFoundation(
+ PRFLX_PORT_TYPE, local_candidate_.protocol(),
+ local_candidate_.relay_protocol(), local_candidate_.address()));
+ 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_);
+
+ // SignalStateChange to force a re-sort in P2PTransportChannel as this
+ // Connection's local candidate has changed.
+ SignalStateChange(this);
+}
+
+bool Connection::rtt_converged() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return rtt_samples_ > (RTT_RATIO + 1);
+}
+
+bool Connection::missing_responses(int64_t now) const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (pings_since_last_response_.empty()) {
+ return false;
+ }
+
+ int64_t waiting = now - pings_since_last_response_[0].sent_time;
+ return waiting > 2 * rtt();
+}
+
+bool Connection::TooManyOutstandingPings(
+ const absl::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.
+ SignalStateChange(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(rtc::WeakPtr<Port> port,
+ size_t index,
+ const Candidate& remote_candidate)
+ : Connection(std::move(port), index, remote_candidate) {}
+
+int ProxyConnection::Send(const void* data,
+ size_t size,
+ const rtc::PacketOptions& options) {
+ if (!port_)
+ return SOCKET_ERROR;
+
+ stats_.sent_total_packets++;
+ int sent =
+ port_->SendTo(data, size, remote_candidate_.address(), options, true);
+ int64_t now = rtc::TimeMillis();
+ if (sent <= 0) {
+ RTC_DCHECK(sent < 0);
+ error_ = port_->GetError();
+ stats_.sent_discarded_packets++;
+ stats_.sent_discarded_bytes += size;
+ } else {
+ send_rate_tracker_.AddSamplesAtTime(now, sent);
+ }
+ last_send_data_ = now;
+ return sent;
+}
+
+int ProxyConnection::GetError() {
+ return error_;
+}
+
+} // namespace cricket