/* * 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.
*/
// Determines whether we have seen at least the given maximum number of // pings fail to have a response. inlinebool 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) returnfalse;
// 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. inlinebool TooLongWithoutResponse( const std::vector<Connection::SentPing>& pings_since_last_response,
int64_t maximum_time,
int64_t now) { if (pings_since_last_response.size() == 0) returnfalse;
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;
} elseif (c.is_stun()) { return IceCandidateType::kSrflx;
} elseif (c.is_prflx()) { return IceCandidateType::kPrflx;
}
RTC_DCHECK(c.is_relay()); return IceCandidateType::kRelay;
}
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. constint DEFAULT_RTT = 3000; // 3 seconds
// We will restrict RTT estimates (when used for determining state) to be // within a reasonable range. constint MINIMUM_RTT = 100; // 0.1 seconds constint MAXIMUM_RTT = 60000; // 60 seconds
constint DEFAULT_RTT_ESTIMATE_HALF_TIME_MS = 500; // Weighting of the old rtt value to new data. constint 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;
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;
}
int Connection::generation() const {
RTC_DCHECK(port_) << ToDebugId() << ": port_ null in generation()"; return port()->generation();
}
uint64_t Connection::priority() const {
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::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::OnReadPacket(constchar* data,
size_t size,
int64_t packet_time_us) {
OnReadPacket(
rtc::ReceivedPacket::CreateFromLegacy(data, size, packet_time_us));
} void Connection::OnReadPacket(const rtc::ReceivedPacket& packet) {
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( reinterpret_cast<constchar*>(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_ = rtc::TimeMillis();
UpdateReceiving(last_data_received_);
recv_rate_tracker_.AddSamples(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;
} elseif (!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
} elseif (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().is_relay() || local_candidate().is_prflx() ||
remote_candidate().is_relay() || remote_candidate().is_prflx()) { 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;
}
}
// 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);
}
}
// 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();
}
}
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));
}
}
// 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());
// 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);
// 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;
void Connection::UpdateState(int64_t 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. int rtt = rtc::SafeClamp(2 * rtt_, MINIMUM_RTT, MAXIMUM_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::Ping(int64_t 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_;
}
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_));
}
}
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));
}
// 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::ReceivedPingResponse( int rtt,
absl::string_view request_id, const std::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();
}
// 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::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. returnfalse;
} 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. returnfalse;
}
// 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);
}
uint32_t Connection::ComputeNetworkCost() const { // 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();
}
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()
<< ":";
}
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;
}
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);
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 ? constbool 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);
} elseif (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_)(
webrtc::RTCError(webrtc::RTCErrorType::UNSUPPORTED_OPERATION));
} elseif (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_)(
webrtc::RTCError(webrtc::RTCErrorType::INTERNAL_ERROR));
}
} elseif (delta_ack) {
RTC_LOG(LS_ERROR) << "Discard GOOG_DELTA_ACK, no consumer";
}
}
void Connection::OnConnectionRequestErrorResponse(ConnectionRequest* request,
StunMessage* response) {
RTC_DCHECK(port_) << ToDebugId()
<< ": port_ null in OnConnectionRequestErrorResponse"; if (!port_) return;
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::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; // 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();
// 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_);
// SignalStateChange to force a re-sort in P2PTransportChannel as this // Connection's local candidate has changed.
SignalStateChange(this);
}
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;
})) { returntrue;
} returnfalse;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.