summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/p2p/base/turn_port.cc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libwebrtc/p2p/base/turn_port.cc')
-rw-r--r--third_party/libwebrtc/p2p/base/turn_port.cc1900
1 files changed, 1900 insertions, 0 deletions
diff --git a/third_party/libwebrtc/p2p/base/turn_port.cc b/third_party/libwebrtc/p2p/base/turn_port.cc
new file mode 100644
index 0000000000..089910e072
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/turn_port.cc
@@ -0,0 +1,1900 @@
+/*
+ * 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 "p2p/base/turn_port.h"
+
+#include <functional>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "absl/algorithm/container.h"
+#include "absl/strings/match.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "api/task_queue/pending_task_safety_flag.h"
+#include "api/transport/stun.h"
+#include "p2p/base/connection.h"
+#include "p2p/base/p2p_constants.h"
+#include "rtc_base/async_packet_socket.h"
+#include "rtc_base/byte_order.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/experiments/field_trial_parser.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/net_helpers.h"
+#include "rtc_base/socket_address.h"
+#include "rtc_base/strings/string_builder.h"
+
+namespace cricket {
+
+namespace {
+
+bool ResolveTurnHostnameForFamily(const webrtc::FieldTrialsView& field_trials) {
+ // Bug fix for TURN hostname resolution on IPv6.
+ // Field trial key reserved in bugs.webrtc.org/14334
+ static constexpr char field_trial_name[] =
+ "WebRTC-IPv6NetworkResolutionFixes";
+ if (!field_trials.IsEnabled(field_trial_name)) {
+ return false;
+ }
+
+ webrtc::FieldTrialParameter<bool> resolve_turn_hostname_for_family(
+ "ResolveTurnHostnameForFamily", /*default_value=*/false);
+ webrtc::ParseFieldTrial({&resolve_turn_hostname_for_family},
+ field_trials.Lookup(field_trial_name));
+ return resolve_turn_hostname_for_family;
+}
+
+} // namespace
+
+using ::webrtc::SafeTask;
+using ::webrtc::TaskQueueBase;
+using ::webrtc::TimeDelta;
+
+// TODO(juberti): Move to stun.h when relay messages have been renamed.
+static const int TURN_ALLOCATE_REQUEST = STUN_ALLOCATE_REQUEST;
+
+// Attributes in comprehension-optional range,
+// ignored by TURN server that doesn't know about them.
+// https://tools.ietf.org/html/rfc5389#section-18.2
+const int STUN_ATTR_TURN_LOGGING_ID = 0xff05;
+
+// TODO(juberti): Extract to turnmessage.h
+static const int TURN_DEFAULT_PORT = 3478;
+static const int TURN_CHANNEL_NUMBER_START = 0x4000;
+
+static constexpr TimeDelta kTurnPermissionTimeout = TimeDelta::Minutes(5);
+
+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) {
+ switch (proto) {
+ case cricket::PROTO_TCP:
+ return ICE_TYPE_PREFERENCE_RELAY_TCP;
+ case cricket::PROTO_TLS:
+ return ICE_TYPE_PREFERENCE_RELAY_TLS;
+ default:
+ RTC_DCHECK(proto == PROTO_UDP);
+ return ICE_TYPE_PREFERENCE_RELAY_UDP;
+ }
+}
+
+class TurnAllocateRequest : public StunRequest {
+ public:
+ explicit TurnAllocateRequest(TurnPort* port);
+ 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, int lifetime = -1);
+ void OnSent() override;
+ void OnResponse(StunMessage* response) override;
+ void OnErrorResponse(StunMessage* response) override;
+ void OnTimeout() override;
+
+ private:
+ TurnPort* port_;
+};
+
+class TurnCreatePermissionRequest : public StunRequest {
+ public:
+ TurnCreatePermissionRequest(TurnPort* port,
+ TurnEntry* entry,
+ const rtc::SocketAddress& ext_addr);
+ ~TurnCreatePermissionRequest() override;
+ void OnSent() override;
+ void OnResponse(StunMessage* response) override;
+ void OnErrorResponse(StunMessage* response) override;
+ void OnTimeout() override;
+
+ private:
+ TurnPort* port_;
+ TurnEntry* entry_;
+ rtc::SocketAddress ext_addr_;
+};
+
+class TurnChannelBindRequest : public StunRequest {
+ public:
+ TurnChannelBindRequest(TurnPort* port,
+ TurnEntry* entry,
+ int channel_id,
+ const rtc::SocketAddress& ext_addr);
+ ~TurnChannelBindRequest() override;
+ void OnSent() override;
+ void OnResponse(StunMessage* response) override;
+ void OnErrorResponse(StunMessage* response) override;
+ void OnTimeout() override;
+
+ private:
+ 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, Connection* conn, int channel_id);
+ ~TurnEntry();
+
+ 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_; }
+
+ // Adds a new connection object to the list of connections that are associated
+ // with this entry. If prior to this call there were no connections being
+ // tracked (i.e. count goes from 0 -> 1), the internal safety flag is reset
+ // which cancels any potential pending deletion tasks.
+ void TrackConnection(Connection* conn);
+
+ // Removes a connection from the list of tracked connections.
+ // * If `conn` was the last connection removed, the function returns a
+ // safety flag that's used to schedule the deletion of the entry after a
+ // timeout expires. If during this timeout `TrackConnection` is called, the
+ // flag will be reset and pending tasks associated with it, cancelled.
+ // * If `conn` was not the last connection, the return value will be nullptr.
+ rtc::scoped_refptr<webrtc::PendingTaskSafetyFlag> UntrackConnection(
+ Connection* conn);
+
+ // 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.
+ webrtc::CallbackList<TurnEntry*> destroyed_callback_list_;
+
+ private:
+ TurnPort* port_;
+ int channel_id_;
+ rtc::SocketAddress ext_addr_;
+ BindState state_;
+ // List of associated connection instances to keep track of how many and
+ // which connections are associated with this entry. Once this is empty,
+ // the entry can be deleted.
+ std::vector<Connection*> connections_;
+ webrtc::ScopedTaskSafety task_safety_;
+};
+
+TurnPort::TurnPort(TaskQueueBase* thread,
+ rtc::PacketSocketFactory* factory,
+ const rtc::Network* network,
+ rtc::AsyncPacketSocket* socket,
+ absl::string_view username,
+ absl::string_view password,
+ const ProtocolAddress& server_address,
+ const RelayCredentials& credentials,
+ int server_priority,
+ const std::vector<std::string>& tls_alpn_protocols,
+ const std::vector<std::string>& tls_elliptic_curves,
+ webrtc::TurnCustomizer* customizer,
+ rtc::SSLCertificateVerifier* tls_cert_verifier,
+ const webrtc::FieldTrialsView* field_trials)
+ : Port(thread,
+ RELAY_PORT_TYPE,
+ factory,
+ network,
+ username,
+ password,
+ field_trials),
+ server_address_(server_address),
+ tls_alpn_protocols_(tls_alpn_protocols),
+ tls_elliptic_curves_(tls_elliptic_curves),
+ tls_cert_verifier_(tls_cert_verifier),
+ credentials_(credentials),
+ socket_(socket),
+ error_(0),
+ stun_dscp_value_(rtc::DSCP_NO_CHANGE),
+ request_manager_(
+ thread,
+ [this](const void* data, size_t size, StunRequest* request) {
+ OnSendStunPacket(data, size, request);
+ }),
+ next_channel_number_(TURN_CHANNEL_NUMBER_START),
+ state_(STATE_CONNECTING),
+ server_priority_(server_priority),
+ allocate_mismatch_retries_(0),
+ turn_customizer_(customizer) {}
+
+TurnPort::TurnPort(TaskQueueBase* thread,
+ rtc::PacketSocketFactory* factory,
+ const rtc::Network* network,
+ uint16_t min_port,
+ uint16_t max_port,
+ absl::string_view username,
+ absl::string_view password,
+ const ProtocolAddress& server_address,
+ const RelayCredentials& credentials,
+ int server_priority,
+ const std::vector<std::string>& tls_alpn_protocols,
+ const std::vector<std::string>& tls_elliptic_curves,
+ webrtc::TurnCustomizer* customizer,
+ rtc::SSLCertificateVerifier* tls_cert_verifier,
+ const webrtc::FieldTrialsView* field_trials)
+ : Port(thread,
+ RELAY_PORT_TYPE,
+ factory,
+ network,
+ min_port,
+ max_port,
+ username,
+ password,
+ field_trials),
+ server_address_(server_address),
+ tls_alpn_protocols_(tls_alpn_protocols),
+ tls_elliptic_curves_(tls_elliptic_curves),
+ tls_cert_verifier_(tls_cert_verifier),
+ credentials_(credentials),
+ socket_(nullptr),
+ error_(0),
+ stun_dscp_value_(rtc::DSCP_NO_CHANGE),
+ request_manager_(
+ thread,
+ [this](const void* data, size_t size, StunRequest* request) {
+ OnSendStunPacket(data, size, request);
+ }),
+ next_channel_number_(TURN_CHANNEL_NUMBER_START),
+ state_(STATE_CONNECTING),
+ server_priority_(server_priority),
+ allocate_mismatch_retries_(0),
+ turn_customizer_(customizer) {}
+
+TurnPort::~TurnPort() {
+ // TODO(juberti): Should this even be necessary?
+
+ // release the allocation by sending a refresh with
+ // lifetime 0.
+ if (ready()) {
+ Release();
+ }
+
+ entries_.clear();
+
+ if (socket_)
+ socket_->UnsubscribeClose(this);
+
+ if (!SharedSocket()) {
+ delete socket_;
+ }
+}
+
+rtc::SocketAddress TurnPort::GetLocalAddress() const {
+ return socket_ ? socket_->GetLocalAddress() : rtc::SocketAddress();
+}
+
+ProtocolType TurnPort::GetProtocol() const {
+ return server_address_.proto;
+}
+
+TlsCertPolicy TurnPort::GetTlsCertPolicy() const {
+ return tls_cert_policy_;
+}
+
+void TurnPort::SetTlsCertPolicy(TlsCertPolicy tls_cert_policy) {
+ tls_cert_policy_ = tls_cert_policy;
+}
+
+void TurnPort::SetTurnLoggingId(absl::string_view turn_logging_id) {
+ turn_logging_id_ = std::string(turn_logging_id);
+}
+
+std::vector<std::string> TurnPort::GetTlsAlpnProtocols() const {
+ return tls_alpn_protocols_;
+}
+
+std::vector<std::string> TurnPort::GetTlsEllipticCurves() const {
+ return tls_elliptic_curves_;
+}
+
+void TurnPort::PrepareAddress() {
+ if (credentials_.username.empty() || credentials_.password.empty()) {
+ RTC_LOG(LS_ERROR) << "Allocation can't be started without setting the"
+ " TURN server credentials for the user.";
+ OnAllocateError(STUN_ERROR_UNAUTHORIZED,
+ "Missing TURN server credentials.");
+ 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 (!AllowedTurnPort(server_address_.address.port(), &field_trials())) {
+ // This can only happen after a 300 ALTERNATE SERVER, since the port can't
+ // be created with a disallowed port number.
+ RTC_LOG(LS_ERROR) << "Attempt to start allocation with disallowed port# "
+ << server_address_.address.port();
+ OnAllocateError(STUN_ERROR_SERVER_ERROR,
+ "Attempt to start allocation to a disallowed port");
+ return;
+ }
+ 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)) {
+ RTC_LOG(LS_ERROR) << "IP address family does not match. server: "
+ << server_address_.address.family()
+ << " local: " << Network()->GetBestIP().family();
+ OnAllocateError(STUN_ERROR_GLOBAL_FAILURE,
+ "IP address family does not match.");
+ return;
+ }
+
+ // Insert the current address to prevent redirection pingpong.
+ attempted_server_addresses_.insert(server_address_.address);
+
+ RTC_LOG(LS_INFO)
+ << ToString() << ": Trying to connect to TURN server via "
+ << ProtoToString(server_address_.proto) << " @ "
+ << server_address_.address.ToSensitiveNameAndAddressString();
+ if (!CreateTurnClientSocket()) {
+ RTC_LOG(LS_ERROR) << "Failed to create TURN client socket";
+ OnAllocateError(SERVER_NOT_REACHABLE_ERROR,
+ "Failed to create TURN client socket.");
+ 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() {
+ RTC_DCHECK(!socket_ || SharedSocket());
+
+ if (server_address_.proto == PROTO_UDP && !SharedSocket()) {
+ socket_ = socket_factory()->CreateUdpSocket(
+ rtc::SocketAddress(Network()->GetBestIP(), 0), min_port(), max_port());
+ } else if (server_address_.proto == PROTO_TCP ||
+ server_address_.proto == PROTO_TLS) {
+ RTC_DCHECK(!SharedSocket());
+ int opts = rtc::PacketSocketFactory::OPT_STUN;
+
+ // Apply server address TLS and insecure bits to options.
+ if (server_address_.proto == PROTO_TLS) {
+ if (tls_cert_policy_ ==
+ TlsCertPolicy::TLS_CERT_POLICY_INSECURE_NO_CHECK) {
+ opts |= rtc::PacketSocketFactory::OPT_TLS_INSECURE;
+ } else {
+ opts |= rtc::PacketSocketFactory::OPT_TLS;
+ }
+ }
+
+ rtc::PacketSocketTcpOptions tcp_options;
+ tcp_options.opts = opts;
+ tcp_options.tls_alpn_protocols = tls_alpn_protocols_;
+ tcp_options.tls_elliptic_curves = tls_elliptic_curves_;
+ tcp_options.tls_cert_verifier = tls_cert_verifier_;
+ socket_ = socket_factory()->CreateClientTcpSocket(
+ rtc::SocketAddress(Network()->GetBestIP(), 0), server_address_.address,
+ proxy(), user_agent(), tcp_options);
+ }
+
+ 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 ||
+ server_address_.proto == PROTO_TLS) {
+ socket_->SignalConnect.connect(this, &TurnPort::OnSocketConnect);
+ socket_->SubscribeClose(this, [this](rtc::AsyncPacketSocket* s, int err) {
+ OnSocketClose(s, err);
+ });
+ } else {
+ state_ = STATE_CONNECTED;
+ }
+ return true;
+}
+
+void TurnPort::OnSocketConnect(rtc::AsyncPacketSocket* socket) {
+ // This slot should only be invoked if we're using a connection-oriented
+ // protocol.
+ RTC_DCHECK(server_address_.proto == PROTO_TCP ||
+ server_address_.proto == PROTO_TLS);
+
+ // Do not use this port if the socket bound to an address not associated with
+ // the desired network interface. 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
+ // not be one of the addresses of the requested interface:
+ // 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).
+ //
+ // Note that, aside from minor differences in log statements, this logic is
+ // identical to that in TcpPort.
+ const rtc::SocketAddress& socket_address = socket->GetLocalAddress();
+ if (absl::c_none_of(Network()->GetIPs(),
+ [socket_address](const rtc::InterfaceAddress& addr) {
+ return socket_address.ipaddr() == addr;
+ })) {
+ if (socket->GetLocalAddress().IsLoopbackIP()) {
+ RTC_LOG(LS_WARNING) << "Socket is bound to the address:"
+ << socket_address.ToSensitiveNameAndAddressString()
+ << ", rather than an address associated with network:"
+ << Network()->ToString()
+ << ". Still allowing it since it's localhost.";
+ } else if (IPIsAny(Network()->GetBestIP())) {
+ RTC_LOG(LS_WARNING)
+ << "Socket is bound to the address:"
+ << socket_address.ToSensitiveNameAndAddressString()
+ << ", rather than an address associated with network:"
+ << Network()->ToString()
+ << ". Still allowing it since it's the 'any' address"
+ ", possibly caused by multiple_routes being disabled.";
+ } else {
+ RTC_LOG(LS_WARNING) << "Socket is bound to the address:"
+ << socket_address.ToSensitiveNameAndAddressString()
+ << ", rather than an address associated with network:"
+ << Network()->ToString() << ". Discarding TURN port.";
+ OnAllocateError(
+ STUN_ERROR_GLOBAL_FAILURE,
+ "Address not associated with the desired network interface.");
+ return;
+ }
+ }
+
+ state_ = STATE_CONNECTED; // It is ready to send stun requests.
+ if (server_address_.address.IsUnresolvedIP()) {
+ server_address_.address = socket_->GetRemoteAddress();
+ }
+
+ RTC_LOG(LS_INFO) << "TurnPort connected to "
+ << socket->GetRemoteAddress().ToSensitiveString()
+ << " using tcp.";
+ SendRequest(new TurnAllocateRequest(this), 0);
+}
+
+void TurnPort::OnSocketClose(rtc::AsyncPacketSocket* socket, int error) {
+ RTC_LOG(LS_WARNING) << ToString()
+ << ": Connection with server failed with error: "
+ << error;
+ RTC_DCHECK(socket == socket_);
+ Close();
+}
+
+void TurnPort::OnAllocateMismatch() {
+ if (allocate_mismatch_retries_ >= MAX_ALLOCATE_MISMATCH_RETRIES) {
+ RTC_LOG(LS_WARNING) << ToString() << ": Giving up on the port after "
+ << allocate_mismatch_retries_
+ << " retries for STUN_ERROR_ALLOCATION_MISMATCH";
+ OnAllocateError(STUN_ERROR_ALLOCATION_MISMATCH,
+ "Maximum retries reached for allocation mismatch.");
+ return;
+ }
+
+ RTC_LOG(LS_INFO) << ToString()
+ << ": Allocating a new socket after "
+ "STUN_ERROR_ALLOCATION_MISMATCH, retry: "
+ << allocate_mismatch_retries_ + 1;
+
+ socket_->UnsubscribeClose(this);
+
+ if (SharedSocket()) {
+ ResetSharedSocket();
+ } else {
+ delete socket_;
+ }
+ socket_ = nullptr;
+
+ ResetNonce();
+ PrepareAddress();
+ ++allocate_mismatch_retries_;
+}
+
+Connection* TurnPort::CreateConnection(const Candidate& remote_candidate,
+ CandidateOrigin origin) {
+ // TURN-UDP can only connect to UDP candidates.
+ if (!SupportsProtocol(remote_candidate.protocol())) {
+ return nullptr;
+ }
+
+ if (state_ == STATE_DISCONNECTED || state_ == STATE_RECEIVEONLY) {
+ return nullptr;
+ }
+
+ // If the remote endpoint signaled us an mDNS candidate, we do not form a pair
+ // with the relay candidate to avoid IP leakage in the CreatePermission
+ // request.
+ if (absl::EndsWith(remote_candidate.address().hostname(), LOCAL_TLD)) {
+ return nullptr;
+ }
+
+ // A TURN port will have two candidates, 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) {
+ const Candidate& local_candidate = Candidates()[index];
+ if (local_candidate.type() == RELAY_PORT_TYPE &&
+ local_candidate.address().family() ==
+ remote_candidate.address().family()) {
+ ProxyConnection* conn =
+ new ProxyConnection(NewWeakPtr(), index, remote_candidate);
+ // Create an entry, if needed, so we can get our permissions set up
+ // correctly.
+ if (CreateOrRefreshEntry(conn, next_channel_number_)) {
+ next_channel_number_++;
+ }
+ AddOrReplaceConnection(conn);
+ return conn;
+ }
+ }
+ return nullptr;
+}
+
+bool TurnPort::FailAndPruneConnection(const rtc::SocketAddress& address) {
+ Connection* conn = GetConnection(address);
+ if (conn != nullptr) {
+ conn->FailAndPrune();
+ return true;
+ }
+ return false;
+}
+
+int TurnPort::SetOption(rtc::Socket::Option opt, int value) {
+ // Remember the last requested DSCP value, for STUN traffic.
+ if (opt == rtc::Socket::OPT_DSCP)
+ stun_dscp_value_ = static_cast<rtc::DiffServCodePoint>(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);
+ RTC_DCHECK(entry);
+
+ if (!ready()) {
+ 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->Send(data, size, payload, modified_options);
+ if (sent <= 0) {
+ error_ = socket_->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);
+}
+
+bool TurnPort::CanHandleIncomingPacketsFrom(
+ const rtc::SocketAddress& addr) const {
+ return server_address_.address == addr;
+}
+
+void TurnPort::SendBindingErrorResponse(StunMessage* message,
+ const rtc::SocketAddress& addr,
+ int error_code,
+ absl::string_view reason) {
+ if (!GetConnection(addr))
+ return;
+
+ Port::SendBindingErrorResponse(message, addr, error_code, reason);
+}
+
+bool TurnPort::HandleIncomingPacket(rtc::AsyncPacketSocket* socket,
+ const char* data,
+ size_t size,
+ const rtc::SocketAddress& remote_addr,
+ int64_t packet_time_us) {
+ 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) {
+ RTC_LOG(LS_WARNING)
+ << ToString() << ": Discarding TURN message from unknown address: "
+ << remote_addr.ToSensitiveNameAndAddressString() << " server_address_: "
+ << server_address_.address.ToSensitiveNameAndAddressString();
+ return false;
+ }
+
+ // The message must be at least the size of a channel header.
+ if (size < TURN_CHANNEL_HEADER_SIZE) {
+ RTC_LOG(LS_WARNING) << ToString()
+ << ": Received TURN message that was too short";
+ return false;
+ }
+
+ if (state_ == STATE_DISCONNECTED) {
+ RTC_LOG(LS_WARNING)
+ << ToString()
+ << ": 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_us);
+ return true;
+ }
+
+ if (msg_type == TURN_DATA_INDICATION) {
+ HandleDataIndication(data, size, packet_time_us);
+ return true;
+ }
+
+ if (SharedSocket() && (msg_type == STUN_BINDING_RESPONSE ||
+ msg_type == STUN_BINDING_ERROR_RESPONSE)) {
+ RTC_LOG(LS_VERBOSE)
+ << ToString()
+ << ": Ignoring STUN binding response message on shared socket.";
+ return false;
+ }
+
+ 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 int64_t& packet_time_us) {
+ HandleIncomingPacket(socket, data, size, remote_addr, packet_time_us);
+}
+
+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();
+ }
+}
+
+bool TurnPort::SupportsProtocol(absl::string_view protocol) const {
+ // Turn port only connects to UDP candidates.
+ return protocol == UDP_PROTOCOL_NAME;
+}
+
+// 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()) {
+ RTC_LOG(LS_WARNING) << ToString() << ": Redirection to ["
+ << address.ToSensitiveNameAndAddressString()
+ << "] ignored, allocation failed.";
+ return false;
+ }
+
+ // If protocol family of server address doesn't match with local, return.
+ if (!IsCompatibleAddress(address)) {
+ RTC_LOG(LS_WARNING) << "Server IP address family does not match with "
+ "local host address family type";
+ return false;
+ }
+
+ // Block redirects to a loopback address.
+ // See: https://bugs.chromium.org/p/chromium/issues/detail?id=649118
+ if (address.IsLoopbackIP()) {
+ RTC_LOG(LS_WARNING) << ToString()
+ << ": Blocking attempted redirect to loopback address.";
+ return false;
+ }
+
+ RTC_LOG(LS_INFO) << ToString() << ": Redirecting from TURN server ["
+ << server_address_.address.ToSensitiveNameAndAddressString()
+ << "] to TURN server ["
+ << address.ToSensitiveNameAndAddressString() << "]";
+ server_address_ = ProtocolAddress(address, server_address_.proto);
+
+ // 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;
+
+ RTC_LOG(LS_INFO) << ToString() << ": Starting TURN host lookup for "
+ << address.ToSensitiveString();
+ resolver_ = socket_factory()->CreateAsyncDnsResolver();
+ auto callback = [this] {
+ // 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).
+ auto& result = resolver_->result();
+ if (result.GetError() != 0 && (server_address_.proto == PROTO_TCP ||
+ server_address_.proto == PROTO_TLS)) {
+ if (!CreateTurnClientSocket()) {
+ OnAllocateError(SERVER_NOT_REACHABLE_ERROR,
+ "TURN host lookup received error.");
+ }
+ 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 (result.GetError() != 0 ||
+ !result.GetResolvedAddress(Network()->GetBestIP().family(),
+ &resolved_address)) {
+ RTC_LOG(LS_WARNING) << ToString() << ": TURN host lookup received error "
+ << result.GetError();
+ error_ = result.GetError();
+ OnAllocateError(SERVER_NOT_REACHABLE_ERROR,
+ "TURN host lookup received error.");
+ return;
+ }
+ server_address_.address = resolved_address;
+ PrepareAddress();
+ };
+ // TODO(bugs.webrtc.org/14733): remove duplicate resolution with STUN port.
+ if (ResolveTurnHostnameForFamily(field_trials())) {
+ resolver_->Start(address, Network()->family(), std::move(callback));
+ } else {
+ resolver_->Start(address, std::move(callback));
+ }
+}
+
+void TurnPort::OnSendStunPacket(const void* data,
+ size_t size,
+ StunRequest* request) {
+ RTC_DCHECK(connected());
+ rtc::PacketOptions options(StunDscpValue());
+ options.info_signaled_after_sent.packet_type = rtc::PacketType::kTurnMessage;
+ CopyPortInformationToPacketInfo(&options.info_signaled_after_sent);
+ if (Send(data, size, options) < 0) {
+ RTC_LOG(LS_ERROR) << ToString() << ": Failed to send TURN message, error: "
+ << 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 candidate type, empty for turn candidates.
+ RELAY_PORT_TYPE, GetRelayPreference(server_address_.proto),
+ server_priority_, ReconstructedServerUrl(), true);
+}
+
+void TurnPort::OnAllocateError(int error_code, absl::string_view reason) {
+ // We will send SignalPortError asynchronously as this can be sent during
+ // port initialization. This way it will not be blocking other port
+ // creation.
+ thread()->PostTask(
+ SafeTask(task_safety_.flag(), [this] { SignalPortError(this); }));
+ std::string address = GetLocalAddress().HostAsSensitiveURIString();
+ int port = GetLocalAddress().port();
+ if (server_address_.proto == PROTO_TCP &&
+ server_address_.address.IsPrivateIP()) {
+ address.clear();
+ port = 0;
+ }
+ SignalCandidateError(
+ this, IceCandidateErrorEvent(address, port, ReconstructedServerUrl(),
+ error_code, reason));
+}
+
+void TurnPort::OnRefreshError() {
+ // Need to clear the requests asynchronously because otherwise, the refresh
+ // request may be deleted twice: once at the end of the message processing
+ // and the other in HandleRefreshError().
+ thread()->PostTask(
+ SafeTask(task_safety_.flag(), [this] { HandleRefreshError(); }));
+}
+
+void TurnPort::HandleRefreshError() {
+ request_manager_.Clear();
+ state_ = STATE_RECEIVEONLY;
+ // Fail and prune all connections; stop sending data.
+ for (auto kv : connections()) {
+ kv.second->FailAndPrune();
+ }
+}
+
+void TurnPort::Release() {
+ // Remove any pending refresh requests.
+ request_manager_.Clear();
+
+ // Send refresh with lifetime 0.
+ TurnRefreshRequest* req = new TurnRefreshRequest(this, 0);
+ SendRequest(req, 0);
+
+ state_ = STATE_RECEIVEONLY;
+}
+
+void TurnPort::Close() {
+ if (!ready()) {
+ OnAllocateError(SERVER_NOT_REACHABLE_ERROR, "");
+ }
+ request_manager_.Clear();
+ // Stop the port from creating new connections.
+ state_ = STATE_DISCONNECTED;
+ // Delete all existing connections; stop sending data.
+ DestroyAllConnections();
+ if (callbacks_for_test_) {
+ callbacks_for_test_->OnTurnPortClosed();
+ }
+}
+
+rtc::DiffServCodePoint TurnPort::StunDscpValue() const {
+ return stun_dscp_value_;
+}
+
+// static
+bool TurnPort::AllowedTurnPort(int port,
+ const webrtc::FieldTrialsView* field_trials) {
+ // Port 53, 80 and 443 are used for existing deployments.
+ // Ports above 1024 are assumed to be OK to use.
+ if (port == 53 || port == 80 || port == 443 || port >= 1024) {
+ return true;
+ }
+ // Allow any port if relevant field trial is set. This allows disabling the
+ // check.
+ if (field_trials && field_trials->IsEnabled("WebRTC-Turn-AllowSystemPorts")) {
+ return true;
+ }
+ return false;
+}
+
+void TurnPort::TryAlternateServer() {
+ 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.
+ RTC_DCHECK(server_address().proto == PROTO_TCP ||
+ server_address().proto == PROTO_TLS);
+ RTC_DCHECK(!SharedSocket());
+ delete socket_;
+ socket_ = nullptr;
+ PrepareAddress();
+ }
+}
+
+void TurnPort::OnAllocateRequestTimeout() {
+ OnAllocateError(SERVER_NOT_REACHABLE_ERROR,
+ "TURN allocate request timed out.");
+}
+
+void TurnPort::HandleDataIndication(const char* data,
+ size_t size,
+ int64_t packet_time_us) {
+ // Read in the message, and process according to RFC5766, Section 10.4.
+ rtc::ByteBufferReader buf(data, size);
+ TurnMessage msg;
+ if (!msg.Read(&buf)) {
+ RTC_LOG(LS_WARNING) << ToString()
+ << ": Received invalid TURN data indication";
+ return;
+ }
+
+ // Check mandatory attributes.
+ const StunAddressAttribute* addr_attr =
+ msg.GetAddress(STUN_ATTR_XOR_PEER_ADDRESS);
+ if (!addr_attr) {
+ RTC_LOG(LS_WARNING) << ToString()
+ << ": Missing STUN_ATTR_XOR_PEER_ADDRESS attribute "
+ "in data indication.";
+ return;
+ }
+
+ const StunByteStringAttribute* data_attr = msg.GetByteString(STUN_ATTR_DATA);
+ if (!data_attr) {
+ RTC_LOG(LS_WARNING) << ToString()
+ << ": Missing STUN_ATTR_DATA attribute in "
+ "data indication.";
+ return;
+ }
+
+ // Log a warning if the data didn't come from an address that we think we have
+ // a permission for.
+ rtc::SocketAddress ext_addr(addr_attr->GetAddress());
+ if (!HasPermission(ext_addr.ipaddr())) {
+ RTC_LOG(LS_WARNING) << ToString()
+ << ": Received TURN data indication with unknown "
+ "peer address, addr: "
+ << ext_addr.ToSensitiveString();
+ }
+
+ DispatchPacket(data_attr->bytes(), data_attr->length(), ext_addr, PROTO_UDP,
+ packet_time_us);
+}
+
+void TurnPort::HandleChannelData(int channel_id,
+ const char* data,
+ size_t size,
+ int64_t packet_time_us) {
+ // 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) {
+ RTC_LOG(LS_WARNING) << ToString()
+ << ": 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) {
+ RTC_LOG(LS_WARNING) << ToString()
+ << ": 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_us);
+}
+
+void TurnPort::DispatchPacket(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);
+ }
+}
+
+bool TurnPort::ScheduleRefresh(uint32_t lifetime) {
+ // Lifetime is in seconds, delay is in milliseconds.
+ int delay = 1 * 60 * 1000;
+
+ // Cutoff lifetime bigger than 1h.
+ constexpr uint32_t max_lifetime = 60 * 60;
+
+ if (lifetime < 2 * 60) {
+ // The RFC does not mention a lower limit on lifetime.
+ // So if server sends a value less than 2 minutes, we schedule a refresh
+ // for half lifetime.
+ RTC_LOG(LS_WARNING) << ToString()
+ << ": Received response with short lifetime: "
+ << lifetime << " seconds.";
+ delay = (lifetime * 1000) / 2;
+ } else if (lifetime > max_lifetime) {
+ // Make 1 hour largest delay, and then we schedule a refresh for one minute
+ // less than max lifetime.
+ RTC_LOG(LS_WARNING) << ToString()
+ << ": Received response with long lifetime: "
+ << lifetime << " seconds.";
+ delay = (max_lifetime - 60) * 1000;
+ } else {
+ // Normal case,
+ // we schedule a refresh for one minute less than requested lifetime.
+ delay = (lifetime - 60) * 1000;
+ }
+
+ SendRequest(new TurnRefreshRequest(this), delay);
+ RTC_LOG(LS_INFO) << ToString() << ": 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.
+ RTC_DCHECK(!hash_.empty());
+ msg->AddAttribute(std::make_unique<StunByteStringAttribute>(
+ STUN_ATTR_USERNAME, credentials_.username));
+ msg->AddAttribute(
+ std::make_unique<StunByteStringAttribute>(STUN_ATTR_REALM, realm_));
+ msg->AddAttribute(
+ std::make_unique<StunByteStringAttribute>(STUN_ATTR_NONCE, nonce_));
+ const bool success = msg->AddMessageIntegrity(hash());
+ RTC_DCHECK(success);
+}
+
+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() {
+ const bool success = ComputeStunCredentialHash(credentials_.username, realm_,
+ credentials_.password, &hash_);
+ RTC_DCHECK(success);
+}
+
+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) {
+ RTC_LOG(LS_ERROR) << "Missing STUN_ATTR_REALM attribute in "
+ "stale nonce error response.";
+ return false;
+ }
+ set_realm(realm_attr->string_view());
+
+ const StunByteStringAttribute* nonce_attr =
+ response->GetByteString(STUN_ATTR_NONCE);
+ if (!nonce_attr) {
+ RTC_LOG(LS_ERROR) << "Missing STUN_ATTR_NONCE attribute in "
+ "stale nonce error response.";
+ return false;
+ }
+ set_nonce(nonce_attr->string_view());
+ return true;
+}
+
+void TurnPort::ResetNonce() {
+ hash_.clear();
+ nonce_.clear();
+ realm_.clear();
+}
+
+bool TurnPort::HasPermission(const rtc::IPAddress& ipaddr) const {
+ return absl::c_any_of(entries_, [&ipaddr](const auto& e) {
+ return e->address().ipaddr() == ipaddr;
+ });
+}
+
+TurnEntry* TurnPort::FindEntry(const rtc::SocketAddress& addr) const {
+ auto it = absl::c_find_if(
+ entries_, [&addr](const auto& e) { return e->address() == addr; });
+ return (it != entries_.end()) ? it->get() : nullptr;
+}
+
+TurnEntry* TurnPort::FindEntry(int channel_id) const {
+ auto it = absl::c_find_if(entries_, [&channel_id](const auto& e) {
+ return e->channel_id() == channel_id;
+ });
+ return (it != entries_.end()) ? it->get() : nullptr;
+}
+
+bool TurnPort::CreateOrRefreshEntry(Connection* conn, int channel_number) {
+ const Candidate& remote_candidate = conn->remote_candidate();
+ TurnEntry* entry = FindEntry(remote_candidate.address());
+ if (entry == nullptr) {
+ entries_.push_back(std::make_unique<TurnEntry>(this, conn, channel_number));
+ return true;
+ }
+
+ // Associate this connection object with an existing entry. If the entry
+ // has been scheduled for deletion, this will cancel that task.
+ entry->TrackConnection(conn);
+
+ return false;
+}
+
+void TurnPort::HandleConnectionDestroyed(Connection* conn) {
+ // Schedule an event to destroy TurnEntry for the connection, which is
+ // being destroyed.
+ const rtc::SocketAddress& remote_address = conn->remote_candidate().address();
+ // We should always have an entry for this connection.
+ TurnEntry* entry = FindEntry(remote_address);
+ rtc::scoped_refptr<webrtc::PendingTaskSafetyFlag> flag =
+ entry->UntrackConnection(conn);
+ if (flag) {
+ // An assumption here is that the lifetime flag for the entry, is within
+ // the lifetime scope of `task_safety_` and therefore use of `this` is safe.
+ // If an entry gets reused (associated with a new connection) while this
+ // task is pending, the entry will reset the safety flag, thus cancel this
+ // task.
+ thread()->PostDelayedTask(SafeTask(flag,
+ [this, entry] {
+ entries_.erase(absl::c_find_if(
+ entries_, [entry](const auto& e) {
+ return e.get() == entry;
+ }));
+ }),
+ kTurnPermissionTimeout);
+ }
+}
+
+void TurnPort::SetCallbacksForTest(CallbacksForTest* callbacks) {
+ RTC_DCHECK(!callbacks_for_test_);
+ callbacks_for_test_ = callbacks;
+}
+
+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;
+}
+
+std::string TurnPort::ReconstructedServerUrl() {
+ // draft-petithuguenin-behave-turn-uris-01
+ // turnURI = scheme ":" turn-host [ ":" turn-port ]
+ // [ "?transport=" transport ]
+ // scheme = "turn" / "turns"
+ // transport = "udp" / "tcp" / transport-ext
+ // transport-ext = 1*unreserved
+ // turn-host = IP-literal / IPv4address / reg-name
+ // turn-port = *DIGIT
+ std::string scheme = "turn";
+ std::string transport = "tcp";
+ switch (server_address_.proto) {
+ case PROTO_SSLTCP:
+ case PROTO_TLS:
+ scheme = "turns";
+ break;
+ case PROTO_UDP:
+ transport = "udp";
+ break;
+ case PROTO_TCP:
+ break;
+ }
+ rtc::StringBuilder url;
+ url << scheme << ":" << server_address_.address.hostname() << ":"
+ << server_address_.address.port() << "?transport=" << transport;
+ return url.Release();
+}
+
+void TurnPort::TurnCustomizerMaybeModifyOutgoingStunMessage(
+ StunMessage* message) {
+ if (turn_customizer_ == nullptr) {
+ return;
+ }
+
+ turn_customizer_->MaybeModifyOutgoingStunMessage(this, message);
+}
+
+bool TurnPort::TurnCustomizerAllowChannelData(const void* data,
+ size_t size,
+ bool payload) {
+ if (turn_customizer_ == nullptr) {
+ return true;
+ }
+
+ return turn_customizer_->AllowChannelData(this, data, size, payload);
+}
+
+void TurnPort::MaybeAddTurnLoggingId(StunMessage* msg) {
+ if (!turn_logging_id_.empty()) {
+ msg->AddAttribute(std::make_unique<StunByteStringAttribute>(
+ STUN_ATTR_TURN_LOGGING_ID, turn_logging_id_));
+ }
+}
+
+TurnAllocateRequest::TurnAllocateRequest(TurnPort* port)
+ : StunRequest(port->request_manager(),
+ std::make_unique<TurnMessage>(TURN_ALLOCATE_REQUEST)),
+ port_(port) {
+ StunMessage* message = mutable_msg();
+ // Create the request as indicated in RFC 5766, Section 6.1.
+ RTC_DCHECK_EQ(message->type(), TURN_ALLOCATE_REQUEST);
+ auto transport_attr =
+ StunAttribute::CreateUInt32(STUN_ATTR_REQUESTED_TRANSPORT);
+ transport_attr->SetValue(IPPROTO_UDP << 24);
+ message->AddAttribute(std::move(transport_attr));
+ if (!port_->hash().empty()) {
+ port_->AddRequestAuthInfo(message);
+ }
+ port_->MaybeAddTurnLoggingId(message);
+ port_->TurnCustomizerMaybeModifyOutgoingStunMessage(message);
+}
+
+void TurnAllocateRequest::OnSent() {
+ RTC_LOG(LS_INFO) << port_->ToString() << ": TURN allocate request sent, id="
+ << rtc::hex_encode(id());
+ StunRequest::OnSent();
+}
+
+void TurnAllocateRequest::OnResponse(StunMessage* response) {
+ RTC_LOG(LS_INFO) << port_->ToString()
+ << ": 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) {
+ RTC_LOG(LS_WARNING) << port_->ToString()
+ << ": 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) {
+ RTC_LOG(LS_WARNING) << port_->ToString()
+ << ": 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) {
+ RTC_LOG(LS_WARNING) << port_->ToString()
+ << ": 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.
+ int error_code = response->GetErrorCodeValue();
+
+ RTC_LOG(LS_INFO) << port_->ToString()
+ << ": Received TURN allocate error response, id="
+ << rtc::hex_encode(id()) << ", code=" << error_code
+ << ", rtt=" << Elapsed();
+
+ switch (error_code) {
+ case STUN_ERROR_UNAUTHORIZED: // Unauthrorized.
+ OnAuthChallenge(response, error_code);
+ break;
+ case STUN_ERROR_TRY_ALTERNATE:
+ OnTryAlternate(response, error_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.
+ TurnPort* port = port_;
+ port->thread()->PostTask(SafeTask(
+ port->task_safety_.flag(), [port] { port->OnAllocateMismatch(); }));
+ } break;
+ default:
+ RTC_LOG(LS_WARNING) << port_->ToString()
+ << ": Received TURN allocate error response, id="
+ << rtc::hex_encode(id()) << ", code=" << error_code
+ << ", rtt=" << Elapsed();
+ const StunErrorCodeAttribute* attr = response->GetErrorCode();
+ port_->OnAllocateError(error_code, attr ? attr->reason() : "");
+ }
+}
+
+void TurnAllocateRequest::OnTimeout() {
+ RTC_LOG(LS_WARNING) << port_->ToString() << ": TURN allocate request "
+ << rtc::hex_encode(id()) << " timeout";
+ 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()) {
+ RTC_LOG(LS_WARNING) << port_->ToString()
+ << ": Failed to authenticate with the server "
+ "after challenge.";
+ const StunErrorCodeAttribute* attr = response->GetErrorCode();
+ port_->OnAllocateError(STUN_ERROR_UNAUTHORIZED, attr ? attr->reason() : "");
+ return;
+ }
+
+ // Check the mandatory attributes.
+ const StunByteStringAttribute* realm_attr =
+ response->GetByteString(STUN_ATTR_REALM);
+ if (!realm_attr) {
+ RTC_LOG(LS_WARNING) << port_->ToString()
+ << ": Missing STUN_ATTR_REALM attribute in "
+ "allocate unauthorized response.";
+ return;
+ }
+ port_->set_realm(realm_attr->string_view());
+
+ const StunByteStringAttribute* nonce_attr =
+ response->GetByteString(STUN_ATTR_NONCE);
+ if (!nonce_attr) {
+ RTC_LOG(LS_WARNING) << port_->ToString()
+ << ": Missing STUN_ATTR_NONCE attribute in "
+ "allocate unauthorized response.";
+ return;
+ }
+ port_->set_nonce(nonce_attr->string_view());
+
+ // 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.
+ const StunErrorCodeAttribute* error_code_attr = response->GetErrorCode();
+ // Get the alternate server address attribute value.
+ const StunAddressAttribute* alternate_server_attr =
+ response->GetAddress(STUN_ATTR_ALTERNATE_SERVER);
+ if (!alternate_server_attr) {
+ RTC_LOG(LS_WARNING) << port_->ToString()
+ << ": Missing STUN_ATTR_ALTERNATE_SERVER "
+ "attribute in try alternate error response";
+ port_->OnAllocateError(STUN_ERROR_TRY_ALTERNATE,
+ error_code_attr ? error_code_attr->reason() : "");
+ return;
+ }
+ if (!port_->SetAlternateServer(alternate_server_attr->GetAddress())) {
+ port_->OnAllocateError(STUN_ERROR_TRY_ALTERNATE,
+ error_code_attr ? error_code_attr->reason() : "");
+ return;
+ }
+
+ // Check the attributes.
+ const StunByteStringAttribute* realm_attr =
+ response->GetByteString(STUN_ATTR_REALM);
+ if (realm_attr) {
+ RTC_LOG(LS_INFO) << port_->ToString()
+ << ": Applying STUN_ATTR_REALM attribute in "
+ "try alternate error response.";
+ port_->set_realm(realm_attr->string_view());
+ }
+
+ const StunByteStringAttribute* nonce_attr =
+ response->GetByteString(STUN_ATTR_NONCE);
+ if (nonce_attr) {
+ RTC_LOG(LS_INFO) << port_->ToString()
+ << ": Applying STUN_ATTR_NONCE attribute in "
+ "try alternate error response.";
+ port_->set_nonce(nonce_attr->string_view());
+ }
+
+ // 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.
+ TurnPort* port = port_;
+ port->thread()->PostTask(SafeTask(port->task_safety_.flag(),
+ [port] { port->TryAlternateServer(); }));
+}
+
+TurnRefreshRequest::TurnRefreshRequest(TurnPort* port, int lifetime /*= -1*/)
+ : StunRequest(port->request_manager(),
+ std::make_unique<TurnMessage>(TURN_REFRESH_REQUEST)),
+ port_(port) {
+ StunMessage* message = mutable_msg();
+ // Create the request as indicated in RFC 5766, Section 7.1.
+ // No attributes need to be included.
+ RTC_DCHECK_EQ(message->type(), TURN_REFRESH_REQUEST);
+ if (lifetime > -1) {
+ message->AddAttribute(
+ std::make_unique<StunUInt32Attribute>(STUN_ATTR_LIFETIME, lifetime));
+ }
+
+ port_->AddRequestAuthInfo(message);
+ port_->TurnCustomizerMaybeModifyOutgoingStunMessage(message);
+}
+
+void TurnRefreshRequest::OnSent() {
+ RTC_LOG(LS_INFO) << port_->ToString() << ": TURN refresh request sent, id="
+ << rtc::hex_encode(id());
+ StunRequest::OnSent();
+}
+
+void TurnRefreshRequest::OnResponse(StunMessage* response) {
+ RTC_LOG(LS_INFO) << port_->ToString()
+ << ": 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) {
+ RTC_LOG(LS_WARNING) << port_->ToString()
+ << ": Missing STUN_ATTR_TURN_LIFETIME attribute in "
+ "refresh success response.";
+ return;
+ }
+
+ if (lifetime_attr->value() > 0) {
+ // Schedule a refresh based on the returned lifetime value.
+ port_->ScheduleRefresh(lifetime_attr->value());
+ } else {
+ // If we scheduled a refresh with lifetime 0, we're releasing this
+ // allocation; see TurnPort::Release.
+ TurnPort* port = port_;
+ port->thread()->PostTask(
+ SafeTask(port->task_safety_.flag(), [port] { port->Close(); }));
+ }
+
+ if (port_->callbacks_for_test_) {
+ port_->callbacks_for_test_->OnTurnRefreshResult(TURN_SUCCESS_RESULT_CODE);
+ }
+}
+
+void TurnRefreshRequest::OnErrorResponse(StunMessage* response) {
+ int error_code = response->GetErrorCodeValue();
+
+ if (error_code == STUN_ERROR_STALE_NONCE) {
+ if (port_->UpdateNonce(response)) {
+ // Send RefreshRequest immediately.
+ port_->SendRequest(new TurnRefreshRequest(port_), 0);
+ }
+ } else {
+ RTC_LOG(LS_WARNING) << port_->ToString()
+ << ": Received TURN refresh error response, id="
+ << rtc::hex_encode(id()) << ", code=" << error_code
+ << ", rtt=" << Elapsed();
+ port_->OnRefreshError();
+ if (port_->callbacks_for_test_) {
+ port_->callbacks_for_test_->OnTurnRefreshResult(error_code);
+ }
+ }
+}
+
+void TurnRefreshRequest::OnTimeout() {
+ RTC_LOG(LS_WARNING) << port_->ToString() << ": TURN refresh timeout "
+ << rtc::hex_encode(id());
+ port_->OnRefreshError();
+}
+
+TurnCreatePermissionRequest::TurnCreatePermissionRequest(
+ TurnPort* port,
+ TurnEntry* entry,
+ const rtc::SocketAddress& ext_addr)
+ : StunRequest(
+ port->request_manager(),
+ std::make_unique<TurnMessage>(TURN_CREATE_PERMISSION_REQUEST)),
+ port_(port),
+ entry_(entry),
+ ext_addr_(ext_addr) {
+ RTC_DCHECK(entry_);
+ entry_->destroyed_callback_list_.AddReceiver(this, [this](TurnEntry* entry) {
+ RTC_DCHECK(entry_ == entry);
+ entry_ = nullptr;
+ });
+ StunMessage* message = mutable_msg();
+ // Create the request as indicated in RFC5766, Section 9.1.
+ RTC_DCHECK_EQ(message->type(), TURN_CREATE_PERMISSION_REQUEST);
+ message->AddAttribute(std::make_unique<StunXorAddressAttribute>(
+ STUN_ATTR_XOR_PEER_ADDRESS, ext_addr_));
+ port_->AddRequestAuthInfo(message);
+ port_->TurnCustomizerMaybeModifyOutgoingStunMessage(message);
+}
+
+TurnCreatePermissionRequest::~TurnCreatePermissionRequest() {
+ if (entry_) {
+ entry_->destroyed_callback_list_.RemoveReceivers(this);
+ }
+}
+
+void TurnCreatePermissionRequest::OnSent() {
+ RTC_LOG(LS_INFO) << port_->ToString()
+ << ": TURN create permission request sent, id="
+ << rtc::hex_encode(id());
+ StunRequest::OnSent();
+}
+
+void TurnCreatePermissionRequest::OnResponse(StunMessage* response) {
+ RTC_LOG(LS_INFO) << port_->ToString()
+ << ": 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) {
+ int error_code = response->GetErrorCodeValue();
+ RTC_LOG(LS_WARNING) << port_->ToString()
+ << ": Received TURN create permission error response, id="
+ << rtc::hex_encode(id()) << ", code=" << error_code
+ << ", rtt=" << Elapsed();
+ if (entry_) {
+ entry_->OnCreatePermissionError(response, error_code);
+ }
+}
+
+void TurnCreatePermissionRequest::OnTimeout() {
+ RTC_LOG(LS_WARNING) << port_->ToString()
+ << ": TURN create permission timeout "
+ << rtc::hex_encode(id());
+ if (entry_) {
+ entry_->OnCreatePermissionTimeout();
+ }
+}
+
+TurnChannelBindRequest::TurnChannelBindRequest(
+ TurnPort* port,
+ TurnEntry* entry,
+ int channel_id,
+ const rtc::SocketAddress& ext_addr)
+ : StunRequest(port->request_manager(),
+ std::make_unique<TurnMessage>(TURN_CHANNEL_BIND_REQUEST)),
+ port_(port),
+ entry_(entry),
+ channel_id_(channel_id),
+ ext_addr_(ext_addr) {
+ RTC_DCHECK(entry_);
+ entry_->destroyed_callback_list_.AddReceiver(this, [this](TurnEntry* entry) {
+ RTC_DCHECK(entry_ == entry);
+ entry_ = nullptr;
+ });
+ StunMessage* message = mutable_msg();
+ // Create the request as indicated in RFC5766, Section 11.1.
+ RTC_DCHECK_EQ(message->type(), TURN_CHANNEL_BIND_REQUEST);
+ message->AddAttribute(std::make_unique<StunUInt32Attribute>(
+ STUN_ATTR_CHANNEL_NUMBER, channel_id_ << 16));
+ message->AddAttribute(std::make_unique<StunXorAddressAttribute>(
+ STUN_ATTR_XOR_PEER_ADDRESS, ext_addr_));
+ port_->AddRequestAuthInfo(message);
+ port_->TurnCustomizerMaybeModifyOutgoingStunMessage(message);
+}
+
+TurnChannelBindRequest::~TurnChannelBindRequest() {
+ if (entry_) {
+ entry_->destroyed_callback_list_.RemoveReceivers(this);
+ }
+}
+
+void TurnChannelBindRequest::OnSent() {
+ RTC_LOG(LS_INFO) << port_->ToString()
+ << ": TURN channel bind request sent, id="
+ << rtc::hex_encode(id());
+ StunRequest::OnSent();
+}
+
+void TurnChannelBindRequest::OnResponse(StunMessage* response) {
+ RTC_LOG(LS_INFO) << port_->ToString()
+ << ": 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.
+ TimeDelta delay = kTurnPermissionTimeout - TimeDelta::Minutes(1);
+ entry_->SendChannelBindRequest(delay.ms());
+ RTC_LOG(LS_INFO) << port_->ToString() << ": Scheduled channel bind in "
+ << delay.ms() << "ms.";
+ }
+}
+
+void TurnChannelBindRequest::OnErrorResponse(StunMessage* response) {
+ int error_code = response->GetErrorCodeValue();
+ RTC_LOG(LS_WARNING) << port_->ToString()
+ << ": Received TURN channel bind error response, id="
+ << rtc::hex_encode(id()) << ", code=" << error_code
+ << ", rtt=" << Elapsed();
+ if (entry_) {
+ entry_->OnChannelBindError(response, error_code);
+ }
+}
+
+void TurnChannelBindRequest::OnTimeout() {
+ RTC_LOG(LS_WARNING) << port_->ToString() << ": TURN channel bind timeout "
+ << rtc::hex_encode(id());
+ if (entry_) {
+ entry_->OnChannelBindTimeout();
+ }
+}
+
+TurnEntry::TurnEntry(TurnPort* port, Connection* conn, int channel_id)
+ : port_(port),
+ channel_id_(channel_id),
+ ext_addr_(conn->remote_candidate().address()),
+ state_(STATE_UNBOUND),
+ connections_({conn}) {
+ // Creating permission for `ext_addr_`.
+ SendCreatePermissionRequest(0);
+}
+
+TurnEntry::~TurnEntry() {
+ destroyed_callback_list_.Send(this);
+}
+
+void TurnEntry::TrackConnection(Connection* conn) {
+ RTC_DCHECK(absl::c_find(connections_, conn) == connections_.end());
+ if (connections_.empty()) {
+ task_safety_.reset();
+ }
+ connections_.push_back(conn);
+}
+
+rtc::scoped_refptr<webrtc::PendingTaskSafetyFlag> TurnEntry::UntrackConnection(
+ Connection* conn) {
+ connections_.erase(absl::c_find(connections_, conn));
+ return connections_.empty() ? task_safety_.flag() : nullptr;
+}
+
+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 ||
+ !port_->TurnCustomizerAllowChannelData(data, size, payload)) {
+ // If we haven't bound the channel yet, we have to use a Send Indication.
+ // The turn_customizer_ can also make us use Send Indication.
+ TurnMessage msg(TURN_SEND_INDICATION);
+ msg.AddAttribute(std::make_unique<StunXorAddressAttribute>(
+ STUN_ATTR_XOR_PEER_ADDRESS, ext_addr_));
+ msg.AddAttribute(
+ std::make_unique<StunByteStringAttribute>(STUN_ATTR_DATA, data, size));
+
+ port_->TurnCustomizerMaybeModifyOutgoingStunMessage(&msg);
+
+ const bool success = msg.Write(&buf);
+ RTC_DCHECK(success);
+
+ // 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);
+ }
+ rtc::PacketOptions modified_options(options);
+ modified_options.info_signaled_after_sent.turn_overhead_bytes =
+ buf.Length() - size;
+ return port_->Send(buf.Data(), buf.Length(), modified_options);
+}
+
+void TurnEntry::OnCreatePermissionSuccess() {
+ RTC_LOG(LS_INFO) << port_->ToString() << ": Create permission for "
+ << ext_addr_.ToSensitiveString() << " succeeded";
+ if (port_->callbacks_for_test_) {
+ port_->callbacks_for_test_->OnTurnCreatePermissionResult(
+ 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.
+ TimeDelta delay = kTurnPermissionTimeout - TimeDelta::Minutes(1);
+ SendCreatePermissionRequest(delay.ms());
+ RTC_LOG(LS_INFO) << port_->ToString()
+ << ": Scheduled create-permission-request in "
+ << delay.ms() << "ms.";
+ }
+}
+
+void TurnEntry::OnCreatePermissionError(StunMessage* response, int code) {
+ if (code == STUN_ERROR_STALE_NONCE) {
+ if (port_->UpdateNonce(response)) {
+ SendCreatePermissionRequest(0);
+ }
+ } else {
+ bool found = port_->FailAndPruneConnection(ext_addr_);
+ if (found) {
+ RTC_LOG(LS_ERROR) << "Received TURN CreatePermission error response, "
+ "code="
+ << code << "; pruned connection.";
+ }
+ }
+ if (port_->callbacks_for_test_) {
+ port_->callbacks_for_test_->OnTurnCreatePermissionResult(code);
+ }
+}
+
+void TurnEntry::OnCreatePermissionTimeout() {
+ port_->FailAndPruneConnection(ext_addr_);
+}
+
+void TurnEntry::OnChannelBindSuccess() {
+ RTC_LOG(LS_INFO) << port_->ToString() << ": Successful channel bind for "
+ << ext_addr_.ToSensitiveString();
+ RTC_DCHECK(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 will fail and prune the connection and rely on ICE restart to
+ // re-establish a new connection if needed.
+ if (code == STUN_ERROR_STALE_NONCE) {
+ if (port_->UpdateNonce(response)) {
+ // Send channel bind request with fresh nonce.
+ SendChannelBindRequest(0);
+ }
+ } else {
+ state_ = STATE_UNBOUND;
+ port_->FailAndPruneConnection(ext_addr_);
+ }
+}
+void TurnEntry::OnChannelBindTimeout() {
+ state_ = STATE_UNBOUND;
+ port_->FailAndPruneConnection(ext_addr_);
+}
+} // namespace cricket