I gave up on removing proxy_info, user_agent and tcp_options. I don't think it's feasible to remove them without removing all the proxy code. The assumption that you can set the proxy and user agent long after you have created the factory is entrenched in unit tests and the code itself. So is the ability to set tcp opts depending on protocol or endpoint properties. It may be easier to untangle proxy stuff from the factory later, when it becomes a more first-class citizen and isn't passed via the allocator. Requires https://chromium-review.googlesource.com/c/chromium/src/+/1778870 to land first. Bug: webrtc:7447 Change-Id: Ib496e2bb689ea415e9f8ec1dfedff13a83fa4a8a Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/150799 Commit-Queue: Patrik Höglund <phoglund@webrtc.org> Reviewed-by: Niels Moller <nisse@webrtc.org> Cr-Commit-Position: refs/heads/master@{#29091}
861 lines
28 KiB
C++
861 lines
28 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/relay_port.h"
|
|
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
|
|
#include <algorithm>
|
|
|
|
#include "p2p/base/connection.h"
|
|
#include "p2p/base/stun.h"
|
|
#include "p2p/base/stun_request.h"
|
|
#include "rtc_base/async_packet_socket.h"
|
|
#include "rtc_base/byte_buffer.h"
|
|
#include "rtc_base/checks.h"
|
|
#include "rtc_base/dscp.h"
|
|
#include "rtc_base/location.h"
|
|
#include "rtc_base/logging.h"
|
|
#include "rtc_base/message_handler.h"
|
|
#include "rtc_base/message_queue.h"
|
|
#include "rtc_base/net_helper.h"
|
|
#include "rtc_base/proxy_info.h"
|
|
#include "rtc_base/time_utils.h"
|
|
|
|
namespace cricket {
|
|
|
|
static const int kMessageConnectTimeout = 1;
|
|
static const int kKeepAliveDelay = 10 * 60 * 1000;
|
|
static const int kRetryTimeout = 50 * 1000; // ICE says 50 secs
|
|
// How long to wait for a socket to connect to remote host in milliseconds
|
|
// before trying another connection.
|
|
static const int kSoftConnectTimeoutMs = 3 * 1000;
|
|
|
|
// Handles a connection to one address/port/protocol combination for a
|
|
// particular RelayEntry.
|
|
class RelayConnection : public sigslot::has_slots<> {
|
|
public:
|
|
RelayConnection(const ProtocolAddress* protocol_address,
|
|
rtc::AsyncPacketSocket* socket,
|
|
rtc::Thread* thread);
|
|
~RelayConnection() override;
|
|
rtc::AsyncPacketSocket* socket() const { return socket_; }
|
|
|
|
const ProtocolAddress* protocol_address() { return protocol_address_; }
|
|
|
|
rtc::SocketAddress GetAddress() const { return protocol_address_->address; }
|
|
|
|
ProtocolType GetProtocol() const { return protocol_address_->proto; }
|
|
|
|
int SetSocketOption(rtc::Socket::Option opt, int value);
|
|
|
|
// Validates a response to a STUN allocate request.
|
|
bool CheckResponse(StunMessage* msg);
|
|
|
|
// Sends data to the relay server.
|
|
int Send(const void* pv, size_t cb, const rtc::PacketOptions& options);
|
|
|
|
// Sends a STUN allocate request message to the relay server.
|
|
void SendAllocateRequest(RelayEntry* entry, int delay);
|
|
|
|
// Return the latest error generated by the socket.
|
|
int GetError() { return socket_->GetError(); }
|
|
|
|
// Called on behalf of a StunRequest to write data to the socket. This is
|
|
// already STUN intended for the server, so no wrapping is necessary.
|
|
void OnSendPacket(const void* data, size_t size, StunRequest* req);
|
|
|
|
private:
|
|
rtc::AsyncPacketSocket* socket_;
|
|
const ProtocolAddress* protocol_address_;
|
|
StunRequestManager* request_manager_;
|
|
rtc::DiffServCodePoint dscp_;
|
|
};
|
|
|
|
// Manages a number of connections to the relayserver, one for each
|
|
// available protocol. We aim to use each connection for only a
|
|
// specific destination address so that we can avoid wrapping every
|
|
// packet in a STUN send / data indication.
|
|
class RelayEntry : public rtc::MessageHandler, public sigslot::has_slots<> {
|
|
public:
|
|
RelayEntry(RelayPort* port, const rtc::SocketAddress& ext_addr);
|
|
~RelayEntry() override;
|
|
|
|
RelayPort* port() { return port_; }
|
|
|
|
const rtc::SocketAddress& address() const { return ext_addr_; }
|
|
void set_address(const rtc::SocketAddress& addr) { ext_addr_ = addr; }
|
|
|
|
bool connected() const { return connected_; }
|
|
bool locked() const { return locked_; }
|
|
|
|
// Returns the last error on the socket of this entry.
|
|
int GetError();
|
|
|
|
// Returns the most preferred connection of the given
|
|
// ones. Connections are rated based on protocol in the order of:
|
|
// UDP, TCP and SSLTCP, where UDP is the most preferred protocol
|
|
static RelayConnection* GetBestConnection(RelayConnection* conn1,
|
|
RelayConnection* conn2);
|
|
|
|
// Sends the STUN requests to the server to initiate this connection.
|
|
void Connect();
|
|
|
|
// Called when this entry becomes connected. The address given is the one
|
|
// exposed to the outside world on the relay server.
|
|
void OnConnect(const rtc::SocketAddress& mapped_addr,
|
|
RelayConnection* socket);
|
|
|
|
// Sends a packet to the given destination address using the socket of this
|
|
// entry. This will wrap the packet in STUN if necessary.
|
|
int SendTo(const void* data,
|
|
size_t size,
|
|
const rtc::SocketAddress& addr,
|
|
const rtc::PacketOptions& options);
|
|
|
|
// Schedules a keep-alive allocate request.
|
|
void ScheduleKeepAlive();
|
|
|
|
void SetServerIndex(size_t sindex) { server_index_ = sindex; }
|
|
|
|
// Sets this option on the socket of each connection.
|
|
int SetSocketOption(rtc::Socket::Option opt, int value);
|
|
|
|
size_t ServerIndex() const { return server_index_; }
|
|
|
|
// Try a different server address
|
|
void HandleConnectFailure(rtc::AsyncPacketSocket* socket);
|
|
|
|
// Implementation of the MessageHandler Interface.
|
|
void OnMessage(rtc::Message* pmsg) override;
|
|
|
|
private:
|
|
RelayPort* port_;
|
|
rtc::SocketAddress ext_addr_;
|
|
size_t server_index_;
|
|
bool connected_;
|
|
bool locked_;
|
|
RelayConnection* current_connection_;
|
|
|
|
// Called when a TCP connection is established or fails
|
|
void OnSocketConnect(rtc::AsyncPacketSocket* socket);
|
|
void OnSocketClose(rtc::AsyncPacketSocket* socket, int error);
|
|
|
|
// Called when a packet is received on this socket.
|
|
void OnReadPacket(rtc::AsyncPacketSocket* socket,
|
|
const char* data,
|
|
size_t size,
|
|
const rtc::SocketAddress& remote_addr,
|
|
const int64_t& packet_time_us);
|
|
|
|
void OnSentPacket(rtc::AsyncPacketSocket* socket,
|
|
const rtc::SentPacket& sent_packet);
|
|
|
|
// Called when the socket is currently able to send.
|
|
void OnReadyToSend(rtc::AsyncPacketSocket* socket);
|
|
|
|
// Sends the given data on the socket to the server with no wrapping. This
|
|
// returns the number of bytes written or -1 if an error occurred.
|
|
int SendPacket(const void* data,
|
|
size_t size,
|
|
const rtc::PacketOptions& options);
|
|
};
|
|
|
|
// Handles an allocate request for a particular RelayEntry.
|
|
class AllocateRequest : public StunRequest {
|
|
public:
|
|
AllocateRequest(RelayEntry* entry, RelayConnection* connection);
|
|
~AllocateRequest() override = default;
|
|
|
|
void Prepare(StunMessage* request) override;
|
|
|
|
void OnSent() override;
|
|
int resend_delay() override;
|
|
|
|
void OnResponse(StunMessage* response) override;
|
|
void OnErrorResponse(StunMessage* response) override;
|
|
void OnTimeout() override;
|
|
|
|
private:
|
|
RelayEntry* entry_;
|
|
RelayConnection* connection_;
|
|
int64_t start_time_;
|
|
};
|
|
|
|
RelayPort::RelayPort(rtc::Thread* thread,
|
|
rtc::PacketSocketFactory* factory,
|
|
rtc::Network* network,
|
|
uint16_t min_port,
|
|
uint16_t max_port,
|
|
const std::string& username,
|
|
const std::string& password)
|
|
: Port(thread,
|
|
RELAY_PORT_TYPE,
|
|
factory,
|
|
network,
|
|
min_port,
|
|
max_port,
|
|
username,
|
|
password),
|
|
ready_(false),
|
|
error_(0) {
|
|
entries_.push_back(new RelayEntry(this, rtc::SocketAddress()));
|
|
// TODO(?): set local preference value for TCP based candidates.
|
|
}
|
|
|
|
RelayPort::~RelayPort() {
|
|
for (size_t i = 0; i < entries_.size(); ++i)
|
|
delete entries_[i];
|
|
thread()->Clear(this);
|
|
}
|
|
|
|
void RelayPort::AddServerAddress(const ProtocolAddress& addr) {
|
|
// Since HTTP proxies usually only allow 443,
|
|
// let's up the priority on PROTO_SSLTCP
|
|
if (addr.proto == PROTO_SSLTCP && (proxy().type == rtc::PROXY_HTTPS ||
|
|
proxy().type == rtc::PROXY_UNKNOWN)) {
|
|
server_addr_.push_front(addr);
|
|
} else {
|
|
server_addr_.push_back(addr);
|
|
}
|
|
}
|
|
|
|
void RelayPort::AddExternalAddress(const ProtocolAddress& addr) {
|
|
std::string proto_name = ProtoToString(addr.proto);
|
|
for (std::vector<ProtocolAddress>::iterator it = external_addr_.begin();
|
|
it != external_addr_.end(); ++it) {
|
|
if ((it->address == addr.address) && (it->proto == addr.proto)) {
|
|
RTC_LOG(INFO) << "Redundant relay address: " << proto_name << " @ "
|
|
<< addr.address.ToSensitiveString();
|
|
return;
|
|
}
|
|
}
|
|
external_addr_.push_back(addr);
|
|
}
|
|
|
|
void RelayPort::SetReady() {
|
|
if (!ready_) {
|
|
std::vector<ProtocolAddress>::iterator iter;
|
|
for (iter = external_addr_.begin(); iter != external_addr_.end(); ++iter) {
|
|
std::string proto_name = ProtoToString(iter->proto);
|
|
// In case of Gturn, related address is set to null socket address.
|
|
// This is due to as mapped address stun attribute is used for allocated
|
|
// address.
|
|
AddAddress(iter->address, iter->address, rtc::SocketAddress(), proto_name,
|
|
proto_name, "", RELAY_PORT_TYPE, ICE_TYPE_PREFERENCE_RELAY_UDP,
|
|
0, "", false);
|
|
}
|
|
ready_ = true;
|
|
SignalPortComplete(this);
|
|
}
|
|
}
|
|
|
|
const ProtocolAddress* RelayPort::ServerAddress(size_t index) const {
|
|
if (index < server_addr_.size())
|
|
return &server_addr_[index];
|
|
return NULL;
|
|
}
|
|
|
|
bool RelayPort::HasMagicCookie(const char* data, size_t size) {
|
|
if (size < 24 + sizeof(TURN_MAGIC_COOKIE_VALUE)) {
|
|
return false;
|
|
} else {
|
|
return memcmp(data + 24, TURN_MAGIC_COOKIE_VALUE,
|
|
sizeof(TURN_MAGIC_COOKIE_VALUE)) == 0;
|
|
}
|
|
}
|
|
|
|
void RelayPort::PrepareAddress() {
|
|
// We initiate a connect on the first entry. If this completes, it will fill
|
|
// in the server address as the address of this port.
|
|
RTC_DCHECK(entries_.size() == 1);
|
|
entries_[0]->Connect();
|
|
ready_ = false;
|
|
}
|
|
|
|
Connection* RelayPort::CreateConnection(const Candidate& address,
|
|
CandidateOrigin origin) {
|
|
// We only create conns to non-udp sockets if they are incoming on this port
|
|
if ((address.protocol() != UDP_PROTOCOL_NAME) &&
|
|
(origin != ORIGIN_THIS_PORT)) {
|
|
return 0;
|
|
}
|
|
|
|
// We don't support loopback on relays
|
|
if (address.type() == Type()) {
|
|
return 0;
|
|
}
|
|
|
|
if (!IsCompatibleAddress(address.address())) {
|
|
return 0;
|
|
}
|
|
|
|
size_t index = 0;
|
|
for (size_t i = 0; i < Candidates().size(); ++i) {
|
|
const Candidate& local = Candidates()[i];
|
|
if (local.protocol() == address.protocol()) {
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Connection* conn = new ProxyConnection(this, index, address);
|
|
AddOrReplaceConnection(conn);
|
|
return conn;
|
|
}
|
|
|
|
int RelayPort::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. Note that the first entry
|
|
// created was not given an address initially, so it can be set to the first
|
|
// address that comes along.
|
|
RelayEntry* entry = 0;
|
|
|
|
for (size_t i = 0; i < entries_.size(); ++i) {
|
|
if (entries_[i]->address().IsNil() && payload) {
|
|
entry = entries_[i];
|
|
entry->set_address(addr);
|
|
break;
|
|
} else if (entries_[i]->address() == addr) {
|
|
entry = entries_[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we did not find one, then we make a new one. This will not be useable
|
|
// until it becomes connected, however.
|
|
if (!entry && payload) {
|
|
entry = new RelayEntry(this, addr);
|
|
if (!entries_.empty()) {
|
|
entry->SetServerIndex(entries_[0]->ServerIndex());
|
|
}
|
|
entry->Connect();
|
|
entries_.push_back(entry);
|
|
}
|
|
|
|
// If the entry is connected, then we can send on it (though wrapping may
|
|
// still be necessary). Otherwise, we can't yet use this connection, so we
|
|
// default to the first one.
|
|
if (!entry || !entry->connected()) {
|
|
RTC_DCHECK(!entries_.empty());
|
|
entry = entries_[0];
|
|
if (!entry->connected()) {
|
|
error_ = ENOTCONN;
|
|
return SOCKET_ERROR;
|
|
}
|
|
}
|
|
|
|
// Send the actual contents to the server using the usual mechanism.
|
|
rtc::PacketOptions modified_options(options);
|
|
CopyPortInformationToPacketInfo(&modified_options.info_signaled_after_sent);
|
|
int sent = entry->SendTo(data, size, addr, modified_options);
|
|
if (sent <= 0) {
|
|
RTC_DCHECK(sent < 0);
|
|
error_ = entry->GetError();
|
|
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);
|
|
}
|
|
|
|
int RelayPort::SetOption(rtc::Socket::Option opt, int value) {
|
|
int result = 0;
|
|
for (size_t i = 0; i < entries_.size(); ++i) {
|
|
if (entries_[i]->SetSocketOption(opt, value) < 0) {
|
|
result = -1;
|
|
error_ = entries_[i]->GetError();
|
|
}
|
|
}
|
|
options_.push_back(OptionValue(opt, value));
|
|
return result;
|
|
}
|
|
|
|
int RelayPort::GetOption(rtc::Socket::Option opt, int* value) {
|
|
std::vector<OptionValue>::iterator it;
|
|
for (it = options_.begin(); it < options_.end(); ++it) {
|
|
if (it->first == opt) {
|
|
*value = it->second;
|
|
return 0;
|
|
}
|
|
}
|
|
return SOCKET_ERROR;
|
|
}
|
|
|
|
int RelayPort::GetError() {
|
|
return error_;
|
|
}
|
|
|
|
bool RelayPort::SupportsProtocol(const std::string& protocol) const {
|
|
// Relay port may create both TCP and UDP connections.
|
|
return true;
|
|
}
|
|
|
|
ProtocolType RelayPort::GetProtocol() const {
|
|
// We shouldn't be using RelayPort, but we need to provide an implementation
|
|
// here.
|
|
return PROTO_UDP;
|
|
}
|
|
|
|
void RelayPort::OnReadPacket(const char* data,
|
|
size_t size,
|
|
const rtc::SocketAddress& remote_addr,
|
|
ProtocolType proto,
|
|
int64_t packet_time_us) {
|
|
if (Connection* conn = GetConnection(remote_addr)) {
|
|
conn->OnReadPacket(data, size, packet_time_us);
|
|
} else {
|
|
Port::OnReadPacket(data, size, remote_addr, proto);
|
|
}
|
|
}
|
|
|
|
RelayConnection::RelayConnection(const ProtocolAddress* protocol_address,
|
|
rtc::AsyncPacketSocket* socket,
|
|
rtc::Thread* thread)
|
|
: socket_(socket),
|
|
protocol_address_(protocol_address),
|
|
dscp_(rtc::DSCP_NO_CHANGE) {
|
|
request_manager_ = new StunRequestManager(thread);
|
|
request_manager_->SignalSendPacket.connect(this,
|
|
&RelayConnection::OnSendPacket);
|
|
}
|
|
|
|
RelayConnection::~RelayConnection() {
|
|
delete request_manager_;
|
|
delete socket_;
|
|
}
|
|
|
|
int RelayConnection::SetSocketOption(rtc::Socket::Option opt, int value) {
|
|
if (opt == rtc::Socket::OPT_DSCP) {
|
|
dscp_ = static_cast<rtc::DiffServCodePoint>(value);
|
|
}
|
|
if (socket_) {
|
|
return socket_->SetOption(opt, value);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool RelayConnection::CheckResponse(StunMessage* msg) {
|
|
return request_manager_->CheckResponse(msg);
|
|
}
|
|
|
|
void RelayConnection::OnSendPacket(const void* data,
|
|
size_t size,
|
|
StunRequest* req) {
|
|
rtc::PacketOptions options(dscp_);
|
|
int sent = socket_->SendTo(data, size, GetAddress(), options);
|
|
if (sent <= 0) {
|
|
RTC_LOG(LS_VERBOSE) << "OnSendPacket: failed sending to "
|
|
<< GetAddress().ToString()
|
|
<< strerror(socket_->GetError());
|
|
RTC_DCHECK(sent < 0);
|
|
}
|
|
}
|
|
|
|
int RelayConnection::Send(const void* pv,
|
|
size_t cb,
|
|
const rtc::PacketOptions& options) {
|
|
return socket_->SendTo(pv, cb, GetAddress(), options);
|
|
}
|
|
|
|
void RelayConnection::SendAllocateRequest(RelayEntry* entry, int delay) {
|
|
request_manager_->SendDelayed(new AllocateRequest(entry, this), delay);
|
|
}
|
|
|
|
RelayEntry::RelayEntry(RelayPort* port, const rtc::SocketAddress& ext_addr)
|
|
: port_(port),
|
|
ext_addr_(ext_addr),
|
|
server_index_(0),
|
|
connected_(false),
|
|
locked_(false),
|
|
current_connection_(NULL) {}
|
|
|
|
RelayEntry::~RelayEntry() {
|
|
// Remove all RelayConnections and dispose sockets.
|
|
delete current_connection_;
|
|
current_connection_ = NULL;
|
|
}
|
|
|
|
void RelayEntry::Connect() {
|
|
// If we're already connected, return.
|
|
if (connected_)
|
|
return;
|
|
|
|
// If we've exhausted all options, bail out.
|
|
const ProtocolAddress* ra = port()->ServerAddress(server_index_);
|
|
if (!ra) {
|
|
RTC_LOG(LS_WARNING) << "No more relay addresses left to try";
|
|
return;
|
|
}
|
|
|
|
// Remove any previous connection.
|
|
if (current_connection_) {
|
|
port()->thread()->Dispose(current_connection_);
|
|
current_connection_ = NULL;
|
|
}
|
|
|
|
// Try to set up our new socket.
|
|
RTC_LOG(LS_INFO) << "Connecting to relay via " << ProtoToString(ra->proto)
|
|
<< " @ " << ra->address.ToSensitiveString();
|
|
|
|
rtc::AsyncPacketSocket* socket = NULL;
|
|
|
|
if (ra->proto == PROTO_UDP) {
|
|
// UDP sockets are simple.
|
|
socket = port_->socket_factory()->CreateUdpSocket(
|
|
rtc::SocketAddress(port_->Network()->GetBestIP(), 0), port_->min_port(),
|
|
port_->max_port());
|
|
} else if (ra->proto == PROTO_TCP || ra->proto == PROTO_SSLTCP) {
|
|
int opts = (ra->proto == PROTO_SSLTCP)
|
|
? rtc::PacketSocketFactory::OPT_TLS_FAKE
|
|
: 0;
|
|
rtc::PacketSocketTcpOptions tcp_opts;
|
|
tcp_opts.opts = opts;
|
|
socket = port_->socket_factory()->CreateClientTcpSocket(
|
|
rtc::SocketAddress(port_->Network()->GetBestIP(), 0), ra->address,
|
|
port_->proxy(), port_->user_agent(), tcp_opts);
|
|
} else {
|
|
RTC_LOG(LS_WARNING) << "Unknown protocol: " << ra->proto;
|
|
}
|
|
|
|
// If we failed to get a socket, move on to the next protocol.
|
|
if (!socket) {
|
|
RTC_LOG(LS_WARNING) << "Socket creation failed";
|
|
port()->thread()->Post(RTC_FROM_HERE, this, kMessageConnectTimeout);
|
|
return;
|
|
}
|
|
|
|
// Otherwise, create the new connection and configure any socket options.
|
|
socket->SignalReadPacket.connect(this, &RelayEntry::OnReadPacket);
|
|
socket->SignalSentPacket.connect(this, &RelayEntry::OnSentPacket);
|
|
socket->SignalReadyToSend.connect(this, &RelayEntry::OnReadyToSend);
|
|
current_connection_ = new RelayConnection(ra, socket, port()->thread());
|
|
for (size_t i = 0; i < port_->options().size(); ++i) {
|
|
current_connection_->SetSocketOption(port_->options()[i].first,
|
|
port_->options()[i].second);
|
|
}
|
|
|
|
// If we're trying UDP, start binding requests.
|
|
// If we're trying TCP, wait for connection with a fixed timeout.
|
|
if ((ra->proto == PROTO_TCP) || (ra->proto == PROTO_SSLTCP)) {
|
|
socket->SignalClose.connect(this, &RelayEntry::OnSocketClose);
|
|
socket->SignalConnect.connect(this, &RelayEntry::OnSocketConnect);
|
|
port()->thread()->PostDelayed(RTC_FROM_HERE, kSoftConnectTimeoutMs, this,
|
|
kMessageConnectTimeout);
|
|
} else {
|
|
current_connection_->SendAllocateRequest(this, 0);
|
|
}
|
|
}
|
|
|
|
int RelayEntry::GetError() {
|
|
if (current_connection_ != NULL) {
|
|
return current_connection_->GetError();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
RelayConnection* RelayEntry::GetBestConnection(RelayConnection* conn1,
|
|
RelayConnection* conn2) {
|
|
return conn1->GetProtocol() <= conn2->GetProtocol() ? conn1 : conn2;
|
|
}
|
|
|
|
void RelayEntry::OnConnect(const rtc::SocketAddress& mapped_addr,
|
|
RelayConnection* connection) {
|
|
// We are connected, notify our parent.
|
|
ProtocolType proto = PROTO_UDP;
|
|
RTC_LOG(INFO) << "Relay allocate succeeded: " << ProtoToString(proto) << " @ "
|
|
<< mapped_addr.ToSensitiveString();
|
|
connected_ = true;
|
|
|
|
port_->AddExternalAddress(ProtocolAddress(mapped_addr, proto));
|
|
port_->SetReady();
|
|
}
|
|
|
|
int RelayEntry::SendTo(const void* data,
|
|
size_t size,
|
|
const rtc::SocketAddress& addr,
|
|
const rtc::PacketOptions& options) {
|
|
// If this connection is locked to the address given, then we can send the
|
|
// packet with no wrapper.
|
|
if (locked_ && (ext_addr_ == addr))
|
|
return SendPacket(data, size, options);
|
|
|
|
// Otherwise, we must wrap the given data in a STUN SEND request so that we
|
|
// can communicate the destination address to the server.
|
|
//
|
|
// Note that we do not use a StunRequest here. This is because there is
|
|
// likely no reason to resend this packet. If it is late, we just drop it.
|
|
// The next send to this address will try again.
|
|
|
|
RelayMessage request;
|
|
request.SetType(STUN_SEND_REQUEST);
|
|
|
|
auto magic_cookie_attr =
|
|
StunAttribute::CreateByteString(STUN_ATTR_MAGIC_COOKIE);
|
|
magic_cookie_attr->CopyBytes(TURN_MAGIC_COOKIE_VALUE,
|
|
sizeof(TURN_MAGIC_COOKIE_VALUE));
|
|
request.AddAttribute(std::move(magic_cookie_attr));
|
|
|
|
auto username_attr = StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
|
|
username_attr->CopyBytes(port_->username_fragment().c_str(),
|
|
port_->username_fragment().size());
|
|
request.AddAttribute(std::move(username_attr));
|
|
|
|
auto addr_attr = StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS);
|
|
addr_attr->SetIP(addr.ipaddr());
|
|
addr_attr->SetPort(addr.port());
|
|
request.AddAttribute(std::move(addr_attr));
|
|
|
|
// Attempt to lock
|
|
if (ext_addr_ == addr) {
|
|
auto options_attr = StunAttribute::CreateUInt32(STUN_ATTR_OPTIONS);
|
|
options_attr->SetValue(0x1);
|
|
request.AddAttribute(std::move(options_attr));
|
|
}
|
|
|
|
auto data_attr = StunAttribute::CreateByteString(STUN_ATTR_DATA);
|
|
data_attr->CopyBytes(data, size);
|
|
request.AddAttribute(std::move(data_attr));
|
|
|
|
// TODO(?): compute the HMAC.
|
|
|
|
rtc::ByteBufferWriter buf;
|
|
request.Write(&buf);
|
|
|
|
return SendPacket(buf.Data(), buf.Length(), options);
|
|
}
|
|
|
|
void RelayEntry::ScheduleKeepAlive() {
|
|
if (current_connection_) {
|
|
current_connection_->SendAllocateRequest(this, kKeepAliveDelay);
|
|
}
|
|
}
|
|
|
|
int RelayEntry::SetSocketOption(rtc::Socket::Option opt, int value) {
|
|
// Set the option on all available sockets.
|
|
int socket_error = 0;
|
|
if (current_connection_) {
|
|
socket_error = current_connection_->SetSocketOption(opt, value);
|
|
}
|
|
return socket_error;
|
|
}
|
|
|
|
void RelayEntry::HandleConnectFailure(rtc::AsyncPacketSocket* socket) {
|
|
// Make sure it's the current connection that has failed, it might
|
|
// be an old socked that has not yet been disposed.
|
|
if (!socket ||
|
|
(current_connection_ && socket == current_connection_->socket())) {
|
|
if (current_connection_)
|
|
port()->SignalConnectFailure(current_connection_->protocol_address());
|
|
|
|
// Try to connect to the next server address.
|
|
server_index_ += 1;
|
|
Connect();
|
|
}
|
|
}
|
|
|
|
void RelayEntry::OnMessage(rtc::Message* pmsg) {
|
|
RTC_DCHECK(pmsg->message_id == kMessageConnectTimeout);
|
|
if (current_connection_) {
|
|
const ProtocolAddress* ra = current_connection_->protocol_address();
|
|
RTC_LOG(LS_WARNING) << "Relay " << ra->proto << " connection to "
|
|
<< ra->address.ToString() << " timed out";
|
|
|
|
// Currently we connect to each server address in sequence. If we
|
|
// have more addresses to try, treat this is an error and move on to
|
|
// the next address, otherwise give this connection more time and
|
|
// await the real timeout.
|
|
//
|
|
// TODO(?): Connect to servers in parallel to speed up connect time
|
|
// and to avoid giving up too early.
|
|
port_->SignalSoftTimeout(ra);
|
|
HandleConnectFailure(current_connection_->socket());
|
|
} else {
|
|
HandleConnectFailure(NULL);
|
|
}
|
|
}
|
|
|
|
void RelayEntry::OnSocketConnect(rtc::AsyncPacketSocket* socket) {
|
|
RTC_LOG(INFO) << "relay tcp connected to "
|
|
<< socket->GetRemoteAddress().ToSensitiveString();
|
|
if (current_connection_ != NULL) {
|
|
current_connection_->SendAllocateRequest(this, 0);
|
|
}
|
|
}
|
|
|
|
void RelayEntry::OnSocketClose(rtc::AsyncPacketSocket* socket, int error) {
|
|
RTC_LOG_ERR_EX(LERROR, error) << "Relay connection failed: socket closed";
|
|
HandleConnectFailure(socket);
|
|
}
|
|
|
|
void RelayEntry::OnReadPacket(rtc::AsyncPacketSocket* socket,
|
|
const char* data,
|
|
size_t size,
|
|
const rtc::SocketAddress& remote_addr,
|
|
const int64_t& packet_time_us) {
|
|
// RTC_DCHECK(remote_addr == port_->server_addr());
|
|
// TODO(?): are we worried about this?
|
|
|
|
if (current_connection_ == NULL || socket != current_connection_->socket()) {
|
|
// This packet comes from an unknown address.
|
|
RTC_LOG(WARNING) << "Dropping packet: unknown address";
|
|
return;
|
|
}
|
|
|
|
// If the magic cookie is not present, then this is an unwrapped packet sent
|
|
// by the server, The actual remote address is the one we recorded.
|
|
if (!port_->HasMagicCookie(data, size)) {
|
|
if (locked_) {
|
|
port_->OnReadPacket(data, size, ext_addr_, PROTO_UDP, packet_time_us);
|
|
} else {
|
|
RTC_LOG(WARNING) << "Dropping packet: entry not locked";
|
|
}
|
|
return;
|
|
}
|
|
|
|
rtc::ByteBufferReader buf(data, size);
|
|
RelayMessage msg;
|
|
if (!msg.Read(&buf)) {
|
|
RTC_LOG(INFO) << "Incoming packet was not STUN";
|
|
return;
|
|
}
|
|
|
|
// The incoming packet should be a STUN ALLOCATE response, SEND response, or
|
|
// DATA indication.
|
|
if (current_connection_->CheckResponse(&msg)) {
|
|
return;
|
|
} else if (msg.type() == STUN_SEND_RESPONSE) {
|
|
if (const StunUInt32Attribute* options_attr =
|
|
msg.GetUInt32(STUN_ATTR_OPTIONS)) {
|
|
if (options_attr->value() & 0x1) {
|
|
locked_ = true;
|
|
}
|
|
}
|
|
return;
|
|
} else if (msg.type() != STUN_DATA_INDICATION) {
|
|
RTC_LOG(INFO) << "Received BAD stun type from server: " << msg.type();
|
|
return;
|
|
}
|
|
|
|
// This must be a data indication.
|
|
|
|
const StunAddressAttribute* addr_attr =
|
|
msg.GetAddress(STUN_ATTR_SOURCE_ADDRESS2);
|
|
if (!addr_attr) {
|
|
RTC_LOG(INFO) << "Data indication has no source address";
|
|
return;
|
|
} else if (addr_attr->family() != 1) {
|
|
RTC_LOG(INFO) << "Source address has bad family";
|
|
return;
|
|
}
|
|
|
|
rtc::SocketAddress remote_addr2(addr_attr->ipaddr(), addr_attr->port());
|
|
|
|
const StunByteStringAttribute* data_attr = msg.GetByteString(STUN_ATTR_DATA);
|
|
if (!data_attr) {
|
|
RTC_LOG(INFO) << "Data indication has no data";
|
|
return;
|
|
}
|
|
|
|
// Process the actual data and remote address in the normal manner.
|
|
port_->OnReadPacket(data_attr->bytes(), data_attr->length(), remote_addr2,
|
|
PROTO_UDP, packet_time_us);
|
|
}
|
|
|
|
void RelayEntry::OnSentPacket(rtc::AsyncPacketSocket* socket,
|
|
const rtc::SentPacket& sent_packet) {
|
|
port_->OnSentPacket(socket, sent_packet);
|
|
}
|
|
|
|
void RelayEntry::OnReadyToSend(rtc::AsyncPacketSocket* socket) {
|
|
if (connected()) {
|
|
port_->OnReadyToSend();
|
|
}
|
|
}
|
|
|
|
int RelayEntry::SendPacket(const void* data,
|
|
size_t size,
|
|
const rtc::PacketOptions& options) {
|
|
int sent = 0;
|
|
if (current_connection_) {
|
|
// We are connected, no need to send packets anywere else than to
|
|
// the current connection.
|
|
sent = current_connection_->Send(data, size, options);
|
|
}
|
|
return sent;
|
|
}
|
|
|
|
AllocateRequest::AllocateRequest(RelayEntry* entry, RelayConnection* connection)
|
|
: StunRequest(new RelayMessage()), entry_(entry), connection_(connection) {
|
|
start_time_ = rtc::TimeMillis();
|
|
}
|
|
|
|
void AllocateRequest::Prepare(StunMessage* request) {
|
|
request->SetType(STUN_ALLOCATE_REQUEST);
|
|
|
|
auto username_attr = StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
|
|
username_attr->CopyBytes(entry_->port()->username_fragment().c_str(),
|
|
entry_->port()->username_fragment().size());
|
|
request->AddAttribute(std::move(username_attr));
|
|
}
|
|
|
|
void AllocateRequest::OnSent() {
|
|
count_ += 1;
|
|
if (count_ == 5)
|
|
timeout_ = true;
|
|
}
|
|
|
|
int AllocateRequest::resend_delay() {
|
|
if (count_ == 0) {
|
|
return 0;
|
|
}
|
|
return 100 * std::max(1 << (count_ - 1), 2);
|
|
}
|
|
|
|
void AllocateRequest::OnResponse(StunMessage* response) {
|
|
const StunAddressAttribute* addr_attr =
|
|
response->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
|
|
if (!addr_attr) {
|
|
RTC_LOG(INFO) << "Allocate response missing mapped address.";
|
|
} else if (addr_attr->family() != 1) {
|
|
RTC_LOG(INFO) << "Mapped address has bad family";
|
|
} else {
|
|
rtc::SocketAddress addr(addr_attr->ipaddr(), addr_attr->port());
|
|
entry_->OnConnect(addr, connection_);
|
|
}
|
|
|
|
// We will do a keep-alive regardless of whether this request suceeds.
|
|
// This should have almost no impact on network usage.
|
|
entry_->ScheduleKeepAlive();
|
|
}
|
|
|
|
void AllocateRequest::OnErrorResponse(StunMessage* response) {
|
|
const StunErrorCodeAttribute* attr = response->GetErrorCode();
|
|
if (!attr) {
|
|
RTC_LOG(LS_ERROR) << "Missing allocate response error code.";
|
|
} else {
|
|
RTC_LOG(INFO) << "Allocate error response: code=" << attr->code()
|
|
<< " reason=" << attr->reason();
|
|
}
|
|
|
|
if (rtc::TimeMillis() - start_time_ <= kRetryTimeout)
|
|
entry_->ScheduleKeepAlive();
|
|
}
|
|
|
|
void AllocateRequest::OnTimeout() {
|
|
RTC_LOG(INFO) << "Allocate request timed out";
|
|
entry_->HandleConnectFailure(connection_->socket());
|
|
}
|
|
|
|
} // namespace cricket
|