webrtc_m130/p2p/base/port.cc

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1857 lines
66 KiB
C++
Raw Normal View History

/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "p2p/base/port.h"
#include <math.h>
#include <algorithm>
#include <utility>
#include <vector>
#include "p2p/base/portallocator.h"
#include "rtc_base/base64.h"
#include "rtc_base/checks.h"
#include "rtc_base/crc32.h"
#include "rtc_base/helpers.h"
#include "rtc_base/logging.h"
#include "rtc_base/messagedigest.h"
#include "rtc_base/network.h"
#include "rtc_base/numerics/safe_minmax.h"
#include "rtc_base/ptr_util.h"
#include "rtc_base/stringencode.h"
#include "rtc_base/stringutils.h"
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<cricket::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<cricket::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(const std::string& type) {
if (type == cricket::LOCAL_PORT_TYPE) {
return webrtc::IceCandidateType::kLocal;
} else if (type == cricket::STUN_PORT_TYPE) {
return webrtc::IceCandidateType::kStun;
} else if (type == cricket::PRFLX_PORT_TYPE) {
return webrtc::IceCandidateType::kPrflx;
} else if (type == cricket::RELAY_PORT_TYPE) {
return webrtc::IceCandidateType::kRelay;
}
return webrtc::IceCandidateType::kUnknown;
}
webrtc::IceCandidatePairProtocol GetProtocolByString(
const std::string& protocol) {
if (protocol == cricket::UDP_PROTOCOL_NAME) {
return webrtc::IceCandidatePairProtocol::kUdp;
} else if (protocol == cricket::TCP_PROTOCOL_NAME) {
return webrtc::IceCandidatePairProtocol::kTcp;
} else if (protocol == cricket::SSLTCP_PROTOCOL_NAME) {
return webrtc::IceCandidatePairProtocol::kSsltcp;
} else if (protocol == cricket::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) {
if (type == rtc::ADAPTER_TYPE_ETHERNET) {
return webrtc::IceCandidateNetworkType::kEthernet;
} else if (type == rtc::ADAPTER_TYPE_LOOPBACK) {
return webrtc::IceCandidateNetworkType::kLoopback;
} else if (type == rtc::ADAPTER_TYPE_WIFI) {
return webrtc::IceCandidateNetworkType::kWifi;
} else if (type == rtc::ADAPTER_TYPE_VPN) {
return webrtc::IceCandidateNetworkType::kVpn;
} else if (type == rtc::ADAPTER_TYPE_CELLULAR) {
return webrtc::IceCandidateNetworkType::kCellular;
}
return webrtc::IceCandidateNetworkType::kUnknown;
}
// 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
// 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
// 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
// The delay before we begin checking if this port is useless. We set
// it to a little higher than a total STUN timeout.
const int kPortTimeoutDelay = cricket::STUN_TOTAL_TIMEOUT + 5000;
// For packet loss estimation.
const int64_t kConsiderPacketLostAfter = 3000; // 3 seconds
// For packet loss estimation.
const int64_t kForgetPacketAfter = 30000; // 30 seconds
} // namespace
namespace cricket {
using webrtc::RTCErrorType;
using webrtc::RTCError;
// TODO(ronghuawu): Use "local", "srflx", "prflx" and "relay". But this requires
// the signaling part be updated correspondingly as well.
const char LOCAL_PORT_TYPE[] = "local";
const char STUN_PORT_TYPE[] = "stun";
const char PRFLX_PORT_TYPE[] = "prflx";
const char RELAY_PORT_TYPE[] = "relay";
static const char* const PROTO_NAMES[] = {UDP_PROTOCOL_NAME, TCP_PROTOCOL_NAME,
SSLTCP_PROTOCOL_NAME,
TLS_PROTOCOL_NAME};
const char* ProtoToString(ProtocolType proto) {
return PROTO_NAMES[proto];
}
bool StringToProto(const char* value, ProtocolType* proto) {
for (size_t i = 0; i <= PROTO_LAST; ++i) {
if (_stricmp(PROTO_NAMES[i], value) == 0) {
*proto = static_cast<ProtocolType>(i);
return true;
}
}
return false;
}
// RFC 6544, TCP candidate encoding rules.
const int DISCARD_PORT = 9;
const char TCPTYPE_ACTIVE_STR[] = "active";
const char TCPTYPE_PASSIVE_STR[] = "passive";
const char TCPTYPE_SIMOPEN_STR[] = "so";
// Foundation: An arbitrary string that is the same for two candidates
// that have the same type, base IP address, protocol (UDP, TCP,
// etc.), and STUN or TURN server. If any of these are different,
// then the foundation will be different. Two candidate pairs with
// the same foundation pairs are likely to have similar network
// characteristics. Foundations are used in the frozen algorithm.
static std::string ComputeFoundation(const std::string& type,
const std::string& protocol,
const std::string& relay_protocol,
const rtc::SocketAddress& base_address) {
std::ostringstream ost;
ost << type << base_address.ipaddr().ToString() << protocol << relay_protocol;
return rtc::ToString<uint32_t>(rtc::ComputeCrc32(ost.str()));
}
CandidateStats::CandidateStats() = default;
CandidateStats::CandidateStats(const CandidateStats&) = default;
CandidateStats::CandidateStats(Candidate candidate) {
this->candidate = candidate;
}
CandidateStats::~CandidateStats() = default;
ConnectionInfo::ConnectionInfo()
: best_connection(false),
writable(false),
receiving(false),
timeout(false),
new_connection(false),
rtt(0),
sent_total_bytes(0),
sent_bytes_second(0),
sent_discarded_packets(0),
sent_total_packets(0),
sent_ping_requests_total(0),
sent_ping_requests_before_first_response(0),
sent_ping_responses(0),
recv_total_bytes(0),
recv_bytes_second(0),
recv_ping_requests(0),
recv_ping_responses(0),
key(nullptr),
state(IceCandidatePairState::WAITING),
priority(0),
nominated(false),
total_round_trip_time_ms(0) {}
ConnectionInfo::ConnectionInfo(const ConnectionInfo&) = default;
ConnectionInfo::~ConnectionInfo() = default;
Port::Port(rtc::Thread* thread,
const std::string& type,
rtc::PacketSocketFactory* factory,
rtc::Network* network,
const std::string& username_fragment,
const std::string& password)
: thread_(thread),
factory_(factory),
type_(type),
send_retransmit_count_attribute_(false),
network_(network),
min_port_(0),
max_port_(0),
component_(ICE_CANDIDATE_COMPONENT_DEFAULT),
generation_(0),
ice_username_fragment_(username_fragment),
password_(password),
timeout_delay_(kPortTimeoutDelay),
enable_port_packets_(false),
ice_role_(ICEROLE_UNKNOWN),
tiebreaker_(0),
shared_socket_(true) {
Construct();
}
Port::Port(rtc::Thread* thread,
const std::string& type,
rtc::PacketSocketFactory* factory,
rtc::Network* network,
const rtc::IPAddress& ip,
const std::string& username_fragment,
const std::string& password)
: Port(thread, type, factory, network, username_fragment, password) {}
Port::Port(rtc::Thread* thread,
const std::string& type,
rtc::PacketSocketFactory* factory,
rtc::Network* network,
uint16_t min_port,
uint16_t max_port,
const std::string& username_fragment,
const std::string& password)
: thread_(thread),
factory_(factory),
type_(type),
send_retransmit_count_attribute_(false),
network_(network),
min_port_(min_port),
max_port_(max_port),
component_(ICE_CANDIDATE_COMPONENT_DEFAULT),
generation_(0),
ice_username_fragment_(username_fragment),
password_(password),
timeout_delay_(kPortTimeoutDelay),
enable_port_packets_(false),
ice_role_(ICEROLE_UNKNOWN),
tiebreaker_(0),
shared_socket_(false) {
RTC_DCHECK(factory_ != NULL);
Construct();
}
void Port::Construct() {
// TODO(pthatcher): Remove this old behavior once we're sure no one
// relies on it. If the username_fragment and password are empty,
// we should just create one.
if (ice_username_fragment_.empty()) {
RTC_DCHECK(password_.empty());
ice_username_fragment_ = rtc::CreateRandomString(ICE_UFRAG_LENGTH);
password_ = rtc::CreateRandomString(ICE_PWD_LENGTH);
}
network_->SignalTypeChanged.connect(this, &Port::OnNetworkTypeChanged);
network_cost_ = network_->GetCost();
thread_->PostDelayed(RTC_FROM_HERE, timeout_delay_, this,
MSG_DESTROY_IF_DEAD);
RTC_LOG(LS_INFO) << ToString()
<< ": Port created with network cost " << network_cost_;
}
Port::~Port() {
// Delete all of the remaining connections. We copy the list up front
// because each deletion will cause it to be modified.
std::vector<Connection*> list;
AddressMap::iterator iter = connections_.begin();
while (iter != connections_.end()) {
list.push_back(iter->second);
++iter;
}
for (uint32_t i = 0; i < list.size(); i++)
delete list[i];
}
const std::string& Port::Type() const {
return type_;
}
rtc::Network* Port::Network() const {
return network_;
}
IceRole Port::GetIceRole() const {
return ice_role_;
}
void Port::SetIceRole(IceRole role) {
ice_role_ = role;
}
void Port::SetIceTiebreaker(uint64_t tiebreaker) {
tiebreaker_ = tiebreaker;
}
uint64_t Port::IceTiebreaker() const {
return tiebreaker_;
}
bool Port::SharedSocket() const {
return shared_socket_;
}
void Port::SetIceParameters(int component,
const std::string& username_fragment,
const std::string& password) {
component_ = component;
ice_username_fragment_ = username_fragment;
password_ = password;
for (Candidate& c : candidates_) {
c.set_component(component);
c.set_username(username_fragment);
c.set_password(password);
}
}
const std::vector<Candidate>& Port::Candidates() const {
return candidates_;
}
Connection* Port::GetConnection(const rtc::SocketAddress& remote_addr) {
AddressMap::const_iterator iter = connections_.find(remote_addr);
if (iter != connections_.end())
return iter->second;
else
return NULL;
}
void Port::AddAddress(const rtc::SocketAddress& address,
const rtc::SocketAddress& base_address,
const rtc::SocketAddress& related_address,
const std::string& protocol,
const std::string& relay_protocol,
const std::string& tcptype,
const std::string& type,
uint32_t type_preference,
uint32_t relay_preference,
bool final) {
AddAddress(address, base_address, related_address, protocol, relay_protocol,
tcptype, type, type_preference, relay_preference, "", final);
}
void Port::AddAddress(const rtc::SocketAddress& address,
const rtc::SocketAddress& base_address,
const rtc::SocketAddress& related_address,
const std::string& protocol,
const std::string& relay_protocol,
const std::string& tcptype,
const std::string& type,
uint32_t type_preference,
uint32_t relay_preference,
const std::string& url,
bool final) {
if (protocol == TCP_PROTOCOL_NAME && type == LOCAL_PORT_TYPE) {
RTC_DCHECK(!tcptype.empty());
}
std::string foundation =
ComputeFoundation(type, protocol, relay_protocol, base_address);
Candidate c(component_, protocol, address, 0U, username_fragment(), password_,
type, generation_, foundation, network_->id(), network_cost_);
c.set_priority(
c.GetPriority(type_preference, network_->preference(), relay_preference));
c.set_relay_protocol(relay_protocol);
c.set_tcptype(tcptype);
c.set_network_name(network_->name());
c.set_network_type(network_->type());
c.set_related_address(related_address);
c.set_url(url);
candidates_.push_back(c);
SignalCandidateReady(this, c);
if (final) {
SignalPortComplete(this);
}
}
void Port::AddOrReplaceConnection(Connection* conn) {
auto ret = connections_.insert(
std::make_pair(conn->remote_candidate().address(), conn));
// If there is a different connection on the same remote address, replace
// it with the new one and destroy the old one.
if (ret.second == false && ret.first->second != conn) {
RTC_LOG(LS_WARNING)
<< ToString()
<< ": A new connection was created on an existing remote address. "
"New remote candidate: "
<< conn->remote_candidate().ToString();
ret.first->second->SignalDestroyed.disconnect(this);
ret.first->second->Destroy();
ret.first->second = conn;
}
conn->SignalDestroyed.connect(this, &Port::OnConnectionDestroyed);
SignalConnectionCreated(this, conn);
}
void Port::OnReadPacket(
const char* data, size_t size, const rtc::SocketAddress& addr,
ProtocolType proto) {
// If the user has enabled port packets, just hand this over.
if (enable_port_packets_) {
SignalReadPacket(this, data, size, addr);
return;
}
// If this is an authenticated STUN request, then signal unknown address and
// send back a proper binding response.
std::unique_ptr<IceMessage> msg;
std::string remote_username;
if (!GetStunMessage(data, size, addr, &msg, &remote_username)) {
RTC_LOG(LS_ERROR) << ToString()
<< ": Received non-STUN packet from unknown address: "
<< addr.ToSensitiveString();
} else if (!msg) {
// STUN message handled already
} else if (msg->type() == STUN_BINDING_REQUEST) {
RTC_LOG(LS_INFO) << "Received STUN ping id="
<< rtc::hex_encode(msg->transaction_id())
<< " from unknown address " << addr.ToSensitiveString();
// We need to signal an unknown address before we handle any role conflict
// below. Otherwise there would be no candidate pair and TURN entry created
// to send the error response in case of a role conflict.
SignalUnknownAddress(this, addr, proto, msg.get(), remote_username, false);
// Check for role conflicts.
if (!MaybeIceRoleConflict(addr, msg.get(), remote_username)) {
RTC_LOG(LS_INFO) << "Received conflicting role from the peer.";
return;
}
} else {
// NOTE(tschmelcher): STUN_BINDING_RESPONSE is benign. It occurs if we
// pruned a connection for this port while it had STUN requests in flight,
// because we then get back responses for them, which this code correctly
// does not handle.
if (msg->type() != STUN_BINDING_RESPONSE) {
RTC_LOG(LS_ERROR) << ToString()
<< ": Received unexpected STUN message type: "
<< msg->type() << " from unknown address: "
<< addr.ToSensitiveString();
}
}
}
void Port::OnReadyToSend() {
AddressMap::iterator iter = connections_.begin();
for (; iter != connections_.end(); ++iter) {
iter->second->OnReadyToSend();
}
}
size_t Port::AddPrflxCandidate(const Candidate& local) {
candidates_.push_back(local);
return (candidates_.size() - 1);
}
bool Port::GetStunMessage(const char* data,
size_t size,
const rtc::SocketAddress& addr,
std::unique_ptr<IceMessage>* out_msg,
std::string* out_username) {
// NOTE: This could clearly be optimized to avoid allocating any memory.
// However, at the data rates we'll be looking at on the client side,
// this probably isn't worth worrying about.
RTC_DCHECK(out_msg != NULL);
RTC_DCHECK(out_username != NULL);
out_username->clear();
// Don't bother parsing the packet if we can tell it's not STUN.
// In ICE mode, all STUN packets will have a valid fingerprint.
if (!StunMessage::ValidateFingerprint(data, size)) {
return false;
}
// Parse the request message. If the packet is not a complete and correct
// STUN message, then ignore it.
std::unique_ptr<IceMessage> stun_msg(new IceMessage());
rtc::ByteBufferReader buf(data, size);
if (!stun_msg->Read(&buf) || (buf.Length() > 0)) {
return false;
}
if (stun_msg->type() == STUN_BINDING_REQUEST) {
// Check for the presence of USERNAME and MESSAGE-INTEGRITY (if ICE) first.
// If not present, fail with a 400 Bad Request.
if (!stun_msg->GetByteString(STUN_ATTR_USERNAME) ||
!stun_msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY)) {
RTC_LOG(LS_ERROR) << ToString()
<< ": Received STUN request without username/M-I from: "
<< addr.ToSensitiveString();
SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_BAD_REQUEST,
STUN_ERROR_REASON_BAD_REQUEST);
return true;
}
// If the username is bad or unknown, fail with a 401 Unauthorized.
std::string local_ufrag;
std::string remote_ufrag;
if (!ParseStunUsername(stun_msg.get(), &local_ufrag, &remote_ufrag) ||
local_ufrag != username_fragment()) {
RTC_LOG(LS_ERROR) << ToString()
<< ": Received STUN request with bad local username "
<< local_ufrag << " from " << addr.ToSensitiveString();
SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_UNAUTHORIZED,
STUN_ERROR_REASON_UNAUTHORIZED);
return true;
}
// If ICE, and the MESSAGE-INTEGRITY is bad, fail with a 401 Unauthorized
if (!stun_msg->ValidateMessageIntegrity(data, size, password_)) {
RTC_LOG(LS_ERROR) << ToString()
<< ": Received STUN request with bad M-I from "
<< addr.ToSensitiveString()
<< ", password_=" << password_;
SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_UNAUTHORIZED,
STUN_ERROR_REASON_UNAUTHORIZED);
return true;
}
out_username->assign(remote_ufrag);
} else if ((stun_msg->type() == STUN_BINDING_RESPONSE) ||
(stun_msg->type() == STUN_BINDING_ERROR_RESPONSE)) {
if (stun_msg->type() == STUN_BINDING_ERROR_RESPONSE) {
if (const StunErrorCodeAttribute* error_code = stun_msg->GetErrorCode()) {
RTC_LOG(LS_ERROR) << ToString()
<< ": Received STUN binding error: class="
<< error_code->eclass()
<< " number=" << error_code->number() << " reason='"
<< error_code->reason() << "' from "
<< addr.ToSensitiveString();
// Return message to allow error-specific processing
} else {
RTC_LOG(LS_ERROR)
<< ToString()
<< ": Received STUN binding error without a error code from "
<< addr.ToSensitiveString();
return true;
}
}
// NOTE: Username should not be used in verifying response messages.
out_username->clear();
} else if (stun_msg->type() == STUN_BINDING_INDICATION) {
RTC_LOG(LS_VERBOSE) << ToString()
<< ": Received STUN binding indication: from "
<< addr.ToSensitiveString();
out_username->clear();
// No stun attributes will be verified, if it's stun indication message.
// Returning from end of the this method.
} else {
RTC_LOG(LS_ERROR) << ToString()
<< ": Received STUN packet with invalid type ("
<< stun_msg->type() << ") from "
<< addr.ToSensitiveString();
return true;
}
// Return the STUN message found.
*out_msg = std::move(stun_msg);
return true;
}
bool Port::IsCompatibleAddress(const rtc::SocketAddress& addr) {
Make Port (and subclasses) fully "Network"-based, instead of IP-based. For ICE, we want sockets that are bound to specific network interfaces, rather than to specific IP addresses. So, a while ago, we added a "Network" class that gets passed into the Port constructor, in addition to the IP address as before. But we never finished the job of removing the IP address field, such that a Port only guarantees something about the network interface it's associated with, and not the specific IP address it ends up with. This CL does that, and as a consequence, if a port ends up bound to an IP address other than the "best" one (returned by Network::GetBestIP), this *won't* be treated as an error. This is relevant to Android, where even though we pass an IP address into "Bind" as a way of identifying the network, the socket actually gets bound using "android_setsocknetwork", which doesn't provide any guarantees about the IP address. So, if a network interface has multiple IPv6 addresses (for instance), we may not correctly predict the one the OS will choose, and that's ok. This CL also moves "SetAlternateLocalAddress" from VirtualSocket to VirtualSocketServer, which makes for much more readable test code. The next step, if there is one, is to pass along the Network class all the way to SocketServer::Bind. Then the socket server could do smart things with the network information. We could even stick a platform- specific network handle in the Network object, such that the socket server could use it for the binding, or for "sendmsg", for example. See bug 7026 for more context about the sendmsg idea. BUG=webrtc:7715 Review-Url: https://codereview.webrtc.org/2989303002 Cr-Commit-Position: refs/heads/master@{#19251}
2017-08-04 15:01:57 -07:00
// Get a representative IP for the Network this port is configured to use.
rtc::IPAddress ip = network_->GetBestIP();
// We use single-stack sockets, so families must match.
Make Port (and subclasses) fully "Network"-based, instead of IP-based. For ICE, we want sockets that are bound to specific network interfaces, rather than to specific IP addresses. So, a while ago, we added a "Network" class that gets passed into the Port constructor, in addition to the IP address as before. But we never finished the job of removing the IP address field, such that a Port only guarantees something about the network interface it's associated with, and not the specific IP address it ends up with. This CL does that, and as a consequence, if a port ends up bound to an IP address other than the "best" one (returned by Network::GetBestIP), this *won't* be treated as an error. This is relevant to Android, where even though we pass an IP address into "Bind" as a way of identifying the network, the socket actually gets bound using "android_setsocknetwork", which doesn't provide any guarantees about the IP address. So, if a network interface has multiple IPv6 addresses (for instance), we may not correctly predict the one the OS will choose, and that's ok. This CL also moves "SetAlternateLocalAddress" from VirtualSocket to VirtualSocketServer, which makes for much more readable test code. The next step, if there is one, is to pass along the Network class all the way to SocketServer::Bind. Then the socket server could do smart things with the network information. We could even stick a platform- specific network handle in the Network object, such that the socket server could use it for the binding, or for "sendmsg", for example. See bug 7026 for more context about the sendmsg idea. BUG=webrtc:7715 Review-Url: https://codereview.webrtc.org/2989303002 Cr-Commit-Position: refs/heads/master@{#19251}
2017-08-04 15:01:57 -07:00
if (addr.family() != ip.family()) {
return false;
}
// Link-local IPv6 ports can only connect to other link-local IPv6 ports.
Make Port (and subclasses) fully "Network"-based, instead of IP-based. For ICE, we want sockets that are bound to specific network interfaces, rather than to specific IP addresses. So, a while ago, we added a "Network" class that gets passed into the Port constructor, in addition to the IP address as before. But we never finished the job of removing the IP address field, such that a Port only guarantees something about the network interface it's associated with, and not the specific IP address it ends up with. This CL does that, and as a consequence, if a port ends up bound to an IP address other than the "best" one (returned by Network::GetBestIP), this *won't* be treated as an error. This is relevant to Android, where even though we pass an IP address into "Bind" as a way of identifying the network, the socket actually gets bound using "android_setsocknetwork", which doesn't provide any guarantees about the IP address. So, if a network interface has multiple IPv6 addresses (for instance), we may not correctly predict the one the OS will choose, and that's ok. This CL also moves "SetAlternateLocalAddress" from VirtualSocket to VirtualSocketServer, which makes for much more readable test code. The next step, if there is one, is to pass along the Network class all the way to SocketServer::Bind. Then the socket server could do smart things with the network information. We could even stick a platform- specific network handle in the Network object, such that the socket server could use it for the binding, or for "sendmsg", for example. See bug 7026 for more context about the sendmsg idea. BUG=webrtc:7715 Review-Url: https://codereview.webrtc.org/2989303002 Cr-Commit-Position: refs/heads/master@{#19251}
2017-08-04 15:01:57 -07:00
if (ip.family() == AF_INET6 &&
(IPIsLinkLocal(ip) != IPIsLinkLocal(addr.ipaddr()))) {
return false;
}
return true;
}
bool Port::ParseStunUsername(const StunMessage* stun_msg,
std::string* local_ufrag,
std::string* remote_ufrag) const {
// The packet must include a username that either begins or ends with our
// fragment. It should begin with our fragment if it is a request and it
// should end with our fragment if it is a response.
local_ufrag->clear();
remote_ufrag->clear();
const StunByteStringAttribute* username_attr =
stun_msg->GetByteString(STUN_ATTR_USERNAME);
if (username_attr == NULL)
return false;
// RFRAG:LFRAG
const std::string username = username_attr->GetString();
size_t colon_pos = username.find(":");
if (colon_pos == std::string::npos) {
return false;
}
*local_ufrag = username.substr(0, colon_pos);
*remote_ufrag = username.substr(colon_pos + 1, username.size());
return true;
}
bool Port::MaybeIceRoleConflict(
const rtc::SocketAddress& addr, IceMessage* stun_msg,
const std::string& remote_ufrag) {
// Validate ICE_CONTROLLING or ICE_CONTROLLED attributes.
bool ret = true;
IceRole remote_ice_role = ICEROLE_UNKNOWN;
uint64_t remote_tiebreaker = 0;
const StunUInt64Attribute* stun_attr =
stun_msg->GetUInt64(STUN_ATTR_ICE_CONTROLLING);
if (stun_attr) {
remote_ice_role = ICEROLE_CONTROLLING;
remote_tiebreaker = stun_attr->value();
}
// If |remote_ufrag| is same as port local username fragment and
// tie breaker value received in the ping message matches port
// tiebreaker value this must be a loopback call.
// We will treat this as valid scenario.
if (remote_ice_role == ICEROLE_CONTROLLING &&
username_fragment() == remote_ufrag &&
remote_tiebreaker == IceTiebreaker()) {
return true;
}
stun_attr = stun_msg->GetUInt64(STUN_ATTR_ICE_CONTROLLED);
if (stun_attr) {
remote_ice_role = ICEROLE_CONTROLLED;
remote_tiebreaker = stun_attr->value();
}
switch (ice_role_) {
case ICEROLE_CONTROLLING:
if (ICEROLE_CONTROLLING == remote_ice_role) {
if (remote_tiebreaker >= tiebreaker_) {
SignalRoleConflict(this);
} else {
// Send Role Conflict (487) error response.
SendBindingErrorResponse(stun_msg, addr,
STUN_ERROR_ROLE_CONFLICT, STUN_ERROR_REASON_ROLE_CONFLICT);
ret = false;
}
}
break;
case ICEROLE_CONTROLLED:
if (ICEROLE_CONTROLLED == remote_ice_role) {
if (remote_tiebreaker < tiebreaker_) {
SignalRoleConflict(this);
} else {
// Send Role Conflict (487) error response.
SendBindingErrorResponse(stun_msg, addr,
STUN_ERROR_ROLE_CONFLICT, STUN_ERROR_REASON_ROLE_CONFLICT);
ret = false;
}
}
break;
default:
RTC_NOTREACHED();
}
return ret;
}
void Port::CreateStunUsername(const std::string& remote_username,
std::string* stun_username_attr_str) const {
stun_username_attr_str->clear();
*stun_username_attr_str = remote_username;
stun_username_attr_str->append(":");
stun_username_attr_str->append(username_fragment());
}
bool Port::HandleIncomingPacket(rtc::AsyncPacketSocket* socket,
const char* data,
size_t size,
const rtc::SocketAddress& remote_addr,
const rtc::PacketTime& packet_time) {
RTC_NOTREACHED();
return false;
}
bool Port::CanHandleIncomingPacketsFrom(const rtc::SocketAddress&) const {
return false;
}
void Port::SendBindingResponse(StunMessage* request,
const rtc::SocketAddress& addr) {
RTC_DCHECK(request->type() == STUN_BINDING_REQUEST);
// Retrieve the username from the request.
const StunByteStringAttribute* username_attr =
request->GetByteString(STUN_ATTR_USERNAME);
RTC_DCHECK(username_attr != NULL);
if (username_attr == NULL) {
// No valid username, skip the response.
return;
}
// Fill in the response message.
StunMessage response;
response.SetType(STUN_BINDING_RESPONSE);
response.SetTransactionID(request->transaction_id());
const StunUInt32Attribute* retransmit_attr =
request->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(rtc::MakeUnique<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(rtc::MakeUnique<StunXorAddressAttribute>(
STUN_ATTR_XOR_MAPPED_ADDRESS, addr));
response.AddMessageIntegrity(password_);
response.AddFingerprint();
// Send the response message.
rtc::ByteBufferWriter buf;
response.Write(&buf);
rtc::PacketOptions options(DefaultDscpValue());
auto err = SendTo(buf.Data(), buf.Length(), addr, options, false);
if (err < 0) {
RTC_LOG(LS_ERROR) << ToString()
<< ": Failed to send STUN ping response, 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.
Connection* conn = GetConnection(addr);
rtc::LoggingSeverity sev = (conn && !conn->writable()) ?
rtc::LS_INFO : rtc::LS_VERBOSE;
RTC_LOG_V(sev) << ToString()
<< ": Sent STUN ping response, to="
<< addr.ToSensitiveString()
<< ", id=" << rtc::hex_encode(response.transaction_id());
conn->stats_.sent_ping_responses++;
conn->LogCandidatePairEvent(
webrtc::IceCandidatePairEventType::kCheckResponseSent);
}
}
void Port::SendBindingErrorResponse(StunMessage* request,
const rtc::SocketAddress& addr,
int error_code, const std::string& reason) {
RTC_DCHECK(request->type() == STUN_BINDING_REQUEST);
// Fill in the response message.
StunMessage response;
response.SetType(STUN_BINDING_ERROR_RESPONSE);
response.SetTransactionID(request->transaction_id());
// When doing GICE, we need to write out the error code incorrectly to
// maintain backwards compatiblility.
auto error_attr = StunAttribute::CreateErrorCode();
error_attr->SetCode(error_code);
error_attr->SetReason(reason);
response.AddAttribute(std::move(error_attr));
// Per Section 10.1.2, certain error cases don't get a MESSAGE-INTEGRITY,
// because we don't have enough information to determine the shared secret.
if (error_code != STUN_ERROR_BAD_REQUEST &&
error_code != STUN_ERROR_UNAUTHORIZED)
response.AddMessageIntegrity(password_);
response.AddFingerprint();
// Send the response message.
rtc::ByteBufferWriter buf;
response.Write(&buf);
rtc::PacketOptions options(DefaultDscpValue());
SendTo(buf.Data(), buf.Length(), addr, options, false);
RTC_LOG(LS_INFO) << ToString()
<< ": Sending STUN binding error: reason=" << reason
<< " to " << addr.ToSensitiveString();
}
void Port::KeepAliveUntilPruned() {
// If it is pruned, we won't bring it up again.
if (state_ == State::INIT) {
state_ = State::KEEP_ALIVE_UNTIL_PRUNED;
}
}
void Port::Prune() {
state_ = State::PRUNED;
thread_->Post(RTC_FROM_HERE, this, MSG_DESTROY_IF_DEAD);
}
void Port::OnMessage(rtc::Message *pmsg) {
RTC_DCHECK(pmsg->message_id == MSG_DESTROY_IF_DEAD);
bool dead =
(state_ == State::INIT || state_ == State::PRUNED) &&
connections_.empty() &&
rtc::TimeMillis() - last_time_all_connections_removed_ >= timeout_delay_;
if (dead) {
Destroy();
}
}
void Port::OnNetworkTypeChanged(const rtc::Network* network) {
RTC_DCHECK(network == network_);
UpdateNetworkCost();
}
std::string Port::ToString() const {
std::stringstream ss;
ss << "Port[" << std::hex << this << std::dec << ":" << content_name_ << ":"
<< component_ << ":" << generation_ << ":" << type_ << ":"
<< network_->ToString() << "]";
return ss.str();
}
// TODO(honghaiz): Make the network cost configurable from user setting.
void Port::UpdateNetworkCost() {
uint16_t new_cost = network_->GetCost();
if (network_cost_ == new_cost) {
return;
}
RTC_LOG(LS_INFO) << "Network cost changed from " << network_cost_ << " to "
<< new_cost
<< ". Number of candidates created: " << candidates_.size()
<< ". Number of connections created: "
<< connections_.size();
network_cost_ = new_cost;
for (cricket::Candidate& candidate : candidates_) {
candidate.set_network_cost(network_cost_);
}
// Network cost change will affect the connection selection criteria.
// Signal the connection state change on each connection to force a
// re-sort in P2PTransportChannel.
for (auto kv : connections_) {
Connection* conn = kv.second;
conn->SignalStateChange(conn);
}
}
void Port::EnablePortPackets() {
enable_port_packets_ = true;
}
void Port::OnConnectionDestroyed(Connection* conn) {
AddressMap::iterator iter =
connections_.find(conn->remote_candidate().address());
RTC_DCHECK(iter != connections_.end());
connections_.erase(iter);
HandleConnectionDestroyed(conn);
// Ports time out after all connections fail if it is not marked as
// "keep alive until pruned."
// Note: If a new connection is added after this message is posted, but it
// fails and is removed before kPortTimeoutDelay, then this message will
// not cause the Port to be destroyed.
if (connections_.empty()) {
last_time_all_connections_removed_ = rtc::TimeMillis();
thread_->PostDelayed(RTC_FROM_HERE, timeout_delay_, this,
MSG_DESTROY_IF_DEAD);
}
}
void Port::Destroy() {
RTC_DCHECK(connections_.empty());
RTC_LOG(LS_INFO) << ToString() << ": Port deleted";
SignalDestroyed(this);
delete this;
}
const std::string Port::username_fragment() const {
return ice_username_fragment_;
}
// A ConnectionRequest is a simple STUN ping used to determine writability.
class ConnectionRequest : public StunRequest {
public:
explicit ConnectionRequest(Connection* connection)
: StunRequest(new IceMessage()),
connection_(connection) {
}
void Prepare(StunMessage* request) override {
request->SetType(STUN_BINDING_REQUEST);
std::string username;
connection_->port()->CreateStunUsername(
connection_->remote_candidate().username(), &username);
request->AddAttribute(
rtc::MakeUnique<StunByteStringAttribute>(STUN_ATTR_USERNAME, username));
// connection_ already holds this ping, so subtract one from count.
if (connection_->port()->send_retransmit_count_attribute()) {
request->AddAttribute(rtc::MakeUnique<StunUInt32Attribute>(
STUN_ATTR_RETRANSMIT_COUNT,
static_cast<uint32_t>(connection_->pings_since_last_response_.size() -
1)));
}
uint32_t network_info = connection_->port()->Network()->id();
network_info = (network_info << 16) | connection_->port()->network_cost();
request->AddAttribute(rtc::MakeUnique<StunUInt32Attribute>(
STUN_ATTR_NETWORK_INFO, network_info));
// Adding ICE_CONTROLLED or ICE_CONTROLLING attribute based on the role.
if (connection_->port()->GetIceRole() == ICEROLE_CONTROLLING) {
request->AddAttribute(rtc::MakeUnique<StunUInt64Attribute>(
STUN_ATTR_ICE_CONTROLLING, connection_->port()->IceTiebreaker()));
// We should have either USE_CANDIDATE attribute or ICE_NOMINATION
// attribute but not both. That was enforced in p2ptransportchannel.
if (connection_->use_candidate_attr()) {
request->AddAttribute(
rtc::MakeUnique<StunByteStringAttribute>(STUN_ATTR_USE_CANDIDATE));
}
if (connection_->nomination() &&
connection_->nomination() != connection_->acked_nomination()) {
request->AddAttribute(rtc::MakeUnique<StunUInt32Attribute>(
STUN_ATTR_NOMINATION, connection_->nomination()));
}
} else if (connection_->port()->GetIceRole() == ICEROLE_CONTROLLED) {
request->AddAttribute(rtc::MakeUnique<StunUInt64Attribute>(
STUN_ATTR_ICE_CONTROLLED, connection_->port()->IceTiebreaker()));
} else {
RTC_NOTREACHED();
}
// Adding 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)
uint32_t type_preference =
(connection_->local_candidate().protocol() == TCP_PROTOCOL_NAME)
? ICE_TYPE_PREFERENCE_PRFLX_TCP
: ICE_TYPE_PREFERENCE_PRFLX;
uint32_t prflx_priority =
type_preference << 24 |
(connection_->local_candidate().priority() & 0x00FFFFFF);
request->AddAttribute(rtc::MakeUnique<StunUInt32Attribute>(
STUN_ATTR_PRIORITY, prflx_priority));
// Adding Message Integrity attribute.
request->AddMessageIntegrity(connection_->remote_candidate().password());
// Adding Fingerprint.
request->AddFingerprint();
}
void OnResponse(StunMessage* response) override {
connection_->OnConnectionRequestResponse(this, response);
}
void OnErrorResponse(StunMessage* response) override {
connection_->OnConnectionRequestErrorResponse(this, response);
}
void OnTimeout() override {
connection_->OnConnectionRequestTimeout(this);
}
void OnSent() override {
connection_->OnConnectionRequestSent(this);
// Each request is sent only once. After a single delay , the request will
// time out.
timeout_ = true;
}
int resend_delay() override {
return CONNECTION_RESPONSE_TIMEOUT;
}
private:
Connection* connection_;
};
//
// Connection
//
Connection::Connection(Port* port,
size_t index,
const Candidate& remote_candidate)
: port_(port),
local_candidate_index_(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),
remote_ice_mode_(ICEMODE_FULL),
requests_(port->thread()),
rtt_(DEFAULT_RTT),
last_ping_sent_(0),
last_ping_received_(0),
last_data_received_(0),
last_ping_response_received_(0),
packet_loss_estimator_(kConsiderPacketLostAfter, kForgetPacketAfter),
reported_(false),
state_(IceCandidatePairState::WAITING),
time_created_ms_(rtc::TimeMillis()) {
// All of our connections start in WAITING state.
// TODO(mallinath) - Start connections from STATE_FROZEN.
// Wire up to send stun packets
requests_.SignalSendPacket.connect(this, &Connection::OnSendStunPacket);
hash_ = static_cast<uint32_t>(std::hash<std::string>{}(ToString()));
RTC_LOG(LS_INFO) << ToString() << ": Connection created";
}
Connection::~Connection() {
}
const Candidate& Connection::local_candidate() const {
RTC_DCHECK(local_candidate_index_ < port_->Candidates().size());
return port_->Candidates()[local_candidate_index_];
}
const Candidate& Connection::remote_candidate() const {
return remote_candidate_;
}
uint64_t Connection::priority() const {
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) {
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) {
bool 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) {
IceCandidatePairState old_state = state_;
state_ = state;
if (state != old_state) {
RTC_LOG(LS_VERBOSE) << ToString() << ": set_state";
}
}
void Connection::set_connected(bool value) {
bool old_value = connected_;
connected_ = value;
if (value != old_value) {
RTC_LOG(LS_VERBOSE) << ToString()
<< ": Change connected_ to " << value;
SignalStateChange(this);
}
}
void Connection::set_use_candidate_attr(bool enable) {
use_candidate_attr_ = enable;
}
int Connection::unwritable_timeout() const {
return unwritable_timeout_.value_or(CONNECTION_WRITE_CONNECT_TIMEOUT);
}
int Connection::unwritable_min_checks() const {
return unwritable_min_checks_.value_or(CONNECTION_WRITE_CONNECT_FAILURES);
}
int Connection::receiving_timeout() const {
return receiving_timeout_.value_or(WEAK_CONNECTION_RECEIVE_TIMEOUT);
}
void Connection::OnSendStunPacket(const void* data, size_t size,
StunRequest* req) {
rtc::PacketOptions options(port_->DefaultDscpValue());
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, const rtc::PacketTime& packet_time) {
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);
SignalReadPacket(this, data, size, packet_time);
// 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);
}
} else if (!msg) {
// The packet was STUN, but failed a check and was handled internally.
} else {
// 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.
rtc::LoggingSeverity sev = (!writable() ? rtc::LS_INFO : rtc::LS_VERBOSE);
switch (msg->type()) {
case STUN_BINDING_REQUEST:
RTC_LOG_V(sev) << ToString()
<< ": Received STUN ping, id="
<< rtc::hex_encode(msg->transaction_id());
if (remote_ufrag == remote_candidate_.username()) {
HandleBindingRequest(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:
if (msg->ValidateMessageIntegrity(
data, size, remote_candidate().password())) {
requests_.CheckResponse(msg.get());
}
// Otherwise silently discard the response message.
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();
break;
default:
RTC_NOTREACHED();
break;
}
}
}
void Connection::HandleBindingRequest(IceMessage* msg) {
// This connection should now be receiving.
ReceivedPing();
const rtc::SocketAddress& remote_addr = remote_candidate_.address();
const std::string& remote_ufrag = remote_candidate_.username();
// Check for role conflicts.
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);
// This is a validated stun request from remote peer.
port_->SendBindingResponse(msg, remote_addr);
// 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_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);
}
}
}
void Connection::OnReadyToSend() {
SignalReadyToSend(this);
}
void Connection::Prune() {
if (!pruned_ || active()) {
RTC_LOG(LS_INFO) << ToString() << ": Connection pruned";
pruned_ = true;
requests_.Clear();
set_write_state(STATE_WRITE_TIMEOUT);
}
}
void Connection::Destroy() {
// TODO(deadbeef, nisse): This may leak if an application closes a
// PeerConnection and then quickly destroys the PeerConnectionFactory (along
// with the networking thread on which this message is posted). Also affects
// tests, with a workaround in
// AutoSocketServerThread::~AutoSocketServerThread.
RTC_LOG(LS_VERBOSE) << ToString()
<< ": Connection destroyed";
port_->thread()->Post(RTC_FROM_HERE, this, MSG_DELETE);
LogCandidatePairEvent(webrtc::IceCandidatePairEventType::kDestroyed);
}
void Connection::FailAndDestroy() {
set_state(IceCandidatePairState::FAILED);
Destroy();
}
Reland of Do not delete a connection in the turn port with permission error or refresh error. (patchset #1 id:1 of https://codereview.webrtc.org/2090833002/ ) Reason for revert: The Webrtc waterfall indicates that this revert is not necessary. Original issue's description: > Revert of Do not delete a connection in the turn port with permission error or refresh error. (patchset #6 id:260001 of https://codereview.webrtc.org/2068263003/ ) > > Reason for revert: > It broke webrtc builds. > > Original issue's description: > > Do not delete a connection in the turn port with permission error, refresh error, or binding error. > > > > Even if those error happened, the connection may still be able to receive packets for a while. > > If we delete the connections, all packets arriving will be dropped. > > > > BUG=webrtc:6007 > > R=deadbeef@webrtc.org, pthatcher@webrtc.org > > > > Committed: https://crrev.com/3d77deb29c15bfb8f794ef3413837a0ec0f0c131 > > Cr-Commit-Position: refs/heads/master@{#13262} > > NOPRESUBMIT=true > NOTREECHECKS=true > NOTRY=true > TBR=pthatcher@webrtc.org,deadbeef@webrtc.org > # Not skipping CQ checks because original CL landed more than 1 days ago. > BUG=webrtc:6007 > > Committed: https://crrev.com/3159ffae6b1d5cba2ad972bd3d8074ac85f2c7f9 > Cr-Commit-Position: refs/heads/master@{#13265} TBR=pthatcher@webrtc.org,deadbeef@webrtc.org # Skipping CQ checks because original CL landed less than 1 days ago. NOPRESUBMIT=true NOTREECHECKS=true NOTRY=true BUG=webrtc:6007 Review-Url: https://codereview.webrtc.org/2090073003 Cr-Commit-Position: refs/heads/master@{#13266}
2016-06-22 16:26:29 -07:00
void Connection::FailAndPrune() {
set_state(IceCandidatePairState::FAILED);
Reland of Do not delete a connection in the turn port with permission error or refresh error. (patchset #1 id:1 of https://codereview.webrtc.org/2090833002/ ) Reason for revert: The Webrtc waterfall indicates that this revert is not necessary. Original issue's description: > Revert of Do not delete a connection in the turn port with permission error or refresh error. (patchset #6 id:260001 of https://codereview.webrtc.org/2068263003/ ) > > Reason for revert: > It broke webrtc builds. > > Original issue's description: > > Do not delete a connection in the turn port with permission error, refresh error, or binding error. > > > > Even if those error happened, the connection may still be able to receive packets for a while. > > If we delete the connections, all packets arriving will be dropped. > > > > BUG=webrtc:6007 > > R=deadbeef@webrtc.org, pthatcher@webrtc.org > > > > Committed: https://crrev.com/3d77deb29c15bfb8f794ef3413837a0ec0f0c131 > > Cr-Commit-Position: refs/heads/master@{#13262} > > NOPRESUBMIT=true > NOTREECHECKS=true > NOTRY=true > TBR=pthatcher@webrtc.org,deadbeef@webrtc.org > # Not skipping CQ checks because original CL landed more than 1 days ago. > BUG=webrtc:6007 > > Committed: https://crrev.com/3159ffae6b1d5cba2ad972bd3d8074ac85f2c7f9 > Cr-Commit-Position: refs/heads/master@{#13265} TBR=pthatcher@webrtc.org,deadbeef@webrtc.org # Skipping CQ checks because original CL landed less than 1 days ago. NOPRESUBMIT=true NOTREECHECKS=true NOTRY=true BUG=webrtc:6007 Review-Url: https://codereview.webrtc.org/2090073003 Cr-Commit-Position: refs/heads/master@{#13266}
2016-06-22 16:26:29 -07:00
Prune();
}
void Connection::PrintPingsSinceLastResponse(std::string* s, size_t max) {
std::ostringstream oss;
oss << std::boolalpha;
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();
}
void Connection::UpdateState(int64_t now) {
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_,
CONNECTION_WRITE_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)) {
Destroy();
}
}
void Connection::Ping(int64_t now) {
last_ping_sent_ = now;
ConnectionRequest *req = new ConnectionRequest(this);
// 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_;
}
pings_since_last_response_.push_back(SentPing(req->id(), now, nomination));
packet_loss_estimator_.ExpectResponse(req->id(), now);
RTC_LOG(LS_VERBOSE) << ToString()
<< ": Sending STUN ping, id="
<< rtc::hex_encode(req->id())
<< ", nomination=" << nomination_;
requests_.Send(req);
state_ = IceCandidatePairState::IN_PROGRESS;
num_pings_sent_++;
}
void Connection::ReceivedPing() {
last_ping_received_ = rtc::TimeMillis();
UpdateReceiving(last_ping_received_);
}
void Connection::ReceivedPingResponse(int rtt, const std::string& request_id) {
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.
auto iter = std::find_if(
pings_since_last_response_.begin(), pings_since_last_response_.end(),
[request_id](const SentPing& ping) { return ping.id == request_id; });
if (iter != pings_since_last_response_.end() &&
iter->nomination > acked_nomination_) {
acked_nomination_ = iter->nomination;
}
total_round_trip_time_ms_ += rtt;
current_round_trip_time_ms_ = static_cast<uint32_t>(rtt);
pings_since_last_response_.clear();
last_ping_response_received_ = rtc::TimeMillis();
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_++;
}
bool Connection::dead(int64_t now) const {
if (last_received() > 0) {
// If it has ever received anything, we keep it alive until it hasn't
// received anything for DEAD_CONNECTION_RECEIVE_TIMEOUT. 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.
return (now > (last_received() + DEAD_CONNECTION_RECEIVE_TIMEOUT));
}
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);
}
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 {
std::stringstream ss;
ss << std::hex << this;
return ss.str();
}
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 {
const char CONNECT_STATE_ABBREV[2] = {
'-', // not connected (false)
'C', // connected (true)
};
const char RECEIVE_STATE_ABBREV[2] = {
'-', // not receiving (false)
'R', // receiving (true)
};
const char WRITE_STATE_ABBREV[4] = {
'W', // STATE_WRITABLE
'w', // STATE_WRITE_UNRELIABLE
'-', // STATE_WRITE_INIT
'x', // STATE_WRITE_TIMEOUT
};
const std::string ICESTATE[4] = {
"W", // STATE_WAITING
"I", // STATE_INPROGRESS
"S", // STATE_SUCCEEDED
"F" // STATE_FAILED
};
const Candidate& local = local_candidate();
const Candidate& remote = remote_candidate();
std::stringstream ss;
ss << "Conn[" << ToDebugId() << ":" << port_->content_name() << ":"
<< 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()
<< "|" << CONNECT_STATE_ABBREV[connected()]
<< RECEIVE_STATE_ABBREV[receiving()] << WRITE_STATE_ABBREV[write_state()]
<< ICESTATE[static_cast<int>(state())] << "|" << remote_nomination() << "|"
<< nomination() << "|" << priority() << "|";
if (rtt_ < DEFAULT_RTT) {
ss << rtt_ << "]";
} else {
ss << "-]";
}
return ss.str();
}
std::string Connection::ToSensitiveString() const {
return ToString();
}
const webrtc::IceCandidatePairDescription& Connection::ToLogDescription() {
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::LogCandidatePairEvent(webrtc::IceCandidatePairEventType type) {
if (ice_event_log_ == nullptr) {
return;
}
ice_event_log_->LogCandidatePairEvent(type, hash(), ToLogDescription());
}
void Connection::OnConnectionRequestResponse(ConnectionRequest* request,
StunMessage* response) {
// 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 STUN ping response, id="
<< rtc::hex_encode(request->id())
<< ", code=0" // Makes logging easier to parse.
", rtt="
<< rtt << ", pings_since_last_response=" << pings;
}
ReceivedPingResponse(rtt, request->id());
int64_t time_received = rtc::TimeMillis();
packet_loss_estimator_.ReceivedResponse(request->id(), time_received);
stats_.recv_ping_responses++;
LogCandidatePairEvent(
webrtc::IceCandidatePairEventType::kCheckResponseReceived);
MaybeUpdateLocalCandidate(request, response);
}
void Connection::OnConnectionRequestErrorResponse(ConnectionRequest* request,
StunMessage* response) {
int error_code = response->GetErrorCodeValue();
RTC_LOG(LS_WARNING) << ToString()
<< ": Received STUN error response id="
<< rtc::hex_encode(request->id())
<< " code=" << error_code
<< " rtt=" << request->Elapsed();
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_STALE_CREDENTIALS) {
// Race failure, retry
} else if (error_code == STUN_ERROR_ROLE_CONFLICT) {
HandleRoleConflictFromPeer();
} else {
// This is not a valid connection.
RTC_LOG(LS_ERROR) << ToString()
<< ": Received STUN error response, code=" << error_code
<< "; killing connection";
FailAndDestroy();
}
}
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) {
// 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 STUN ping, id=" << rtc::hex_encode(request->id())
<< ", use_candidate=" << use_candidate_attr()
<< ", nomination=" << nomination();
stats_.sent_ping_requests_total++;
LogCandidatePairEvent(webrtc::IceCandidatePairEventType::kCheckSent);
if (stats_.recv_ping_responses == 0) {
stats_.sent_ping_requests_before_first_response++;
}
}
void Connection::HandleRoleConflictFromPeer() {
port_->SignalRoleConflict(port_);
}
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
// rtc::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;
}
}
void Connection::OnMessage(rtc::Message *pmsg) {
RTC_DCHECK(pmsg->message_id == MSG_DELETE);
RTC_LOG(LS_INFO) << "Connection deleted with number of pings sent: "
<< num_pings_sent_;
SignalDestroyed(this);
delete this;
}
int64_t Connection::last_received() const {
return std::max(last_data_received_,
std::max(last_ping_received_, last_ping_response_received_));
}
ConnectionInfo Connection::stats() {
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_.new_connection = !reported_;
stats_.rtt = rtt_;
stats_.local_candidate = local_candidate();
stats_.remote_candidate = remote_candidate();
stats_.key = this;
stats_.state = state_;
stats_.priority = priority();
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_;
return stats_;
}
void Connection::MaybeUpdateLocalCandidate(ConnectionRequest* request,
StunMessage* response) {
// 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 (size_t i = 0; i < port_->Candidates().size(); ++i) {
if (port_->Candidates()[i].address() == addr->GetAddress()) {
if (local_candidate_index_ != i) {
RTC_LOG(LS_INFO) << ToString()
<< ": Updating local candidate type to srflx.";
local_candidate_index_ = i;
// 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);
Candidate new_local_candidate;
new_local_candidate.set_id(id);
new_local_candidate.set_component(local_candidate().component());
new_local_candidate.set_type(PRFLX_PORT_TYPE);
new_local_candidate.set_protocol(local_candidate().protocol());
new_local_candidate.set_address(addr->GetAddress());
new_local_candidate.set_priority(priority);
new_local_candidate.set_username(local_candidate().username());
new_local_candidate.set_password(local_candidate().password());
new_local_candidate.set_network_name(local_candidate().network_name());
new_local_candidate.set_network_type(local_candidate().network_type());
new_local_candidate.set_related_address(local_candidate().address());
new_local_candidate.set_generation(local_candidate().generation());
new_local_candidate.set_foundation(ComputeFoundation(
PRFLX_PORT_TYPE, local_candidate().protocol(),
local_candidate().relay_protocol(), local_candidate().address()));
new_local_candidate.set_network_id(local_candidate().network_id());
new_local_candidate.set_network_cost(local_candidate().network_cost());
// Change the local candidate of this Connection to the new prflx candidate.
RTC_LOG(LS_INFO) << ToString()
<< ": Updating local candidate type to prflx.";
local_candidate_index_ = port_->AddPrflxCandidate(new_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 {
return rtt_samples_ > (RTT_RATIO + 1);
}
bool Connection::missing_responses(int64_t now) const {
if (pings_since_last_response_.empty()) {
return false;
}
int64_t waiting = now - pings_since_last_response_[0].sent_time;
return waiting > 2 * rtt();
}
ProxyConnection::ProxyConnection(Port* port,
size_t index,
const Candidate& remote_candidate)
: Connection(port, index, remote_candidate) {}
int ProxyConnection::Send(const void* data, size_t size,
const rtc::PacketOptions& options) {
stats_.sent_total_packets++;
int sent = port_->SendTo(data, size, remote_candidate_.address(),
options, true);
if (sent <= 0) {
RTC_DCHECK(sent < 0);
error_ = port_->GetError();
stats_.sent_discarded_packets++;
} else {
send_rate_tracker_.AddSamples(sent);
}
return sent;
}
int ProxyConnection::GetError() {
return error_;
}
} // namespace cricket