1536 lines
53 KiB
C++
1536 lines
53 KiB
C++
/*
|
|
* Copyright 2012 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 "webrtc/p2p/base/turnport.h"
|
|
|
|
#include <functional>
|
|
|
|
#include "webrtc/p2p/base/common.h"
|
|
#include "webrtc/p2p/base/stun.h"
|
|
#include "webrtc/base/asyncpacketsocket.h"
|
|
#include "webrtc/base/byteorder.h"
|
|
#include "webrtc/base/common.h"
|
|
#include "webrtc/base/logging.h"
|
|
#include "webrtc/base/nethelpers.h"
|
|
#include "webrtc/base/socketaddress.h"
|
|
#include "webrtc/base/stringencode.h"
|
|
|
|
namespace cricket {
|
|
|
|
// TODO(juberti): Move to stun.h when relay messages have been renamed.
|
|
static const int TURN_ALLOCATE_REQUEST = STUN_ALLOCATE_REQUEST;
|
|
|
|
// TODO(juberti): Extract to turnmessage.h
|
|
static const int TURN_DEFAULT_PORT = 3478;
|
|
static const int TURN_CHANNEL_NUMBER_START = 0x4000;
|
|
static const int TURN_PERMISSION_TIMEOUT = 5 * 60 * 1000; // 5 minutes
|
|
|
|
static const size_t TURN_CHANNEL_HEADER_SIZE = 4U;
|
|
|
|
// Retry at most twice (i.e. three different ALLOCATE requests) on
|
|
// STUN_ERROR_ALLOCATION_MISMATCH error per rfc5766.
|
|
static const size_t MAX_ALLOCATE_MISMATCH_RETRIES = 2;
|
|
|
|
static const int TURN_SUCCESS_RESULT_CODE = 0;
|
|
|
|
inline bool IsTurnChannelData(uint16_t msg_type) {
|
|
return ((msg_type & 0xC000) == 0x4000); // MSB are 0b01
|
|
}
|
|
|
|
static int GetRelayPreference(cricket::ProtocolType proto, bool secure) {
|
|
int relay_preference = ICE_TYPE_PREFERENCE_RELAY;
|
|
if (proto == cricket::PROTO_TCP) {
|
|
relay_preference -= 1;
|
|
if (secure)
|
|
relay_preference -= 1;
|
|
}
|
|
|
|
ASSERT(relay_preference >= 0);
|
|
return relay_preference;
|
|
}
|
|
|
|
class TurnAllocateRequest : public StunRequest {
|
|
public:
|
|
explicit TurnAllocateRequest(TurnPort* port);
|
|
void Prepare(StunMessage* request) override;
|
|
void OnSent() override;
|
|
void OnResponse(StunMessage* response) override;
|
|
void OnErrorResponse(StunMessage* response) override;
|
|
void OnTimeout() override;
|
|
|
|
private:
|
|
// Handles authentication challenge from the server.
|
|
void OnAuthChallenge(StunMessage* response, int code);
|
|
void OnTryAlternate(StunMessage* response, int code);
|
|
void OnUnknownAttribute(StunMessage* response);
|
|
|
|
TurnPort* port_;
|
|
};
|
|
|
|
class TurnRefreshRequest : public StunRequest {
|
|
public:
|
|
explicit TurnRefreshRequest(TurnPort* port);
|
|
void Prepare(StunMessage* request) override;
|
|
void OnSent() override;
|
|
void OnResponse(StunMessage* response) override;
|
|
void OnErrorResponse(StunMessage* response) override;
|
|
void OnTimeout() override;
|
|
void set_lifetime(int lifetime) { lifetime_ = lifetime; }
|
|
|
|
private:
|
|
TurnPort* port_;
|
|
int lifetime_;
|
|
};
|
|
|
|
class TurnCreatePermissionRequest : public StunRequest,
|
|
public sigslot::has_slots<> {
|
|
public:
|
|
TurnCreatePermissionRequest(TurnPort* port, TurnEntry* entry,
|
|
const rtc::SocketAddress& ext_addr);
|
|
void Prepare(StunMessage* request) override;
|
|
void OnSent() override;
|
|
void OnResponse(StunMessage* response) override;
|
|
void OnErrorResponse(StunMessage* response) override;
|
|
void OnTimeout() override;
|
|
|
|
private:
|
|
void OnEntryDestroyed(TurnEntry* entry);
|
|
|
|
TurnPort* port_;
|
|
TurnEntry* entry_;
|
|
rtc::SocketAddress ext_addr_;
|
|
};
|
|
|
|
class TurnChannelBindRequest : public StunRequest,
|
|
public sigslot::has_slots<> {
|
|
public:
|
|
TurnChannelBindRequest(TurnPort* port, TurnEntry* entry, int channel_id,
|
|
const rtc::SocketAddress& ext_addr);
|
|
void Prepare(StunMessage* request) override;
|
|
void OnSent() override;
|
|
void OnResponse(StunMessage* response) override;
|
|
void OnErrorResponse(StunMessage* response) override;
|
|
void OnTimeout() override;
|
|
|
|
private:
|
|
void OnEntryDestroyed(TurnEntry* entry);
|
|
|
|
TurnPort* port_;
|
|
TurnEntry* entry_;
|
|
int channel_id_;
|
|
rtc::SocketAddress ext_addr_;
|
|
};
|
|
|
|
// Manages a "connection" to a remote destination. We will attempt to bring up
|
|
// a channel for this remote destination to reduce the overhead of sending data.
|
|
class TurnEntry : public sigslot::has_slots<> {
|
|
public:
|
|
enum BindState { STATE_UNBOUND, STATE_BINDING, STATE_BOUND };
|
|
TurnEntry(TurnPort* port, int channel_id,
|
|
const rtc::SocketAddress& ext_addr);
|
|
|
|
TurnPort* port() { return port_; }
|
|
|
|
int channel_id() const { return channel_id_; }
|
|
// For testing only.
|
|
void set_channel_id(int channel_id) { channel_id_ = channel_id; }
|
|
|
|
const rtc::SocketAddress& address() const { return ext_addr_; }
|
|
BindState state() const { return state_; }
|
|
|
|
int64_t destruction_timestamp() { return destruction_timestamp_; }
|
|
void set_destruction_timestamp(int64_t destruction_timestamp) {
|
|
destruction_timestamp_ = destruction_timestamp;
|
|
}
|
|
|
|
// Helper methods to send permission and channel bind requests.
|
|
void SendCreatePermissionRequest(int delay);
|
|
void SendChannelBindRequest(int delay);
|
|
// Sends a packet to the given destination address.
|
|
// This will wrap the packet in STUN if necessary.
|
|
int Send(const void* data, size_t size, bool payload,
|
|
const rtc::PacketOptions& options);
|
|
|
|
void OnCreatePermissionSuccess();
|
|
void OnCreatePermissionError(StunMessage* response, int code);
|
|
void OnCreatePermissionTimeout();
|
|
void OnChannelBindSuccess();
|
|
void OnChannelBindError(StunMessage* response, int code);
|
|
void OnChannelBindTimeout();
|
|
// Signal sent when TurnEntry is destroyed.
|
|
sigslot::signal1<TurnEntry*> SignalDestroyed;
|
|
|
|
private:
|
|
TurnPort* port_;
|
|
int channel_id_;
|
|
rtc::SocketAddress ext_addr_;
|
|
BindState state_;
|
|
// A non-zero value indicates that this entry is scheduled to be destroyed.
|
|
// It is also used as an ID of the event scheduling. When the destruction
|
|
// event actually fires, the TurnEntry will be destroyed only if the
|
|
// timestamp here matches the one in the firing event.
|
|
int64_t destruction_timestamp_ = 0;
|
|
};
|
|
|
|
TurnPort::TurnPort(rtc::Thread* thread,
|
|
rtc::PacketSocketFactory* factory,
|
|
rtc::Network* network,
|
|
rtc::AsyncPacketSocket* socket,
|
|
const std::string& username,
|
|
const std::string& password,
|
|
const ProtocolAddress& server_address,
|
|
const RelayCredentials& credentials,
|
|
int server_priority,
|
|
const std::string& origin)
|
|
: Port(thread,
|
|
factory,
|
|
network,
|
|
socket->GetLocalAddress().ipaddr(),
|
|
username,
|
|
password),
|
|
server_address_(server_address),
|
|
credentials_(credentials),
|
|
socket_(socket),
|
|
resolver_(NULL),
|
|
error_(0),
|
|
request_manager_(thread),
|
|
next_channel_number_(TURN_CHANNEL_NUMBER_START),
|
|
state_(STATE_CONNECTING),
|
|
server_priority_(server_priority),
|
|
allocate_mismatch_retries_(0) {
|
|
request_manager_.SignalSendPacket.connect(this, &TurnPort::OnSendStunPacket);
|
|
request_manager_.set_origin(origin);
|
|
}
|
|
|
|
TurnPort::TurnPort(rtc::Thread* thread,
|
|
rtc::PacketSocketFactory* factory,
|
|
rtc::Network* network,
|
|
const rtc::IPAddress& ip,
|
|
uint16_t min_port,
|
|
uint16_t max_port,
|
|
const std::string& username,
|
|
const std::string& password,
|
|
const ProtocolAddress& server_address,
|
|
const RelayCredentials& credentials,
|
|
int server_priority,
|
|
const std::string& origin)
|
|
: Port(thread,
|
|
RELAY_PORT_TYPE,
|
|
factory,
|
|
network,
|
|
ip,
|
|
min_port,
|
|
max_port,
|
|
username,
|
|
password),
|
|
server_address_(server_address),
|
|
credentials_(credentials),
|
|
socket_(NULL),
|
|
resolver_(NULL),
|
|
error_(0),
|
|
request_manager_(thread),
|
|
next_channel_number_(TURN_CHANNEL_NUMBER_START),
|
|
state_(STATE_CONNECTING),
|
|
server_priority_(server_priority),
|
|
allocate_mismatch_retries_(0) {
|
|
request_manager_.SignalSendPacket.connect(this, &TurnPort::OnSendStunPacket);
|
|
request_manager_.set_origin(origin);
|
|
}
|
|
|
|
TurnPort::~TurnPort() {
|
|
// TODO(juberti): Should this even be necessary?
|
|
|
|
// release the allocation by sending a refresh with
|
|
// lifetime 0.
|
|
if (ready()) {
|
|
TurnRefreshRequest bye(this);
|
|
bye.set_lifetime(0);
|
|
SendRequest(&bye, 0);
|
|
}
|
|
|
|
while (!entries_.empty()) {
|
|
DestroyEntry(entries_.front());
|
|
}
|
|
if (resolver_) {
|
|
resolver_->Destroy(false);
|
|
}
|
|
if (!SharedSocket()) {
|
|
delete socket_;
|
|
}
|
|
}
|
|
|
|
rtc::SocketAddress TurnPort::GetLocalAddress() const {
|
|
return socket_ ? socket_->GetLocalAddress() : rtc::SocketAddress();
|
|
}
|
|
|
|
void TurnPort::PrepareAddress() {
|
|
if (credentials_.username.empty() ||
|
|
credentials_.password.empty()) {
|
|
LOG(LS_ERROR) << "Allocation can't be started without setting the"
|
|
<< " TURN server credentials for the user.";
|
|
OnAllocateError();
|
|
return;
|
|
}
|
|
|
|
if (!server_address_.address.port()) {
|
|
// We will set default TURN port, if no port is set in the address.
|
|
server_address_.address.SetPort(TURN_DEFAULT_PORT);
|
|
}
|
|
|
|
if (server_address_.address.IsUnresolvedIP()) {
|
|
ResolveTurnAddress(server_address_.address);
|
|
} else {
|
|
// If protocol family of server address doesn't match with local, return.
|
|
if (!IsCompatibleAddress(server_address_.address)) {
|
|
LOG(LS_ERROR) << "IP address family does not match: "
|
|
<< "server: " << server_address_.address.family()
|
|
<< "local: " << ip().family();
|
|
OnAllocateError();
|
|
return;
|
|
}
|
|
|
|
// Insert the current address to prevent redirection pingpong.
|
|
attempted_server_addresses_.insert(server_address_.address);
|
|
|
|
LOG_J(LS_INFO, this) << "Trying to connect to TURN server via "
|
|
<< ProtoToString(server_address_.proto) << " @ "
|
|
<< server_address_.address.ToSensitiveString();
|
|
if (!CreateTurnClientSocket()) {
|
|
LOG(LS_ERROR) << "Failed to create TURN client socket";
|
|
OnAllocateError();
|
|
return;
|
|
}
|
|
if (server_address_.proto == PROTO_UDP) {
|
|
// If its UDP, send AllocateRequest now.
|
|
// For TCP and TLS AllcateRequest will be sent by OnSocketConnect.
|
|
SendRequest(new TurnAllocateRequest(this), 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool TurnPort::CreateTurnClientSocket() {
|
|
ASSERT(!socket_ || SharedSocket());
|
|
|
|
if (server_address_.proto == PROTO_UDP && !SharedSocket()) {
|
|
socket_ = socket_factory()->CreateUdpSocket(
|
|
rtc::SocketAddress(ip(), 0), min_port(), max_port());
|
|
} else if (server_address_.proto == PROTO_TCP) {
|
|
ASSERT(!SharedSocket());
|
|
int opts = rtc::PacketSocketFactory::OPT_STUN;
|
|
// If secure bit is enabled in server address, use TLS over TCP.
|
|
if (server_address_.secure) {
|
|
opts |= rtc::PacketSocketFactory::OPT_TLS;
|
|
}
|
|
socket_ = socket_factory()->CreateClientTcpSocket(
|
|
rtc::SocketAddress(ip(), 0), server_address_.address,
|
|
proxy(), user_agent(), opts);
|
|
}
|
|
|
|
if (!socket_) {
|
|
error_ = SOCKET_ERROR;
|
|
return false;
|
|
}
|
|
|
|
// Apply options if any.
|
|
for (SocketOptionsMap::iterator iter = socket_options_.begin();
|
|
iter != socket_options_.end(); ++iter) {
|
|
socket_->SetOption(iter->first, iter->second);
|
|
}
|
|
|
|
if (!SharedSocket()) {
|
|
// If socket is shared, AllocationSequence will receive the packet.
|
|
socket_->SignalReadPacket.connect(this, &TurnPort::OnReadPacket);
|
|
}
|
|
|
|
socket_->SignalReadyToSend.connect(this, &TurnPort::OnReadyToSend);
|
|
|
|
socket_->SignalSentPacket.connect(this, &TurnPort::OnSentPacket);
|
|
|
|
// TCP port is ready to send stun requests after the socket is connected,
|
|
// while UDP port is ready to do so once the socket is created.
|
|
if (server_address_.proto == PROTO_TCP) {
|
|
socket_->SignalConnect.connect(this, &TurnPort::OnSocketConnect);
|
|
socket_->SignalClose.connect(this, &TurnPort::OnSocketClose);
|
|
} else {
|
|
state_ = STATE_CONNECTED;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void TurnPort::OnSocketConnect(rtc::AsyncPacketSocket* socket) {
|
|
ASSERT(server_address_.proto == PROTO_TCP);
|
|
// Do not use this port if the socket bound to a different address than
|
|
// the one we asked for. This is seen in Chrome, where TCP sockets cannot be
|
|
// given a binding address, and the platform is expected to pick the
|
|
// correct local address.
|
|
|
|
// However, there are two situations in which we allow the bound address to
|
|
// differ from the requested address: 1. The bound address is the loopback
|
|
// address. This happens when a proxy forces TCP to bind to only the
|
|
// localhost address (see issue 3927). 2. The bound address is the "any
|
|
// address". This happens when multiple_routes is disabled (see issue 4780).
|
|
if (socket->GetLocalAddress().ipaddr() != ip()) {
|
|
if (socket->GetLocalAddress().IsLoopbackIP()) {
|
|
LOG(LS_WARNING) << "Socket is bound to a different address:"
|
|
<< socket->GetLocalAddress().ipaddr().ToString()
|
|
<< ", rather then the local port:" << ip().ToString()
|
|
<< ". Still allowing it since it's localhost.";
|
|
} else if (IPIsAny(ip())) {
|
|
LOG(LS_WARNING) << "Socket is bound to a different address:"
|
|
<< socket->GetLocalAddress().ipaddr().ToString()
|
|
<< ", rather then the local port:" << ip().ToString()
|
|
<< ". Still allowing it since it's any address"
|
|
<< ", possibly caused by multiple_routes being disabled.";
|
|
} else {
|
|
LOG(LS_WARNING) << "Socket is bound to a different address:"
|
|
<< socket->GetLocalAddress().ipaddr().ToString()
|
|
<< ", rather then the local port:" << ip().ToString()
|
|
<< ". Discarding TURN port.";
|
|
OnAllocateError();
|
|
return;
|
|
}
|
|
}
|
|
|
|
state_ = STATE_CONNECTED; // It is ready to send stun requests.
|
|
if (server_address_.address.IsUnresolvedIP()) {
|
|
server_address_.address = socket_->GetRemoteAddress();
|
|
}
|
|
|
|
LOG(LS_INFO) << "TurnPort connected to " << socket->GetRemoteAddress()
|
|
<< " using tcp.";
|
|
SendRequest(new TurnAllocateRequest(this), 0);
|
|
}
|
|
|
|
void TurnPort::OnSocketClose(rtc::AsyncPacketSocket* socket, int error) {
|
|
LOG_J(LS_WARNING, this) << "Connection with server failed, error=" << error;
|
|
ASSERT(socket == socket_);
|
|
Close();
|
|
}
|
|
|
|
void TurnPort::OnAllocateMismatch() {
|
|
if (allocate_mismatch_retries_ >= MAX_ALLOCATE_MISMATCH_RETRIES) {
|
|
LOG_J(LS_WARNING, this) << "Giving up on the port after "
|
|
<< allocate_mismatch_retries_
|
|
<< " retries for STUN_ERROR_ALLOCATION_MISMATCH";
|
|
OnAllocateError();
|
|
return;
|
|
}
|
|
|
|
LOG_J(LS_INFO, this) << "Allocating a new socket after "
|
|
<< "STUN_ERROR_ALLOCATION_MISMATCH, retry = "
|
|
<< allocate_mismatch_retries_ + 1;
|
|
if (SharedSocket()) {
|
|
ResetSharedSocket();
|
|
} else {
|
|
delete socket_;
|
|
}
|
|
socket_ = NULL;
|
|
|
|
ResetNonce();
|
|
PrepareAddress();
|
|
++allocate_mismatch_retries_;
|
|
}
|
|
|
|
Connection* TurnPort::CreateConnection(const Candidate& address,
|
|
CandidateOrigin origin) {
|
|
// TURN-UDP can only connect to UDP candidates.
|
|
if (!SupportsProtocol(address.protocol())) {
|
|
return NULL;
|
|
}
|
|
|
|
if (!IsCompatibleAddress(address.address())) {
|
|
return NULL;
|
|
}
|
|
|
|
if (state_ == STATE_DISCONNECTED) {
|
|
return NULL;
|
|
}
|
|
|
|
// Create an entry, if needed, so we can get our permissions set up correctly.
|
|
CreateOrRefreshEntry(address.address());
|
|
|
|
// A TURN port will have two candiates, STUN and TURN. STUN may not
|
|
// present in all cases. If present stun candidate will be added first
|
|
// and TURN candidate later.
|
|
for (size_t index = 0; index < Candidates().size(); ++index) {
|
|
if (Candidates()[index].type() == RELAY_PORT_TYPE) {
|
|
ProxyConnection* conn = new ProxyConnection(this, index, address);
|
|
AddOrReplaceConnection(conn);
|
|
return conn;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
bool TurnPort::DestroyConnection(const rtc::SocketAddress& address) {
|
|
Connection* conn = GetConnection(address);
|
|
if (conn != nullptr) {
|
|
conn->Destroy();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int TurnPort::SetOption(rtc::Socket::Option opt, int value) {
|
|
if (!socket_) {
|
|
// If socket is not created yet, these options will be applied during socket
|
|
// creation.
|
|
socket_options_[opt] = value;
|
|
return 0;
|
|
}
|
|
return socket_->SetOption(opt, value);
|
|
}
|
|
|
|
int TurnPort::GetOption(rtc::Socket::Option opt, int* value) {
|
|
if (!socket_) {
|
|
SocketOptionsMap::const_iterator it = socket_options_.find(opt);
|
|
if (it == socket_options_.end()) {
|
|
return -1;
|
|
}
|
|
*value = it->second;
|
|
return 0;
|
|
}
|
|
|
|
return socket_->GetOption(opt, value);
|
|
}
|
|
|
|
int TurnPort::GetError() {
|
|
return error_;
|
|
}
|
|
|
|
int TurnPort::SendTo(const void* data, size_t size,
|
|
const rtc::SocketAddress& addr,
|
|
const rtc::PacketOptions& options,
|
|
bool payload) {
|
|
// Try to find an entry for this specific address; we should have one.
|
|
TurnEntry* entry = FindEntry(addr);
|
|
if (!entry) {
|
|
LOG(LS_ERROR) << "Did not find the TurnEntry for address " << addr;
|
|
return 0;
|
|
}
|
|
|
|
if (!ready()) {
|
|
error_ = EWOULDBLOCK;
|
|
return SOCKET_ERROR;
|
|
}
|
|
|
|
// Send the actual contents to the server using the usual mechanism.
|
|
int sent = entry->Send(data, size, payload, options);
|
|
if (sent <= 0) {
|
|
return SOCKET_ERROR;
|
|
}
|
|
|
|
// The caller of the function is expecting the number of user data bytes,
|
|
// rather than the size of the packet.
|
|
return static_cast<int>(size);
|
|
}
|
|
|
|
bool TurnPort::HandleIncomingPacket(rtc::AsyncPacketSocket* socket,
|
|
const char* data, size_t size,
|
|
const rtc::SocketAddress& remote_addr,
|
|
const rtc::PacketTime& packet_time) {
|
|
if (socket != socket_) {
|
|
// The packet was received on a shared socket after we've allocated a new
|
|
// socket for this TURN port.
|
|
return false;
|
|
}
|
|
|
|
// This is to guard against a STUN response from previous server after
|
|
// alternative server redirection. TODO(guoweis): add a unit test for this
|
|
// race condition.
|
|
if (remote_addr != server_address_.address) {
|
|
LOG_J(LS_WARNING, this) << "Discarding TURN message from unknown address:"
|
|
<< remote_addr.ToString()
|
|
<< ", server_address_:"
|
|
<< server_address_.address.ToString();
|
|
return false;
|
|
}
|
|
|
|
// The message must be at least the size of a channel header.
|
|
if (size < TURN_CHANNEL_HEADER_SIZE) {
|
|
LOG_J(LS_WARNING, this) << "Received TURN message that was too short";
|
|
return false;
|
|
}
|
|
|
|
if (state_ == STATE_DISCONNECTED) {
|
|
LOG_J(LS_WARNING, this)
|
|
<< "Received TURN message while the Turn port is disconnected";
|
|
return false;
|
|
}
|
|
|
|
// Check the message type, to see if is a Channel Data message.
|
|
// The message will either be channel data, a TURN data indication, or
|
|
// a response to a previous request.
|
|
uint16_t msg_type = rtc::GetBE16(data);
|
|
if (IsTurnChannelData(msg_type)) {
|
|
HandleChannelData(msg_type, data, size, packet_time);
|
|
return true;
|
|
|
|
}
|
|
|
|
if (msg_type == TURN_DATA_INDICATION) {
|
|
HandleDataIndication(data, size, packet_time);
|
|
return true;
|
|
}
|
|
|
|
if (SharedSocket() && (msg_type == STUN_BINDING_RESPONSE ||
|
|
msg_type == STUN_BINDING_ERROR_RESPONSE)) {
|
|
LOG_J(LS_VERBOSE, this) <<
|
|
"Ignoring STUN binding response message on shared socket.";
|
|
return false;
|
|
}
|
|
|
|
// This must be a response for one of our requests.
|
|
// Check success responses, but not errors, for MESSAGE-INTEGRITY.
|
|
if (IsStunSuccessResponseType(msg_type) &&
|
|
!StunMessage::ValidateMessageIntegrity(data, size, hash())) {
|
|
LOG_J(LS_WARNING, this) << "Received TURN message with invalid "
|
|
<< "message integrity, msg_type=" << msg_type;
|
|
return true;
|
|
}
|
|
request_manager_.CheckResponse(data, size);
|
|
|
|
return true;
|
|
}
|
|
|
|
void TurnPort::OnReadPacket(rtc::AsyncPacketSocket* socket,
|
|
const char* data,
|
|
size_t size,
|
|
const rtc::SocketAddress& remote_addr,
|
|
const rtc::PacketTime& packet_time) {
|
|
HandleIncomingPacket(socket, data, size, remote_addr, packet_time);
|
|
}
|
|
|
|
void TurnPort::OnSentPacket(rtc::AsyncPacketSocket* socket,
|
|
const rtc::SentPacket& sent_packet) {
|
|
PortInterface::SignalSentPacket(sent_packet);
|
|
}
|
|
|
|
void TurnPort::OnReadyToSend(rtc::AsyncPacketSocket* socket) {
|
|
if (ready()) {
|
|
Port::OnReadyToSend();
|
|
}
|
|
}
|
|
|
|
|
|
// Update current server address port with the alternate server address port.
|
|
bool TurnPort::SetAlternateServer(const rtc::SocketAddress& address) {
|
|
// Check if we have seen this address before and reject if we did.
|
|
AttemptedServerSet::iterator iter = attempted_server_addresses_.find(address);
|
|
if (iter != attempted_server_addresses_.end()) {
|
|
LOG_J(LS_WARNING, this) << "Redirection to ["
|
|
<< address.ToSensitiveString()
|
|
<< "] ignored, allocation failed.";
|
|
return false;
|
|
}
|
|
|
|
// If protocol family of server address doesn't match with local, return.
|
|
if (!IsCompatibleAddress(address)) {
|
|
LOG(LS_WARNING) << "Server IP address family does not match with "
|
|
<< "local host address family type";
|
|
return false;
|
|
}
|
|
|
|
LOG_J(LS_INFO, this) << "Redirecting from TURN server ["
|
|
<< server_address_.address.ToSensitiveString()
|
|
<< "] to TURN server ["
|
|
<< address.ToSensitiveString()
|
|
<< "]";
|
|
server_address_ = ProtocolAddress(address, server_address_.proto,
|
|
server_address_.secure);
|
|
|
|
// Insert the current address to prevent redirection pingpong.
|
|
attempted_server_addresses_.insert(server_address_.address);
|
|
return true;
|
|
}
|
|
|
|
void TurnPort::ResolveTurnAddress(const rtc::SocketAddress& address) {
|
|
if (resolver_)
|
|
return;
|
|
|
|
LOG_J(LS_INFO, this) << "Starting TURN host lookup for "
|
|
<< address.ToSensitiveString();
|
|
resolver_ = socket_factory()->CreateAsyncResolver();
|
|
resolver_->SignalDone.connect(this, &TurnPort::OnResolveResult);
|
|
resolver_->Start(address);
|
|
}
|
|
|
|
void TurnPort::OnResolveResult(rtc::AsyncResolverInterface* resolver) {
|
|
ASSERT(resolver == resolver_);
|
|
// If DNS resolve is failed when trying to connect to the server using TCP,
|
|
// one of the reason could be due to DNS queries blocked by firewall.
|
|
// In such cases we will try to connect to the server with hostname, assuming
|
|
// socket layer will resolve the hostname through a HTTP proxy (if any).
|
|
if (resolver_->GetError() != 0 && server_address_.proto == PROTO_TCP) {
|
|
if (!CreateTurnClientSocket()) {
|
|
OnAllocateError();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Copy the original server address in |resolved_address|. For TLS based
|
|
// sockets we need hostname along with resolved address.
|
|
rtc::SocketAddress resolved_address = server_address_.address;
|
|
if (resolver_->GetError() != 0 ||
|
|
!resolver_->GetResolvedAddress(ip().family(), &resolved_address)) {
|
|
LOG_J(LS_WARNING, this) << "TURN host lookup received error "
|
|
<< resolver_->GetError();
|
|
error_ = resolver_->GetError();
|
|
OnAllocateError();
|
|
return;
|
|
}
|
|
// Signal needs both resolved and unresolved address. After signal is sent
|
|
// we can copy resolved address back into |server_address_|.
|
|
SignalResolvedServerAddress(this, server_address_.address,
|
|
resolved_address);
|
|
server_address_.address = resolved_address;
|
|
PrepareAddress();
|
|
}
|
|
|
|
void TurnPort::OnSendStunPacket(const void* data, size_t size,
|
|
StunRequest* request) {
|
|
ASSERT(connected());
|
|
rtc::PacketOptions options(DefaultDscpValue());
|
|
if (Send(data, size, options) < 0) {
|
|
LOG_J(LS_ERROR, this) << "Failed to send TURN message, err="
|
|
<< socket_->GetError();
|
|
}
|
|
}
|
|
|
|
void TurnPort::OnStunAddress(const rtc::SocketAddress& address) {
|
|
// STUN Port will discover STUN candidate, as it's supplied with first TURN
|
|
// server address.
|
|
// Why not using this address? - P2PTransportChannel will start creating
|
|
// connections after first candidate, which means it could start creating the
|
|
// connections before TURN candidate added. For that to handle, we need to
|
|
// supply STUN candidate from this port to UDPPort, and TurnPort should have
|
|
// handle to UDPPort to pass back the address.
|
|
}
|
|
|
|
void TurnPort::OnAllocateSuccess(const rtc::SocketAddress& address,
|
|
const rtc::SocketAddress& stun_address) {
|
|
state_ = STATE_READY;
|
|
|
|
rtc::SocketAddress related_address = stun_address;
|
|
|
|
// For relayed candidate, Base is the candidate itself.
|
|
AddAddress(address, // Candidate address.
|
|
address, // Base address.
|
|
related_address, // Related address.
|
|
UDP_PROTOCOL_NAME,
|
|
ProtoToString(server_address_.proto), // The first hop protocol.
|
|
"", // TCP canddiate type, empty for turn candidates.
|
|
RELAY_PORT_TYPE,
|
|
GetRelayPreference(server_address_.proto, server_address_.secure),
|
|
server_priority_, true);
|
|
}
|
|
|
|
void TurnPort::OnAllocateError() {
|
|
// We will send SignalPortError asynchronously as this can be sent during
|
|
// port initialization. This way it will not be blocking other port
|
|
// creation.
|
|
thread()->Post(RTC_FROM_HERE, this, MSG_ALLOCATE_ERROR);
|
|
}
|
|
|
|
void TurnPort::OnTurnRefreshError() {
|
|
// Need to Close the port asynchronously because otherwise, the refresh
|
|
// request may be deleted twice: once at the end of the message processing
|
|
// and the other in Close().
|
|
thread()->Post(RTC_FROM_HERE, this, MSG_REFRESH_ERROR);
|
|
}
|
|
|
|
void TurnPort::Close() {
|
|
if (!ready()) {
|
|
OnAllocateError();
|
|
}
|
|
request_manager_.Clear();
|
|
// Stop the port from creating new connections.
|
|
state_ = STATE_DISCONNECTED;
|
|
// Delete all existing connections; stop sending data.
|
|
for (auto kv : connections()) {
|
|
kv.second->Destroy();
|
|
}
|
|
}
|
|
|
|
void TurnPort::OnMessage(rtc::Message* message) {
|
|
switch (message->message_id) {
|
|
case MSG_ALLOCATE_ERROR:
|
|
SignalPortError(this);
|
|
break;
|
|
case MSG_ALLOCATE_MISMATCH:
|
|
OnAllocateMismatch();
|
|
break;
|
|
case MSG_REFRESH_ERROR:
|
|
Close();
|
|
break;
|
|
case MSG_TRY_ALTERNATE_SERVER:
|
|
if (server_address().proto == PROTO_UDP) {
|
|
// Send another allocate request to alternate server, with the received
|
|
// realm and nonce values.
|
|
SendRequest(new TurnAllocateRequest(this), 0);
|
|
} else {
|
|
// Since it's TCP, we have to delete the connected socket and reconnect
|
|
// with the alternate server. PrepareAddress will send stun binding once
|
|
// the new socket is connected.
|
|
ASSERT(server_address().proto == PROTO_TCP);
|
|
ASSERT(!SharedSocket());
|
|
delete socket_;
|
|
socket_ = NULL;
|
|
PrepareAddress();
|
|
}
|
|
break;
|
|
default:
|
|
Port::OnMessage(message);
|
|
}
|
|
}
|
|
|
|
void TurnPort::OnAllocateRequestTimeout() {
|
|
OnAllocateError();
|
|
}
|
|
|
|
void TurnPort::HandleDataIndication(const char* data, size_t size,
|
|
const rtc::PacketTime& packet_time) {
|
|
// Read in the message, and process according to RFC5766, Section 10.4.
|
|
rtc::ByteBufferReader buf(data, size);
|
|
TurnMessage msg;
|
|
if (!msg.Read(&buf)) {
|
|
LOG_J(LS_WARNING, this) << "Received invalid TURN data indication";
|
|
return;
|
|
}
|
|
|
|
// Check mandatory attributes.
|
|
const StunAddressAttribute* addr_attr =
|
|
msg.GetAddress(STUN_ATTR_XOR_PEER_ADDRESS);
|
|
if (!addr_attr) {
|
|
LOG_J(LS_WARNING, this) << "Missing STUN_ATTR_XOR_PEER_ADDRESS attribute "
|
|
<< "in data indication.";
|
|
return;
|
|
}
|
|
|
|
const StunByteStringAttribute* data_attr =
|
|
msg.GetByteString(STUN_ATTR_DATA);
|
|
if (!data_attr) {
|
|
LOG_J(LS_WARNING, this) << "Missing STUN_ATTR_DATA attribute in "
|
|
<< "data indication.";
|
|
return;
|
|
}
|
|
|
|
// Verify that the data came from somewhere we think we have a permission for.
|
|
rtc::SocketAddress ext_addr(addr_attr->GetAddress());
|
|
if (!HasPermission(ext_addr.ipaddr())) {
|
|
LOG_J(LS_WARNING, this) << "Received TURN data indication with invalid "
|
|
<< "peer address, addr="
|
|
<< ext_addr.ToSensitiveString();
|
|
return;
|
|
}
|
|
|
|
DispatchPacket(data_attr->bytes(), data_attr->length(), ext_addr,
|
|
PROTO_UDP, packet_time);
|
|
}
|
|
|
|
void TurnPort::HandleChannelData(int channel_id, const char* data,
|
|
size_t size,
|
|
const rtc::PacketTime& packet_time) {
|
|
// Read the message, and process according to RFC5766, Section 11.6.
|
|
// 0 1 2 3
|
|
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
// | Channel Number | Length |
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
// | |
|
|
// / Application Data /
|
|
// / /
|
|
// | |
|
|
// | +-------------------------------+
|
|
// | |
|
|
// +-------------------------------+
|
|
|
|
// Extract header fields from the message.
|
|
uint16_t len = rtc::GetBE16(data + 2);
|
|
if (len > size - TURN_CHANNEL_HEADER_SIZE) {
|
|
LOG_J(LS_WARNING, this) << "Received TURN channel data message with "
|
|
<< "incorrect length, len=" << len;
|
|
return;
|
|
}
|
|
// Allowing messages larger than |len|, as ChannelData can be padded.
|
|
|
|
TurnEntry* entry = FindEntry(channel_id);
|
|
if (!entry) {
|
|
LOG_J(LS_WARNING, this) << "Received TURN channel data message for invalid "
|
|
<< "channel, channel_id=" << channel_id;
|
|
return;
|
|
}
|
|
|
|
DispatchPacket(data + TURN_CHANNEL_HEADER_SIZE, len, entry->address(),
|
|
PROTO_UDP, packet_time);
|
|
}
|
|
|
|
void TurnPort::DispatchPacket(const char* data, size_t size,
|
|
const rtc::SocketAddress& remote_addr,
|
|
ProtocolType proto, const rtc::PacketTime& packet_time) {
|
|
if (Connection* conn = GetConnection(remote_addr)) {
|
|
conn->OnReadPacket(data, size, packet_time);
|
|
} else {
|
|
Port::OnReadPacket(data, size, remote_addr, proto);
|
|
}
|
|
}
|
|
|
|
bool TurnPort::ScheduleRefresh(int lifetime) {
|
|
// Lifetime is in seconds; we schedule a refresh for one minute less.
|
|
if (lifetime < 2 * 60) {
|
|
LOG_J(LS_WARNING, this) << "Received response with lifetime that was "
|
|
<< "too short, lifetime=" << lifetime;
|
|
return false;
|
|
}
|
|
|
|
int delay = (lifetime - 60) * 1000;
|
|
SendRequest(new TurnRefreshRequest(this), delay);
|
|
LOG_J(LS_INFO, this) << "Scheduled refresh in " << delay << "ms.";
|
|
return true;
|
|
}
|
|
|
|
void TurnPort::SendRequest(StunRequest* req, int delay) {
|
|
request_manager_.SendDelayed(req, delay);
|
|
}
|
|
|
|
void TurnPort::AddRequestAuthInfo(StunMessage* msg) {
|
|
// If we've gotten the necessary data from the server, add it to our request.
|
|
VERIFY(!hash_.empty());
|
|
VERIFY(msg->AddAttribute(new StunByteStringAttribute(
|
|
STUN_ATTR_USERNAME, credentials_.username)));
|
|
VERIFY(msg->AddAttribute(new StunByteStringAttribute(
|
|
STUN_ATTR_REALM, realm_)));
|
|
VERIFY(msg->AddAttribute(new StunByteStringAttribute(
|
|
STUN_ATTR_NONCE, nonce_)));
|
|
VERIFY(msg->AddMessageIntegrity(hash()));
|
|
}
|
|
|
|
int TurnPort::Send(const void* data, size_t len,
|
|
const rtc::PacketOptions& options) {
|
|
return socket_->SendTo(data, len, server_address_.address, options);
|
|
}
|
|
|
|
void TurnPort::UpdateHash() {
|
|
VERIFY(ComputeStunCredentialHash(credentials_.username, realm_,
|
|
credentials_.password, &hash_));
|
|
}
|
|
|
|
bool TurnPort::UpdateNonce(StunMessage* response) {
|
|
// When stale nonce error received, we should update
|
|
// hash and store realm and nonce.
|
|
// Check the mandatory attributes.
|
|
const StunByteStringAttribute* realm_attr =
|
|
response->GetByteString(STUN_ATTR_REALM);
|
|
if (!realm_attr) {
|
|
LOG(LS_ERROR) << "Missing STUN_ATTR_REALM attribute in "
|
|
<< "stale nonce error response.";
|
|
return false;
|
|
}
|
|
set_realm(realm_attr->GetString());
|
|
|
|
const StunByteStringAttribute* nonce_attr =
|
|
response->GetByteString(STUN_ATTR_NONCE);
|
|
if (!nonce_attr) {
|
|
LOG(LS_ERROR) << "Missing STUN_ATTR_NONCE attribute in "
|
|
<< "stale nonce error response.";
|
|
return false;
|
|
}
|
|
set_nonce(nonce_attr->GetString());
|
|
return true;
|
|
}
|
|
|
|
void TurnPort::ResetNonce() {
|
|
hash_.clear();
|
|
nonce_.clear();
|
|
realm_.clear();
|
|
}
|
|
|
|
static bool MatchesIP(TurnEntry* e, rtc::IPAddress ipaddr) {
|
|
return e->address().ipaddr() == ipaddr;
|
|
}
|
|
bool TurnPort::HasPermission(const rtc::IPAddress& ipaddr) const {
|
|
return (std::find_if(entries_.begin(), entries_.end(),
|
|
std::bind2nd(std::ptr_fun(MatchesIP), ipaddr)) != entries_.end());
|
|
}
|
|
|
|
static bool MatchesAddress(TurnEntry* e, rtc::SocketAddress addr) {
|
|
return e->address() == addr;
|
|
}
|
|
TurnEntry* TurnPort::FindEntry(const rtc::SocketAddress& addr) const {
|
|
EntryList::const_iterator it = std::find_if(entries_.begin(), entries_.end(),
|
|
std::bind2nd(std::ptr_fun(MatchesAddress), addr));
|
|
return (it != entries_.end()) ? *it : NULL;
|
|
}
|
|
|
|
static bool MatchesChannelId(TurnEntry* e, int id) {
|
|
return e->channel_id() == id;
|
|
}
|
|
TurnEntry* TurnPort::FindEntry(int channel_id) const {
|
|
EntryList::const_iterator it = std::find_if(entries_.begin(), entries_.end(),
|
|
std::bind2nd(std::ptr_fun(MatchesChannelId), channel_id));
|
|
return (it != entries_.end()) ? *it : NULL;
|
|
}
|
|
|
|
bool TurnPort::EntryExists(TurnEntry* e) {
|
|
auto it = std::find(entries_.begin(), entries_.end(), e);
|
|
return it != entries_.end();
|
|
}
|
|
|
|
void TurnPort::CreateOrRefreshEntry(const rtc::SocketAddress& addr) {
|
|
TurnEntry* entry = FindEntry(addr);
|
|
if (entry == nullptr) {
|
|
entry = new TurnEntry(this, next_channel_number_++, addr);
|
|
entries_.push_back(entry);
|
|
} else {
|
|
// The channel binding request for the entry will be refreshed automatically
|
|
// until the entry is destroyed.
|
|
CancelEntryDestruction(entry);
|
|
}
|
|
}
|
|
|
|
void TurnPort::DestroyEntry(TurnEntry* entry) {
|
|
ASSERT(entry != NULL);
|
|
entry->SignalDestroyed(entry);
|
|
entries_.remove(entry);
|
|
delete entry;
|
|
}
|
|
|
|
void TurnPort::DestroyEntryIfNotCancelled(TurnEntry* entry, int64_t timestamp) {
|
|
if (!EntryExists(entry)) {
|
|
return;
|
|
}
|
|
bool cancelled = timestamp != entry->destruction_timestamp();
|
|
if (!cancelled) {
|
|
DestroyEntry(entry);
|
|
}
|
|
}
|
|
|
|
void TurnPort::HandleConnectionDestroyed(Connection* conn) {
|
|
// Schedule an event to destroy TurnEntry for the connection, which is
|
|
// already destroyed.
|
|
const rtc::SocketAddress& remote_address = conn->remote_candidate().address();
|
|
TurnEntry* entry = FindEntry(remote_address);
|
|
ASSERT(entry != NULL);
|
|
ScheduleEntryDestruction(entry);
|
|
}
|
|
|
|
void TurnPort::ScheduleEntryDestruction(TurnEntry* entry) {
|
|
ASSERT(entry->destruction_timestamp() == 0);
|
|
int64_t timestamp = rtc::TimeMillis();
|
|
entry->set_destruction_timestamp(timestamp);
|
|
invoker_.AsyncInvokeDelayed<void>(
|
|
RTC_FROM_HERE, thread(),
|
|
rtc::Bind(&TurnPort::DestroyEntryIfNotCancelled, this, entry, timestamp),
|
|
TURN_PERMISSION_TIMEOUT);
|
|
}
|
|
|
|
void TurnPort::CancelEntryDestruction(TurnEntry* entry) {
|
|
ASSERT(entry->destruction_timestamp() != 0);
|
|
entry->set_destruction_timestamp(0);
|
|
}
|
|
|
|
bool TurnPort::SetEntryChannelId(const rtc::SocketAddress& address,
|
|
int channel_id) {
|
|
TurnEntry* entry = FindEntry(address);
|
|
if (!entry) {
|
|
return false;
|
|
}
|
|
entry->set_channel_id(channel_id);
|
|
return true;
|
|
}
|
|
|
|
TurnAllocateRequest::TurnAllocateRequest(TurnPort* port)
|
|
: StunRequest(new TurnMessage()),
|
|
port_(port) {
|
|
}
|
|
|
|
void TurnAllocateRequest::Prepare(StunMessage* request) {
|
|
// Create the request as indicated in RFC 5766, Section 6.1.
|
|
request->SetType(TURN_ALLOCATE_REQUEST);
|
|
StunUInt32Attribute* transport_attr = StunAttribute::CreateUInt32(
|
|
STUN_ATTR_REQUESTED_TRANSPORT);
|
|
transport_attr->SetValue(IPPROTO_UDP << 24);
|
|
VERIFY(request->AddAttribute(transport_attr));
|
|
if (!port_->hash().empty()) {
|
|
port_->AddRequestAuthInfo(request);
|
|
}
|
|
}
|
|
|
|
void TurnAllocateRequest::OnSent() {
|
|
LOG_J(LS_INFO, port_) << "TURN allocate request sent"
|
|
<< ", id=" << rtc::hex_encode(id());
|
|
StunRequest::OnSent();
|
|
}
|
|
|
|
void TurnAllocateRequest::OnResponse(StunMessage* response) {
|
|
LOG_J(LS_INFO, port_) << "TURN allocate requested successfully"
|
|
<< ", id=" << rtc::hex_encode(id())
|
|
<< ", code=0" // Makes logging easier to parse.
|
|
<< ", rtt=" << Elapsed();
|
|
|
|
// Check mandatory attributes as indicated in RFC5766, Section 6.3.
|
|
const StunAddressAttribute* mapped_attr =
|
|
response->GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
|
|
if (!mapped_attr) {
|
|
LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_XOR_MAPPED_ADDRESS "
|
|
<< "attribute in allocate success response";
|
|
return;
|
|
}
|
|
// Using XOR-Mapped-Address for stun.
|
|
port_->OnStunAddress(mapped_attr->GetAddress());
|
|
|
|
const StunAddressAttribute* relayed_attr =
|
|
response->GetAddress(STUN_ATTR_XOR_RELAYED_ADDRESS);
|
|
if (!relayed_attr) {
|
|
LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_XOR_RELAYED_ADDRESS "
|
|
<< "attribute in allocate success response";
|
|
return;
|
|
}
|
|
|
|
const StunUInt32Attribute* lifetime_attr =
|
|
response->GetUInt32(STUN_ATTR_TURN_LIFETIME);
|
|
if (!lifetime_attr) {
|
|
LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_TURN_LIFETIME attribute in "
|
|
<< "allocate success response";
|
|
return;
|
|
}
|
|
// Notify the port the allocate succeeded, and schedule a refresh request.
|
|
port_->OnAllocateSuccess(relayed_attr->GetAddress(),
|
|
mapped_attr->GetAddress());
|
|
port_->ScheduleRefresh(lifetime_attr->value());
|
|
}
|
|
|
|
void TurnAllocateRequest::OnErrorResponse(StunMessage* response) {
|
|
// Process error response according to RFC5766, Section 6.4.
|
|
const StunErrorCodeAttribute* error_code = response->GetErrorCode();
|
|
|
|
LOG_J(LS_INFO, port_) << "Received TURN allocate error response"
|
|
<< ", id=" << rtc::hex_encode(id())
|
|
<< ", code=" << error_code->code()
|
|
<< ", rtt=" << Elapsed();
|
|
|
|
switch (error_code->code()) {
|
|
case STUN_ERROR_UNAUTHORIZED: // Unauthrorized.
|
|
OnAuthChallenge(response, error_code->code());
|
|
break;
|
|
case STUN_ERROR_TRY_ALTERNATE:
|
|
OnTryAlternate(response, error_code->code());
|
|
break;
|
|
case STUN_ERROR_ALLOCATION_MISMATCH:
|
|
// We must handle this error async because trying to delete the socket in
|
|
// OnErrorResponse will cause a deadlock on the socket.
|
|
port_->thread()->Post(RTC_FROM_HERE, port_,
|
|
TurnPort::MSG_ALLOCATE_MISMATCH);
|
|
break;
|
|
default:
|
|
LOG_J(LS_WARNING, port_) << "Received TURN allocate error response"
|
|
<< ", id=" << rtc::hex_encode(id())
|
|
<< ", code=" << error_code->code()
|
|
<< ", rtt=" << Elapsed();
|
|
port_->OnAllocateError();
|
|
}
|
|
}
|
|
|
|
void TurnAllocateRequest::OnTimeout() {
|
|
LOG_J(LS_WARNING, port_) << "TURN allocate request "
|
|
<< rtc::hex_encode(id()) << " timout";
|
|
port_->OnAllocateRequestTimeout();
|
|
}
|
|
|
|
void TurnAllocateRequest::OnAuthChallenge(StunMessage* response, int code) {
|
|
// If we failed to authenticate even after we sent our credentials, fail hard.
|
|
if (code == STUN_ERROR_UNAUTHORIZED && !port_->hash().empty()) {
|
|
LOG_J(LS_WARNING, port_) << "Failed to authenticate with the server "
|
|
<< "after challenge.";
|
|
port_->OnAllocateError();
|
|
return;
|
|
}
|
|
|
|
// Check the mandatory attributes.
|
|
const StunByteStringAttribute* realm_attr =
|
|
response->GetByteString(STUN_ATTR_REALM);
|
|
if (!realm_attr) {
|
|
LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_REALM attribute in "
|
|
<< "allocate unauthorized response.";
|
|
return;
|
|
}
|
|
port_->set_realm(realm_attr->GetString());
|
|
|
|
const StunByteStringAttribute* nonce_attr =
|
|
response->GetByteString(STUN_ATTR_NONCE);
|
|
if (!nonce_attr) {
|
|
LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_NONCE attribute in "
|
|
<< "allocate unauthorized response.";
|
|
return;
|
|
}
|
|
port_->set_nonce(nonce_attr->GetString());
|
|
|
|
// Send another allocate request, with the received realm and nonce values.
|
|
port_->SendRequest(new TurnAllocateRequest(port_), 0);
|
|
}
|
|
|
|
void TurnAllocateRequest::OnTryAlternate(StunMessage* response, int code) {
|
|
|
|
// According to RFC 5389 section 11, there are use cases where
|
|
// authentication of response is not possible, we're not validating
|
|
// message integrity.
|
|
|
|
// Get the alternate server address attribute value.
|
|
const StunAddressAttribute* alternate_server_attr =
|
|
response->GetAddress(STUN_ATTR_ALTERNATE_SERVER);
|
|
if (!alternate_server_attr) {
|
|
LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_ALTERNATE_SERVER "
|
|
<< "attribute in try alternate error response";
|
|
port_->OnAllocateError();
|
|
return;
|
|
}
|
|
if (!port_->SetAlternateServer(alternate_server_attr->GetAddress())) {
|
|
port_->OnAllocateError();
|
|
return;
|
|
}
|
|
|
|
// Check the attributes.
|
|
const StunByteStringAttribute* realm_attr =
|
|
response->GetByteString(STUN_ATTR_REALM);
|
|
if (realm_attr) {
|
|
LOG_J(LS_INFO, port_) << "Applying STUN_ATTR_REALM attribute in "
|
|
<< "try alternate error response.";
|
|
port_->set_realm(realm_attr->GetString());
|
|
}
|
|
|
|
const StunByteStringAttribute* nonce_attr =
|
|
response->GetByteString(STUN_ATTR_NONCE);
|
|
if (nonce_attr) {
|
|
LOG_J(LS_INFO, port_) << "Applying STUN_ATTR_NONCE attribute in "
|
|
<< "try alternate error response.";
|
|
port_->set_nonce(nonce_attr->GetString());
|
|
}
|
|
|
|
// For TCP, we can't close the original Tcp socket during handling a 300 as
|
|
// we're still inside that socket's event handler. Doing so will cause
|
|
// deadlock.
|
|
port_->thread()->Post(RTC_FROM_HERE, port_,
|
|
TurnPort::MSG_TRY_ALTERNATE_SERVER);
|
|
}
|
|
|
|
TurnRefreshRequest::TurnRefreshRequest(TurnPort* port)
|
|
: StunRequest(new TurnMessage()),
|
|
port_(port),
|
|
lifetime_(-1) {
|
|
}
|
|
|
|
void TurnRefreshRequest::Prepare(StunMessage* request) {
|
|
// Create the request as indicated in RFC 5766, Section 7.1.
|
|
// No attributes need to be included.
|
|
request->SetType(TURN_REFRESH_REQUEST);
|
|
if (lifetime_ > -1) {
|
|
VERIFY(request->AddAttribute(new StunUInt32Attribute(
|
|
STUN_ATTR_LIFETIME, lifetime_)));
|
|
}
|
|
|
|
port_->AddRequestAuthInfo(request);
|
|
}
|
|
|
|
void TurnRefreshRequest::OnSent() {
|
|
LOG_J(LS_INFO, port_) << "TURN refresh request sent"
|
|
<< ", id=" << rtc::hex_encode(id());
|
|
StunRequest::OnSent();
|
|
}
|
|
|
|
void TurnRefreshRequest::OnResponse(StunMessage* response) {
|
|
LOG_J(LS_INFO, port_) << "TURN refresh requested successfully"
|
|
<< ", id=" << rtc::hex_encode(id())
|
|
<< ", code=0" // Makes logging easier to parse.
|
|
<< ", rtt=" << Elapsed();
|
|
|
|
// Check mandatory attributes as indicated in RFC5766, Section 7.3.
|
|
const StunUInt32Attribute* lifetime_attr =
|
|
response->GetUInt32(STUN_ATTR_TURN_LIFETIME);
|
|
if (!lifetime_attr) {
|
|
LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_TURN_LIFETIME attribute in "
|
|
<< "refresh success response.";
|
|
return;
|
|
}
|
|
|
|
// Schedule a refresh based on the returned lifetime value.
|
|
port_->ScheduleRefresh(lifetime_attr->value());
|
|
port_->SignalTurnRefreshResult(port_, TURN_SUCCESS_RESULT_CODE);
|
|
}
|
|
|
|
void TurnRefreshRequest::OnErrorResponse(StunMessage* response) {
|
|
const StunErrorCodeAttribute* error_code = response->GetErrorCode();
|
|
|
|
if (error_code->code() == STUN_ERROR_STALE_NONCE) {
|
|
if (port_->UpdateNonce(response)) {
|
|
// Send RefreshRequest immediately.
|
|
port_->SendRequest(new TurnRefreshRequest(port_), 0);
|
|
}
|
|
} else {
|
|
LOG_J(LS_WARNING, port_) << "Received TURN refresh error response"
|
|
<< ", id=" << rtc::hex_encode(id())
|
|
<< ", code=" << error_code->code()
|
|
<< ", rtt=" << Elapsed();
|
|
port_->OnTurnRefreshError();
|
|
port_->SignalTurnRefreshResult(port_, error_code->code());
|
|
}
|
|
}
|
|
|
|
void TurnRefreshRequest::OnTimeout() {
|
|
LOG_J(LS_WARNING, port_) << "TURN refresh timeout " << rtc::hex_encode(id());
|
|
port_->OnTurnRefreshError();
|
|
}
|
|
|
|
TurnCreatePermissionRequest::TurnCreatePermissionRequest(
|
|
TurnPort* port, TurnEntry* entry,
|
|
const rtc::SocketAddress& ext_addr)
|
|
: StunRequest(new TurnMessage()),
|
|
port_(port),
|
|
entry_(entry),
|
|
ext_addr_(ext_addr) {
|
|
entry_->SignalDestroyed.connect(
|
|
this, &TurnCreatePermissionRequest::OnEntryDestroyed);
|
|
}
|
|
|
|
void TurnCreatePermissionRequest::Prepare(StunMessage* request) {
|
|
// Create the request as indicated in RFC5766, Section 9.1.
|
|
request->SetType(TURN_CREATE_PERMISSION_REQUEST);
|
|
VERIFY(request->AddAttribute(new StunXorAddressAttribute(
|
|
STUN_ATTR_XOR_PEER_ADDRESS, ext_addr_)));
|
|
port_->AddRequestAuthInfo(request);
|
|
}
|
|
|
|
void TurnCreatePermissionRequest::OnSent() {
|
|
LOG_J(LS_INFO, port_) << "TURN create permission request sent"
|
|
<< ", id=" << rtc::hex_encode(id());
|
|
StunRequest::OnSent();
|
|
}
|
|
|
|
void TurnCreatePermissionRequest::OnResponse(StunMessage* response) {
|
|
LOG_J(LS_INFO, port_) << "TURN permission requested successfully"
|
|
<< ", id=" << rtc::hex_encode(id())
|
|
<< ", code=0" // Makes logging easier to parse.
|
|
<< ", rtt=" << Elapsed();
|
|
|
|
if (entry_) {
|
|
entry_->OnCreatePermissionSuccess();
|
|
}
|
|
}
|
|
|
|
void TurnCreatePermissionRequest::OnErrorResponse(StunMessage* response) {
|
|
const StunErrorCodeAttribute* error_code = response->GetErrorCode();
|
|
LOG_J(LS_WARNING, port_) << "Received TURN create permission error response"
|
|
<< ", id=" << rtc::hex_encode(id())
|
|
<< ", code=" << error_code->code()
|
|
<< ", rtt=" << Elapsed();
|
|
if (entry_) {
|
|
entry_->OnCreatePermissionError(response, error_code->code());
|
|
}
|
|
}
|
|
|
|
void TurnCreatePermissionRequest::OnTimeout() {
|
|
LOG_J(LS_WARNING, port_) << "TURN create permission timeout "
|
|
<< rtc::hex_encode(id());
|
|
if (entry_) {
|
|
entry_->OnCreatePermissionTimeout();
|
|
}
|
|
}
|
|
|
|
void TurnCreatePermissionRequest::OnEntryDestroyed(TurnEntry* entry) {
|
|
ASSERT(entry_ == entry);
|
|
entry_ = NULL;
|
|
}
|
|
|
|
TurnChannelBindRequest::TurnChannelBindRequest(
|
|
TurnPort* port, TurnEntry* entry,
|
|
int channel_id, const rtc::SocketAddress& ext_addr)
|
|
: StunRequest(new TurnMessage()),
|
|
port_(port),
|
|
entry_(entry),
|
|
channel_id_(channel_id),
|
|
ext_addr_(ext_addr) {
|
|
entry_->SignalDestroyed.connect(
|
|
this, &TurnChannelBindRequest::OnEntryDestroyed);
|
|
}
|
|
|
|
void TurnChannelBindRequest::Prepare(StunMessage* request) {
|
|
// Create the request as indicated in RFC5766, Section 11.1.
|
|
request->SetType(TURN_CHANNEL_BIND_REQUEST);
|
|
VERIFY(request->AddAttribute(new StunUInt32Attribute(
|
|
STUN_ATTR_CHANNEL_NUMBER, channel_id_ << 16)));
|
|
VERIFY(request->AddAttribute(new StunXorAddressAttribute(
|
|
STUN_ATTR_XOR_PEER_ADDRESS, ext_addr_)));
|
|
port_->AddRequestAuthInfo(request);
|
|
}
|
|
|
|
void TurnChannelBindRequest::OnSent() {
|
|
LOG_J(LS_INFO, port_) << "TURN channel bind request sent"
|
|
<< ", id=" << rtc::hex_encode(id());
|
|
StunRequest::OnSent();
|
|
}
|
|
|
|
void TurnChannelBindRequest::OnResponse(StunMessage* response) {
|
|
LOG_J(LS_INFO, port_) << "TURN channel bind requested successfully"
|
|
<< ", id=" << rtc::hex_encode(id())
|
|
<< ", code=0" // Makes logging easier to parse.
|
|
<< ", rtt=" << Elapsed();
|
|
|
|
if (entry_) {
|
|
entry_->OnChannelBindSuccess();
|
|
// Refresh the channel binding just under the permission timeout
|
|
// threshold. The channel binding has a longer lifetime, but
|
|
// this is the easiest way to keep both the channel and the
|
|
// permission from expiring.
|
|
int delay = TURN_PERMISSION_TIMEOUT - 60000;
|
|
entry_->SendChannelBindRequest(delay);
|
|
LOG_J(LS_INFO, port_) << "Scheduled channel bind in " << delay << "ms.";
|
|
}
|
|
}
|
|
|
|
void TurnChannelBindRequest::OnErrorResponse(StunMessage* response) {
|
|
const StunErrorCodeAttribute* error_code = response->GetErrorCode();
|
|
LOG_J(LS_WARNING, port_) << "Received TURN channel bind error response"
|
|
<< ", id=" << rtc::hex_encode(id())
|
|
<< ", code=" << error_code->code()
|
|
<< ", rtt=" << Elapsed();
|
|
if (entry_) {
|
|
entry_->OnChannelBindError(response, error_code->code());
|
|
}
|
|
}
|
|
|
|
void TurnChannelBindRequest::OnTimeout() {
|
|
LOG_J(LS_WARNING, port_) << "TURN channel bind timeout "
|
|
<< rtc::hex_encode(id());
|
|
if (entry_) {
|
|
entry_->OnChannelBindTimeout();
|
|
}
|
|
}
|
|
|
|
void TurnChannelBindRequest::OnEntryDestroyed(TurnEntry* entry) {
|
|
ASSERT(entry_ == entry);
|
|
entry_ = NULL;
|
|
}
|
|
|
|
TurnEntry::TurnEntry(TurnPort* port, int channel_id,
|
|
const rtc::SocketAddress& ext_addr)
|
|
: port_(port),
|
|
channel_id_(channel_id),
|
|
ext_addr_(ext_addr),
|
|
state_(STATE_UNBOUND) {
|
|
// Creating permission for |ext_addr_|.
|
|
SendCreatePermissionRequest(0);
|
|
}
|
|
|
|
void TurnEntry::SendCreatePermissionRequest(int delay) {
|
|
port_->SendRequest(new TurnCreatePermissionRequest(port_, this, ext_addr_),
|
|
delay);
|
|
}
|
|
|
|
void TurnEntry::SendChannelBindRequest(int delay) {
|
|
port_->SendRequest(new TurnChannelBindRequest(
|
|
port_, this, channel_id_, ext_addr_), delay);
|
|
}
|
|
|
|
int TurnEntry::Send(const void* data, size_t size, bool payload,
|
|
const rtc::PacketOptions& options) {
|
|
rtc::ByteBufferWriter buf;
|
|
if (state_ != STATE_BOUND) {
|
|
// If we haven't bound the channel yet, we have to use a Send Indication.
|
|
TurnMessage msg;
|
|
msg.SetType(TURN_SEND_INDICATION);
|
|
msg.SetTransactionID(
|
|
rtc::CreateRandomString(kStunTransactionIdLength));
|
|
VERIFY(msg.AddAttribute(new StunXorAddressAttribute(
|
|
STUN_ATTR_XOR_PEER_ADDRESS, ext_addr_)));
|
|
VERIFY(msg.AddAttribute(new StunByteStringAttribute(
|
|
STUN_ATTR_DATA, data, size)));
|
|
VERIFY(msg.Write(&buf));
|
|
|
|
// If we're sending real data, request a channel bind that we can use later.
|
|
if (state_ == STATE_UNBOUND && payload) {
|
|
SendChannelBindRequest(0);
|
|
state_ = STATE_BINDING;
|
|
}
|
|
} else {
|
|
// If the channel is bound, we can send the data as a Channel Message.
|
|
buf.WriteUInt16(channel_id_);
|
|
buf.WriteUInt16(static_cast<uint16_t>(size));
|
|
buf.WriteBytes(reinterpret_cast<const char*>(data), size);
|
|
}
|
|
return port_->Send(buf.Data(), buf.Length(), options);
|
|
}
|
|
|
|
void TurnEntry::OnCreatePermissionSuccess() {
|
|
LOG_J(LS_INFO, port_) << "Create permission for "
|
|
<< ext_addr_.ToSensitiveString()
|
|
<< " succeeded";
|
|
port_->SignalCreatePermissionResult(port_, ext_addr_,
|
|
TURN_SUCCESS_RESULT_CODE);
|
|
|
|
// If |state_| is STATE_BOUND, the permission will be refreshed
|
|
// by ChannelBindRequest.
|
|
if (state_ != STATE_BOUND) {
|
|
// Refresh the permission request about 1 minute before the permission
|
|
// times out.
|
|
int delay = TURN_PERMISSION_TIMEOUT - 60000;
|
|
SendCreatePermissionRequest(delay);
|
|
LOG_J(LS_INFO, port_) << "Scheduled create-permission-request in "
|
|
<< delay << "ms.";
|
|
}
|
|
}
|
|
|
|
void TurnEntry::OnCreatePermissionError(StunMessage* response, int code) {
|
|
if (code == STUN_ERROR_STALE_NONCE) {
|
|
if (port_->UpdateNonce(response)) {
|
|
SendCreatePermissionRequest(0);
|
|
}
|
|
} else {
|
|
port_->DestroyConnection(ext_addr_);
|
|
// Send signal with error code.
|
|
port_->SignalCreatePermissionResult(port_, ext_addr_, code);
|
|
Connection* c = port_->GetConnection(ext_addr_);
|
|
if (c) {
|
|
LOG_J(LS_ERROR, c) << "Received TURN CreatePermission error response, "
|
|
<< "code=" << code << "; killing connection.";
|
|
c->FailAndDestroy();
|
|
}
|
|
}
|
|
}
|
|
|
|
void TurnEntry::OnCreatePermissionTimeout() {
|
|
port_->DestroyConnection(ext_addr_);
|
|
}
|
|
|
|
void TurnEntry::OnChannelBindSuccess() {
|
|
LOG_J(LS_INFO, port_) << "Channel bind for " << ext_addr_.ToSensitiveString()
|
|
<< " succeeded";
|
|
ASSERT(state_ == STATE_BINDING || state_ == STATE_BOUND);
|
|
state_ = STATE_BOUND;
|
|
}
|
|
|
|
void TurnEntry::OnChannelBindError(StunMessage* response, int code) {
|
|
// If the channel bind fails due to errors other than STATE_NONCE,
|
|
// we just destroy the connection and rely on ICE restart to re-establish
|
|
// the connection.
|
|
if (code == STUN_ERROR_STALE_NONCE) {
|
|
if (port_->UpdateNonce(response)) {
|
|
// Send channel bind request with fresh nonce.
|
|
SendChannelBindRequest(0);
|
|
}
|
|
} else {
|
|
state_ = STATE_UNBOUND;
|
|
port_->DestroyConnection(ext_addr_);
|
|
}
|
|
}
|
|
void TurnEntry::OnChannelBindTimeout() {
|
|
state_ = STATE_UNBOUND;
|
|
port_->DestroyConnection(ext_addr_);
|
|
}
|
|
} // namespace cricket
|