933 lines
33 KiB
C++
933 lines
33 KiB
C++
/*
|
|
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license
|
|
* that can be found in the LICENSE file in the root of the source
|
|
* tree. An additional intellectual property rights grant can be found
|
|
* in the file PATENTS. All contributing project authors may
|
|
* be found in the AUTHORS file in the root of the source tree.
|
|
*/
|
|
|
|
#include "p2p/base/port.h"
|
|
|
|
#include <math.h>
|
|
|
|
#include <algorithm>
|
|
#include <memory>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "absl/algorithm/container.h"
|
|
#include "absl/strings/match.h"
|
|
#include "p2p/base/connection.h"
|
|
#include "p2p/base/port_allocator.h"
|
|
#include "rtc_base/checks.h"
|
|
#include "rtc_base/crc32.h"
|
|
#include "rtc_base/helpers.h"
|
|
#include "rtc_base/logging.h"
|
|
#include "rtc_base/mdns_responder_interface.h"
|
|
#include "rtc_base/message_digest.h"
|
|
#include "rtc_base/network.h"
|
|
#include "rtc_base/numerics/safe_minmax.h"
|
|
#include "rtc_base/string_encode.h"
|
|
#include "rtc_base/string_utils.h"
|
|
#include "rtc_base/strings/string_builder.h"
|
|
#include "rtc_base/third_party/base64/base64.h"
|
|
#include "rtc_base/trace_event.h"
|
|
#include "system_wrappers/include/field_trial.h"
|
|
|
|
namespace {
|
|
|
|
rtc::PacketInfoProtocolType ConvertProtocolTypeToPacketInfoProtocolType(
|
|
cricket::ProtocolType type) {
|
|
switch (type) {
|
|
case cricket::ProtocolType::PROTO_UDP:
|
|
return rtc::PacketInfoProtocolType::kUdp;
|
|
case cricket::ProtocolType::PROTO_TCP:
|
|
return rtc::PacketInfoProtocolType::kTcp;
|
|
case cricket::ProtocolType::PROTO_SSLTCP:
|
|
return rtc::PacketInfoProtocolType::kSsltcp;
|
|
case cricket::ProtocolType::PROTO_TLS:
|
|
return rtc::PacketInfoProtocolType::kTls;
|
|
default:
|
|
return rtc::PacketInfoProtocolType::kUnknown;
|
|
}
|
|
}
|
|
|
|
// The delay before we begin checking if this port is useless. We set
|
|
// it to a little higher than a total STUN timeout.
|
|
const int kPortTimeoutDelay = cricket::STUN_TOTAL_TIMEOUT + 5000;
|
|
|
|
} // namespace
|
|
|
|
namespace cricket {
|
|
|
|
using webrtc::RTCError;
|
|
using webrtc::RTCErrorType;
|
|
|
|
// TODO(ronghuawu): Use "local", "srflx", "prflx" and "relay". But this requires
|
|
// the signaling part be updated correspondingly as well.
|
|
const char LOCAL_PORT_TYPE[] = "local";
|
|
const char STUN_PORT_TYPE[] = "stun";
|
|
const char PRFLX_PORT_TYPE[] = "prflx";
|
|
const char RELAY_PORT_TYPE[] = "relay";
|
|
|
|
static const char* const PROTO_NAMES[] = {UDP_PROTOCOL_NAME, TCP_PROTOCOL_NAME,
|
|
SSLTCP_PROTOCOL_NAME,
|
|
TLS_PROTOCOL_NAME};
|
|
|
|
const char* ProtoToString(ProtocolType proto) {
|
|
return PROTO_NAMES[proto];
|
|
}
|
|
|
|
bool StringToProto(const char* value, ProtocolType* proto) {
|
|
for (size_t i = 0; i <= PROTO_LAST; ++i) {
|
|
if (absl::EqualsIgnoreCase(PROTO_NAMES[i], value)) {
|
|
*proto = static_cast<ProtocolType>(i);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// RFC 6544, TCP candidate encoding rules.
|
|
const int DISCARD_PORT = 9;
|
|
const char TCPTYPE_ACTIVE_STR[] = "active";
|
|
const char TCPTYPE_PASSIVE_STR[] = "passive";
|
|
const char TCPTYPE_SIMOPEN_STR[] = "so";
|
|
|
|
std::string Port::ComputeFoundation(const std::string& type,
|
|
const std::string& protocol,
|
|
const std::string& relay_protocol,
|
|
const rtc::SocketAddress& base_address) {
|
|
rtc::StringBuilder sb;
|
|
sb << type << base_address.ipaddr().ToString() << protocol << relay_protocol;
|
|
return rtc::ToString(rtc::ComputeCrc32(sb.Release()));
|
|
}
|
|
|
|
Port::Port(rtc::Thread* thread,
|
|
const std::string& type,
|
|
rtc::PacketSocketFactory* factory,
|
|
rtc::Network* network,
|
|
const std::string& username_fragment,
|
|
const std::string& password)
|
|
: thread_(thread),
|
|
factory_(factory),
|
|
type_(type),
|
|
send_retransmit_count_attribute_(false),
|
|
network_(network),
|
|
min_port_(0),
|
|
max_port_(0),
|
|
component_(ICE_CANDIDATE_COMPONENT_DEFAULT),
|
|
generation_(0),
|
|
ice_username_fragment_(username_fragment),
|
|
password_(password),
|
|
timeout_delay_(kPortTimeoutDelay),
|
|
enable_port_packets_(false),
|
|
ice_role_(ICEROLE_UNKNOWN),
|
|
tiebreaker_(0),
|
|
shared_socket_(true),
|
|
weak_factory_(this) {
|
|
RTC_DCHECK(factory_ != NULL);
|
|
Construct();
|
|
}
|
|
|
|
Port::Port(rtc::Thread* thread,
|
|
const std::string& type,
|
|
rtc::PacketSocketFactory* factory,
|
|
rtc::Network* network,
|
|
uint16_t min_port,
|
|
uint16_t max_port,
|
|
const std::string& username_fragment,
|
|
const std::string& password)
|
|
: thread_(thread),
|
|
factory_(factory),
|
|
type_(type),
|
|
send_retransmit_count_attribute_(false),
|
|
network_(network),
|
|
min_port_(min_port),
|
|
max_port_(max_port),
|
|
component_(ICE_CANDIDATE_COMPONENT_DEFAULT),
|
|
generation_(0),
|
|
ice_username_fragment_(username_fragment),
|
|
password_(password),
|
|
timeout_delay_(kPortTimeoutDelay),
|
|
enable_port_packets_(false),
|
|
ice_role_(ICEROLE_UNKNOWN),
|
|
tiebreaker_(0),
|
|
shared_socket_(false),
|
|
weak_factory_(this) {
|
|
RTC_DCHECK(factory_ != NULL);
|
|
Construct();
|
|
}
|
|
|
|
void Port::Construct() {
|
|
// TODO(pthatcher): Remove this old behavior once we're sure no one
|
|
// relies on it. If the username_fragment and password are empty,
|
|
// we should just create one.
|
|
if (ice_username_fragment_.empty()) {
|
|
RTC_DCHECK(password_.empty());
|
|
ice_username_fragment_ = rtc::CreateRandomString(ICE_UFRAG_LENGTH);
|
|
password_ = rtc::CreateRandomString(ICE_PWD_LENGTH);
|
|
}
|
|
network_->SignalTypeChanged.connect(this, &Port::OnNetworkTypeChanged);
|
|
network_cost_ = network_->GetCost();
|
|
|
|
thread_->PostDelayed(RTC_FROM_HERE, timeout_delay_, this,
|
|
MSG_DESTROY_IF_DEAD);
|
|
RTC_LOG(LS_INFO) << ToString() << ": Port created with network cost "
|
|
<< network_cost_;
|
|
}
|
|
|
|
Port::~Port() {
|
|
RTC_DCHECK_RUN_ON(thread_);
|
|
CancelPendingTasks();
|
|
|
|
// Delete all of the remaining connections. We copy the list up front
|
|
// because each deletion will cause it to be modified.
|
|
|
|
std::vector<Connection*> list;
|
|
|
|
AddressMap::iterator iter = connections_.begin();
|
|
while (iter != connections_.end()) {
|
|
list.push_back(iter->second);
|
|
++iter;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < list.size(); i++)
|
|
delete list[i];
|
|
}
|
|
|
|
const std::string& Port::Type() const {
|
|
return type_;
|
|
}
|
|
rtc::Network* Port::Network() const {
|
|
return network_;
|
|
}
|
|
|
|
IceRole Port::GetIceRole() const {
|
|
return ice_role_;
|
|
}
|
|
|
|
void Port::SetIceRole(IceRole role) {
|
|
ice_role_ = role;
|
|
}
|
|
|
|
void Port::SetIceTiebreaker(uint64_t tiebreaker) {
|
|
tiebreaker_ = tiebreaker;
|
|
}
|
|
uint64_t Port::IceTiebreaker() const {
|
|
return tiebreaker_;
|
|
}
|
|
|
|
bool Port::SharedSocket() const {
|
|
return shared_socket_;
|
|
}
|
|
|
|
void Port::SetIceParameters(int component,
|
|
const std::string& username_fragment,
|
|
const std::string& password) {
|
|
component_ = component;
|
|
ice_username_fragment_ = username_fragment;
|
|
password_ = password;
|
|
for (Candidate& c : candidates_) {
|
|
c.set_component(component);
|
|
c.set_username(username_fragment);
|
|
c.set_password(password);
|
|
}
|
|
}
|
|
|
|
const std::vector<Candidate>& Port::Candidates() const {
|
|
return candidates_;
|
|
}
|
|
|
|
Connection* Port::GetConnection(const rtc::SocketAddress& remote_addr) {
|
|
AddressMap::const_iterator iter = connections_.find(remote_addr);
|
|
if (iter != connections_.end())
|
|
return iter->second;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
void Port::AddAddress(const rtc::SocketAddress& address,
|
|
const rtc::SocketAddress& base_address,
|
|
const rtc::SocketAddress& related_address,
|
|
const std::string& protocol,
|
|
const std::string& relay_protocol,
|
|
const std::string& tcptype,
|
|
const std::string& type,
|
|
uint32_t type_preference,
|
|
uint32_t relay_preference,
|
|
const std::string& url,
|
|
bool is_final) {
|
|
if (protocol == TCP_PROTOCOL_NAME && type == LOCAL_PORT_TYPE) {
|
|
RTC_DCHECK(!tcptype.empty());
|
|
}
|
|
|
|
std::string foundation =
|
|
ComputeFoundation(type, protocol, relay_protocol, base_address);
|
|
Candidate c(component_, protocol, address, 0U, username_fragment(), password_,
|
|
type, generation_, foundation, network_->id(), network_cost_);
|
|
c.set_priority(
|
|
c.GetPriority(type_preference, network_->preference(), relay_preference));
|
|
c.set_relay_protocol(relay_protocol);
|
|
c.set_tcptype(tcptype);
|
|
c.set_network_name(network_->name());
|
|
c.set_network_type(network_->type());
|
|
c.set_url(url);
|
|
c.set_related_address(related_address);
|
|
|
|
bool pending = MaybeObfuscateAddress(&c, type, is_final);
|
|
|
|
if (!pending) {
|
|
FinishAddingAddress(c, is_final);
|
|
}
|
|
}
|
|
|
|
bool Port::MaybeObfuscateAddress(Candidate* c,
|
|
const std::string& type,
|
|
bool is_final) {
|
|
// TODO(bugs.webrtc.org/9723): Use a config to control the feature of IP
|
|
// handling with mDNS.
|
|
if (network_->GetMdnsResponder() == nullptr) {
|
|
return false;
|
|
}
|
|
if (type != LOCAL_PORT_TYPE) {
|
|
return false;
|
|
}
|
|
|
|
auto copy = *c;
|
|
auto weak_ptr = weak_factory_.GetWeakPtr();
|
|
auto callback = [weak_ptr, copy, is_final](const rtc::IPAddress& addr,
|
|
const std::string& name) mutable {
|
|
RTC_DCHECK(copy.address().ipaddr() == addr);
|
|
rtc::SocketAddress hostname_address(name, copy.address().port());
|
|
// In Port and Connection, we need the IP address information to
|
|
// correctly handle the update of candidate type to prflx. The removal
|
|
// of IP address when signaling this candidate will take place in
|
|
// BasicPortAllocatorSession::OnCandidateReady, via SanitizeCandidate.
|
|
hostname_address.SetResolvedIP(addr);
|
|
copy.set_address(hostname_address);
|
|
copy.set_related_address(rtc::SocketAddress());
|
|
if (weak_ptr != nullptr) {
|
|
weak_ptr->set_mdns_name_registration_status(
|
|
MdnsNameRegistrationStatus::kCompleted);
|
|
weak_ptr->FinishAddingAddress(copy, is_final);
|
|
}
|
|
};
|
|
set_mdns_name_registration_status(MdnsNameRegistrationStatus::kInProgress);
|
|
network_->GetMdnsResponder()->CreateNameForAddress(copy.address().ipaddr(),
|
|
callback);
|
|
return true;
|
|
}
|
|
|
|
void Port::FinishAddingAddress(const Candidate& c, bool is_final) {
|
|
candidates_.push_back(c);
|
|
SignalCandidateReady(this, c);
|
|
|
|
PostAddAddress(is_final);
|
|
}
|
|
|
|
void Port::PostAddAddress(bool is_final) {
|
|
if (is_final) {
|
|
SignalPortComplete(this);
|
|
}
|
|
}
|
|
|
|
void Port::AddOrReplaceConnection(Connection* conn) {
|
|
auto ret = connections_.insert(
|
|
std::make_pair(conn->remote_candidate().address(), conn));
|
|
// If there is a different connection on the same remote address, replace
|
|
// it with the new one and destroy the old one.
|
|
if (ret.second == false && ret.first->second != conn) {
|
|
RTC_LOG(LS_WARNING)
|
|
<< ToString()
|
|
<< ": A new connection was created on an existing remote address. "
|
|
"New remote candidate: "
|
|
<< conn->remote_candidate().ToSensitiveString();
|
|
ret.first->second->SignalDestroyed.disconnect(this);
|
|
ret.first->second->Destroy();
|
|
ret.first->second = conn;
|
|
}
|
|
conn->SignalDestroyed.connect(this, &Port::OnConnectionDestroyed);
|
|
SignalConnectionCreated(this, conn);
|
|
}
|
|
|
|
void Port::OnReadPacket(const char* data,
|
|
size_t size,
|
|
const rtc::SocketAddress& addr,
|
|
ProtocolType proto) {
|
|
// If the user has enabled port packets, just hand this over.
|
|
if (enable_port_packets_) {
|
|
SignalReadPacket(this, data, size, addr);
|
|
return;
|
|
}
|
|
|
|
// If this is an authenticated STUN request, then signal unknown address and
|
|
// send back a proper binding response.
|
|
std::unique_ptr<IceMessage> msg;
|
|
std::string remote_username;
|
|
if (!GetStunMessage(data, size, addr, &msg, &remote_username)) {
|
|
RTC_LOG(LS_ERROR) << ToString()
|
|
<< ": Received non-STUN packet from unknown address: "
|
|
<< addr.ToSensitiveString();
|
|
} else if (!msg) {
|
|
// STUN message handled already
|
|
} else if (msg->type() == STUN_BINDING_REQUEST) {
|
|
RTC_LOG(LS_INFO) << "Received " << StunMethodToString(msg->type())
|
|
<< " id=" << rtc::hex_encode(msg->transaction_id())
|
|
<< " from unknown address " << addr.ToSensitiveString();
|
|
// We need to signal an unknown address before we handle any role conflict
|
|
// below. Otherwise there would be no candidate pair and TURN entry created
|
|
// to send the error response in case of a role conflict.
|
|
SignalUnknownAddress(this, addr, proto, msg.get(), remote_username, false);
|
|
// Check for role conflicts.
|
|
if (!MaybeIceRoleConflict(addr, msg.get(), remote_username)) {
|
|
RTC_LOG(LS_INFO) << "Received conflicting role from the peer.";
|
|
return;
|
|
}
|
|
} else if (msg->type() == GOOG_PING_REQUEST) {
|
|
// This is a PING sent to a connection that was destroyed.
|
|
// Send back that this is the case and a authenticated BINDING
|
|
// is needed.
|
|
SendBindingErrorResponse(msg.get(), addr, STUN_ERROR_BAD_REQUEST,
|
|
STUN_ERROR_REASON_BAD_REQUEST);
|
|
} else {
|
|
// NOTE(tschmelcher): STUN_BINDING_RESPONSE is benign. It occurs if we
|
|
// pruned a connection for this port while it had STUN requests in flight,
|
|
// because we then get back responses for them, which this code correctly
|
|
// does not handle.
|
|
if (msg->type() != STUN_BINDING_RESPONSE &&
|
|
msg->type() != GOOG_PING_RESPONSE &&
|
|
msg->type() != GOOG_PING_ERROR_RESPONSE) {
|
|
RTC_LOG(LS_ERROR) << ToString()
|
|
<< ": Received unexpected STUN message type: "
|
|
<< msg->type() << " from unknown address: "
|
|
<< addr.ToSensitiveString();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Port::OnReadyToSend() {
|
|
AddressMap::iterator iter = connections_.begin();
|
|
for (; iter != connections_.end(); ++iter) {
|
|
iter->second->OnReadyToSend();
|
|
}
|
|
}
|
|
|
|
size_t Port::AddPrflxCandidate(const Candidate& local) {
|
|
candidates_.push_back(local);
|
|
return (candidates_.size() - 1);
|
|
}
|
|
|
|
bool Port::GetStunMessage(const char* data,
|
|
size_t size,
|
|
const rtc::SocketAddress& addr,
|
|
std::unique_ptr<IceMessage>* out_msg,
|
|
std::string* out_username) {
|
|
// NOTE: This could clearly be optimized to avoid allocating any memory.
|
|
// However, at the data rates we'll be looking at on the client side,
|
|
// this probably isn't worth worrying about.
|
|
RTC_DCHECK(out_msg != NULL);
|
|
RTC_DCHECK(out_username != NULL);
|
|
out_username->clear();
|
|
|
|
// Don't bother parsing the packet if we can tell it's not STUN.
|
|
// In ICE mode, all STUN packets will have a valid fingerprint.
|
|
// Except GOOG_PING_REQUEST/RESPONSE that does not send fingerprint.
|
|
int types[] = {GOOG_PING_REQUEST, GOOG_PING_RESPONSE,
|
|
GOOG_PING_ERROR_RESPONSE};
|
|
if (!StunMessage::IsStunMethod(types, data, size) &&
|
|
!StunMessage::ValidateFingerprint(data, size)) {
|
|
return false;
|
|
}
|
|
|
|
// Parse the request message. If the packet is not a complete and correct
|
|
// STUN message, then ignore it.
|
|
std::unique_ptr<IceMessage> stun_msg(new IceMessage());
|
|
rtc::ByteBufferReader buf(data, size);
|
|
if (!stun_msg->Read(&buf) || (buf.Length() > 0)) {
|
|
return false;
|
|
}
|
|
|
|
// Get list of attributes in the "comprehension-required" range that were not
|
|
// comprehended. If one or more is found, the behavior differs based on the
|
|
// type of the incoming message; see below.
|
|
std::vector<uint16_t> unknown_attributes =
|
|
stun_msg->GetNonComprehendedAttributes();
|
|
|
|
if (stun_msg->type() == STUN_BINDING_REQUEST) {
|
|
// Check for the presence of USERNAME and MESSAGE-INTEGRITY (if ICE) first.
|
|
// If not present, fail with a 400 Bad Request.
|
|
if (!stun_msg->GetByteString(STUN_ATTR_USERNAME) ||
|
|
!stun_msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY)) {
|
|
RTC_LOG(LS_ERROR) << ToString() << ": Received "
|
|
<< StunMethodToString(stun_msg->type())
|
|
<< " without username/M-I from: "
|
|
<< addr.ToSensitiveString();
|
|
SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_BAD_REQUEST,
|
|
STUN_ERROR_REASON_BAD_REQUEST);
|
|
return true;
|
|
}
|
|
|
|
// If the username is bad or unknown, fail with a 401 Unauthorized.
|
|
std::string local_ufrag;
|
|
std::string remote_ufrag;
|
|
if (!ParseStunUsername(stun_msg.get(), &local_ufrag, &remote_ufrag) ||
|
|
local_ufrag != username_fragment()) {
|
|
RTC_LOG(LS_ERROR) << ToString() << ": Received "
|
|
<< StunMethodToString(stun_msg->type())
|
|
<< " with bad local username " << local_ufrag
|
|
<< " from " << addr.ToSensitiveString();
|
|
SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_UNAUTHORIZED,
|
|
STUN_ERROR_REASON_UNAUTHORIZED);
|
|
return true;
|
|
}
|
|
|
|
// If ICE, and the MESSAGE-INTEGRITY is bad, fail with a 401 Unauthorized
|
|
if (stun_msg->ValidateMessageIntegrity(password_) !=
|
|
StunMessage::IntegrityStatus::kIntegrityOk) {
|
|
RTC_LOG(LS_ERROR) << ToString() << ": Received "
|
|
<< StunMethodToString(stun_msg->type())
|
|
<< " with bad M-I from " << addr.ToSensitiveString()
|
|
<< ", password_=" << password_;
|
|
SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_UNAUTHORIZED,
|
|
STUN_ERROR_REASON_UNAUTHORIZED);
|
|
return true;
|
|
}
|
|
|
|
// If a request contains unknown comprehension-required attributes, reply
|
|
// with an error. See RFC5389 section 7.3.1.
|
|
if (!unknown_attributes.empty()) {
|
|
SendUnknownAttributesErrorResponse(stun_msg.get(), addr,
|
|
unknown_attributes);
|
|
return true;
|
|
}
|
|
|
|
out_username->assign(remote_ufrag);
|
|
} else if ((stun_msg->type() == STUN_BINDING_RESPONSE) ||
|
|
(stun_msg->type() == STUN_BINDING_ERROR_RESPONSE)) {
|
|
if (stun_msg->type() == STUN_BINDING_ERROR_RESPONSE) {
|
|
if (const StunErrorCodeAttribute* error_code = stun_msg->GetErrorCode()) {
|
|
RTC_LOG(LS_ERROR) << ToString() << ": Received "
|
|
<< StunMethodToString(stun_msg->type())
|
|
<< ": class=" << error_code->eclass()
|
|
<< " number=" << error_code->number() << " reason='"
|
|
<< error_code->reason() << "' from "
|
|
<< addr.ToSensitiveString();
|
|
// Return message to allow error-specific processing
|
|
} else {
|
|
RTC_LOG(LS_ERROR) << ToString() << ": Received "
|
|
<< StunMethodToString(stun_msg->type())
|
|
<< " without a error code from "
|
|
<< addr.ToSensitiveString();
|
|
return true;
|
|
}
|
|
}
|
|
// If a response contains unknown comprehension-required attributes, it's
|
|
// simply discarded and the transaction is considered failed. See RFC5389
|
|
// sections 7.3.3 and 7.3.4.
|
|
if (!unknown_attributes.empty()) {
|
|
RTC_LOG(LS_ERROR) << ToString()
|
|
<< ": Discarding STUN response due to unknown "
|
|
"comprehension-required attribute";
|
|
return true;
|
|
}
|
|
// NOTE: Username should not be used in verifying response messages.
|
|
out_username->clear();
|
|
} else if (stun_msg->type() == STUN_BINDING_INDICATION) {
|
|
RTC_LOG(LS_VERBOSE) << ToString() << ": Received "
|
|
<< StunMethodToString(stun_msg->type()) << ": from "
|
|
<< addr.ToSensitiveString();
|
|
out_username->clear();
|
|
|
|
// If an indication contains unknown comprehension-required attributes,[]
|
|
// it's simply discarded. See RFC5389 section 7.3.2.
|
|
if (!unknown_attributes.empty()) {
|
|
RTC_LOG(LS_ERROR) << ToString()
|
|
<< ": Discarding STUN indication due to "
|
|
"unknown comprehension-required attribute";
|
|
return true;
|
|
}
|
|
// No stun attributes will be verified, if it's stun indication message.
|
|
// Returning from end of the this method.
|
|
} else if (stun_msg->type() == GOOG_PING_REQUEST) {
|
|
if (stun_msg->ValidateMessageIntegrity(password_) !=
|
|
StunMessage::IntegrityStatus::kIntegrityOk) {
|
|
RTC_LOG(LS_ERROR) << ToString() << ": Received "
|
|
<< StunMethodToString(stun_msg->type())
|
|
<< " with bad M-I from " << addr.ToSensitiveString()
|
|
<< ", password_=" << password_;
|
|
SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_UNAUTHORIZED,
|
|
STUN_ERROR_REASON_UNAUTHORIZED);
|
|
return true;
|
|
}
|
|
RTC_LOG(LS_VERBOSE) << ToString() << ": Received "
|
|
<< StunMethodToString(stun_msg->type()) << " from "
|
|
<< addr.ToSensitiveString();
|
|
out_username->clear();
|
|
} else if (stun_msg->type() == GOOG_PING_RESPONSE ||
|
|
stun_msg->type() == GOOG_PING_ERROR_RESPONSE) {
|
|
// note: the MessageIntegrity32 will be verified in Connection.cc
|
|
RTC_LOG(LS_VERBOSE) << ToString() << ": Received "
|
|
<< StunMethodToString(stun_msg->type()) << " from "
|
|
<< addr.ToSensitiveString();
|
|
out_username->clear();
|
|
} else {
|
|
RTC_LOG(LS_ERROR) << ToString()
|
|
<< ": Received STUN packet with invalid type ("
|
|
<< stun_msg->type() << ") from "
|
|
<< addr.ToSensitiveString();
|
|
return true;
|
|
}
|
|
|
|
// Return the STUN message found.
|
|
*out_msg = std::move(stun_msg);
|
|
return true;
|
|
}
|
|
|
|
bool Port::IsCompatibleAddress(const rtc::SocketAddress& addr) {
|
|
// Get a representative IP for the Network this port is configured to use.
|
|
rtc::IPAddress ip = network_->GetBestIP();
|
|
// We use single-stack sockets, so families must match.
|
|
if (addr.family() != ip.family()) {
|
|
return false;
|
|
}
|
|
// Link-local IPv6 ports can only connect to other link-local IPv6 ports.
|
|
if (ip.family() == AF_INET6 &&
|
|
(IPIsLinkLocal(ip) != IPIsLinkLocal(addr.ipaddr()))) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
rtc::DiffServCodePoint Port::StunDscpValue() const {
|
|
// By default, inherit from whatever the MediaChannel sends.
|
|
return rtc::DSCP_NO_CHANGE;
|
|
}
|
|
|
|
void Port::set_timeout_delay(int delay) {
|
|
RTC_DCHECK_RUN_ON(thread_);
|
|
// Although this method is meant to only be used by tests, some downstream
|
|
// projects have started using it. Ideally we should update our tests to not
|
|
// require to modify this state and instead use a testing harness that allows
|
|
// adjusting the clock and then just use the kPortTimeoutDelay constant
|
|
// directly.
|
|
timeout_delay_ = delay;
|
|
}
|
|
|
|
bool Port::ParseStunUsername(const StunMessage* stun_msg,
|
|
std::string* local_ufrag,
|
|
std::string* remote_ufrag) const {
|
|
// The packet must include a username that either begins or ends with our
|
|
// fragment. It should begin with our fragment if it is a request and it
|
|
// should end with our fragment if it is a response.
|
|
local_ufrag->clear();
|
|
remote_ufrag->clear();
|
|
const StunByteStringAttribute* username_attr =
|
|
stun_msg->GetByteString(STUN_ATTR_USERNAME);
|
|
if (username_attr == NULL)
|
|
return false;
|
|
|
|
// RFRAG:LFRAG
|
|
const std::string username = username_attr->GetString();
|
|
size_t colon_pos = username.find(':');
|
|
if (colon_pos == std::string::npos) {
|
|
return false;
|
|
}
|
|
|
|
*local_ufrag = username.substr(0, colon_pos);
|
|
*remote_ufrag = username.substr(colon_pos + 1, username.size());
|
|
return true;
|
|
}
|
|
|
|
bool Port::MaybeIceRoleConflict(const rtc::SocketAddress& addr,
|
|
IceMessage* stun_msg,
|
|
const std::string& remote_ufrag) {
|
|
// Validate ICE_CONTROLLING or ICE_CONTROLLED attributes.
|
|
bool ret = true;
|
|
IceRole remote_ice_role = ICEROLE_UNKNOWN;
|
|
uint64_t remote_tiebreaker = 0;
|
|
const StunUInt64Attribute* stun_attr =
|
|
stun_msg->GetUInt64(STUN_ATTR_ICE_CONTROLLING);
|
|
if (stun_attr) {
|
|
remote_ice_role = ICEROLE_CONTROLLING;
|
|
remote_tiebreaker = stun_attr->value();
|
|
}
|
|
|
|
// If `remote_ufrag` is same as port local username fragment and
|
|
// tie breaker value received in the ping message matches port
|
|
// tiebreaker value this must be a loopback call.
|
|
// We will treat this as valid scenario.
|
|
if (remote_ice_role == ICEROLE_CONTROLLING &&
|
|
username_fragment() == remote_ufrag &&
|
|
remote_tiebreaker == IceTiebreaker()) {
|
|
return true;
|
|
}
|
|
|
|
stun_attr = stun_msg->GetUInt64(STUN_ATTR_ICE_CONTROLLED);
|
|
if (stun_attr) {
|
|
remote_ice_role = ICEROLE_CONTROLLED;
|
|
remote_tiebreaker = stun_attr->value();
|
|
}
|
|
|
|
switch (ice_role_) {
|
|
case ICEROLE_CONTROLLING:
|
|
if (ICEROLE_CONTROLLING == remote_ice_role) {
|
|
if (remote_tiebreaker >= tiebreaker_) {
|
|
SignalRoleConflict(this);
|
|
} else {
|
|
// Send Role Conflict (487) error response.
|
|
SendBindingErrorResponse(stun_msg, addr, STUN_ERROR_ROLE_CONFLICT,
|
|
STUN_ERROR_REASON_ROLE_CONFLICT);
|
|
ret = false;
|
|
}
|
|
}
|
|
break;
|
|
case ICEROLE_CONTROLLED:
|
|
if (ICEROLE_CONTROLLED == remote_ice_role) {
|
|
if (remote_tiebreaker < tiebreaker_) {
|
|
SignalRoleConflict(this);
|
|
} else {
|
|
// Send Role Conflict (487) error response.
|
|
SendBindingErrorResponse(stun_msg, addr, STUN_ERROR_ROLE_CONFLICT,
|
|
STUN_ERROR_REASON_ROLE_CONFLICT);
|
|
ret = false;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
RTC_DCHECK_NOTREACHED();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void Port::CreateStunUsername(const std::string& remote_username,
|
|
std::string* stun_username_attr_str) const {
|
|
stun_username_attr_str->clear();
|
|
*stun_username_attr_str = remote_username;
|
|
stun_username_attr_str->append(":");
|
|
stun_username_attr_str->append(username_fragment());
|
|
}
|
|
|
|
bool Port::HandleIncomingPacket(rtc::AsyncPacketSocket* socket,
|
|
const char* data,
|
|
size_t size,
|
|
const rtc::SocketAddress& remote_addr,
|
|
int64_t packet_time_us) {
|
|
RTC_DCHECK_NOTREACHED();
|
|
return false;
|
|
}
|
|
|
|
bool Port::CanHandleIncomingPacketsFrom(const rtc::SocketAddress&) const {
|
|
return false;
|
|
}
|
|
|
|
void Port::SendBindingErrorResponse(StunMessage* request,
|
|
const rtc::SocketAddress& addr,
|
|
int error_code,
|
|
const std::string& reason) {
|
|
RTC_DCHECK(request->type() == STUN_BINDING_REQUEST ||
|
|
request->type() == GOOG_PING_REQUEST);
|
|
|
|
// Fill in the response message.
|
|
StunMessage response;
|
|
if (request->type() == STUN_BINDING_REQUEST) {
|
|
response.SetType(STUN_BINDING_ERROR_RESPONSE);
|
|
} else {
|
|
response.SetType(GOOG_PING_ERROR_RESPONSE);
|
|
}
|
|
response.SetTransactionID(request->transaction_id());
|
|
|
|
// When doing GICE, we need to write out the error code incorrectly to
|
|
// maintain backwards compatiblility.
|
|
auto error_attr = StunAttribute::CreateErrorCode();
|
|
error_attr->SetCode(error_code);
|
|
error_attr->SetReason(reason);
|
|
response.AddAttribute(std::move(error_attr));
|
|
|
|
// Per Section 10.1.2, certain error cases don't get a MESSAGE-INTEGRITY,
|
|
// because we don't have enough information to determine the shared secret.
|
|
if (error_code != STUN_ERROR_BAD_REQUEST &&
|
|
error_code != STUN_ERROR_UNAUTHORIZED &&
|
|
request->type() != GOOG_PING_REQUEST) {
|
|
if (request->type() == STUN_BINDING_REQUEST) {
|
|
response.AddMessageIntegrity(password_);
|
|
} else {
|
|
response.AddMessageIntegrity32(password_);
|
|
}
|
|
}
|
|
|
|
if (request->type() == STUN_BINDING_REQUEST) {
|
|
response.AddFingerprint();
|
|
}
|
|
|
|
// Send the response message.
|
|
rtc::ByteBufferWriter buf;
|
|
response.Write(&buf);
|
|
rtc::PacketOptions options(StunDscpValue());
|
|
options.info_signaled_after_sent.packet_type =
|
|
rtc::PacketType::kIceConnectivityCheckResponse;
|
|
SendTo(buf.Data(), buf.Length(), addr, options, false);
|
|
RTC_LOG(LS_INFO) << ToString() << ": Sending STUN "
|
|
<< StunMethodToString(response.type())
|
|
<< ": reason=" << reason << " to "
|
|
<< addr.ToSensitiveString();
|
|
}
|
|
|
|
void Port::SendUnknownAttributesErrorResponse(
|
|
StunMessage* request,
|
|
const rtc::SocketAddress& addr,
|
|
const std::vector<uint16_t>& unknown_types) {
|
|
RTC_DCHECK(request->type() == STUN_BINDING_REQUEST);
|
|
|
|
// Fill in the response message.
|
|
StunMessage response;
|
|
response.SetType(STUN_BINDING_ERROR_RESPONSE);
|
|
response.SetTransactionID(request->transaction_id());
|
|
|
|
auto error_attr = StunAttribute::CreateErrorCode();
|
|
error_attr->SetCode(STUN_ERROR_UNKNOWN_ATTRIBUTE);
|
|
error_attr->SetReason(STUN_ERROR_REASON_UNKNOWN_ATTRIBUTE);
|
|
response.AddAttribute(std::move(error_attr));
|
|
|
|
std::unique_ptr<StunUInt16ListAttribute> unknown_attr =
|
|
StunAttribute::CreateUnknownAttributes();
|
|
for (uint16_t type : unknown_types) {
|
|
unknown_attr->AddType(type);
|
|
}
|
|
response.AddAttribute(std::move(unknown_attr));
|
|
|
|
response.AddMessageIntegrity(password_);
|
|
response.AddFingerprint();
|
|
|
|
// Send the response message.
|
|
rtc::ByteBufferWriter buf;
|
|
response.Write(&buf);
|
|
rtc::PacketOptions options(StunDscpValue());
|
|
options.info_signaled_after_sent.packet_type =
|
|
rtc::PacketType::kIceConnectivityCheckResponse;
|
|
SendTo(buf.Data(), buf.Length(), addr, options, false);
|
|
RTC_LOG(LS_ERROR) << ToString() << ": Sending STUN binding error: reason="
|
|
<< STUN_ERROR_UNKNOWN_ATTRIBUTE << " to "
|
|
<< addr.ToSensitiveString();
|
|
}
|
|
|
|
void Port::KeepAliveUntilPruned() {
|
|
// If it is pruned, we won't bring it up again.
|
|
if (state_ == State::INIT) {
|
|
state_ = State::KEEP_ALIVE_UNTIL_PRUNED;
|
|
}
|
|
}
|
|
|
|
void Port::Prune() {
|
|
state_ = State::PRUNED;
|
|
thread_->Post(RTC_FROM_HERE, this, MSG_DESTROY_IF_DEAD);
|
|
}
|
|
|
|
// Call to stop any currently pending operations from running.
|
|
void Port::CancelPendingTasks() {
|
|
TRACE_EVENT0("webrtc", "Port::CancelPendingTasks");
|
|
RTC_DCHECK_RUN_ON(thread_);
|
|
thread_->Clear(this);
|
|
}
|
|
|
|
void Port::OnMessage(rtc::Message* pmsg) {
|
|
RTC_DCHECK_RUN_ON(thread_);
|
|
RTC_DCHECK(pmsg->message_id == MSG_DESTROY_IF_DEAD);
|
|
bool dead =
|
|
(state_ == State::INIT || state_ == State::PRUNED) &&
|
|
connections_.empty() &&
|
|
rtc::TimeMillis() - last_time_all_connections_removed_ >= timeout_delay_;
|
|
if (dead) {
|
|
Destroy();
|
|
}
|
|
}
|
|
|
|
void Port::SubscribePortDestroyed(
|
|
std::function<void(PortInterface*)> callback) {
|
|
port_destroyed_callback_list_.AddReceiver(callback);
|
|
}
|
|
|
|
void Port::SendPortDestroyed(Port* port) {
|
|
port_destroyed_callback_list_.Send(port);
|
|
}
|
|
void Port::OnNetworkTypeChanged(const rtc::Network* network) {
|
|
RTC_DCHECK(network == network_);
|
|
|
|
UpdateNetworkCost();
|
|
}
|
|
|
|
std::string Port::ToString() const {
|
|
rtc::StringBuilder ss;
|
|
ss << "Port[" << rtc::ToHex(reinterpret_cast<uintptr_t>(this)) << ":"
|
|
<< content_name_ << ":" << component_ << ":" << generation_ << ":" << type_
|
|
<< ":" << network_->ToString() << "]";
|
|
return ss.Release();
|
|
}
|
|
|
|
// TODO(honghaiz): Make the network cost configurable from user setting.
|
|
void Port::UpdateNetworkCost() {
|
|
uint16_t new_cost = network_->GetCost();
|
|
if (network_cost_ == new_cost) {
|
|
return;
|
|
}
|
|
RTC_LOG(LS_INFO) << "Network cost changed from " << network_cost_ << " to "
|
|
<< new_cost
|
|
<< ". Number of candidates created: " << candidates_.size()
|
|
<< ". Number of connections created: "
|
|
<< connections_.size();
|
|
network_cost_ = new_cost;
|
|
for (cricket::Candidate& candidate : candidates_) {
|
|
candidate.set_network_cost(network_cost_);
|
|
}
|
|
// Network cost change will affect the connection selection criteria.
|
|
// Signal the connection state change on each connection to force a
|
|
// re-sort in P2PTransportChannel.
|
|
for (const auto& kv : connections_) {
|
|
Connection* conn = kv.second;
|
|
conn->SignalStateChange(conn);
|
|
}
|
|
}
|
|
|
|
void Port::EnablePortPackets() {
|
|
enable_port_packets_ = true;
|
|
}
|
|
|
|
void Port::OnConnectionDestroyed(Connection* conn) {
|
|
AddressMap::iterator iter =
|
|
connections_.find(conn->remote_candidate().address());
|
|
RTC_DCHECK(iter != connections_.end());
|
|
connections_.erase(iter);
|
|
HandleConnectionDestroyed(conn);
|
|
|
|
// Ports time out after all connections fail if it is not marked as
|
|
// "keep alive until pruned."
|
|
// Note: If a new connection is added after this message is posted, but it
|
|
// fails and is removed before kPortTimeoutDelay, then this message will
|
|
// not cause the Port to be destroyed.
|
|
if (connections_.empty()) {
|
|
last_time_all_connections_removed_ = rtc::TimeMillis();
|
|
thread_->PostDelayed(RTC_FROM_HERE, timeout_delay_, this,
|
|
MSG_DESTROY_IF_DEAD);
|
|
}
|
|
}
|
|
|
|
void Port::Destroy() {
|
|
RTC_DCHECK(connections_.empty());
|
|
RTC_LOG(LS_INFO) << ToString() << ": Port deleted";
|
|
SendPortDestroyed(this);
|
|
delete this;
|
|
}
|
|
|
|
const std::string Port::username_fragment() const {
|
|
return ice_username_fragment_;
|
|
}
|
|
|
|
void Port::CopyPortInformationToPacketInfo(rtc::PacketInfo* info) const {
|
|
info->protocol = ConvertProtocolTypeToPacketInfoProtocolType(GetProtocol());
|
|
info->network_id = Network()->id();
|
|
}
|
|
|
|
} // namespace cricket
|