aboutsummaryrefslogtreecommitdiff
path: root/webrtc/p2p/base/turnport.cc
diff options
context:
space:
mode:
Diffstat (limited to 'webrtc/p2p/base/turnport.cc')
-rw-r--r--webrtc/p2p/base/turnport.cc1371
1 files changed, 1371 insertions, 0 deletions
diff --git a/webrtc/p2p/base/turnport.cc b/webrtc/p2p/base/turnport.cc
new file mode 100644
index 0000000000..3fdcac5f31
--- /dev/null
+++ b/webrtc/p2p/base/turnport.cc
@@ -0,0 +1,1371 @@
+/*
+ * 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;
+
+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_; }
+ const rtc::SocketAddress& address() const { return ext_addr_; }
+ BindState state() const { return state_; }
+
+ // Helper methods to send permission and channel bind requests.
+ void SendCreatePermissionRequest();
+ 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 OnChannelBindSuccess();
+ void OnChannelBindError(StunMessage* response, int code);
+ // Signal sent when TurnEntry is destroyed.
+ sigslot::signal1<TurnEntry*> SignalDestroyed;
+
+ private:
+ TurnPort* port_;
+ int channel_id_;
+ rtc::SocketAddress ext_addr_;
+ BindState state_;
+};
+
+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()->address());
+ }
+ 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.IsUnresolved()) {
+ 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);
+
+ // 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.IsUnresolved()) {
+ 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_);
+ if (!ready()) {
+ OnAllocateError();
+ }
+ request_manager_.Clear();
+ state_ = STATE_DISCONNECTED;
+}
+
+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;
+
+ PrepareAddress();
+ ++allocate_mismatch_retries_;
+}
+
+Connection* TurnPort::CreateConnection(const Candidate& address,
+ CandidateOrigin origin) {
+ // TURN-UDP can only connect to UDP candidates.
+ if (address.protocol() != UDP_PROTOCOL_NAME) {
+ 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.
+ CreateEntry(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);
+ conn->SignalDestroyed.connect(this, &TurnPort::OnConnectionDestroyed);
+ AddConnection(conn);
+ return conn;
+ }
+ }
+ return NULL;
+}
+
+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);
+}
+
+void TurnPort::OnReadPacket(
+ rtc::AsyncPacketSocket* socket, const char* data, size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const rtc::PacketTime& packet_time) {
+ ASSERT(socket == socket_);
+
+ // 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;
+ }
+
+ // 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;
+ }
+
+ // 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);
+ } else if (msg_type == TURN_DATA_INDICATION) {
+ HandleDataIndication(data, size, packet_time);
+ } else {
+ 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;
+ }
+
+ // 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;
+ }
+ request_manager_.CheckResponse(data, size);
+ }
+}
+
+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;
+
+ 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;
+ if (!(candidate_filter() & CF_REFLEXIVE)) {
+ // If candidate filter only allows relay type of address, empty raddr to
+ // avoid local address leakage.
+ related_address = rtc::EmptySocketAddressWithFamily(stun_address.family());
+ }
+
+ // 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(this, MSG_ERROR);
+}
+
+void TurnPort::OnMessage(rtc::Message* message) {
+ if (message->message_id == MSG_ERROR) {
+ SignalPortError(this);
+ return;
+ } else if (message->message_id == MSG_ALLOCATE_MISMATCH) {
+ OnAllocateMismatch();
+ return;
+ } else if (message->message_id == 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();
+ }
+ return;
+ }
+
+ 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::ByteBuffer 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;
+}
+
+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;
+}
+
+TurnEntry* TurnPort::CreateEntry(const rtc::SocketAddress& addr) {
+ ASSERT(FindEntry(addr) == NULL);
+ TurnEntry* entry = new TurnEntry(this, next_channel_number_++, addr);
+ entries_.push_back(entry);
+ return entry;
+}
+
+void TurnPort::DestroyEntry(const rtc::SocketAddress& addr) {
+ TurnEntry* entry = FindEntry(addr);
+ ASSERT(entry != NULL);
+ entry->SignalDestroyed(entry);
+ entries_.remove(entry);
+ delete entry;
+}
+
+void TurnPort::OnConnectionDestroyed(Connection* conn) {
+ // Destroying TurnEntry for the connection, which is already destroyed.
+ DestroyEntry(conn->remote_candidate().address());
+}
+
+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(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(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());
+}
+
+void TurnRefreshRequest::OnErrorResponse(StunMessage* response) {
+ const StunErrorCodeAttribute* error_code = response->GetErrorCode();
+
+ LOG_J(LS_INFO, port_) << "Received TURN refresh error response"
+ << ", id=" << rtc::hex_encode(id())
+ << ", code=" << error_code->code()
+ << ", rtt=" << Elapsed();
+
+ 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();
+ }
+}
+
+void TurnRefreshRequest::OnTimeout() {
+ LOG_J(LS_WARNING, port_) << "TURN refresh timeout " << rtc::hex_encode(id());
+}
+
+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());
+}
+
+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());
+}
+
+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();
+}
+
+void TurnEntry::SendCreatePermissionRequest() {
+ port_->SendRequest(new TurnCreatePermissionRequest(
+ port_, this, ext_addr_), 0);
+}
+
+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::ByteBuffer 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";
+ // For success result code will be 0.
+ port_->SignalCreatePermissionResult(port_, ext_addr_, 0);
+}
+
+void TurnEntry::OnCreatePermissionError(StunMessage* response, int code) {
+ if (code == STUN_ERROR_STALE_NONCE) {
+ if (port_->UpdateNonce(response)) {
+ SendCreatePermissionRequest();
+ }
+ } else {
+ // Send signal with error code.
+ port_->SignalCreatePermissionResult(port_, ext_addr_, code);
+ }
+}
+
+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) {
+ // TODO(mallinath) - Implement handling of error response for channel
+ // bind request as per http://tools.ietf.org/html/rfc5766#section-11.3
+ if (code == STUN_ERROR_STALE_NONCE) {
+ if (port_->UpdateNonce(response)) {
+ // Send channel bind request with fresh nonce.
+ SendChannelBindRequest(0);
+ }
+ }
+}
+
+} // namespace cricket