Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 14:04:28 +01:00
parent 81b91f4139
commit f8c34fa5ee
22732 changed files with 4815320 additions and 2 deletions

View file

@ -0,0 +1,8 @@
hta@webrtc.org
mflodman@webrtc.org
perkj@webrtc.org
qingsi@webrtc.org
sergeyu@chromium.org
tommi@webrtc.org
deadbeef@webrtc.org
jonaso@webrtc.org

View file

@ -0,0 +1,39 @@
/*
* Copyright 2022 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.
*/
#ifndef P2P_BASE_ACTIVE_ICE_CONTROLLER_FACTORY_INTERFACE_H_
#define P2P_BASE_ACTIVE_ICE_CONTROLLER_FACTORY_INTERFACE_H_
#include <memory>
#include "p2p/base/active_ice_controller_interface.h"
#include "p2p/base/ice_agent_interface.h"
#include "p2p/base/ice_controller_factory_interface.h"
namespace cricket {
// An active ICE controller may be constructed with the same arguments as a
// legacy ICE controller. Additionally, an ICE agent must be provided for the
// active ICE controller to interact with.
struct ActiveIceControllerFactoryArgs {
IceControllerFactoryArgs legacy_args;
IceAgentInterface* ice_agent;
};
class ActiveIceControllerFactoryInterface {
public:
virtual ~ActiveIceControllerFactoryInterface() = default;
virtual std::unique_ptr<ActiveIceControllerInterface> Create(
const ActiveIceControllerFactoryArgs&) = 0;
};
} // namespace cricket
#endif // P2P_BASE_ACTIVE_ICE_CONTROLLER_FACTORY_INTERFACE_H_

View file

@ -0,0 +1,84 @@
/*
* Copyright 2022 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.
*/
#ifndef P2P_BASE_ACTIVE_ICE_CONTROLLER_INTERFACE_H_
#define P2P_BASE_ACTIVE_ICE_CONTROLLER_INTERFACE_H_
#include "absl/types/optional.h"
#include "api/array_view.h"
#include "p2p/base/connection.h"
#include "p2p/base/ice_switch_reason.h"
#include "p2p/base/ice_transport_internal.h"
#include "p2p/base/transport_description.h"
namespace cricket {
// ActiveIceControllerInterface defines the methods for a module that actively
// manages the connection used by an ICE transport.
//
// An active ICE controller receives updates from the ICE transport when
// - the connections state is mutated
// - a new connection should be selected as a result of an external event (eg.
// a different connection nominated by the remote peer)
//
// The active ICE controller takes the appropriate decisions and requests the
// ICE agent to perform the necessary actions through the IceAgentInterface.
class ActiveIceControllerInterface {
public:
virtual ~ActiveIceControllerInterface() = default;
// Sets the current ICE configuration.
virtual void SetIceConfig(const IceConfig& config) = 0;
// Called when a new connection is added to the ICE transport.
virtual void OnConnectionAdded(const Connection* connection) = 0;
// Called when the transport switches that connection in active use.
virtual void OnConnectionSwitched(const Connection* connection) = 0;
// Called when a connection is destroyed.
virtual void OnConnectionDestroyed(const Connection* connection) = 0;
// Called when a STUN ping has been sent on a connection. This does not
// indicate that a STUN response has been received.
virtual void OnConnectionPinged(const Connection* connection) = 0;
// Called when one of the following changes for a connection.
// - rtt estimate
// - write state
// - receiving
// - connected
// - nominated
virtual void OnConnectionUpdated(const Connection* connection) = 0;
// Compute "STUN_ATTR_USE_CANDIDATE" for a STUN ping on the given connection.
virtual bool GetUseCandidateAttribute(const Connection* connection,
NominationMode mode,
IceMode remote_ice_mode) const = 0;
// Called to enque a request to pick and switch to the best available
// connection.
virtual void OnSortAndSwitchRequest(IceSwitchReason reason) = 0;
// Called to pick and switch to the best available connection immediately.
virtual void OnImmediateSortAndSwitchRequest(IceSwitchReason reason) = 0;
// Called to switch to the given connection immediately without checking for
// the best available connection.
virtual bool OnImmediateSwitchRequest(IceSwitchReason reason,
const Connection* selected) = 0;
// Only for unit tests
virtual const Connection* FindNextPingableConnection() = 0;
};
} // namespace cricket
#endif // P2P_BASE_ACTIVE_ICE_CONTROLLER_INTERFACE_H_

View file

@ -0,0 +1,162 @@
/*
* Copyright 2013 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/async_stun_tcp_socket.h"
#include <errno.h>
#include <stdint.h>
#include <string.h>
#include <cstddef>
#include <cstdint>
#include "api/array_view.h"
#include "api/transport/stun.h"
#include "api/units/timestamp.h"
#include "rtc_base/byte_order.h"
#include "rtc_base/checks.h"
#include "rtc_base/network/received_packet.h"
#include "rtc_base/network/sent_packet.h"
#include "rtc_base/time_utils.h"
namespace cricket {
static const size_t kMaxPacketSize = 64 * 1024;
typedef uint16_t PacketLength;
static const size_t kPacketLenSize = sizeof(PacketLength);
static const size_t kPacketLenOffset = 2;
static const size_t kBufSize = kMaxPacketSize + kStunHeaderSize;
static const size_t kTurnChannelDataHdrSize = 4;
inline bool IsStunMessage(uint16_t msg_type) {
// The first two bits of a channel data message are 0b01.
return (msg_type & 0xC000) ? false : true;
}
// AsyncStunTCPSocket
// Binds and connects `socket` and creates AsyncTCPSocket for
// it. Takes ownership of `socket`. Returns NULL if bind() or
// connect() fail (`socket` is destroyed in that case).
AsyncStunTCPSocket* AsyncStunTCPSocket::Create(
rtc::Socket* socket,
const rtc::SocketAddress& bind_address,
const rtc::SocketAddress& remote_address) {
return new AsyncStunTCPSocket(
AsyncTCPSocketBase::ConnectSocket(socket, bind_address, remote_address));
}
AsyncStunTCPSocket::AsyncStunTCPSocket(rtc::Socket* socket)
: rtc::AsyncTCPSocketBase(socket, kBufSize) {}
int AsyncStunTCPSocket::Send(const void* pv,
size_t cb,
const rtc::PacketOptions& options) {
if (cb > kBufSize || cb < kPacketLenSize + kPacketLenOffset) {
SetError(EMSGSIZE);
return -1;
}
// If we are blocking on send, then silently drop this packet
if (!IsOutBufferEmpty())
return static_cast<int>(cb);
int pad_bytes;
size_t expected_pkt_len = GetExpectedLength(pv, cb, &pad_bytes);
// Accepts only complete STUN/ChannelData packets.
if (cb != expected_pkt_len)
return -1;
AppendToOutBuffer(pv, cb);
RTC_DCHECK(pad_bytes < 4);
char padding[4] = {0};
AppendToOutBuffer(padding, pad_bytes);
int res = FlushOutBuffer();
if (res <= 0) {
// drop packet if we made no progress
ClearOutBuffer();
return res;
}
rtc::SentPacket sent_packet(options.packet_id, rtc::TimeMillis());
SignalSentPacket(this, sent_packet);
// We claim to have sent the whole thing, even if we only sent partial
return static_cast<int>(cb);
}
size_t AsyncStunTCPSocket::ProcessInput(rtc::ArrayView<const uint8_t> data) {
rtc::SocketAddress remote_addr(GetRemoteAddress());
// STUN packet - First 4 bytes. Total header size is 20 bytes.
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |0 0| STUN Message Type | Message Length |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// TURN ChannelData
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Channel Number | Length |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
size_t processed_bytes = 0;
while (true) {
size_t bytes_left = data.size() - processed_bytes;
// We need at least 4 bytes to read the STUN or ChannelData packet length.
if (bytes_left < kPacketLenOffset + kPacketLenSize)
return processed_bytes;
int pad_bytes;
size_t expected_pkt_len = GetExpectedLength(data.data() + processed_bytes,
bytes_left, &pad_bytes);
size_t actual_length = expected_pkt_len + pad_bytes;
if (bytes_left < actual_length) {
return processed_bytes;
}
rtc::ReceivedPacket received_packet(
data.subview(processed_bytes, expected_pkt_len), remote_addr,
webrtc::Timestamp::Micros(rtc::TimeMicros()));
NotifyPacketReceived(received_packet);
processed_bytes += actual_length;
}
}
size_t AsyncStunTCPSocket::GetExpectedLength(const void* data,
size_t len,
int* pad_bytes) {
*pad_bytes = 0;
PacketLength pkt_len =
rtc::GetBE16(static_cast<const char*>(data) + kPacketLenOffset);
size_t expected_pkt_len;
uint16_t msg_type = rtc::GetBE16(data);
if (IsStunMessage(msg_type)) {
// STUN message.
expected_pkt_len = kStunHeaderSize + pkt_len;
} else {
// TURN ChannelData message.
expected_pkt_len = kTurnChannelDataHdrSize + pkt_len;
// From RFC 5766 section 11.5
// Over TCP and TLS-over-TCP, the ChannelData message MUST be padded to
// a multiple of four bytes in order to ensure the alignment of
// subsequent messages. The padding is not reflected in the length
// field of the ChannelData message, so the actual size of a ChannelData
// message (including padding) is (4 + Length) rounded up to the nearest
// multiple of 4. Over UDP, the padding is not required but MAY be
// included.
if (expected_pkt_len % 4)
*pad_bytes = 4 - (expected_pkt_len % 4);
}
return expected_pkt_len;
}
} // namespace cricket

View file

@ -0,0 +1,51 @@
/*
* Copyright 2013 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.
*/
#ifndef P2P_BASE_ASYNC_STUN_TCP_SOCKET_H_
#define P2P_BASE_ASYNC_STUN_TCP_SOCKET_H_
#include <stddef.h>
#include "rtc_base/async_packet_socket.h"
#include "rtc_base/async_tcp_socket.h"
#include "rtc_base/socket.h"
#include "rtc_base/socket_address.h"
namespace cricket {
class AsyncStunTCPSocket : public rtc::AsyncTCPSocketBase {
public:
// Binds and connects `socket` and creates AsyncTCPSocket for
// it. Takes ownership of `socket`. Returns NULL if bind() or
// connect() fail (`socket` is destroyed in that case).
static AsyncStunTCPSocket* Create(rtc::Socket* socket,
const rtc::SocketAddress& bind_address,
const rtc::SocketAddress& remote_address);
explicit AsyncStunTCPSocket(rtc::Socket* socket);
AsyncStunTCPSocket(const AsyncStunTCPSocket&) = delete;
AsyncStunTCPSocket& operator=(const AsyncStunTCPSocket&) = delete;
int Send(const void* pv,
size_t cb,
const rtc::PacketOptions& options) override;
size_t ProcessInput(rtc::ArrayView<const uint8_t> data) override;
private:
// This method returns the message hdr + length written in the header.
// This method also returns the number of padding bytes needed/added to the
// turn message. `pad_bytes` should be used only when `is_turn` is true.
size_t GetExpectedLength(const void* data, size_t len, int* pad_bytes);
};
} // namespace cricket
#endif // P2P_BASE_ASYNC_STUN_TCP_SOCKET_H_

View file

@ -0,0 +1,47 @@
/*
* Copyright 2018 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/basic_async_resolver_factory.h"
#include <memory>
#include <utility>
#include "absl/memory/memory.h"
#include "api/async_dns_resolver.h"
#include "rtc_base/async_dns_resolver.h"
#include "rtc_base/logging.h"
namespace webrtc {
std::unique_ptr<webrtc::AsyncDnsResolverInterface>
BasicAsyncDnsResolverFactory::Create() {
return std::make_unique<AsyncDnsResolver>();
}
std::unique_ptr<webrtc::AsyncDnsResolverInterface>
BasicAsyncDnsResolverFactory::CreateAndResolve(
const rtc::SocketAddress& addr,
absl::AnyInvocable<void()> callback) {
std::unique_ptr<webrtc::AsyncDnsResolverInterface> resolver = Create();
resolver->Start(addr, std::move(callback));
return resolver;
}
std::unique_ptr<webrtc::AsyncDnsResolverInterface>
BasicAsyncDnsResolverFactory::CreateAndResolve(
const rtc::SocketAddress& addr,
int family,
absl::AnyInvocable<void()> callback) {
std::unique_ptr<webrtc::AsyncDnsResolverInterface> resolver = Create();
resolver->Start(addr, family, std::move(callback));
return resolver;
}
} // namespace webrtc

View file

@ -0,0 +1,42 @@
/*
* Copyright 2018 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.
*/
#ifndef P2P_BASE_BASIC_ASYNC_RESOLVER_FACTORY_H_
#define P2P_BASE_BASIC_ASYNC_RESOLVER_FACTORY_H_
#include <functional>
#include <memory>
#include <utility>
#include "api/async_dns_resolver.h"
namespace webrtc {
// A factory that vends AsyncDnsResolver instances.
class BasicAsyncDnsResolverFactory final
: public AsyncDnsResolverFactoryInterface {
public:
BasicAsyncDnsResolverFactory() = default;
std::unique_ptr<webrtc::AsyncDnsResolverInterface> CreateAndResolve(
const rtc::SocketAddress& addr,
absl::AnyInvocable<void()> callback) override;
std::unique_ptr<webrtc::AsyncDnsResolverInterface> CreateAndResolve(
const rtc::SocketAddress& addr,
int family,
absl::AnyInvocable<void()> callback) override;
std::unique_ptr<webrtc::AsyncDnsResolverInterface> Create() override;
};
} // namespace webrtc
#endif // P2P_BASE_BASIC_ASYNC_RESOLVER_FACTORY_H_

View file

@ -0,0 +1,856 @@
/*
* Copyright 2019 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/basic_ice_controller.h"
namespace {
// The minimum improvement in RTT that justifies a switch.
const int kMinImprovement = 10;
bool IsRelayRelay(const cricket::Connection* conn) {
return conn->local_candidate().is_relay() &&
conn->remote_candidate().is_relay();
}
bool IsUdp(const cricket::Connection* conn) {
return conn->local_candidate().relay_protocol() == cricket::UDP_PROTOCOL_NAME;
}
// TODO(qingsi) Use an enum to replace the following constants for all
// comparision results.
static constexpr int a_is_better = 1;
static constexpr int b_is_better = -1;
static constexpr int a_and_b_equal = 0;
bool LocalCandidateUsesPreferredNetwork(
const cricket::Connection* conn,
absl::optional<rtc::AdapterType> network_preference) {
rtc::AdapterType network_type = conn->network()->type();
return network_preference.has_value() && (network_type == network_preference);
}
int CompareCandidatePairsByNetworkPreference(
const cricket::Connection* a,
const cricket::Connection* b,
absl::optional<rtc::AdapterType> network_preference) {
bool a_uses_preferred_network =
LocalCandidateUsesPreferredNetwork(a, network_preference);
bool b_uses_preferred_network =
LocalCandidateUsesPreferredNetwork(b, network_preference);
if (a_uses_preferred_network && !b_uses_preferred_network) {
return a_is_better;
} else if (!a_uses_preferred_network && b_uses_preferred_network) {
return b_is_better;
}
return a_and_b_equal;
}
} // namespace
namespace cricket {
BasicIceController::BasicIceController(const IceControllerFactoryArgs& args)
: ice_transport_state_func_(args.ice_transport_state_func),
ice_role_func_(args.ice_role_func),
is_connection_pruned_func_(args.is_connection_pruned_func),
field_trials_(args.ice_field_trials) {}
BasicIceController::~BasicIceController() {}
void BasicIceController::SetIceConfig(const IceConfig& config) {
config_ = config;
}
void BasicIceController::SetSelectedConnection(
const Connection* selected_connection) {
selected_connection_ = selected_connection;
}
void BasicIceController::AddConnection(const Connection* connection) {
connections_.push_back(connection);
unpinged_connections_.insert(connection);
}
void BasicIceController::OnConnectionDestroyed(const Connection* connection) {
pinged_connections_.erase(connection);
unpinged_connections_.erase(connection);
connections_.erase(absl::c_find(connections_, connection));
if (selected_connection_ == connection)
selected_connection_ = nullptr;
}
bool BasicIceController::HasPingableConnection() const {
int64_t now = rtc::TimeMillis();
return absl::c_any_of(connections_, [this, now](const Connection* c) {
return IsPingable(c, now);
});
}
IceControllerInterface::PingResult BasicIceController::SelectConnectionToPing(
int64_t last_ping_sent_ms) {
// When the selected connection is not receiving or not writable, or any
// active connection has not been pinged enough times, use the weak ping
// interval.
bool need_more_pings_at_weak_interval =
absl::c_any_of(connections_, [](const Connection* conn) {
return conn->active() &&
conn->num_pings_sent() < MIN_PINGS_AT_WEAK_PING_INTERVAL;
});
int ping_interval = (weak() || need_more_pings_at_weak_interval)
? weak_ping_interval()
: strong_ping_interval();
const Connection* conn = nullptr;
if (rtc::TimeMillis() >= last_ping_sent_ms + ping_interval) {
conn = FindNextPingableConnection();
}
PingResult res(conn, std::min(ping_interval, check_receiving_interval()));
return res;
}
void BasicIceController::MarkConnectionPinged(const Connection* conn) {
if (conn && pinged_connections_.insert(conn).second) {
unpinged_connections_.erase(conn);
}
}
// Returns the next pingable connection to ping.
const Connection* BasicIceController::FindNextPingableConnection() {
int64_t now = rtc::TimeMillis();
// Rule 1: Selected connection takes priority over non-selected ones.
if (selected_connection_ && selected_connection_->connected() &&
selected_connection_->writable() &&
WritableConnectionPastPingInterval(selected_connection_, now)) {
return selected_connection_;
}
// Rule 2: If the channel is weak, we need to find a new writable and
// receiving connection, probably on a different network. If there are lots of
// connections, it may take several seconds between two pings for every
// non-selected connection. This will cause the receiving state of those
// connections to be false, and thus they won't be selected. This is
// problematic for network fail-over. We want to make sure at least one
// connection per network is pinged frequently enough in order for it to be
// selectable. So we prioritize one connection per network.
// Rule 2.1: Among such connections, pick the one with the earliest
// last-ping-sent time.
if (weak()) {
std::vector<const Connection*> pingable_selectable_connections;
absl::c_copy_if(GetBestWritableConnectionPerNetwork(),
std::back_inserter(pingable_selectable_connections),
[this, now](const Connection* conn) {
return WritableConnectionPastPingInterval(conn, now);
});
auto iter = absl::c_min_element(
pingable_selectable_connections,
[](const Connection* conn1, const Connection* conn2) {
return conn1->last_ping_sent() < conn2->last_ping_sent();
});
if (iter != pingable_selectable_connections.end()) {
return *iter;
}
}
// Rule 3: Triggered checks have priority over non-triggered connections.
// Rule 3.1: Among triggered checks, oldest takes precedence.
const Connection* oldest_triggered_check =
FindOldestConnectionNeedingTriggeredCheck(now);
if (oldest_triggered_check) {
return oldest_triggered_check;
}
// Rule 4: Unpinged connections have priority over pinged ones.
RTC_CHECK(connections_.size() ==
pinged_connections_.size() + unpinged_connections_.size());
// If there are unpinged and pingable connections, only ping those.
// Otherwise, treat everything as unpinged.
// TODO(honghaiz): Instead of adding two separate vectors, we can add a state
// "pinged" to filter out unpinged connections.
if (absl::c_none_of(unpinged_connections_,
[this, now](const Connection* conn) {
return this->IsPingable(conn, now);
})) {
unpinged_connections_.insert(pinged_connections_.begin(),
pinged_connections_.end());
pinged_connections_.clear();
}
// Among un-pinged pingable connections, "more pingable" takes precedence.
std::vector<const Connection*> pingable_connections;
absl::c_copy_if(
unpinged_connections_, std::back_inserter(pingable_connections),
[this, now](const Connection* conn) { return IsPingable(conn, now); });
auto iter = absl::c_max_element(
pingable_connections,
[this](const Connection* conn1, const Connection* conn2) {
// Some implementations of max_element
// compare an element with itself.
if (conn1 == conn2) {
return false;
}
return MorePingable(conn1, conn2) == conn2;
});
if (iter != pingable_connections.end()) {
return *iter;
}
return nullptr;
}
// Find "triggered checks". We ping first those connections that have
// received a ping but have not sent a ping since receiving it
// (last_ping_received > last_ping_sent). But we shouldn't do
// triggered checks if the connection is already writable.
const Connection* BasicIceController::FindOldestConnectionNeedingTriggeredCheck(
int64_t now) {
const Connection* oldest_needing_triggered_check = nullptr;
for (auto* conn : connections_) {
if (!IsPingable(conn, now)) {
continue;
}
bool needs_triggered_check =
(!conn->writable() &&
conn->last_ping_received() > conn->last_ping_sent());
if (needs_triggered_check &&
(!oldest_needing_triggered_check ||
(conn->last_ping_received() <
oldest_needing_triggered_check->last_ping_received()))) {
oldest_needing_triggered_check = conn;
}
}
if (oldest_needing_triggered_check) {
RTC_LOG(LS_INFO) << "Selecting connection for triggered check: "
<< oldest_needing_triggered_check->ToString();
}
return oldest_needing_triggered_check;
}
bool BasicIceController::WritableConnectionPastPingInterval(
const Connection* conn,
int64_t now) const {
int interval = CalculateActiveWritablePingInterval(conn, now);
return conn->last_ping_sent() + interval <= now;
}
int BasicIceController::CalculateActiveWritablePingInterval(
const Connection* conn,
int64_t now) const {
// Ping each connection at a higher rate at least
// MIN_PINGS_AT_WEAK_PING_INTERVAL times.
if (conn->num_pings_sent() < MIN_PINGS_AT_WEAK_PING_INTERVAL) {
return weak_ping_interval();
}
int stable_interval =
config_.stable_writable_connection_ping_interval_or_default();
int weak_or_stablizing_interval = std::min(
stable_interval, WEAK_OR_STABILIZING_WRITABLE_CONNECTION_PING_INTERVAL);
// If the channel is weak or the connection is not stable yet, use the
// weak_or_stablizing_interval.
return (!weak() && conn->stable(now)) ? stable_interval
: weak_or_stablizing_interval;
}
// Is the connection in a state for us to even consider pinging the other side?
// We consider a connection pingable even if it's not connected because that's
// how a TCP connection is kicked into reconnecting on the active side.
bool BasicIceController::IsPingable(const Connection* conn, int64_t now) const {
const Candidate& remote = conn->remote_candidate();
// We should never get this far with an empty remote ufrag.
RTC_DCHECK(!remote.username().empty());
if (remote.username().empty() || remote.password().empty()) {
// If we don't have an ICE ufrag and pwd, there's no way we can ping.
return false;
}
// A failed connection will not be pinged.
if (conn->state() == IceCandidatePairState::FAILED) {
return false;
}
// An never connected connection cannot be written to at all, so pinging is
// out of the question. However, if it has become WRITABLE, it is in the
// reconnecting state so ping is needed.
if (!conn->connected() && !conn->writable()) {
return false;
}
// If we sent a number of pings wo/ reply, skip sending more
// until we get one.
if (conn->TooManyOutstandingPings(field_trials_->max_outstanding_pings)) {
return false;
}
// If the channel is weakly connected, ping all connections.
if (weak()) {
return true;
}
// Always ping active connections regardless whether the channel is completed
// or not, but backup connections are pinged at a slower rate.
if (IsBackupConnection(conn)) {
return conn->rtt_samples() == 0 ||
(now >= conn->last_ping_response_received() +
config_.backup_connection_ping_interval_or_default());
}
// Don't ping inactive non-backup connections.
if (!conn->active()) {
return false;
}
// Do ping unwritable, active connections.
if (!conn->writable()) {
return true;
}
// Ping writable, active connections if it's been long enough since the last
// ping.
return WritableConnectionPastPingInterval(conn, now);
}
// A connection is considered a backup connection if the channel state
// is completed, the connection is not the selected connection and it is active.
bool BasicIceController::IsBackupConnection(const Connection* conn) const {
return ice_transport_state_func_() == IceTransportState::STATE_COMPLETED &&
conn != selected_connection_ && conn->active();
}
const Connection* BasicIceController::MorePingable(const Connection* conn1,
const Connection* conn2) {
RTC_DCHECK(conn1 != conn2);
if (config_.prioritize_most_likely_candidate_pairs) {
const Connection* most_likely_to_work_conn = MostLikelyToWork(conn1, conn2);
if (most_likely_to_work_conn) {
return most_likely_to_work_conn;
}
}
const Connection* least_recently_pinged_conn =
LeastRecentlyPinged(conn1, conn2);
if (least_recently_pinged_conn) {
return least_recently_pinged_conn;
}
// During the initial state when nothing has been pinged yet, return the first
// one in the ordered `connections_`.
auto connections = connections_;
return *(std::find_if(connections.begin(), connections.end(),
[conn1, conn2](const Connection* conn) {
return conn == conn1 || conn == conn2;
}));
}
const Connection* BasicIceController::MostLikelyToWork(
const Connection* conn1,
const Connection* conn2) {
bool rr1 = IsRelayRelay(conn1);
bool rr2 = IsRelayRelay(conn2);
if (rr1 && !rr2) {
return conn1;
} else if (rr2 && !rr1) {
return conn2;
} else if (rr1 && rr2) {
bool udp1 = IsUdp(conn1);
bool udp2 = IsUdp(conn2);
if (udp1 && !udp2) {
return conn1;
} else if (udp2 && udp1) {
return conn2;
}
}
return nullptr;
}
const Connection* BasicIceController::LeastRecentlyPinged(
const Connection* conn1,
const Connection* conn2) {
if (conn1->last_ping_sent() < conn2->last_ping_sent()) {
return conn1;
}
if (conn1->last_ping_sent() > conn2->last_ping_sent()) {
return conn2;
}
return nullptr;
}
std::map<const rtc::Network*, const Connection*>
BasicIceController::GetBestConnectionByNetwork() const {
// `connections_` has been sorted, so the first one in the list on a given
// network is the best connection on the network, except that the selected
// connection is always the best connection on the network.
std::map<const rtc::Network*, const Connection*> best_connection_by_network;
if (selected_connection_) {
best_connection_by_network[selected_connection_->network()] =
selected_connection_;
}
// TODO(honghaiz): Need to update this if `connections_` are not sorted.
for (const Connection* conn : connections_) {
const rtc::Network* network = conn->network();
// This only inserts when the network does not exist in the map.
best_connection_by_network.insert(std::make_pair(network, conn));
}
return best_connection_by_network;
}
std::vector<const Connection*>
BasicIceController::GetBestWritableConnectionPerNetwork() const {
std::vector<const Connection*> connections;
for (auto kv : GetBestConnectionByNetwork()) {
const Connection* conn = kv.second;
if (conn->writable() && conn->connected()) {
connections.push_back(conn);
}
}
return connections;
}
IceControllerInterface::SwitchResult
BasicIceController::HandleInitialSelectDampening(
IceSwitchReason reason,
const Connection* new_connection) {
if (!field_trials_->initial_select_dampening.has_value() &&
!field_trials_->initial_select_dampening_ping_received.has_value()) {
// experiment not enabled => select connection.
return {new_connection, absl::nullopt};
}
int64_t now = rtc::TimeMillis();
int64_t max_delay = 0;
if (new_connection->last_ping_received() > 0 &&
field_trials_->initial_select_dampening_ping_received.has_value()) {
max_delay = *field_trials_->initial_select_dampening_ping_received;
} else if (field_trials_->initial_select_dampening.has_value()) {
max_delay = *field_trials_->initial_select_dampening;
}
int64_t start_wait =
initial_select_timestamp_ms_ == 0 ? now : initial_select_timestamp_ms_;
int64_t max_wait_until = start_wait + max_delay;
if (now >= max_wait_until) {
RTC_LOG(LS_INFO) << "reset initial_select_timestamp_ = "
<< initial_select_timestamp_ms_
<< " selection delayed by: " << (now - start_wait) << "ms";
initial_select_timestamp_ms_ = 0;
return {new_connection, absl::nullopt};
}
// We are not yet ready to select first connection...
if (initial_select_timestamp_ms_ == 0) {
// Set timestamp on first time...
// but run the delayed invokation everytime to
// avoid possibility that we miss it.
initial_select_timestamp_ms_ = now;
RTC_LOG(LS_INFO) << "set initial_select_timestamp_ms_ = "
<< initial_select_timestamp_ms_;
}
int min_delay = max_delay;
if (field_trials_->initial_select_dampening.has_value()) {
min_delay = std::min(min_delay, *field_trials_->initial_select_dampening);
}
if (field_trials_->initial_select_dampening_ping_received.has_value()) {
min_delay = std::min(
min_delay, *field_trials_->initial_select_dampening_ping_received);
}
RTC_LOG(LS_INFO) << "delay initial selection up to " << min_delay << "ms";
return {.connection = absl::nullopt,
.recheck_event = IceRecheckEvent(
IceSwitchReason::ICE_CONTROLLER_RECHECK, min_delay)};
}
IceControllerInterface::SwitchResult BasicIceController::ShouldSwitchConnection(
IceSwitchReason reason,
const Connection* new_connection) {
if (!ReadyToSend(new_connection) || selected_connection_ == new_connection) {
return {absl::nullopt, absl::nullopt};
}
if (selected_connection_ == nullptr) {
return HandleInitialSelectDampening(reason, new_connection);
}
// Do not switch to a connection that is not receiving if it is not on a
// preferred network or it has higher cost because it may be just spuriously
// better.
int compare_a_b_by_networks = CompareCandidatePairNetworks(
new_connection, selected_connection_, config_.network_preference);
if (compare_a_b_by_networks == b_is_better && !new_connection->receiving()) {
return {absl::nullopt, absl::nullopt};
}
bool missed_receiving_unchanged_threshold = false;
absl::optional<int64_t> receiving_unchanged_threshold(
rtc::TimeMillis() - config_.receiving_switching_delay_or_default());
int cmp = CompareConnections(selected_connection_, new_connection,
receiving_unchanged_threshold,
&missed_receiving_unchanged_threshold);
absl::optional<IceRecheckEvent> recheck_event;
if (missed_receiving_unchanged_threshold &&
config_.receiving_switching_delay_or_default()) {
// If we do not switch to the connection because it missed the receiving
// threshold, the new connection is in a better receiving state than the
// currently selected connection. So we need to re-check whether it needs
// to be switched at a later time.
recheck_event.emplace(reason,
config_.receiving_switching_delay_or_default());
}
if (cmp < 0) {
return {new_connection, absl::nullopt};
} else if (cmp > 0) {
return {absl::nullopt, recheck_event};
}
// If everything else is the same, switch only if rtt has improved by
// a margin.
if (new_connection->rtt() <= selected_connection_->rtt() - kMinImprovement) {
return {new_connection, absl::nullopt};
}
return {absl::nullopt, recheck_event};
}
IceControllerInterface::SwitchResult
BasicIceController::SortAndSwitchConnection(IceSwitchReason reason) {
// Find the best alternative connection by sorting. It is important to note
// that amongst equal preference, writable connections, this will choose the
// one whose estimated latency is lowest. So it is the only one that we
// need to consider switching to.
// TODO(honghaiz): Don't sort; Just use std::max_element in the right places.
absl::c_stable_sort(
connections_, [this](const Connection* a, const Connection* b) {
int cmp = CompareConnections(a, b, absl::nullopt, nullptr);
if (cmp != 0) {
return cmp > 0;
}
// Otherwise, sort based on latency estimate.
return a->rtt() < b->rtt();
});
RTC_LOG(LS_VERBOSE) << "Sorting " << connections_.size()
<< " available connections due to: "
<< IceSwitchReasonToString(reason);
for (size_t i = 0; i < connections_.size(); ++i) {
RTC_LOG(LS_VERBOSE) << connections_[i]->ToString();
}
const Connection* top_connection =
(!connections_.empty()) ? connections_[0] : nullptr;
return ShouldSwitchConnection(reason, top_connection);
}
bool BasicIceController::ReadyToSend(const Connection* connection) const {
// Note that we allow sending on an unreliable connection, because it's
// possible that it became unreliable simply due to bad chance.
// So this shouldn't prevent attempting to send media.
return connection != nullptr &&
(connection->writable() ||
connection->write_state() == Connection::STATE_WRITE_UNRELIABLE ||
PresumedWritable(connection));
}
bool BasicIceController::PresumedWritable(const Connection* conn) const {
return (conn->write_state() == Connection::STATE_WRITE_INIT &&
config_.presume_writable_when_fully_relayed &&
conn->local_candidate().is_relay() &&
(conn->remote_candidate().is_relay() ||
conn->remote_candidate().is_prflx()));
}
// Compare two connections based on their writing, receiving, and connected
// states.
int BasicIceController::CompareConnectionStates(
const Connection* a,
const Connection* b,
absl::optional<int64_t> receiving_unchanged_threshold,
bool* missed_receiving_unchanged_threshold) const {
// First, prefer a connection that's writable or presumed writable over
// one that's not writable.
bool a_writable = a->writable() || PresumedWritable(a);
bool b_writable = b->writable() || PresumedWritable(b);
if (a_writable && !b_writable) {
return a_is_better;
}
if (!a_writable && b_writable) {
return b_is_better;
}
// Sort based on write-state. Better states have lower values.
if (a->write_state() < b->write_state()) {
return a_is_better;
}
if (b->write_state() < a->write_state()) {
return b_is_better;
}
// We prefer a receiving connection to a non-receiving, higher-priority
// connection when sorting connections and choosing which connection to
// switch to.
if (a->receiving() && !b->receiving()) {
return a_is_better;
}
if (!a->receiving() && b->receiving()) {
if (!receiving_unchanged_threshold ||
(a->receiving_unchanged_since() <= *receiving_unchanged_threshold &&
b->receiving_unchanged_since() <= *receiving_unchanged_threshold)) {
return b_is_better;
}
*missed_receiving_unchanged_threshold = true;
}
// WARNING: Some complexity here about TCP reconnecting.
// When a TCP connection fails because of a TCP socket disconnecting, the
// active side of the connection will attempt to reconnect for 5 seconds while
// pretending to be writable (the connection is not set to the unwritable
// state). On the passive side, the connection also remains writable even
// though it is disconnected, and a new connection is created when the active
// side connects. At that point, there are two TCP connections on the passive
// side: 1. the old, disconnected one that is pretending to be writable, and
// 2. the new, connected one that is maybe not yet writable. For purposes of
// pruning, pinging, and selecting the selected connection, we want to treat
// the new connection as "better" than the old one. We could add a method
// called something like Connection::ImReallyBadEvenThoughImWritable, but that
// is equivalent to the existing Connection::connected(), which we already
// have. So, in code throughout this file, we'll check whether the connection
// is connected() or not, and if it is not, treat it as "worse" than a
// connected one, even though it's writable. In the code below, we're doing
// so to make sure we treat a new writable connection as better than an old
// disconnected connection.
// In the case where we reconnect TCP connections, the original best
// connection is disconnected without changing to WRITE_TIMEOUT. In this case,
// the new connection, when it becomes writable, should have higher priority.
if (a->write_state() == Connection::STATE_WRITABLE &&
b->write_state() == Connection::STATE_WRITABLE) {
if (a->connected() && !b->connected()) {
return a_is_better;
}
if (!a->connected() && b->connected()) {
return b_is_better;
}
}
return 0;
}
// Compares two connections based only on the candidate and network information.
// Returns positive if `a` is better than `b`.
int BasicIceController::CompareConnectionCandidates(const Connection* a,
const Connection* b) const {
int compare_a_b_by_networks =
CompareCandidatePairNetworks(a, b, config_.network_preference);
if (compare_a_b_by_networks != a_and_b_equal) {
return compare_a_b_by_networks;
}
// Compare connection priority. Lower values get sorted last.
if (a->priority() > b->priority()) {
return a_is_better;
}
if (a->priority() < b->priority()) {
return b_is_better;
}
// If we're still tied at this point, prefer a younger generation.
// (Younger generation means a larger generation number).
int cmp = (a->remote_candidate().generation() + a->generation()) -
(b->remote_candidate().generation() + b->generation());
if (cmp != 0) {
return cmp;
}
// A periodic regather (triggered by the regather_all_networks_interval_range)
// will produce candidates that appear the same but would use a new port. We
// want to use the new candidates and purge the old candidates as they come
// in, so use the fact that the old ports get pruned immediately to rank the
// candidates with an active port/remote candidate higher.
bool a_pruned = is_connection_pruned_func_(a);
bool b_pruned = is_connection_pruned_func_(b);
if (!a_pruned && b_pruned) {
return a_is_better;
}
if (a_pruned && !b_pruned) {
return b_is_better;
}
// Otherwise, must be equal
return 0;
}
int BasicIceController::CompareConnections(
const Connection* a,
const Connection* b,
absl::optional<int64_t> receiving_unchanged_threshold,
bool* missed_receiving_unchanged_threshold) const {
RTC_CHECK(a != nullptr);
RTC_CHECK(b != nullptr);
// We prefer to switch to a writable and receiving connection over a
// non-writable or non-receiving connection, even if the latter has
// been nominated by the controlling side.
int state_cmp = CompareConnectionStates(a, b, receiving_unchanged_threshold,
missed_receiving_unchanged_threshold);
if (state_cmp != 0) {
return state_cmp;
}
if (ice_role_func_() == ICEROLE_CONTROLLED) {
// Compare the connections based on the nomination states and the last data
// received time if this is on the controlled side.
if (a->remote_nomination() > b->remote_nomination()) {
return a_is_better;
}
if (a->remote_nomination() < b->remote_nomination()) {
return b_is_better;
}
if (a->last_data_received() > b->last_data_received()) {
return a_is_better;
}
if (a->last_data_received() < b->last_data_received()) {
return b_is_better;
}
}
// Compare the network cost and priority.
return CompareConnectionCandidates(a, b);
}
int BasicIceController::CompareCandidatePairNetworks(
const Connection* a,
const Connection* b,
absl::optional<rtc::AdapterType> network_preference) const {
int compare_a_b_by_network_preference =
CompareCandidatePairsByNetworkPreference(a, b,
config_.network_preference);
// The network preference has a higher precedence than the network cost.
if (compare_a_b_by_network_preference != a_and_b_equal) {
return compare_a_b_by_network_preference;
}
bool a_vpn = a->network()->IsVpn();
bool b_vpn = b->network()->IsVpn();
switch (config_.vpn_preference) {
case webrtc::VpnPreference::kDefault:
break;
case webrtc::VpnPreference::kOnlyUseVpn:
case webrtc::VpnPreference::kPreferVpn:
if (a_vpn && !b_vpn) {
return a_is_better;
} else if (!a_vpn && b_vpn) {
return b_is_better;
}
break;
case webrtc::VpnPreference::kNeverUseVpn:
case webrtc::VpnPreference::kAvoidVpn:
if (a_vpn && !b_vpn) {
return b_is_better;
} else if (!a_vpn && b_vpn) {
return a_is_better;
}
break;
default:
break;
}
uint32_t a_cost = a->ComputeNetworkCost();
uint32_t b_cost = b->ComputeNetworkCost();
// Prefer lower network cost.
if (a_cost < b_cost) {
return a_is_better;
}
if (a_cost > b_cost) {
return b_is_better;
}
return a_and_b_equal;
}
std::vector<const Connection*> BasicIceController::PruneConnections() {
// We can prune any connection for which there is a connected, writable
// connection on the same network with better or equal priority. We leave
// those with better priority just in case they become writable later (at
// which point, we would prune out the current selected connection). We leave
// connections on other networks because they may not be using the same
// resources and they may represent very distinct paths over which we can
// switch. If `best_conn_on_network` is not connected, we may be reconnecting
// a TCP connection and should not prune connections in this network.
// See the big comment in CompareConnectionStates.
//
// An exception is made for connections on an "any address" network, meaning
// not bound to any specific network interface. We don't want to keep one of
// these alive as a backup, since it could be using the same network
// interface as the higher-priority, selected candidate pair.
std::vector<const Connection*> connections_to_prune;
auto best_connection_by_network = GetBestConnectionByNetwork();
for (const Connection* conn : connections_) {
const Connection* best_conn = selected_connection_;
if (!rtc::IPIsAny(conn->network()->GetBestIP())) {
// If the connection is bound to a specific network interface (not an
// "any address" network), compare it against the best connection for
// that network interface rather than the best connection overall. This
// ensures that at least one connection per network will be left
// unpruned.
best_conn = best_connection_by_network[conn->network()];
}
// Do not prune connections if the connection being compared against is
// weak. Otherwise, it may delete connections prematurely.
if (best_conn && conn != best_conn && !best_conn->weak() &&
CompareConnectionCandidates(best_conn, conn) >= 0) {
connections_to_prune.push_back(conn);
}
}
return connections_to_prune;
}
bool BasicIceController::GetUseCandidateAttr(const Connection* conn,
NominationMode mode,
IceMode remote_ice_mode) const {
switch (mode) {
case NominationMode::REGULAR:
// TODO(honghaiz): Implement regular nomination.
return false;
case NominationMode::AGGRESSIVE:
if (remote_ice_mode == ICEMODE_LITE) {
return GetUseCandidateAttr(conn, NominationMode::REGULAR,
remote_ice_mode);
}
return true;
case NominationMode::SEMI_AGGRESSIVE: {
// Nominate if
// a) Remote is in FULL ICE AND
// a.1) `conn` is the selected connection OR
// a.2) there is no selected connection OR
// a.3) the selected connection is unwritable OR
// a.4) `conn` has higher priority than selected_connection.
// b) Remote is in LITE ICE AND
// b.1) `conn` is the selected_connection AND
// b.2) `conn` is writable.
bool selected = conn == selected_connection_;
if (remote_ice_mode == ICEMODE_LITE) {
return selected && conn->writable();
}
bool better_than_selected =
!selected_connection_ || !selected_connection_->writable() ||
CompareConnectionCandidates(selected_connection_, conn) < 0;
return selected || better_than_selected;
}
default:
RTC_DCHECK_NOTREACHED();
return false;
}
}
} // namespace cricket

View file

@ -0,0 +1,167 @@
/*
* Copyright 2019 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.
*/
#ifndef P2P_BASE_BASIC_ICE_CONTROLLER_H_
#define P2P_BASE_BASIC_ICE_CONTROLLER_H_
#include <algorithm>
#include <map>
#include <set>
#include <utility>
#include <vector>
#include "p2p/base/ice_controller_factory_interface.h"
#include "p2p/base/ice_controller_interface.h"
namespace cricket {
class BasicIceController : public IceControllerInterface {
public:
explicit BasicIceController(const IceControllerFactoryArgs& args);
virtual ~BasicIceController();
void SetIceConfig(const IceConfig& config) override;
void SetSelectedConnection(const Connection* selected_connection) override;
void AddConnection(const Connection* connection) override;
void OnConnectionDestroyed(const Connection* connection) override;
rtc::ArrayView<const Connection* const> GetConnections() const override {
return connections_;
}
rtc::ArrayView<const Connection*> connections() const override {
return rtc::ArrayView<const Connection*>(
const_cast<const Connection**>(connections_.data()),
connections_.size());
}
bool HasPingableConnection() const override;
PingResult SelectConnectionToPing(int64_t last_ping_sent_ms) override;
bool GetUseCandidateAttr(const Connection* conn,
NominationMode mode,
IceMode remote_ice_mode) const override;
SwitchResult ShouldSwitchConnection(IceSwitchReason reason,
const Connection* connection) override;
SwitchResult SortAndSwitchConnection(IceSwitchReason reason) override;
std::vector<const Connection*> PruneConnections() override;
// These methods are only for tests.
const Connection* FindNextPingableConnection() override;
void MarkConnectionPinged(const Connection* conn) override;
private:
// A transport channel is weak if the current best connection is either
// not receiving or not writable, or if there is no best connection at all.
bool weak() const {
return !selected_connection_ || selected_connection_->weak();
}
int weak_ping_interval() const {
return std::max(config_.ice_check_interval_weak_connectivity_or_default(),
config_.ice_check_min_interval_or_default());
}
int strong_ping_interval() const {
return std::max(config_.ice_check_interval_strong_connectivity_or_default(),
config_.ice_check_min_interval_or_default());
}
int check_receiving_interval() const {
return std::max(MIN_CHECK_RECEIVING_INTERVAL,
config_.receiving_timeout_or_default() / 10);
}
const Connection* FindOldestConnectionNeedingTriggeredCheck(int64_t now);
// Between `conn1` and `conn2`, this function returns the one which should
// be pinged first.
const Connection* MorePingable(const Connection* conn1,
const Connection* conn2);
// Select the connection which is Relay/Relay. If both of them are,
// UDP relay protocol takes precedence.
const Connection* MostLikelyToWork(const Connection* conn1,
const Connection* conn2);
// Compare the last_ping_sent time and return the one least recently pinged.
const Connection* LeastRecentlyPinged(const Connection* conn1,
const Connection* conn2);
bool IsPingable(const Connection* conn, int64_t now) const;
bool IsBackupConnection(const Connection* conn) const;
// Whether a writable connection is past its ping interval and needs to be
// pinged again.
bool WritableConnectionPastPingInterval(const Connection* conn,
int64_t now) const;
int CalculateActiveWritablePingInterval(const Connection* conn,
int64_t now) const;
std::map<const rtc::Network*, const Connection*> GetBestConnectionByNetwork()
const;
std::vector<const Connection*> GetBestWritableConnectionPerNetwork() const;
bool ReadyToSend(const Connection* connection) const;
bool PresumedWritable(const Connection* conn) const;
int CompareCandidatePairNetworks(
const Connection* a,
const Connection* b,
absl::optional<rtc::AdapterType> network_preference) const;
// The methods below return a positive value if `a` is preferable to `b`,
// a negative value if `b` is preferable, and 0 if they're equally preferable.
// If `receiving_unchanged_threshold` is set, then when `b` is receiving and
// `a` is not, returns a negative value only if `b` has been in receiving
// state and `a` has been in not receiving state since
// `receiving_unchanged_threshold` and sets
// `missed_receiving_unchanged_threshold` to true otherwise.
int CompareConnectionStates(
const Connection* a,
const Connection* b,
absl::optional<int64_t> receiving_unchanged_threshold,
bool* missed_receiving_unchanged_threshold) const;
int CompareConnectionCandidates(const Connection* a,
const Connection* b) const;
// Compares two connections based on the connection states
// (writable/receiving/connected), nomination states, last data received time,
// and static preferences. Does not include latency. Used by both sorting
// and ShouldSwitchSelectedConnection().
// Returns a positive value if `a` is better than `b`.
int CompareConnections(const Connection* a,
const Connection* b,
absl::optional<int64_t> receiving_unchanged_threshold,
bool* missed_receiving_unchanged_threshold) const;
SwitchResult HandleInitialSelectDampening(IceSwitchReason reason,
const Connection* new_connection);
std::function<IceTransportState()> ice_transport_state_func_;
std::function<IceRole()> ice_role_func_;
std::function<bool(const Connection*)> is_connection_pruned_func_;
IceConfig config_;
const IceFieldTrials* field_trials_;
// `connections_` is a sorted list with the first one always be the
// `selected_connection_` when it's not nullptr. The combination of
// `pinged_connections_` and `unpinged_connections_` has the same
// connections as `connections_`. These 2 sets maintain whether a
// connection should be pinged next or not.
const Connection* selected_connection_ = nullptr;
std::vector<const Connection*> connections_;
std::set<const Connection*> pinged_connections_;
std::set<const Connection*> unpinged_connections_;
// Timestamp for when we got the first selectable connection.
int64_t initial_select_timestamp_ms_ = 0;
};
} // namespace cricket
#endif // P2P_BASE_BASIC_ICE_CONTROLLER_H_

View file

@ -0,0 +1,201 @@
/*
* Copyright 2011 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/basic_packet_socket_factory.h"
#include <stddef.h>
#include <string>
#include "absl/memory/memory.h"
#include "api/async_dns_resolver.h"
#include "p2p/base/async_stun_tcp_socket.h"
#include "rtc_base/async_dns_resolver.h"
#include "rtc_base/async_tcp_socket.h"
#include "rtc_base/async_udp_socket.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/socket.h"
#include "rtc_base/socket_adapters.h"
#include "rtc_base/ssl_adapter.h"
namespace rtc {
BasicPacketSocketFactory::BasicPacketSocketFactory(
SocketFactory* socket_factory)
: socket_factory_(socket_factory) {}
BasicPacketSocketFactory::~BasicPacketSocketFactory() {}
AsyncPacketSocket* BasicPacketSocketFactory::CreateUdpSocket(
const SocketAddress& address,
uint16_t min_port,
uint16_t max_port) {
// UDP sockets are simple.
Socket* socket = socket_factory_->CreateSocket(address.family(), SOCK_DGRAM);
if (!socket) {
return NULL;
}
if (BindSocket(socket, address, min_port, max_port) < 0) {
RTC_LOG(LS_ERROR) << "UDP bind failed with error " << socket->GetError();
delete socket;
return NULL;
}
return new AsyncUDPSocket(socket);
}
AsyncListenSocket* BasicPacketSocketFactory::CreateServerTcpSocket(
const SocketAddress& local_address,
uint16_t min_port,
uint16_t max_port,
int opts) {
// Fail if TLS is required.
if (opts & PacketSocketFactory::OPT_TLS) {
RTC_LOG(LS_ERROR) << "TLS support currently is not available.";
return NULL;
}
if (opts & PacketSocketFactory::OPT_TLS_FAKE) {
RTC_LOG(LS_ERROR) << "Fake TLS not supported.";
return NULL;
}
Socket* socket =
socket_factory_->CreateSocket(local_address.family(), SOCK_STREAM);
if (!socket) {
return NULL;
}
if (BindSocket(socket, local_address, min_port, max_port) < 0) {
RTC_LOG(LS_ERROR) << "TCP bind failed with error " << socket->GetError();
delete socket;
return NULL;
}
RTC_CHECK(!(opts & PacketSocketFactory::OPT_STUN));
return new AsyncTcpListenSocket(absl::WrapUnique(socket));
}
AsyncPacketSocket* BasicPacketSocketFactory::CreateClientTcpSocket(
const SocketAddress& local_address,
const SocketAddress& remote_address,
const ProxyInfo& proxy_info,
const std::string& user_agent,
const PacketSocketTcpOptions& tcp_options) {
Socket* socket =
socket_factory_->CreateSocket(local_address.family(), SOCK_STREAM);
if (!socket) {
return NULL;
}
if (BindSocket(socket, local_address, 0, 0) < 0) {
// Allow BindSocket to fail if we're binding to the ANY address, since this
// is mostly redundant in the first place. The socket will be bound when we
// call Connect() instead.
if (local_address.IsAnyIP()) {
RTC_LOG(LS_WARNING) << "TCP bind failed with error " << socket->GetError()
<< "; ignoring since socket is using 'any' address.";
} else {
RTC_LOG(LS_ERROR) << "TCP bind failed with error " << socket->GetError();
delete socket;
return NULL;
}
}
// Set TCP_NODELAY (via OPT_NODELAY) for improved performance; this causes
// small media packets to be sent immediately rather than being buffered up,
// reducing latency.
//
// Must be done before calling Connect, otherwise it may fail.
if (socket->SetOption(Socket::OPT_NODELAY, 1) != 0) {
RTC_LOG(LS_ERROR) << "Setting TCP_NODELAY option failed with error "
<< socket->GetError();
}
if (proxy_info.type == PROXY_HTTPS) {
socket =
new AsyncHttpsProxySocket(socket, user_agent, proxy_info.address,
proxy_info.username, proxy_info.password);
}
// Assert that at most one TLS option is used.
int tlsOpts = tcp_options.opts & (PacketSocketFactory::OPT_TLS |
PacketSocketFactory::OPT_TLS_FAKE |
PacketSocketFactory::OPT_TLS_INSECURE);
RTC_DCHECK((tlsOpts & (tlsOpts - 1)) == 0);
if ((tlsOpts & PacketSocketFactory::OPT_TLS) ||
(tlsOpts & PacketSocketFactory::OPT_TLS_INSECURE)) {
// Using TLS, wrap the socket in an SSL adapter.
SSLAdapter* ssl_adapter = SSLAdapter::Create(socket);
if (!ssl_adapter) {
return NULL;
}
if (tlsOpts & PacketSocketFactory::OPT_TLS_INSECURE) {
ssl_adapter->SetIgnoreBadCert(true);
}
ssl_adapter->SetAlpnProtocols(tcp_options.tls_alpn_protocols);
ssl_adapter->SetEllipticCurves(tcp_options.tls_elliptic_curves);
ssl_adapter->SetCertVerifier(tcp_options.tls_cert_verifier);
socket = ssl_adapter;
if (ssl_adapter->StartSSL(remote_address.hostname().c_str()) != 0) {
delete ssl_adapter;
return NULL;
}
} else if (tlsOpts & PacketSocketFactory::OPT_TLS_FAKE) {
// Using fake TLS, wrap the TCP socket in a pseudo-SSL socket.
socket = new AsyncSSLSocket(socket);
}
if (socket->Connect(remote_address) < 0) {
RTC_LOG(LS_ERROR) << "TCP connect failed with error " << socket->GetError();
delete socket;
return NULL;
}
// Finally, wrap that socket in a TCP or STUN TCP packet socket.
AsyncPacketSocket* tcp_socket;
if (tcp_options.opts & PacketSocketFactory::OPT_STUN) {
tcp_socket = new cricket::AsyncStunTCPSocket(socket);
} else {
tcp_socket = new AsyncTCPSocket(socket);
}
return tcp_socket;
}
std::unique_ptr<webrtc::AsyncDnsResolverInterface>
BasicPacketSocketFactory::CreateAsyncDnsResolver() {
return std::make_unique<webrtc::AsyncDnsResolver>();
}
int BasicPacketSocketFactory::BindSocket(Socket* socket,
const SocketAddress& local_address,
uint16_t min_port,
uint16_t max_port) {
int ret = -1;
if (min_port == 0 && max_port == 0) {
// If there's no port range, let the OS pick a port for us.
ret = socket->Bind(local_address);
} else {
// Otherwise, try to find a port in the provided range.
for (int port = min_port; ret < 0 && port <= max_port; ++port) {
ret = socket->Bind(SocketAddress(local_address.ipaddr(), port));
}
}
return ret;
}
} // namespace rtc

View file

@ -0,0 +1,65 @@
/*
* Copyright 2011 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.
*/
#ifndef P2P_BASE_BASIC_PACKET_SOCKET_FACTORY_H_
#define P2P_BASE_BASIC_PACKET_SOCKET_FACTORY_H_
#include <stdint.h>
#include <memory>
#include <string>
#include "api/async_dns_resolver.h"
#include "api/packet_socket_factory.h"
#include "rtc_base/async_packet_socket.h"
#include "rtc_base/proxy_info.h"
#include "rtc_base/socket.h"
#include "rtc_base/socket_address.h"
#include "rtc_base/socket_factory.h"
#include "rtc_base/system/rtc_export.h"
namespace rtc {
class SocketFactory;
class RTC_EXPORT BasicPacketSocketFactory : public PacketSocketFactory {
public:
explicit BasicPacketSocketFactory(SocketFactory* socket_factory);
~BasicPacketSocketFactory() override;
AsyncPacketSocket* CreateUdpSocket(const SocketAddress& local_address,
uint16_t min_port,
uint16_t max_port) override;
AsyncListenSocket* CreateServerTcpSocket(const SocketAddress& local_address,
uint16_t min_port,
uint16_t max_port,
int opts) override;
AsyncPacketSocket* CreateClientTcpSocket(
const SocketAddress& local_address,
const SocketAddress& remote_address,
const ProxyInfo& proxy_info,
const std::string& user_agent,
const PacketSocketTcpOptions& tcp_options) override;
std::unique_ptr<webrtc::AsyncDnsResolverInterface> CreateAsyncDnsResolver()
override;
private:
int BindSocket(Socket* socket,
const SocketAddress& local_address,
uint16_t min_port,
uint16_t max_port);
SocketFactory* socket_factory_;
};
} // namespace rtc
#endif // P2P_BASE_BASIC_PACKET_SOCKET_FACTORY_H_

View file

@ -0,0 +1,40 @@
/*
* Copyright 2016 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.
*/
#ifndef P2P_BASE_CANDIDATE_PAIR_INTERFACE_H_
#define P2P_BASE_CANDIDATE_PAIR_INTERFACE_H_
namespace cricket {
class Candidate;
class CandidatePairInterface {
public:
virtual ~CandidatePairInterface() {}
virtual const Candidate& local_candidate() const = 0;
virtual const Candidate& remote_candidate() const = 0;
};
// Specific implementation of the interface, suitable for being a
// data member of other structs.
struct CandidatePair final : public CandidatePairInterface {
~CandidatePair() override = default;
const Candidate& local_candidate() const override { return local; }
const Candidate& remote_candidate() const override { return remote; }
Candidate local;
Candidate remote;
};
} // namespace cricket
#endif // P2P_BASE_CANDIDATE_PAIR_INTERFACE_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,535 @@
/*
* Copyright 2019 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.
*/
#ifndef P2P_BASE_CONNECTION_H_
#define P2P_BASE_CONNECTION_H_
#include <stddef.h>
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "absl/functional/any_invocable.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "api/candidate.h"
#include "api/rtc_error.h"
#include "api/sequence_checker.h"
#include "api/task_queue/task_queue_base.h"
#include "api/transport/stun.h"
#include "logging/rtc_event_log/events/rtc_event_ice_candidate_pair.h"
#include "logging/rtc_event_log/events/rtc_event_ice_candidate_pair_config.h"
#include "logging/rtc_event_log/ice_logger.h"
#include "p2p/base/candidate_pair_interface.h"
#include "p2p/base/connection_info.h"
#include "p2p/base/p2p_transport_channel_ice_field_trials.h"
#include "p2p/base/port_interface.h"
#include "p2p/base/stun_request.h"
#include "p2p/base/transport_description.h"
#include "rtc_base/async_packet_socket.h"
#include "rtc_base/network.h"
#include "rtc_base/network/received_packet.h"
#include "rtc_base/numerics/event_based_exponential_moving_average.h"
#include "rtc_base/rate_tracker.h"
#include "rtc_base/system/rtc_export.h"
#include "rtc_base/third_party/sigslot/sigslot.h"
#include "rtc_base/thread_annotations.h"
#include "rtc_base/weak_ptr.h"
namespace cricket {
// Version number for GOOG_PING, this is added to have the option of
// adding other flavors in the future.
constexpr int kGoogPingVersion = 1;
// Forward declaration so that a ConnectionRequest can contain a Connection.
class Connection;
// Represents a communication link between a port on the local client and a
// port on the remote client.
class RTC_EXPORT Connection : public CandidatePairInterface {
public:
struct SentPing {
SentPing(absl::string_view id, int64_t sent_time, uint32_t nomination)
: id(id), sent_time(sent_time), nomination(nomination) {}
std::string id;
int64_t sent_time;
uint32_t nomination;
};
~Connection() override;
// A unique ID assigned when the connection is created.
uint32_t id() const { return id_; }
webrtc::TaskQueueBase* network_thread() const;
// Implementation of virtual methods in CandidatePairInterface.
// Returns the description of the local port
const Candidate& local_candidate() const override;
// Returns the description of the remote port to which we communicate.
const Candidate& remote_candidate() const override;
// Return local network for this connection.
virtual const rtc::Network* network() const;
// Return generation for this connection.
virtual int generation() const;
// Returns the pair priority.
virtual uint64_t priority() const;
enum WriteState {
STATE_WRITABLE = 0, // we have received ping responses recently
STATE_WRITE_UNRELIABLE = 1, // we have had a few ping failures
STATE_WRITE_INIT = 2, // we have yet to receive a ping response
STATE_WRITE_TIMEOUT = 3, // we have had a large number of ping failures
};
WriteState write_state() const;
bool writable() const;
bool receiving() const;
const PortInterface* port() const {
RTC_DCHECK_RUN_ON(network_thread_);
return port_.get();
}
// Determines whether the connection has finished connecting. This can only
// be false for TCP connections.
bool connected() const;
bool weak() const;
bool active() const;
bool pending_delete() const { return !port_; }
// A connection is dead if it can be safely deleted.
bool dead(int64_t now) const;
// Estimate of the round-trip time over this connection.
int rtt() const;
int unwritable_timeout() const;
void set_unwritable_timeout(const absl::optional<int>& value_ms);
int unwritable_min_checks() const;
void set_unwritable_min_checks(const absl::optional<int>& value);
int inactive_timeout() const;
void set_inactive_timeout(const absl::optional<int>& value);
// Gets the `ConnectionInfo` stats, where `best_connection` has not been
// populated (default value false).
ConnectionInfo stats();
sigslot::signal1<Connection*> SignalStateChange;
// Sent when the connection has decided that it is no longer of value. It
// will delete itself immediately after this call.
sigslot::signal1<Connection*> SignalDestroyed;
// The connection can send and receive packets asynchronously. This matches
// the interface of AsyncPacketSocket, which may use UDP or TCP under the
// covers.
virtual int Send(const void* data,
size_t size,
const rtc::PacketOptions& options) = 0;
// Error if Send() returns < 0
virtual int GetError() = 0;
// Register as a recipient of received packets. There can only be one.
void RegisterReceivedPacketCallback(
absl::AnyInvocable<void(Connection*, const rtc::ReceivedPacket&)>
received_packet_callback);
void DeregisterReceivedPacketCallback();
sigslot::signal1<Connection*> SignalReadyToSend;
// Called when a packet is received on this connection.
void OnReadPacket(const rtc::ReceivedPacket& packet);
[[deprecated("Pass a rtc::ReceivedPacket")]] void
OnReadPacket(const char* data, size_t size, int64_t packet_time_us);
// Called when the socket is currently able to send.
void OnReadyToSend();
// Called when a connection is determined to be no longer useful to us. We
// still keep it around in case the other side wants to use it. But we can
// safely stop pinging on it and we can allow it to time out if the other
// side stops using it as well.
bool pruned() const;
void Prune();
bool use_candidate_attr() const;
void set_use_candidate_attr(bool enable);
void set_nomination(uint32_t value);
uint32_t remote_nomination() const;
// One or several pairs may be nominated based on if Regular or Aggressive
// Nomination is used. https://tools.ietf.org/html/rfc5245#section-8
// `nominated` is defined both for the controlling or controlled agent based
// on if a nomination has been pinged or acknowledged. The controlled agent
// gets its `remote_nomination_` set when pinged by the controlling agent with
// a nomination value. The controlling agent gets its `acked_nomination_` set
// when receiving a response to a nominating ping.
bool nominated() const;
int receiving_timeout() const;
void set_receiving_timeout(absl::optional<int> receiving_timeout_ms);
// Deletes a `Connection` instance is by calling the `DestroyConnection`
// method in `Port`.
// Note: When the function returns, the object has been deleted.
void Destroy();
// Signals object destruction, releases outstanding references and performs
// final logging.
// The function will return `true` when shutdown was performed, signals
// emitted and outstanding references released. If the function returns
// `false`, `Shutdown()` has previously been called.
bool Shutdown();
// Prunes the connection and sets its state to STATE_FAILED,
// It will not be used or send pings although it can still receive packets.
void FailAndPrune();
// Checks that the state of this connection is up-to-date. The argument is
// the current time, which is compared against various timeouts.
void UpdateState(int64_t now);
void UpdateLocalIceParameters(int component,
absl::string_view username_fragment,
absl::string_view password);
// Called when this connection should try checking writability again.
int64_t last_ping_sent() const;
void Ping(int64_t now,
std::unique_ptr<StunByteStringAttribute> delta = nullptr);
void ReceivedPingResponse(
int rtt,
absl::string_view request_id,
const absl::optional<uint32_t>& nomination = absl::nullopt);
std::unique_ptr<IceMessage> BuildPingRequest(
std::unique_ptr<StunByteStringAttribute> delta)
RTC_RUN_ON(network_thread_);
int64_t last_ping_response_received() const;
const absl::optional<std::string>& last_ping_id_received() const;
// Used to check if any STUN ping response has been received.
int rtt_samples() const;
// Called whenever a valid ping is received on this connection. This is
// public because the connection intercepts the first ping for us.
int64_t last_ping_received() const;
void ReceivedPing(
const absl::optional<std::string>& request_id = absl::nullopt);
// Handles the binding request; sends a response if this is a valid request.
void HandleStunBindingOrGoogPingRequest(IceMessage* msg);
// Handles the piggyback acknowledgement of the lastest connectivity check
// that the remote peer has received, if it is indicated in the incoming
// connectivity check from the peer.
void HandlePiggybackCheckAcknowledgementIfAny(StunMessage* msg);
// Timestamp when data was last sent (or attempted to be sent).
int64_t last_send_data() const;
int64_t last_data_received() const;
// Debugging description of this connection
std::string ToDebugId() const;
std::string ToString() const;
std::string ToSensitiveString() const;
// Structured description of this candidate pair.
const webrtc::IceCandidatePairDescription& ToLogDescription();
void set_ice_event_log(webrtc::IceEventLog* ice_event_log);
// Prints pings_since_last_response_ into a string.
void PrintPingsSinceLastResponse(std::string* pings, size_t max);
// `set_selected` is only used for logging in ToString above. The flag is
// set true by P2PTransportChannel for its selected candidate pair.
// TODO(tommi): Remove `selected()` once not referenced downstream.
bool selected() const;
void set_selected(bool selected);
// This signal will be fired if this connection is nominated by the
// controlling side.
sigslot::signal1<Connection*> SignalNominated;
IceCandidatePairState state() const;
int num_pings_sent() const;
uint32_t ComputeNetworkCost() const;
// Update the ICE password and/or generation of the remote candidate if the
// ufrag in `params` matches the candidate's ufrag, and the
// candidate's password and/or ufrag has not been set.
void MaybeSetRemoteIceParametersAndGeneration(const IceParameters& params,
int generation);
// If `remote_candidate_` is peer reflexive and is equivalent to
// `new_candidate` except the type, update `remote_candidate_` to
// `new_candidate`.
void MaybeUpdatePeerReflexiveCandidate(const Candidate& new_candidate);
// Returns the last received time of any data, stun request, or stun
// response in milliseconds
int64_t last_received() const;
// Returns the last time when the connection changed its receiving state.
int64_t receiving_unchanged_since() const;
// Constructs the prflx priority as described in
// https://datatracker.ietf.org/doc/html/rfc5245#section-4.1.2.1
uint32_t prflx_priority() const;
bool stable(int64_t now) const;
// Check if we sent `val` pings without receving a response.
bool TooManyOutstandingPings(const absl::optional<int>& val) const;
// Called by Port when the network cost changes.
void SetLocalCandidateNetworkCost(uint16_t cost);
void SetIceFieldTrials(const IceFieldTrials* field_trials);
const rtc::EventBasedExponentialMovingAverage& GetRttEstimate() const {
return rtt_estimate_;
}
// Reset the connection to a state of a newly connected.
// - STATE_WRITE_INIT
// - receving = false
// - throw away all pending request
// - reset RttEstimate
//
// Keep the following unchanged:
// - connected
// - remote_candidate
// - statistics
//
// Does not trigger SignalStateChange
void ForgetLearnedState();
void SendStunBindingResponse(const StunMessage* message);
void SendGoogPingResponse(const StunMessage* message);
void SendResponseMessage(const StunMessage& response);
// An accessor for unit tests.
PortInterface* PortForTest() { return port_.get(); }
const PortInterface* PortForTest() const { return port_.get(); }
std::unique_ptr<IceMessage> BuildPingRequestForTest() {
RTC_DCHECK_RUN_ON(network_thread_);
return BuildPingRequest(nullptr);
}
// Public for unit tests.
uint32_t acked_nomination() const;
void set_remote_nomination(uint32_t remote_nomination);
const std::string& remote_password_for_test() const {
return remote_candidate().password();
}
void set_remote_password_for_test(absl::string_view pwd) {
remote_candidate_.set_password(pwd);
}
void SetStunDictConsumer(
std::function<std::unique_ptr<StunAttribute>(
const StunByteStringAttribute*)> goog_delta_consumer,
std::function<void(webrtc::RTCErrorOr<const StunUInt64Attribute*>)>
goog_delta_ack_consumer) {
goog_delta_consumer_ = std::move(goog_delta_consumer);
goog_delta_ack_consumer_ = std::move(goog_delta_ack_consumer);
}
void ClearStunDictConsumer() {
goog_delta_consumer_ = absl::nullopt;
goog_delta_ack_consumer_ = absl::nullopt;
}
protected:
// A ConnectionRequest is a simple STUN ping used to determine writability.
class ConnectionRequest;
// Constructs a new connection to the given remote port.
Connection(rtc::WeakPtr<PortInterface> port,
size_t index,
const Candidate& candidate);
// Called back when StunRequestManager has a stun packet to send
void OnSendStunPacket(const void* data, size_t size, StunRequest* req);
// Callbacks from ConnectionRequest
virtual void OnConnectionRequestResponse(StunRequest* req,
StunMessage* response);
void OnConnectionRequestErrorResponse(ConnectionRequest* req,
StunMessage* response)
RTC_RUN_ON(network_thread_);
void OnConnectionRequestTimeout(ConnectionRequest* req)
RTC_RUN_ON(network_thread_);
void OnConnectionRequestSent(ConnectionRequest* req)
RTC_RUN_ON(network_thread_);
bool rtt_converged() const;
// If the response is not received within 2 * RTT, the response is assumed to
// be missing.
bool missing_responses(int64_t now) const;
// Changes the state and signals if necessary.
void set_write_state(WriteState value);
void UpdateReceiving(int64_t now);
void set_state(IceCandidatePairState state);
void set_connected(bool value);
// The local port where this connection sends and receives packets.
PortInterface* port() { return port_.get(); }
// NOTE: A pointer to the network thread is held by `port_` so in theory we
// shouldn't need to hold on to this pointer here, but rather defer to
// port_->thread(). However, some tests delete the classes in the wrong order
// so `port_` may be deleted before an instance of this class is deleted.
// TODO(tommi): This ^^^ should be fixed.
webrtc::TaskQueueBase* const network_thread_;
const uint32_t id_;
rtc::WeakPtr<PortInterface> port_;
Candidate local_candidate_ RTC_GUARDED_BY(network_thread_);
Candidate remote_candidate_;
ConnectionInfo stats_;
rtc::RateTracker recv_rate_tracker_;
rtc::RateTracker send_rate_tracker_;
int64_t last_send_data_ = 0;
private:
// Update the local candidate based on the mapped address attribute.
// If the local candidate changed, fires SignalStateChange.
void MaybeUpdateLocalCandidate(StunRequest* request, StunMessage* response)
RTC_RUN_ON(network_thread_);
void LogCandidatePairConfig(webrtc::IceCandidatePairConfigType type)
RTC_RUN_ON(network_thread_);
void LogCandidatePairEvent(webrtc::IceCandidatePairEventType type,
uint32_t transaction_id)
RTC_RUN_ON(network_thread_);
// Check if this IceMessage is identical
// to last message ack:ed STUN_BINDING_REQUEST.
bool ShouldSendGoogPing(const StunMessage* message)
RTC_RUN_ON(network_thread_);
WriteState write_state_ RTC_GUARDED_BY(network_thread_);
bool receiving_ RTC_GUARDED_BY(network_thread_);
bool connected_ RTC_GUARDED_BY(network_thread_);
bool pruned_ RTC_GUARDED_BY(network_thread_);
bool selected_ RTC_GUARDED_BY(network_thread_) = false;
// By default `use_candidate_attr_` flag will be true,
// as we will be using aggressive nomination.
// But when peer is ice-lite, this flag "must" be initialized to false and
// turn on when connection becomes "best connection".
bool use_candidate_attr_ RTC_GUARDED_BY(network_thread_);
// Used by the controlling side to indicate that this connection will be
// selected for transmission if the peer supports ICE-renomination when this
// value is positive. A larger-value indicates that a connection is nominated
// later and should be selected by the controlled side with higher precedence.
// A zero-value indicates not nominating this connection.
uint32_t nomination_ RTC_GUARDED_BY(network_thread_) = 0;
// The last nomination that has been acknowledged.
uint32_t acked_nomination_ RTC_GUARDED_BY(network_thread_) = 0;
// Used by the controlled side to remember the nomination value received from
// the controlling side. When the peer does not support ICE re-nomination, its
// value will be 1 if the connection has been nominated.
uint32_t remote_nomination_ RTC_GUARDED_BY(network_thread_) = 0;
StunRequestManager requests_ RTC_GUARDED_BY(network_thread_);
int rtt_ RTC_GUARDED_BY(network_thread_);
int rtt_samples_ RTC_GUARDED_BY(network_thread_) = 0;
// https://w3c.github.io/webrtc-stats/#dom-rtcicecandidatepairstats-totalroundtriptime
uint64_t total_round_trip_time_ms_ RTC_GUARDED_BY(network_thread_) = 0;
// https://w3c.github.io/webrtc-stats/#dom-rtcicecandidatepairstats-currentroundtriptime
absl::optional<uint32_t> current_round_trip_time_ms_
RTC_GUARDED_BY(network_thread_);
int64_t last_ping_sent_ RTC_GUARDED_BY(
network_thread_); // last time we sent a ping to the other side
int64_t last_ping_received_
RTC_GUARDED_BY(network_thread_); // last time we received a ping from the
// other side
int64_t last_data_received_ RTC_GUARDED_BY(network_thread_);
int64_t last_ping_response_received_ RTC_GUARDED_BY(network_thread_);
int64_t receiving_unchanged_since_ RTC_GUARDED_BY(network_thread_) = 0;
std::vector<SentPing> pings_since_last_response_
RTC_GUARDED_BY(network_thread_);
// Transaction ID of the last connectivity check received. Null if having not
// received a ping yet.
absl::optional<std::string> last_ping_id_received_
RTC_GUARDED_BY(network_thread_);
absl::optional<int> unwritable_timeout_ RTC_GUARDED_BY(network_thread_);
absl::optional<int> unwritable_min_checks_ RTC_GUARDED_BY(network_thread_);
absl::optional<int> inactive_timeout_ RTC_GUARDED_BY(network_thread_);
IceCandidatePairState state_ RTC_GUARDED_BY(network_thread_);
// Time duration to switch from receiving to not receiving.
absl::optional<int> receiving_timeout_ RTC_GUARDED_BY(network_thread_);
const int64_t time_created_ms_ RTC_GUARDED_BY(network_thread_);
const int64_t delta_internal_unix_epoch_ms_ RTC_GUARDED_BY(network_thread_);
int num_pings_sent_ RTC_GUARDED_BY(network_thread_) = 0;
absl::optional<webrtc::IceCandidatePairDescription> log_description_
RTC_GUARDED_BY(network_thread_);
webrtc::IceEventLog* ice_event_log_ RTC_GUARDED_BY(network_thread_) = nullptr;
// GOOG_PING_REQUEST is sent in place of STUN_BINDING_REQUEST
// if configured via field trial, the remote peer supports it (signaled
// in STUN_BINDING) and if the last STUN BINDING is identical to the one
// that is about to be sent.
absl::optional<bool> remote_support_goog_ping_
RTC_GUARDED_BY(network_thread_);
std::unique_ptr<StunMessage> cached_stun_binding_
RTC_GUARDED_BY(network_thread_);
const IceFieldTrials* field_trials_;
rtc::EventBasedExponentialMovingAverage rtt_estimate_
RTC_GUARDED_BY(network_thread_);
absl::optional<std::function<std::unique_ptr<StunAttribute>(
const StunByteStringAttribute*)>>
goog_delta_consumer_;
absl::optional<
std::function<void(webrtc::RTCErrorOr<const StunUInt64Attribute*>)>>
goog_delta_ack_consumer_;
absl::AnyInvocable<void(Connection*, const rtc::ReceivedPacket&)>
received_packet_callback_;
};
// ProxyConnection defers all the interesting work to the port.
class ProxyConnection : public Connection {
public:
ProxyConnection(rtc::WeakPtr<PortInterface> port,
size_t index,
const Candidate& remote_candidate);
int Send(const void* data,
size_t size,
const rtc::PacketOptions& options) override;
int GetError() override;
private:
int error_ = 0;
};
} // namespace cricket
#endif // P2P_BASE_CONNECTION_H_

View file

@ -0,0 +1,44 @@
/*
* Copyright 2019 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/connection_info.h"
namespace cricket {
ConnectionInfo::ConnectionInfo()
: best_connection(false),
writable(false),
receiving(false),
timeout(false),
rtt(0),
sent_discarded_bytes(0),
sent_total_bytes(0),
sent_bytes_second(0),
sent_discarded_packets(0),
sent_total_packets(0),
sent_ping_requests_total(0),
sent_ping_requests_before_first_response(0),
sent_ping_responses(0),
recv_total_bytes(0),
recv_bytes_second(0),
packets_received(0),
recv_ping_requests(0),
recv_ping_responses(0),
key(nullptr),
state(IceCandidatePairState::WAITING),
priority(0),
nominated(false),
total_round_trip_time_ms(0) {}
ConnectionInfo::ConnectionInfo(const ConnectionInfo&) = default;
ConnectionInfo::~ConnectionInfo() = default;
} // namespace cricket

View file

@ -0,0 +1,87 @@
/*
* Copyright 2019 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.
*/
#ifndef P2P_BASE_CONNECTION_INFO_H_
#define P2P_BASE_CONNECTION_INFO_H_
#include <vector>
#include "absl/types/optional.h"
#include "api/candidate.h"
#include "api/units/timestamp.h"
namespace cricket {
// States are from RFC 5245. http://tools.ietf.org/html/rfc5245#section-5.7.4
enum class IceCandidatePairState {
WAITING = 0, // Check has not been performed, Waiting pair on CL.
IN_PROGRESS, // Check has been sent, transaction is in progress.
SUCCEEDED, // Check already done, produced a successful result.
FAILED, // Check for this connection failed.
// According to spec there should also be a frozen state, but nothing is ever
// frozen because we have not implemented ICE freezing logic.
};
// Stats that we can return about the connections for a transport channel.
// TODO(hta): Rename to ConnectionStats
struct ConnectionInfo {
ConnectionInfo();
ConnectionInfo(const ConnectionInfo&);
~ConnectionInfo();
bool best_connection; // Is this the best connection we have?
bool writable; // Has this connection received a STUN response?
bool receiving; // Has this connection received anything?
bool timeout; // Has this connection timed out?
size_t rtt; // The STUN RTT for this connection.
size_t sent_discarded_bytes; // Number of outgoing bytes discarded due to
// socket errors.
size_t sent_total_bytes; // Total bytes sent on this connection. Does not
// include discarded bytes.
size_t sent_bytes_second; // Bps over the last measurement interval.
size_t sent_discarded_packets; // Number of outgoing packets discarded due to
// socket errors.
size_t sent_total_packets; // Number of total outgoing packets attempted for
// sending, including discarded packets.
size_t sent_ping_requests_total; // Number of STUN ping request sent.
size_t sent_ping_requests_before_first_response; // Number of STUN ping
// sent before receiving the first response.
size_t sent_ping_responses; // Number of STUN ping response sent.
size_t recv_total_bytes; // Total bytes received on this connection.
size_t recv_bytes_second; // Bps over the last measurement interval.
size_t packets_received; // Number of packets that were received.
size_t recv_ping_requests; // Number of STUN ping request received.
size_t recv_ping_responses; // Number of STUN ping response received.
Candidate local_candidate; // The local candidate for this connection.
Candidate remote_candidate; // The remote candidate for this connection.
void* key; // A static value that identifies this conn.
// https://w3c.github.io/webrtc-stats/#dom-rtcicecandidatepairstats-state
IceCandidatePairState state;
// https://w3c.github.io/webrtc-stats/#dom-rtcicecandidatepairstats-priority
uint64_t priority;
// https://w3c.github.io/webrtc-stats/#dom-rtcicecandidatepairstats-nominated
bool nominated;
// https://w3c.github.io/webrtc-stats/#dom-rtcicecandidatepairstats-totalroundtriptime
uint64_t total_round_trip_time_ms;
// https://w3c.github.io/webrtc-stats/#dom-rtcicecandidatepairstats-currentroundtriptime
absl::optional<uint32_t> current_round_trip_time_ms;
// https://w3c.github.io/webrtc-stats/#dom-rtcicecandidatepairstats-lastpacketreceivedtimestamp
absl::optional<webrtc::Timestamp> last_data_received;
absl::optional<webrtc::Timestamp> last_data_sent;
};
// Information about all the candidate pairs of a channel.
typedef std::vector<ConnectionInfo> ConnectionInfos;
} // namespace cricket
#endif // P2P_BASE_CONNECTION_INFO_H_

View file

@ -0,0 +1,54 @@
/*
* Copyright 2019 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/default_ice_transport_factory.h"
#include <utility>
#include "api/make_ref_counted.h"
#include "p2p/base/basic_ice_controller.h"
#include "p2p/base/ice_controller_factory_interface.h"
namespace {
class BasicIceControllerFactory
: public cricket::IceControllerFactoryInterface {
public:
std::unique_ptr<cricket::IceControllerInterface> Create(
const cricket::IceControllerFactoryArgs& args) override {
return std::make_unique<cricket::BasicIceController>(args);
}
};
} // namespace
namespace webrtc {
DefaultIceTransport::DefaultIceTransport(
std::unique_ptr<cricket::P2PTransportChannel> internal)
: internal_(std::move(internal)) {}
DefaultIceTransport::~DefaultIceTransport() {
RTC_DCHECK_RUN_ON(&thread_checker_);
}
rtc::scoped_refptr<IceTransportInterface>
DefaultIceTransportFactory::CreateIceTransport(
const std::string& transport_name,
int component,
IceTransportInit init) {
BasicIceControllerFactory factory;
init.set_ice_controller_factory(&factory);
return rtc::make_ref_counted<DefaultIceTransport>(
cricket::P2PTransportChannel::Create(transport_name, component,
std::move(init)));
}
} // namespace webrtc

View file

@ -0,0 +1,58 @@
/*
* Copyright 2019 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.
*/
#ifndef P2P_BASE_DEFAULT_ICE_TRANSPORT_FACTORY_H_
#define P2P_BASE_DEFAULT_ICE_TRANSPORT_FACTORY_H_
#include <memory>
#include <string>
#include "api/ice_transport_interface.h"
#include "p2p/base/p2p_transport_channel.h"
#include "rtc_base/thread.h"
namespace webrtc {
// The default ICE transport wraps the implementation of IceTransportInternal
// provided by P2PTransportChannel. This default transport is not thread safe
// and must be constructed, used and destroyed on the same network thread on
// which the internal P2PTransportChannel lives.
class DefaultIceTransport : public IceTransportInterface {
public:
explicit DefaultIceTransport(
std::unique_ptr<cricket::P2PTransportChannel> internal);
~DefaultIceTransport();
cricket::IceTransportInternal* internal() override {
RTC_DCHECK_RUN_ON(&thread_checker_);
return internal_.get();
}
private:
const SequenceChecker thread_checker_{};
std::unique_ptr<cricket::P2PTransportChannel> internal_
RTC_GUARDED_BY(thread_checker_);
};
class DefaultIceTransportFactory : public IceTransportFactory {
public:
DefaultIceTransportFactory() = default;
~DefaultIceTransportFactory() = default;
// Must be called on the network thread and returns a DefaultIceTransport.
rtc::scoped_refptr<IceTransportInterface> CreateIceTransport(
const std::string& transport_name,
int component,
IceTransportInit init) override;
};
} // namespace webrtc
#endif // P2P_BASE_DEFAULT_ICE_TRANSPORT_FACTORY_H_

View file

@ -0,0 +1,877 @@
/*
* Copyright 2011 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/dtls_transport.h"
#include <algorithm>
#include <memory>
#include <utility>
#include "absl/memory/memory.h"
#include "absl/strings/string_view.h"
#include "api/array_view.h"
#include "api/dtls_transport_interface.h"
#include "api/rtc_event_log/rtc_event_log.h"
#include "logging/rtc_event_log/events/rtc_event_dtls_transport_state.h"
#include "logging/rtc_event_log/events/rtc_event_dtls_writable_state.h"
#include "p2p/base/packet_transport_internal.h"
#include "rtc_base/buffer.h"
#include "rtc_base/checks.h"
#include "rtc_base/dscp.h"
#include "rtc_base/logging.h"
#include "rtc_base/rtc_certificate.h"
#include "rtc_base/ssl_stream_adapter.h"
#include "rtc_base/stream.h"
#include "rtc_base/thread.h"
namespace cricket {
// We don't pull the RTP constants from rtputils.h, to avoid a layer violation.
static const size_t kDtlsRecordHeaderLen = 13;
static const size_t kMaxDtlsPacketLen = 2048;
static const size_t kMinRtpPacketLen = 12;
// Maximum number of pending packets in the queue. Packets are read immediately
// after they have been written, so a capacity of "1" is sufficient.
//
// However, this bug seems to indicate that's not the case: crbug.com/1063834
// So, temporarily increasing it to 2 to see if that makes a difference.
static const size_t kMaxPendingPackets = 2;
// Minimum and maximum values for the initial DTLS handshake timeout. We'll pick
// an initial timeout based on ICE RTT estimates, but clamp it to this range.
static const int kMinHandshakeTimeout = 50;
static const int kMaxHandshakeTimeout = 3000;
static bool IsDtlsPacket(const char* data, size_t len) {
const uint8_t* u = reinterpret_cast<const uint8_t*>(data);
return (len >= kDtlsRecordHeaderLen && (u[0] > 19 && u[0] < 64));
}
static bool IsDtlsClientHelloPacket(const char* data, size_t len) {
if (!IsDtlsPacket(data, len)) {
return false;
}
const uint8_t* u = reinterpret_cast<const uint8_t*>(data);
return len > 17 && u[0] == 22 && u[13] == 1;
}
static bool IsRtpPacket(const char* data, size_t len) {
const uint8_t* u = reinterpret_cast<const uint8_t*>(data);
return (len >= kMinRtpPacketLen && (u[0] & 0xC0) == 0x80);
}
StreamInterfaceChannel::StreamInterfaceChannel(
IceTransportInternal* ice_transport)
: ice_transport_(ice_transport),
state_(rtc::SS_OPEN),
packets_(kMaxPendingPackets, kMaxDtlsPacketLen) {}
rtc::StreamResult StreamInterfaceChannel::Read(rtc::ArrayView<uint8_t> buffer,
size_t& read,
int& error) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
if (state_ == rtc::SS_CLOSED)
return rtc::SR_EOS;
if (state_ == rtc::SS_OPENING)
return rtc::SR_BLOCK;
if (!packets_.ReadFront(buffer.data(), buffer.size(), &read)) {
return rtc::SR_BLOCK;
}
return rtc::SR_SUCCESS;
}
rtc::StreamResult StreamInterfaceChannel::Write(
rtc::ArrayView<const uint8_t> data,
size_t& written,
int& error) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
// Always succeeds, since this is an unreliable transport anyway.
// TODO(zhihuang): Should this block if ice_transport_'s temporarily
// unwritable?
rtc::PacketOptions packet_options;
ice_transport_->SendPacket(reinterpret_cast<const char*>(data.data()),
data.size(), packet_options);
written = data.size();
return rtc::SR_SUCCESS;
}
bool StreamInterfaceChannel::OnPacketReceived(const char* data, size_t size) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
if (packets_.size() > 0) {
RTC_LOG(LS_WARNING) << "Packet already in queue.";
}
bool ret = packets_.WriteBack(data, size, NULL);
if (!ret) {
// Somehow we received another packet before the SSLStreamAdapter read the
// previous one out of our temporary buffer. In this case, we'll log an
// error and still signal the read event, hoping that it will read the
// packet currently in packets_.
RTC_LOG(LS_ERROR) << "Failed to write packet to queue.";
}
SignalEvent(this, rtc::SE_READ, 0);
return ret;
}
rtc::StreamState StreamInterfaceChannel::GetState() const {
RTC_DCHECK_RUN_ON(&sequence_checker_);
return state_;
}
void StreamInterfaceChannel::Close() {
RTC_DCHECK_RUN_ON(&sequence_checker_);
packets_.Clear();
state_ = rtc::SS_CLOSED;
}
DtlsTransport::DtlsTransport(IceTransportInternal* ice_transport,
const webrtc::CryptoOptions& crypto_options,
webrtc::RtcEventLog* event_log,
rtc::SSLProtocolVersion max_version)
: component_(ice_transport->component()),
ice_transport_(ice_transport),
downward_(NULL),
srtp_ciphers_(crypto_options.GetSupportedDtlsSrtpCryptoSuites()),
ssl_max_version_(max_version),
event_log_(event_log) {
RTC_DCHECK(ice_transport_);
ConnectToIceTransport();
}
DtlsTransport::~DtlsTransport() = default;
webrtc::DtlsTransportState DtlsTransport::dtls_state() const {
return dtls_state_;
}
const std::string& DtlsTransport::transport_name() const {
return ice_transport_->transport_name();
}
int DtlsTransport::component() const {
return component_;
}
bool DtlsTransport::IsDtlsActive() const {
return dtls_active_;
}
bool DtlsTransport::SetLocalCertificate(
const rtc::scoped_refptr<rtc::RTCCertificate>& certificate) {
if (dtls_active_) {
if (certificate == local_certificate_) {
// This may happen during renegotiation.
RTC_LOG(LS_INFO) << ToString() << ": Ignoring identical DTLS identity";
return true;
} else {
RTC_LOG(LS_ERROR) << ToString()
<< ": Can't change DTLS local identity in this state";
return false;
}
}
if (certificate) {
local_certificate_ = certificate;
dtls_active_ = true;
} else {
RTC_LOG(LS_INFO) << ToString()
<< ": NULL DTLS identity supplied. Not doing DTLS";
}
return true;
}
rtc::scoped_refptr<rtc::RTCCertificate> DtlsTransport::GetLocalCertificate()
const {
return local_certificate_;
}
bool DtlsTransport::SetDtlsRole(rtc::SSLRole role) {
if (dtls_) {
RTC_DCHECK(dtls_role_);
if (*dtls_role_ != role) {
RTC_LOG(LS_ERROR)
<< "SSL Role can't be reversed after the session is setup.";
return false;
}
return true;
}
dtls_role_ = role;
return true;
}
bool DtlsTransport::GetDtlsRole(rtc::SSLRole* role) const {
if (!dtls_role_) {
return false;
}
*role = *dtls_role_;
return true;
}
bool DtlsTransport::GetSslCipherSuite(int* cipher) {
if (dtls_state() != webrtc::DtlsTransportState::kConnected) {
return false;
}
return dtls_->GetSslCipherSuite(cipher);
}
webrtc::RTCError DtlsTransport::SetRemoteParameters(
absl::string_view digest_alg,
const uint8_t* digest,
size_t digest_len,
absl::optional<rtc::SSLRole> role) {
rtc::Buffer remote_fingerprint_value(digest, digest_len);
bool is_dtls_restart =
dtls_active_ && remote_fingerprint_value_ != remote_fingerprint_value;
// Set SSL role. Role must be set before fingerprint is applied, which
// initiates DTLS setup.
if (role) {
if (is_dtls_restart) {
dtls_role_ = *role;
} else {
if (!SetDtlsRole(*role)) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Failed to set SSL role for the transport.");
}
}
}
// Apply remote fingerprint.
if (!SetRemoteFingerprint(digest_alg, digest, digest_len)) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Failed to apply remote fingerprint.");
}
return webrtc::RTCError::OK();
}
bool DtlsTransport::SetRemoteFingerprint(absl::string_view digest_alg,
const uint8_t* digest,
size_t digest_len) {
rtc::Buffer remote_fingerprint_value(digest, digest_len);
// Once we have the local certificate, the same remote fingerprint can be set
// multiple times.
if (dtls_active_ && remote_fingerprint_value_ == remote_fingerprint_value &&
!digest_alg.empty()) {
// This may happen during renegotiation.
RTC_LOG(LS_INFO) << ToString()
<< ": Ignoring identical remote DTLS fingerprint";
return true;
}
// If the other side doesn't support DTLS, turn off `dtls_active_`.
// TODO(deadbeef): Remove this. It's dangerous, because it relies on higher
// level code to ensure DTLS is actually used, but there are tests that
// depend on it, for the case where an m= section is rejected. In that case
// SetRemoteFingerprint shouldn't even be called though.
if (digest_alg.empty()) {
RTC_DCHECK(!digest_len);
RTC_LOG(LS_INFO) << ToString() << ": Other side didn't support DTLS.";
dtls_active_ = false;
return true;
}
// Otherwise, we must have a local certificate before setting remote
// fingerprint.
if (!dtls_active_) {
RTC_LOG(LS_ERROR) << ToString()
<< ": Can't set DTLS remote settings in this state.";
return false;
}
// At this point we know we are doing DTLS
bool fingerprint_changing = remote_fingerprint_value_.size() > 0u;
remote_fingerprint_value_ = std::move(remote_fingerprint_value);
remote_fingerprint_algorithm_ = std::string(digest_alg);
if (dtls_ && !fingerprint_changing) {
// This can occur if DTLS is set up before a remote fingerprint is
// received. For instance, if we set up DTLS due to receiving an early
// ClientHello.
rtc::SSLPeerCertificateDigestError err;
if (!dtls_->SetPeerCertificateDigest(
remote_fingerprint_algorithm_,
reinterpret_cast<unsigned char*>(remote_fingerprint_value_.data()),
remote_fingerprint_value_.size(), &err)) {
RTC_LOG(LS_ERROR) << ToString()
<< ": Couldn't set DTLS certificate digest.";
set_dtls_state(webrtc::DtlsTransportState::kFailed);
// If the error is "verification failed", don't return false, because
// this means the fingerprint was formatted correctly but didn't match
// the certificate from the DTLS handshake. Thus the DTLS state should go
// to "failed", but SetRemoteDescription shouldn't fail.
return err == rtc::SSLPeerCertificateDigestError::VERIFICATION_FAILED;
}
return true;
}
// If the fingerprint is changing, we'll tear down the DTLS association and
// create a new one, resetting our state.
if (dtls_ && fingerprint_changing) {
dtls_.reset(nullptr);
set_dtls_state(webrtc::DtlsTransportState::kNew);
set_writable(false);
}
if (!SetupDtls()) {
set_dtls_state(webrtc::DtlsTransportState::kFailed);
return false;
}
return true;
}
std::unique_ptr<rtc::SSLCertChain> DtlsTransport::GetRemoteSSLCertChain()
const {
if (!dtls_) {
return nullptr;
}
return dtls_->GetPeerSSLCertChain();
}
bool DtlsTransport::ExportKeyingMaterial(absl::string_view label,
const uint8_t* context,
size_t context_len,
bool use_context,
uint8_t* result,
size_t result_len) {
return (dtls_.get())
? dtls_->ExportKeyingMaterial(label, context, context_len,
use_context, result, result_len)
: false;
}
bool DtlsTransport::SetupDtls() {
RTC_DCHECK(dtls_role_);
{
auto downward = std::make_unique<StreamInterfaceChannel>(ice_transport_);
StreamInterfaceChannel* downward_ptr = downward.get();
dtls_ = rtc::SSLStreamAdapter::Create(
std::move(downward),
[this](rtc::SSLHandshakeError error) { OnDtlsHandshakeError(error); });
if (!dtls_) {
RTC_LOG(LS_ERROR) << ToString() << ": Failed to create DTLS adapter.";
return false;
}
downward_ = downward_ptr;
}
dtls_->SetIdentity(local_certificate_->identity()->Clone());
dtls_->SetMode(rtc::SSL_MODE_DTLS);
dtls_->SetMaxProtocolVersion(ssl_max_version_);
dtls_->SetServerRole(*dtls_role_);
dtls_->SignalEvent.connect(this, &DtlsTransport::OnDtlsEvent);
if (remote_fingerprint_value_.size() &&
!dtls_->SetPeerCertificateDigest(
remote_fingerprint_algorithm_,
reinterpret_cast<unsigned char*>(remote_fingerprint_value_.data()),
remote_fingerprint_value_.size())) {
RTC_LOG(LS_ERROR) << ToString()
<< ": Couldn't set DTLS certificate digest.";
return false;
}
// Set up DTLS-SRTP, if it's been enabled.
if (!srtp_ciphers_.empty()) {
if (!dtls_->SetDtlsSrtpCryptoSuites(srtp_ciphers_)) {
RTC_LOG(LS_ERROR) << ToString() << ": Couldn't set DTLS-SRTP ciphers.";
return false;
}
} else {
RTC_LOG(LS_INFO) << ToString() << ": Not using DTLS-SRTP.";
}
RTC_LOG(LS_INFO) << ToString() << ": DTLS setup complete.";
// If the underlying ice_transport is already writable at this point, we may
// be able to start DTLS right away.
MaybeStartDtls();
return true;
}
bool DtlsTransport::GetSrtpCryptoSuite(int* cipher) {
if (dtls_state() != webrtc::DtlsTransportState::kConnected) {
return false;
}
return dtls_->GetDtlsSrtpCryptoSuite(cipher);
}
bool DtlsTransport::GetSslVersionBytes(int* version) const {
if (dtls_state() != webrtc::DtlsTransportState::kConnected) {
return false;
}
return dtls_->GetSslVersionBytes(version);
}
uint16_t DtlsTransport::GetSslPeerSignatureAlgorithm() const {
if (dtls_state() != webrtc::DtlsTransportState::kConnected) {
return rtc::kSslSignatureAlgorithmUnknown; // "not applicable"
}
return dtls_->GetPeerSignatureAlgorithm();
}
// Called from upper layers to send a media packet.
int DtlsTransport::SendPacket(const char* data,
size_t size,
const rtc::PacketOptions& options,
int flags) {
if (!dtls_active_) {
// Not doing DTLS.
return ice_transport_->SendPacket(data, size, options);
}
switch (dtls_state()) {
case webrtc::DtlsTransportState::kNew:
// Can't send data until the connection is active.
// TODO(ekr@rtfm.com): assert here if dtls_ is NULL?
return -1;
case webrtc::DtlsTransportState::kConnecting:
// Can't send data until the connection is active.
return -1;
case webrtc::DtlsTransportState::kConnected:
if (flags & PF_SRTP_BYPASS) {
RTC_DCHECK(!srtp_ciphers_.empty());
if (!IsRtpPacket(data, size)) {
return -1;
}
return ice_transport_->SendPacket(data, size, options);
} else {
size_t written;
int error;
return (dtls_->WriteAll(
rtc::MakeArrayView(reinterpret_cast<const uint8_t*>(data),
size),
written, error) == rtc::SR_SUCCESS)
? static_cast<int>(size)
: -1;
}
case webrtc::DtlsTransportState::kFailed:
// Can't send anything when we're failed.
RTC_LOG(LS_ERROR) << ToString()
<< ": Couldn't send packet due to "
"webrtc::DtlsTransportState::kFailed.";
return -1;
case webrtc::DtlsTransportState::kClosed:
// Can't send anything when we're closed.
RTC_LOG(LS_ERROR) << ToString()
<< ": Couldn't send packet due to "
"webrtc::DtlsTransportState::kClosed.";
return -1;
default:
RTC_DCHECK_NOTREACHED();
return -1;
}
}
IceTransportInternal* DtlsTransport::ice_transport() {
return ice_transport_;
}
bool DtlsTransport::IsDtlsConnected() {
return dtls_ && dtls_->IsTlsConnected();
}
bool DtlsTransport::receiving() const {
return receiving_;
}
bool DtlsTransport::writable() const {
return writable_;
}
int DtlsTransport::GetError() {
return ice_transport_->GetError();
}
absl::optional<rtc::NetworkRoute> DtlsTransport::network_route() const {
return ice_transport_->network_route();
}
bool DtlsTransport::GetOption(rtc::Socket::Option opt, int* value) {
return ice_transport_->GetOption(opt, value);
}
int DtlsTransport::SetOption(rtc::Socket::Option opt, int value) {
return ice_transport_->SetOption(opt, value);
}
void DtlsTransport::ConnectToIceTransport() {
RTC_DCHECK(ice_transport_);
ice_transport_->SignalWritableState.connect(this,
&DtlsTransport::OnWritableState);
ice_transport_->SignalReadPacket.connect(this, &DtlsTransport::OnReadPacket);
ice_transport_->SignalSentPacket.connect(this, &DtlsTransport::OnSentPacket);
ice_transport_->SignalReadyToSend.connect(this,
&DtlsTransport::OnReadyToSend);
ice_transport_->SignalReceivingState.connect(
this, &DtlsTransport::OnReceivingState);
ice_transport_->SignalNetworkRouteChanged.connect(
this, &DtlsTransport::OnNetworkRouteChanged);
}
// The state transition logic here is as follows:
// (1) If we're not doing DTLS-SRTP, then the state is just the
// state of the underlying impl()
// (2) If we're doing DTLS-SRTP:
// - Prior to the DTLS handshake, the state is neither receiving nor
// writable
// - When the impl goes writable for the first time we
// start the DTLS handshake
// - Once the DTLS handshake completes, the state is that of the
// impl again
void DtlsTransport::OnWritableState(rtc::PacketTransportInternal* transport) {
RTC_DCHECK_RUN_ON(&thread_checker_);
RTC_DCHECK(transport == ice_transport_);
RTC_LOG(LS_VERBOSE) << ToString()
<< ": ice_transport writable state changed to "
<< ice_transport_->writable();
if (!dtls_active_) {
// Not doing DTLS.
// Note: SignalWritableState fired by set_writable.
set_writable(ice_transport_->writable());
return;
}
switch (dtls_state()) {
case webrtc::DtlsTransportState::kNew:
MaybeStartDtls();
break;
case webrtc::DtlsTransportState::kConnected:
// Note: SignalWritableState fired by set_writable.
set_writable(ice_transport_->writable());
break;
case webrtc::DtlsTransportState::kConnecting:
// Do nothing.
break;
case webrtc::DtlsTransportState::kFailed:
// Should not happen. Do nothing.
RTC_LOG(LS_ERROR) << ToString()
<< ": OnWritableState() called in state "
"webrtc::DtlsTransportState::kFailed.";
break;
case webrtc::DtlsTransportState::kClosed:
// Should not happen. Do nothing.
RTC_LOG(LS_ERROR) << ToString()
<< ": OnWritableState() called in state "
"webrtc::DtlsTransportState::kClosed.";
break;
case webrtc::DtlsTransportState::kNumValues:
RTC_DCHECK_NOTREACHED();
break;
}
}
void DtlsTransport::OnReceivingState(rtc::PacketTransportInternal* transport) {
RTC_DCHECK_RUN_ON(&thread_checker_);
RTC_DCHECK(transport == ice_transport_);
RTC_LOG(LS_VERBOSE) << ToString()
<< ": ice_transport "
"receiving state changed to "
<< ice_transport_->receiving();
if (!dtls_active_ || dtls_state() == webrtc::DtlsTransportState::kConnected) {
// Note: SignalReceivingState fired by set_receiving.
set_receiving(ice_transport_->receiving());
}
}
void DtlsTransport::OnReadPacket(rtc::PacketTransportInternal* transport,
const char* data,
size_t size,
const int64_t& packet_time_us,
int flags) {
RTC_DCHECK_RUN_ON(&thread_checker_);
RTC_DCHECK(transport == ice_transport_);
RTC_DCHECK(flags == 0);
if (!dtls_active_) {
// Not doing DTLS.
SignalReadPacket(this, data, size, packet_time_us, 0);
return;
}
switch (dtls_state()) {
case webrtc::DtlsTransportState::kNew:
if (dtls_) {
RTC_LOG(LS_INFO) << ToString()
<< ": Packet received before DTLS started.";
} else {
RTC_LOG(LS_WARNING) << ToString()
<< ": Packet received before we know if we are "
"doing DTLS or not.";
}
// Cache a client hello packet received before DTLS has actually started.
if (IsDtlsClientHelloPacket(data, size)) {
RTC_LOG(LS_INFO) << ToString()
<< ": Caching DTLS ClientHello packet until DTLS is "
"started.";
cached_client_hello_.SetData(data, size);
// If we haven't started setting up DTLS yet (because we don't have a
// remote fingerprint/role), we can use the client hello as a clue that
// the peer has chosen the client role, and proceed with the handshake.
// The fingerprint will be verified when it's set.
if (!dtls_ && local_certificate_) {
SetDtlsRole(rtc::SSL_SERVER);
SetupDtls();
}
} else {
RTC_LOG(LS_INFO) << ToString()
<< ": Not a DTLS ClientHello packet; dropping.";
}
break;
case webrtc::DtlsTransportState::kConnecting:
case webrtc::DtlsTransportState::kConnected:
// We should only get DTLS or SRTP packets; STUN's already been demuxed.
// Is this potentially a DTLS packet?
if (IsDtlsPacket(data, size)) {
if (!HandleDtlsPacket(data, size)) {
RTC_LOG(LS_ERROR) << ToString() << ": Failed to handle DTLS packet.";
return;
}
} else {
// Not a DTLS packet; our handshake should be complete by now.
if (dtls_state() != webrtc::DtlsTransportState::kConnected) {
RTC_LOG(LS_ERROR) << ToString()
<< ": Received non-DTLS packet before DTLS "
"complete.";
return;
}
// And it had better be a SRTP packet.
if (!IsRtpPacket(data, size)) {
RTC_LOG(LS_ERROR)
<< ToString() << ": Received unexpected non-DTLS packet.";
return;
}
// Sanity check.
RTC_DCHECK(!srtp_ciphers_.empty());
// Signal this upwards as a bypass packet.
SignalReadPacket(this, data, size, packet_time_us, PF_SRTP_BYPASS);
}
break;
case webrtc::DtlsTransportState::kFailed:
case webrtc::DtlsTransportState::kClosed:
case webrtc::DtlsTransportState::kNumValues:
// This shouldn't be happening. Drop the packet.
break;
}
}
void DtlsTransport::OnSentPacket(rtc::PacketTransportInternal* transport,
const rtc::SentPacket& sent_packet) {
RTC_DCHECK_RUN_ON(&thread_checker_);
SignalSentPacket(this, sent_packet);
}
void DtlsTransport::OnReadyToSend(rtc::PacketTransportInternal* transport) {
RTC_DCHECK_RUN_ON(&thread_checker_);
if (writable()) {
SignalReadyToSend(this);
}
}
void DtlsTransport::OnDtlsEvent(rtc::StreamInterface* dtls, int sig, int err) {
RTC_DCHECK_RUN_ON(&thread_checker_);
RTC_DCHECK(dtls == dtls_.get());
if (sig & rtc::SE_OPEN) {
// This is the first time.
RTC_LOG(LS_INFO) << ToString() << ": DTLS handshake complete.";
if (dtls_->GetState() == rtc::SS_OPEN) {
// The check for OPEN shouldn't be necessary but let's make
// sure we don't accidentally frob the state if it's closed.
set_dtls_state(webrtc::DtlsTransportState::kConnected);
set_writable(true);
}
}
if (sig & rtc::SE_READ) {
uint8_t buf[kMaxDtlsPacketLen];
size_t read;
int read_error;
rtc::StreamResult ret;
// The underlying DTLS stream may have received multiple DTLS records in
// one packet, so read all of them.
do {
ret = dtls_->Read(buf, read, read_error);
if (ret == rtc::SR_SUCCESS) {
SignalReadPacket(this, reinterpret_cast<const char*>(buf), read,
rtc::TimeMicros(), 0);
} else if (ret == rtc::SR_EOS) {
// Remote peer shut down the association with no error.
RTC_LOG(LS_INFO) << ToString() << ": DTLS transport closed by remote";
set_writable(false);
set_dtls_state(webrtc::DtlsTransportState::kClosed);
SignalClosed(this);
} else if (ret == rtc::SR_ERROR) {
// Remote peer shut down the association with an error.
RTC_LOG(LS_INFO)
<< ToString()
<< ": Closed by remote with DTLS transport error, code="
<< read_error;
set_writable(false);
set_dtls_state(webrtc::DtlsTransportState::kFailed);
SignalClosed(this);
}
} while (ret == rtc::SR_SUCCESS);
}
if (sig & rtc::SE_CLOSE) {
RTC_DCHECK(sig == rtc::SE_CLOSE); // SE_CLOSE should be by itself.
set_writable(false);
if (!err) {
RTC_LOG(LS_INFO) << ToString() << ": DTLS transport closed";
set_dtls_state(webrtc::DtlsTransportState::kClosed);
} else {
RTC_LOG(LS_INFO) << ToString() << ": DTLS transport error, code=" << err;
set_dtls_state(webrtc::DtlsTransportState::kFailed);
}
}
}
void DtlsTransport::OnNetworkRouteChanged(
absl::optional<rtc::NetworkRoute> network_route) {
RTC_DCHECK_RUN_ON(&thread_checker_);
SignalNetworkRouteChanged(network_route);
}
void DtlsTransport::MaybeStartDtls() {
if (dtls_ && ice_transport_->writable()) {
ConfigureHandshakeTimeout();
if (dtls_->StartSSL()) {
// This should never fail:
// Because we are operating in a nonblocking mode and all
// incoming packets come in via OnReadPacket(), which rejects
// packets in this state, the incoming queue must be empty. We
// ignore write errors, thus any errors must be because of
// configuration and therefore are our fault.
RTC_DCHECK_NOTREACHED() << "StartSSL failed.";
RTC_LOG(LS_ERROR) << ToString() << ": Couldn't start DTLS handshake";
set_dtls_state(webrtc::DtlsTransportState::kFailed);
return;
}
RTC_LOG(LS_INFO) << ToString()
<< ": DtlsTransport: Started DTLS handshake active="
<< IsDtlsActive();
set_dtls_state(webrtc::DtlsTransportState::kConnecting);
// Now that the handshake has started, we can process a cached ClientHello
// (if one exists).
if (cached_client_hello_.size()) {
if (*dtls_role_ == rtc::SSL_SERVER) {
RTC_LOG(LS_INFO) << ToString()
<< ": Handling cached DTLS ClientHello packet.";
if (!HandleDtlsPacket(cached_client_hello_.data<char>(),
cached_client_hello_.size())) {
RTC_LOG(LS_ERROR) << ToString() << ": Failed to handle DTLS packet.";
}
} else {
RTC_LOG(LS_WARNING) << ToString()
<< ": Discarding cached DTLS ClientHello packet "
"because we don't have the server role.";
}
cached_client_hello_.Clear();
}
}
}
// Called from OnReadPacket when a DTLS packet is received.
bool DtlsTransport::HandleDtlsPacket(const char* data, size_t size) {
// Sanity check we're not passing junk that
// just looks like DTLS.
const uint8_t* tmp_data = reinterpret_cast<const uint8_t*>(data);
size_t tmp_size = size;
while (tmp_size > 0) {
if (tmp_size < kDtlsRecordHeaderLen)
return false; // Too short for the header
size_t record_len = (tmp_data[11] << 8) | (tmp_data[12]);
if ((record_len + kDtlsRecordHeaderLen) > tmp_size)
return false; // Body too short
tmp_data += record_len + kDtlsRecordHeaderLen;
tmp_size -= record_len + kDtlsRecordHeaderLen;
}
// Looks good. Pass to the SIC which ends up being passed to
// the DTLS stack.
return downward_->OnPacketReceived(data, size);
}
void DtlsTransport::set_receiving(bool receiving) {
if (receiving_ == receiving) {
return;
}
receiving_ = receiving;
SignalReceivingState(this);
}
void DtlsTransport::set_writable(bool writable) {
if (writable_ == writable) {
return;
}
if (event_log_) {
event_log_->Log(
std::make_unique<webrtc::RtcEventDtlsWritableState>(writable));
}
RTC_LOG(LS_VERBOSE) << ToString() << ": set_writable to: " << writable;
writable_ = writable;
if (writable_) {
SignalReadyToSend(this);
}
SignalWritableState(this);
}
void DtlsTransport::set_dtls_state(webrtc::DtlsTransportState state) {
if (dtls_state_ == state) {
return;
}
if (event_log_) {
event_log_->Log(
std::make_unique<webrtc::RtcEventDtlsTransportState>(state));
}
RTC_LOG(LS_VERBOSE) << ToString() << ": set_dtls_state from:"
<< static_cast<int>(dtls_state_) << " to "
<< static_cast<int>(state);
dtls_state_ = state;
SendDtlsState(this, state);
}
void DtlsTransport::OnDtlsHandshakeError(rtc::SSLHandshakeError error) {
SendDtlsHandshakeError(error);
}
void DtlsTransport::ConfigureHandshakeTimeout() {
RTC_DCHECK(dtls_);
absl::optional<int> rtt = ice_transport_->GetRttEstimate();
if (rtt) {
// Limit the timeout to a reasonable range in case the ICE RTT takes
// extreme values.
int initial_timeout = std::max(kMinHandshakeTimeout,
std::min(kMaxHandshakeTimeout, 2 * (*rtt)));
RTC_LOG(LS_INFO) << ToString() << ": configuring DTLS handshake timeout "
<< initial_timeout << " based on ICE RTT " << *rtt;
dtls_->SetInitialRetransmissionTimeout(initial_timeout);
} else {
RTC_LOG(LS_INFO)
<< ToString()
<< ": no RTT estimate - using default DTLS handshake timeout";
}
}
} // namespace cricket

View file

@ -0,0 +1,270 @@
/*
* Copyright 2011 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.
*/
#ifndef P2P_BASE_DTLS_TRANSPORT_H_
#define P2P_BASE_DTLS_TRANSPORT_H_
#include <memory>
#include <string>
#include <vector>
#include "absl/strings/string_view.h"
#include "api/crypto/crypto_options.h"
#include "api/dtls_transport_interface.h"
#include "api/sequence_checker.h"
#include "p2p/base/dtls_transport_internal.h"
#include "p2p/base/ice_transport_internal.h"
#include "rtc_base/buffer.h"
#include "rtc_base/buffer_queue.h"
#include "rtc_base/ssl_stream_adapter.h"
#include "rtc_base/stream.h"
#include "rtc_base/strings/string_builder.h"
#include "rtc_base/system/no_unique_address.h"
namespace rtc {
class PacketTransportInternal;
}
namespace cricket {
// A bridge between a packet-oriented/transport-type interface on
// the bottom and a StreamInterface on the top.
class StreamInterfaceChannel : public rtc::StreamInterface {
public:
explicit StreamInterfaceChannel(IceTransportInternal* ice_transport);
StreamInterfaceChannel(const StreamInterfaceChannel&) = delete;
StreamInterfaceChannel& operator=(const StreamInterfaceChannel&) = delete;
// Push in a packet; this gets pulled out from Read().
bool OnPacketReceived(const char* data, size_t size);
// Implementations of StreamInterface
rtc::StreamState GetState() const override;
void Close() override;
rtc::StreamResult Read(rtc::ArrayView<uint8_t> buffer,
size_t& read,
int& error) override;
rtc::StreamResult Write(rtc::ArrayView<const uint8_t> data,
size_t& written,
int& error) override;
private:
RTC_NO_UNIQUE_ADDRESS webrtc::SequenceChecker sequence_checker_;
IceTransportInternal* const ice_transport_; // owned by DtlsTransport
rtc::StreamState state_ RTC_GUARDED_BY(sequence_checker_);
rtc::BufferQueue packets_ RTC_GUARDED_BY(sequence_checker_);
};
// This class provides a DTLS SSLStreamAdapter inside a TransportChannel-style
// packet-based interface, wrapping an existing TransportChannel instance
// (e.g a P2PTransportChannel)
// Here's the way this works:
//
// DtlsTransport {
// SSLStreamAdapter* dtls_ {
// StreamInterfaceChannel downward_ {
// IceTransportInternal* ice_transport_;
// }
// }
// }
//
// - Data which comes into DtlsTransport from the underlying
// ice_transport_ via OnReadPacket() is checked for whether it is DTLS
// or not, and if it is, is passed to DtlsTransport::HandleDtlsPacket,
// which pushes it into to downward_. dtls_ is listening for events on
// downward_, so it immediately calls downward_->Read().
//
// - Data written to DtlsTransport is passed either to downward_ or directly
// to ice_transport_, depending on whether DTLS is negotiated and whether
// the flags include PF_SRTP_BYPASS
//
// - The SSLStreamAdapter writes to downward_->Write() which translates it
// into packet writes on ice_transport_.
//
// This class is not thread safe; all methods must be called on the same thread
// as the constructor.
class DtlsTransport : public DtlsTransportInternal {
public:
// `ice_transport` is the ICE transport this DTLS transport is wrapping. It
// must outlive this DTLS transport.
//
// `crypto_options` are the options used for the DTLS handshake. This affects
// whether GCM crypto suites are negotiated.
//
// `event_log` is an optional RtcEventLog for logging state changes. It should
// outlive the DtlsTransport.
DtlsTransport(
IceTransportInternal* ice_transport,
const webrtc::CryptoOptions& crypto_options,
webrtc::RtcEventLog* event_log,
rtc::SSLProtocolVersion max_version = rtc::SSL_PROTOCOL_DTLS_12);
~DtlsTransport() override;
DtlsTransport(const DtlsTransport&) = delete;
DtlsTransport& operator=(const DtlsTransport&) = delete;
webrtc::DtlsTransportState dtls_state() const override;
const std::string& transport_name() const override;
int component() const override;
// DTLS is active if a local certificate was set. Otherwise this acts in a
// "passthrough" mode, sending packets directly through the underlying ICE
// transport.
// TODO(deadbeef): Remove this weirdness, and handle it in the upper layers.
bool IsDtlsActive() const override;
// SetLocalCertificate is what makes DTLS active. It must be called before
// SetRemoteFinterprint.
// TODO(deadbeef): Once DtlsTransport no longer has the concept of being
// "active" or not (acting as a passthrough if not active), just require this
// certificate on construction or "Start".
bool SetLocalCertificate(
const rtc::scoped_refptr<rtc::RTCCertificate>& certificate) override;
rtc::scoped_refptr<rtc::RTCCertificate> GetLocalCertificate() const override;
// SetRemoteFingerprint must be called after SetLocalCertificate, and any
// other methods like SetDtlsRole. It's what triggers the actual DTLS setup.
// TODO(deadbeef): Rename to "Start" like in ORTC?
bool SetRemoteFingerprint(absl::string_view digest_alg,
const uint8_t* digest,
size_t digest_len) override;
// SetRemoteParameters must be called after SetLocalCertificate.
webrtc::RTCError SetRemoteParameters(
absl::string_view digest_alg,
const uint8_t* digest,
size_t digest_len,
absl::optional<rtc::SSLRole> role) override;
// Called to send a packet (via DTLS, if turned on).
int SendPacket(const char* data,
size_t size,
const rtc::PacketOptions& options,
int flags) override;
bool GetOption(rtc::Socket::Option opt, int* value) override;
// Find out which TLS version was negotiated
bool GetSslVersionBytes(int* version) const override;
// Find out which DTLS-SRTP cipher was negotiated
bool GetSrtpCryptoSuite(int* cipher) override;
// Find out which signature algorithm was used by the peer. Returns values
// from
// https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-signaturescheme
// If not applicable, it returns zero.
uint16_t GetSslPeerSignatureAlgorithm() const override;
bool GetDtlsRole(rtc::SSLRole* role) const override;
bool SetDtlsRole(rtc::SSLRole role) override;
// Find out which DTLS cipher was negotiated
bool GetSslCipherSuite(int* cipher) override;
// Once DTLS has been established, this method retrieves the certificate
// chain in use by the remote peer, for use in external identity
// verification.
std::unique_ptr<rtc::SSLCertChain> GetRemoteSSLCertChain() const override;
// Once DTLS has established (i.e., this ice_transport is writable), this
// method extracts the keys negotiated during the DTLS handshake, for use in
// external encryption. DTLS-SRTP uses this to extract the needed SRTP keys.
// See the SSLStreamAdapter documentation for info on the specific parameters.
bool ExportKeyingMaterial(absl::string_view label,
const uint8_t* context,
size_t context_len,
bool use_context,
uint8_t* result,
size_t result_len) override;
IceTransportInternal* ice_transport() override;
// For informational purposes. Tells if the DTLS handshake has finished.
// This may be true even if writable() is false, if the remote fingerprint
// has not yet been verified.
bool IsDtlsConnected();
bool receiving() const override;
bool writable() const override;
int GetError() override;
absl::optional<rtc::NetworkRoute> network_route() const override;
int SetOption(rtc::Socket::Option opt, int value) override;
std::string ToString() const {
const absl::string_view RECEIVING_ABBREV[2] = {"_", "R"};
const absl::string_view WRITABLE_ABBREV[2] = {"_", "W"};
rtc::StringBuilder sb;
sb << "DtlsTransport[" << transport_name() << "|" << component_ << "|"
<< RECEIVING_ABBREV[receiving()] << WRITABLE_ABBREV[writable()] << "]";
return sb.Release();
}
private:
void ConnectToIceTransport();
void OnWritableState(rtc::PacketTransportInternal* transport);
void OnReadPacket(rtc::PacketTransportInternal* transport,
const char* data,
size_t size,
const int64_t& packet_time_us,
int flags);
void OnSentPacket(rtc::PacketTransportInternal* transport,
const rtc::SentPacket& sent_packet);
void OnReadyToSend(rtc::PacketTransportInternal* transport);
void OnReceivingState(rtc::PacketTransportInternal* transport);
void OnDtlsEvent(rtc::StreamInterface* stream_, int sig, int err);
void OnNetworkRouteChanged(absl::optional<rtc::NetworkRoute> network_route);
bool SetupDtls();
void MaybeStartDtls();
bool HandleDtlsPacket(const char* data, size_t size);
void OnDtlsHandshakeError(rtc::SSLHandshakeError error);
void ConfigureHandshakeTimeout();
void set_receiving(bool receiving);
void set_writable(bool writable);
// Sets the DTLS state, signaling if necessary.
void set_dtls_state(webrtc::DtlsTransportState state);
webrtc::SequenceChecker thread_checker_;
const int component_;
webrtc::DtlsTransportState dtls_state_ = webrtc::DtlsTransportState::kNew;
// Underlying ice_transport, not owned by this class.
IceTransportInternal* const ice_transport_;
std::unique_ptr<rtc::SSLStreamAdapter> dtls_; // The DTLS stream
StreamInterfaceChannel*
downward_; // Wrapper for ice_transport_, owned by dtls_.
const std::vector<int> srtp_ciphers_; // SRTP ciphers to use with DTLS.
bool dtls_active_ = false;
rtc::scoped_refptr<rtc::RTCCertificate> local_certificate_;
absl::optional<rtc::SSLRole> dtls_role_;
const rtc::SSLProtocolVersion ssl_max_version_;
rtc::Buffer remote_fingerprint_value_;
std::string remote_fingerprint_algorithm_;
// Cached DTLS ClientHello packet that was received before we started the
// DTLS handshake. This could happen if the hello was received before the
// ice transport became writable, or before a remote fingerprint was received.
rtc::Buffer cached_client_hello_;
bool receiving_ = false;
bool writable_ = false;
webrtc::RtcEventLog* const event_log_;
};
} // namespace cricket
#endif // P2P_BASE_DTLS_TRANSPORT_H_

View file

@ -0,0 +1,40 @@
/*
* Copyright 2018 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.
*/
#ifndef P2P_BASE_DTLS_TRANSPORT_FACTORY_H_
#define P2P_BASE_DTLS_TRANSPORT_FACTORY_H_
#include <memory>
#include <string>
#include "p2p/base/dtls_transport_internal.h"
#include "p2p/base/ice_transport_internal.h"
namespace cricket {
// This interface is used to create DTLS transports. The external transports
// can be injected into the JsepTransportController through it.
//
// TODO(qingsi): Remove this factory in favor of one that produces
// DtlsTransportInterface given by the public API if this is going to be
// injectable.
class DtlsTransportFactory {
public:
virtual ~DtlsTransportFactory() = default;
virtual std::unique_ptr<DtlsTransportInternal> CreateDtlsTransport(
IceTransportInternal* ice,
const webrtc::CryptoOptions& crypto_options,
rtc::SSLProtocolVersion max_version) = 0;
};
} // namespace cricket
#endif // P2P_BASE_DTLS_TRANSPORT_FACTORY_H_

View file

@ -0,0 +1,19 @@
/*
* Copyright 2017 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/dtls_transport_internal.h"
namespace cricket {
DtlsTransportInternal::DtlsTransportInternal() = default;
DtlsTransportInternal::~DtlsTransportInternal() = default;
} // namespace cricket

View file

@ -0,0 +1,163 @@
/*
* Copyright 2016 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.
*/
#ifndef P2P_BASE_DTLS_TRANSPORT_INTERNAL_H_
#define P2P_BASE_DTLS_TRANSPORT_INTERNAL_H_
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <string>
#include <utility>
#include "absl/base/attributes.h"
#include "absl/strings/string_view.h"
#include "api/crypto/crypto_options.h"
#include "api/dtls_transport_interface.h"
#include "api/scoped_refptr.h"
#include "p2p/base/ice_transport_internal.h"
#include "p2p/base/packet_transport_internal.h"
#include "rtc_base/callback_list.h"
#include "rtc_base/ssl_certificate.h"
#include "rtc_base/ssl_fingerprint.h"
#include "rtc_base/ssl_stream_adapter.h"
namespace cricket {
enum PacketFlags {
PF_NORMAL = 0x00, // A normal packet.
PF_SRTP_BYPASS = 0x01, // An encrypted SRTP packet; bypass any additional
// crypto provided by the transport (e.g. DTLS)
};
// DtlsTransportInternal is an internal interface that does DTLS, also
// negotiating SRTP crypto suites so that it may be used for DTLS-SRTP.
//
// Once the public interface is supported,
// (https://www.w3.org/TR/webrtc/#rtcdtlstransport-interface)
// the DtlsTransportInterface will be split from this class.
class DtlsTransportInternal : public rtc::PacketTransportInternal {
public:
~DtlsTransportInternal() override;
DtlsTransportInternal(const DtlsTransportInternal&) = delete;
DtlsTransportInternal& operator=(const DtlsTransportInternal&) = delete;
virtual webrtc::DtlsTransportState dtls_state() const = 0;
virtual int component() const = 0;
virtual bool IsDtlsActive() const = 0;
virtual bool GetDtlsRole(rtc::SSLRole* role) const = 0;
virtual bool SetDtlsRole(rtc::SSLRole role) = 0;
// Finds out which TLS/DTLS version is running.
virtual bool GetSslVersionBytes(int* version) const = 0;
// Finds out which DTLS-SRTP cipher was negotiated.
// TODO(zhihuang): Remove this once all dependencies implement this.
virtual bool GetSrtpCryptoSuite(int* cipher) = 0;
// Finds out which DTLS cipher was negotiated.
// TODO(zhihuang): Remove this once all dependencies implement this.
virtual bool GetSslCipherSuite(int* cipher) = 0;
// Find out which signature algorithm was used by the peer. Returns values
// from
// https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-signaturescheme
// If not applicable, it returns zero.
virtual uint16_t GetSslPeerSignatureAlgorithm() const = 0;
// Gets the local RTCCertificate used for DTLS.
virtual rtc::scoped_refptr<rtc::RTCCertificate> GetLocalCertificate()
const = 0;
virtual bool SetLocalCertificate(
const rtc::scoped_refptr<rtc::RTCCertificate>& certificate) = 0;
// Gets a copy of the remote side's SSL certificate chain.
virtual std::unique_ptr<rtc::SSLCertChain> GetRemoteSSLCertChain() const = 0;
// Allows key material to be extracted for external encryption.
virtual bool ExportKeyingMaterial(absl::string_view label,
const uint8_t* context,
size_t context_len,
bool use_context,
uint8_t* result,
size_t result_len) = 0;
// Set DTLS remote fingerprint. Must be after local identity set.
ABSL_DEPRECATED("Use SetRemoteParameters instead.")
virtual bool SetRemoteFingerprint(absl::string_view digest_alg,
const uint8_t* digest,
size_t digest_len) = 0;
// Set DTLS remote fingerprint and role. Must be after local identity set.
virtual webrtc::RTCError SetRemoteParameters(
absl::string_view digest_alg,
const uint8_t* digest,
size_t digest_len,
absl::optional<rtc::SSLRole> role) = 0;
ABSL_DEPRECATED("Set the max version via construction.")
bool SetSslMaxProtocolVersion(rtc::SSLProtocolVersion version) {
return true;
}
// Expose the underneath IceTransport.
virtual IceTransportInternal* ice_transport() = 0;
// F: void(DtlsTransportInternal*, const webrtc::DtlsTransportState)
template <typename F>
void SubscribeDtlsTransportState(F&& callback) {
dtls_transport_state_callback_list_.AddReceiver(std::forward<F>(callback));
}
template <typename F>
void SubscribeDtlsTransportState(const void* id, F&& callback) {
dtls_transport_state_callback_list_.AddReceiver(id,
std::forward<F>(callback));
}
// Unsubscribe the subscription with given id.
void UnsubscribeDtlsTransportState(const void* id) {
dtls_transport_state_callback_list_.RemoveReceivers(id);
}
void SendDtlsState(DtlsTransportInternal* transport,
webrtc::DtlsTransportState state) {
dtls_transport_state_callback_list_.Send(transport, state);
}
// Emitted whenever the Dtls handshake failed on some transport channel.
// F: void(rtc::SSLHandshakeError)
template <typename F>
void SubscribeDtlsHandshakeError(F&& callback) {
dtls_handshake_error_callback_list_.AddReceiver(std::forward<F>(callback));
}
void SendDtlsHandshakeError(rtc::SSLHandshakeError error) {
dtls_handshake_error_callback_list_.Send(error);
}
protected:
DtlsTransportInternal();
private:
webrtc::CallbackList<const rtc::SSLHandshakeError>
dtls_handshake_error_callback_list_;
webrtc::CallbackList<DtlsTransportInternal*, const webrtc::DtlsTransportState>
dtls_transport_state_callback_list_;
};
} // namespace cricket
#endif // P2P_BASE_DTLS_TRANSPORT_INTERNAL_H_

View file

@ -0,0 +1,319 @@
/*
* Copyright 2017 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.
*/
#ifndef P2P_BASE_FAKE_DTLS_TRANSPORT_H_
#define P2P_BASE_FAKE_DTLS_TRANSPORT_H_
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/string_view.h"
#include "api/crypto/crypto_options.h"
#include "api/dtls_transport_interface.h"
#include "p2p/base/dtls_transport_internal.h"
#include "p2p/base/fake_ice_transport.h"
#include "rtc_base/fake_ssl_identity.h"
#include "rtc_base/rtc_certificate.h"
namespace cricket {
// Fake DTLS transport which is implemented by wrapping a fake ICE transport.
// Doesn't interact directly with fake ICE transport for anything other than
// sending packets.
class FakeDtlsTransport : public DtlsTransportInternal {
public:
explicit FakeDtlsTransport(FakeIceTransport* ice_transport)
: ice_transport_(ice_transport),
transport_name_(ice_transport->transport_name()),
component_(ice_transport->component()),
dtls_fingerprint_("", nullptr) {
RTC_DCHECK(ice_transport_);
ice_transport_->SignalReadPacket.connect(
this, &FakeDtlsTransport::OnIceTransportReadPacket);
ice_transport_->SignalNetworkRouteChanged.connect(
this, &FakeDtlsTransport::OnNetworkRouteChanged);
}
explicit FakeDtlsTransport(std::unique_ptr<FakeIceTransport> ice)
: owned_ice_transport_(std::move(ice)),
transport_name_(owned_ice_transport_->transport_name()),
component_(owned_ice_transport_->component()),
dtls_fingerprint_("", rtc::ArrayView<const uint8_t>()) {
ice_transport_ = owned_ice_transport_.get();
ice_transport_->SignalReadPacket.connect(
this, &FakeDtlsTransport::OnIceTransportReadPacket);
ice_transport_->SignalNetworkRouteChanged.connect(
this, &FakeDtlsTransport::OnNetworkRouteChanged);
}
// If this constructor is called, a new fake ICE transport will be created,
// and this FakeDtlsTransport will take the ownership.
FakeDtlsTransport(const std::string& name, int component)
: FakeDtlsTransport(std::make_unique<FakeIceTransport>(name, component)) {
}
FakeDtlsTransport(const std::string& name,
int component,
rtc::Thread* network_thread)
: FakeDtlsTransport(std::make_unique<FakeIceTransport>(name,
component,
network_thread)) {}
~FakeDtlsTransport() override {
if (dest_ && dest_->dest_ == this) {
dest_->dest_ = nullptr;
}
}
// Get inner fake ICE transport.
FakeIceTransport* fake_ice_transport() { return ice_transport_; }
// If async, will send packets by "Post"-ing to message queue instead of
// synchronously "Send"-ing.
void SetAsync(bool async) { ice_transport_->SetAsync(async); }
void SetAsyncDelay(int delay_ms) { ice_transport_->SetAsyncDelay(delay_ms); }
// SetWritable, SetReceiving and SetDestination are the main methods that can
// be used for testing, to simulate connectivity or lack thereof.
void SetWritable(bool writable) {
ice_transport_->SetWritable(writable);
set_writable(writable);
}
void SetReceiving(bool receiving) {
ice_transport_->SetReceiving(receiving);
set_receiving(receiving);
}
void SetDtlsState(webrtc::DtlsTransportState state) {
dtls_state_ = state;
SendDtlsState(this, dtls_state_);
}
// Simulates the two DTLS transports connecting to each other.
// If `asymmetric` is true this method only affects this FakeDtlsTransport.
// If false, it affects `dest` as well.
void SetDestination(FakeDtlsTransport* dest, bool asymmetric = false) {
if (dest == dest_) {
return;
}
RTC_DCHECK(!dest || !dest_)
<< "Changing fake destination from one to another is not supported.";
if (dest && !dest_) {
// This simulates the DTLS handshake.
dest_ = dest;
if (local_cert_ && dest_->local_cert_) {
do_dtls_ = true;
RTC_LOG(LS_INFO) << "FakeDtlsTransport is doing DTLS";
} else {
do_dtls_ = false;
RTC_LOG(LS_INFO) << "FakeDtlsTransport is not doing DTLS";
}
SetWritable(true);
if (!asymmetric) {
dest->SetDestination(this, true);
}
// If the `dtls_role_` is unset, set it to SSL_CLIENT by default.
if (!dtls_role_) {
dtls_role_ = std::move(rtc::SSL_CLIENT);
}
SetDtlsState(webrtc::DtlsTransportState::kConnected);
ice_transport_->SetDestination(
static_cast<FakeIceTransport*>(dest->ice_transport()), asymmetric);
} else {
// Simulates loss of connectivity, by asymmetrically forgetting dest_.
dest_ = nullptr;
SetWritable(false);
ice_transport_->SetDestination(nullptr, asymmetric);
}
}
// Fake DtlsTransportInternal implementation.
webrtc::DtlsTransportState dtls_state() const override { return dtls_state_; }
const std::string& transport_name() const override { return transport_name_; }
int component() const override { return component_; }
const rtc::SSLFingerprint& dtls_fingerprint() const {
return dtls_fingerprint_;
}
webrtc::RTCError SetRemoteParameters(absl::string_view alg,
const uint8_t* digest,
size_t digest_len,
absl::optional<rtc::SSLRole> role) {
if (role) {
SetDtlsRole(*role);
}
SetRemoteFingerprint(alg, digest, digest_len);
return webrtc::RTCError::OK();
}
bool SetRemoteFingerprint(absl::string_view alg,
const uint8_t* digest,
size_t digest_len) {
dtls_fingerprint_ =
rtc::SSLFingerprint(alg, rtc::MakeArrayView(digest, digest_len));
return true;
}
bool SetDtlsRole(rtc::SSLRole role) override {
dtls_role_ = std::move(role);
return true;
}
bool GetDtlsRole(rtc::SSLRole* role) const override {
if (!dtls_role_) {
return false;
}
*role = *dtls_role_;
return true;
}
bool SetLocalCertificate(
const rtc::scoped_refptr<rtc::RTCCertificate>& certificate) override {
do_dtls_ = true;
local_cert_ = certificate;
return true;
}
void SetRemoteSSLCertificate(rtc::FakeSSLCertificate* cert) {
remote_cert_ = cert;
}
bool IsDtlsActive() const override { return do_dtls_; }
bool GetSslVersionBytes(int* version) const override {
if (!do_dtls_) {
return false;
}
*version = 0x0102;
return true;
}
bool GetSrtpCryptoSuite(int* crypto_suite) override {
if (!do_dtls_) {
return false;
}
*crypto_suite = crypto_suite_;
return true;
}
void SetSrtpCryptoSuite(int crypto_suite) { crypto_suite_ = crypto_suite; }
bool GetSslCipherSuite(int* cipher_suite) override {
if (ssl_cipher_suite_) {
*cipher_suite = *ssl_cipher_suite_;
return true;
}
return false;
}
void SetSslCipherSuite(absl::optional<int> cipher_suite) {
ssl_cipher_suite_ = cipher_suite;
}
uint16_t GetSslPeerSignatureAlgorithm() const override { return 0; }
rtc::scoped_refptr<rtc::RTCCertificate> GetLocalCertificate() const override {
return local_cert_;
}
std::unique_ptr<rtc::SSLCertChain> GetRemoteSSLCertChain() const override {
if (!remote_cert_) {
return nullptr;
}
return std::make_unique<rtc::SSLCertChain>(remote_cert_->Clone());
}
bool ExportKeyingMaterial(absl::string_view label,
const uint8_t* context,
size_t context_len,
bool use_context,
uint8_t* result,
size_t result_len) override {
if (!do_dtls_) {
return false;
}
memset(result, 0xff, result_len);
return true;
}
void set_ssl_max_protocol_version(rtc::SSLProtocolVersion version) {
ssl_max_version_ = version;
}
rtc::SSLProtocolVersion ssl_max_protocol_version() const {
return ssl_max_version_;
}
IceTransportInternal* ice_transport() override { return ice_transport_; }
// PacketTransportInternal implementation, which passes through to fake ICE
// transport for sending actual packets.
bool writable() const override { return writable_; }
bool receiving() const override { return receiving_; }
int SendPacket(const char* data,
size_t len,
const rtc::PacketOptions& options,
int flags) override {
// We expect only SRTP packets to be sent through this interface.
if (flags != PF_SRTP_BYPASS && flags != 0) {
return -1;
}
return ice_transport_->SendPacket(data, len, options, flags);
}
int SetOption(rtc::Socket::Option opt, int value) override {
return ice_transport_->SetOption(opt, value);
}
bool GetOption(rtc::Socket::Option opt, int* value) override {
return ice_transport_->GetOption(opt, value);
}
int GetError() override { return ice_transport_->GetError(); }
absl::optional<rtc::NetworkRoute> network_route() const override {
return ice_transport_->network_route();
}
private:
void OnIceTransportReadPacket(PacketTransportInternal* ice_,
const char* data,
size_t len,
const int64_t& packet_time_us,
int flags) {
SignalReadPacket(this, data, len, packet_time_us, flags);
}
void set_receiving(bool receiving) {
if (receiving_ == receiving) {
return;
}
receiving_ = receiving;
SignalReceivingState(this);
}
void set_writable(bool writable) {
if (writable_ == writable) {
return;
}
writable_ = writable;
if (writable_) {
SignalReadyToSend(this);
}
SignalWritableState(this);
}
void OnNetworkRouteChanged(absl::optional<rtc::NetworkRoute> network_route) {
SignalNetworkRouteChanged(network_route);
}
FakeIceTransport* ice_transport_;
std::unique_ptr<FakeIceTransport> owned_ice_transport_;
std::string transport_name_;
int component_;
FakeDtlsTransport* dest_ = nullptr;
rtc::scoped_refptr<rtc::RTCCertificate> local_cert_;
rtc::FakeSSLCertificate* remote_cert_ = nullptr;
bool do_dtls_ = false;
rtc::SSLProtocolVersion ssl_max_version_ = rtc::SSL_PROTOCOL_DTLS_12;
rtc::SSLFingerprint dtls_fingerprint_;
absl::optional<rtc::SSLRole> dtls_role_;
int crypto_suite_ = rtc::kSrtpAes128CmSha1_80;
absl::optional<int> ssl_cipher_suite_;
webrtc::DtlsTransportState dtls_state_ = webrtc::DtlsTransportState::kNew;
bool receiving_ = false;
bool writable_ = false;
};
} // namespace cricket
#endif // P2P_BASE_FAKE_DTLS_TRANSPORT_H_

View file

@ -0,0 +1,446 @@
/*
* Copyright 2017 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.
*/
#ifndef P2P_BASE_FAKE_ICE_TRANSPORT_H_
#define P2P_BASE_FAKE_ICE_TRANSPORT_H_
#include <map>
#include <memory>
#include <string>
#include <utility>
#include "absl/algorithm/container.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "api/ice_transport_interface.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "api/units/time_delta.h"
#include "p2p/base/ice_transport_internal.h"
#include "rtc_base/copy_on_write_buffer.h"
#include "rtc_base/task_queue_for_test.h"
namespace cricket {
using ::webrtc::SafeTask;
using ::webrtc::TimeDelta;
// All methods must be called on the network thread (which is either the thread
// calling the constructor, or the separate thread explicitly passed to the
// constructor).
class FakeIceTransport : public IceTransportInternal {
public:
explicit FakeIceTransport(absl::string_view name,
int component,
rtc::Thread* network_thread = nullptr)
: name_(name),
component_(component),
network_thread_(network_thread ? network_thread
: rtc::Thread::Current()) {
RTC_DCHECK(network_thread_);
}
// Must be called either on the network thread, or after the network thread
// has been shut down.
~FakeIceTransport() override {
if (dest_ && dest_->dest_ == this) {
dest_->dest_ = nullptr;
}
}
// If async, will send packets by "Post"-ing to message queue instead of
// synchronously "Send"-ing.
void SetAsync(bool async) {
RTC_DCHECK_RUN_ON(network_thread_);
async_ = async;
}
void SetAsyncDelay(int delay_ms) {
RTC_DCHECK_RUN_ON(network_thread_);
async_delay_ms_ = delay_ms;
}
// SetWritable, SetReceiving and SetDestination are the main methods that can
// be used for testing, to simulate connectivity or lack thereof.
void SetWritable(bool writable) {
RTC_DCHECK_RUN_ON(network_thread_);
set_writable(writable);
}
void SetReceiving(bool receiving) {
RTC_DCHECK_RUN_ON(network_thread_);
set_receiving(receiving);
}
// Simulates the two transports connecting to each other.
// If `asymmetric` is true this method only affects this FakeIceTransport.
// If false, it affects `dest` as well.
void SetDestination(FakeIceTransport* dest, bool asymmetric = false) {
RTC_DCHECK_RUN_ON(network_thread_);
if (dest == dest_) {
return;
}
RTC_DCHECK(!dest || !dest_)
<< "Changing fake destination from one to another is not supported.";
if (dest) {
// This simulates the delivery of candidates.
dest_ = dest;
set_writable(true);
if (!asymmetric) {
dest->SetDestination(this, true);
}
} else {
// Simulates loss of connectivity, by asymmetrically forgetting dest_.
dest_ = nullptr;
set_writable(false);
}
}
void SetTransportState(webrtc::IceTransportState state,
IceTransportState legacy_state) {
RTC_DCHECK_RUN_ON(network_thread_);
transport_state_ = state;
legacy_transport_state_ = legacy_state;
SignalIceTransportStateChanged(this);
}
void SetConnectionCount(size_t connection_count) {
RTC_DCHECK_RUN_ON(network_thread_);
size_t old_connection_count = connection_count_;
connection_count_ = connection_count;
if (connection_count) {
had_connection_ = true;
}
// In this fake transport channel, `connection_count_` determines the
// transport state.
if (connection_count_ < old_connection_count) {
SignalStateChanged(this);
}
}
void SetCandidatesGatheringComplete() {
RTC_DCHECK_RUN_ON(network_thread_);
if (gathering_state_ != kIceGatheringComplete) {
gathering_state_ = kIceGatheringComplete;
SendGatheringStateEvent();
}
}
// Convenience functions for accessing ICE config and other things.
int receiving_timeout() const {
RTC_DCHECK_RUN_ON(network_thread_);
return ice_config_.receiving_timeout_or_default();
}
bool gather_continually() const {
RTC_DCHECK_RUN_ON(network_thread_);
return ice_config_.gather_continually();
}
const Candidates& remote_candidates() const {
RTC_DCHECK_RUN_ON(network_thread_);
return remote_candidates_;
}
// Fake IceTransportInternal implementation.
const std::string& transport_name() const override { return name_; }
int component() const override { return component_; }
uint64_t IceTiebreaker() const {
RTC_DCHECK_RUN_ON(network_thread_);
return tiebreaker_;
}
IceMode remote_ice_mode() const {
RTC_DCHECK_RUN_ON(network_thread_);
return remote_ice_mode_;
}
const std::string& ice_ufrag() const { return ice_parameters_.ufrag; }
const std::string& ice_pwd() const { return ice_parameters_.pwd; }
const std::string& remote_ice_ufrag() const {
return remote_ice_parameters_.ufrag;
}
const std::string& remote_ice_pwd() const {
return remote_ice_parameters_.pwd;
}
const IceParameters& ice_parameters() const { return ice_parameters_; }
const IceParameters& remote_ice_parameters() const {
return remote_ice_parameters_;
}
IceTransportState GetState() const override {
RTC_DCHECK_RUN_ON(network_thread_);
if (legacy_transport_state_) {
return *legacy_transport_state_;
}
if (connection_count_ == 0) {
return had_connection_ ? IceTransportState::STATE_FAILED
: IceTransportState::STATE_INIT;
}
if (connection_count_ == 1) {
return IceTransportState::STATE_COMPLETED;
}
return IceTransportState::STATE_CONNECTING;
}
webrtc::IceTransportState GetIceTransportState() const override {
RTC_DCHECK_RUN_ON(network_thread_);
if (transport_state_) {
return *transport_state_;
}
if (connection_count_ == 0) {
return had_connection_ ? webrtc::IceTransportState::kFailed
: webrtc::IceTransportState::kNew;
}
if (connection_count_ == 1) {
return webrtc::IceTransportState::kCompleted;
}
return webrtc::IceTransportState::kConnected;
}
void SetIceRole(IceRole role) override {
RTC_DCHECK_RUN_ON(network_thread_);
role_ = role;
}
IceRole GetIceRole() const override {
RTC_DCHECK_RUN_ON(network_thread_);
return role_;
}
void SetIceTiebreaker(uint64_t tiebreaker) override {
RTC_DCHECK_RUN_ON(network_thread_);
tiebreaker_ = tiebreaker;
}
void SetIceParameters(const IceParameters& ice_params) override {
RTC_DCHECK_RUN_ON(network_thread_);
ice_parameters_ = ice_params;
}
void SetRemoteIceParameters(const IceParameters& params) override {
RTC_DCHECK_RUN_ON(network_thread_);
remote_ice_parameters_ = params;
}
void SetRemoteIceMode(IceMode mode) override {
RTC_DCHECK_RUN_ON(network_thread_);
remote_ice_mode_ = mode;
}
void MaybeStartGathering() override {
RTC_DCHECK_RUN_ON(network_thread_);
if (gathering_state_ == kIceGatheringNew) {
gathering_state_ = kIceGatheringGathering;
SendGatheringStateEvent();
}
}
IceGatheringState gathering_state() const override {
RTC_DCHECK_RUN_ON(network_thread_);
return gathering_state_;
}
void SetIceConfig(const IceConfig& config) override {
RTC_DCHECK_RUN_ON(network_thread_);
ice_config_ = config;
}
void AddRemoteCandidate(const Candidate& candidate) override {
RTC_DCHECK_RUN_ON(network_thread_);
remote_candidates_.push_back(candidate);
}
void RemoveRemoteCandidate(const Candidate& candidate) override {
RTC_DCHECK_RUN_ON(network_thread_);
auto it = absl::c_find(remote_candidates_, candidate);
if (it == remote_candidates_.end()) {
RTC_LOG(LS_INFO) << "Trying to remove a candidate which doesn't exist.";
return;
}
remote_candidates_.erase(it);
}
void RemoveAllRemoteCandidates() override {
RTC_DCHECK_RUN_ON(network_thread_);
remote_candidates_.clear();
}
bool GetStats(IceTransportStats* ice_transport_stats) override {
CandidateStats candidate_stats;
ConnectionInfo candidate_pair_stats;
ice_transport_stats->candidate_stats_list.clear();
ice_transport_stats->candidate_stats_list.push_back(candidate_stats);
ice_transport_stats->connection_infos.clear();
ice_transport_stats->connection_infos.push_back(candidate_pair_stats);
return true;
}
absl::optional<int> GetRttEstimate() override { return absl::nullopt; }
const Connection* selected_connection() const override { return nullptr; }
absl::optional<const CandidatePair> GetSelectedCandidatePair()
const override {
return absl::nullopt;
}
// Fake PacketTransportInternal implementation.
bool writable() const override {
RTC_DCHECK_RUN_ON(network_thread_);
return writable_;
}
bool receiving() const override {
RTC_DCHECK_RUN_ON(network_thread_);
return receiving_;
}
// If combine is enabled, every two consecutive packets to be sent with
// "SendPacket" will be combined into one outgoing packet.
void combine_outgoing_packets(bool combine) {
RTC_DCHECK_RUN_ON(network_thread_);
combine_outgoing_packets_ = combine;
}
int SendPacket(const char* data,
size_t len,
const rtc::PacketOptions& options,
int flags) override {
RTC_DCHECK_RUN_ON(network_thread_);
if (!dest_) {
return -1;
}
send_packet_.AppendData(data, len);
if (!combine_outgoing_packets_ || send_packet_.size() > len) {
rtc::CopyOnWriteBuffer packet(std::move(send_packet_));
if (async_) {
network_thread_->PostDelayedTask(
SafeTask(task_safety_.flag(),
[this, packet] {
RTC_DCHECK_RUN_ON(network_thread_);
FakeIceTransport::SendPacketInternal(packet);
}),
TimeDelta::Millis(async_delay_ms_));
} else {
SendPacketInternal(packet);
}
}
rtc::SentPacket sent_packet(options.packet_id, rtc::TimeMillis());
SignalSentPacket(this, sent_packet);
return static_cast<int>(len);
}
int SetOption(rtc::Socket::Option opt, int value) override {
RTC_DCHECK_RUN_ON(network_thread_);
socket_options_[opt] = value;
return true;
}
bool GetOption(rtc::Socket::Option opt, int* value) override {
RTC_DCHECK_RUN_ON(network_thread_);
auto it = socket_options_.find(opt);
if (it != socket_options_.end()) {
*value = it->second;
return true;
} else {
return false;
}
}
int GetError() override { return 0; }
rtc::CopyOnWriteBuffer last_sent_packet() {
RTC_DCHECK_RUN_ON(network_thread_);
return last_sent_packet_;
}
absl::optional<rtc::NetworkRoute> network_route() const override {
RTC_DCHECK_RUN_ON(network_thread_);
return network_route_;
}
void SetNetworkRoute(absl::optional<rtc::NetworkRoute> network_route) {
RTC_DCHECK_RUN_ON(network_thread_);
network_route_ = network_route;
SendTask(network_thread_, [this] {
RTC_DCHECK_RUN_ON(network_thread_);
SignalNetworkRouteChanged(network_route_);
});
}
private:
void set_writable(bool writable)
RTC_EXCLUSIVE_LOCKS_REQUIRED(network_thread_) {
if (writable_ == writable) {
return;
}
RTC_LOG(LS_INFO) << "Change writable_ to " << writable;
writable_ = writable;
if (writable_) {
SignalReadyToSend(this);
}
SignalWritableState(this);
}
void set_receiving(bool receiving)
RTC_EXCLUSIVE_LOCKS_REQUIRED(network_thread_) {
if (receiving_ == receiving) {
return;
}
receiving_ = receiving;
SignalReceivingState(this);
}
void SendPacketInternal(const rtc::CopyOnWriteBuffer& packet)
RTC_EXCLUSIVE_LOCKS_REQUIRED(network_thread_) {
if (dest_) {
last_sent_packet_ = packet;
dest_->SignalReadPacket(dest_, packet.data<char>(), packet.size(),
rtc::TimeMicros(), 0);
}
}
const std::string name_;
const int component_;
FakeIceTransport* dest_ RTC_GUARDED_BY(network_thread_) = nullptr;
bool async_ RTC_GUARDED_BY(network_thread_) = false;
int async_delay_ms_ RTC_GUARDED_BY(network_thread_) = 0;
Candidates remote_candidates_ RTC_GUARDED_BY(network_thread_);
IceConfig ice_config_ RTC_GUARDED_BY(network_thread_);
IceRole role_ RTC_GUARDED_BY(network_thread_) = ICEROLE_UNKNOWN;
uint64_t tiebreaker_ RTC_GUARDED_BY(network_thread_) = 0;
IceParameters ice_parameters_ RTC_GUARDED_BY(network_thread_);
IceParameters remote_ice_parameters_ RTC_GUARDED_BY(network_thread_);
IceMode remote_ice_mode_ RTC_GUARDED_BY(network_thread_) = ICEMODE_FULL;
size_t connection_count_ RTC_GUARDED_BY(network_thread_) = 0;
absl::optional<webrtc::IceTransportState> transport_state_
RTC_GUARDED_BY(network_thread_);
absl::optional<IceTransportState> legacy_transport_state_
RTC_GUARDED_BY(network_thread_);
IceGatheringState gathering_state_ RTC_GUARDED_BY(network_thread_) =
kIceGatheringNew;
bool had_connection_ RTC_GUARDED_BY(network_thread_) = false;
bool writable_ RTC_GUARDED_BY(network_thread_) = false;
bool receiving_ RTC_GUARDED_BY(network_thread_) = false;
bool combine_outgoing_packets_ RTC_GUARDED_BY(network_thread_) = false;
rtc::CopyOnWriteBuffer send_packet_ RTC_GUARDED_BY(network_thread_);
absl::optional<rtc::NetworkRoute> network_route_
RTC_GUARDED_BY(network_thread_);
std::map<rtc::Socket::Option, int> socket_options_
RTC_GUARDED_BY(network_thread_);
rtc::CopyOnWriteBuffer last_sent_packet_ RTC_GUARDED_BY(network_thread_);
rtc::Thread* const network_thread_;
webrtc::ScopedTaskSafetyDetached task_safety_;
};
class FakeIceTransportWrapper : public webrtc::IceTransportInterface {
public:
explicit FakeIceTransportWrapper(
std::unique_ptr<cricket::FakeIceTransport> internal)
: internal_(std::move(internal)) {}
cricket::IceTransportInternal* internal() override { return internal_.get(); }
private:
std::unique_ptr<cricket::FakeIceTransport> internal_;
};
} // namespace cricket
#endif // P2P_BASE_FAKE_ICE_TRANSPORT_H_

View file

@ -0,0 +1,143 @@
/*
* Copyright 2017 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.
*/
#ifndef P2P_BASE_FAKE_PACKET_TRANSPORT_H_
#define P2P_BASE_FAKE_PACKET_TRANSPORT_H_
#include <map>
#include <string>
#include "p2p/base/packet_transport_internal.h"
#include "rtc_base/copy_on_write_buffer.h"
namespace rtc {
// Used to simulate a packet-based transport.
class FakePacketTransport : public PacketTransportInternal {
public:
explicit FakePacketTransport(const std::string& transport_name)
: transport_name_(transport_name) {}
~FakePacketTransport() override {
if (dest_ && dest_->dest_ == this) {
dest_->dest_ = nullptr;
}
}
// SetWritable, SetReceiving and SetDestination are the main methods that can
// be used for testing, to simulate connectivity or lack thereof.
void SetWritable(bool writable) { set_writable(writable); }
void SetReceiving(bool receiving) { set_receiving(receiving); }
// Simulates the two transports connecting to each other.
// If `asymmetric` is true this method only affects this FakePacketTransport.
// If false, it affects `dest` as well.
void SetDestination(FakePacketTransport* dest, bool asymmetric) {
if (dest) {
dest_ = dest;
set_writable(true);
if (!asymmetric) {
dest->SetDestination(this, true);
}
} else {
// Simulates loss of connectivity, by asymmetrically forgetting dest_.
dest_ = nullptr;
set_writable(false);
}
}
// Fake PacketTransportInternal implementation.
const std::string& transport_name() const override { return transport_name_; }
bool writable() const override { return writable_; }
bool receiving() const override { return receiving_; }
int SendPacket(const char* data,
size_t len,
const PacketOptions& options,
int flags) override {
if (!dest_) {
return -1;
}
CopyOnWriteBuffer packet(data, len);
SendPacketInternal(packet);
SentPacket sent_packet(options.packet_id, TimeMillis());
SignalSentPacket(this, sent_packet);
return static_cast<int>(len);
}
int SetOption(Socket::Option opt, int value) override {
options_[opt] = value;
return 0;
}
bool GetOption(Socket::Option opt, int* value) override {
auto it = options_.find(opt);
if (it == options_.end()) {
return false;
}
*value = it->second;
return true;
}
int GetError() override { return error_; }
void SetError(int error) { error_ = error; }
const CopyOnWriteBuffer* last_sent_packet() { return &last_sent_packet_; }
absl::optional<NetworkRoute> network_route() const override {
return network_route_;
}
void SetNetworkRoute(absl::optional<NetworkRoute> network_route) {
network_route_ = network_route;
SignalNetworkRouteChanged(network_route);
}
private:
void set_writable(bool writable) {
if (writable_ == writable) {
return;
}
writable_ = writable;
if (writable_) {
SignalReadyToSend(this);
}
SignalWritableState(this);
}
void set_receiving(bool receiving) {
if (receiving_ == receiving) {
return;
}
receiving_ = receiving;
SignalReceivingState(this);
}
void SendPacketInternal(const CopyOnWriteBuffer& packet) {
last_sent_packet_ = packet;
if (dest_) {
dest_->SignalReadPacket(dest_, packet.data<char>(), packet.size(),
TimeMicros(), 0);
}
}
CopyOnWriteBuffer last_sent_packet_;
std::string transport_name_;
FakePacketTransport* dest_ = nullptr;
bool writable_ = false;
bool receiving_ = false;
std::map<Socket::Option, int> options_;
int error_ = 0;
absl::optional<NetworkRoute> network_route_;
};
} // namespace rtc
#endif // P2P_BASE_FAKE_PACKET_TRANSPORT_H_

View file

@ -0,0 +1,282 @@
/*
* Copyright 2010 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.
*/
#ifndef P2P_BASE_FAKE_PORT_ALLOCATOR_H_
#define P2P_BASE_FAKE_PORT_ALLOCATOR_H_
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/string_view.h"
#include "p2p/base/basic_packet_socket_factory.h"
#include "p2p/base/port_allocator.h"
#include "p2p/base/udp_port.h"
#include "rtc_base/memory/always_valid_pointer.h"
#include "rtc_base/net_helpers.h"
#include "rtc_base/net_test_helpers.h"
#include "rtc_base/task_queue_for_test.h"
#include "rtc_base/thread.h"
namespace rtc {
class SocketFactory;
}
namespace cricket {
class TestUDPPort : public UDPPort {
public:
static TestUDPPort* Create(rtc::Thread* thread,
rtc::PacketSocketFactory* factory,
const rtc::Network* network,
uint16_t min_port,
uint16_t max_port,
absl::string_view username,
absl::string_view password,
bool emit_localhost_for_anyaddress,
const webrtc::FieldTrialsView* field_trials) {
TestUDPPort* port =
new TestUDPPort(thread, factory, network, min_port, max_port, username,
password, emit_localhost_for_anyaddress, field_trials);
if (!port->Init()) {
delete port;
port = nullptr;
}
return port;
}
protected:
TestUDPPort(rtc::Thread* thread,
rtc::PacketSocketFactory* factory,
const rtc::Network* network,
uint16_t min_port,
uint16_t max_port,
absl::string_view username,
absl::string_view password,
bool emit_localhost_for_anyaddress,
const webrtc::FieldTrialsView* field_trials)
: UDPPort(thread,
LOCAL_PORT_TYPE,
factory,
network,
min_port,
max_port,
username,
password,
emit_localhost_for_anyaddress,
field_trials) {}
};
// A FakePortAllocatorSession can be used with either a real or fake socket
// factory. It gathers a single loopback port, using IPv6 if available and
// not disabled.
class FakePortAllocatorSession : public PortAllocatorSession {
public:
FakePortAllocatorSession(PortAllocator* allocator,
rtc::Thread* network_thread,
rtc::PacketSocketFactory* factory,
absl::string_view content_name,
int component,
absl::string_view ice_ufrag,
absl::string_view ice_pwd,
const webrtc::FieldTrialsView* field_trials)
: PortAllocatorSession(content_name,
component,
ice_ufrag,
ice_pwd,
allocator->flags()),
allocator_(allocator),
network_thread_(network_thread),
factory_(factory),
ipv4_network_("network",
"unittest",
rtc::IPAddress(INADDR_LOOPBACK),
32),
ipv6_network_("network",
"unittest",
rtc::IPAddress(in6addr_loopback),
64),
port_(),
port_config_count_(0),
stun_servers_(allocator->stun_servers()),
turn_servers_(allocator->turn_servers()),
field_trials_(field_trials) {
ipv4_network_.AddIP(rtc::IPAddress(INADDR_LOOPBACK));
ipv6_network_.AddIP(rtc::IPAddress(in6addr_loopback));
}
void SetCandidateFilter(uint32_t filter) override {
candidate_filter_ = filter;
}
void StartGettingPorts() override {
if (!port_) {
rtc::Network& network =
(rtc::HasIPv6Enabled() && (flags() & PORTALLOCATOR_ENABLE_IPV6))
? ipv6_network_
: ipv4_network_;
port_.reset(TestUDPPort::Create(network_thread_, factory_, &network, 0, 0,
username(), password(), false,
field_trials_));
RTC_DCHECK(port_);
port_->SetIceTiebreaker(allocator_->ice_tiebreaker());
port_->SubscribePortDestroyed(
[this](PortInterface* port) { OnPortDestroyed(port); });
AddPort(port_.get());
}
++port_config_count_;
running_ = true;
}
void StopGettingPorts() override { running_ = false; }
bool IsGettingPorts() override { return running_; }
void ClearGettingPorts() override { is_cleared = true; }
bool IsCleared() const override { return is_cleared; }
void RegatherOnFailedNetworks() override {
SignalIceRegathering(this, IceRegatheringReason::NETWORK_FAILURE);
}
std::vector<PortInterface*> ReadyPorts() const override {
return ready_ports_;
}
std::vector<Candidate> ReadyCandidates() const override {
return candidates_;
}
void PruneAllPorts() override { port_->Prune(); }
bool CandidatesAllocationDone() const override { return allocation_done_; }
int port_config_count() { return port_config_count_; }
const ServerAddresses& stun_servers() const { return stun_servers_; }
const std::vector<RelayServerConfig>& turn_servers() const {
return turn_servers_;
}
uint32_t candidate_filter() const { return candidate_filter_; }
int transport_info_update_count() const {
return transport_info_update_count_;
}
protected:
void UpdateIceParametersInternal() override {
// Since this class is a fake and this method only is overridden for tests,
// we don't need to actually update the transport info.
++transport_info_update_count_;
}
private:
void AddPort(cricket::Port* port) {
port->set_component(component());
port->set_generation(generation());
port->SignalPortComplete.connect(this,
&FakePortAllocatorSession::OnPortComplete);
port->PrepareAddress();
ready_ports_.push_back(port);
SignalPortReady(this, port);
port->KeepAliveUntilPruned();
}
void OnPortComplete(cricket::Port* port) {
const std::vector<Candidate>& candidates = port->Candidates();
candidates_.insert(candidates_.end(), candidates.begin(), candidates.end());
SignalCandidatesReady(this, candidates);
allocation_done_ = true;
SignalCandidatesAllocationDone(this);
}
void OnPortDestroyed(cricket::PortInterface* port) {
// Don't want to double-delete port if it deletes itself.
port_.release();
}
PortAllocator* allocator_;
rtc::Thread* network_thread_;
rtc::PacketSocketFactory* factory_;
rtc::Network ipv4_network_;
rtc::Network ipv6_network_;
std::unique_ptr<cricket::Port> port_;
int port_config_count_;
std::vector<Candidate> candidates_;
std::vector<PortInterface*> ready_ports_;
bool allocation_done_ = false;
bool is_cleared = false;
ServerAddresses stun_servers_;
std::vector<RelayServerConfig> turn_servers_;
uint32_t candidate_filter_ = CF_ALL;
int transport_info_update_count_ = 0;
bool running_ = false;
const webrtc::FieldTrialsView* field_trials_;
};
class FakePortAllocator : public cricket::PortAllocator {
public:
FakePortAllocator(rtc::Thread* network_thread,
rtc::PacketSocketFactory* factory,
webrtc::FieldTrialsView* field_trials)
: FakePortAllocator(network_thread, factory, nullptr, field_trials) {}
FakePortAllocator(rtc::Thread* network_thread,
std::unique_ptr<rtc::PacketSocketFactory> factory,
webrtc::FieldTrialsView* field_trials)
: FakePortAllocator(network_thread,
nullptr,
std::move(factory),
field_trials) {}
void SetNetworkIgnoreMask(int network_ignore_mask) override {}
cricket::PortAllocatorSession* CreateSessionInternal(
absl::string_view content_name,
int component,
absl::string_view ice_ufrag,
absl::string_view ice_pwd) override {
return new FakePortAllocatorSession(
this, network_thread_, factory_.get(), std::string(content_name),
component, std::string(ice_ufrag), std::string(ice_pwd), field_trials_);
}
bool initialized() const { return initialized_; }
// For testing: Manipulate MdnsObfuscationEnabled()
bool MdnsObfuscationEnabled() const override {
return mdns_obfuscation_enabled_;
}
void SetMdnsObfuscationEnabledForTesting(bool enabled) {
mdns_obfuscation_enabled_ = enabled;
}
private:
FakePortAllocator(rtc::Thread* network_thread,
rtc::PacketSocketFactory* factory,
std::unique_ptr<rtc::PacketSocketFactory> owned_factory,
webrtc::FieldTrialsView* field_trials)
: network_thread_(network_thread),
factory_(std::move(owned_factory), factory),
field_trials_(field_trials) {
if (network_thread_ == nullptr) {
network_thread_ = rtc::Thread::Current();
Initialize();
return;
}
SendTask(network_thread_, [this] { Initialize(); });
}
rtc::Thread* network_thread_;
const webrtc::AlwaysValidPointerNoDefault<rtc::PacketSocketFactory> factory_;
const webrtc::FieldTrialsView* field_trials_;
bool mdns_obfuscation_enabled_ = false;
};
} // namespace cricket
#endif // P2P_BASE_FAKE_PORT_ALLOCATOR_H_

View file

@ -0,0 +1,80 @@
/*
* Copyright 2022 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.
*/
#ifndef P2P_BASE_ICE_AGENT_INTERFACE_H_
#define P2P_BASE_ICE_AGENT_INTERFACE_H_
#include "api/array_view.h"
#include "p2p/base/connection.h"
#include "p2p/base/ice_switch_reason.h"
namespace cricket {
// IceAgentInterface provides methods that allow an ICE controller to manipulate
// the connections available to a transport, and used by the transport to
// transfer data.
class IceAgentInterface {
public:
virtual ~IceAgentInterface() = default;
// Get the time when the last ping was sent.
// This is only needed in some scenarios if the agent decides to ping on its
// own, eg. in some switchover scenarios. Otherwise the ICE controller could
// keep this state on its own.
// TODO(bugs.webrtc.org/14367): route extra pings through the ICE controller.
virtual int64_t GetLastPingSentMs() const = 0;
// Get the ICE role of this ICE agent.
virtual IceRole GetIceRole() const = 0;
// Called when a pingable connection first becomes available.
virtual void OnStartedPinging() = 0;
// Update the state of all available connections.
virtual void UpdateConnectionStates() = 0;
// Update the internal state of the ICE agent. An ICE controller should call
// this at the end of a sequence of actions to combine several mutations into
// a single state refresh.
// TODO(bugs.webrtc.org/14431): ICE agent state updates should be internal to
// the agent. If batching is necessary, use a more appropriate interface.
virtual void UpdateState() = 0;
// Reset the given connections to a state of newly connected connections.
// - STATE_WRITE_INIT
// - receving = false
// - throw away all pending request
// - reset RttEstimate
//
// Keep the following unchanged:
// - connected
// - remote_candidate
// - statistics
//
// SignalStateChange will not be triggered.
virtual void ForgetLearnedStateForConnections(
rtc::ArrayView<const Connection* const> connections) = 0;
// Send a STUN ping request for the given connection.
virtual void SendPingRequest(const Connection* connection) = 0;
// Switch the transport to use the given connection.
virtual void SwitchSelectedConnection(const Connection* new_connection,
IceSwitchReason reason) = 0;
// Prune away the given connections. Returns true if pruning is permitted and
// successfully performed.
virtual bool PruneConnections(
rtc::ArrayView<const Connection* const> connections) = 0;
};
} // namespace cricket
#endif // P2P_BASE_ICE_AGENT_INTERFACE_H_

View file

@ -0,0 +1,40 @@
/*
* Copyright 2019 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.
*/
#ifndef P2P_BASE_ICE_CONTROLLER_FACTORY_INTERFACE_H_
#define P2P_BASE_ICE_CONTROLLER_FACTORY_INTERFACE_H_
#include <memory>
#include <string>
#include "p2p/base/ice_controller_interface.h"
#include "p2p/base/ice_transport_internal.h"
namespace cricket {
// struct with arguments to IceControllerFactoryInterface::Create
struct IceControllerFactoryArgs {
std::function<IceTransportState()> ice_transport_state_func;
std::function<IceRole()> ice_role_func;
std::function<bool(const Connection*)> is_connection_pruned_func;
const IceFieldTrials* ice_field_trials;
std::string ice_controller_field_trials;
};
class IceControllerFactoryInterface {
public:
virtual ~IceControllerFactoryInterface() = default;
virtual std::unique_ptr<IceControllerInterface> Create(
const IceControllerFactoryArgs& args) = 0;
};
} // namespace cricket
#endif // P2P_BASE_ICE_CONTROLLER_FACTORY_INTERFACE_H_

View file

@ -0,0 +1,27 @@
/*
* Copyright 2019 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/ice_controller_interface.h"
#include <string>
#include "p2p/base/ice_switch_reason.h"
namespace cricket {
std::string IceRecheckEvent::ToString() const {
std::string str = IceSwitchReasonToString(reason);
if (recheck_delay_ms) {
str += " (after delay: " + std::to_string(recheck_delay_ms) + ")";
}
return str;
}
} // namespace cricket

View file

@ -0,0 +1,150 @@
/*
* Copyright 2019 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.
*/
#ifndef P2P_BASE_ICE_CONTROLLER_INTERFACE_H_
#define P2P_BASE_ICE_CONTROLLER_INTERFACE_H_
#include <string>
#include <utility>
#include <vector>
#include "absl/types/optional.h"
#include "p2p/base/connection.h"
#include "p2p/base/ice_switch_reason.h"
#include "p2p/base/ice_transport_internal.h"
#include "rtc_base/checks.h"
#include "rtc_base/system/rtc_export.h"
namespace cricket {
struct IceFieldTrials; // Forward declaration to avoid circular dependency.
struct RTC_EXPORT IceRecheckEvent {
IceRecheckEvent(IceSwitchReason _reason, int _recheck_delay_ms)
: reason(_reason), recheck_delay_ms(_recheck_delay_ms) {}
std::string ToString() const;
IceSwitchReason reason;
int recheck_delay_ms;
};
// Defines the interface for a module that control
// - which connection to ping
// - which connection to use
// - which connection to prune
// - which connection to forget learned state on
//
// The P2PTransportChannel owns (creates and destroys) Connections,
// but P2PTransportChannel gives const pointers to the the IceController using
// `AddConnection`, i.e the IceController should not call any non-const methods
// on a Connection but signal back in the interface if any mutable function
// shall be called.
//
// Current these are limited to:
// Connection::Ping - returned in PingResult
// Connection::Prune - retuned in PruneConnections
// Connection::ForgetLearnedState - return in SwitchResult
//
// The IceController shall keep track of all connections added
// (and not destroyed) and give them back using the GetConnections() function.
//
// When a Connection gets destroyed
// - signals on Connection::SignalDestroyed
// - P2PTransportChannel calls IceController::OnConnectionDestroyed
class IceControllerInterface {
public:
// This represents the result of a switch call.
struct SwitchResult {
// Connection that we should (optionally) switch to.
absl::optional<const Connection*> connection;
// An optional recheck event for when a Switch() should be attempted again.
absl::optional<IceRecheckEvent> recheck_event;
// A vector with connection to run ForgetLearnedState on.
std::vector<const Connection*> connections_to_forget_state_on;
};
// This represents the result of a call to SelectConnectionToPing.
struct PingResult {
PingResult(const Connection* conn, int _recheck_delay_ms)
: connection(conn ? absl::optional<const Connection*>(conn)
: absl::nullopt),
recheck_delay_ms(_recheck_delay_ms) {}
// Connection that we should (optionally) ping.
const absl::optional<const Connection*> connection;
// The delay before P2PTransportChannel shall call SelectConnectionToPing()
// again.
//
// Since the IceController determines which connection to ping and
// only returns one connection at a time, the recheck_delay_ms does not have
// any obvious implication on bitrate for pings. E.g the recheck_delay_ms
// will be shorter if there are more connections available.
const int recheck_delay_ms = 0;
};
virtual ~IceControllerInterface() = default;
// These setters are called when the state of P2PTransportChannel is mutated.
virtual void SetIceConfig(const IceConfig& config) = 0;
virtual void SetSelectedConnection(const Connection* selected_connection) = 0;
virtual void AddConnection(const Connection* connection) = 0;
virtual void OnConnectionDestroyed(const Connection* connection) = 0;
// These are all connections that has been added and not destroyed.
virtual rtc::ArrayView<const Connection* const> GetConnections() const {
// Stub implementation to simplify downstream roll.
RTC_CHECK_NOTREACHED();
return {};
}
// TODO(bugs.webrtc.org/15702): Remove this after downstream is cleaned up.
virtual rtc::ArrayView<const Connection*> connections() const {
// Stub implementation to simplify downstream removal.
RTC_CHECK_NOTREACHED();
return {};
}
// Is there a pingable connection ?
// This function is used to boot-strap pinging, after this returns true
// SelectConnectionToPing() will be called periodically.
virtual bool HasPingableConnection() const = 0;
// Select a connection to Ping, or nullptr if none.
virtual PingResult SelectConnectionToPing(int64_t last_ping_sent_ms) = 0;
// Compute the "STUN_ATTR_USE_CANDIDATE" for `conn`.
virtual bool GetUseCandidateAttr(const Connection* conn,
NominationMode mode,
IceMode remote_ice_mode) const = 0;
// These methods is only added to not have to change all unit tests
// that simulate pinging by marking a connection pinged.
virtual const Connection* FindNextPingableConnection() = 0;
virtual void MarkConnectionPinged(const Connection* con) = 0;
// Check if we should switch to `connection`.
// This method is called for IceSwitchReasons that can switch directly
// i.e without resorting.
virtual SwitchResult ShouldSwitchConnection(IceSwitchReason reason,
const Connection* connection) = 0;
// Sort connections and check if we should switch.
virtual SwitchResult SortAndSwitchConnection(IceSwitchReason reason) = 0;
// Prune connections.
virtual std::vector<const Connection*> PruneConnections() = 0;
};
} // namespace cricket
#endif // P2P_BASE_ICE_CONTROLLER_INTERFACE_H_

View file

@ -0,0 +1,38 @@
/*
* Copyright 2018 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/ice_credentials_iterator.h"
#include "p2p/base/p2p_constants.h"
#include "rtc_base/helpers.h"
namespace cricket {
IceCredentialsIterator::IceCredentialsIterator(
const std::vector<IceParameters>& pooled_credentials)
: pooled_ice_credentials_(pooled_credentials) {}
IceCredentialsIterator::~IceCredentialsIterator() = default;
IceParameters IceCredentialsIterator::CreateRandomIceCredentials() {
return IceParameters(rtc::CreateRandomString(ICE_UFRAG_LENGTH),
rtc::CreateRandomString(ICE_PWD_LENGTH), false);
}
IceParameters IceCredentialsIterator::GetIceCredentials() {
if (pooled_ice_credentials_.empty()) {
return CreateRandomIceCredentials();
}
IceParameters credentials = pooled_ice_credentials_.back();
pooled_ice_credentials_.pop_back();
return credentials;
}
} // namespace cricket

View file

@ -0,0 +1,37 @@
/*
* Copyright 2018 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.
*/
#ifndef P2P_BASE_ICE_CREDENTIALS_ITERATOR_H_
#define P2P_BASE_ICE_CREDENTIALS_ITERATOR_H_
#include <vector>
#include "p2p/base/transport_description.h"
namespace cricket {
class IceCredentialsIterator {
public:
explicit IceCredentialsIterator(const std::vector<IceParameters>&);
virtual ~IceCredentialsIterator();
// Get next pooled ice credentials.
// Returns a new random credential if the pool is empty.
IceParameters GetIceCredentials();
static IceParameters CreateRandomIceCredentials();
private:
std::vector<IceParameters> pooled_ice_credentials_;
};
} // namespace cricket
#endif // P2P_BASE_ICE_CREDENTIALS_ITERATOR_H_

View file

@ -0,0 +1,47 @@
/*
* Copyright 2022 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/ice_switch_reason.h"
#include <string>
namespace cricket {
std::string IceSwitchReasonToString(IceSwitchReason reason) {
switch (reason) {
case IceSwitchReason::REMOTE_CANDIDATE_GENERATION_CHANGE:
return "remote candidate generation maybe changed";
case IceSwitchReason::NETWORK_PREFERENCE_CHANGE:
return "network preference changed";
case IceSwitchReason::NEW_CONNECTION_FROM_LOCAL_CANDIDATE:
return "new candidate pairs created from a new local candidate";
case IceSwitchReason::NEW_CONNECTION_FROM_REMOTE_CANDIDATE:
return "new candidate pairs created from a new remote candidate";
case IceSwitchReason::NEW_CONNECTION_FROM_UNKNOWN_REMOTE_ADDRESS:
return "a new candidate pair created from an unknown remote address";
case IceSwitchReason::NOMINATION_ON_CONTROLLED_SIDE:
return "nomination on the controlled side";
case IceSwitchReason::DATA_RECEIVED:
return "data received";
case IceSwitchReason::CONNECT_STATE_CHANGE:
return "candidate pair state changed";
case IceSwitchReason::SELECTED_CONNECTION_DESTROYED:
return "selected candidate pair destroyed";
case IceSwitchReason::ICE_CONTROLLER_RECHECK:
return "ice-controller-request-recheck";
case IceSwitchReason::APPLICATION_REQUESTED:
return "application requested";
case IceSwitchReason::UNKNOWN:
default:
return "unknown";
}
}
} // namespace cricket

View file

@ -0,0 +1,43 @@
/*
* Copyright 2022 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.
*/
#ifndef P2P_BASE_ICE_SWITCH_REASON_H_
#define P2P_BASE_ICE_SWITCH_REASON_H_
#include <string>
#include "rtc_base/system/rtc_export.h"
namespace cricket {
enum class IceSwitchReason {
UNKNOWN,
REMOTE_CANDIDATE_GENERATION_CHANGE,
NETWORK_PREFERENCE_CHANGE,
NEW_CONNECTION_FROM_LOCAL_CANDIDATE,
NEW_CONNECTION_FROM_REMOTE_CANDIDATE,
NEW_CONNECTION_FROM_UNKNOWN_REMOTE_ADDRESS,
NOMINATION_ON_CONTROLLED_SIDE,
DATA_RECEIVED,
CONNECT_STATE_CHANGE,
SELECTED_CONNECTION_DESTROYED,
// The ICE_CONTROLLER_RECHECK enum value lets an IceController request
// P2PTransportChannel to recheck a switch periodically without an event
// taking place.
ICE_CONTROLLER_RECHECK,
// The webrtc application requested a connection switch.
APPLICATION_REQUESTED,
};
RTC_EXPORT std::string IceSwitchReasonToString(IceSwitchReason reason);
} // namespace cricket
#endif // P2P_BASE_ICE_SWITCH_REASON_H_

View file

@ -0,0 +1,146 @@
/*
* Copyright 2017 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/ice_transport_internal.h"
#include "absl/strings/string_view.h"
#include "p2p/base/p2p_constants.h"
namespace cricket {
using webrtc::RTCError;
using webrtc::RTCErrorType;
RTCError VerifyCandidate(const Candidate& cand) {
// No address zero.
if (cand.address().IsNil() || cand.address().IsAnyIP()) {
return RTCError(RTCErrorType::INVALID_PARAMETER,
"candidate has address of zero");
}
// Disallow all ports below 1024, except for 80 and 443 on public addresses.
int port = cand.address().port();
if (cand.protocol() == cricket::TCP_PROTOCOL_NAME &&
(cand.tcptype() == cricket::TCPTYPE_ACTIVE_STR || port == 0)) {
// Expected for active-only candidates per
// http://tools.ietf.org/html/rfc6544#section-4.5 so no error.
// Libjingle clients emit port 0, in "active" mode.
return RTCError::OK();
}
if (port < 1024) {
if ((port != 80) && (port != 443)) {
return RTCError(RTCErrorType::INVALID_PARAMETER,
"candidate has port below 1024, but not 80 or 443");
}
if (cand.address().IsPrivateIP()) {
return RTCError(
RTCErrorType::INVALID_PARAMETER,
"candidate has port of 80 or 443 with private IP address");
}
}
return RTCError::OK();
}
RTCError VerifyCandidates(const Candidates& candidates) {
for (const Candidate& candidate : candidates) {
RTCError error = VerifyCandidate(candidate);
if (!error.ok())
return error;
}
return RTCError::OK();
}
IceConfig::IceConfig() = default;
IceConfig::IceConfig(int receiving_timeout_ms,
int backup_connection_ping_interval,
ContinualGatheringPolicy gathering_policy,
bool prioritize_most_likely_candidate_pairs,
int stable_writable_connection_ping_interval_ms,
bool presume_writable_when_fully_relayed,
int regather_on_failed_networks_interval_ms,
int receiving_switching_delay_ms)
: receiving_timeout(receiving_timeout_ms),
backup_connection_ping_interval(backup_connection_ping_interval),
continual_gathering_policy(gathering_policy),
prioritize_most_likely_candidate_pairs(
prioritize_most_likely_candidate_pairs),
stable_writable_connection_ping_interval(
stable_writable_connection_ping_interval_ms),
presume_writable_when_fully_relayed(presume_writable_when_fully_relayed),
regather_on_failed_networks_interval(
regather_on_failed_networks_interval_ms),
receiving_switching_delay(receiving_switching_delay_ms) {}
IceConfig::~IceConfig() = default;
int IceConfig::receiving_timeout_or_default() const {
return receiving_timeout.value_or(RECEIVING_TIMEOUT);
}
int IceConfig::backup_connection_ping_interval_or_default() const {
return backup_connection_ping_interval.value_or(
BACKUP_CONNECTION_PING_INTERVAL);
}
int IceConfig::stable_writable_connection_ping_interval_or_default() const {
return stable_writable_connection_ping_interval.value_or(
STRONG_AND_STABLE_WRITABLE_CONNECTION_PING_INTERVAL);
}
int IceConfig::regather_on_failed_networks_interval_or_default() const {
return regather_on_failed_networks_interval.value_or(
REGATHER_ON_FAILED_NETWORKS_INTERVAL);
}
int IceConfig::receiving_switching_delay_or_default() const {
return receiving_switching_delay.value_or(RECEIVING_SWITCHING_DELAY);
}
int IceConfig::ice_check_interval_strong_connectivity_or_default() const {
return ice_check_interval_strong_connectivity.value_or(STRONG_PING_INTERVAL);
}
int IceConfig::ice_check_interval_weak_connectivity_or_default() const {
return ice_check_interval_weak_connectivity.value_or(WEAK_PING_INTERVAL);
}
int IceConfig::ice_check_min_interval_or_default() const {
return ice_check_min_interval.value_or(-1);
}
int IceConfig::ice_unwritable_timeout_or_default() const {
return ice_unwritable_timeout.value_or(CONNECTION_WRITE_CONNECT_TIMEOUT);
}
int IceConfig::ice_unwritable_min_checks_or_default() const {
return ice_unwritable_min_checks.value_or(CONNECTION_WRITE_CONNECT_FAILURES);
}
int IceConfig::ice_inactive_timeout_or_default() const {
return ice_inactive_timeout.value_or(CONNECTION_WRITE_TIMEOUT);
}
int IceConfig::stun_keepalive_interval_or_default() const {
return stun_keepalive_interval.value_or(STUN_KEEPALIVE_INTERVAL);
}
IceTransportInternal::IceTransportInternal() {
// Set up detector for use of SignalGatheringState rather than
// SetGatheringStateCallback, and behave accordingly
// TODO(bugs.webrtc.org/11943): remove when Signal removed
SignalGatheringState.connect(
this, &IceTransportInternal::SignalGatheringStateFired);
}
IceTransportInternal::~IceTransportInternal() = default;
void IceTransportInternal::SetIceCredentials(absl::string_view ice_ufrag,
absl::string_view ice_pwd) {
SetIceParameters(IceParameters(ice_ufrag, ice_pwd, false));
}
void IceTransportInternal::SetRemoteIceCredentials(absl::string_view ice_ufrag,
absl::string_view ice_pwd) {
SetRemoteIceParameters(IceParameters(ice_ufrag, ice_pwd, false));
}
} // namespace cricket

View file

@ -0,0 +1,418 @@
/*
* Copyright 2016 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.
*/
#ifndef P2P_BASE_ICE_TRANSPORT_INTERNAL_H_
#define P2P_BASE_ICE_TRANSPORT_INTERNAL_H_
#include <stdint.h>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "api/candidate.h"
#include "api/rtc_error.h"
#include "api/transport/enums.h"
#include "p2p/base/connection.h"
#include "p2p/base/packet_transport_internal.h"
#include "p2p/base/port.h"
#include "p2p/base/stun_dictionary.h"
#include "p2p/base/transport_description.h"
#include "rtc_base/network_constants.h"
#include "rtc_base/system/rtc_export.h"
#include "rtc_base/third_party/sigslot/sigslot.h"
#include "rtc_base/time_utils.h"
namespace cricket {
struct IceTransportStats {
CandidateStatsList candidate_stats_list;
ConnectionInfos connection_infos;
// Number of times the selected candidate pair has changed
// Initially 0 and 1 once the first candidate pair has been selected.
// The counter is increase also when "unselecting" a connection.
uint32_t selected_candidate_pair_changes = 0;
// Bytes/packets sent/received.
// note: Is not the same as sum(connection_infos.bytes_sent)
// as connections are created and destroyed while the ICE transport
// is alive.
uint64_t bytes_sent = 0;
uint64_t bytes_received = 0;
uint64_t packets_sent = 0;
uint64_t packets_received = 0;
IceRole ice_role = ICEROLE_UNKNOWN;
std::string ice_local_username_fragment;
webrtc::IceTransportState ice_state = webrtc::IceTransportState::kNew;
};
typedef std::vector<Candidate> Candidates;
enum IceConnectionState {
kIceConnectionConnecting = 0,
kIceConnectionFailed,
kIceConnectionConnected, // Writable, but still checking one or more
// connections
kIceConnectionCompleted,
};
// TODO(deadbeef): Unify with PeerConnectionInterface::IceConnectionState
// once /talk/ and /webrtc/ are combined, and also switch to ENUM_NAME naming
// style.
enum IceGatheringState {
kIceGatheringNew = 0,
kIceGatheringGathering,
kIceGatheringComplete,
};
enum ContinualGatheringPolicy {
// All port allocator sessions will stop after a writable connection is found.
GATHER_ONCE = 0,
// The most recent port allocator session will keep on running.
GATHER_CONTINUALLY,
};
// ICE Nomination mode.
enum class NominationMode {
REGULAR, // Nominate once per ICE restart (Not implemented yet).
AGGRESSIVE, // Nominate every connection except that it will behave as if
// REGULAR when the remote is an ICE-LITE endpoint.
SEMI_AGGRESSIVE // Our current implementation of the nomination algorithm.
// The details are described in P2PTransportChannel.
};
// Utility method that checks if various required Candidate fields are filled in
// and contain valid values. If conditions are not met, an RTCError with the
// appropriated error number and description is returned. If the configuration
// is valid RTCError::OK() is returned.
webrtc::RTCError VerifyCandidate(const Candidate& cand);
// Runs through a list of cricket::Candidate instances and calls VerifyCandidate
// for each one, stopping on the first error encounted and returning that error
// value if so. On success returns RTCError::OK().
webrtc::RTCError VerifyCandidates(const Candidates& candidates);
// Information about ICE configuration.
// TODO(deadbeef): Use absl::optional to represent unset values, instead of
// -1.
//
// TODO(bugs.webrtc.org/15609): Define a public API for this.
struct RTC_EXPORT IceConfig {
// The ICE connection receiving timeout value in milliseconds.
absl::optional<int> receiving_timeout;
// Time interval in milliseconds to ping a backup connection when the ICE
// channel is strongly connected.
absl::optional<int> backup_connection_ping_interval;
ContinualGatheringPolicy continual_gathering_policy = GATHER_ONCE;
bool gather_continually() const {
return continual_gathering_policy == GATHER_CONTINUALLY;
}
// Whether we should prioritize Relay/Relay candidate when nothing
// is writable yet.
bool prioritize_most_likely_candidate_pairs = false;
// Writable connections are pinged at a slower rate once stablized.
absl::optional<int> stable_writable_connection_ping_interval;
// If set to true, this means the ICE transport should presume TURN-to-TURN
// candidate pairs will succeed, even before a binding response is received.
bool presume_writable_when_fully_relayed = false;
// If true, after the ICE transport type (as the candidate filter used by the
// port allocator) is changed such that new types of ICE candidates are
// allowed by the new filter, e.g. from CF_RELAY to CF_ALL, candidates that
// have been gathered by the ICE transport but filtered out and not signaled
// to the upper layers, will be surfaced.
bool surface_ice_candidates_on_ice_transport_type_changed = false;
// Interval to check on all networks and to perform ICE regathering on any
// active network having no connection on it.
absl::optional<int> regather_on_failed_networks_interval;
// The time period in which we will not switch the selected connection
// when a new connection becomes receiving but the selected connection is not
// in case that the selected connection may become receiving soon.
absl::optional<int> receiving_switching_delay;
// TODO(honghaiz): Change the default to regular nomination.
// Default nomination mode if the remote does not support renomination.
NominationMode default_nomination_mode = NominationMode::SEMI_AGGRESSIVE;
// The interval in milliseconds at which ICE checks (STUN pings) will be sent
// for a candidate pair when it is both writable and receiving (strong
// connectivity). This parameter overrides the default value given by
// `STRONG_PING_INTERVAL` in p2ptransport.h if set.
absl::optional<int> ice_check_interval_strong_connectivity;
// The interval in milliseconds at which ICE checks (STUN pings) will be sent
// for a candidate pair when it is either not writable or not receiving (weak
// connectivity). This parameter overrides the default value given by
// `WEAK_PING_INTERVAL` in p2ptransport.h if set.
absl::optional<int> ice_check_interval_weak_connectivity;
// ICE checks (STUN pings) will not be sent at higher rate (lower interval)
// than this, no matter what other settings there are.
// Measure in milliseconds.
//
// Note that this parameter overrides both the above check intervals for
// candidate pairs with strong or weak connectivity, if either of the above
// interval is shorter than the min interval.
absl::optional<int> ice_check_min_interval;
// The min time period for which a candidate pair must wait for response to
// connectivity checks before it becomes unwritable. This parameter
// overrides the default value given by `CONNECTION_WRITE_CONNECT_TIMEOUT`
// in port.h if set, when determining the writability of a candidate pair.
absl::optional<int> ice_unwritable_timeout;
// The min number of connectivity checks that a candidate pair must sent
// without receiving response before it becomes unwritable. This parameter
// overrides the default value given by `CONNECTION_WRITE_CONNECT_FAILURES` in
// port.h if set, when determining the writability of a candidate pair.
absl::optional<int> ice_unwritable_min_checks;
// The min time period for which a candidate pair must wait for response to
// connectivity checks it becomes inactive. This parameter overrides the
// default value given by `CONNECTION_WRITE_TIMEOUT` in port.h if set, when
// determining the writability of a candidate pair.
absl::optional<int> ice_inactive_timeout;
// The interval in milliseconds at which STUN candidates will resend STUN
// binding requests to keep NAT bindings open.
absl::optional<int> stun_keepalive_interval;
absl::optional<rtc::AdapterType> network_preference;
webrtc::VpnPreference vpn_preference = webrtc::VpnPreference::kDefault;
IceConfig();
IceConfig(int receiving_timeout_ms,
int backup_connection_ping_interval,
ContinualGatheringPolicy gathering_policy,
bool prioritize_most_likely_candidate_pairs,
int stable_writable_connection_ping_interval_ms,
bool presume_writable_when_fully_relayed,
int regather_on_failed_networks_interval_ms,
int receiving_switching_delay_ms);
~IceConfig();
// Helper getters for parameters with implementation-specific default value.
// By convention, parameters with default value are represented by
// absl::optional and setting a parameter to null restores its default value.
int receiving_timeout_or_default() const;
int backup_connection_ping_interval_or_default() const;
int stable_writable_connection_ping_interval_or_default() const;
int regather_on_failed_networks_interval_or_default() const;
int receiving_switching_delay_or_default() const;
int ice_check_interval_strong_connectivity_or_default() const;
int ice_check_interval_weak_connectivity_or_default() const;
int ice_check_min_interval_or_default() const;
int ice_unwritable_timeout_or_default() const;
int ice_unwritable_min_checks_or_default() const;
int ice_inactive_timeout_or_default() const;
int stun_keepalive_interval_or_default() const;
};
// TODO(zhihuang): Replace this with
// PeerConnectionInterface::IceConnectionState.
enum class IceTransportState {
STATE_INIT,
STATE_CONNECTING, // Will enter this state once a connection is created
STATE_COMPLETED,
STATE_FAILED
};
// IceTransportInternal is an internal abstract class that does ICE.
// Once the public interface is supported,
// (https://www.w3.org/TR/webrtc/#rtcicetransport)
// the IceTransportInterface will be split from this class.
//
// TODO(bugs.webrtc.org/15609): Define a public API for this.
class RTC_EXPORT IceTransportInternal : public rtc::PacketTransportInternal {
public:
IceTransportInternal();
~IceTransportInternal() override;
// TODO(bugs.webrtc.org/9308): Remove GetState once all uses have been
// migrated to GetIceTransportState.
virtual IceTransportState GetState() const = 0;
virtual webrtc::IceTransportState GetIceTransportState() const = 0;
virtual int component() const = 0;
virtual IceRole GetIceRole() const = 0;
virtual void SetIceRole(IceRole role) = 0;
virtual void SetIceTiebreaker(uint64_t tiebreaker) = 0;
virtual void SetIceCredentials(absl::string_view ice_ufrag,
absl::string_view ice_pwd);
virtual void SetRemoteIceCredentials(absl::string_view ice_ufrag,
absl::string_view ice_pwd);
// The ufrag and pwd in `ice_params` must be set
// before candidate gathering can start.
virtual void SetIceParameters(const IceParameters& ice_params) = 0;
virtual void SetRemoteIceParameters(const IceParameters& ice_params) = 0;
virtual void SetRemoteIceMode(IceMode mode) = 0;
virtual void SetIceConfig(const IceConfig& config) = 0;
// Start gathering candidates if not already started, or if an ICE restart
// occurred.
virtual void MaybeStartGathering() = 0;
virtual void AddRemoteCandidate(const Candidate& candidate) = 0;
virtual void RemoveRemoteCandidate(const Candidate& candidate) = 0;
virtual void RemoveAllRemoteCandidates() = 0;
virtual IceGatheringState gathering_state() const = 0;
// Returns the current stats for this connection.
virtual bool GetStats(IceTransportStats* ice_transport_stats) = 0;
// Returns RTT estimate over the currently active connection, or an empty
// absl::optional if there is none.
virtual absl::optional<int> GetRttEstimate() = 0;
// TODO(qingsi): Remove this method once Chrome does not depend on it anymore.
virtual const Connection* selected_connection() const = 0;
// Returns the selected candidate pair, or an empty absl::optional if there is
// none.
virtual absl::optional<const CandidatePair> GetSelectedCandidatePair()
const = 0;
virtual absl::optional<std::reference_wrapper<StunDictionaryWriter>>
GetDictionaryWriter() {
return absl::nullopt;
}
// Signal Exposed for backwards compatibility.
sigslot::signal1<IceTransportInternal*> SignalGatheringState;
void SetGatheringStateCallback(
absl::AnyInvocable<void(IceTransportInternal*)> callback) {
RTC_DCHECK(!gathering_state_callback_);
gathering_state_callback_ = std::move(callback);
}
// Handles sending and receiving of candidates.
sigslot::signal2<IceTransportInternal*, const Candidate&>
SignalCandidateGathered;
void SetCandidateErrorCallback(
absl::AnyInvocable<void(IceTransportInternal*,
const IceCandidateErrorEvent&)> callback) {
RTC_DCHECK(!candidate_error_callback_);
candidate_error_callback_ = std::move(callback);
}
void SetCandidatesRemovedCallback(
absl::AnyInvocable<void(IceTransportInternal*, const Candidates&)>
callback) {
RTC_DCHECK(!candidates_removed_callback_);
candidates_removed_callback_ = std::move(callback);
}
// Deprecated by PacketTransportInternal::SignalNetworkRouteChanged.
// This signal occurs when there is a change in the way that packets are
// being routed, i.e. to a different remote location. The candidate
// indicates where and how we are currently sending media.
// TODO(zhihuang): Update the Chrome remoting to use the new
// SignalNetworkRouteChanged.
sigslot::signal2<IceTransportInternal*, const Candidate&> SignalRouteChange;
void SetCandidatePairChangeCallback(
absl::AnyInvocable<void(const cricket::CandidatePairChangeEvent&)>
callback) {
RTC_DCHECK(!candidate_pair_change_callback_);
candidate_pair_change_callback_ = std::move(callback);
}
// Invoked when there is conflict in the ICE role between local and remote
// agents.
sigslot::signal1<IceTransportInternal*> SignalRoleConflict;
// Emitted whenever the transport state changed.
// TODO(bugs.webrtc.org/9308): Remove once all uses have migrated to the new
// IceTransportState.
sigslot::signal1<IceTransportInternal*> SignalStateChanged;
// Emitted whenever the new standards-compliant transport state changed.
sigslot::signal1<IceTransportInternal*> SignalIceTransportStateChanged;
// Invoked when the transport is being destroyed.
sigslot::signal1<IceTransportInternal*> SignalDestroyed;
// Invoked when remote dictionary has been updated,
// i.e. modifications to attributes from remote ice agent has
// reflected in our StunDictionaryView.
template <typename F>
void AddDictionaryViewUpdatedCallback(const void* tag, F&& callback) {
dictionary_view_updated_callback_list_.AddReceiver(
tag, std::forward<F>(callback));
}
void RemoveDictionaryViewUpdatedCallback(const void* tag) {
dictionary_view_updated_callback_list_.RemoveReceivers(tag);
}
// Invoked when local dictionary has been synchronized,
// i.e. remote ice agent has reported acknowledged updates from us.
template <typename F>
void AddDictionaryWriterSyncedCallback(const void* tag, F&& callback) {
dictionary_writer_synced_callback_list_.AddReceiver(
tag, std::forward<F>(callback));
}
void RemoveDictionaryWriterSyncedCallback(const void* tag) {
dictionary_writer_synced_callback_list_.RemoveReceivers(tag);
}
protected:
void SendGatheringStateEvent() { SignalGatheringState(this); }
webrtc::CallbackList<IceTransportInternal*,
const StunDictionaryView&,
rtc::ArrayView<uint16_t>>
dictionary_view_updated_callback_list_;
webrtc::CallbackList<IceTransportInternal*, const StunDictionaryWriter&>
dictionary_writer_synced_callback_list_;
absl::AnyInvocable<void(IceTransportInternal*)> gathering_state_callback_;
absl::AnyInvocable<void(IceTransportInternal*, const IceCandidateErrorEvent&)>
candidate_error_callback_;
absl::AnyInvocable<void(IceTransportInternal*, const Candidates&)>
candidates_removed_callback_;
absl::AnyInvocable<void(const cricket::CandidatePairChangeEvent&)>
candidate_pair_change_callback_;
private:
// TODO(bugs.webrtc.org/11943): remove when removing Signal
void SignalGatheringStateFired(IceTransportInternal* transport) {
if (gathering_state_callback_) {
gathering_state_callback_(transport);
}
}
};
} // namespace cricket
#endif // P2P_BASE_ICE_TRANSPORT_INTERNAL_H_

View file

@ -0,0 +1,89 @@
/*
* Copyright 2018 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.
*/
#ifndef P2P_BASE_MOCK_ACTIVE_ICE_CONTROLLER_H_
#define P2P_BASE_MOCK_ACTIVE_ICE_CONTROLLER_H_
#include <memory>
#include "p2p/base/active_ice_controller_factory_interface.h"
#include "p2p/base/active_ice_controller_interface.h"
#include "test/gmock.h"
namespace cricket {
class MockActiveIceController : public cricket::ActiveIceControllerInterface {
public:
explicit MockActiveIceController(
const cricket::ActiveIceControllerFactoryArgs& args) {}
~MockActiveIceController() override = default;
MOCK_METHOD(void, SetIceConfig, (const cricket::IceConfig&), (override));
MOCK_METHOD(void,
OnConnectionAdded,
(const cricket::Connection*),
(override));
MOCK_METHOD(void,
OnConnectionSwitched,
(const cricket::Connection*),
(override));
MOCK_METHOD(void,
OnConnectionDestroyed,
(const cricket::Connection*),
(override));
MOCK_METHOD(void,
OnConnectionPinged,
(const cricket::Connection*),
(override));
MOCK_METHOD(void,
OnConnectionUpdated,
(const cricket::Connection*),
(override));
MOCK_METHOD(bool,
GetUseCandidateAttribute,
(const cricket::Connection*,
cricket::NominationMode,
cricket::IceMode),
(const, override));
MOCK_METHOD(void,
OnSortAndSwitchRequest,
(cricket::IceSwitchReason),
(override));
MOCK_METHOD(void,
OnImmediateSortAndSwitchRequest,
(cricket::IceSwitchReason),
(override));
MOCK_METHOD(bool,
OnImmediateSwitchRequest,
(cricket::IceSwitchReason, const cricket::Connection*),
(override));
MOCK_METHOD(const cricket::Connection*,
FindNextPingableConnection,
(),
(override));
};
class MockActiveIceControllerFactory
: public cricket::ActiveIceControllerFactoryInterface {
public:
~MockActiveIceControllerFactory() override = default;
std::unique_ptr<cricket::ActiveIceControllerInterface> Create(
const cricket::ActiveIceControllerFactoryArgs& args) {
RecordActiveIceControllerCreated();
return std::make_unique<MockActiveIceController>(args);
}
MOCK_METHOD(void, RecordActiveIceControllerCreated, ());
};
} // namespace cricket
#endif // P2P_BASE_MOCK_ACTIVE_ICE_CONTROLLER_H_

View file

@ -0,0 +1,53 @@
/*
* Copyright (c) 2022 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.
*/
#ifndef P2P_BASE_MOCK_DNS_RESOLVING_PACKET_SOCKET_FACTORY_H_
#define P2P_BASE_MOCK_DNS_RESOLVING_PACKET_SOCKET_FACTORY_H_
#include <functional>
#include <memory>
#include "api/test/mock_async_dns_resolver.h"
#include "p2p/base/basic_packet_socket_factory.h"
namespace rtc {
// A PacketSocketFactory implementation for tests that uses a mock DnsResolver
// and allows setting expectations on the resolver and results.
class MockDnsResolvingPacketSocketFactory : public BasicPacketSocketFactory {
public:
using Expectations = std::function<void(webrtc::MockAsyncDnsResolver*,
webrtc::MockAsyncDnsResolverResult*)>;
explicit MockDnsResolvingPacketSocketFactory(SocketFactory* socket_factory)
: BasicPacketSocketFactory(socket_factory) {}
std::unique_ptr<webrtc::AsyncDnsResolverInterface> CreateAsyncDnsResolver()
override {
std::unique_ptr<webrtc::MockAsyncDnsResolver> resolver =
std::make_unique<webrtc::MockAsyncDnsResolver>();
if (expectations_) {
expectations_(resolver.get(), &resolver_result_);
}
return resolver;
}
void SetExpectations(Expectations expectations) {
expectations_ = expectations;
}
private:
webrtc::MockAsyncDnsResolverResult resolver_result_;
Expectations expectations_;
};
} // namespace rtc
#endif // P2P_BASE_MOCK_DNS_RESOLVING_PACKET_SOCKET_FACTORY_H_

View file

@ -0,0 +1,50 @@
/*
* Copyright 2018 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.
*/
#ifndef P2P_BASE_MOCK_ICE_AGENT_H_
#define P2P_BASE_MOCK_ICE_AGENT_H_
#include <vector>
#include "p2p/base/connection.h"
#include "p2p/base/ice_agent_interface.h"
#include "p2p/base/ice_switch_reason.h"
#include "p2p/base/transport_description.h"
#include "test/gmock.h"
namespace cricket {
class MockIceAgent : public IceAgentInterface {
public:
~MockIceAgent() override = default;
MOCK_METHOD(int64_t, GetLastPingSentMs, (), (override, const));
MOCK_METHOD(IceRole, GetIceRole, (), (override, const));
MOCK_METHOD(void, OnStartedPinging, (), (override));
MOCK_METHOD(void, UpdateConnectionStates, (), (override));
MOCK_METHOD(void, UpdateState, (), (override));
MOCK_METHOD(void,
ForgetLearnedStateForConnections,
(rtc::ArrayView<const Connection* const>),
(override));
MOCK_METHOD(void, SendPingRequest, (const Connection*), (override));
MOCK_METHOD(void,
SwitchSelectedConnection,
(const Connection*, IceSwitchReason),
(override));
MOCK_METHOD(bool,
PruneConnections,
(rtc::ArrayView<const Connection* const>),
(override));
};
} // namespace cricket
#endif // P2P_BASE_MOCK_ICE_AGENT_H_

View file

@ -0,0 +1,94 @@
/*
* Copyright 2018 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.
*/
#ifndef P2P_BASE_MOCK_ICE_CONTROLLER_H_
#define P2P_BASE_MOCK_ICE_CONTROLLER_H_
#include <memory>
#include <vector>
#include "p2p/base/ice_controller_factory_interface.h"
#include "p2p/base/ice_controller_interface.h"
#include "test/gmock.h"
namespace cricket {
class MockIceController : public cricket::IceControllerInterface {
public:
explicit MockIceController(const cricket::IceControllerFactoryArgs& args) {}
~MockIceController() override = default;
MOCK_METHOD(void, SetIceConfig, (const cricket::IceConfig&), (override));
MOCK_METHOD(void,
SetSelectedConnection,
(const cricket::Connection*),
(override));
MOCK_METHOD(void, AddConnection, (const cricket::Connection*), (override));
MOCK_METHOD(void,
OnConnectionDestroyed,
(const cricket::Connection*),
(override));
MOCK_METHOD(rtc::ArrayView<const cricket::Connection* const>,
GetConnections,
(),
(const, override));
MOCK_METHOD(rtc::ArrayView<const cricket::Connection*>,
connections,
(),
(const, override));
MOCK_METHOD(bool, HasPingableConnection, (), (const, override));
MOCK_METHOD(cricket::IceControllerInterface::PingResult,
SelectConnectionToPing,
(int64_t),
(override));
MOCK_METHOD(bool,
GetUseCandidateAttr,
(const cricket::Connection*,
cricket::NominationMode,
cricket::IceMode),
(const, override));
MOCK_METHOD(const cricket::Connection*,
FindNextPingableConnection,
(),
(override));
MOCK_METHOD(void,
MarkConnectionPinged,
(const cricket::Connection*),
(override));
MOCK_METHOD(cricket::IceControllerInterface::SwitchResult,
ShouldSwitchConnection,
(cricket::IceSwitchReason, const cricket::Connection*),
(override));
MOCK_METHOD(cricket::IceControllerInterface::SwitchResult,
SortAndSwitchConnection,
(cricket::IceSwitchReason),
(override));
MOCK_METHOD(std::vector<const cricket::Connection*>,
PruneConnections,
(),
(override));
};
class MockIceControllerFactory : public cricket::IceControllerFactoryInterface {
public:
~MockIceControllerFactory() override = default;
std::unique_ptr<cricket::IceControllerInterface> Create(
const cricket::IceControllerFactoryArgs& args) override {
RecordIceControllerCreated();
return std::make_unique<MockIceController>(args);
}
MOCK_METHOD(void, RecordIceControllerCreated, ());
};
} // namespace cricket
#endif // P2P_BASE_MOCK_ICE_CONTROLLER_H_

View file

@ -0,0 +1,90 @@
/*
* Copyright 2016 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.
*/
#ifndef P2P_BASE_MOCK_ICE_TRANSPORT_H_
#define P2P_BASE_MOCK_ICE_TRANSPORT_H_
#include <memory>
#include <string>
#include <vector>
#include "p2p/base/ice_transport_internal.h"
#include "rtc_base/gunit.h"
#include "test/gmock.h"
using ::testing::_;
using ::testing::Return;
namespace cricket {
// Used in Chromium/remoting/protocol/channel_socket_adapter_unittest.cc
class MockIceTransport : public IceTransportInternal {
public:
MockIceTransport() {
SignalReadyToSend(this);
SignalWritableState(this);
}
MOCK_METHOD(int,
SendPacket,
(const char* data,
size_t len,
const rtc::PacketOptions& options,
int flags),
(override));
MOCK_METHOD(int, SetOption, (rtc::Socket::Option opt, int value), (override));
MOCK_METHOD(int, GetError, (), (override));
MOCK_METHOD(cricket::IceRole, GetIceRole, (), (const, override));
MOCK_METHOD(bool,
GetStats,
(cricket::IceTransportStats * ice_transport_stats),
(override));
IceTransportState GetState() const override {
return IceTransportState::STATE_INIT;
}
webrtc::IceTransportState GetIceTransportState() const override {
return webrtc::IceTransportState::kNew;
}
const std::string& transport_name() const override { return transport_name_; }
int component() const override { return 0; }
void SetIceRole(IceRole role) override {}
void SetIceTiebreaker(uint64_t tiebreaker) override {}
// The ufrag and pwd in `ice_params` must be set
// before candidate gathering can start.
void SetIceParameters(const IceParameters& ice_params) override {}
void SetRemoteIceParameters(const IceParameters& ice_params) override {}
void SetRemoteIceMode(IceMode mode) override {}
void SetIceConfig(const IceConfig& config) override {}
absl::optional<int> GetRttEstimate() override { return absl::nullopt; }
const Connection* selected_connection() const override { return nullptr; }
absl::optional<const CandidatePair> GetSelectedCandidatePair()
const override {
return absl::nullopt;
}
void MaybeStartGathering() override {}
void AddRemoteCandidate(const Candidate& candidate) override {}
void RemoveRemoteCandidate(const Candidate& candidate) override {}
void RemoveAllRemoteCandidates() override {}
IceGatheringState gathering_state() const override {
return IceGatheringState::kIceGatheringComplete;
}
bool receiving() const override { return true; }
bool writable() const override { return true; }
private:
std::string transport_name_;
};
} // namespace cricket
#endif // P2P_BASE_MOCK_ICE_TRANSPORT_H_

View file

@ -0,0 +1,75 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "p2p/base/p2p_constants.h"
namespace cricket {
const char CN_AUDIO[] = "audio";
const char CN_VIDEO[] = "video";
const char CN_DATA[] = "data";
const char CN_OTHER[] = "main";
const char GROUP_TYPE_BUNDLE[] = "BUNDLE";
// Minimum ufrag length is 4 characters as per RFC5245.
const int ICE_UFRAG_LENGTH = 4;
// Minimum password length of 22 characters as per RFC5245. We chose 24 because
// some internal systems expect password to be multiple of 4.
const int ICE_PWD_LENGTH = 24;
const size_t ICE_UFRAG_MIN_LENGTH = 4;
const size_t ICE_PWD_MIN_LENGTH = 22;
const size_t ICE_UFRAG_MAX_LENGTH = 256;
const size_t ICE_PWD_MAX_LENGTH = 256;
// This is media-specific, so might belong
// somewhere like media/base/mediaconstants.h
const int ICE_CANDIDATE_COMPONENT_RTP = 1;
const int ICE_CANDIDATE_COMPONENT_RTCP = 2;
const int ICE_CANDIDATE_COMPONENT_DEFAULT = 1;
// From RFC 4145, SDP setup attribute values.
const char CONNECTIONROLE_ACTIVE_STR[] = "active";
const char CONNECTIONROLE_PASSIVE_STR[] = "passive";
const char CONNECTIONROLE_ACTPASS_STR[] = "actpass";
const char CONNECTIONROLE_HOLDCONN_STR[] = "holdconn";
const char LOCAL_TLD[] = ".local";
const int MIN_CHECK_RECEIVING_INTERVAL = 50;
const int RECEIVING_TIMEOUT = MIN_CHECK_RECEIVING_INTERVAL * 50;
const int RECEIVING_SWITCHING_DELAY = 1000;
const int BACKUP_CONNECTION_PING_INTERVAL = 25 * 1000;
const int REGATHER_ON_FAILED_NETWORKS_INTERVAL = 5 * 60 * 1000;
// When the socket is unwritable, we will use 10 Kbps (ignoring IP+UDP headers)
// for pinging. When the socket is writable, we will use only 1 Kbps because we
// don't want to degrade the quality on a modem. These numbers should work well
// on a 28.8K modem, which is the slowest connection on which the voice quality
// is reasonable at all.
const int STUN_PING_PACKET_SIZE = 60 * 8;
const int STRONG_PING_INTERVAL = 1000 * STUN_PING_PACKET_SIZE / 1000; // 480ms.
const int WEAK_PING_INTERVAL = 1000 * STUN_PING_PACKET_SIZE / 10000; // 48ms.
const int WEAK_OR_STABILIZING_WRITABLE_CONNECTION_PING_INTERVAL = 900;
const int STRONG_AND_STABLE_WRITABLE_CONNECTION_PING_INTERVAL = 2500;
const int CONNECTION_WRITE_CONNECT_TIMEOUT = 5 * 1000; // 5 seconds
const uint32_t CONNECTION_WRITE_CONNECT_FAILURES = 5; // 5 pings
const int STUN_KEEPALIVE_INTERVAL = 10 * 1000; // 10 seconds
const int MIN_CONNECTION_LIFETIME = 10 * 1000; // 10 seconds.
const int DEAD_CONNECTION_RECEIVE_TIMEOUT = 30 * 1000; // 30 seconds.
const int WEAK_CONNECTION_RECEIVE_TIMEOUT = 2500; // 2.5 seconds
const int CONNECTION_WRITE_TIMEOUT = 15 * 1000; // 15 seconds
// There is no harm to keep this value high other than a small amount
// of increased memory, but in some networks (2G), we observe up to 60s RTTs.
const int CONNECTION_RESPONSE_TIMEOUT = 60 * 1000; // 60 seconds
} // namespace cricket

View file

@ -0,0 +1,129 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef P2P_BASE_P2P_CONSTANTS_H_
#define P2P_BASE_P2P_CONSTANTS_H_
#include <stddef.h>
#include <stdint.h>
#include "rtc_base/system/rtc_export.h"
namespace cricket {
// CN_ == "content name". When we initiate a session, we choose the
// name, and when we receive a Gingle session, we provide default
// names (since Gingle has no content names). But when we receive a
// Jingle call, the content name can be anything, so don't rely on
// these values being the same as the ones received.
extern const char CN_AUDIO[];
extern const char CN_VIDEO[];
extern const char CN_DATA[];
extern const char CN_OTHER[];
// GN stands for group name
extern const char GROUP_TYPE_BUNDLE[];
RTC_EXPORT extern const int ICE_UFRAG_LENGTH;
RTC_EXPORT extern const int ICE_PWD_LENGTH;
extern const size_t ICE_UFRAG_MIN_LENGTH;
extern const size_t ICE_PWD_MIN_LENGTH;
extern const size_t ICE_UFRAG_MAX_LENGTH;
extern const size_t ICE_PWD_MAX_LENGTH;
RTC_EXPORT extern const int ICE_CANDIDATE_COMPONENT_RTP;
RTC_EXPORT extern const int ICE_CANDIDATE_COMPONENT_RTCP;
RTC_EXPORT extern const int ICE_CANDIDATE_COMPONENT_DEFAULT;
// RFC 4145, SDP setup attribute values.
extern const char CONNECTIONROLE_ACTIVE_STR[];
extern const char CONNECTIONROLE_PASSIVE_STR[];
extern const char CONNECTIONROLE_ACTPASS_STR[];
extern const char CONNECTIONROLE_HOLDCONN_STR[];
// RFC 6762, the .local pseudo-top-level domain used for mDNS names.
extern const char LOCAL_TLD[];
// Constants for time intervals are in milliseconds unless otherwise stated.
//
// Most of the following constants are the default values of IceConfig
// paramters. See IceConfig for detailed definition.
//
// Default value of IceConfig.receiving_timeout.
extern const int RECEIVING_TIMEOUT;
// Default value IceConfig.ice_check_min_interval.
extern const int MIN_CHECK_RECEIVING_INTERVAL;
// The next two ping intervals are at the ICE transport level.
//
// STRONG_PING_INTERVAL is applied when the selected connection is both
// writable and receiving.
//
// Default value of IceConfig.ice_check_interval_strong_connectivity.
extern const int STRONG_PING_INTERVAL;
// WEAK_PING_INTERVAL is applied when the selected connection is either
// not writable or not receiving.
//
// Defaul value of IceConfig.ice_check_interval_weak_connectivity.
extern const int WEAK_PING_INTERVAL;
// The next two ping intervals are at the candidate pair level.
//
// Writable candidate pairs are pinged at a slower rate once they are stabilized
// and the channel is strongly connected.
extern const int STRONG_AND_STABLE_WRITABLE_CONNECTION_PING_INTERVAL;
// Writable candidate pairs are pinged at a faster rate while the connections
// are stabilizing or the channel is weak.
extern const int WEAK_OR_STABILIZING_WRITABLE_CONNECTION_PING_INTERVAL;
// Default value of IceConfig.backup_connection_ping_interval
extern const int BACKUP_CONNECTION_PING_INTERVAL;
// Defualt value of IceConfig.receiving_switching_delay.
extern const int RECEIVING_SWITCHING_DELAY;
// Default value of IceConfig.regather_on_failed_networks_interval.
extern const int REGATHER_ON_FAILED_NETWORKS_INTERVAL;
// Default vaule of IceConfig.ice_unwritable_timeout.
extern const int CONNECTION_WRITE_CONNECT_TIMEOUT;
// Default vaule of IceConfig.ice_unwritable_min_checks.
extern const uint32_t CONNECTION_WRITE_CONNECT_FAILURES;
// Default value of IceConfig.ice_inactive_timeout;
extern const int CONNECTION_WRITE_TIMEOUT;
// Default value of IceConfig.stun_keepalive_interval;
extern const int STUN_KEEPALIVE_INTERVAL;
static const int MIN_PINGS_AT_WEAK_PING_INTERVAL = 3;
// The following constants are used at the candidate pair level to determine the
// state of a candidate pair.
//
// The timeout duration when a connection does not receive anything.
extern const int WEAK_CONNECTION_RECEIVE_TIMEOUT;
// A connection will be declared dead if it has not received anything for this
// long.
extern const int DEAD_CONNECTION_RECEIVE_TIMEOUT;
// This is the length of time that we wait for a ping response to come back.
extern const int CONNECTION_RESPONSE_TIMEOUT;
// The minimum time we will wait before destroying a connection after creating
// it.
extern const int MIN_CONNECTION_LIFETIME;
// The type preference MUST be an integer from 0 to 126 inclusive.
// https://datatracker.ietf.org/doc/html/rfc5245#section-4.1.2.1
enum IcePriorityValue : uint8_t {
ICE_TYPE_PREFERENCE_RELAY_TLS = 0,
ICE_TYPE_PREFERENCE_RELAY_TCP = 1,
ICE_TYPE_PREFERENCE_RELAY_UDP = 2,
ICE_TYPE_PREFERENCE_PRFLX_TCP = 80,
ICE_TYPE_PREFERENCE_HOST_TCP = 90,
ICE_TYPE_PREFERENCE_SRFLX = 100,
ICE_TYPE_PREFERENCE_PRFLX = 110,
ICE_TYPE_PREFERENCE_HOST = 126
};
} // namespace cricket
#endif // P2P_BASE_P2P_CONSTANTS_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,520 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
// P2PTransportChannel wraps up the state management of the connection between
// two P2P clients. Clients have candidate ports for connecting, and
// connections which are combinations of candidates from each end (Alice and
// Bob each have candidates, one candidate from Alice and one candidate from
// Bob are used to make a connection, repeat to make many connections).
//
// When all of the available connections become invalid (non-writable), we
// kick off a process of determining more candidates and more connections.
//
#ifndef P2P_BASE_P2P_TRANSPORT_CHANNEL_H_
#define P2P_BASE_P2P_TRANSPORT_CHANNEL_H_
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "absl/base/attributes.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "api/array_view.h"
#include "api/async_dns_resolver.h"
#include "api/candidate.h"
#include "api/ice_transport_interface.h"
#include "api/rtc_error.h"
#include "api/sequence_checker.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "api/transport/enums.h"
#include "api/transport/stun.h"
#include "logging/rtc_event_log/events/rtc_event_ice_candidate_pair_config.h"
#include "logging/rtc_event_log/ice_logger.h"
#include "p2p/base/active_ice_controller_factory_interface.h"
#include "p2p/base/candidate_pair_interface.h"
#include "p2p/base/connection.h"
#include "p2p/base/ice_agent_interface.h"
#include "p2p/base/ice_controller_factory_interface.h"
#include "p2p/base/ice_controller_interface.h"
#include "p2p/base/ice_switch_reason.h"
#include "p2p/base/ice_transport_internal.h"
#include "p2p/base/p2p_constants.h"
#include "p2p/base/p2p_transport_channel_ice_field_trials.h"
#include "p2p/base/port.h"
#include "p2p/base/port_allocator.h"
#include "p2p/base/port_interface.h"
#include "p2p/base/regathering_controller.h"
#include "p2p/base/stun_dictionary.h"
#include "p2p/base/transport_description.h"
#include "rtc_base/async_packet_socket.h"
#include "rtc_base/checks.h"
#include "rtc_base/dscp.h"
#include "rtc_base/network/sent_packet.h"
#include "rtc_base/network_route.h"
#include "rtc_base/socket.h"
#include "rtc_base/socket_address.h"
#include "rtc_base/strings/string_builder.h"
#include "rtc_base/system/rtc_export.h"
#include "rtc_base/thread.h"
#include "rtc_base/thread_annotations.h"
namespace webrtc {
class RtcEventLog;
} // namespace webrtc
namespace cricket {
bool IceCredentialsChanged(absl::string_view old_ufrag,
absl::string_view old_pwd,
absl::string_view new_ufrag,
absl::string_view new_pwd);
// Adds the port on which the candidate originated.
class RemoteCandidate : public Candidate {
public:
RemoteCandidate(const Candidate& c, PortInterface* origin_port)
: Candidate(c), origin_port_(origin_port) {}
PortInterface* origin_port() { return origin_port_; }
private:
PortInterface* origin_port_;
};
// P2PTransportChannel manages the candidates and connection process to keep
// two P2P clients connected to each other.
class RTC_EXPORT P2PTransportChannel : public IceTransportInternal,
public IceAgentInterface {
public:
static std::unique_ptr<P2PTransportChannel> Create(
absl::string_view transport_name,
int component,
webrtc::IceTransportInit init);
// For testing only.
// TODO(zstein): Remove once AsyncDnsResolverFactory is required.
P2PTransportChannel(absl::string_view transport_name,
int component,
PortAllocator* allocator,
const webrtc::FieldTrialsView* field_trials = nullptr);
~P2PTransportChannel() override;
P2PTransportChannel(const P2PTransportChannel&) = delete;
P2PTransportChannel& operator=(const P2PTransportChannel&) = delete;
// From TransportChannelImpl:
IceTransportState GetState() const override;
webrtc::IceTransportState GetIceTransportState() const override;
const std::string& transport_name() const override;
int component() const override;
bool writable() const override;
bool receiving() const override;
void SetIceRole(IceRole role) override;
IceRole GetIceRole() const override;
void SetIceTiebreaker(uint64_t tiebreaker) override;
void SetIceParameters(const IceParameters& ice_params) override;
void SetRemoteIceParameters(const IceParameters& ice_params) override;
void SetRemoteIceMode(IceMode mode) override;
// TODO(deadbeef): Deprecated. Remove when Chromium's
// IceTransportChannel does not depend on this.
void Connect() {}
void MaybeStartGathering() override;
IceGatheringState gathering_state() const override;
void ResolveHostnameCandidate(const Candidate& candidate);
void AddRemoteCandidate(const Candidate& candidate) override;
void RemoveRemoteCandidate(const Candidate& candidate) override;
void RemoveAllRemoteCandidates() override;
// Sets the parameters in IceConfig. We do not set them blindly. Instead, we
// only update the parameter if it is considered set in `config`. For example,
// a negative value of receiving_timeout will be considered "not set" and we
// will not use it to update the respective parameter in `config_`.
// TODO(deadbeef): Use absl::optional instead of negative values.
void SetIceConfig(const IceConfig& config) override;
const IceConfig& config() const;
static webrtc::RTCError ValidateIceConfig(const IceConfig& config);
// From TransportChannel:
int SendPacket(const char* data,
size_t len,
const rtc::PacketOptions& options,
int flags) override;
int SetOption(rtc::Socket::Option opt, int value) override;
bool GetOption(rtc::Socket::Option opt, int* value) override;
int GetError() override;
bool GetStats(IceTransportStats* ice_transport_stats) override;
absl::optional<int> GetRttEstimate() override;
const Connection* selected_connection() const override;
absl::optional<const CandidatePair> GetSelectedCandidatePair() const override;
// From IceAgentInterface
void OnStartedPinging() override;
int64_t GetLastPingSentMs() const override;
void UpdateConnectionStates() override;
void UpdateState() override;
void SendPingRequest(const Connection* connection) override;
void SwitchSelectedConnection(const Connection* connection,
IceSwitchReason reason) override;
void ForgetLearnedStateForConnections(
rtc::ArrayView<const Connection* const> connections) override;
bool PruneConnections(
rtc::ArrayView<const Connection* const> connections) override;
// TODO(honghaiz): Remove this method once the reference of it in
// Chromoting is removed.
const Connection* best_connection() const {
RTC_DCHECK_RUN_ON(network_thread_);
return selected_connection_;
}
void set_incoming_only(bool value) {
RTC_DCHECK_RUN_ON(network_thread_);
incoming_only_ = value;
}
// Note: These are only for testing purpose.
// `ports_` and `pruned_ports` should not be changed from outside.
const std::vector<PortInterface*>& ports() {
RTC_DCHECK_RUN_ON(network_thread_);
return ports_;
}
const std::vector<PortInterface*>& pruned_ports() {
RTC_DCHECK_RUN_ON(network_thread_);
return pruned_ports_;
}
IceMode remote_ice_mode() const {
RTC_DCHECK_RUN_ON(network_thread_);
return remote_ice_mode_;
}
void PruneAllPorts();
int check_receiving_interval() const;
absl::optional<rtc::NetworkRoute> network_route() const override;
void RemoveConnection(Connection* connection);
// Helper method used only in unittest.
rtc::DiffServCodePoint DefaultDscpValue() const;
// Public for unit tests.
Connection* FindNextPingableConnection();
void MarkConnectionPinged(Connection* conn);
// Public for unit tests.
rtc::ArrayView<Connection* const> connections() const;
void RemoveConnectionForTest(Connection* connection);
// Public for unit tests.
PortAllocatorSession* allocator_session() const {
RTC_DCHECK_RUN_ON(network_thread_);
if (allocator_sessions_.empty()) {
return nullptr;
}
return allocator_sessions_.back().get();
}
// Public for unit tests.
const std::vector<RemoteCandidate>& remote_candidates() const {
RTC_DCHECK_RUN_ON(network_thread_);
return remote_candidates_;
}
std::string ToString() const {
RTC_DCHECK_RUN_ON(network_thread_);
const std::string RECEIVING_ABBREV[2] = {"_", "R"};
const std::string WRITABLE_ABBREV[2] = {"_", "W"};
rtc::StringBuilder ss;
ss << "Channel[" << transport_name_ << "|" << component_ << "|"
<< RECEIVING_ABBREV[receiving_] << WRITABLE_ABBREV[writable_] << "]";
return ss.Release();
}
absl::optional<std::reference_wrapper<StunDictionaryWriter>>
GetDictionaryWriter() override {
return stun_dict_writer_;
}
private:
P2PTransportChannel(
absl::string_view transport_name,
int component,
PortAllocator* allocator,
// DNS resolver factory
webrtc::AsyncDnsResolverFactoryInterface* async_dns_resolver_factory,
// If the P2PTransportChannel has to delete the DNS resolver factory
// on release, this pointer is set.
std::unique_ptr<webrtc::AsyncDnsResolverFactoryInterface>
owned_dns_resolver_factory,
webrtc::RtcEventLog* event_log,
IceControllerFactoryInterface* ice_controller_factory,
ActiveIceControllerFactoryInterface* active_ice_controller_factory,
const webrtc::FieldTrialsView* field_trials);
bool IsGettingPorts() {
RTC_DCHECK_RUN_ON(network_thread_);
return allocator_session()->IsGettingPorts();
}
// Returns true if it's possible to send packets on `connection`.
bool ReadyToSend(const Connection* connection) const;
bool PresumedWritable(const Connection* conn) const;
void SendPingRequestInternal(Connection* connection);
rtc::NetworkRoute ConfigureNetworkRoute(const Connection* conn);
void SwitchSelectedConnectionInternal(Connection* conn,
IceSwitchReason reason);
void UpdateTransportState();
void HandleAllTimedOut();
void MaybeStopPortAllocatorSessions();
void OnSelectedConnectionDestroyed() RTC_RUN_ON(network_thread_);
// ComputeIceTransportState computes the RTCIceTransportState as described in
// https://w3c.github.io/webrtc-pc/#dom-rtcicetransportstate. ComputeState
// computes the value we currently export as RTCIceTransportState.
// TODO(bugs.webrtc.org/9308): Remove ComputeState once it's no longer used.
IceTransportState ComputeState() const;
webrtc::IceTransportState ComputeIceTransportState() const;
bool CreateConnections(const Candidate& remote_candidate,
PortInterface* origin_port);
bool CreateConnection(PortInterface* port,
const Candidate& remote_candidate,
PortInterface* origin_port);
bool FindConnection(const Connection* connection) const;
uint32_t GetRemoteCandidateGeneration(const Candidate& candidate);
bool IsDuplicateRemoteCandidate(const Candidate& candidate);
void RememberRemoteCandidate(const Candidate& remote_candidate,
PortInterface* origin_port);
void PingConnection(Connection* conn);
void AddAllocatorSession(std::unique_ptr<PortAllocatorSession> session);
void AddConnection(Connection* connection);
void OnPortReady(PortAllocatorSession* session, PortInterface* port);
void OnPortsPruned(PortAllocatorSession* session,
const std::vector<PortInterface*>& ports);
void OnCandidatesReady(PortAllocatorSession* session,
const std::vector<Candidate>& candidates);
void OnCandidateError(PortAllocatorSession* session,
const IceCandidateErrorEvent& event);
void OnCandidatesRemoved(PortAllocatorSession* session,
const std::vector<Candidate>& candidates);
void OnCandidatesAllocationDone(PortAllocatorSession* session);
void OnUnknownAddress(PortInterface* port,
const rtc::SocketAddress& addr,
ProtocolType proto,
IceMessage* stun_msg,
const std::string& remote_username,
bool port_muxed);
void OnCandidateFilterChanged(uint32_t prev_filter, uint32_t cur_filter);
// When a port is destroyed, remove it from both lists `ports_`
// and `pruned_ports_`.
void OnPortDestroyed(PortInterface* port);
// When pruning a port, move it from `ports_` to `pruned_ports_`.
// Returns true if the port is found and removed from `ports_`.
bool PrunePort(PortInterface* port);
void OnRoleConflict(PortInterface* port);
void OnConnectionStateChange(Connection* connection);
void OnReadPacket(Connection* connection, const rtc::ReceivedPacket& packet);
void OnSentPacket(const rtc::SentPacket& sent_packet);
void OnReadyToSend(Connection* connection);
void OnConnectionDestroyed(Connection* connection);
void OnNominated(Connection* conn);
void LogCandidatePairConfig(Connection* conn,
webrtc::IceCandidatePairConfigType type);
uint32_t GetNominationAttr(Connection* conn) const;
bool GetUseCandidateAttr(Connection* conn) const;
bool AllowedToPruneConnections() const;
// Returns the latest remote ICE parameters or nullptr if there are no remote
// ICE parameters yet.
IceParameters* remote_ice() {
RTC_DCHECK_RUN_ON(network_thread_);
return remote_ice_parameters_.empty() ? nullptr
: &remote_ice_parameters_.back();
}
// Returns the remote IceParameters and generation that match `ufrag`
// if found, and returns nullptr otherwise.
const IceParameters* FindRemoteIceFromUfrag(absl::string_view ufrag,
uint32_t* generation);
// Returns the index of the latest remote ICE parameters, or 0 if no remote
// ICE parameters have been received.
uint32_t remote_ice_generation() {
RTC_DCHECK_RUN_ON(network_thread_);
return remote_ice_parameters_.empty()
? 0
: static_cast<uint32_t>(remote_ice_parameters_.size() - 1);
}
// Indicates if the given local port has been pruned.
bool IsPortPruned(const PortInterface* port) const;
// Indicates if the given remote candidate has been pruned.
bool IsRemoteCandidatePruned(const Candidate& cand) const;
// Sets the writable state, signaling if necessary.
void SetWritable(bool writable);
// Sets the receiving state, signaling if necessary.
void SetReceiving(bool receiving);
// Clears the address and the related address fields of a local candidate to
// avoid IP leakage. This is applicable in several scenarios as commented in
// `PortAllocator::SanitizeCandidate`.
Candidate SanitizeLocalCandidate(const Candidate& c) const;
// Clears the address field of a remote candidate to avoid IP leakage. This is
// applicable in the following scenarios:
// 1. mDNS candidates are received.
// 2. Peer-reflexive remote candidates.
Candidate SanitizeRemoteCandidate(const Candidate& c) const;
// Cast a Connection returned from IceController and verify that it exists.
// (P2P owns all Connections, and only gives const pointers to IceController,
// see IceControllerInterface).
Connection* FromIceController(const Connection* conn) {
// Verify that IceController does not return a connection
// that we have destroyed.
RTC_DCHECK(FindConnection(conn));
return const_cast<Connection*>(conn);
}
int64_t ComputeEstimatedDisconnectedTimeMs(int64_t now,
Connection* old_connection);
void ParseFieldTrials(const webrtc::FieldTrialsView* field_trials);
std::string transport_name_ RTC_GUARDED_BY(network_thread_);
int component_ RTC_GUARDED_BY(network_thread_);
PortAllocator* allocator_ RTC_GUARDED_BY(network_thread_);
webrtc::AsyncDnsResolverFactoryInterface* const async_dns_resolver_factory_
RTC_GUARDED_BY(network_thread_);
const std::unique_ptr<webrtc::AsyncDnsResolverFactoryInterface>
owned_dns_resolver_factory_;
rtc::Thread* const network_thread_;
bool incoming_only_ RTC_GUARDED_BY(network_thread_);
int error_ RTC_GUARDED_BY(network_thread_);
std::vector<std::unique_ptr<PortAllocatorSession>> allocator_sessions_
RTC_GUARDED_BY(network_thread_);
// `ports_` contains ports that are used to form new connections when
// new remote candidates are added.
std::vector<PortInterface*> ports_ RTC_GUARDED_BY(network_thread_);
// `pruned_ports_` contains ports that have been removed from `ports_` and
// are not being used to form new connections, but that aren't yet destroyed.
// They may have existing connections, and they still fire signals such as
// SignalUnknownAddress.
std::vector<PortInterface*> pruned_ports_ RTC_GUARDED_BY(network_thread_);
Connection* selected_connection_ RTC_GUARDED_BY(network_thread_) = nullptr;
std::vector<Connection*> connections_ RTC_GUARDED_BY(network_thread_);
std::vector<RemoteCandidate> remote_candidates_
RTC_GUARDED_BY(network_thread_);
bool had_connection_ RTC_GUARDED_BY(network_thread_) =
false; // if connections_ has ever been nonempty
typedef std::map<rtc::Socket::Option, int> OptionMap;
OptionMap options_ RTC_GUARDED_BY(network_thread_);
IceParameters ice_parameters_ RTC_GUARDED_BY(network_thread_);
std::vector<IceParameters> remote_ice_parameters_
RTC_GUARDED_BY(network_thread_);
IceMode remote_ice_mode_ RTC_GUARDED_BY(network_thread_);
IceRole ice_role_ RTC_GUARDED_BY(network_thread_);
uint64_t ice_tiebreaker_ RTC_GUARDED_BY(network_thread_);
IceGatheringState gathering_state_ RTC_GUARDED_BY(network_thread_);
std::unique_ptr<webrtc::BasicRegatheringController> regathering_controller_
RTC_GUARDED_BY(network_thread_);
int64_t last_ping_sent_ms_ RTC_GUARDED_BY(network_thread_) = 0;
int weak_ping_interval_ RTC_GUARDED_BY(network_thread_) = WEAK_PING_INTERVAL;
// TODO(jonasolsson): Remove state_ and rename standardized_state_ once state_
// is no longer used to compute the ICE connection state.
IceTransportState state_ RTC_GUARDED_BY(network_thread_) =
IceTransportState::STATE_INIT;
webrtc::IceTransportState standardized_state_
RTC_GUARDED_BY(network_thread_) = webrtc::IceTransportState::kNew;
IceConfig config_ RTC_GUARDED_BY(network_thread_);
int last_sent_packet_id_ RTC_GUARDED_BY(network_thread_) =
-1; // -1 indicates no packet was sent before.
// The value put in the "nomination" attribute for the next nominated
// connection. A zero-value indicates the connection will not be nominated.
uint32_t nomination_ RTC_GUARDED_BY(network_thread_) = 0;
bool receiving_ RTC_GUARDED_BY(network_thread_) = false;
bool writable_ RTC_GUARDED_BY(network_thread_) = false;
bool has_been_writable_ RTC_GUARDED_BY(network_thread_) =
false; // if writable_ has ever been true
absl::optional<rtc::NetworkRoute> network_route_
RTC_GUARDED_BY(network_thread_);
webrtc::IceEventLog ice_event_log_ RTC_GUARDED_BY(network_thread_);
std::unique_ptr<ActiveIceControllerInterface> ice_controller_
RTC_GUARDED_BY(network_thread_);
struct CandidateAndResolver final {
CandidateAndResolver(
const Candidate& candidate,
std::unique_ptr<webrtc::AsyncDnsResolverInterface>&& resolver);
~CandidateAndResolver();
// Moveable, but not copyable.
CandidateAndResolver(CandidateAndResolver&&) = default;
CandidateAndResolver& operator=(CandidateAndResolver&&) = default;
Candidate candidate_;
std::unique_ptr<webrtc::AsyncDnsResolverInterface> resolver_;
};
std::vector<CandidateAndResolver> resolvers_ RTC_GUARDED_BY(network_thread_);
void FinishAddingRemoteCandidate(const Candidate& new_remote_candidate);
void OnCandidateResolved(webrtc::AsyncDnsResolverInterface* resolver);
void AddRemoteCandidateWithResult(
Candidate candidate,
const webrtc::AsyncDnsResolverResult& result);
std::unique_ptr<StunAttribute> GoogDeltaReceived(
const StunByteStringAttribute*);
void GoogDeltaAckReceived(webrtc::RTCErrorOr<const StunUInt64Attribute*>);
// Bytes/packets sent/received on this channel.
uint64_t bytes_sent_ = 0;
uint64_t bytes_received_ = 0;
uint64_t packets_sent_ = 0;
uint64_t packets_received_ = 0;
// Number of times the selected_connection_ has been modified.
uint32_t selected_candidate_pair_changes_ = 0;
// When was last data received on a existing connection,
// from connection->last_data_received() that uses rtc::TimeMillis().
int64_t last_data_received_ms_ = 0;
// Parsed field trials.
IceFieldTrials ice_field_trials_;
// A dictionary of attributes that will be reflected to peer.
StunDictionaryWriter stun_dict_writer_;
// A dictionary that tracks attributes from peer.
StunDictionaryView stun_dict_view_;
};
} // namespace cricket
#endif // P2P_BASE_P2P_TRANSPORT_CHANNEL_H_

View file

@ -0,0 +1,81 @@
/*
* Copyright 2019 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.
*/
#ifndef P2P_BASE_P2P_TRANSPORT_CHANNEL_ICE_FIELD_TRIALS_H_
#define P2P_BASE_P2P_TRANSPORT_CHANNEL_ICE_FIELD_TRIALS_H_
#include "absl/types/optional.h"
namespace cricket {
// Field trials for P2PTransportChannel and friends,
// put in separate file so that they can be shared e.g
// with Connection.
struct IceFieldTrials {
// This struct is built using the FieldTrialParser, and then not modified.
// TODO(jonaso) : Consider how members of this struct can be made const.
bool skip_relay_to_non_relay_connections = false;
absl::optional<int> max_outstanding_pings;
// Wait X ms before selecting a connection when having none.
// This will make media slower, but will give us chance to find
// a better connection before starting.
absl::optional<int> initial_select_dampening;
// If the connection has recevied a ping-request, delay by
// maximum this delay. This will make media slower, but will
// give us chance to find a better connection before starting.
absl::optional<int> initial_select_dampening_ping_received;
// Announce GOOG_PING support in STUN_BINDING_RESPONSE if requested
// by peer.
bool announce_goog_ping = true;
// Enable sending GOOG_PING if remote announce it.
bool enable_goog_ping = false;
// Decay rate for RTT estimate using EventBasedExponentialMovingAverage
// expressed as halving time.
int rtt_estimate_halftime_ms = 500;
// Sending a PING directly after a switch on ICE_CONTROLLING-side.
// TODO(jonaso) : Deprecate this in favor of
// `send_ping_on_selected_ice_controlling`.
bool send_ping_on_switch_ice_controlling = false;
// Sending a PING directly after selecting a connection
// (i.e either a switch or the inital selection).
bool send_ping_on_selected_ice_controlling = false;
// Sending a PING directly after a nomination on ICE_CONTROLLED-side.
bool send_ping_on_nomination_ice_controlled = false;
// The timeout after which the connection will be considered dead if no
// traffic is received.
int dead_connection_timeout_ms = 30000;
// Stop gathering when having a strong connection.
bool stop_gather_on_strongly_connected = true;
// DSCP taging.
absl::optional<int> override_dscp;
bool piggyback_ice_check_acknowledgement = false;
bool extra_ice_ping = false;
// Announce/enable GOOG_DELTA
bool enable_goog_delta = true; // send GOOG DELTA
bool answer_goog_delta = true; // answer GOOG DELTA
};
} // namespace cricket
#endif // P2P_BASE_P2P_TRANSPORT_CHANNEL_ICE_FIELD_TRIALS_H_

View file

@ -0,0 +1,27 @@
/*
* Copyright 2017 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/packet_transport_internal.h"
namespace rtc {
PacketTransportInternal::PacketTransportInternal() = default;
PacketTransportInternal::~PacketTransportInternal() = default;
bool PacketTransportInternal::GetOption(rtc::Socket::Option opt, int* value) {
return false;
}
absl::optional<NetworkRoute> PacketTransportInternal::network_route() const {
return absl::optional<NetworkRoute>();
}
} // namespace rtc

View file

@ -0,0 +1,108 @@
/*
* Copyright 2017 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.
*/
#ifndef P2P_BASE_PACKET_TRANSPORT_INTERNAL_H_
#define P2P_BASE_PACKET_TRANSPORT_INTERNAL_H_
#include <string>
#include <vector>
#include "absl/types/optional.h"
#include "p2p/base/port.h"
#include "rtc_base/async_packet_socket.h"
#include "rtc_base/network_route.h"
#include "rtc_base/socket.h"
#include "rtc_base/system/rtc_export.h"
#include "rtc_base/third_party/sigslot/sigslot.h"
namespace rtc {
struct PacketOptions;
struct SentPacket;
class RTC_EXPORT PacketTransportInternal : public sigslot::has_slots<> {
public:
virtual const std::string& transport_name() const = 0;
// The transport has been established.
virtual bool writable() const = 0;
// The transport has received a packet in the last X milliseconds, here X is
// configured by each implementation.
virtual bool receiving() const = 0;
// Attempts to send the given packet.
// The return value is < 0 on failure. The return value in failure case is not
// descriptive. Depending on failure cause and implementation details
// GetError() returns an descriptive errno.h error value.
// This mimics posix socket send() or sendto() behavior.
// TODO(johan): Reliable, meaningful, consistent error codes for all
// implementations would be nice.
// TODO(johan): Remove the default argument once channel code is updated.
virtual int SendPacket(const char* data,
size_t len,
const rtc::PacketOptions& options,
int flags = 0) = 0;
// Sets a socket option. Note that not all options are
// supported by all transport types.
virtual int SetOption(rtc::Socket::Option opt, int value) = 0;
// TODO(pthatcher): Once Chrome's MockPacketTransportInterface implements
// this, remove the default implementation.
virtual bool GetOption(rtc::Socket::Option opt, int* value);
// Returns the most recent error that occurred on this channel.
virtual int GetError() = 0;
// Returns the current network route with transport overhead.
// TODO(zhihuang): Make it pure virtual once the Chrome/remoting is updated.
virtual absl::optional<NetworkRoute> network_route() const;
// Emitted when the writable state, represented by `writable()`, changes.
sigslot::signal1<PacketTransportInternal*> SignalWritableState;
// Emitted when the PacketTransportInternal is ready to send packets. "Ready
// to send" is more sensitive than the writable state; a transport may be
// writable, but temporarily not able to send packets. For example, the
// underlying transport's socket buffer may be full, as indicated by
// SendPacket's return code and/or GetError.
sigslot::signal1<PacketTransportInternal*> SignalReadyToSend;
// Emitted when receiving state changes to true.
sigslot::signal1<PacketTransportInternal*> SignalReceivingState;
// Signalled each time a packet is received on this channel.
sigslot::signal5<PacketTransportInternal*,
const char*,
size_t,
// TODO(bugs.webrtc.org/9584): Change to passing the int64_t
// timestamp by value.
const int64_t&,
int>
SignalReadPacket;
// Signalled each time a packet is sent on this channel.
sigslot::signal2<PacketTransportInternal*, const rtc::SentPacket&>
SignalSentPacket;
// Signalled when the current network route has changed.
sigslot::signal1<absl::optional<rtc::NetworkRoute>> SignalNetworkRouteChanged;
// Signalled when the transport is closed.
sigslot::signal1<PacketTransportInternal*> SignalClosed;
protected:
PacketTransportInternal();
~PacketTransportInternal() override;
};
} // namespace rtc
#endif // P2P_BASE_PACKET_TRANSPORT_INTERNAL_H_

View file

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

View file

@ -0,0 +1,536 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef P2P_BASE_PORT_H_
#define P2P_BASE_PORT_H_
#include <stddef.h>
#include <stdint.h>
#include <functional>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "absl/base/attributes.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "api/candidate.h"
#include "api/field_trials_view.h"
#include "api/packet_socket_factory.h"
#include "api/rtc_error.h"
#include "api/sequence_checker.h"
#include "api/task_queue/task_queue_base.h"
#include "api/transport/field_trial_based_config.h"
#include "api/transport/stun.h"
#include "logging/rtc_event_log/events/rtc_event_ice_candidate_pair.h"
#include "logging/rtc_event_log/events/rtc_event_ice_candidate_pair_config.h"
#include "logging/rtc_event_log/ice_logger.h"
#include "p2p/base/candidate_pair_interface.h"
#include "p2p/base/connection.h"
#include "p2p/base/connection_info.h"
#include "p2p/base/p2p_constants.h"
#include "p2p/base/port_interface.h"
#include "p2p/base/stun_request.h"
#include "p2p/base/transport_description.h"
#include "rtc_base/async_packet_socket.h"
#include "rtc_base/callback_list.h"
#include "rtc_base/checks.h"
#include "rtc_base/dscp.h"
#include "rtc_base/memory/always_valid_pointer.h"
#include "rtc_base/net_helper.h"
#include "rtc_base/network.h"
#include "rtc_base/network/received_packet.h"
#include "rtc_base/network/sent_packet.h"
#include "rtc_base/proxy_info.h"
#include "rtc_base/rate_tracker.h"
#include "rtc_base/socket_address.h"
#include "rtc_base/system/rtc_export.h"
#include "rtc_base/third_party/sigslot/sigslot.h"
#include "rtc_base/thread_annotations.h"
#include "rtc_base/weak_ptr.h"
namespace cricket {
// RFC 6544, TCP candidate encoding rules.
extern const int DISCARD_PORT;
extern const char TCPTYPE_ACTIVE_STR[];
extern const char TCPTYPE_PASSIVE_STR[];
extern const char TCPTYPE_SIMOPEN_STR[];
enum class MdnsNameRegistrationStatus {
// IP concealment with mDNS is not enabled or the name registration process is
// not started yet.
kNotStarted,
// A request to create and register an mDNS name for a local IP address of a
// host candidate is sent to the mDNS responder.
kInProgress,
// The name registration is complete and the created name is returned by the
// mDNS responder.
kCompleted,
};
// Stats that we can return about the port of a STUN candidate.
class StunStats {
public:
StunStats() = default;
StunStats(const StunStats&) = default;
~StunStats() = default;
StunStats& operator=(const StunStats& other) = default;
int stun_binding_requests_sent = 0;
int stun_binding_responses_received = 0;
double stun_binding_rtt_ms_total = 0;
double stun_binding_rtt_ms_squared_total = 0;
};
// Stats that we can return about a candidate.
class CandidateStats {
public:
CandidateStats() = default;
CandidateStats(const CandidateStats&) = default;
CandidateStats(CandidateStats&&) = default;
CandidateStats(Candidate candidate,
absl::optional<StunStats> stats = absl::nullopt)
: candidate_(std::move(candidate)), stun_stats_(std::move(stats)) {}
~CandidateStats() = default;
CandidateStats& operator=(const CandidateStats& other) = default;
const Candidate& candidate() const { return candidate_; }
const absl::optional<StunStats>& stun_stats() const { return stun_stats_; }
private:
Candidate candidate_;
// STUN port stats if this candidate is a STUN candidate.
absl::optional<StunStats> stun_stats_;
};
typedef std::vector<CandidateStats> CandidateStatsList;
const char* ProtoToString(ProtocolType proto);
absl::optional<ProtocolType> StringToProto(absl::string_view proto_name);
struct ProtocolAddress {
rtc::SocketAddress address;
ProtocolType proto;
ProtocolAddress(const rtc::SocketAddress& a, ProtocolType p)
: address(a), proto(p) {}
bool operator==(const ProtocolAddress& o) const {
return address == o.address && proto == o.proto;
}
bool operator!=(const ProtocolAddress& o) const { return !(*this == o); }
};
struct IceCandidateErrorEvent {
IceCandidateErrorEvent() = default;
IceCandidateErrorEvent(absl::string_view address,
int port,
absl::string_view url,
int error_code,
absl::string_view error_text)
: address(std::move(address)),
port(port),
url(std::move(url)),
error_code(error_code),
error_text(std::move(error_text)) {}
std::string address;
int port = 0;
std::string url;
int error_code = 0;
std::string error_text;
};
struct CandidatePairChangeEvent {
CandidatePair selected_candidate_pair;
int64_t last_data_received_ms;
std::string reason;
// How long do we estimate that we've been disconnected.
int64_t estimated_disconnected_time_ms;
};
typedef std::set<rtc::SocketAddress> ServerAddresses;
// Represents a local communication mechanism that can be used to create
// connections to similar mechanisms of the other client. Subclasses of this
// one add support for specific mechanisms like local UDP ports.
class RTC_EXPORT Port : public PortInterface, public sigslot::has_slots<> {
public:
// INIT: The state when a port is just created.
// KEEP_ALIVE_UNTIL_PRUNED: A port should not be destroyed even if no
// connection is using it.
// PRUNED: It will be destroyed if no connection is using it for a period of
// 30 seconds.
enum class State { INIT, KEEP_ALIVE_UNTIL_PRUNED, PRUNED };
Port(webrtc::TaskQueueBase* thread,
absl::string_view type ABSL_ATTRIBUTE_LIFETIME_BOUND,
rtc::PacketSocketFactory* factory,
const rtc::Network* network,
absl::string_view username_fragment,
absl::string_view password,
const webrtc::FieldTrialsView* field_trials = nullptr);
Port(webrtc::TaskQueueBase* thread,
absl::string_view type ABSL_ATTRIBUTE_LIFETIME_BOUND,
rtc::PacketSocketFactory* factory,
const rtc::Network* network,
uint16_t min_port,
uint16_t max_port,
absl::string_view username_fragment,
absl::string_view password,
const webrtc::FieldTrialsView* field_trials = nullptr);
~Port() override;
// Note that the port type does NOT uniquely identify different subclasses of
// Port. Use the 2-tuple of the port type AND the protocol (GetProtocol()) to
// uniquely identify subclasses. Whenever a new subclass of Port introduces a
// conflict in the value of the 2-tuple, make sure that the implementation
// that relies on this 2-tuple for RTTI is properly changed.
const absl::string_view Type() const override;
const rtc::Network* Network() const override;
// Methods to set/get ICE role and tiebreaker values.
IceRole GetIceRole() const override;
void SetIceRole(IceRole role) override;
void SetIceTiebreaker(uint64_t tiebreaker) override;
uint64_t IceTiebreaker() const override;
bool SharedSocket() const override;
void ResetSharedSocket() { shared_socket_ = false; }
// Should not destroy the port even if no connection is using it. Called when
// a port is ready to use.
void KeepAliveUntilPruned();
// Allows a port to be destroyed if no connection is using it.
void Prune();
// Call to stop any currently pending operations from running.
void CancelPendingTasks();
// The thread on which this port performs its I/O.
webrtc::TaskQueueBase* thread() override { return thread_; }
// The factory used to create the sockets of this port.
rtc::PacketSocketFactory* socket_factory() const override { return factory_; }
// For debugging purposes.
const std::string& content_name() const override { return content_name_; }
void set_content_name(absl::string_view content_name) {
content_name_ = std::string(content_name);
}
int component() const { return component_; }
void set_component(int component) { component_ = component; }
bool send_retransmit_count_attribute() const override {
return send_retransmit_count_attribute_;
}
void set_send_retransmit_count_attribute(bool enable) {
send_retransmit_count_attribute_ = enable;
}
// Identifies the generation that this port was created in.
uint32_t generation() const override { return generation_; }
void set_generation(uint32_t generation) override {
generation_ = generation;
}
const std::string& username_fragment() const;
const std::string& password() const { return password_; }
// May be called when this port was initially created by a pooled
// PortAllocatorSession, and is now being assigned to an ICE transport.
// Updates the information for candidates as well.
void SetIceParameters(int component,
absl::string_view username_fragment,
absl::string_view password);
// Fired when candidates are discovered by the port. When all candidates
// are discovered that belong to port SignalAddressReady is fired.
sigslot::signal2<Port*, const Candidate&> SignalCandidateReady;
// Provides all of the above information in one handy object.
const std::vector<Candidate>& Candidates() const override;
// Fired when candidate discovery failed using certain server.
sigslot::signal2<Port*, const IceCandidateErrorEvent&> SignalCandidateError;
// SignalPortComplete is sent when port completes the task of candidates
// allocation.
sigslot::signal1<Port*> SignalPortComplete;
// This signal sent when port fails to allocate candidates and this port
// can't be used in establishing the connections. When port is in shared mode
// and port fails to allocate one of the candidates, port shouldn't send
// this signal as other candidates might be usefull in establishing the
// connection.
sigslot::signal1<Port*> SignalPortError;
void SubscribePortDestroyed(
std::function<void(PortInterface*)> callback) override;
void SendPortDestroyed(Port* port);
// Returns a map containing all of the connections of this port, keyed by the
// remote address.
typedef std::map<rtc::SocketAddress, Connection*> AddressMap;
const AddressMap& connections() { return connections_; }
// Returns the connection to the given address or NULL if none exists.
Connection* GetConnection(const rtc::SocketAddress& remote_addr) override;
// Removes and deletes a connection object. `DestroyConnection` will
// delete the connection object directly whereas `DestroyConnectionAsync`
// defers the `delete` operation to when the call stack has been unwound.
// Async may be needed when deleting a connection object from within a
// callback.
void DestroyConnection(Connection* conn) override {
DestroyConnectionInternal(conn, false);
}
void DestroyConnectionAsync(Connection* conn) override {
DestroyConnectionInternal(conn, true);
}
// In a shared socket mode each port which shares the socket will decide
// to accept the packet based on the `remote_addr`. Currently only UDP
// port implemented this method.
// TODO(mallinath) - Make it pure virtual.
virtual bool HandleIncomingPacket(rtc::AsyncPacketSocket* socket,
const rtc::ReceivedPacket& packet);
// Shall the port handle packet from this `remote_addr`.
// This method is overridden by TurnPort.
virtual bool CanHandleIncomingPacketsFrom(
const rtc::SocketAddress& remote_addr) const;
// Sends a response error to the given request.
void SendBindingErrorResponse(StunMessage* message,
const rtc::SocketAddress& addr,
int error_code,
absl::string_view reason) override;
void SendUnknownAttributesErrorResponse(
StunMessage* message,
const rtc::SocketAddress& addr,
const std::vector<uint16_t>& unknown_types);
void set_proxy(absl::string_view user_agent, const rtc::ProxyInfo& proxy) {
user_agent_ = std::string(user_agent);
proxy_ = proxy;
}
const std::string& user_agent() override { return user_agent_; }
const rtc::ProxyInfo& proxy() override { return proxy_; }
void EnablePortPackets() override;
// Called if the port has no connections and is no longer useful.
void Destroy();
// Debugging description of this port
std::string ToString() const override;
uint16_t min_port() { return min_port_; }
uint16_t max_port() { return max_port_; }
// Timeout shortening function to speed up unit tests.
void set_timeout_delay(int delay);
// This method will return local and remote username fragements from the
// stun username attribute if present.
bool ParseStunUsername(const StunMessage* stun_msg,
std::string* local_username,
std::string* remote_username) const override;
std::string CreateStunUsername(
absl::string_view remote_username) const override;
bool MaybeIceRoleConflict(const rtc::SocketAddress& addr,
IceMessage* stun_msg,
absl::string_view remote_ufrag) override;
// Called when a packet has been sent to the socket.
// This is made pure virtual to notify subclasses of Port that they MUST
// listen to AsyncPacketSocket::SignalSentPacket and then call
// PortInterface::OnSentPacket.
virtual void OnSentPacket(rtc::AsyncPacketSocket* socket,
const rtc::SentPacket& sent_packet) = 0;
// Called when the socket is currently able to send.
void OnReadyToSend();
// Called when the Connection discovers a local peer reflexive candidate.
void AddPrflxCandidate(const Candidate& local) override;
int16_t network_cost() const override { return network_cost_; }
void GetStunStats(absl::optional<StunStats>* stats) override {}
// Foundation: An arbitrary string that is the same for two candidates
// that have the same type, base IP address, protocol (UDP, TCP,
// etc.), and STUN or TURN server. If any of these are different,
// then the foundation will be different. Two candidate pairs with
// the same foundation pairs are likely to have similar network
// characteristics. Foundations are used in the frozen algorithm.
std::string ComputeFoundation(
absl::string_view type,
absl::string_view protocol,
absl::string_view relay_protocol,
const rtc::SocketAddress& base_address) override;
protected:
void UpdateNetworkCost() override;
rtc::WeakPtr<Port> NewWeakPtr() { return weak_factory_.GetWeakPtr(); }
void AddAddress(const rtc::SocketAddress& address,
const rtc::SocketAddress& base_address,
const rtc::SocketAddress& related_address,
absl::string_view protocol,
absl::string_view relay_protocol,
absl::string_view tcptype,
absl::string_view type,
uint32_t type_preference,
uint32_t relay_preference,
absl::string_view url,
bool is_final);
void FinishAddingAddress(const Candidate& c, bool is_final)
RTC_RUN_ON(thread_);
virtual void PostAddAddress(bool is_final);
// Adds the given connection to the map keyed by the remote candidate address.
// If an existing connection has the same address, the existing one will be
// replaced and destroyed.
void AddOrReplaceConnection(Connection* conn);
// Called when a packet is received from an unknown address that is not
// currently a connection. If this is an authenticated STUN binding request,
// then we will signal the client.
void OnReadPacket(const rtc::ReceivedPacket& packet, ProtocolType proto);
[[deprecated(
"Use OnReadPacket(const rtc::ReceivedPacket& packet, ProtocolType "
"proto)")]] void
OnReadPacket(const char* data,
size_t size,
const rtc::SocketAddress& addr,
ProtocolType proto) {
OnReadPacket(rtc::ReceivedPacket::CreateFromLegacy(
data, size, /*packet_time_us = */ -1, addr),
proto);
}
// If the given data comprises a complete and correct STUN message then the
// return value is true, otherwise false. If the message username corresponds
// with this port's username fragment, msg will contain the parsed STUN
// message. Otherwise, the function may send a STUN response internally.
// remote_username contains the remote fragment of the STUN username.
bool GetStunMessage(const char* data,
size_t size,
const rtc::SocketAddress& addr,
std::unique_ptr<IceMessage>* out_msg,
std::string* out_username) override;
// Checks if the address in addr is compatible with the port's ip.
bool IsCompatibleAddress(const rtc::SocketAddress& addr);
// Returns DSCP value packets generated by the port itself should use.
rtc::DiffServCodePoint StunDscpValue() const override;
// Extra work to be done in subclasses when a connection is destroyed.
virtual void HandleConnectionDestroyed(Connection* conn) {}
void DestroyAllConnections();
void CopyPortInformationToPacketInfo(rtc::PacketInfo* info) const;
MdnsNameRegistrationStatus mdns_name_registration_status() const {
return mdns_name_registration_status_;
}
void set_mdns_name_registration_status(MdnsNameRegistrationStatus status) {
mdns_name_registration_status_ = status;
}
const webrtc::FieldTrialsView& field_trials() const { return *field_trials_; }
private:
void Construct();
void PostDestroyIfDead(bool delayed);
void DestroyIfDead();
// Called internally when deleting a connection object.
// Returns true if the connection object was removed from the `connections_`
// list and the state updated accordingly. If the connection was not found
// in the list, the return value is false. Note that this may indicate
// incorrect behavior of external code that might be attempting to delete
// connection objects from within a 'on destroyed' callback notification
// for the connection object itself.
bool OnConnectionDestroyed(Connection* conn);
// Private implementation of DestroyConnection to keep the async usage
// distinct.
void DestroyConnectionInternal(Connection* conn, bool async);
void OnNetworkTypeChanged(const rtc::Network* network);
webrtc::TaskQueueBase* const thread_;
rtc::PacketSocketFactory* const factory_;
const absl::string_view type_;
bool send_retransmit_count_attribute_;
const rtc::Network* network_;
uint16_t min_port_;
uint16_t max_port_;
std::string content_name_;
int component_;
uint32_t generation_;
// In order to establish a connection to this Port (so that real data can be
// sent through), the other side must send us a STUN binding request that is
// authenticated with this username_fragment and password.
// PortAllocatorSession will provide these username_fragment and password.
std::string ice_username_fragment_ RTC_GUARDED_BY(thread_);
std::string password_ RTC_GUARDED_BY(thread_);
std::vector<Candidate> candidates_ RTC_GUARDED_BY(thread_);
AddressMap connections_;
int timeout_delay_;
bool enable_port_packets_;
IceRole ice_role_;
uint64_t tiebreaker_;
bool shared_socket_;
// Information to use when going through a proxy.
std::string user_agent_;
rtc::ProxyInfo proxy_;
// A virtual cost perceived by the user, usually based on the network type
// (WiFi. vs. Cellular). It takes precedence over the priority when
// comparing two connections.
int16_t network_cost_;
State state_ = State::INIT;
int64_t last_time_all_connections_removed_ = 0;
MdnsNameRegistrationStatus mdns_name_registration_status_ =
MdnsNameRegistrationStatus::kNotStarted;
rtc::WeakPtrFactory<Port> weak_factory_;
webrtc::AlwaysValidPointer<const webrtc::FieldTrialsView,
webrtc::FieldTrialBasedConfig>
field_trials_;
bool MaybeObfuscateAddress(const Candidate& c, bool is_final)
RTC_RUN_ON(thread_);
webrtc::CallbackList<PortInterface*> port_destroyed_callback_list_;
};
} // namespace cricket
#endif // P2P_BASE_PORT_H_

View file

@ -0,0 +1,324 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "p2p/base/port_allocator.h"
#include <iterator>
#include <optional>
#include <set>
#include <utility>
#include "absl/strings/string_view.h"
#include "p2p/base/ice_credentials_iterator.h"
#include "rtc_base/checks.h"
namespace cricket {
RelayServerConfig::RelayServerConfig() {}
RelayServerConfig::RelayServerConfig(const rtc::SocketAddress& address,
absl::string_view username,
absl::string_view password,
ProtocolType proto)
: credentials(username, password) {
ports.push_back(ProtocolAddress(address, proto));
}
RelayServerConfig::RelayServerConfig(absl::string_view address,
int port,
absl::string_view username,
absl::string_view password,
ProtocolType proto)
: RelayServerConfig(rtc::SocketAddress(address, port),
username,
password,
proto) {}
// Legacy constructor where "secure" and PROTO_TCP implies PROTO_TLS.
RelayServerConfig::RelayServerConfig(absl::string_view address,
int port,
absl::string_view username,
absl::string_view password,
ProtocolType proto,
bool secure)
: RelayServerConfig(address,
port,
username,
password,
(proto == PROTO_TCP && secure ? PROTO_TLS : proto)) {}
RelayServerConfig::RelayServerConfig(const RelayServerConfig&) = default;
RelayServerConfig::~RelayServerConfig() = default;
PortAllocatorSession::PortAllocatorSession(absl::string_view content_name,
int component,
absl::string_view ice_ufrag,
absl::string_view ice_pwd,
uint32_t flags)
: flags_(flags),
generation_(0),
content_name_(content_name),
component_(component),
ice_ufrag_(ice_ufrag),
ice_pwd_(ice_pwd) {
// Pooled sessions are allowed to be created with empty content name,
// component, ufrag and password.
RTC_DCHECK(ice_ufrag.empty() == ice_pwd.empty());
}
PortAllocatorSession::~PortAllocatorSession() = default;
bool PortAllocatorSession::IsCleared() const {
return false;
}
bool PortAllocatorSession::IsStopped() const {
return false;
}
uint32_t PortAllocatorSession::generation() {
return generation_;
}
void PortAllocatorSession::set_generation(uint32_t generation) {
generation_ = generation;
}
PortAllocator::PortAllocator()
: flags_(kDefaultPortAllocatorFlags),
min_port_(0),
max_port_(0),
max_ipv6_networks_(kDefaultMaxIPv6Networks),
step_delay_(kDefaultStepDelay),
allow_tcp_listen_(true),
candidate_filter_(CF_ALL),
tiebreaker_(rtc::CreateRandomId64()) {
// The allocator will be attached to a thread in Initialize.
thread_checker_.Detach();
}
void PortAllocator::Initialize() {
RTC_DCHECK(thread_checker_.IsCurrent());
initialized_ = true;
}
PortAllocator::~PortAllocator() {
CheckRunOnValidThreadIfInitialized();
}
void PortAllocator::set_restrict_ice_credentials_change(bool value) {
restrict_ice_credentials_change_ = value;
}
// Deprecated
bool PortAllocator::SetConfiguration(
const ServerAddresses& stun_servers,
const std::vector<RelayServerConfig>& turn_servers,
int candidate_pool_size,
bool prune_turn_ports,
webrtc::TurnCustomizer* turn_customizer,
const absl::optional<int>& stun_candidate_keepalive_interval) {
webrtc::PortPrunePolicy turn_port_prune_policy =
prune_turn_ports ? webrtc::PRUNE_BASED_ON_PRIORITY : webrtc::NO_PRUNE;
return SetConfiguration(stun_servers, turn_servers, candidate_pool_size,
turn_port_prune_policy, turn_customizer,
stun_candidate_keepalive_interval);
}
bool PortAllocator::SetConfiguration(
const ServerAddresses& stun_servers,
const std::vector<RelayServerConfig>& turn_servers,
int candidate_pool_size,
webrtc::PortPrunePolicy turn_port_prune_policy,
webrtc::TurnCustomizer* turn_customizer,
const absl::optional<int>& stun_candidate_keepalive_interval) {
RTC_DCHECK_GE(candidate_pool_size, 0);
RTC_DCHECK_LE(candidate_pool_size, static_cast<int>(UINT16_MAX));
CheckRunOnValidThreadIfInitialized();
// A positive candidate pool size would lead to the creation of a pooled
// allocator session and starting getting ports, which we should only do on
// the network thread.
RTC_DCHECK(candidate_pool_size == 0 || thread_checker_.IsCurrent());
bool ice_servers_changed =
(stun_servers != stun_servers_ || turn_servers != turn_servers_);
stun_servers_ = stun_servers;
turn_servers_ = turn_servers;
turn_port_prune_policy_ = turn_port_prune_policy;
candidate_pool_size_ = candidate_pool_size;
// If ICE servers changed, throw away any existing pooled sessions and create
// new ones.
if (ice_servers_changed) {
pooled_sessions_.clear();
}
turn_customizer_ = turn_customizer;
// If `candidate_pool_size_` is less than the number of pooled sessions, get
// rid of the extras.
while (candidate_pool_size_ < static_cast<int>(pooled_sessions_.size())) {
pooled_sessions_.back().reset(nullptr);
pooled_sessions_.pop_back();
}
// `stun_candidate_keepalive_interval_` will be used in STUN port allocation
// in future sessions. We also update the ready ports in the pooled sessions.
// Ports in sessions that are taken and owned by P2PTransportChannel will be
// updated there via IceConfig.
stun_candidate_keepalive_interval_ = stun_candidate_keepalive_interval;
for (const auto& session : pooled_sessions_) {
session->SetStunKeepaliveIntervalForReadyPorts(
stun_candidate_keepalive_interval_);
}
// If `candidate_pool_size_` is greater than the number of pooled sessions,
// create new sessions.
while (static_cast<int>(pooled_sessions_.size()) < candidate_pool_size_) {
IceParameters iceCredentials =
IceCredentialsIterator::CreateRandomIceCredentials();
PortAllocatorSession* pooled_session =
CreateSessionInternal("", 0, iceCredentials.ufrag, iceCredentials.pwd);
pooled_session->set_pooled(true);
pooled_session->StartGettingPorts();
pooled_sessions_.push_back(
std::unique_ptr<PortAllocatorSession>(pooled_session));
}
return true;
}
std::unique_ptr<PortAllocatorSession> PortAllocator::CreateSession(
absl::string_view content_name,
int component,
absl::string_view ice_ufrag,
absl::string_view ice_pwd) {
CheckRunOnValidThreadAndInitialized();
auto session = std::unique_ptr<PortAllocatorSession>(
CreateSessionInternal(content_name, component, ice_ufrag, ice_pwd));
session->SetCandidateFilter(candidate_filter());
return session;
}
std::unique_ptr<PortAllocatorSession> PortAllocator::TakePooledSession(
absl::string_view content_name,
int component,
absl::string_view ice_ufrag,
absl::string_view ice_pwd) {
CheckRunOnValidThreadAndInitialized();
RTC_DCHECK(!ice_ufrag.empty());
RTC_DCHECK(!ice_pwd.empty());
if (pooled_sessions_.empty()) {
return nullptr;
}
IceParameters credentials(ice_ufrag, ice_pwd, false);
// If restrict_ice_credentials_change_ is TRUE, then call FindPooledSession
// with ice credentials. Otherwise call it with nullptr which means
// "find any" pooled session.
auto cit = FindPooledSession(restrict_ice_credentials_change_ ? &credentials
: nullptr);
if (cit == pooled_sessions_.end()) {
return nullptr;
}
auto it =
pooled_sessions_.begin() + std::distance(pooled_sessions_.cbegin(), cit);
std::unique_ptr<PortAllocatorSession> ret = std::move(*it);
ret->SetIceParameters(content_name, component, ice_ufrag, ice_pwd);
ret->set_pooled(false);
// According to JSEP, a pooled session should filter candidates only
// after it's taken out of the pool.
ret->SetCandidateFilter(candidate_filter());
pooled_sessions_.erase(it);
return ret;
}
const PortAllocatorSession* PortAllocator::GetPooledSession(
const IceParameters* ice_credentials) const {
CheckRunOnValidThreadAndInitialized();
auto it = FindPooledSession(ice_credentials);
if (it == pooled_sessions_.end()) {
return nullptr;
} else {
return it->get();
}
}
std::vector<std::unique_ptr<PortAllocatorSession>>::const_iterator
PortAllocator::FindPooledSession(const IceParameters* ice_credentials) const {
for (auto it = pooled_sessions_.begin(); it != pooled_sessions_.end(); ++it) {
if (ice_credentials == nullptr ||
((*it)->ice_ufrag() == ice_credentials->ufrag &&
(*it)->ice_pwd() == ice_credentials->pwd)) {
return it;
}
}
return pooled_sessions_.end();
}
void PortAllocator::DiscardCandidatePool() {
CheckRunOnValidThreadIfInitialized();
pooled_sessions_.clear();
}
void PortAllocator::SetCandidateFilter(uint32_t filter) {
CheckRunOnValidThreadIfInitialized();
if (candidate_filter_ == filter) {
return;
}
uint32_t prev_filter = candidate_filter_;
candidate_filter_ = filter;
SignalCandidateFilterChanged(prev_filter, filter);
}
void PortAllocator::GetCandidateStatsFromPooledSessions(
CandidateStatsList* candidate_stats_list) {
CheckRunOnValidThreadAndInitialized();
for (const auto& session : pooled_sessions()) {
session->GetCandidateStatsFromReadyPorts(candidate_stats_list);
}
}
std::vector<IceParameters> PortAllocator::GetPooledIceCredentials() {
CheckRunOnValidThreadAndInitialized();
std::vector<IceParameters> list;
for (const auto& session : pooled_sessions_) {
list.push_back(
IceParameters(session->ice_ufrag(), session->ice_pwd(), false));
}
return list;
}
Candidate PortAllocator::SanitizeCandidate(const Candidate& c) const {
CheckRunOnValidThreadAndInitialized();
// For a local host candidate, we need to conceal its IP address candidate if
// the mDNS obfuscation is enabled.
bool use_hostname_address =
(c.is_local() || c.is_prflx()) && MdnsObfuscationEnabled();
// If adapter enumeration is disabled or host candidates are disabled,
// clear the raddr of STUN candidates to avoid local address leakage.
bool filter_stun_related_address =
((flags() & PORTALLOCATOR_DISABLE_ADAPTER_ENUMERATION) &&
(flags() & PORTALLOCATOR_DISABLE_DEFAULT_LOCAL_CANDIDATE)) ||
!(candidate_filter_ & CF_HOST) || MdnsObfuscationEnabled();
// If the candidate filter doesn't allow reflexive addresses, empty TURN raddr
// to avoid reflexive address leakage.
bool filter_turn_related_address = !(candidate_filter_ & CF_REFLEXIVE);
// Sanitize related_address when using MDNS.
bool filter_prflx_related_address = MdnsObfuscationEnabled();
bool filter_related_address =
((c.is_stun() && filter_stun_related_address) ||
(c.is_relay() && filter_turn_related_address) ||
(c.is_prflx() && filter_prflx_related_address));
return c.ToSanitizedCopy(use_hostname_address, filter_related_address);
}
} // namespace cricket

View file

@ -0,0 +1,673 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef P2P_BASE_PORT_ALLOCATOR_H_
#define P2P_BASE_PORT_ALLOCATOR_H_
#include <stdint.h>
#include <deque>
#include <memory>
#include <string>
#include <vector>
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "api/candidate.h"
#include "api/sequence_checker.h"
#include "api/transport/enums.h"
#include "p2p/base/port.h"
#include "p2p/base/port_interface.h"
#include "p2p/base/transport_description.h"
#include "rtc_base/checks.h"
#include "rtc_base/helpers.h"
#include "rtc_base/network.h"
#include "rtc_base/proxy_info.h"
#include "rtc_base/socket_address.h"
#include "rtc_base/ssl_certificate.h"
#include "rtc_base/system/rtc_export.h"
#include "rtc_base/third_party/sigslot/sigslot.h"
#include "rtc_base/thread.h"
namespace webrtc {
class TurnCustomizer;
} // namespace webrtc
namespace cricket {
// PortAllocator is responsible for allocating Port types for a given
// P2PSocket. It also handles port freeing.
//
// Clients can override this class to control port allocation, including
// what kinds of ports are allocated.
enum {
// Disable local UDP ports. This doesn't impact how we connect to relay
// servers.
PORTALLOCATOR_DISABLE_UDP = 0x01,
PORTALLOCATOR_DISABLE_STUN = 0x02,
PORTALLOCATOR_DISABLE_RELAY = 0x04,
// Disable local TCP ports. This doesn't impact how we connect to relay
// servers.
PORTALLOCATOR_DISABLE_TCP = 0x08,
PORTALLOCATOR_ENABLE_IPV6 = 0x40,
PORTALLOCATOR_ENABLE_SHARED_SOCKET = 0x100,
PORTALLOCATOR_ENABLE_STUN_RETRANSMIT_ATTRIBUTE = 0x200,
// When specified, we'll only allocate the STUN candidate for the public
// interface as seen by regular http traffic and the HOST candidate associated
// with the default local interface.
PORTALLOCATOR_DISABLE_ADAPTER_ENUMERATION = 0x400,
// When specified along with PORTALLOCATOR_DISABLE_ADAPTER_ENUMERATION, the
// default local candidate mentioned above will not be allocated. Only the
// STUN candidate will be.
PORTALLOCATOR_DISABLE_DEFAULT_LOCAL_CANDIDATE = 0x800,
// Disallow use of UDP when connecting to a relay server. Since proxy servers
// usually don't handle UDP, using UDP will leak the IP address.
PORTALLOCATOR_DISABLE_UDP_RELAY = 0x1000,
// When multiple networks exist, do not gather candidates on the ones with
// high cost. So if both Wi-Fi and cellular networks exist, gather only on the
// Wi-Fi network. If a network type is "unknown", it has a cost lower than
// cellular but higher than Wi-Fi/Ethernet. So if an unknown network exists,
// cellular networks will not be used to gather candidates and if a Wi-Fi
// network is present, "unknown" networks will not be usd to gather
// candidates. Doing so ensures that even if a cellular network type was not
// detected initially, it would not be used if a Wi-Fi network is present.
PORTALLOCATOR_DISABLE_COSTLY_NETWORKS = 0x2000,
// When specified, do not collect IPv6 ICE candidates on Wi-Fi.
PORTALLOCATOR_ENABLE_IPV6_ON_WIFI = 0x4000,
// When this flag is set, ports not bound to any specific network interface
// will be used, in addition to normal ports bound to the enumerated
// interfaces. Without this flag, these "any address" ports would only be
// used when network enumeration fails or is disabled. But under certain
// conditions, these ports may succeed where others fail, so they may allow
// the application to work in a wider variety of environments, at the expense
// of having to allocate additional candidates.
PORTALLOCATOR_ENABLE_ANY_ADDRESS_PORTS = 0x8000,
// Exclude link-local network interfaces
// from considertaion after adapter enumeration.
PORTALLOCATOR_DISABLE_LINK_LOCAL_NETWORKS = 0x10000,
};
// Defines various reasons that have caused ICE regathering.
enum class IceRegatheringReason {
NETWORK_CHANGE, // Network interfaces on the device changed
NETWORK_FAILURE, // Regather only on networks that have failed
OCCASIONAL_REFRESH, // Periodic regather on all networks
MAX_VALUE
};
const uint32_t kDefaultPortAllocatorFlags = 0;
const uint32_t kDefaultStepDelay = 1000; // 1 sec step delay.
// As per RFC 5245 Appendix B.1, STUN transactions need to be paced at certain
// internal. Less than 20ms is not acceptable. We choose 50ms as our default.
const uint32_t kMinimumStepDelay = 50;
// Turning on IPv6 could make many IPv6 interfaces available for connectivity
// check and delay the call setup time. kDefaultMaxIPv6Networks is the default
// upper limit of IPv6 networks but could be changed by
// set_max_ipv6_networks().
constexpr int kDefaultMaxIPv6Networks = 5;
// CF = CANDIDATE FILTER
enum : uint32_t {
CF_NONE = 0x0,
CF_HOST = 0x1,
CF_REFLEXIVE = 0x2,
CF_RELAY = 0x4,
CF_ALL = 0x7,
};
// TLS certificate policy.
enum class TlsCertPolicy {
// For TLS based protocols, ensure the connection is secure by not
// circumventing certificate validation.
TLS_CERT_POLICY_SECURE,
// For TLS based protocols, disregard security completely by skipping
// certificate validation. This is insecure and should never be used unless
// security is irrelevant in that particular context.
TLS_CERT_POLICY_INSECURE_NO_CHECK,
};
// TODO(deadbeef): Rename to TurnCredentials (and username to ufrag).
struct RelayCredentials {
RelayCredentials() {}
RelayCredentials(absl::string_view username, absl::string_view password)
: username(username), password(password) {}
bool operator==(const RelayCredentials& o) const {
return username == o.username && password == o.password;
}
bool operator!=(const RelayCredentials& o) const { return !(*this == o); }
std::string username;
std::string password;
};
typedef std::vector<ProtocolAddress> PortList;
// TODO(deadbeef): Rename to TurnServerConfig.
struct RTC_EXPORT RelayServerConfig {
RelayServerConfig();
RelayServerConfig(const rtc::SocketAddress& address,
absl::string_view username,
absl::string_view password,
ProtocolType proto);
RelayServerConfig(absl::string_view address,
int port,
absl::string_view username,
absl::string_view password,
ProtocolType proto);
// Legacy constructor where "secure" and PROTO_TCP implies PROTO_TLS.
RelayServerConfig(absl::string_view address,
int port,
absl::string_view username,
absl::string_view password,
ProtocolType proto,
bool secure);
RelayServerConfig(const RelayServerConfig&);
~RelayServerConfig();
bool operator==(const RelayServerConfig& o) const {
return ports == o.ports && credentials == o.credentials;
}
bool operator!=(const RelayServerConfig& o) const { return !(*this == o); }
PortList ports;
RelayCredentials credentials;
TlsCertPolicy tls_cert_policy = TlsCertPolicy::TLS_CERT_POLICY_SECURE;
std::vector<std::string> tls_alpn_protocols;
std::vector<std::string> tls_elliptic_curves;
rtc::SSLCertificateVerifier* tls_cert_verifier = nullptr;
std::string turn_logging_id;
};
class RTC_EXPORT PortAllocatorSession : public sigslot::has_slots<> {
public:
// Content name passed in mostly for logging and debugging.
PortAllocatorSession(absl::string_view content_name,
int component,
absl::string_view ice_ufrag,
absl::string_view ice_pwd,
uint32_t flags);
// Subclasses should clean up any ports created.
~PortAllocatorSession() override;
uint32_t flags() const { return flags_; }
void set_flags(uint32_t flags) { flags_ = flags; }
std::string content_name() const { return content_name_; }
int component() const { return component_; }
const std::string& ice_ufrag() const { return ice_ufrag_; }
const std::string& ice_pwd() const { return ice_pwd_; }
bool pooled() const { return pooled_; }
// Setting this filter should affect not only candidates gathered in the
// future, but candidates already gathered and ports already "ready",
// which would be returned by ReadyCandidates() and ReadyPorts().
//
// Default filter should be CF_ALL.
virtual void SetCandidateFilter(uint32_t filter) = 0;
// Starts gathering ports and ICE candidates.
virtual void StartGettingPorts() = 0;
// Completely stops gathering. Will not gather again unless StartGettingPorts
// is called again.
virtual void StopGettingPorts() = 0;
// Whether the session is actively getting ports.
virtual bool IsGettingPorts() = 0;
//
// NOTE: The group of methods below is only used for continual gathering.
//
// ClearGettingPorts should have the same immediate effect as
// StopGettingPorts, but if the implementation supports continual gathering,
// ClearGettingPorts allows additional ports/candidates to be gathered if the
// network conditions change.
virtual void ClearGettingPorts() = 0;
// Whether it is in the state where the existing gathering process is stopped,
// but new ones may be started (basically after calling ClearGettingPorts).
virtual bool IsCleared() const;
// Whether the session has completely stopped.
virtual bool IsStopped() const;
// Re-gathers candidates on networks that do not have any connections. More
// precisely, a network interface may have more than one IP addresses (e.g.,
// IPv4 and IPv6 addresses). Each address subnet will be used to create a
// network. Only if all networks of an interface have no connection, the
// implementation should start re-gathering on all networks of that interface.
virtual void RegatherOnFailedNetworks() {}
// Get candidate-level stats from all candidates on the ready ports and return
// the stats to the given list.
virtual void GetCandidateStatsFromReadyPorts(
CandidateStatsList* candidate_stats_list) const {}
// Set the interval at which STUN candidates will resend STUN binding requests
// on the underlying ports to keep NAT bindings open.
// The default value of the interval in implementation is restored if a null
// optional value is passed.
virtual void SetStunKeepaliveIntervalForReadyPorts(
const absl::optional<int>& stun_keepalive_interval) {}
// Another way of getting the information provided by the signals below.
//
// Ports and candidates are not guaranteed to be in the same order as the
// signals were emitted in.
virtual std::vector<PortInterface*> ReadyPorts() const = 0;
virtual std::vector<Candidate> ReadyCandidates() const = 0;
virtual bool CandidatesAllocationDone() const = 0;
// Marks all ports in the current session as "pruned" so that they may be
// destroyed if no connection is using them.
virtual void PruneAllPorts() {}
sigslot::signal2<PortAllocatorSession*, PortInterface*> SignalPortReady;
// Fires this signal when the network of the ports failed (either because the
// interface is down, or because there is no connection on the interface),
// or when TURN ports are pruned because a higher-priority TURN port becomes
// ready(pairable).
sigslot::signal2<PortAllocatorSession*, const std::vector<PortInterface*>&>
SignalPortsPruned;
sigslot::signal2<PortAllocatorSession*, const std::vector<Candidate>&>
SignalCandidatesReady;
sigslot::signal2<PortAllocatorSession*, const IceCandidateErrorEvent&>
SignalCandidateError;
// Candidates should be signaled to be removed when the port that generated
// the candidates is removed.
sigslot::signal2<PortAllocatorSession*, const std::vector<Candidate>&>
SignalCandidatesRemoved;
sigslot::signal1<PortAllocatorSession*> SignalCandidatesAllocationDone;
sigslot::signal2<PortAllocatorSession*, IceRegatheringReason>
SignalIceRegathering;
virtual uint32_t generation();
virtual void set_generation(uint32_t generation);
protected:
// This method is called when a pooled session (which doesn't have these
// properties initially) is returned by PortAllocator::TakePooledSession,
// and the content name, component, and ICE ufrag/pwd are updated.
//
// A subclass may need to override this method to perform additional actions,
// such as applying the updated information to ports and candidates.
virtual void UpdateIceParametersInternal() {}
// TODO(deadbeef): Get rid of these when everyone switches to ice_ufrag and
// ice_pwd.
const std::string& username() const { return ice_ufrag_; }
const std::string& password() const { return ice_pwd_; }
private:
void SetIceParameters(absl::string_view content_name,
int component,
absl::string_view ice_ufrag,
absl::string_view ice_pwd) {
content_name_ = std::string(content_name);
component_ = component;
ice_ufrag_ = std::string(ice_ufrag);
ice_pwd_ = std::string(ice_pwd);
UpdateIceParametersInternal();
}
void set_pooled(bool value) { pooled_ = value; }
uint32_t flags_;
uint32_t generation_;
std::string content_name_;
int component_;
std::string ice_ufrag_;
std::string ice_pwd_;
bool pooled_ = false;
// SetIceParameters is an implementation detail which only PortAllocator
// should be able to call.
friend class PortAllocator;
};
// Every method of PortAllocator (including the destructor) must be called on
// the same thread after Initialize is called.
//
// This allows a PortAllocator subclass to be constructed and configured on one
// thread, and passed into an object that uses it on a different thread.
class RTC_EXPORT PortAllocator : public sigslot::has_slots<> {
public:
PortAllocator();
~PortAllocator() override;
// This MUST be called on the PortAllocator's thread after finishing
// constructing and configuring the PortAllocator subclasses.
virtual void Initialize();
// Set to true if some Ports need to know the ICE credentials when they are
// created. This will ensure that the PortAllocator will only match pooled
// allocator sessions to the ICE transport with the same credentials.
virtual void set_restrict_ice_credentials_change(bool value);
// Set STUN and TURN servers to be used in future sessions, and set
// candidate pool size, as described in JSEP.
//
// If the servers are changing, and the candidate pool size is nonzero, and
// FreezeCandidatePool hasn't been called, existing pooled sessions will be
// destroyed and new ones created.
//
// If the servers are not changing but the candidate pool size is, and
// FreezeCandidatePool hasn't been called, pooled sessions will be either
// created or destroyed as necessary.
//
// Returns true if the configuration could successfully be changed.
// Deprecated
bool SetConfiguration(const ServerAddresses& stun_servers,
const std::vector<RelayServerConfig>& turn_servers,
int candidate_pool_size,
bool prune_turn_ports,
webrtc::TurnCustomizer* turn_customizer = nullptr,
const absl::optional<int>&
stun_candidate_keepalive_interval = absl::nullopt);
bool SetConfiguration(const ServerAddresses& stun_servers,
const std::vector<RelayServerConfig>& turn_servers,
int candidate_pool_size,
webrtc::PortPrunePolicy turn_port_prune_policy,
webrtc::TurnCustomizer* turn_customizer = nullptr,
const absl::optional<int>&
stun_candidate_keepalive_interval = absl::nullopt);
const ServerAddresses& stun_servers() const {
CheckRunOnValidThreadIfInitialized();
return stun_servers_;
}
const std::vector<RelayServerConfig>& turn_servers() const {
CheckRunOnValidThreadIfInitialized();
return turn_servers_;
}
int candidate_pool_size() const {
CheckRunOnValidThreadIfInitialized();
return candidate_pool_size_;
}
const absl::optional<int>& stun_candidate_keepalive_interval() const {
CheckRunOnValidThreadIfInitialized();
return stun_candidate_keepalive_interval_;
}
// Sets the network types to ignore.
// Values are defined by the AdapterType enum.
// For instance, calling this with
// ADAPTER_TYPE_ETHERNET | ADAPTER_TYPE_LOOPBACK will ignore Ethernet and
// loopback interfaces.
virtual void SetNetworkIgnoreMask(int network_ignore_mask) = 0;
// Set whether VPN connections should be preferred, avoided, mandated or
// blocked.
virtual void SetVpnPreference(webrtc::VpnPreference preference) {
vpn_preference_ = preference;
}
// Set list of <ipaddress, mask> that shall be categorized as VPN.
// Implemented by BasicPortAllocator.
virtual void SetVpnList(const std::vector<rtc::NetworkMask>& vpn_list) {}
std::unique_ptr<PortAllocatorSession> CreateSession(
absl::string_view content_name,
int component,
absl::string_view ice_ufrag,
absl::string_view ice_pwd);
// Get an available pooled session and set the transport information on it.
//
// Caller takes ownership of the returned session.
//
// If restrict_ice_credentials_change is TRUE, then it will only
// return a pooled session with matching ice credentials.
// If no pooled sessions are available, returns null.
std::unique_ptr<PortAllocatorSession> TakePooledSession(
absl::string_view content_name,
int component,
absl::string_view ice_ufrag,
absl::string_view ice_pwd);
// Returns the next session that would be returned by TakePooledSession
// optionally restricting it to sessions with specified ice credentials.
const PortAllocatorSession* GetPooledSession(
const IceParameters* ice_credentials = nullptr) const;
// Discard any remaining pooled sessions.
void DiscardCandidatePool();
// Clears the address and the related address fields of a local candidate to
// avoid IP leakage. This is applicable in several scenarios:
// 1. Sanitization is configured via the candidate filter.
// 2. Sanitization is configured via the port allocator flags.
// 3. mDNS concealment of private IPs is enabled.
Candidate SanitizeCandidate(const Candidate& c) const;
uint64_t ice_tiebreaker() const { return tiebreaker_; }
uint32_t flags() const {
CheckRunOnValidThreadIfInitialized();
return flags_;
}
void set_flags(uint32_t flags) {
CheckRunOnValidThreadIfInitialized();
flags_ = flags;
}
// These three methods are deprecated. If connections need to go through a
// proxy, the application should create a BasicPortAllocator given a custom
// PacketSocketFactory that creates proxy sockets.
const std::string& user_agent() const {
CheckRunOnValidThreadIfInitialized();
return agent_;
}
const rtc::ProxyInfo& proxy() const {
CheckRunOnValidThreadIfInitialized();
return proxy_;
}
void set_proxy(absl::string_view agent, const rtc::ProxyInfo& proxy) {
CheckRunOnValidThreadIfInitialized();
agent_ = std::string(agent);
proxy_ = proxy;
}
// Gets/Sets the port range to use when choosing client ports.
int min_port() const {
CheckRunOnValidThreadIfInitialized();
return min_port_;
}
int max_port() const {
CheckRunOnValidThreadIfInitialized();
return max_port_;
}
bool SetPortRange(int min_port, int max_port) {
CheckRunOnValidThreadIfInitialized();
if (min_port > max_port) {
return false;
}
min_port_ = min_port;
max_port_ = max_port;
return true;
}
// Can be used to change the default numer of IPv6 network interfaces used
// (5). Can set to INT_MAX to effectively disable the limit.
//
// TODO(deadbeef): Applications shouldn't have to arbitrarily limit the
// number of available IPv6 network interfaces just because they could slow
// ICE down. We should work on making our ICE logic smarter (for example,
// prioritizing pinging connections that are most likely to work) so that
// every network interface can be used without impacting ICE's speed.
void set_max_ipv6_networks(int networks) {
CheckRunOnValidThreadIfInitialized();
max_ipv6_networks_ = networks;
}
int max_ipv6_networks() {
CheckRunOnValidThreadIfInitialized();
return max_ipv6_networks_;
}
// Delay between different candidate gathering phases (UDP, TURN, TCP).
// Defaults to 1 second, but PeerConnection sets it to 50ms.
// TODO(deadbeef): Get rid of this. Its purpose is to avoid sending too many
// STUN transactions at once, but that's already happening if you configure
// multiple STUN servers or have multiple network interfaces. We should
// implement some global pacing logic instead if that's our goal.
uint32_t step_delay() const {
CheckRunOnValidThreadIfInitialized();
return step_delay_;
}
void set_step_delay(uint32_t delay) {
CheckRunOnValidThreadIfInitialized();
step_delay_ = delay;
}
bool allow_tcp_listen() const {
CheckRunOnValidThreadIfInitialized();
return allow_tcp_listen_;
}
void set_allow_tcp_listen(bool allow_tcp_listen) {
CheckRunOnValidThreadIfInitialized();
allow_tcp_listen_ = allow_tcp_listen;
}
uint32_t candidate_filter() {
CheckRunOnValidThreadIfInitialized();
return candidate_filter_;
}
// The new filter value will be populated to future allocation sessions, when
// they are created via CreateSession, and also pooled sessions when one is
// taken via TakePooledSession.
//
// A change in the candidate filter also fires a signal
// `SignalCandidateFilterChanged`, so that objects subscribed to this signal
// can, for example, update the candidate filter for sessions created by this
// allocator and already taken by the object.
//
// Specifically for the session taken by the ICE transport, we currently do
// not support removing candidate pairs formed with local candidates from this
// session that are disabled by the new candidate filter.
void SetCandidateFilter(uint32_t filter);
// Deprecated.
// TODO(qingsi): Remove this after Chromium migrates to the new method.
void set_candidate_filter(uint32_t filter) { SetCandidateFilter(filter); }
// Deprecated (by the next method).
bool prune_turn_ports() const {
CheckRunOnValidThreadIfInitialized();
return turn_port_prune_policy_ == webrtc::PRUNE_BASED_ON_PRIORITY;
}
webrtc::PortPrunePolicy turn_port_prune_policy() const {
CheckRunOnValidThreadIfInitialized();
return turn_port_prune_policy_;
}
webrtc::TurnCustomizer* turn_customizer() {
CheckRunOnValidThreadIfInitialized();
return turn_customizer_;
}
// Collect candidate stats from pooled allocator sessions. This can be used to
// collect candidate stats without creating an offer/answer or setting local
// description. After the local description is set, the ownership of the
// pooled session is taken by P2PTransportChannel, and the
// candidate stats can be collected from P2PTransportChannel::GetStats.
virtual void GetCandidateStatsFromPooledSessions(
CandidateStatsList* candidate_stats_list);
// Return IceParameters of the pooled sessions.
std::vector<IceParameters> GetPooledIceCredentials();
// Fired when `candidate_filter_` changes.
sigslot::signal2<uint32_t /* prev_filter */, uint32_t /* cur_filter */>
SignalCandidateFilterChanged;
protected:
// TODO(webrtc::13579): Remove std::string version once downstream users have
// migrated to the absl::string_view version.
virtual PortAllocatorSession* CreateSessionInternal(
absl::string_view content_name,
int component,
absl::string_view ice_ufrag,
absl::string_view ice_pwd) = 0;
const std::vector<std::unique_ptr<PortAllocatorSession>>& pooled_sessions() {
return pooled_sessions_;
}
// Returns true if there is an mDNS responder attached to the network manager.
virtual bool MdnsObfuscationEnabled() const { return false; }
// The following thread checks are only done in DCHECK for the consistency
// with the exsiting thread checks.
void CheckRunOnValidThreadIfInitialized() const {
RTC_DCHECK(!initialized_ || thread_checker_.IsCurrent());
}
void CheckRunOnValidThreadAndInitialized() const {
RTC_DCHECK(initialized_ && thread_checker_.IsCurrent());
}
bool initialized_ = false;
uint32_t flags_;
std::string agent_;
rtc::ProxyInfo proxy_;
int min_port_;
int max_port_;
int max_ipv6_networks_;
uint32_t step_delay_;
bool allow_tcp_listen_;
uint32_t candidate_filter_;
std::string origin_;
webrtc::SequenceChecker thread_checker_;
webrtc::VpnPreference vpn_preference_ = webrtc::VpnPreference::kDefault;
private:
ServerAddresses stun_servers_;
std::vector<RelayServerConfig> turn_servers_;
int candidate_pool_size_ = 0; // Last value passed into SetConfiguration.
std::vector<std::unique_ptr<PortAllocatorSession>> pooled_sessions_;
webrtc::PortPrunePolicy turn_port_prune_policy_ = webrtc::NO_PRUNE;
// Customizer for TURN messages.
// The instance is owned by application and will be shared among
// all TurnPort(s) created.
webrtc::TurnCustomizer* turn_customizer_ = nullptr;
absl::optional<int> stun_candidate_keepalive_interval_;
// If true, TakePooledSession() will only return sessions that has same ice
// credentials as requested.
bool restrict_ice_credentials_change_ = false;
// Returns iterator to pooled session with specified ice_credentials or first
// if ice_credentials is nullptr.
std::vector<std::unique_ptr<PortAllocatorSession>>::const_iterator
FindPooledSession(const IceParameters* ice_credentials = nullptr) const;
// ICE tie breaker.
uint64_t tiebreaker_;
};
} // namespace cricket
#endif // P2P_BASE_PORT_ALLOCATOR_H_

View file

@ -0,0 +1,23 @@
/*
* Copyright 2017 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "p2p/base/port_interface.h"
#include <string>
#include "absl/strings/string_view.h"
namespace cricket {
PortInterface::PortInterface() = default;
PortInterface::~PortInterface() = default;
} // namespace cricket

View file

@ -0,0 +1,221 @@
/*
* 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.
*/
#ifndef P2P_BASE_PORT_INTERFACE_H_
#define P2P_BASE_PORT_INTERFACE_H_
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "api/candidate.h"
#include "api/packet_socket_factory.h"
#include "p2p/base/transport_description.h"
#include "rtc_base/async_packet_socket.h"
#include "rtc_base/callback_list.h"
#include "rtc_base/proxy_info.h"
#include "rtc_base/socket_address.h"
namespace rtc {
class Network;
struct PacketOptions;
} // namespace rtc
namespace cricket {
class Connection;
class IceMessage;
class StunMessage;
class StunStats;
enum ProtocolType {
PROTO_UDP,
PROTO_TCP,
PROTO_SSLTCP, // Pseudo-TLS.
PROTO_TLS,
PROTO_LAST = PROTO_TLS
};
// Defines the interface for a port, which represents a local communication
// mechanism that can be used to create connections to similar mechanisms of
// the other client. Various types of ports will implement this interface.
class PortInterface {
public:
virtual ~PortInterface();
virtual const absl::string_view Type() const = 0;
virtual const rtc::Network* Network() const = 0;
// Methods to set/get ICE role and tiebreaker values.
virtual void SetIceRole(IceRole role) = 0;
virtual IceRole GetIceRole() const = 0;
virtual void SetIceTiebreaker(uint64_t tiebreaker) = 0;
virtual uint64_t IceTiebreaker() const = 0;
virtual bool SharedSocket() const = 0;
virtual bool SupportsProtocol(absl::string_view protocol) const = 0;
// PrepareAddress will attempt to get an address for this port that other
// clients can send to. It may take some time before the address is ready.
// Once it is ready, we will send SignalAddressReady. If errors are
// preventing the port from getting an address, it may send
// SignalAddressError.
virtual void PrepareAddress() = 0;
// Returns the connection to the given address or NULL if none exists.
virtual Connection* GetConnection(const rtc::SocketAddress& remote_addr) = 0;
// Creates a new connection to the given address.
enum CandidateOrigin { ORIGIN_THIS_PORT, ORIGIN_OTHER_PORT, ORIGIN_MESSAGE };
virtual Connection* CreateConnection(const Candidate& remote_candidate,
CandidateOrigin origin) = 0;
// Functions on the underlying socket(s).
virtual int SetOption(rtc::Socket::Option opt, int value) = 0;
virtual int GetOption(rtc::Socket::Option opt, int* value) = 0;
virtual int GetError() = 0;
virtual ProtocolType GetProtocol() const = 0;
virtual const std::vector<Candidate>& Candidates() const = 0;
// Sends the given packet to the given address, provided that the address is
// that of a connection or an address that has sent to us already.
virtual int SendTo(const void* data,
size_t size,
const rtc::SocketAddress& addr,
const rtc::PacketOptions& options,
bool payload) = 0;
// Indicates that we received a successful STUN binding request from an
// address that doesn't correspond to any current connection. To turn this
// into a real connection, call CreateConnection.
sigslot::signal6<PortInterface*,
const rtc::SocketAddress&,
ProtocolType,
IceMessage*,
const std::string&,
bool>
SignalUnknownAddress;
// Sends a response message (normal or error) to the given request. One of
// these methods should be called as a response to SignalUnknownAddress.
virtual void SendBindingErrorResponse(StunMessage* message,
const rtc::SocketAddress& addr,
int error_code,
absl::string_view reason) = 0;
// Signaled when this port decides to delete itself because it no longer has
// any usefulness.
virtual void SubscribePortDestroyed(
std::function<void(PortInterface*)> callback) = 0;
// Signaled when Port discovers ice role conflict with the peer.
sigslot::signal1<PortInterface*> SignalRoleConflict;
// Normally, packets arrive through a connection (or they result signaling of
// unknown address). Calling this method turns off delivery of packets
// through their respective connection and instead delivers every packet
// through this port.
virtual void EnablePortPackets() = 0;
sigslot::
signal4<PortInterface*, const char*, size_t, const rtc::SocketAddress&>
SignalReadPacket;
// Emitted each time a packet is sent on this port.
sigslot::signal1<const rtc::SentPacket&> SignalSentPacket;
virtual std::string ToString() const = 0;
virtual void GetStunStats(absl::optional<StunStats>* stats) = 0;
// Removes and deletes a connection object. `DestroyConnection` will
// delete the connection object directly whereas `DestroyConnectionAsync`
// defers the `delete` operation to when the call stack has been unwound.
// Async may be needed when deleting a connection object from within a
// callback.
virtual void DestroyConnection(Connection* conn) = 0;
virtual void DestroyConnectionAsync(Connection* conn) = 0;
// The thread on which this port performs its I/O.
virtual webrtc::TaskQueueBase* thread() = 0;
// The factory used to create the sockets of this port.
virtual rtc::PacketSocketFactory* socket_factory() const = 0;
virtual const std::string& user_agent() = 0;
virtual const rtc::ProxyInfo& proxy() = 0;
// Identifies the generation that this port was created in.
virtual uint32_t generation() const = 0;
virtual void set_generation(uint32_t generation) = 0;
virtual bool send_retransmit_count_attribute() const = 0;
// For debugging purposes.
virtual const std::string& content_name() const = 0;
// Called when the Connection discovers a local peer reflexive candidate.
virtual void AddPrflxCandidate(const Candidate& local) = 0;
// Foundation: An arbitrary string that is the same for two candidates
// that have the same type, base IP address, protocol (UDP, TCP,
// etc.), and STUN or TURN server. If any of these are different,
// then the foundation will be different. Two candidate pairs with
// the same foundation pairs are likely to have similar network
// characteristics. Foundations are used in the frozen algorithm.
virtual std::string ComputeFoundation(
absl::string_view type,
absl::string_view protocol,
absl::string_view relay_protocol,
const rtc::SocketAddress& base_address) = 0;
protected:
PortInterface();
virtual void UpdateNetworkCost() = 0;
// Returns DSCP value packets generated by the port itself should use.
virtual rtc::DiffServCodePoint StunDscpValue() const = 0;
// If the given data comprises a complete and correct STUN message then the
// return value is true, otherwise false. If the message username corresponds
// with this port's username fragment, msg will contain the parsed STUN
// message. Otherwise, the function may send a STUN response internally.
// remote_username contains the remote fragment of the STUN username.
virtual bool GetStunMessage(const char* data,
size_t size,
const rtc::SocketAddress& addr,
std::unique_ptr<IceMessage>* out_msg,
std::string* out_username) = 0;
// This method will return local and remote username fragements from the
// stun username attribute if present.
virtual bool ParseStunUsername(const StunMessage* stun_msg,
std::string* local_username,
std::string* remote_username) const = 0;
virtual std::string CreateStunUsername(
absl::string_view remote_username) const = 0;
virtual bool MaybeIceRoleConflict(const rtc::SocketAddress& addr,
IceMessage* stun_msg,
absl::string_view remote_ufrag) = 0;
virtual int16_t network_cost() const = 0;
// Connection and Port are entangled; functions exposed to Port only
// should not be public.
friend class Connection;
};
} // namespace cricket
#endif // P2P_BASE_PORT_INTERFACE_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,295 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef P2P_BASE_PSEUDO_TCP_H_
#define P2P_BASE_PSEUDO_TCP_H_
#include <stddef.h>
#include <stdint.h>
#include <list>
#include <memory>
#include "rtc_base/synchronization/mutex.h"
#include "rtc_base/system/rtc_export.h"
namespace cricket {
//////////////////////////////////////////////////////////////////////
// IPseudoTcpNotify
//////////////////////////////////////////////////////////////////////
class PseudoTcp;
class IPseudoTcpNotify {
public:
// Notification of tcp events
virtual void OnTcpOpen(PseudoTcp* tcp) = 0;
virtual void OnTcpReadable(PseudoTcp* tcp) = 0;
virtual void OnTcpWriteable(PseudoTcp* tcp) = 0;
virtual void OnTcpClosed(PseudoTcp* tcp, uint32_t error) = 0;
// Write the packet onto the network
enum WriteResult { WR_SUCCESS, WR_TOO_LARGE, WR_FAIL };
virtual WriteResult TcpWritePacket(PseudoTcp* tcp,
const char* buffer,
size_t len) = 0;
protected:
virtual ~IPseudoTcpNotify() {}
};
//////////////////////////////////////////////////////////////////////
// PseudoTcp
//////////////////////////////////////////////////////////////////////
class RTC_EXPORT PseudoTcp {
public:
static uint32_t Now();
PseudoTcp(IPseudoTcpNotify* notify, uint32_t conv);
virtual ~PseudoTcp();
int Connect();
int Recv(char* buffer, size_t len);
int Send(const char* buffer, size_t len);
void Close(bool force);
int GetError();
enum TcpState {
TCP_LISTEN,
TCP_SYN_SENT,
TCP_SYN_RECEIVED,
TCP_ESTABLISHED,
TCP_CLOSED
};
TcpState State() const { return m_state; }
// Call this when the PMTU changes.
void NotifyMTU(uint16_t mtu);
// Call this based on timeout value returned from GetNextClock.
// It's ok to call this too frequently.
void NotifyClock(uint32_t now);
// Call this whenever a packet arrives.
// Returns true if the packet was processed successfully.
bool NotifyPacket(const char* buffer, size_t len);
// Call this to determine the next time NotifyClock should be called.
// Returns false if the socket is ready to be destroyed.
bool GetNextClock(uint32_t now, long& timeout);
// Call these to get/set option values to tailor this PseudoTcp
// instance's behaviour for the kind of data it will carry.
// If an unrecognized option is set or got, an assertion will fire.
//
// Setting options for OPT_RCVBUF or OPT_SNDBUF after Connect() is called
// will result in an assertion.
enum Option {
OPT_NODELAY, // Whether to enable Nagle's algorithm (0 == off)
OPT_ACKDELAY, // The Delayed ACK timeout (0 == off).
OPT_RCVBUF, // Set the receive buffer size, in bytes.
OPT_SNDBUF, // Set the send buffer size, in bytes.
};
void GetOption(Option opt, int* value);
void SetOption(Option opt, int value);
// Returns current congestion window in bytes.
uint32_t GetCongestionWindow() const;
// Returns amount of data in bytes that has been sent, but haven't
// been acknowledged.
uint32_t GetBytesInFlight() const;
// Returns number of bytes that were written in buffer and haven't
// been sent.
uint32_t GetBytesBufferedNotSent() const;
// Returns current round-trip time estimate in milliseconds.
uint32_t GetRoundTripTimeEstimateMs() const;
protected:
enum SendFlags { sfNone, sfDelayedAck, sfImmediateAck };
struct Segment {
uint32_t conv, seq, ack;
uint8_t flags;
uint16_t wnd;
const char* data;
uint32_t len;
uint32_t tsval, tsecr;
};
struct SSegment {
SSegment(uint32_t s, uint32_t l, bool c)
: seq(s), len(l), /*tstamp(0),*/ xmit(0), bCtrl(c) {}
uint32_t seq, len;
// uint32_t tstamp;
uint8_t xmit;
bool bCtrl;
};
typedef std::list<SSegment> SList;
struct RSegment {
uint32_t seq, len;
};
uint32_t queue(const char* data, uint32_t len, bool bCtrl);
// Creates a packet and submits it to the network. This method can either
// send payload or just an ACK packet.
//
// `seq` is the sequence number of this packet.
// `flags` is the flags for sending this packet.
// `offset` is the offset to read from `m_sbuf`.
// `len` is the number of bytes to read from `m_sbuf` as payload. If this
// value is 0 then this is an ACK packet, otherwise this packet has payload.
IPseudoTcpNotify::WriteResult packet(uint32_t seq,
uint8_t flags,
uint32_t offset,
uint32_t len);
bool parse(const uint8_t* buffer, uint32_t size);
void attemptSend(SendFlags sflags = sfNone);
void closedown(uint32_t err = 0);
bool clock_check(uint32_t now, long& nTimeout);
bool process(Segment& seg);
bool transmit(const SList::iterator& seg, uint32_t now);
void adjustMTU();
protected:
// This method is used in test only to query receive buffer state.
bool isReceiveBufferFull() const;
// This method is only used in tests, to disable window scaling
// support for testing backward compatibility.
void disableWindowScale();
private:
// Queue the connect message with TCP options.
void queueConnectMessage();
// Parse TCP options in the header.
void parseOptions(const char* data, uint32_t len);
// Apply a TCP option that has been read from the header.
void applyOption(char kind, const char* data, uint32_t len);
// Apply window scale option.
void applyWindowScaleOption(uint8_t scale_factor);
// Resize the send buffer with `new_size` in bytes.
void resizeSendBuffer(uint32_t new_size);
// Resize the receive buffer with `new_size` in bytes. This call adjusts
// window scale factor `m_swnd_scale` accordingly.
void resizeReceiveBuffer(uint32_t new_size);
class LockedFifoBuffer final {
public:
explicit LockedFifoBuffer(size_t size);
~LockedFifoBuffer();
size_t GetBuffered() const;
bool SetCapacity(size_t size);
bool ReadOffset(void* buffer,
size_t bytes,
size_t offset,
size_t* bytes_read);
bool WriteOffset(const void* buffer,
size_t bytes,
size_t offset,
size_t* bytes_written);
bool Read(void* buffer, size_t bytes, size_t* bytes_read);
bool Write(const void* buffer, size_t bytes, size_t* bytes_written);
void ConsumeReadData(size_t size);
void ConsumeWriteBuffer(size_t size);
bool GetWriteRemaining(size_t* size) const;
private:
bool ReadOffsetLocked(void* buffer,
size_t bytes,
size_t offset,
size_t* bytes_read)
RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
bool WriteOffsetLocked(const void* buffer,
size_t bytes,
size_t offset,
size_t* bytes_written)
RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// the allocated buffer
std::unique_ptr<char[]> buffer_ RTC_GUARDED_BY(mutex_);
// size of the allocated buffer
size_t buffer_length_ RTC_GUARDED_BY(mutex_);
// amount of readable data in the buffer
size_t data_length_ RTC_GUARDED_BY(mutex_);
// offset to the readable data
size_t read_position_ RTC_GUARDED_BY(mutex_);
mutable webrtc::Mutex mutex_;
};
IPseudoTcpNotify* m_notify;
enum Shutdown { SD_NONE, SD_GRACEFUL, SD_FORCEFUL } m_shutdown;
int m_error;
// TCB data
TcpState m_state;
uint32_t m_conv;
bool m_bReadEnable, m_bWriteEnable, m_bOutgoing;
uint32_t m_lasttraffic;
// Incoming data
typedef std::list<RSegment> RList;
RList m_rlist;
uint32_t m_rbuf_len, m_rcv_nxt, m_rcv_wnd, m_lastrecv;
uint8_t m_rwnd_scale; // Window scale factor.
LockedFifoBuffer m_rbuf;
// Outgoing data
SList m_slist;
uint32_t m_sbuf_len, m_snd_nxt, m_snd_wnd, m_lastsend, m_snd_una;
uint8_t m_swnd_scale; // Window scale factor.
LockedFifoBuffer m_sbuf;
// Maximum segment size, estimated protocol level, largest segment sent
uint32_t m_mss, m_msslevel, m_largest, m_mtu_advise;
// Retransmit timer
uint32_t m_rto_base;
// Timestamp tracking
uint32_t m_ts_recent, m_ts_lastack;
// Round-trip calculation
uint32_t m_rx_rttvar, m_rx_srtt, m_rx_rto;
// Congestion avoidance, Fast retransmit/recovery, Delayed ACKs
uint32_t m_ssthresh, m_cwnd;
uint8_t m_dup_acks;
uint32_t m_recover;
uint32_t m_t_ack;
// Configuration options
bool m_use_nagling;
uint32_t m_ack_delay;
// This is used by unit tests to test backward compatibility of
// PseudoTcp implementations that don't support window scaling.
bool m_support_wnd_scale;
};
} // namespace cricket
#endif // P2P_BASE_PSEUDO_TCP_H_

View file

@ -0,0 +1,80 @@
/*
* Copyright 2018 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/regathering_controller.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "api/units/time_delta.h"
namespace webrtc {
BasicRegatheringController::BasicRegatheringController(
const Config& config,
cricket::IceTransportInternal* ice_transport,
rtc::Thread* thread)
: config_(config), ice_transport_(ice_transport), thread_(thread) {
RTC_DCHECK(thread_);
RTC_DCHECK_RUN_ON(thread_);
RTC_DCHECK(ice_transport_);
ice_transport_->SignalStateChanged.connect(
this, &BasicRegatheringController::OnIceTransportStateChanged);
ice_transport->SignalWritableState.connect(
this, &BasicRegatheringController::OnIceTransportWritableState);
ice_transport->SignalReceivingState.connect(
this, &BasicRegatheringController::OnIceTransportReceivingState);
ice_transport->SignalNetworkRouteChanged.connect(
this, &BasicRegatheringController::OnIceTransportNetworkRouteChanged);
}
BasicRegatheringController::~BasicRegatheringController() {
RTC_DCHECK_RUN_ON(thread_);
}
void BasicRegatheringController::Start() {
RTC_DCHECK_RUN_ON(thread_);
ScheduleRecurringRegatheringOnFailedNetworks();
}
void BasicRegatheringController::SetConfig(const Config& config) {
RTC_DCHECK_RUN_ON(thread_);
bool need_reschedule_on_failed_networks =
pending_regathering_ && (config_.regather_on_failed_networks_interval !=
config.regather_on_failed_networks_interval);
config_ = config;
if (need_reschedule_on_failed_networks) {
ScheduleRecurringRegatheringOnFailedNetworks();
}
}
void BasicRegatheringController::
ScheduleRecurringRegatheringOnFailedNetworks() {
RTC_DCHECK_RUN_ON(thread_);
RTC_DCHECK(config_.regather_on_failed_networks_interval >= 0);
// Reset pending_regathering_ to cancel any potentially pending tasks.
pending_regathering_.reset(new ScopedTaskSafety());
thread_->PostDelayedTask(
SafeTask(pending_regathering_->flag(),
[this]() {
RTC_DCHECK_RUN_ON(thread_);
// Only regather when the current session is in the CLEARED
// state (i.e., not running or stopped). It is only
// possible to enter this state when we gather continually,
// so there is an implicit check on continual gathering
// here.
if (allocator_session_ && allocator_session_->IsCleared()) {
allocator_session_->RegatherOnFailedNetworks();
}
ScheduleRecurringRegatheringOnFailedNetworks();
}),
TimeDelta::Millis(config_.regather_on_failed_networks_interval));
}
} // namespace webrtc

View file

@ -0,0 +1,97 @@
/*
* Copyright 2018 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.
*/
#ifndef P2P_BASE_REGATHERING_CONTROLLER_H_
#define P2P_BASE_REGATHERING_CONTROLLER_H_
#include <memory>
#include "api/task_queue/pending_task_safety_flag.h"
#include "p2p/base/ice_transport_internal.h"
#include "p2p/base/port_allocator.h"
#include "rtc_base/thread.h"
namespace webrtc {
// Controls regathering of candidates for the ICE transport passed into it,
// reacting to signals like SignalWritableState, SignalNetworkRouteChange, etc.,
// using methods like GetStats to get additional information, and calling
// methods like RegatherOnFailedNetworks on the PortAllocatorSession when
// regathering is desired.
//
// "Regathering" is defined as gathering additional candidates within a single
// ICE generation (or in other words, PortAllocatorSession), and is possible
// when "continual gathering" is enabled. This may allow connectivity to be
// maintained and/or restored without a full ICE restart.
//
// Regathering will only begin after PortAllocationSession is set via
// set_allocator_session. This should be called any time the "active"
// PortAllocatorSession is changed (in other words, when an ICE restart occurs),
// so that candidates are gathered for the "current" ICE generation.
//
// All methods of BasicRegatheringController should be called on the same
// thread as the one passed to the constructor, and this thread should be the
// same one where PortAllocatorSession runs, which is also identical to the
// network thread of the ICE transport, as given by
// P2PTransportChannel::thread().
class BasicRegatheringController : public sigslot::has_slots<> {
public:
struct Config {
int regather_on_failed_networks_interval =
cricket::REGATHER_ON_FAILED_NETWORKS_INTERVAL;
};
BasicRegatheringController() = delete;
BasicRegatheringController(const Config& config,
cricket::IceTransportInternal* ice_transport,
rtc::Thread* thread);
~BasicRegatheringController() override;
// TODO(qingsi): Remove this method after implementing a new signal in
// P2PTransportChannel and reacting to that signal for the initial schedules
// of regathering.
void Start();
void set_allocator_session(cricket::PortAllocatorSession* allocator_session) {
allocator_session_ = allocator_session;
}
// Setting a different config of the regathering interval range on all
// networks cancels and reschedules the recurring schedules, if any, of
// regathering on all networks. The same applies to the change of the
// regathering interval on the failed networks. This rescheduling behavior is
// seperately defined for the two config parameters.
void SetConfig(const Config& config);
private:
// TODO(qingsi): Implement the following methods and use methods from the ICE
// transport like GetStats to get additional information for the decision
// making in regathering.
void OnIceTransportStateChanged(cricket::IceTransportInternal*) {}
void OnIceTransportWritableState(rtc::PacketTransportInternal*) {}
void OnIceTransportReceivingState(rtc::PacketTransportInternal*) {}
void OnIceTransportNetworkRouteChanged(absl::optional<rtc::NetworkRoute>) {}
// Schedules delayed and repeated regathering of local candidates on failed
// networks, where the delay in milliseconds is given by the config. Each
// repetition is separated by the same delay. When scheduled, all previous
// schedules are canceled.
void ScheduleRecurringRegatheringOnFailedNetworks();
// Cancels regathering scheduled by ScheduleRecurringRegatheringOnAllNetworks.
void CancelScheduledRecurringRegatheringOnAllNetworks();
// We use a flag to be able to cancel pending regathering operations when
// the object goes out of scope or the config changes.
std::unique_ptr<ScopedTaskSafety> pending_regathering_;
Config config_;
cricket::IceTransportInternal* ice_transport_;
cricket::PortAllocatorSession* allocator_session_ = nullptr;
rtc::Thread* const thread_;
};
} // namespace webrtc
#endif // P2P_BASE_REGATHERING_CONTROLLER_H_

View file

@ -0,0 +1,358 @@
/*
* Copyright 2020 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/stun_dictionary.h"
#include <algorithm>
#include <deque>
#include <utility>
#include "rtc_base/logging.h"
namespace cricket {
const StunAddressAttribute* StunDictionaryView::GetAddress(int key) const {
const StunAttribute* attr = GetOrNull(key, STUN_VALUE_ADDRESS);
if (attr == nullptr) {
return nullptr;
}
return reinterpret_cast<const StunAddressAttribute*>(attr);
}
const StunUInt32Attribute* StunDictionaryView::GetUInt32(int key) const {
const StunAttribute* attr = GetOrNull(key, STUN_VALUE_UINT32);
if (attr == nullptr) {
return nullptr;
}
return reinterpret_cast<const StunUInt32Attribute*>(attr);
}
const StunUInt64Attribute* StunDictionaryView::GetUInt64(int key) const {
const StunAttribute* attr = GetOrNull(key, STUN_VALUE_UINT64);
if (attr == nullptr) {
return nullptr;
}
return reinterpret_cast<const StunUInt64Attribute*>(attr);
}
const StunByteStringAttribute* StunDictionaryView::GetByteString(
int key) const {
const StunAttribute* attr = GetOrNull(key, STUN_VALUE_BYTE_STRING);
if (attr == nullptr) {
return nullptr;
}
return reinterpret_cast<const StunByteStringAttribute*>(attr);
}
const StunUInt16ListAttribute* StunDictionaryView::GetUInt16List(
int key) const {
const StunAttribute* attr = GetOrNull(key, STUN_VALUE_UINT16_LIST);
if (attr == nullptr) {
return nullptr;
}
return reinterpret_cast<const StunUInt16ListAttribute*>(attr);
}
const StunAttribute* StunDictionaryView::GetOrNull(
int key,
absl::optional<StunAttributeValueType> type) const {
const auto it = attrs_.find(key);
if (it == attrs_.end()) {
return nullptr;
}
if (type && it->second->value_type() != *type) {
RTC_LOG(LS_WARNING) << "Get key: " << key << " with type: " << *type
<< " found different type: "
<< it->second->value_type();
return nullptr;
}
return (*it).second.get();
}
webrtc::RTCErrorOr<
std::pair<uint64_t, std::deque<std::unique_ptr<StunAttribute>>>>
StunDictionaryView::ParseDelta(const StunByteStringAttribute& delta) {
rtc::ByteBufferReader buf(delta.array_view());
uint16_t magic;
if (!buf.ReadUInt16(&magic)) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Failed to read magic number");
}
if (magic != kDeltaMagic) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Invalid magic number");
}
uint16_t delta_version;
if (!buf.ReadUInt16(&delta_version)) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Failed to read version");
}
if (delta_version != kDeltaVersion) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Unsupported delta version");
}
// Now read all the attributes
std::deque<std::unique_ptr<StunAttribute>> attrs;
while (buf.Length()) {
uint16_t key, length, value_type;
if (!buf.ReadUInt16(&key)) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Failed to read attribute key");
}
if (!buf.ReadUInt16(&length)) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Failed to read attribute length");
}
if (!buf.ReadUInt16(&value_type)) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Failed to read value type");
}
StunAttributeValueType value_type_enum =
static_cast<StunAttributeValueType>(value_type);
std::unique_ptr<StunAttribute> attr(
StunAttribute::Create(value_type_enum, key, length, nullptr));
if (!attr) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Failed to create attribute");
}
if (attr->length() != length) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Inconsistent attribute length");
}
if (!attr->Read(&buf)) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Failed to read attribute content");
}
attrs.push_back(std::move(attr));
}
// The first attribute should be the version...
if (attrs.empty()) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Empty delta!");
}
if (attrs[0]->type() != kVersionKey ||
attrs[0]->value_type() != STUN_VALUE_UINT64) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Missing version!");
}
uint64_t version_in_delta =
reinterpret_cast<const StunUInt64Attribute*>(attrs[0].get())->value();
attrs.pop_front();
return std::make_pair(std::max(version_in_delta, version_in_delta),
std::move(attrs));
}
// Apply a delta return an StunUInt64Attribute to ack the update.
webrtc::RTCErrorOr<
std::pair<std::unique_ptr<StunUInt64Attribute>, std::vector<uint16_t>>>
StunDictionaryView::ApplyDelta(const StunByteStringAttribute& delta) {
auto parsed_delta = ParseDelta(delta);
if (!parsed_delta.ok()) {
return webrtc::RTCError(parsed_delta.error());
}
uint64_t version_in_delta = parsed_delta.value().first;
// Check that update does not overflow max_bytes_stored_.
int new_bytes_stored = bytes_stored_;
for (auto& attr : parsed_delta.value().second) {
auto old_version = version_per_key_.find(attr->type());
if (old_version == version_per_key_.end() ||
version_in_delta > old_version->second) {
size_t new_length = attr->length();
size_t old_length = GetLength(attr->type());
if (old_version == version_per_key_.end()) {
new_length += sizeof(int64_t);
}
new_bytes_stored = new_bytes_stored + new_length - old_length;
if (new_bytes_stored <= 0) {
RTC_LOG(LS_WARNING)
<< "attr: " << attr->type() << " old_length: " << old_length
<< " new_length: " << new_length
<< " bytes_stored_: " << bytes_stored_
<< " new_bytes_stored: " << new_bytes_stored;
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER);
}
if (new_bytes_stored > max_bytes_stored_) {
RTC_LOG(LS_INFO) << "attr: " << attr->type()
<< " old_length: " << old_length
<< " new_length: " << new_length
<< " bytes_stored_: " << bytes_stored_
<< " new_bytes_stored: " << new_bytes_stored;
}
}
}
if (new_bytes_stored > max_bytes_stored_) {
RTC_LOG(LS_INFO) << " bytes_stored_: " << bytes_stored_
<< " new_bytes_stored: " << new_bytes_stored;
return webrtc::RTCError(webrtc::RTCErrorType::RESOURCE_EXHAUSTED);
}
// Apply the update.
std::vector<uint16_t> keys;
for (auto& attr : parsed_delta.value().second) {
if (version_in_delta > version_per_key_[attr->type()]) {
version_per_key_[attr->type()] = version_in_delta;
keys.push_back(attr->type());
if (attr->value_type() == STUN_VALUE_BYTE_STRING && attr->length() == 0) {
attrs_.erase(attr->type());
} else {
int attribute_type = attr->type();
attrs_[attribute_type] = std::move(attr);
}
}
}
bytes_stored_ = new_bytes_stored;
return std::make_pair(std::make_unique<StunUInt64Attribute>(
STUN_ATTR_GOOG_DELTA_ACK, version_in_delta),
std::move(keys));
}
size_t StunDictionaryView::GetLength(int key) const {
auto attr = GetOrNull(key);
if (attr != nullptr) {
return attr->length();
}
return 0;
}
void StunDictionaryWriter::Disable() {
disabled_ = true;
}
void StunDictionaryWriter::Delete(int key) {
if (disabled_) {
return;
}
if (dictionary_) {
if (dictionary_->attrs_.find(key) == dictionary_->attrs_.end()) {
return;
}
}
// remove any pending updates.
pending_.erase(
std::remove_if(pending_.begin(), pending_.end(),
[key](const auto& p) { return p.second->type() == key; }),
pending_.end());
// Create tombstone.
auto tombstone = std::make_unique<StunByteStringAttribute>(key);
// add a pending entry.
pending_.push_back(std::make_pair(++version_, tombstone.get()));
// store the tombstone.
tombstones_[key] = std::move(tombstone);
if (dictionary_) {
// remove value
dictionary_->attrs_.erase(key);
}
}
void StunDictionaryWriter::Set(std::unique_ptr<StunAttribute> attr) {
if (disabled_) {
return;
}
int key = attr->type();
// remove any pending updates.
pending_.erase(
std::remove_if(pending_.begin(), pending_.end(),
[key](const auto& p) { return p.second->type() == key; }),
pending_.end());
// remove any existing key.
tombstones_.erase(key);
// create pending entry.
pending_.push_back(std::make_pair(++version_, attr.get()));
if (dictionary_) {
// store attribute.
dictionary_->attrs_[key] = std::move(attr);
}
}
// Create an StunByteStringAttribute containing the pending (e.g not ack:ed)
// modifications.
std::unique_ptr<StunByteStringAttribute> StunDictionaryWriter::CreateDelta() {
if (disabled_) {
return nullptr;
}
if (pending_.empty()) {
return nullptr;
}
rtc::ByteBufferWriter buf;
buf.WriteUInt16(StunDictionaryView::kDeltaMagic); // 0,1
buf.WriteUInt16(StunDictionaryView::kDeltaVersion); // 2,3
// max version in Delta.
buf.WriteUInt16(StunDictionaryView::kVersionKey); // 4,5
buf.WriteUInt16(8); // 6,7
buf.WriteUInt16(STUN_VALUE_UINT64); // 8,9
buf.WriteUInt64(pending_.back().first); // 10-17
// attributes
for (const auto& attr : pending_) {
buf.WriteUInt16(attr.second->type());
buf.WriteUInt16(static_cast<uint16_t>(attr.second->length()));
buf.WriteUInt16(attr.second->value_type());
if (!attr.second->Write(&buf)) {
RTC_LOG(LS_ERROR) << "Failed to write key: " << attr.second->type();
return nullptr;
}
}
return std::make_unique<StunByteStringAttribute>(STUN_ATTR_GOOG_DELTA,
buf.Data(), buf.Length());
}
// Apply a delta ack, i.e prune list of pending changes.
void StunDictionaryWriter::ApplyDeltaAck(const StunUInt64Attribute& ack) {
uint64_t acked_version = ack.value();
auto entries_to_remove = std::remove_if(
pending_.begin(), pending_.end(),
[acked_version](const auto& p) { return p.first <= acked_version; });
// remove tombstones.
for (auto it = entries_to_remove; it != pending_.end(); ++it) {
tombstones_.erase((*it).second->type());
}
pending_.erase(entries_to_remove, pending_.end());
}
// Check if a key has a pending change (i.e a change
// that has not been acked).
bool StunDictionaryWriter::Pending(int key) const {
for (const auto& attr : pending_) {
if (attr.second->type() == key) {
return true;
}
}
return false;
}
int StunDictionaryWriter::Pending() const {
return pending_.size();
}
} // namespace cricket

View file

@ -0,0 +1,204 @@
/*
* Copyright 2020 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.
*/
#ifndef P2P_BASE_STUN_DICTIONARY_H_
#define P2P_BASE_STUN_DICTIONARY_H_
#include <deque>
#include <map>
#include <memory>
#include <utility>
#include <vector>
#include "api/rtc_error.h"
#include "api/transport/stun.h"
namespace cricket {
// A StunDictionaryView is a dictionary of StunAttributes.
// - the StunAttributes can be read using the |Get|-methods.
// - the dictionary is updated by using the |ApplyDelta|-method.
//
// A StunDictionaryWriter is used to create |delta|s for the |ApplyDelta|-method
// - It keeps track of which updates has been applied at StunDictionaryView.
// - It optionally keeps a local StunDictionaryView contains modification made
// `locally`
//
// A pair StunDictionaryView(A)/StunDictionaryWriter(B) are linked so that
// modifications to B is transfered to A using the STUN_ATTR_GOOG_DELTA
// (StunByteStringAttribute) and the modification is ack:ed using
// STUN_ATTR_GOOG_DELTA_ACK (StunUInt64Attribute).
//
// Note:
// 1) It is possible to update one StunDictionaryView from multiple writers,
// but this only works of the different writers write disjoint keys (which
// is not checked/enforced by these classes).
// 2) The opposite, one writer updating multiple StunDictionaryView, is not
// possible.
class StunDictionaryView {
public:
// A reserved key used to transport the version number
static constexpr uint16_t kVersionKey = 0xFFFF;
// A magic number used when transporting deltas.
static constexpr uint16_t kDeltaMagic = 0x7788;
// The version number for the delta format.
static constexpr uint16_t kDeltaVersion = 0x1;
// Gets the desired attribute value, or NULL if no such attribute type exists.
// The pointer returned is guaranteed to be valid until ApplyDelta is called.
const StunAddressAttribute* GetAddress(int key) const;
const StunUInt32Attribute* GetUInt32(int key) const;
const StunUInt64Attribute* GetUInt64(int key) const;
const StunByteStringAttribute* GetByteString(int key) const;
const StunUInt16ListAttribute* GetUInt16List(int key) const;
bool empty() const { return attrs_.empty(); }
size_t size() const { return attrs_.size(); }
int bytes_stored() const { return bytes_stored_; }
void set_max_bytes_stored(int max_bytes_stored) {
max_bytes_stored_ = max_bytes_stored;
}
// Apply a delta and return
// a pair with
// - StunUInt64Attribute to ack the |delta|.
// - vector of keys that was modified.
webrtc::RTCErrorOr<
std::pair<std::unique_ptr<StunUInt64Attribute>, std::vector<uint16_t>>>
ApplyDelta(const StunByteStringAttribute& delta);
private:
friend class StunDictionaryWriter;
const StunAttribute* GetOrNull(
int key,
absl::optional<StunAttributeValueType> = absl::nullopt) const;
size_t GetLength(int key) const;
static webrtc::RTCErrorOr<
std::pair<uint64_t, std::deque<std::unique_ptr<StunAttribute>>>>
ParseDelta(const StunByteStringAttribute& delta);
std::map<uint16_t, std::unique_ptr<StunAttribute>> attrs_;
std::map<uint16_t, uint64_t> version_per_key_;
int max_bytes_stored_ = 16384;
int bytes_stored_ = 0;
};
class StunDictionaryWriter {
public:
StunDictionaryWriter() {
dictionary_ = std::make_unique<StunDictionaryView>();
}
explicit StunDictionaryWriter(
std::unique_ptr<StunDictionaryView> dictionary) {
dictionary_ = std::move(dictionary);
}
// A pending modification.
template <typename T>
class Modification {
public:
~Modification() { commit(); }
T* operator->() { return attr_.get(); }
void abort() { attr_ = nullptr; }
void commit() {
if (attr_) {
writer_->Set(std::move(attr_));
}
}
private:
friend class StunDictionaryWriter;
Modification(StunDictionaryWriter* writer, std::unique_ptr<T> attr)
: writer_(writer), attr_(std::move(attr)) {}
StunDictionaryWriter* writer_;
std::unique_ptr<T> attr_;
Modification(const Modification<T>&) =
delete; // not copyable (but movable).
Modification& operator=(Modification<T>&) =
delete; // not copyable (but movable).
};
// Record a modification.
Modification<StunAddressAttribute> SetAddress(int key) {
return Modification<StunAddressAttribute>(
this, StunAttribute::CreateAddress(key));
}
Modification<StunUInt32Attribute> SetUInt32(int key) {
return Modification<StunUInt32Attribute>(this,
StunAttribute::CreateUInt32(key));
}
Modification<StunUInt64Attribute> SetUInt64(int key) {
return Modification<StunUInt64Attribute>(this,
StunAttribute::CreateUInt64(key));
}
Modification<StunByteStringAttribute> SetByteString(int key) {
return Modification<StunByteStringAttribute>(
this, StunAttribute::CreateByteString(key));
}
Modification<StunUInt16ListAttribute> SetUInt16List(int key) {
return Modification<StunUInt16ListAttribute>(
this, StunAttribute::CreateUInt16ListAttribute(key));
}
// Delete a key.
void Delete(int key);
// Check if a key has a pending change (i.e a change
// that has not been acked).
bool Pending(int key) const;
// Return number of of pending modifications.
int Pending() const;
// Create an StunByteStringAttribute containing the pending (e.g not ack:ed)
// modifications.
std::unique_ptr<StunByteStringAttribute> CreateDelta();
// Apply an delta ack.
void ApplyDeltaAck(const StunUInt64Attribute&);
// Return pointer to (optional) StunDictionaryView.
const StunDictionaryView* dictionary() { return dictionary_.get(); }
const StunDictionaryView* operator->() { return dictionary_.get(); }
// Disable writer,
// i.e CreateDelta always return null, and no modifications are made.
// This is called if remote peer does not support GOOG_DELTA.
void Disable();
bool disabled() const { return disabled_; }
private:
void Set(std::unique_ptr<StunAttribute> attr);
bool disabled_ = false;
// version of modification.
int64_t version_ = 1;
// (optional) StunDictionaryView.
std::unique_ptr<StunDictionaryView> dictionary_;
// sorted list of changes that has not been yet been ack:ed.
std::vector<std::pair<uint64_t, const StunAttribute*>> pending_;
// tombstones, i.e values that has been deleted but not yet acked.
std::map<uint16_t, std::unique_ptr<StunAttribute>> tombstones_;
};
} // namespace cricket
#endif // P2P_BASE_STUN_DICTIONARY_H_

View file

@ -0,0 +1,673 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "p2p/base/stun_port.h"
#include <utility>
#include <vector>
#include "absl/memory/memory.h"
#include "absl/strings/string_view.h"
#include "api/transport/stun.h"
#include "p2p/base/connection.h"
#include "p2p/base/p2p_constants.h"
#include "p2p/base/port_allocator.h"
#include "rtc_base/checks.h"
#include "rtc_base/experiments/field_trial_parser.h"
#include "rtc_base/helpers.h"
#include "rtc_base/ip_address.h"
#include "rtc_base/logging.h"
#include "rtc_base/network/received_packet.h"
#include "rtc_base/strings/string_builder.h"
namespace cricket {
// TODO(?): Move these to a common place (used in relayport too)
const int RETRY_TIMEOUT = 50 * 1000; // 50 seconds
// Stop logging errors in UDPPort::SendTo after we have logged
// `kSendErrorLogLimit` messages. Start again after a successful send.
const int kSendErrorLogLimit = 5;
// Handles a binding request sent to the STUN server.
class StunBindingRequest : public StunRequest {
public:
StunBindingRequest(UDPPort* port,
const rtc::SocketAddress& addr,
int64_t start_time)
: StunRequest(port->request_manager(),
std::make_unique<StunMessage>(STUN_BINDING_REQUEST)),
port_(port),
server_addr_(addr),
start_time_(start_time) {
SetAuthenticationRequired(false);
}
const rtc::SocketAddress& server_addr() const { return server_addr_; }
void OnResponse(StunMessage* response) override {
const StunAddressAttribute* addr_attr =
response->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
if (!addr_attr) {
RTC_LOG(LS_ERROR) << "Binding response missing mapped address.";
} else if (addr_attr->family() != STUN_ADDRESS_IPV4 &&
addr_attr->family() != STUN_ADDRESS_IPV6) {
RTC_LOG(LS_ERROR) << "Binding address has bad family";
} else {
rtc::SocketAddress addr(addr_attr->ipaddr(), addr_attr->port());
port_->OnStunBindingRequestSucceeded(this->Elapsed(), server_addr_, addr);
}
// The keep-alive requests will be stopped after its lifetime has passed.
if (WithinLifetime(rtc::TimeMillis())) {
port_->request_manager_.SendDelayed(
new StunBindingRequest(port_, server_addr_, start_time_),
port_->stun_keepalive_delay());
}
}
void OnErrorResponse(StunMessage* response) override {
const StunErrorCodeAttribute* attr = response->GetErrorCode();
if (!attr) {
RTC_LOG(LS_ERROR) << "Missing binding response error code.";
} else {
RTC_LOG(LS_ERROR) << "Binding error response:"
" class="
<< attr->eclass() << " number=" << attr->number()
<< " reason=" << attr->reason();
}
port_->OnStunBindingOrResolveRequestFailed(
server_addr_, attr ? attr->number() : STUN_ERROR_GLOBAL_FAILURE,
attr ? attr->reason()
: "STUN binding response with no error code attribute.");
int64_t now = rtc::TimeMillis();
if (WithinLifetime(now) &&
rtc::TimeDiff(now, start_time_) < RETRY_TIMEOUT) {
port_->request_manager_.SendDelayed(
new StunBindingRequest(port_, server_addr_, start_time_),
port_->stun_keepalive_delay());
}
}
void OnTimeout() override {
RTC_LOG(LS_ERROR) << "Binding request timed out from "
<< port_->GetLocalAddress().ToSensitiveString() << " ("
<< port_->Network()->name() << ")";
port_->OnStunBindingOrResolveRequestFailed(
server_addr_, SERVER_NOT_REACHABLE_ERROR,
"STUN binding request timed out.");
}
private:
// Returns true if `now` is within the lifetime of the request (a negative
// lifetime means infinite).
bool WithinLifetime(int64_t now) const {
int lifetime = port_->stun_keepalive_lifetime();
return lifetime < 0 || rtc::TimeDiff(now, start_time_) <= lifetime;
}
UDPPort* port_;
const rtc::SocketAddress server_addr_;
int64_t start_time_;
};
UDPPort::AddressResolver::AddressResolver(
rtc::PacketSocketFactory* factory,
std::function<void(const rtc::SocketAddress&, int)> done_callback)
: socket_factory_(factory), done_(std::move(done_callback)) {}
void UDPPort::AddressResolver::Resolve(
const rtc::SocketAddress& address,
int family,
const webrtc::FieldTrialsView& field_trials) {
if (resolvers_.find(address) != resolvers_.end())
return;
auto resolver = socket_factory_->CreateAsyncDnsResolver();
auto resolver_ptr = resolver.get();
std::pair<rtc::SocketAddress,
std::unique_ptr<webrtc::AsyncDnsResolverInterface>>
pair = std::make_pair(address, std::move(resolver));
resolvers_.insert(std::move(pair));
auto callback = [this, address] {
ResolverMap::const_iterator it = resolvers_.find(address);
if (it != resolvers_.end()) {
done_(it->first, it->second->result().GetError());
}
};
resolver_ptr->Start(address, family, std::move(callback));
}
bool UDPPort::AddressResolver::GetResolvedAddress(
const rtc::SocketAddress& input,
int family,
rtc::SocketAddress* output) const {
ResolverMap::const_iterator it = resolvers_.find(input);
if (it == resolvers_.end())
return false;
return it->second->result().GetResolvedAddress(family, output);
}
UDPPort::UDPPort(rtc::Thread* thread,
absl::string_view type,
rtc::PacketSocketFactory* factory,
const rtc::Network* network,
rtc::AsyncPacketSocket* socket,
absl::string_view username,
absl::string_view password,
bool emit_local_for_anyaddress,
const webrtc::FieldTrialsView* field_trials)
: Port(thread, type, factory, network, username, password, field_trials),
request_manager_(
thread,
[this](const void* data, size_t size, StunRequest* request) {
OnSendPacket(data, size, request);
}),
socket_(socket),
error_(0),
ready_(false),
stun_keepalive_delay_(STUN_KEEPALIVE_INTERVAL),
dscp_(rtc::DSCP_NO_CHANGE),
emit_local_for_anyaddress_(emit_local_for_anyaddress) {}
UDPPort::UDPPort(rtc::Thread* thread,
absl::string_view type,
rtc::PacketSocketFactory* factory,
const rtc::Network* network,
uint16_t min_port,
uint16_t max_port,
absl::string_view username,
absl::string_view password,
bool emit_local_for_anyaddress,
const webrtc::FieldTrialsView* field_trials)
: Port(thread,
type,
factory,
network,
min_port,
max_port,
username,
password,
field_trials),
request_manager_(
thread,
[this](const void* data, size_t size, StunRequest* request) {
OnSendPacket(data, size, request);
}),
socket_(nullptr),
error_(0),
ready_(false),
stun_keepalive_delay_(STUN_KEEPALIVE_INTERVAL),
dscp_(rtc::DSCP_NO_CHANGE),
emit_local_for_anyaddress_(emit_local_for_anyaddress) {}
bool UDPPort::Init() {
stun_keepalive_lifetime_ = GetStunKeepaliveLifetime();
if (!SharedSocket()) {
RTC_DCHECK(socket_ == nullptr);
socket_ = socket_factory()->CreateUdpSocket(
rtc::SocketAddress(Network()->GetBestIP(), 0), min_port(), max_port());
if (!socket_) {
RTC_LOG(LS_WARNING) << ToString() << ": UDP socket creation failed";
return false;
}
socket_->RegisterReceivedPacketCallback(
[&](rtc::AsyncPacketSocket* socket, const rtc::ReceivedPacket& packet) {
OnReadPacket(socket, packet);
});
}
socket_->SignalSentPacket.connect(this, &UDPPort::OnSentPacket);
socket_->SignalReadyToSend.connect(this, &UDPPort::OnReadyToSend);
socket_->SignalAddressReady.connect(this, &UDPPort::OnLocalAddressReady);
return true;
}
UDPPort::~UDPPort() {
if (!SharedSocket())
delete socket_;
}
void UDPPort::PrepareAddress() {
RTC_DCHECK(request_manager_.empty());
if (socket_->GetState() == rtc::AsyncPacketSocket::STATE_BOUND) {
OnLocalAddressReady(socket_, socket_->GetLocalAddress());
}
}
void UDPPort::MaybePrepareStunCandidate() {
// Sending binding request to the STUN server if address is available to
// prepare STUN candidate.
if (!server_addresses_.empty()) {
SendStunBindingRequests();
} else {
// Port is done allocating candidates.
MaybeSetPortCompleteOrError();
}
}
Connection* UDPPort::CreateConnection(const Candidate& address,
CandidateOrigin origin) {
if (!SupportsProtocol(address.protocol())) {
return nullptr;
}
if (!IsCompatibleAddress(address.address())) {
return nullptr;
}
// In addition to DCHECK-ing the non-emptiness of local candidates, we also
// skip this Port with null if there are latent bugs to violate it; otherwise
// it would lead to a crash when accessing the local candidate of the
// connection that would be created below.
if (Candidates().empty()) {
RTC_DCHECK_NOTREACHED();
return nullptr;
}
// When the socket is shared, the srflx candidate is gathered by the UDPPort.
// The assumption here is that
// 1) if the IP concealment with mDNS is not enabled, the gathering of the
// host candidate of this port (which is synchronous),
// 2) or otherwise if enabled, the start of name registration of the host
// candidate (as the start of asynchronous gathering)
// is always before the gathering of a srflx candidate (and any prflx
// candidate).
//
// See also the definition of MdnsNameRegistrationStatus::kNotStarted in
// port.h.
RTC_DCHECK(!SharedSocket() || Candidates()[0].is_local() ||
mdns_name_registration_status() !=
MdnsNameRegistrationStatus::kNotStarted);
Connection* conn = new ProxyConnection(NewWeakPtr(), 0, address);
AddOrReplaceConnection(conn);
return conn;
}
int UDPPort::SendTo(const void* data,
size_t size,
const rtc::SocketAddress& addr,
const rtc::PacketOptions& options,
bool payload) {
rtc::PacketOptions modified_options(options);
CopyPortInformationToPacketInfo(&modified_options.info_signaled_after_sent);
int sent = socket_->SendTo(data, size, addr, modified_options);
if (sent < 0) {
error_ = socket_->GetError();
// Rate limiting added for crbug.com/856088.
// TODO(webrtc:9622): Use general rate limiting mechanism once it exists.
if (send_error_count_ < kSendErrorLogLimit) {
++send_error_count_;
RTC_LOG(LS_ERROR) << ToString() << ": UDP send of " << size
<< " bytes to host "
<< addr.ToSensitiveNameAndAddressString()
<< " failed with error " << error_;
}
} else {
send_error_count_ = 0;
}
return sent;
}
void UDPPort::UpdateNetworkCost() {
Port::UpdateNetworkCost();
stun_keepalive_lifetime_ = GetStunKeepaliveLifetime();
}
rtc::DiffServCodePoint UDPPort::StunDscpValue() const {
return dscp_;
}
int UDPPort::SetOption(rtc::Socket::Option opt, int value) {
if (opt == rtc::Socket::OPT_DSCP) {
// Save value for future packets we instantiate.
dscp_ = static_cast<rtc::DiffServCodePoint>(value);
}
return socket_->SetOption(opt, value);
}
int UDPPort::GetOption(rtc::Socket::Option opt, int* value) {
return socket_->GetOption(opt, value);
}
int UDPPort::GetError() {
return error_;
}
bool UDPPort::HandleIncomingPacket(rtc::AsyncPacketSocket* socket,
const rtc::ReceivedPacket& packet) {
// All packets given to UDP port will be consumed.
OnReadPacket(socket, packet);
return true;
}
bool UDPPort::SupportsProtocol(absl::string_view protocol) const {
return protocol == UDP_PROTOCOL_NAME;
}
ProtocolType UDPPort::GetProtocol() const {
return PROTO_UDP;
}
void UDPPort::GetStunStats(absl::optional<StunStats>* stats) {
*stats = stats_;
}
void UDPPort::set_stun_keepalive_delay(const absl::optional<int>& delay) {
stun_keepalive_delay_ = delay.value_or(STUN_KEEPALIVE_INTERVAL);
}
void UDPPort::OnLocalAddressReady(rtc::AsyncPacketSocket* socket,
const rtc::SocketAddress& address) {
// When adapter enumeration is disabled and binding to the any address, the
// default local address will be issued as a candidate instead if
// `emit_local_for_anyaddress` is true. This is to allow connectivity for
// applications which absolutely requires a HOST candidate.
rtc::SocketAddress addr = address;
// If MaybeSetDefaultLocalAddress fails, we keep the "any" IP so that at
// least the port is listening.
MaybeSetDefaultLocalAddress(&addr);
AddAddress(addr, addr, rtc::SocketAddress(), UDP_PROTOCOL_NAME, "", "",
LOCAL_PORT_TYPE, ICE_TYPE_PREFERENCE_HOST, 0, "", false);
MaybePrepareStunCandidate();
}
void UDPPort::PostAddAddress(bool is_final) {
MaybeSetPortCompleteOrError();
}
void UDPPort::OnReadPacket(rtc::AsyncPacketSocket* socket,
const rtc::ReceivedPacket& packet) {
RTC_DCHECK(socket == socket_);
RTC_DCHECK(!packet.source_address().IsUnresolvedIP());
// Look for a response from the STUN server.
// Even if the response doesn't match one of our outstanding requests, we
// will eat it because it might be a response to a retransmitted packet, and
// we already cleared the request when we got the first response.
if (server_addresses_.find(packet.source_address()) !=
server_addresses_.end()) {
request_manager_.CheckResponse(
reinterpret_cast<const char*>(packet.payload().data()),
packet.payload().size());
return;
}
if (Connection* conn = GetConnection(packet.source_address())) {
conn->OnReadPacket(packet);
} else {
Port::OnReadPacket(packet, PROTO_UDP);
}
}
void UDPPort::OnSentPacket(rtc::AsyncPacketSocket* socket,
const rtc::SentPacket& sent_packet) {
PortInterface::SignalSentPacket(sent_packet);
}
void UDPPort::OnReadyToSend(rtc::AsyncPacketSocket* socket) {
Port::OnReadyToSend();
}
void UDPPort::SendStunBindingRequests() {
// We will keep pinging the stun server to make sure our NAT pin-hole stays
// open until the deadline (specified in SendStunBindingRequest).
RTC_DCHECK(request_manager_.empty());
for (ServerAddresses::const_iterator it = server_addresses_.begin();
it != server_addresses_.end();) {
// sending a STUN binding request may cause the current SocketAddress to be
// erased from the set, invalidating the loop iterator before it is
// incremented (even if the SocketAddress itself still exists). So make a
// copy of the loop iterator, which may be safely invalidated.
ServerAddresses::const_iterator addr = it++;
SendStunBindingRequest(*addr);
}
}
void UDPPort::ResolveStunAddress(const rtc::SocketAddress& stun_addr) {
if (!resolver_) {
resolver_.reset(new AddressResolver(
socket_factory(), [&](const rtc::SocketAddress& input, int error) {
OnResolveResult(input, error);
}));
}
RTC_LOG(LS_INFO) << ToString() << ": Starting STUN host lookup for "
<< stun_addr.ToSensitiveString();
resolver_->Resolve(stun_addr, Network()->family(), field_trials());
}
void UDPPort::OnResolveResult(const rtc::SocketAddress& input, int error) {
RTC_DCHECK(resolver_.get() != nullptr);
rtc::SocketAddress resolved;
if (error != 0 || !resolver_->GetResolvedAddress(
input, Network()->GetBestIP().family(), &resolved)) {
RTC_LOG(LS_WARNING) << ToString()
<< ": StunPort: stun host lookup received error "
<< error;
OnStunBindingOrResolveRequestFailed(input, SERVER_NOT_REACHABLE_ERROR,
"STUN host lookup received error.");
return;
}
server_addresses_.erase(input);
if (server_addresses_.find(resolved) == server_addresses_.end()) {
server_addresses_.insert(resolved);
SendStunBindingRequest(resolved);
}
}
void UDPPort::SendStunBindingRequest(const rtc::SocketAddress& stun_addr) {
if (stun_addr.IsUnresolvedIP()) {
ResolveStunAddress(stun_addr);
} else if (socket_->GetState() == rtc::AsyncPacketSocket::STATE_BOUND) {
// Check if `server_addr_` is compatible with the port's ip.
if (IsCompatibleAddress(stun_addr)) {
request_manager_.Send(
new StunBindingRequest(this, stun_addr, rtc::TimeMillis()));
} else {
// Since we can't send stun messages to the server, we should mark this
// port ready.
const char* reason = "STUN server address is incompatible.";
RTC_LOG(LS_WARNING) << reason;
OnStunBindingOrResolveRequestFailed(stun_addr, SERVER_NOT_REACHABLE_ERROR,
reason);
}
}
}
bool UDPPort::MaybeSetDefaultLocalAddress(rtc::SocketAddress* addr) const {
if (!addr->IsAnyIP() || !emit_local_for_anyaddress_ ||
!Network()->default_local_address_provider()) {
return true;
}
rtc::IPAddress default_address;
bool result =
Network()->default_local_address_provider()->GetDefaultLocalAddress(
addr->family(), &default_address);
if (!result || default_address.IsNil()) {
return false;
}
addr->SetIP(default_address);
return true;
}
void UDPPort::OnStunBindingRequestSucceeded(
int rtt_ms,
const rtc::SocketAddress& stun_server_addr,
const rtc::SocketAddress& stun_reflected_addr) {
RTC_DCHECK(stats_.stun_binding_responses_received <
stats_.stun_binding_requests_sent);
stats_.stun_binding_responses_received++;
stats_.stun_binding_rtt_ms_total += rtt_ms;
stats_.stun_binding_rtt_ms_squared_total += rtt_ms * rtt_ms;
if (bind_request_succeeded_servers_.find(stun_server_addr) !=
bind_request_succeeded_servers_.end()) {
return;
}
bind_request_succeeded_servers_.insert(stun_server_addr);
// If socket is shared and `stun_reflected_addr` is equal to local socket
// address and mDNS obfuscation is not enabled, or if the same address has
// been added by another STUN server, then discarding the stun address.
// For STUN, related address is the local socket address.
if ((!SharedSocket() || stun_reflected_addr != socket_->GetLocalAddress() ||
Network()->GetMdnsResponder() != nullptr) &&
!HasStunCandidateWithAddress(stun_reflected_addr)) {
rtc::SocketAddress related_address = socket_->GetLocalAddress();
// If we can't stamp the related address correctly, empty it to avoid leak.
if (!MaybeSetDefaultLocalAddress(&related_address)) {
related_address =
rtc::EmptySocketAddressWithFamily(related_address.family());
}
rtc::StringBuilder url;
url << "stun:" << stun_server_addr.hostname() << ":"
<< stun_server_addr.port();
AddAddress(stun_reflected_addr, socket_->GetLocalAddress(), related_address,
UDP_PROTOCOL_NAME, "", "", STUN_PORT_TYPE,
ICE_TYPE_PREFERENCE_SRFLX, 0, url.str(), false);
}
MaybeSetPortCompleteOrError();
}
void UDPPort::OnStunBindingOrResolveRequestFailed(
const rtc::SocketAddress& stun_server_addr,
int error_code,
absl::string_view reason) {
rtc::StringBuilder url;
url << "stun:" << stun_server_addr.ToString();
SignalCandidateError(
this, IceCandidateErrorEvent(GetLocalAddress().HostAsSensitiveURIString(),
GetLocalAddress().port(), url.str(),
error_code, reason));
if (bind_request_failed_servers_.find(stun_server_addr) !=
bind_request_failed_servers_.end()) {
return;
}
bind_request_failed_servers_.insert(stun_server_addr);
MaybeSetPortCompleteOrError();
}
void UDPPort::MaybeSetPortCompleteOrError() {
if (mdns_name_registration_status() ==
MdnsNameRegistrationStatus::kInProgress) {
return;
}
if (ready_) {
return;
}
// Do not set port ready if we are still waiting for bind responses.
const size_t servers_done_bind_request =
bind_request_failed_servers_.size() +
bind_request_succeeded_servers_.size();
if (server_addresses_.size() != servers_done_bind_request) {
return;
}
// Setting ready status.
ready_ = true;
// The port is "completed" if there is no stun server provided, or the bind
// request succeeded for any stun server, or the socket is shared.
if (server_addresses_.empty() || bind_request_succeeded_servers_.size() > 0 ||
SharedSocket()) {
SignalPortComplete(this);
} else {
SignalPortError(this);
}
}
// TODO(?): merge this with SendTo above.
void UDPPort::OnSendPacket(const void* data, size_t size, StunRequest* req) {
StunBindingRequest* sreq = static_cast<StunBindingRequest*>(req);
rtc::PacketOptions options(StunDscpValue());
options.info_signaled_after_sent.packet_type = rtc::PacketType::kStunMessage;
CopyPortInformationToPacketInfo(&options.info_signaled_after_sent);
if (socket_->SendTo(data, size, sreq->server_addr(), options) < 0) {
RTC_LOG_ERR_EX(LS_ERROR, socket_->GetError())
<< "UDP send of " << size << " bytes to host "
<< sreq->server_addr().ToSensitiveNameAndAddressString()
<< " failed with error " << error_;
}
stats_.stun_binding_requests_sent++;
}
bool UDPPort::HasStunCandidateWithAddress(
const rtc::SocketAddress& addr) const {
const std::vector<Candidate>& existing_candidates = Candidates();
std::vector<Candidate>::const_iterator it = existing_candidates.begin();
for (; it != existing_candidates.end(); ++it) {
if (it->is_stun() && it->address() == addr)
return true;
}
return false;
}
std::unique_ptr<StunPort> StunPort::Create(
rtc::Thread* 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 ServerAddresses& servers,
absl::optional<int> stun_keepalive_interval,
const webrtc::FieldTrialsView* field_trials) {
// Using `new` to access a non-public constructor.
auto port = absl::WrapUnique(new StunPort(thread, factory, network, min_port,
max_port, username, password,
servers, field_trials));
port->set_stun_keepalive_delay(stun_keepalive_interval);
if (!port->Init()) {
return nullptr;
}
return port;
}
StunPort::StunPort(rtc::Thread* 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 ServerAddresses& servers,
const webrtc::FieldTrialsView* field_trials)
: UDPPort(thread,
STUN_PORT_TYPE,
factory,
network,
min_port,
max_port,
username,
password,
false,
field_trials) {
set_server_addresses(servers);
}
void StunPort::PrepareAddress() {
SendStunBindingRequests();
}
} // namespace cricket

View file

@ -0,0 +1,298 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef P2P_BASE_STUN_PORT_H_
#define P2P_BASE_STUN_PORT_H_
#include <functional>
#include <map>
#include <memory>
#include <string>
#include "absl/memory/memory.h"
#include "absl/strings/string_view.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "p2p/base/port.h"
#include "p2p/base/stun_request.h"
#include "rtc_base/async_packet_socket.h"
#include "rtc_base/network/received_packet.h"
#include "rtc_base/system/rtc_export.h"
namespace cricket {
// Lifetime chosen for STUN ports on low-cost networks.
static const int INFINITE_LIFETIME = -1;
// Lifetime for STUN ports on high-cost networks: 2 minutes
static const int HIGH_COST_PORT_KEEPALIVE_LIFETIME = 2 * 60 * 1000;
// Communicates using the address on the outside of a NAT.
class RTC_EXPORT UDPPort : public Port {
public:
static std::unique_ptr<UDPPort> Create(
rtc::Thread* thread,
rtc::PacketSocketFactory* factory,
const rtc::Network* network,
rtc::AsyncPacketSocket* socket,
absl::string_view username,
absl::string_view password,
bool emit_local_for_anyaddress,
absl::optional<int> stun_keepalive_interval,
const webrtc::FieldTrialsView* field_trials = nullptr) {
// Using `new` to access a non-public constructor.
auto port = absl::WrapUnique(
new UDPPort(thread, LOCAL_PORT_TYPE, factory, network, socket, username,
password, emit_local_for_anyaddress, field_trials));
port->set_stun_keepalive_delay(stun_keepalive_interval);
if (!port->Init()) {
return nullptr;
}
return port;
}
static std::unique_ptr<UDPPort> Create(
rtc::Thread* thread,
rtc::PacketSocketFactory* factory,
const rtc::Network* network,
uint16_t min_port,
uint16_t max_port,
absl::string_view username,
absl::string_view password,
bool emit_local_for_anyaddress,
absl::optional<int> stun_keepalive_interval,
const webrtc::FieldTrialsView* field_trials = nullptr) {
// Using `new` to access a non-public constructor.
auto port = absl::WrapUnique(new UDPPort(
thread, LOCAL_PORT_TYPE, factory, network, min_port, max_port, username,
password, emit_local_for_anyaddress, field_trials));
port->set_stun_keepalive_delay(stun_keepalive_interval);
if (!port->Init()) {
return nullptr;
}
return port;
}
~UDPPort() override;
rtc::SocketAddress GetLocalAddress() const {
return socket_->GetLocalAddress();
}
const ServerAddresses& server_addresses() const { return server_addresses_; }
void set_server_addresses(const ServerAddresses& addresses) {
server_addresses_ = addresses;
}
void PrepareAddress() override;
Connection* CreateConnection(const Candidate& address,
CandidateOrigin origin) override;
int SetOption(rtc::Socket::Option opt, int value) override;
int GetOption(rtc::Socket::Option opt, int* value) override;
int GetError() override;
bool HandleIncomingPacket(rtc::AsyncPacketSocket* socket,
const rtc::ReceivedPacket& packet) override;
bool SupportsProtocol(absl::string_view protocol) const override;
ProtocolType GetProtocol() const override;
void GetStunStats(absl::optional<StunStats>* stats) override;
void set_stun_keepalive_delay(const absl::optional<int>& delay);
int stun_keepalive_delay() const { return stun_keepalive_delay_; }
// Visible for testing.
int stun_keepalive_lifetime() const { return stun_keepalive_lifetime_; }
void set_stun_keepalive_lifetime(int lifetime) {
stun_keepalive_lifetime_ = lifetime;
}
StunRequestManager& request_manager() { return request_manager_; }
protected:
UDPPort(rtc::Thread* thread,
absl::string_view type,
rtc::PacketSocketFactory* factory,
const rtc::Network* network,
uint16_t min_port,
uint16_t max_port,
absl::string_view username,
absl::string_view password,
bool emit_local_for_anyaddress,
const webrtc::FieldTrialsView* field_trials);
UDPPort(rtc::Thread* thread,
absl::string_view type,
rtc::PacketSocketFactory* factory,
const rtc::Network* network,
rtc::AsyncPacketSocket* socket,
absl::string_view username,
absl::string_view password,
bool emit_local_for_anyaddress,
const webrtc::FieldTrialsView* field_trials);
bool Init();
int SendTo(const void* data,
size_t size,
const rtc::SocketAddress& addr,
const rtc::PacketOptions& options,
bool payload) override;
void UpdateNetworkCost() override;
rtc::DiffServCodePoint StunDscpValue() const override;
void OnLocalAddressReady(rtc::AsyncPacketSocket* socket,
const rtc::SocketAddress& address);
void PostAddAddress(bool is_final) override;
void OnReadPacket(rtc::AsyncPacketSocket* socket,
const rtc::ReceivedPacket& packet);
void OnSentPacket(rtc::AsyncPacketSocket* socket,
const rtc::SentPacket& sent_packet) override;
void OnReadyToSend(rtc::AsyncPacketSocket* socket);
// This method will send STUN binding request if STUN server address is set.
void MaybePrepareStunCandidate();
void SendStunBindingRequests();
// Helper function which will set `addr`'s IP to the default local address if
// `addr` is the "any" address and `emit_local_for_anyaddress_` is true. When
// returning false, it indicates that the operation has failed and the
// address shouldn't be used by any candidate.
bool MaybeSetDefaultLocalAddress(rtc::SocketAddress* addr) const;
private:
// A helper class which can be called repeatedly to resolve multiple
// addresses, as opposed to rtc::AsyncDnsResolverInterface, which can only
// resolve one address per instance.
class AddressResolver {
public:
explicit AddressResolver(
rtc::PacketSocketFactory* factory,
std::function<void(const rtc::SocketAddress&, int)> done_callback);
void Resolve(const rtc::SocketAddress& address,
int family,
const webrtc::FieldTrialsView& field_trials);
bool GetResolvedAddress(const rtc::SocketAddress& input,
int family,
rtc::SocketAddress* output) const;
private:
typedef std::map<rtc::SocketAddress,
std::unique_ptr<webrtc::AsyncDnsResolverInterface>>
ResolverMap;
rtc::PacketSocketFactory* socket_factory_;
// The function is called when resolving the specified address is finished.
// The first argument is the input address, the second argument is the error
// or 0 if it succeeded.
std::function<void(const rtc::SocketAddress&, int)> done_;
// Resolver may fire callbacks that refer to done_, so ensure
// that all resolvers are destroyed first.
ResolverMap resolvers_;
};
// DNS resolution of the STUN server.
void ResolveStunAddress(const rtc::SocketAddress& stun_addr);
void OnResolveResult(const rtc::SocketAddress& input, int error);
// Send a STUN binding request to the given address. Calling this method may
// cause the set of known server addresses to be modified, eg. by replacing an
// unresolved server address with a resolved address.
void SendStunBindingRequest(const rtc::SocketAddress& stun_addr);
// Below methods handles binding request responses.
void OnStunBindingRequestSucceeded(
int rtt_ms,
const rtc::SocketAddress& stun_server_addr,
const rtc::SocketAddress& stun_reflected_addr);
void OnStunBindingOrResolveRequestFailed(
const rtc::SocketAddress& stun_server_addr,
int error_code,
absl::string_view reason);
// Sends STUN requests to the server.
void OnSendPacket(const void* data, size_t size, StunRequest* req);
// TODO(mallinaht) - Move this up to cricket::Port when SignalAddressReady is
// changed to SignalPortReady.
void MaybeSetPortCompleteOrError();
bool HasStunCandidateWithAddress(const rtc::SocketAddress& addr) const;
// If this is a low-cost network, it will keep on sending STUN binding
// requests indefinitely to keep the NAT binding alive. Otherwise, stop
// sending STUN binding requests after HIGH_COST_PORT_KEEPALIVE_LIFETIME.
int GetStunKeepaliveLifetime() {
return (network_cost() >= rtc::kNetworkCostHigh)
? HIGH_COST_PORT_KEEPALIVE_LIFETIME
: INFINITE_LIFETIME;
}
ServerAddresses server_addresses_;
ServerAddresses bind_request_succeeded_servers_;
ServerAddresses bind_request_failed_servers_;
StunRequestManager request_manager_;
rtc::AsyncPacketSocket* socket_;
int error_;
int send_error_count_ = 0;
std::unique_ptr<AddressResolver> resolver_;
bool ready_;
int stun_keepalive_delay_;
int stun_keepalive_lifetime_ = INFINITE_LIFETIME;
rtc::DiffServCodePoint dscp_;
StunStats stats_;
// This is true by default and false when
// PORTALLOCATOR_DISABLE_DEFAULT_LOCAL_CANDIDATE is specified.
bool emit_local_for_anyaddress_;
friend class StunBindingRequest;
};
class StunPort : public UDPPort {
public:
static std::unique_ptr<StunPort> Create(
rtc::Thread* 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 ServerAddresses& servers,
absl::optional<int> stun_keepalive_interval,
const webrtc::FieldTrialsView* field_trials);
void PrepareAddress() override;
protected:
StunPort(rtc::Thread* 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 ServerAddresses& servers,
const webrtc::FieldTrialsView* field_trials);
};
} // namespace cricket
#endif // P2P_BASE_STUN_PORT_H_

View file

@ -0,0 +1,331 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "p2p/base/stun_request.h"
#include <algorithm>
#include <memory>
#include <utility>
#include <vector>
#include "absl/memory/memory.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "rtc_base/checks.h"
#include "rtc_base/helpers.h"
#include "rtc_base/logging.h"
#include "rtc_base/string_encode.h"
#include "rtc_base/time_utils.h" // For TimeMillis
namespace cricket {
using ::webrtc::SafeTask;
// RFC 5389 says SHOULD be 500ms.
// For years, this was 100ms, but for networks that
// experience moments of high RTT (such as 2G networks), this doesn't
// work well.
const int STUN_INITIAL_RTO = 250; // milliseconds
// The timeout doubles each retransmission, up to this many times
// RFC 5389 says SHOULD retransmit 7 times.
// This has been 8 for years (not sure why).
const int STUN_MAX_RETRANSMISSIONS = 8; // Total sends: 9
// We also cap the doubling, even though the standard doesn't say to.
// This has been 1.6 seconds for years, but for networks that
// experience moments of high RTT (such as 2G networks), this doesn't
// work well.
const int STUN_MAX_RTO = 8000; // milliseconds, or 5 doublings
StunRequestManager::StunRequestManager(
webrtc::TaskQueueBase* thread,
std::function<void(const void*, size_t, StunRequest*)> send_packet)
: thread_(thread), send_packet_(std::move(send_packet)) {}
StunRequestManager::~StunRequestManager() = default;
void StunRequestManager::Send(StunRequest* request) {
SendDelayed(request, 0);
}
void StunRequestManager::SendDelayed(StunRequest* request, int delay) {
RTC_DCHECK_RUN_ON(thread_);
RTC_DCHECK_EQ(this, request->manager());
RTC_DCHECK(!request->AuthenticationRequired() ||
request->msg()->integrity() !=
StunMessage::IntegrityStatus::kNotSet)
<< "Sending request w/o integrity!";
auto [iter, was_inserted] =
requests_.emplace(request->id(), absl::WrapUnique(request));
RTC_DCHECK(was_inserted);
request->Send(webrtc::TimeDelta::Millis(delay));
}
void StunRequestManager::FlushForTest(int msg_type) {
RTC_DCHECK_RUN_ON(thread_);
for (const auto& [unused, request] : requests_) {
if (msg_type == kAllRequestsForTest || msg_type == request->type()) {
// Calling `Send` implies starting the send operation which may be posted
// on a timer and be repeated on a timer until timeout. To make sure that
// a call to `Send` doesn't conflict with a previously started `Send`
// operation, we reset the `task_safety_` flag here, which has the effect
// of canceling any outstanding tasks and prepare a new flag for
// operations related to this call to `Send`.
request->ResetTasksForTest();
request->Send(webrtc::TimeDelta::Zero());
}
}
}
bool StunRequestManager::HasRequestForTest(int msg_type) {
RTC_DCHECK_RUN_ON(thread_);
RTC_DCHECK_NE(msg_type, kAllRequestsForTest);
for (const auto& [unused, request] : requests_) {
if (msg_type == request->type()) {
return true;
}
}
return false;
}
void StunRequestManager::Clear() {
RTC_DCHECK_RUN_ON(thread_);
requests_.clear();
}
bool StunRequestManager::CheckResponse(StunMessage* msg) {
RTC_DCHECK_RUN_ON(thread_);
RequestMap::iterator iter = requests_.find(msg->transaction_id());
if (iter == requests_.end())
return false;
StunRequest* request = iter->second.get();
// Now that we know the request, we can see if the response is
// integrity-protected or not. Some requests explicitly disables
// integrity checks using SetAuthenticationRequired.
// TODO(chromium:1177125): Remove below!
// And we suspect that for some tests, the message integrity is not set in the
// request. Complain, and then don't check.
bool skip_integrity_checking =
(request->msg()->integrity() == StunMessage::IntegrityStatus::kNotSet);
if (!request->AuthenticationRequired()) {
// This is a STUN_BINDING to from stun_port.cc or
// the initial (unauthenticated) TURN_ALLOCATE_REQUEST.
} else if (skip_integrity_checking) {
// TODO(chromium:1177125): Remove below!
// This indicates lazy test writing (not adding integrity attribute).
// Complain, but only in debug mode (while developing).
RTC_LOG(LS_ERROR)
<< "CheckResponse called on a passwordless request. Fix test!";
RTC_DCHECK(false)
<< "CheckResponse called on a passwordless request. Fix test!";
} else {
if (msg->integrity() == StunMessage::IntegrityStatus::kNotSet) {
// Checking status for the first time. Normal.
msg->ValidateMessageIntegrity(request->msg()->password());
} else if (msg->integrity() == StunMessage::IntegrityStatus::kIntegrityOk &&
msg->password() == request->msg()->password()) {
// Status is already checked, with the same password. This is the case
// we would want to see happen.
} else if (msg->integrity() ==
StunMessage::IntegrityStatus::kIntegrityBad) {
// This indicates that the original check had the wrong password.
// Bad design, needs revisiting.
// TODO(crbug.com/1177125): Fix this.
msg->RevalidateMessageIntegrity(request->msg()->password());
} else {
RTC_CHECK_NOTREACHED();
}
}
if (!msg->GetNonComprehendedAttributes().empty()) {
// If a response contains unknown comprehension-required attributes, it's
// simply discarded and the transaction is considered failed. See RFC5389
// sections 7.3.3 and 7.3.4.
RTC_LOG(LS_ERROR) << ": Discarding response due to unknown "
"comprehension-required attribute.";
requests_.erase(iter);
return false;
} else if (msg->type() == GetStunSuccessResponseType(request->type())) {
if (!msg->IntegrityOk() && !skip_integrity_checking) {
return false;
}
// Erase element from hash before calling callback. This ensures
// that the callback can modify the StunRequestManager any way it
// sees fit.
std::unique_ptr<StunRequest> owned_request = std::move(iter->second);
requests_.erase(iter);
owned_request->OnResponse(msg);
return true;
} else if (msg->type() == GetStunErrorResponseType(request->type())) {
// Erase element from hash before calling callback. This ensures
// that the callback can modify the StunRequestManager any way it
// sees fit.
std::unique_ptr<StunRequest> owned_request = std::move(iter->second);
requests_.erase(iter);
owned_request->OnErrorResponse(msg);
return true;
} else {
RTC_LOG(LS_ERROR) << "Received response with wrong type: " << msg->type()
<< " (expecting "
<< GetStunSuccessResponseType(request->type()) << ")";
return false;
}
}
bool StunRequestManager::empty() const {
RTC_DCHECK_RUN_ON(thread_);
return requests_.empty();
}
bool StunRequestManager::CheckResponse(const char* data, size_t size) {
RTC_DCHECK_RUN_ON(thread_);
// Check the appropriate bytes of the stream to see if they match the
// transaction ID of a response we are expecting.
if (size < 20)
return false;
std::string id;
id.append(data + kStunTransactionIdOffset, kStunTransactionIdLength);
RequestMap::iterator iter = requests_.find(id);
if (iter == requests_.end())
return false;
// Parse the STUN message and continue processing as usual.
rtc::ByteBufferReader buf(
rtc::MakeArrayView(reinterpret_cast<const uint8_t*>(data), size));
std::unique_ptr<StunMessage> response(iter->second->msg_->CreateNew());
if (!response->Read(&buf)) {
RTC_LOG(LS_WARNING) << "Failed to read STUN response "
<< rtc::hex_encode(id);
return false;
}
return CheckResponse(response.get());
}
void StunRequestManager::OnRequestTimedOut(StunRequest* request) {
RTC_DCHECK_RUN_ON(thread_);
requests_.erase(request->id());
}
void StunRequestManager::SendPacket(const void* data,
size_t size,
StunRequest* request) {
RTC_DCHECK_EQ(this, request->manager());
send_packet_(data, size, request);
}
StunRequest::StunRequest(StunRequestManager& manager)
: manager_(manager),
msg_(new StunMessage(STUN_INVALID_MESSAGE_TYPE)),
tstamp_(0),
count_(0),
timeout_(false) {
RTC_DCHECK_RUN_ON(network_thread());
}
StunRequest::StunRequest(StunRequestManager& manager,
std::unique_ptr<StunMessage> message)
: manager_(manager),
msg_(std::move(message)),
tstamp_(0),
count_(0),
timeout_(false) {
RTC_DCHECK_RUN_ON(network_thread());
RTC_DCHECK(!msg_->transaction_id().empty());
}
StunRequest::~StunRequest() {}
int StunRequest::type() {
RTC_DCHECK(msg_ != NULL);
return msg_->type();
}
const StunMessage* StunRequest::msg() const {
return msg_.get();
}
int StunRequest::Elapsed() const {
RTC_DCHECK_RUN_ON(network_thread());
return static_cast<int>(rtc::TimeMillis() - tstamp_);
}
void StunRequest::SendInternal() {
RTC_DCHECK_RUN_ON(network_thread());
if (timeout_) {
OnTimeout();
manager_.OnRequestTimedOut(this);
return;
}
tstamp_ = rtc::TimeMillis();
rtc::ByteBufferWriter buf;
msg_->Write(&buf);
manager_.SendPacket(buf.Data(), buf.Length(), this);
OnSent();
SendDelayed(webrtc::TimeDelta::Millis(resend_delay()));
}
void StunRequest::SendDelayed(webrtc::TimeDelta delay) {
network_thread()->PostDelayedTask(
SafeTask(task_safety_.flag(), [this]() { SendInternal(); }), delay);
}
void StunRequest::Send(webrtc::TimeDelta delay) {
RTC_DCHECK_RUN_ON(network_thread());
RTC_DCHECK_GE(delay.ms(), 0);
RTC_DCHECK(!task_safety_.flag()->alive()) << "Send already called?";
task_safety_.flag()->SetAlive();
delay.IsZero() ? SendInternal() : SendDelayed(delay);
}
void StunRequest::ResetTasksForTest() {
RTC_DCHECK_RUN_ON(network_thread());
task_safety_.reset(webrtc::PendingTaskSafetyFlag::CreateDetachedInactive());
count_ = 0;
RTC_DCHECK(!timeout_);
}
void StunRequest::OnSent() {
RTC_DCHECK_RUN_ON(network_thread());
count_ += 1;
int retransmissions = (count_ - 1);
if (retransmissions >= STUN_MAX_RETRANSMISSIONS) {
timeout_ = true;
}
RTC_DLOG(LS_VERBOSE) << "Sent STUN request " << count_
<< "; resend delay = " << resend_delay();
}
int StunRequest::resend_delay() {
RTC_DCHECK_RUN_ON(network_thread());
if (count_ == 0) {
return 0;
}
int retransmissions = (count_ - 1);
int rto = STUN_INITIAL_RTO << retransmissions;
return std::min(rto, STUN_MAX_RTO);
}
void StunRequest::set_timed_out() {
RTC_DCHECK_RUN_ON(network_thread());
timeout_ = true;
}
} // namespace cricket

View file

@ -0,0 +1,169 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef P2P_BASE_STUN_REQUEST_H_
#define P2P_BASE_STUN_REQUEST_H_
#include <stddef.h>
#include <stdint.h>
#include <functional>
#include <map>
#include <memory>
#include <string>
#include "api/task_queue/pending_task_safety_flag.h"
#include "api/task_queue/task_queue_base.h"
#include "api/transport/stun.h"
#include "api/units/time_delta.h"
namespace cricket {
class StunRequest;
const int kAllRequestsForTest = 0;
// Total max timeouts: 39.75 seconds
// For years, this was 9.5 seconds, but for networks that experience moments of
// high RTT (such as 40s on 2G networks), this doesn't work well.
const int STUN_TOTAL_TIMEOUT = 39750; // milliseconds
// Manages a set of STUN requests, sending and resending until we receive a
// response or determine that the request has timed out.
class StunRequestManager {
public:
StunRequestManager(
webrtc::TaskQueueBase* thread,
std::function<void(const void*, size_t, StunRequest*)> send_packet);
~StunRequestManager();
// Starts sending the given request (perhaps after a delay).
void Send(StunRequest* request);
void SendDelayed(StunRequest* request, int delay);
// If `msg_type` is kAllRequestsForTest, sends all pending requests right
// away. Otherwise, sends those that have a matching type right away. Only for
// testing.
// TODO(tommi): Remove this method and update tests that use it to simulate
// production code.
void FlushForTest(int msg_type);
// Returns true if at least one request with `msg_type` is scheduled for
// transmission. For testing only.
// TODO(tommi): Remove this method and update tests that use it to simulate
// production code.
bool HasRequestForTest(int msg_type);
// Removes all stun requests that were added previously.
void Clear();
// Determines whether the given message is a response to one of the
// outstanding requests, and if so, processes it appropriately.
bool CheckResponse(StunMessage* msg);
bool CheckResponse(const char* data, size_t size);
// Called from a StunRequest when a timeout occurs.
void OnRequestTimedOut(StunRequest* request);
bool empty() const;
webrtc::TaskQueueBase* network_thread() const { return thread_; }
void SendPacket(const void* data, size_t size, StunRequest* request);
private:
typedef std::map<std::string, std::unique_ptr<StunRequest>> RequestMap;
webrtc::TaskQueueBase* const thread_;
RequestMap requests_ RTC_GUARDED_BY(thread_);
const std::function<void(const void*, size_t, StunRequest*)> send_packet_;
};
// Represents an individual request to be sent. The STUN message can either be
// constructed beforehand or built on demand.
class StunRequest {
public:
explicit StunRequest(StunRequestManager& manager);
StunRequest(StunRequestManager& manager,
std::unique_ptr<StunMessage> message);
virtual ~StunRequest();
// The manager handling this request (if it has been scheduled for sending).
StunRequestManager* manager() { return &manager_; }
// Returns the transaction ID of this request.
const std::string& id() const { return msg_->transaction_id(); }
// Returns the reduced transaction ID of this request.
uint32_t reduced_transaction_id() const {
return msg_->reduced_transaction_id();
}
// Returns the STUN type of the request message.
int type();
// Returns a const pointer to `msg_`.
const StunMessage* msg() const;
// Time elapsed since last send (in ms)
int Elapsed() const;
// Add method to explitly allow requests w/o password.
// - STUN_BINDINGs from StunPort to a stun server
// - The initial TURN_ALLOCATE_REQUEST
void SetAuthenticationRequired(bool val) { authentication_required_ = val; }
bool AuthenticationRequired() const { return authentication_required_; }
protected:
friend class StunRequestManager;
// Called by StunRequestManager.
void Send(webrtc::TimeDelta delay);
// Called from FlushForTest.
// TODO(tommi): Remove when FlushForTest gets removed.
void ResetTasksForTest();
StunMessage* mutable_msg() { return msg_.get(); }
// Called when the message receives a response or times out.
virtual void OnResponse(StunMessage* response) {}
virtual void OnErrorResponse(StunMessage* response) {}
virtual void OnTimeout() {}
// Called when the message is sent.
virtual void OnSent();
// Returns the next delay for resends in milliseconds.
virtual int resend_delay();
webrtc::TaskQueueBase* network_thread() const {
return manager_.network_thread();
}
void set_timed_out();
private:
void SendInternal();
// Calls `PostDelayedTask` to queue up a call to SendInternal after the
// specified timeout.
void SendDelayed(webrtc::TimeDelta delay);
StunRequestManager& manager_;
const std::unique_ptr<StunMessage> msg_;
int64_t tstamp_ RTC_GUARDED_BY(network_thread());
int count_ RTC_GUARDED_BY(network_thread());
bool timeout_ RTC_GUARDED_BY(network_thread());
webrtc::ScopedTaskSafety task_safety_{
webrtc::PendingTaskSafetyFlag::CreateDetachedInactive()};
bool authentication_required_ = true;
};
} // namespace cricket
#endif // P2P_BASE_STUN_REQUEST_H_

View file

@ -0,0 +1,111 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "p2p/base/stun_server.h"
#include <string>
#include <utility>
#include "absl/strings/string_view.h"
#include "api/sequence_checker.h"
#include "rtc_base/async_packet_socket.h"
#include "rtc_base/byte_buffer.h"
#include "rtc_base/logging.h"
#include "rtc_base/network/received_packet.h"
namespace cricket {
StunServer::StunServer(rtc::AsyncUDPSocket* socket) : socket_(socket) {
socket_->RegisterReceivedPacketCallback(
[&](rtc::AsyncPacketSocket* socket, const rtc::ReceivedPacket& packet) {
OnPacket(socket, packet);
});
}
StunServer::~StunServer() {
RTC_DCHECK_RUN_ON(&sequence_checker_);
socket_->DeregisterReceivedPacketCallback();
}
void StunServer::OnPacket(rtc::AsyncPacketSocket* socket,
const rtc::ReceivedPacket& packet) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
// Parse the STUN message; eat any messages that fail to parse.
rtc::ByteBufferReader bbuf(packet.payload());
StunMessage msg;
if (!msg.Read(&bbuf)) {
return;
}
// TODO(?): If unknown non-optional (<= 0x7fff) attributes are found,
// send a
// 420 "Unknown Attribute" response.
// Send the message to the appropriate handler function.
switch (msg.type()) {
case STUN_BINDING_REQUEST:
OnBindingRequest(&msg, packet.source_address());
break;
default:
SendErrorResponse(msg, packet.source_address(), 600,
"Operation Not Supported");
}
}
void StunServer::OnBindingRequest(StunMessage* msg,
const rtc::SocketAddress& remote_addr) {
StunMessage response(STUN_BINDING_RESPONSE, msg->transaction_id());
GetStunBindResponse(msg, remote_addr, &response);
SendResponse(response, remote_addr);
}
void StunServer::SendErrorResponse(const StunMessage& msg,
const rtc::SocketAddress& addr,
int error_code,
absl::string_view error_desc) {
StunMessage err_msg(GetStunErrorResponseType(msg.type()),
msg.transaction_id());
auto err_code = StunAttribute::CreateErrorCode();
err_code->SetCode(error_code);
err_code->SetReason(std::string(error_desc));
err_msg.AddAttribute(std::move(err_code));
SendResponse(err_msg, addr);
}
void StunServer::SendResponse(const StunMessage& msg,
const rtc::SocketAddress& addr) {
rtc::ByteBufferWriter buf;
msg.Write(&buf);
rtc::PacketOptions options;
if (socket_->SendTo(buf.Data(), buf.Length(), addr, options) < 0)
RTC_LOG_ERR(LS_ERROR) << "sendto";
}
void StunServer::GetStunBindResponse(StunMessage* message,
const rtc::SocketAddress& remote_addr,
StunMessage* response) const {
RTC_DCHECK_EQ(response->type(), STUN_BINDING_RESPONSE);
RTC_DCHECK_EQ(response->transaction_id(), message->transaction_id());
// Tell the user the address that we received their message from.
std::unique_ptr<StunAddressAttribute> mapped_addr;
if (message->IsLegacy()) {
mapped_addr = StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
} else {
mapped_addr = StunAttribute::CreateXorAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
}
mapped_addr->SetAddress(remote_addr);
response->AddAttribute(std::move(mapped_addr));
}
} // namespace cricket

View file

@ -0,0 +1,68 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef P2P_BASE_STUN_SERVER_H_
#define P2P_BASE_STUN_SERVER_H_
#include <stddef.h>
#include <memory>
#include "absl/strings/string_view.h"
#include "api/transport/stun.h"
#include "rtc_base/async_packet_socket.h"
#include "rtc_base/async_udp_socket.h"
#include "rtc_base/socket_address.h"
namespace cricket {
const int STUN_SERVER_PORT = 3478;
class StunServer {
public:
// Creates a STUN server, which will listen on the given socket.
explicit StunServer(rtc::AsyncUDPSocket* socket);
// Removes the STUN server from the socket and deletes the socket.
virtual ~StunServer();
protected:
// Callback for packets from socket.
void OnPacket(rtc::AsyncPacketSocket* socket,
const rtc::ReceivedPacket& packet);
// Handlers for the different types of STUN/TURN requests:
virtual void OnBindingRequest(StunMessage* msg,
const rtc::SocketAddress& addr);
void OnAllocateRequest(StunMessage* msg, const rtc::SocketAddress& addr);
void OnSharedSecretRequest(StunMessage* msg, const rtc::SocketAddress& addr);
void OnSendRequest(StunMessage* msg, const rtc::SocketAddress& addr);
// Sends an error response to the given message back to the user.
void SendErrorResponse(const StunMessage& msg,
const rtc::SocketAddress& addr,
int error_code,
absl::string_view error_desc);
// Sends the given message to the appropriate destination.
void SendResponse(const StunMessage& msg, const rtc::SocketAddress& addr);
// A helper method to compose a STUN binding response.
void GetStunBindResponse(StunMessage* message,
const rtc::SocketAddress& remote_addr,
StunMessage* response) const;
private:
webrtc::SequenceChecker sequence_checker_;
std::unique_ptr<rtc::AsyncUDPSocket> socket_;
};
} // namespace cricket
#endif // P2P_BASE_STUN_SERVER_H_

View file

@ -0,0 +1,645 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
/*
* This is a diagram of how TCP reconnect works for the active side. The
* passive side just waits for an incoming connection.
*
* - Connected: Indicate whether the TCP socket is connected.
*
* - Writable: Whether the stun binding is completed. Sending a data packet
* before stun binding completed will trigger IPC socket layer to shutdown
* the connection.
*
* - PendingTCP: `connection_pending_` indicates whether there is an
* outstanding TCP connection in progress.
*
* - PretendWri: Tracked by `pretending_to_be_writable_`. Marking connection as
* WRITE_TIMEOUT will cause the connection be deleted. Instead, we're
* "pretending" we're still writable for a period of time such that reconnect
* could work.
*
* Data could only be sent in state 3. Sening data during state 2 & 6 will get
* EWOULDBLOCK, 4 & 5 EPIPE.
*
* OS Timeout 7 -------------+
* +----------------------->|Connected: N |
* | |Writable: N | Timeout
* | Timeout |Connection is |<----------------+
* | +------------------->|Dead | |
* | | +--------------+ |
* | | ^ |
* | | OnClose | |
* | | +-----------------------+ | |
* | | | | |Timeout |
* | | v | | |
* | 4 +----------+ 5 -----+--+--+ 6 -----+-----+
* | |Connected: N|Send() or |Connected: N| |Connected: Y|
* | |Writable: Y|Ping() |Writable: Y|OnConnect |Writable: Y|
* | |PendingTCP:N+--------> |PendingTCP:Y+---------> |PendingTCP:N|
* | |PretendWri:Y| |PretendWri:Y| |PretendWri:Y|
* | +-----+------+ +------------+ +---+--+-----+
* | ^ ^ | |
* | | | OnClose | |
* | | +----------------------------------------------+ |
* | | |
* | | Stun Binding Completed |
* | | |
* | | OnClose |
* | +------------------------------------------------+ |
* | | v
* 1 -----------+ 2 -----------+Stun 3 -----------+
* |Connected: N| |Connected: Y|Binding |Connected: Y|
* |Writable: N|OnConnect |Writable: N|Completed |Writable: Y|
* |PendingTCP:Y+---------> |PendingTCP:N+--------> |PendingTCP:N|
* |PretendWri:N| |PretendWri:N| |PretendWri:N|
* +------------+ +------------+ +------------+
*
*/
#include "p2p/base/tcp_port.h"
#include <errno.h>
#include <utility>
#include <vector>
#include "absl/algorithm/container.h"
#include "absl/memory/memory.h"
#include "absl/strings/string_view.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "api/units/time_delta.h"
#include "p2p/base/p2p_constants.h"
#include "rtc_base/checks.h"
#include "rtc_base/ip_address.h"
#include "rtc_base/logging.h"
#include "rtc_base/net_helper.h"
#include "rtc_base/network/received_packet.h"
#include "rtc_base/rate_tracker.h"
#include "rtc_base/thread.h"
namespace cricket {
using ::webrtc::SafeTask;
using ::webrtc::TimeDelta;
TCPPort::TCPPort(rtc::Thread* thread,
rtc::PacketSocketFactory* factory,
const rtc::Network* network,
uint16_t min_port,
uint16_t max_port,
absl::string_view username,
absl::string_view password,
bool allow_listen,
const webrtc::FieldTrialsView* field_trials)
: Port(thread,
LOCAL_PORT_TYPE,
factory,
network,
min_port,
max_port,
username,
password,
field_trials),
allow_listen_(allow_listen),
error_(0) {
// TODO(mallinath) - Set preference value as per RFC 6544.
// http://b/issue?id=7141794
if (allow_listen_) {
TryCreateServerSocket();
}
// Set TCP_NODELAY (via OPT_NODELAY) for improved performance; this causes
// small media packets to be sent immediately rather than being buffered up,
// reducing latency.
SetOption(rtc::Socket::OPT_NODELAY, 1);
}
TCPPort::~TCPPort() {
listen_socket_ = nullptr;
std::list<Incoming>::iterator it;
for (it = incoming_.begin(); it != incoming_.end(); ++it)
delete it->socket;
incoming_.clear();
}
Connection* TCPPort::CreateConnection(const Candidate& address,
CandidateOrigin origin) {
if (!SupportsProtocol(address.protocol())) {
return NULL;
}
if ((address.tcptype() == TCPTYPE_ACTIVE_STR && !address.is_prflx()) ||
(address.tcptype().empty() && address.address().port() == 0)) {
// It's active only candidate, we should not try to create connections
// for these candidates.
return NULL;
}
// We can't accept TCP connections incoming on other ports
if (origin == ORIGIN_OTHER_PORT)
return NULL;
// We don't know how to act as an ssl server yet
if ((address.protocol() == SSLTCP_PROTOCOL_NAME) &&
(origin == ORIGIN_THIS_PORT)) {
return NULL;
}
if (!IsCompatibleAddress(address.address())) {
return NULL;
}
TCPConnection* conn = NULL;
if (rtc::AsyncPacketSocket* socket = GetIncoming(address.address(), true)) {
// Incoming connection; we already created a socket and connected signals,
// so we need to hand off the "read packet" responsibility to
// TCPConnection.
socket->DeregisterReceivedPacketCallback();
conn = new TCPConnection(NewWeakPtr(), address, socket);
} else {
// Outgoing connection, which will create a new socket for which we still
// need to connect SignalReadyToSend and SignalSentPacket.
conn = new TCPConnection(NewWeakPtr(), address);
if (conn->socket()) {
conn->socket()->SignalReadyToSend.connect(this, &TCPPort::OnReadyToSend);
conn->socket()->SignalSentPacket.connect(this, &TCPPort::OnSentPacket);
}
}
AddOrReplaceConnection(conn);
return conn;
}
void TCPPort::PrepareAddress() {
if (listen_socket_) {
// Socket may be in the CLOSED state if Listen()
// failed, we still want to add the socket address.
RTC_LOG(LS_VERBOSE) << "Preparing TCP address, current state: "
<< static_cast<int>(listen_socket_->GetState());
AddAddress(listen_socket_->GetLocalAddress(),
listen_socket_->GetLocalAddress(), rtc::SocketAddress(),
TCP_PROTOCOL_NAME, "", TCPTYPE_PASSIVE_STR, LOCAL_PORT_TYPE,
ICE_TYPE_PREFERENCE_HOST_TCP, 0, "", true);
} else {
RTC_LOG(LS_INFO) << ToString()
<< ": Not listening due to firewall restrictions.";
// Note: We still add the address, since otherwise the remote side won't
// recognize our incoming TCP connections. According to
// https://tools.ietf.org/html/rfc6544#section-4.5, for active candidate,
// the port must be set to the discard port, i.e. 9. We can't be 100% sure
// which IP address will actually be used, so GetBestIP is as good as we
// can do.
// TODO(deadbeef): We could do something like create a dummy socket just to
// see what IP we get. But that may be overkill.
AddAddress(rtc::SocketAddress(Network()->GetBestIP(), DISCARD_PORT),
rtc::SocketAddress(Network()->GetBestIP(), 0),
rtc::SocketAddress(), TCP_PROTOCOL_NAME, "", TCPTYPE_ACTIVE_STR,
LOCAL_PORT_TYPE, ICE_TYPE_PREFERENCE_HOST_TCP, 0, "", true);
}
}
int TCPPort::SendTo(const void* data,
size_t size,
const rtc::SocketAddress& addr,
const rtc::PacketOptions& options,
bool payload) {
rtc::AsyncPacketSocket* socket = NULL;
TCPConnection* conn = static_cast<TCPConnection*>(GetConnection(addr));
// For Connection, this is the code path used by Ping() to establish
// WRITABLE. It has to send through the socket directly as TCPConnection::Send
// checks writability.
if (conn) {
if (!conn->connected()) {
conn->MaybeReconnect();
return SOCKET_ERROR;
}
socket = conn->socket();
if (!socket) {
// The failure to initialize should have been logged elsewhere,
// so this log is not important.
RTC_LOG(LS_INFO) << ToString()
<< ": Attempted to send to an uninitialized socket: "
<< addr.ToSensitiveString();
error_ = EHOSTUNREACH;
return SOCKET_ERROR;
}
} else {
socket = GetIncoming(addr);
if (!socket) {
RTC_LOG(LS_ERROR) << ToString()
<< ": Attempted to send to an unknown destination: "
<< addr.ToSensitiveString();
error_ = EHOSTUNREACH;
return SOCKET_ERROR;
}
}
rtc::PacketOptions modified_options(options);
CopyPortInformationToPacketInfo(&modified_options.info_signaled_after_sent);
int sent = socket->Send(data, size, modified_options);
if (sent < 0) {
error_ = socket->GetError();
// Error from this code path for a Connection (instead of from a bare
// socket) will not trigger reconnecting. In theory, this shouldn't matter
// as OnClose should always be called and set connected to false.
RTC_LOG(LS_ERROR) << ToString() << ": TCP send of " << size
<< " bytes failed with error " << error_;
}
return sent;
}
int TCPPort::GetOption(rtc::Socket::Option opt, int* value) {
auto const& it = socket_options_.find(opt);
if (it == socket_options_.end()) {
return -1;
}
*value = it->second;
return 0;
}
int TCPPort::SetOption(rtc::Socket::Option opt, int value) {
socket_options_[opt] = value;
return 0;
}
int TCPPort::GetError() {
return error_;
}
bool TCPPort::SupportsProtocol(absl::string_view protocol) const {
return protocol == TCP_PROTOCOL_NAME || protocol == SSLTCP_PROTOCOL_NAME;
}
ProtocolType TCPPort::GetProtocol() const {
return PROTO_TCP;
}
void TCPPort::OnNewConnection(rtc::AsyncListenSocket* socket,
rtc::AsyncPacketSocket* new_socket) {
RTC_DCHECK_EQ(socket, listen_socket_.get());
for (const auto& option : socket_options_) {
new_socket->SetOption(option.first, option.second);
}
Incoming incoming;
incoming.addr = new_socket->GetRemoteAddress();
incoming.socket = new_socket;
incoming.socket->RegisterReceivedPacketCallback(
[&](rtc::AsyncPacketSocket* socket, const rtc::ReceivedPacket& packet) {
OnReadPacket(socket, packet);
});
incoming.socket->SignalReadyToSend.connect(this, &TCPPort::OnReadyToSend);
incoming.socket->SignalSentPacket.connect(this, &TCPPort::OnSentPacket);
RTC_LOG(LS_VERBOSE) << ToString() << ": Accepted connection from "
<< incoming.addr.ToSensitiveString();
incoming_.push_back(incoming);
}
void TCPPort::TryCreateServerSocket() {
listen_socket_ = absl::WrapUnique(socket_factory()->CreateServerTcpSocket(
rtc::SocketAddress(Network()->GetBestIP(), 0), min_port(), max_port(),
false /* ssl */));
if (!listen_socket_) {
RTC_LOG(LS_WARNING)
<< ToString()
<< ": TCP server socket creation failed; continuing anyway.";
return;
}
listen_socket_->SignalNewConnection.connect(this, &TCPPort::OnNewConnection);
}
rtc::AsyncPacketSocket* TCPPort::GetIncoming(const rtc::SocketAddress& addr,
bool remove) {
rtc::AsyncPacketSocket* socket = NULL;
for (std::list<Incoming>::iterator it = incoming_.begin();
it != incoming_.end(); ++it) {
if (it->addr == addr) {
socket = it->socket;
if (remove)
incoming_.erase(it);
break;
}
}
return socket;
}
void TCPPort::OnReadPacket(rtc::AsyncPacketSocket* socket,
const rtc::ReceivedPacket& packet) {
Port::OnReadPacket(packet, PROTO_TCP);
}
void TCPPort::OnSentPacket(rtc::AsyncPacketSocket* socket,
const rtc::SentPacket& sent_packet) {
PortInterface::SignalSentPacket(sent_packet);
}
void TCPPort::OnReadyToSend(rtc::AsyncPacketSocket* socket) {
Port::OnReadyToSend();
}
// TODO(qingsi): `CONNECTION_WRITE_CONNECT_TIMEOUT` is overriden by
// `ice_unwritable_timeout` in IceConfig when determining the writability state.
// Replace this constant with the config parameter assuming the default value if
// we decide it is also applicable here.
TCPConnection::TCPConnection(rtc::WeakPtr<Port> tcp_port,
const Candidate& candidate,
rtc::AsyncPacketSocket* socket)
: Connection(std::move(tcp_port), 0, candidate),
socket_(socket),
error_(0),
outgoing_(socket == NULL),
connection_pending_(false),
pretending_to_be_writable_(false),
reconnection_timeout_(cricket::CONNECTION_WRITE_CONNECT_TIMEOUT) {
RTC_DCHECK_RUN_ON(network_thread_);
RTC_DCHECK_EQ(port()->GetProtocol(), PROTO_TCP); // Needs to be TCPPort.
SignalDestroyed.connect(this, &TCPConnection::OnDestroyed);
if (outgoing_) {
CreateOutgoingTcpSocket();
} else {
// Incoming connections should match one of the network addresses. Same as
// what's being checked in OnConnect, but just DCHECKing here.
RTC_LOG(LS_VERBOSE) << ToString() << ": socket ipaddr: "
<< socket_->GetLocalAddress().ToSensitiveString()
<< ", port() Network:" << port()->Network()->ToString();
RTC_DCHECK(absl::c_any_of(
port_->Network()->GetIPs(), [this](const rtc::InterfaceAddress& addr) {
return socket_->GetLocalAddress().ipaddr() == addr;
}));
ConnectSocketSignals(socket);
}
}
TCPConnection::~TCPConnection() {
RTC_DCHECK_RUN_ON(network_thread_);
}
int TCPConnection::Send(const void* data,
size_t size,
const rtc::PacketOptions& options) {
if (!socket_) {
error_ = ENOTCONN;
return SOCKET_ERROR;
}
// Sending after OnClose on active side will trigger a reconnect for a
// outgoing connection. Note that the write state is still WRITABLE as we want
// to spend a few seconds attempting a reconnect before saying we're
// unwritable.
if (!connected()) {
MaybeReconnect();
return SOCKET_ERROR;
}
// Note that this is important to put this after the previous check to give
// the connection a chance to reconnect.
if (pretending_to_be_writable_ || write_state() != STATE_WRITABLE) {
// TODO(?): Should STATE_WRITE_TIMEOUT return a non-blocking error?
error_ = ENOTCONN;
return SOCKET_ERROR;
}
stats_.sent_total_packets++;
rtc::PacketOptions modified_options(options);
tcp_port()->CopyPortInformationToPacketInfo(
&modified_options.info_signaled_after_sent);
int sent = socket_->Send(data, size, modified_options);
int64_t now = rtc::TimeMillis();
if (sent < 0) {
stats_.sent_discarded_packets++;
error_ = socket_->GetError();
} else {
send_rate_tracker_.AddSamplesAtTime(now, sent);
}
last_send_data_ = now;
return sent;
}
int TCPConnection::GetError() {
return error_;
}
void TCPConnection::OnConnectionRequestResponse(StunRequest* req,
StunMessage* response) {
// Process the STUN response before we inform upper layer ready to send.
Connection::OnConnectionRequestResponse(req, response);
// If we're in the state of pretending to be writeable, we should inform the
// upper layer it's ready to send again as previous EWOULDLBLOCK from socket
// would have stopped the outgoing stream.
if (pretending_to_be_writable_) {
Connection::OnReadyToSend();
}
pretending_to_be_writable_ = false;
RTC_DCHECK(write_state() == STATE_WRITABLE);
}
void TCPConnection::OnConnect(rtc::AsyncPacketSocket* socket) {
RTC_DCHECK_EQ(socket, socket_.get());
if (!port_) {
RTC_LOG(LS_ERROR) << "TCPConnection: Port has been deleted.";
return;
}
// 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 TurnPort.
const rtc::SocketAddress& socket_address = socket->GetLocalAddress();
if (absl::c_any_of(port_->Network()->GetIPs(),
[socket_address](const rtc::InterfaceAddress& addr) {
return socket_address.ipaddr() == addr;
})) {
RTC_LOG(LS_VERBOSE) << ToString() << ": Connection established to "
<< socket->GetRemoteAddress().ToSensitiveString();
} else {
if (socket->GetLocalAddress().IsLoopbackIP()) {
RTC_LOG(LS_WARNING) << "Socket is bound to the address:"
<< socket_address.ipaddr().ToSensitiveString()
<< ", rather than an address associated with network:"
<< port_->Network()->ToString()
<< ". Still allowing it since it's localhost.";
} else if (IPIsAny(port_->Network()->GetBestIP())) {
RTC_LOG(LS_WARNING)
<< "Socket is bound to the address:"
<< socket_address.ipaddr().ToSensitiveString()
<< ", rather than an address associated with network:"
<< port_->Network()->ToString()
<< ". Still allowing it since it's the 'any' address"
", possibly caused by multiple_routes being disabled.";
} else {
RTC_LOG(LS_WARNING) << "Dropping connection as TCP socket bound to IP "
<< socket_address.ipaddr().ToSensitiveString()
<< ", rather than an address associated with network:"
<< port_->Network()->ToString();
OnClose(socket, 0);
return;
}
}
// Connection is established successfully.
set_connected(true);
connection_pending_ = false;
}
void TCPConnection::OnClose(rtc::AsyncPacketSocket* socket, int error) {
RTC_DCHECK_RUN_ON(network_thread());
RTC_DCHECK_EQ(socket, socket_.get());
RTC_LOG(LS_INFO) << ToString() << ": Connection closed with error " << error;
if (!port_) {
RTC_LOG(LS_ERROR) << "TCPConnection: Port has been deleted.";
return;
}
// Guard against the condition where IPC socket will call OnClose for every
// packet it can't send.
if (connected()) {
set_connected(false);
// Prevent the connection from being destroyed by redundant SignalClose
// events.
pretending_to_be_writable_ = true;
// If this connection can't become connected and writable again in 5
// seconds, it's time to tear this down. This is the case for the original
// TCP connection on passive side during a reconnect.
// We don't attempt reconnect right here. This is to avoid a case where the
// shutdown is intentional and reconnect is not necessary. We only reconnect
// when the connection is used to Send() or Ping().
network_thread()->PostDelayedTask(
SafeTask(network_safety_.flag(),
[this]() {
if (pretending_to_be_writable_) {
Destroy();
}
}),
TimeDelta::Millis(reconnection_timeout()));
} else if (!pretending_to_be_writable_) {
// OnClose could be called when the underneath socket times out during the
// initial connect() (i.e. `pretending_to_be_writable_` is false) . We have
// to manually destroy here as this connection, as never connected, will not
// be scheduled for ping to trigger destroy.
DisconnectSocketSignals(socket_.get());
port()->DestroyConnectionAsync(this);
}
}
void TCPConnection::MaybeReconnect() {
RTC_DCHECK_RUN_ON(network_thread());
// Only reconnect for an outgoing TCPConnection when OnClose was signaled and
// no outstanding reconnect is pending.
if (connected() || connection_pending_ || !outgoing_) {
return;
}
RTC_LOG(LS_INFO) << ToString()
<< ": TCP Connection with remote is closed, "
"trying to reconnect";
CreateOutgoingTcpSocket();
error_ = EPIPE;
}
void TCPConnection::OnReadPacket(rtc::AsyncPacketSocket* socket,
const rtc::ReceivedPacket& packet) {
RTC_DCHECK_RUN_ON(network_thread());
RTC_DCHECK_EQ(socket, socket_.get());
Connection::OnReadPacket(packet);
}
void TCPConnection::OnReadyToSend(rtc::AsyncPacketSocket* socket) {
RTC_DCHECK_RUN_ON(network_thread());
RTC_DCHECK_EQ(socket, socket_.get());
Connection::OnReadyToSend();
}
void TCPConnection::OnDestroyed(Connection* c) {
RTC_DCHECK_RUN_ON(network_thread());
RTC_DCHECK_EQ(c, this);
if (socket_) {
DisconnectSocketSignals(socket_.get());
}
}
void TCPConnection::CreateOutgoingTcpSocket() {
RTC_DCHECK(outgoing_);
int opts = (remote_candidate().protocol() == SSLTCP_PROTOCOL_NAME)
? rtc::PacketSocketFactory::OPT_TLS_FAKE
: 0;
if (socket_) {
DisconnectSocketSignals(socket_.get());
}
rtc::PacketSocketTcpOptions tcp_opts;
tcp_opts.opts = opts;
socket_.reset(port()->socket_factory()->CreateClientTcpSocket(
rtc::SocketAddress(port()->Network()->GetBestIP(), 0),
remote_candidate().address(), port()->proxy(), port()->user_agent(),
tcp_opts));
if (socket_) {
RTC_LOG(LS_VERBOSE) << ToString() << ": Connecting from "
<< socket_->GetLocalAddress().ToSensitiveString()
<< " to "
<< remote_candidate().address().ToSensitiveString();
set_connected(false);
connection_pending_ = true;
ConnectSocketSignals(socket_.get());
} else {
RTC_LOG(LS_WARNING) << ToString() << ": Failed to create connection to "
<< remote_candidate().address().ToSensitiveString();
set_state(IceCandidatePairState::FAILED);
// We can't FailAndPrune directly here. FailAndPrune and deletes all
// the StunRequests from the request_map_. And if this is in the stack
// of Connection::Ping(), we are still using the request.
// Unwind the stack and defer the FailAndPrune.
network_thread()->PostTask(
SafeTask(network_safety_.flag(), [this]() { FailAndPrune(); }));
}
}
void TCPConnection::ConnectSocketSignals(rtc::AsyncPacketSocket* socket) {
if (outgoing_) {
socket->SignalConnect.connect(this, &TCPConnection::OnConnect);
}
socket->RegisterReceivedPacketCallback(
[&](rtc::AsyncPacketSocket* socket, const rtc::ReceivedPacket& packet) {
OnReadPacket(socket, packet);
});
socket->SignalReadyToSend.connect(this, &TCPConnection::OnReadyToSend);
socket->SubscribeCloseEvent(this, [this, safety = network_safety_.flag()](
rtc::AsyncPacketSocket* s, int err) {
if (safety->alive())
OnClose(s, err);
});
}
void TCPConnection::DisconnectSocketSignals(rtc::AsyncPacketSocket* socket) {
if (outgoing_) {
socket->SignalConnect.disconnect(this);
}
socket->DeregisterReceivedPacketCallback();
socket->SignalReadyToSend.disconnect(this);
socket->UnsubscribeCloseEvent(this);
}
} // namespace cricket

View file

@ -0,0 +1,203 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef P2P_BASE_TCP_PORT_H_
#define P2P_BASE_TCP_PORT_H_
#include <list>
#include <memory>
#include <string>
#include "absl/memory/memory.h"
#include "absl/strings/string_view.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "p2p/base/connection.h"
#include "p2p/base/port.h"
#include "rtc_base/async_packet_socket.h"
#include "rtc_base/containers/flat_map.h"
#include "rtc_base/network/received_packet.h"
namespace cricket {
class TCPConnection;
// Communicates using a local TCP port.
//
// This class is designed to allow subclasses to take advantage of the
// connection management provided by this class. A subclass should take of all
// packet sending and preparation, but when a packet is received, it should
// call this TCPPort::OnReadPacket (3 arg) to dispatch to a connection.
class TCPPort : public Port {
public:
static std::unique_ptr<TCPPort> Create(
rtc::Thread* thread,
rtc::PacketSocketFactory* factory,
const rtc::Network* network,
uint16_t min_port,
uint16_t max_port,
absl::string_view username,
absl::string_view password,
bool allow_listen,
const webrtc::FieldTrialsView* field_trials = nullptr) {
// Using `new` to access a non-public constructor.
return absl::WrapUnique(new TCPPort(thread, factory, network, min_port,
max_port, username, password,
allow_listen, field_trials));
}
~TCPPort() override;
Connection* CreateConnection(const Candidate& address,
CandidateOrigin origin) override;
void PrepareAddress() override;
// Options apply to accepted sockets.
// TODO(bugs.webrtc.org/13065): Apply also to outgoing and existing
// connections.
int GetOption(rtc::Socket::Option opt, int* value) override;
int SetOption(rtc::Socket::Option opt, int value) override;
int GetError() override;
bool SupportsProtocol(absl::string_view protocol) const override;
ProtocolType GetProtocol() const override;
protected:
TCPPort(rtc::Thread* thread,
rtc::PacketSocketFactory* factory,
const rtc::Network* network,
uint16_t min_port,
uint16_t max_port,
absl::string_view username,
absl::string_view password,
bool allow_listen,
const webrtc::FieldTrialsView* field_trials);
// Handles sending using the local TCP socket.
int SendTo(const void* data,
size_t size,
const rtc::SocketAddress& addr,
const rtc::PacketOptions& options,
bool payload) override;
// Accepts incoming TCP connection.
void OnNewConnection(rtc::AsyncListenSocket* socket,
rtc::AsyncPacketSocket* new_socket);
private:
struct Incoming {
rtc::SocketAddress addr;
rtc::AsyncPacketSocket* socket;
};
void TryCreateServerSocket();
rtc::AsyncPacketSocket* GetIncoming(const rtc::SocketAddress& addr,
bool remove = false);
// Receives packet signal from the local TCP Socket.
void OnReadPacket(rtc::AsyncPacketSocket* socket,
const rtc::ReceivedPacket& packet);
void OnSentPacket(rtc::AsyncPacketSocket* socket,
const rtc::SentPacket& sent_packet) override;
void OnReadyToSend(rtc::AsyncPacketSocket* socket);
bool allow_listen_;
std::unique_ptr<rtc::AsyncListenSocket> listen_socket_;
// Options to be applied to accepted sockets.
// TODO(bugs.webrtc:13065): Configure connect/accept in the same way, but
// currently, setting OPT_NODELAY for client sockets is done (unconditionally)
// by BasicPacketSocketFactory::CreateClientTcpSocket.
webrtc::flat_map<rtc::Socket::Option, int> socket_options_;
int error_;
std::list<Incoming> incoming_;
friend class TCPConnection;
};
class TCPConnection : public Connection, public sigslot::has_slots<> {
public:
// Connection is outgoing unless socket is specified
TCPConnection(rtc::WeakPtr<Port> tcp_port,
const Candidate& candidate,
rtc::AsyncPacketSocket* socket = nullptr);
~TCPConnection() override;
int Send(const void* data,
size_t size,
const rtc::PacketOptions& options) override;
int GetError() override;
rtc::AsyncPacketSocket* socket() { return socket_.get(); }
// Allow test cases to overwrite the default timeout period.
int reconnection_timeout() const { return reconnection_timeout_; }
void set_reconnection_timeout(int timeout_in_ms) {
reconnection_timeout_ = timeout_in_ms;
}
protected:
// Set waiting_for_stun_binding_complete_ to false to allow data packets in
// addition to what Port::OnConnectionRequestResponse does.
void OnConnectionRequestResponse(StunRequest* req,
StunMessage* response) override;
private:
friend class TCPPort; // For `MaybeReconnect()`.
// Helper function to handle the case when Ping or Send fails with error
// related to socket close.
void MaybeReconnect();
void CreateOutgoingTcpSocket() RTC_RUN_ON(network_thread());
void ConnectSocketSignals(rtc::AsyncPacketSocket* socket)
RTC_RUN_ON(network_thread());
void DisconnectSocketSignals(rtc::AsyncPacketSocket* socket)
RTC_RUN_ON(network_thread());
void OnConnect(rtc::AsyncPacketSocket* socket);
void OnClose(rtc::AsyncPacketSocket* socket, int error);
void OnReadPacket(rtc::AsyncPacketSocket* socket,
const rtc::ReceivedPacket& packet);
void OnReadyToSend(rtc::AsyncPacketSocket* socket);
void OnDestroyed(Connection* c);
TCPPort* tcp_port() {
RTC_DCHECK_EQ(port()->GetProtocol(), PROTO_TCP);
return static_cast<TCPPort*>(port());
}
std::unique_ptr<rtc::AsyncPacketSocket> socket_;
int error_;
const bool outgoing_;
// Guard against multiple outgoing tcp connection during a reconnect.
bool connection_pending_;
// Guard against data packets sent when we reconnect a TCP connection. During
// reconnecting, when a new tcp connection has being made, we can't send data
// packets out until the STUN binding is completed (i.e. the write state is
// set to WRITABLE again by Connection::OnConnectionRequestResponse). IPC
// socket, when receiving data packets before that, will trigger OnError which
// will terminate the newly created connection.
bool pretending_to_be_writable_;
// Allow test case to overwrite the default timeout period.
int reconnection_timeout_;
webrtc::ScopedTaskSafety network_safety_;
};
} // namespace cricket
#endif // P2P_BASE_TCP_PORT_H_

View file

@ -0,0 +1,48 @@
/*
* Copyright 2017 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/test_stun_server.h"
#include <memory>
#include "rtc_base/socket.h"
#include "rtc_base/socket_server.h"
namespace cricket {
std::unique_ptr<TestStunServer, std::function<void(TestStunServer*)>>
TestStunServer::Create(rtc::SocketServer* ss,
const rtc::SocketAddress& addr,
rtc::Thread& network_thread) {
rtc::Socket* socket = ss->CreateSocket(addr.family(), SOCK_DGRAM);
rtc::AsyncUDPSocket* udp_socket = rtc::AsyncUDPSocket::Create(socket, addr);
TestStunServer* server = nullptr;
network_thread.BlockingCall(
[&]() { server = new TestStunServer(udp_socket, network_thread); });
std::unique_ptr<TestStunServer, std::function<void(TestStunServer*)>> result(
server, [&](TestStunServer* server) {
network_thread.BlockingCall([server]() { delete server; });
});
return result;
}
void TestStunServer::OnBindingRequest(StunMessage* msg,
const rtc::SocketAddress& remote_addr) {
RTC_DCHECK_RUN_ON(&network_thread_);
if (fake_stun_addr_.IsNil()) {
StunServer::OnBindingRequest(msg, remote_addr);
} else {
StunMessage response(STUN_BINDING_RESPONSE, msg->transaction_id());
GetStunBindResponse(msg, fake_stun_addr_, &response);
SendResponse(response, remote_addr);
}
}
} // namespace cricket

View file

@ -0,0 +1,55 @@
/*
* Copyright 2008 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.
*/
#ifndef P2P_BASE_TEST_STUN_SERVER_H_
#define P2P_BASE_TEST_STUN_SERVER_H_
#include <memory>
#include "api/transport/stun.h"
#include "p2p/base/stun_server.h"
#include "rtc_base/async_udp_socket.h"
#include "rtc_base/socket_address.h"
#include "rtc_base/socket_server.h"
#include "rtc_base/thread.h"
namespace cricket {
// A test STUN server. Useful for unit tests.
class TestStunServer : StunServer {
public:
using StunServerPtr =
std::unique_ptr<TestStunServer, std::function<void(TestStunServer*)>>;
static StunServerPtr Create(rtc::SocketServer* ss,
const rtc::SocketAddress& addr,
rtc::Thread& network_thread);
// Set a fake STUN address to return to the client.
void set_fake_stun_addr(const rtc::SocketAddress& addr) {
fake_stun_addr_ = addr;
}
private:
static void DeleteOnNetworkThread(TestStunServer* server);
TestStunServer(rtc::AsyncUDPSocket* socket, rtc::Thread& network_thread)
: StunServer(socket), network_thread_(network_thread) {}
void OnBindingRequest(StunMessage* msg,
const rtc::SocketAddress& remote_addr) override;
private:
rtc::SocketAddress fake_stun_addr_;
rtc::Thread& network_thread_;
};
} // namespace cricket
#endif // P2P_BASE_TEST_STUN_SERVER_H_

View file

@ -0,0 +1,59 @@
/*
* Copyright 2017 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.
*/
#ifndef P2P_BASE_TEST_TURN_CUSTOMIZER_H_
#define P2P_BASE_TEST_TURN_CUSTOMIZER_H_
#include <memory>
#include "api/turn_customizer.h"
#include "rtc_base/gunit.h"
namespace cricket {
class TestTurnCustomizer : public webrtc::TurnCustomizer {
public:
TestTurnCustomizer() {}
virtual ~TestTurnCustomizer() {}
enum TestTurnAttributeExtensions {
// Test only attribute
STUN_ATTR_COUNTER = 0xFF02 // Number
};
void MaybeModifyOutgoingStunMessage(cricket::PortInterface* port,
cricket::StunMessage* message) override {
modify_cnt_++;
ASSERT_NE(0, message->type());
if (add_counter_) {
message->AddAttribute(std::make_unique<cricket::StunUInt32Attribute>(
STUN_ATTR_COUNTER, modify_cnt_));
}
return;
}
bool AllowChannelData(cricket::PortInterface* port,
const void* data,
size_t size,
bool payload) override {
allow_channel_data_cnt_++;
return allow_channel_data_;
}
bool add_counter_ = false;
bool allow_channel_data_ = true;
unsigned int modify_cnt_ = 0;
unsigned int allow_channel_data_cnt_ = 0;
};
} // namespace cricket
#endif // P2P_BASE_TEST_TURN_CUSTOMIZER_H_

View file

@ -0,0 +1,161 @@
/*
* 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.
*/
#ifndef P2P_BASE_TEST_TURN_SERVER_H_
#define P2P_BASE_TEST_TURN_SERVER_H_
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/string_view.h"
#include "api/sequence_checker.h"
#include "api/transport/stun.h"
#include "p2p/base/basic_packet_socket_factory.h"
#include "p2p/base/turn_server.h"
#include "rtc_base/async_udp_socket.h"
#include "rtc_base/ssl_adapter.h"
#include "rtc_base/ssl_identity.h"
#include "rtc_base/thread.h"
namespace cricket {
static const char kTestRealm[] = "example.org";
static const char kTestSoftware[] = "TestTurnServer";
class TestTurnRedirector : public TurnRedirectInterface {
public:
explicit TestTurnRedirector(const std::vector<rtc::SocketAddress>& addresses)
: alternate_server_addresses_(addresses),
iter_(alternate_server_addresses_.begin()) {}
virtual bool ShouldRedirect(const rtc::SocketAddress&,
rtc::SocketAddress* out) {
if (!out || iter_ == alternate_server_addresses_.end()) {
return false;
}
*out = *iter_++;
return true;
}
private:
const std::vector<rtc::SocketAddress>& alternate_server_addresses_;
std::vector<rtc::SocketAddress>::const_iterator iter_;
};
class TestTurnServer : public TurnAuthInterface {
public:
TestTurnServer(rtc::Thread* thread,
rtc::SocketFactory* socket_factory,
const rtc::SocketAddress& int_addr,
const rtc::SocketAddress& udp_ext_addr,
ProtocolType int_protocol = PROTO_UDP,
bool ignore_bad_cert = true,
absl::string_view common_name = "test turn server")
: server_(thread), socket_factory_(socket_factory) {
AddInternalSocket(int_addr, int_protocol, ignore_bad_cert, common_name);
server_.SetExternalSocketFactory(
new rtc::BasicPacketSocketFactory(socket_factory), udp_ext_addr);
server_.set_realm(kTestRealm);
server_.set_software(kTestSoftware);
server_.set_auth_hook(this);
}
~TestTurnServer() { RTC_DCHECK(thread_checker_.IsCurrent()); }
void set_enable_otu_nonce(bool enable) {
RTC_DCHECK(thread_checker_.IsCurrent());
server_.set_enable_otu_nonce(enable);
}
TurnServer* server() {
RTC_DCHECK(thread_checker_.IsCurrent());
return &server_;
}
void set_redirect_hook(TurnRedirectInterface* redirect_hook) {
RTC_DCHECK(thread_checker_.IsCurrent());
server_.set_redirect_hook(redirect_hook);
}
void set_enable_permission_checks(bool enable) {
RTC_DCHECK(thread_checker_.IsCurrent());
server_.set_enable_permission_checks(enable);
}
void AddInternalSocket(const rtc::SocketAddress& int_addr,
ProtocolType proto,
bool ignore_bad_cert = true,
absl::string_view common_name = "test turn server") {
RTC_DCHECK(thread_checker_.IsCurrent());
if (proto == cricket::PROTO_UDP) {
server_.AddInternalSocket(
rtc::AsyncUDPSocket::Create(socket_factory_, int_addr), proto);
} else if (proto == cricket::PROTO_TCP || proto == cricket::PROTO_TLS) {
// For TCP we need to create a server socket which can listen for incoming
// new connections.
rtc::Socket* socket = socket_factory_->CreateSocket(AF_INET, SOCK_STREAM);
socket->Bind(int_addr);
socket->Listen(5);
if (proto == cricket::PROTO_TLS) {
// For TLS, wrap the TCP socket with an SSL adapter. The adapter must
// be configured with a self-signed certificate for testing.
// Additionally, the client will not present a valid certificate, so we
// must not fail when checking the peer's identity.
std::unique_ptr<rtc::SSLAdapterFactory> ssl_adapter_factory =
rtc::SSLAdapterFactory::Create();
ssl_adapter_factory->SetRole(rtc::SSL_SERVER);
ssl_adapter_factory->SetIdentity(
rtc::SSLIdentity::Create(common_name, rtc::KeyParams()));
ssl_adapter_factory->SetIgnoreBadCert(ignore_bad_cert);
server_.AddInternalServerSocket(socket, proto,
std::move(ssl_adapter_factory));
} else {
server_.AddInternalServerSocket(socket, proto);
}
} else {
RTC_DCHECK_NOTREACHED() << "Unknown protocol type: " << proto;
}
}
// Finds the first allocation in the server allocation map with a source
// ip and port matching the socket address provided.
TurnServerAllocation* FindAllocation(const rtc::SocketAddress& src) {
RTC_DCHECK(thread_checker_.IsCurrent());
const TurnServer::AllocationMap& map = server_.allocations();
for (TurnServer::AllocationMap::const_iterator it = map.begin();
it != map.end(); ++it) {
if (src == it->first.src()) {
return it->second.get();
}
}
return NULL;
}
private:
// For this test server, succeed if the password is the same as the username.
// Obviously, do not use this in a production environment.
virtual bool GetKey(absl::string_view username,
absl::string_view realm,
std::string* key) {
RTC_DCHECK(thread_checker_.IsCurrent());
return ComputeStunCredentialHash(std::string(username), std::string(realm),
std::string(username), key);
}
TurnServer server_;
rtc::SocketFactory* socket_factory_;
webrtc::SequenceChecker thread_checker_;
};
} // namespace cricket
#endif // P2P_BASE_TEST_TURN_SERVER_H_

View file

@ -0,0 +1,196 @@
/*
* Copyright 2013 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/transport_description.h"
#include "absl/strings/ascii.h"
#include "absl/strings/match.h"
#include "absl/strings/string_view.h"
#include "p2p/base/p2p_constants.h"
#include "rtc_base/arraysize.h"
#include "rtc_base/logging.h"
#include "rtc_base/strings/string_builder.h"
using webrtc::RTCError;
using webrtc::RTCErrorOr;
using webrtc::RTCErrorType;
namespace cricket {
namespace {
bool IsIceChar(char c) {
// Note: '-', '=', '#' and '_' are *not* valid ice-chars but temporarily
// permitted in order to allow external software to upgrade.
if (c == '-' || c == '=' || c == '#' || c == '_') {
RTC_LOG(LS_WARNING)
<< "'-', '=', '#' and '-' are not valid ice-char and thus not "
<< "permitted in ufrag or pwd. This is a protocol violation that "
<< "is permitted to allow upgrading but will be rejected in "
<< "the future. See https://crbug.com/1053756";
return true;
}
return absl::ascii_isalnum(c) || c == '+' || c == '/';
}
RTCError ValidateIceUfrag(absl::string_view raw_ufrag) {
if (!(ICE_UFRAG_MIN_LENGTH <= raw_ufrag.size() &&
raw_ufrag.size() <= ICE_UFRAG_MAX_LENGTH)) {
rtc::StringBuilder sb;
sb << "ICE ufrag must be between " << ICE_UFRAG_MIN_LENGTH << " and "
<< ICE_UFRAG_MAX_LENGTH << " characters long.";
return RTCError(RTCErrorType::SYNTAX_ERROR, sb.Release());
}
if (!absl::c_all_of(raw_ufrag, IsIceChar)) {
return RTCError(
RTCErrorType::SYNTAX_ERROR,
"ICE ufrag must contain only alphanumeric characters, '+', and '/'.");
}
return RTCError::OK();
}
RTCError ValidateIcePwd(absl::string_view raw_pwd) {
if (!(ICE_PWD_MIN_LENGTH <= raw_pwd.size() &&
raw_pwd.size() <= ICE_PWD_MAX_LENGTH)) {
rtc::StringBuilder sb;
sb << "ICE pwd must be between " << ICE_PWD_MIN_LENGTH << " and "
<< ICE_PWD_MAX_LENGTH << " characters long.";
return RTCError(RTCErrorType::SYNTAX_ERROR, sb.Release());
}
if (!absl::c_all_of(raw_pwd, IsIceChar)) {
return RTCError(
RTCErrorType::SYNTAX_ERROR,
"ICE pwd must contain only alphanumeric characters, '+', and '/'.");
}
return RTCError::OK();
}
} // namespace
RTCErrorOr<IceParameters> IceParameters::Parse(absl::string_view raw_ufrag,
absl::string_view raw_pwd) {
IceParameters parameters(std::string(raw_ufrag), std::string(raw_pwd),
/* renomination= */ false);
auto result = parameters.Validate();
if (!result.ok()) {
return result;
}
return parameters;
}
RTCError IceParameters::Validate() const {
// For legacy protocols.
// TODO(zhihuang): Remove this once the legacy protocol is no longer
// supported.
if (ufrag.empty() && pwd.empty()) {
return RTCError::OK();
}
auto ufrag_result = ValidateIceUfrag(ufrag);
if (!ufrag_result.ok()) {
return ufrag_result;
}
auto pwd_result = ValidateIcePwd(pwd);
if (!pwd_result.ok()) {
return pwd_result;
}
return RTCError::OK();
}
absl::optional<ConnectionRole> StringToConnectionRole(
absl::string_view role_str) {
const char* const roles[] = {
CONNECTIONROLE_ACTIVE_STR, CONNECTIONROLE_PASSIVE_STR,
CONNECTIONROLE_ACTPASS_STR, CONNECTIONROLE_HOLDCONN_STR};
for (size_t i = 0; i < arraysize(roles); ++i) {
if (absl::EqualsIgnoreCase(roles[i], role_str)) {
return static_cast<ConnectionRole>(CONNECTIONROLE_ACTIVE + i);
}
}
return absl::nullopt;
}
bool ConnectionRoleToString(const ConnectionRole& role, std::string* role_str) {
switch (role) {
case cricket::CONNECTIONROLE_ACTIVE:
*role_str = cricket::CONNECTIONROLE_ACTIVE_STR;
break;
case cricket::CONNECTIONROLE_ACTPASS:
*role_str = cricket::CONNECTIONROLE_ACTPASS_STR;
break;
case cricket::CONNECTIONROLE_PASSIVE:
*role_str = cricket::CONNECTIONROLE_PASSIVE_STR;
break;
case cricket::CONNECTIONROLE_HOLDCONN:
*role_str = cricket::CONNECTIONROLE_HOLDCONN_STR;
break;
default:
return false;
}
return true;
}
TransportDescription::TransportDescription()
: ice_mode(ICEMODE_FULL), connection_role(CONNECTIONROLE_NONE) {}
TransportDescription::TransportDescription(
const std::vector<std::string>& transport_options,
absl::string_view ice_ufrag,
absl::string_view ice_pwd,
IceMode ice_mode,
ConnectionRole role,
const rtc::SSLFingerprint* identity_fingerprint)
: transport_options(transport_options),
ice_ufrag(ice_ufrag),
ice_pwd(ice_pwd),
ice_mode(ice_mode),
connection_role(role),
identity_fingerprint(CopyFingerprint(identity_fingerprint)) {}
TransportDescription::TransportDescription(absl::string_view ice_ufrag,
absl::string_view ice_pwd)
: ice_ufrag(ice_ufrag),
ice_pwd(ice_pwd),
ice_mode(ICEMODE_FULL),
connection_role(CONNECTIONROLE_NONE) {}
TransportDescription::TransportDescription(const TransportDescription& from)
: transport_options(from.transport_options),
ice_ufrag(from.ice_ufrag),
ice_pwd(from.ice_pwd),
ice_mode(from.ice_mode),
connection_role(from.connection_role),
identity_fingerprint(CopyFingerprint(from.identity_fingerprint.get())) {}
TransportDescription::~TransportDescription() = default;
TransportDescription& TransportDescription::operator=(
const TransportDescription& from) {
// Self-assignment
if (this == &from)
return *this;
transport_options = from.transport_options;
ice_ufrag = from.ice_ufrag;
ice_pwd = from.ice_pwd;
ice_mode = from.ice_mode;
connection_role = from.connection_role;
identity_fingerprint.reset(CopyFingerprint(from.identity_fingerprint.get()));
return *this;
}
} // namespace cricket

View file

@ -0,0 +1,143 @@
/*
* 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.
*/
#ifndef P2P_BASE_TRANSPORT_DESCRIPTION_H_
#define P2P_BASE_TRANSPORT_DESCRIPTION_H_
#include <memory>
#include <string>
#include <vector>
#include "absl/algorithm/container.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "api/rtc_error.h"
#include "p2p/base/p2p_constants.h"
#include "rtc_base/ssl_fingerprint.h"
#include "rtc_base/system/rtc_export.h"
namespace cricket {
// Whether our side of the call is driving the negotiation, or the other side.
enum IceRole { ICEROLE_CONTROLLING = 0, ICEROLE_CONTROLLED, ICEROLE_UNKNOWN };
// ICE RFC 5245 implementation type.
enum IceMode {
ICEMODE_FULL, // As defined in http://tools.ietf.org/html/rfc5245#section-4.1
ICEMODE_LITE // As defined in http://tools.ietf.org/html/rfc5245#section-4.2
};
// RFC 4145 - http://tools.ietf.org/html/rfc4145#section-4
// 'active': The endpoint will initiate an outgoing connection.
// 'passive': The endpoint will accept an incoming connection.
// 'actpass': The endpoint is willing to accept an incoming
// connection or to initiate an outgoing connection.
enum ConnectionRole {
CONNECTIONROLE_NONE = 0,
CONNECTIONROLE_ACTIVE,
CONNECTIONROLE_PASSIVE,
CONNECTIONROLE_ACTPASS,
CONNECTIONROLE_HOLDCONN,
};
struct IceParameters {
// Constructs an IceParameters from a user-provided ufrag/pwd combination.
// Returns a SyntaxError if the ufrag or pwd are malformed.
static RTC_EXPORT webrtc::RTCErrorOr<IceParameters> Parse(
absl::string_view raw_ufrag,
absl::string_view raw_pwd);
// TODO(honghaiz): Include ICE mode in this structure to match the ORTC
// struct:
// http://ortc.org/wp-content/uploads/2016/03/ortc.html#idl-def-RTCIceParameters
std::string ufrag;
std::string pwd;
bool renomination = false;
IceParameters() = default;
IceParameters(absl::string_view ice_ufrag,
absl::string_view ice_pwd,
bool ice_renomination)
: ufrag(ice_ufrag), pwd(ice_pwd), renomination(ice_renomination) {}
bool operator==(const IceParameters& other) const {
return ufrag == other.ufrag && pwd == other.pwd &&
renomination == other.renomination;
}
bool operator!=(const IceParameters& other) const {
return !(*this == other);
}
// Validate IceParameters, returns a SyntaxError if the ufrag or pwd are
// malformed.
webrtc::RTCError Validate() const;
};
extern const char CONNECTIONROLE_ACTIVE_STR[];
extern const char CONNECTIONROLE_PASSIVE_STR[];
extern const char CONNECTIONROLE_ACTPASS_STR[];
extern const char CONNECTIONROLE_HOLDCONN_STR[];
constexpr auto* ICE_OPTION_TRICKLE = "trickle";
constexpr auto* ICE_OPTION_RENOMINATION = "renomination";
absl::optional<ConnectionRole> StringToConnectionRole(
absl::string_view role_str);
bool ConnectionRoleToString(const ConnectionRole& role, std::string* role_str);
struct TransportDescription {
TransportDescription();
TransportDescription(const std::vector<std::string>& transport_options,
absl::string_view ice_ufrag,
absl::string_view ice_pwd,
IceMode ice_mode,
ConnectionRole role,
const rtc::SSLFingerprint* identity_fingerprint);
TransportDescription(absl::string_view ice_ufrag, absl::string_view ice_pwd);
TransportDescription(const TransportDescription& from);
~TransportDescription();
TransportDescription& operator=(const TransportDescription& from);
// TODO(deadbeef): Rename to HasIceOption, etc.
bool HasOption(absl::string_view option) const {
return absl::c_linear_search(transport_options, option);
}
void AddOption(absl::string_view option) {
transport_options.emplace_back(option);
}
bool secure() const { return identity_fingerprint != nullptr; }
IceParameters GetIceParameters() const {
return IceParameters(ice_ufrag, ice_pwd,
HasOption(ICE_OPTION_RENOMINATION));
}
static rtc::SSLFingerprint* CopyFingerprint(const rtc::SSLFingerprint* from) {
if (!from)
return NULL;
return new rtc::SSLFingerprint(*from);
}
// These are actually ICE options (appearing in the ice-options attribute in
// SDP).
// TODO(deadbeef): Rename to ice_options.
std::vector<std::string> transport_options;
std::string ice_ufrag;
std::string ice_pwd;
IceMode ice_mode;
ConnectionRole connection_role;
std::unique_ptr<rtc::SSLFingerprint> identity_fingerprint;
};
} // namespace cricket
#endif // P2P_BASE_TRANSPORT_DESCRIPTION_H_

View file

@ -0,0 +1,159 @@
/*
* 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/transport_description_factory.h"
#include <stddef.h>
#include <memory>
#include <string>
#include "p2p/base/transport_description.h"
#include "rtc_base/logging.h"
#include "rtc_base/ssl_fingerprint.h"
namespace cricket {
TransportDescriptionFactory::TransportDescriptionFactory(
const webrtc::FieldTrialsView& field_trials)
: field_trials_(field_trials) {}
TransportDescriptionFactory::~TransportDescriptionFactory() = default;
std::unique_ptr<TransportDescription> TransportDescriptionFactory::CreateOffer(
const TransportOptions& options,
const TransportDescription* current_description,
IceCredentialsIterator* ice_credentials) const {
auto desc = std::make_unique<TransportDescription>();
// Generate the ICE credentials if we don't already have them.
if (!current_description || options.ice_restart) {
IceParameters credentials = ice_credentials->GetIceCredentials();
desc->ice_ufrag = credentials.ufrag;
desc->ice_pwd = credentials.pwd;
} else {
desc->ice_ufrag = current_description->ice_ufrag;
desc->ice_pwd = current_description->ice_pwd;
}
desc->AddOption(ICE_OPTION_TRICKLE);
if (options.enable_ice_renomination) {
desc->AddOption(ICE_OPTION_RENOMINATION);
}
// If we are not trying to establish a secure transport, don't add a
// fingerprint.
if (insecure_ && !certificate_) {
return desc;
}
// Fail if we can't create the fingerprint.
// If we are the initiator set role to "actpass".
if (!SetSecurityInfo(desc.get(), CONNECTIONROLE_ACTPASS)) {
return NULL;
}
return desc;
}
std::unique_ptr<TransportDescription> TransportDescriptionFactory::CreateAnswer(
const TransportDescription* offer,
const TransportOptions& options,
bool require_transport_attributes,
const TransportDescription* current_description,
IceCredentialsIterator* ice_credentials) const {
// TODO(juberti): Figure out why we get NULL offers, and fix this upstream.
if (!offer) {
RTC_LOG(LS_WARNING) << "Failed to create TransportDescription answer "
"because offer is NULL";
return NULL;
}
auto desc = std::make_unique<TransportDescription>();
// Generate the ICE credentials if we don't already have them or ice is
// being restarted.
if (!current_description || options.ice_restart) {
IceParameters credentials = ice_credentials->GetIceCredentials();
desc->ice_ufrag = credentials.ufrag;
desc->ice_pwd = credentials.pwd;
} else {
desc->ice_ufrag = current_description->ice_ufrag;
desc->ice_pwd = current_description->ice_pwd;
}
desc->AddOption(ICE_OPTION_TRICKLE);
if (options.enable_ice_renomination) {
desc->AddOption(ICE_OPTION_RENOMINATION);
}
// Special affordance for testing: Answer without DTLS params
// if we are insecure without a certificate, or if we are
// insecure with a non-DTLS offer.
if ((!certificate_ || !offer->identity_fingerprint.get()) && insecure()) {
return desc;
}
if (!offer->identity_fingerprint.get()) {
if (require_transport_attributes) {
// We require DTLS, but the other side didn't offer it. Fail.
RTC_LOG(LS_WARNING) << "Failed to create TransportDescription answer "
"because of incompatible security settings";
return NULL;
}
// This may be a bundled section, fingerprint may legitimately be missing.
return desc;
}
// Negotiate security params.
// The offer supports DTLS, so answer with DTLS.
RTC_CHECK(certificate_);
ConnectionRole role = CONNECTIONROLE_NONE;
// If the offer does not constrain the role, go with preference.
if (offer->connection_role == CONNECTIONROLE_ACTPASS) {
role = (options.prefer_passive_role) ? CONNECTIONROLE_PASSIVE
: CONNECTIONROLE_ACTIVE;
} else if (offer->connection_role == CONNECTIONROLE_ACTIVE) {
role = CONNECTIONROLE_PASSIVE;
} else if (offer->connection_role == CONNECTIONROLE_PASSIVE) {
role = CONNECTIONROLE_ACTIVE;
} else if (offer->connection_role == CONNECTIONROLE_NONE) {
// This case may be reached if a=setup is not present in the SDP.
RTC_LOG(LS_WARNING) << "Remote offer connection role is NONE, which is "
"a protocol violation";
role = (options.prefer_passive_role) ? CONNECTIONROLE_PASSIVE
: CONNECTIONROLE_ACTIVE;
} else {
RTC_LOG(LS_ERROR) << "Remote offer connection role is " << role
<< " which is a protocol violation";
RTC_DCHECK_NOTREACHED();
return NULL;
}
if (!SetSecurityInfo(desc.get(), role)) {
return NULL;
}
return desc;
}
bool TransportDescriptionFactory::SetSecurityInfo(TransportDescription* desc,
ConnectionRole role) const {
if (!certificate_) {
RTC_LOG(LS_ERROR) << "Cannot create identity digest with no certificate";
return false;
}
// This digest algorithm is used to produce the a=fingerprint lines in SDP.
// RFC 4572 Section 5 requires that those lines use the same hash function as
// the certificate's signature, which is what CreateFromCertificate does.
desc->identity_fingerprint =
rtc::SSLFingerprint::CreateFromCertificate(*certificate_);
if (!desc->identity_fingerprint) {
return false;
}
// Assign security role.
desc->connection_role = role;
return true;
}
} // namespace cricket

View file

@ -0,0 +1,94 @@
/*
* 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.
*/
#ifndef P2P_BASE_TRANSPORT_DESCRIPTION_FACTORY_H_
#define P2P_BASE_TRANSPORT_DESCRIPTION_FACTORY_H_
#include <memory>
#include <utility>
#include "api/field_trials_view.h"
#include "p2p/base/ice_credentials_iterator.h"
#include "p2p/base/transport_description.h"
#include "rtc_base/rtc_certificate.h"
namespace rtc {
class SSLIdentity;
}
namespace cricket {
struct TransportOptions {
bool ice_restart = false;
bool prefer_passive_role = false;
// If true, ICE renomination is supported and will be used if it is also
// supported by the remote side.
bool enable_ice_renomination = false;
};
// Creates transport descriptions according to the supplied configuration.
// When creating answers, performs the appropriate negotiation
// of the various fields to determine the proper result.
class TransportDescriptionFactory {
public:
// Default ctor; use methods below to set configuration.
explicit TransportDescriptionFactory(
const webrtc::FieldTrialsView& field_trials);
~TransportDescriptionFactory();
// The certificate to use when setting up DTLS.
const rtc::scoped_refptr<rtc::RTCCertificate>& certificate() const {
return certificate_;
}
// Specifies the certificate to use
void set_certificate(rtc::scoped_refptr<rtc::RTCCertificate> certificate) {
certificate_ = std::move(certificate);
}
// Creates a transport description suitable for use in an offer.
std::unique_ptr<TransportDescription> CreateOffer(
const TransportOptions& options,
const TransportDescription* current_description,
IceCredentialsIterator* ice_credentials) const;
// Create a transport description that is a response to an offer.
//
// If `require_transport_attributes` is true, then TRANSPORT category
// attributes are expected to be present in `offer`, as defined by
// sdp-mux-attributes, and null will be returned otherwise. It's expected
// that this will be set to false for an m= section that's in a BUNDLE group
// but isn't the first m= section in the group.
std::unique_ptr<TransportDescription> CreateAnswer(
const TransportDescription* offer,
const TransportOptions& options,
bool require_transport_attributes,
const TransportDescription* current_description,
IceCredentialsIterator* ice_credentials) const;
const webrtc::FieldTrialsView& trials() const { return field_trials_; }
// Functions for disabling encryption - test only!
// In insecure mode, the connection will accept a description without
// fingerprint, and will generate SDP even if certificate is not set.
// If certificate is set, it will accept a description both with and
// without fingerprint, but will generate a description with fingerprint.
bool insecure() const { return insecure_; }
void SetInsecureForTesting() { insecure_ = true; }
private:
bool SetSecurityInfo(TransportDescription* description,
ConnectionRole role) const;
bool insecure_ = false;
rtc::scoped_refptr<rtc::RTCCertificate> certificate_;
const webrtc::FieldTrialsView& field_trials_;
};
} // namespace cricket
#endif // P2P_BASE_TRANSPORT_DESCRIPTION_FACTORY_H_

View file

@ -0,0 +1,42 @@
/*
* 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.
*/
#ifndef P2P_BASE_TRANSPORT_INFO_H_
#define P2P_BASE_TRANSPORT_INFO_H_
#include <string>
#include <vector>
#include "api/candidate.h"
#include "p2p/base/p2p_constants.h"
#include "p2p/base/transport_description.h"
#include "rtc_base/helpers.h"
namespace cricket {
// A TransportInfo is NOT a transport-info message. It is comparable
// to a "ContentInfo". A transport-infos message is basically just a
// collection of TransportInfos.
struct TransportInfo {
TransportInfo() {}
TransportInfo(const std::string& content_name,
const TransportDescription& description)
: content_name(content_name), description(description) {}
std::string content_name;
TransportDescription description;
};
typedef std::vector<TransportInfo> TransportInfos;
} // namespace cricket
#endif // P2P_BASE_TRANSPORT_INFO_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,374 @@
/*
* 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.
*/
#ifndef P2P_BASE_TURN_PORT_H_
#define P2P_BASE_TURN_PORT_H_
#include <stdio.h>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <vector>
#include "absl/memory/memory.h"
#include "absl/strings/string_view.h"
#include "api/async_dns_resolver.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "api/task_queue/task_queue_base.h"
#include "p2p/base/port.h"
#include "p2p/base/port_allocator.h"
#include "p2p/client/relay_port_factory_interface.h"
#include "rtc_base/async_packet_socket.h"
#include "rtc_base/network/received_packet.h"
#include "rtc_base/ssl_certificate.h"
namespace webrtc {
class TurnCustomizer;
}
namespace cricket {
const int kMaxTurnUsernameLength = 509; // RFC 8489 section 14.3
extern const int STUN_ATTR_TURN_LOGGING_ID;
extern const char TURN_PORT_TYPE[];
class TurnAllocateRequest;
class TurnEntry;
class TurnPort : public Port {
public:
enum PortState {
STATE_CONNECTING, // Initial state, cannot send any packets.
STATE_CONNECTED, // Socket connected, ready to send stun requests.
STATE_READY, // Received allocate success, can send any packets.
STATE_RECEIVEONLY, // Had REFRESH_REQUEST error, cannot send any packets.
STATE_DISCONNECTED, // TCP connection died, cannot send/receive any
// packets.
};
static bool Validate(const CreateRelayPortArgs& args) {
// Do basic parameter validation.
if (args.config->credentials.username.size() > kMaxTurnUsernameLength) {
RTC_LOG(LS_ERROR) << "Attempt to use TURN with a too long username "
<< "of length "
<< args.config->credentials.username.size();
return false;
}
// Do not connect to low-numbered ports. The default STUN port is 3478.
if (!AllowedTurnPort(args.server_address->address.port(),
args.field_trials)) {
RTC_LOG(LS_ERROR) << "Attempt to use TURN to connect to port "
<< args.server_address->address.port();
return false;
}
return true;
}
// Create a TURN port using the shared UDP socket, `socket`.
static std::unique_ptr<TurnPort> Create(const CreateRelayPortArgs& args,
rtc::AsyncPacketSocket* socket) {
if (!Validate(args)) {
return nullptr;
}
// Using `new` to access a non-public constructor.
return absl::WrapUnique(
new TurnPort(args.network_thread, args.socket_factory, args.network,
socket, args.username, args.password, *args.server_address,
args.config->credentials, args.relative_priority,
args.config->tls_alpn_protocols,
args.config->tls_elliptic_curves, args.turn_customizer,
args.config->tls_cert_verifier, args.field_trials));
}
// Create a TURN port that will use a new socket, bound to `network` and
// using a port in the range between `min_port` and `max_port`.
static std::unique_ptr<TurnPort> Create(const CreateRelayPortArgs& args,
int min_port,
int max_port) {
if (!Validate(args)) {
return nullptr;
}
// Using `new` to access a non-public constructor.
return absl::WrapUnique(
new TurnPort(args.network_thread, args.socket_factory, args.network,
min_port, max_port, args.username, args.password,
*args.server_address, args.config->credentials,
args.relative_priority, args.config->tls_alpn_protocols,
args.config->tls_elliptic_curves, args.turn_customizer,
args.config->tls_cert_verifier, args.field_trials));
}
~TurnPort() override;
const ProtocolAddress& server_address() const { return server_address_; }
// Returns an empty address if the local address has not been assigned.
rtc::SocketAddress GetLocalAddress() const;
bool ready() const { return state_ == STATE_READY; }
bool connected() const {
return state_ == STATE_READY || state_ == STATE_CONNECTED;
}
const RelayCredentials& credentials() const { return credentials_; }
ProtocolType GetProtocol() const override;
virtual TlsCertPolicy GetTlsCertPolicy() const;
virtual void SetTlsCertPolicy(TlsCertPolicy tls_cert_policy);
void SetTurnLoggingId(absl::string_view turn_logging_id);
virtual std::vector<std::string> GetTlsAlpnProtocols() const;
virtual std::vector<std::string> GetTlsEllipticCurves() const;
// Release a TURN allocation by sending a refresh with lifetime 0.
// Sets state to STATE_RECEIVEONLY.
void Release();
void PrepareAddress() override;
Connection* CreateConnection(const Candidate& c,
PortInterface::CandidateOrigin origin) override;
int SendTo(const void* data,
size_t size,
const rtc::SocketAddress& addr,
const rtc::PacketOptions& options,
bool payload) override;
int SetOption(rtc::Socket::Option opt, int value) override;
int GetOption(rtc::Socket::Option opt, int* value) override;
int GetError() override;
bool HandleIncomingPacket(rtc::AsyncPacketSocket* socket,
const rtc::ReceivedPacket& packet) override;
bool CanHandleIncomingPacketsFrom(
const rtc::SocketAddress& addr) const override;
// Checks if a connection exists for `addr` before forwarding the call to
// the base class.
void SendBindingErrorResponse(StunMessage* message,
const rtc::SocketAddress& addr,
int error_code,
absl::string_view reason) override;
virtual void OnReadPacket(rtc::AsyncPacketSocket* socket,
const rtc::ReceivedPacket& packet);
void OnSentPacket(rtc::AsyncPacketSocket* socket,
const rtc::SentPacket& sent_packet) override;
virtual void OnReadyToSend(rtc::AsyncPacketSocket* socket);
bool SupportsProtocol(absl::string_view protocol) const override;
void OnSocketConnect(rtc::AsyncPacketSocket* socket);
void OnSocketClose(rtc::AsyncPacketSocket* socket, int error);
const std::string& hash() const { return hash_; }
const std::string& nonce() const { return nonce_; }
int error() const { return error_; }
void OnAllocateMismatch();
rtc::AsyncPacketSocket* socket() const { return socket_; }
StunRequestManager& request_manager() { return request_manager_; }
bool HasRequests() { return !request_manager_.empty(); }
void set_credentials(const RelayCredentials& credentials) {
credentials_ = credentials;
}
// Finds the turn entry with `address` and sets its channel id.
// Returns true if the entry is found.
bool SetEntryChannelId(const rtc::SocketAddress& address, int channel_id);
void HandleConnectionDestroyed(Connection* conn) override;
void CloseForTest() { Close(); }
// TODO(solenberg): Tests should be refactored to not peek at internal state.
class CallbacksForTest {
public:
virtual ~CallbacksForTest() {}
virtual void OnTurnCreatePermissionResult(int code) = 0;
virtual void OnTurnRefreshResult(int code) = 0;
virtual void OnTurnPortClosed() = 0;
};
void SetCallbacksForTest(CallbacksForTest* callbacks);
protected:
TurnPort(webrtc::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 = nullptr,
const webrtc::FieldTrialsView* field_trials = nullptr);
TurnPort(webrtc::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 = nullptr,
const webrtc::FieldTrialsView* field_trials = nullptr);
// NOTE: This method needs to be accessible for StunPort
// return true if entry was created (i.e channel_number consumed).
bool CreateOrRefreshEntry(Connection* conn, int channel_number);
rtc::DiffServCodePoint StunDscpValue() const override;
// Shuts down the turn port, frees requests and deletes connections.
void Close();
private:
typedef std::map<rtc::Socket::Option, int> SocketOptionsMap;
typedef std::set<rtc::SocketAddress> AttemptedServerSet;
static bool AllowedTurnPort(int port,
const webrtc::FieldTrialsView* field_trials);
void TryAlternateServer();
bool CreateTurnClientSocket();
void set_nonce(absl::string_view nonce) { nonce_ = std::string(nonce); }
void set_realm(absl::string_view realm) {
if (realm != realm_) {
realm_ = std::string(realm);
UpdateHash();
}
}
void OnRefreshError();
void HandleRefreshError();
bool SetAlternateServer(const rtc::SocketAddress& address);
void ResolveTurnAddress(const rtc::SocketAddress& address);
void OnResolveResult(const webrtc::AsyncDnsResolverResult& result);
void AddRequestAuthInfo(StunMessage* msg);
void OnSendStunPacket(const void* data, size_t size, StunRequest* request);
// Stun address from allocate success response.
// Currently used only for testing.
void OnStunAddress(const rtc::SocketAddress& address);
void OnAllocateSuccess(const rtc::SocketAddress& address,
const rtc::SocketAddress& stun_address);
void OnAllocateError(int error_code, absl::string_view reason);
void OnAllocateRequestTimeout();
void HandleDataIndication(const char* data,
size_t size,
int64_t packet_time_us);
void HandleChannelData(int channel_id,
const char* data,
size_t size,
int64_t packet_time_us);
void DispatchPacket(const char* data,
size_t size,
const rtc::SocketAddress& remote_addr,
ProtocolType proto,
int64_t packet_time_us);
bool ScheduleRefresh(uint32_t lifetime);
void SendRequest(StunRequest* request, int delay);
int Send(const void* data, size_t size, const rtc::PacketOptions& options);
void UpdateHash();
bool UpdateNonce(StunMessage* response);
void ResetNonce();
bool HasPermission(const rtc::IPAddress& ipaddr) const;
TurnEntry* FindEntry(const rtc::SocketAddress& address) const;
TurnEntry* FindEntry(int channel_id) const;
// Marks the connection with remote address `address` failed and
// pruned (a.k.a. write-timed-out). Returns true if a connection is found.
bool FailAndPruneConnection(const rtc::SocketAddress& address);
void MaybeAddTurnLoggingId(StunMessage* message);
void TurnCustomizerMaybeModifyOutgoingStunMessage(StunMessage* message);
bool TurnCustomizerAllowChannelData(const void* data,
size_t size,
bool payload);
ProtocolAddress server_address_;
// Reconstruct the URL of the server which the candidate is gathered from.
// A copy needs to be stored as server_address_ will resolve and clear its
// hostname field.
std::string ReconstructServerUrl();
std::string server_url_;
TlsCertPolicy tls_cert_policy_ = TlsCertPolicy::TLS_CERT_POLICY_SECURE;
std::vector<std::string> tls_alpn_protocols_;
std::vector<std::string> tls_elliptic_curves_;
rtc::SSLCertificateVerifier* tls_cert_verifier_;
RelayCredentials credentials_;
AttemptedServerSet attempted_server_addresses_;
rtc::AsyncPacketSocket* socket_;
SocketOptionsMap socket_options_;
std::unique_ptr<webrtc::AsyncDnsResolverInterface> resolver_;
int error_;
rtc::DiffServCodePoint stun_dscp_value_;
StunRequestManager request_manager_;
std::string realm_; // From 401/438 response message.
std::string nonce_; // From 401/438 response message.
std::string hash_; // Digest of username:realm:password
int next_channel_number_;
std::vector<std::unique_ptr<TurnEntry>> entries_;
PortState state_;
// By default the value will be set to 0. This value will be used in
// calculating the candidate priority.
int server_priority_;
// The number of retries made due to allocate mismatch error.
size_t allocate_mismatch_retries_;
// Optional TurnCustomizer that can modify outgoing messages. Once set, this
// must outlive the TurnPort's lifetime.
webrtc::TurnCustomizer* turn_customizer_ = nullptr;
// Optional TurnLoggingId.
// An identifier set by application that is added to TURN_ALLOCATE_REQUEST
// and can be used to match client/backend logs.
// TODO(jonaso): This should really be initialized in constructor,
// but that is currently so terrible. Fix once constructor is changed
// to be more easy to work with.
std::string turn_logging_id_;
webrtc::ScopedTaskSafety task_safety_;
CallbacksForTest* callbacks_for_test_ = nullptr;
friend class TurnEntry;
friend class TurnAllocateRequest;
friend class TurnRefreshRequest;
friend class TurnCreatePermissionRequest;
friend class TurnChannelBindRequest;
};
} // namespace cricket
#endif // P2P_BASE_TURN_PORT_H_

View file

@ -0,0 +1,879 @@
/*
* 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_server.h"
#include <algorithm>
#include <memory>
#include <tuple> // for std::tie
#include <utility>
#include "absl/algorithm/container.h"
#include "absl/memory/memory.h"
#include "absl/strings/string_view.h"
#include "api/array_view.h"
#include "api/packet_socket_factory.h"
#include "api/task_queue/task_queue_base.h"
#include "api/transport/stun.h"
#include "p2p/base/async_stun_tcp_socket.h"
#include "rtc_base/byte_buffer.h"
#include "rtc_base/checks.h"
#include "rtc_base/helpers.h"
#include "rtc_base/logging.h"
#include "rtc_base/message_digest.h"
#include "rtc_base/socket_adapters.h"
#include "rtc_base/strings/string_builder.h"
namespace cricket {
namespace {
using ::webrtc::TimeDelta;
// TODO(juberti): Move this all to a future turnmessage.h
// static const int IPPROTO_UDP = 17;
constexpr TimeDelta kNonceTimeout = TimeDelta::Minutes(60);
constexpr TimeDelta kDefaultAllocationTimeout = TimeDelta::Minutes(10);
constexpr TimeDelta kPermissionTimeout = TimeDelta::Minutes(5);
constexpr TimeDelta kChannelTimeout = TimeDelta::Minutes(10);
constexpr int kMinChannelNumber = 0x4000;
constexpr int kMaxChannelNumber = 0x7FFF;
constexpr size_t kNonceKeySize = 16;
constexpr size_t kNonceSize = 48;
constexpr size_t TURN_CHANNEL_HEADER_SIZE = 4U;
// TODO(mallinath) - Move these to a common place.
bool IsTurnChannelData(uint16_t msg_type) {
// The first two bits of a channel data message are 0b01.
return ((msg_type & 0xC000) == 0x4000);
}
} // namespace
int GetStunSuccessResponseTypeOrZero(const StunMessage& req) {
const int resp_type = GetStunSuccessResponseType(req.type());
return resp_type == -1 ? 0 : resp_type;
}
int GetStunErrorResponseTypeOrZero(const StunMessage& req) {
const int resp_type = GetStunErrorResponseType(req.type());
return resp_type == -1 ? 0 : resp_type;
}
static void InitErrorResponse(int code,
absl::string_view reason,
StunMessage* resp) {
resp->AddAttribute(std::make_unique<cricket::StunErrorCodeAttribute>(
STUN_ATTR_ERROR_CODE, code, std::string(reason)));
}
TurnServer::TurnServer(webrtc::TaskQueueBase* thread)
: thread_(thread),
nonce_key_(rtc::CreateRandomString(kNonceKeySize)),
auth_hook_(NULL),
redirect_hook_(NULL),
enable_otu_nonce_(false) {}
TurnServer::~TurnServer() {
RTC_DCHECK_RUN_ON(thread_);
for (InternalSocketMap::iterator it = server_sockets_.begin();
it != server_sockets_.end(); ++it) {
rtc::AsyncPacketSocket* socket = it->first;
delete socket;
}
for (ServerSocketMap::iterator it = server_listen_sockets_.begin();
it != server_listen_sockets_.end(); ++it) {
rtc::Socket* socket = it->first;
delete socket;
}
}
void TurnServer::AddInternalSocket(rtc::AsyncPacketSocket* socket,
ProtocolType proto) {
RTC_DCHECK_RUN_ON(thread_);
RTC_DCHECK(server_sockets_.end() == server_sockets_.find(socket));
server_sockets_[socket] = proto;
socket->RegisterReceivedPacketCallback(
[&](rtc::AsyncPacketSocket* socket, const rtc::ReceivedPacket& packet) {
RTC_DCHECK_RUN_ON(thread_);
OnInternalPacket(socket, packet);
});
}
void TurnServer::AddInternalServerSocket(
rtc::Socket* socket,
ProtocolType proto,
std::unique_ptr<rtc::SSLAdapterFactory> ssl_adapter_factory) {
RTC_DCHECK_RUN_ON(thread_);
RTC_DCHECK(server_listen_sockets_.end() ==
server_listen_sockets_.find(socket));
server_listen_sockets_[socket] = {proto, std::move(ssl_adapter_factory)};
socket->SignalReadEvent.connect(this, &TurnServer::OnNewInternalConnection);
}
void TurnServer::SetExternalSocketFactory(
rtc::PacketSocketFactory* factory,
const rtc::SocketAddress& external_addr) {
RTC_DCHECK_RUN_ON(thread_);
external_socket_factory_.reset(factory);
external_addr_ = external_addr;
}
void TurnServer::OnNewInternalConnection(rtc::Socket* socket) {
RTC_DCHECK_RUN_ON(thread_);
RTC_DCHECK(server_listen_sockets_.find(socket) !=
server_listen_sockets_.end());
AcceptConnection(socket);
}
void TurnServer::AcceptConnection(rtc::Socket* server_socket) {
// Check if someone is trying to connect to us.
rtc::SocketAddress accept_addr;
rtc::Socket* accepted_socket = server_socket->Accept(&accept_addr);
if (accepted_socket != NULL) {
const ServerSocketInfo& info = server_listen_sockets_[server_socket];
if (info.ssl_adapter_factory) {
rtc::SSLAdapter* ssl_adapter =
info.ssl_adapter_factory->CreateAdapter(accepted_socket);
ssl_adapter->StartSSL("");
accepted_socket = ssl_adapter;
}
cricket::AsyncStunTCPSocket* tcp_socket =
new cricket::AsyncStunTCPSocket(accepted_socket);
tcp_socket->SubscribeCloseEvent(this,
[this](rtc::AsyncPacketSocket* s, int err) {
OnInternalSocketClose(s, err);
});
// Finally add the socket so it can start communicating with the client.
AddInternalSocket(tcp_socket, info.proto);
}
}
void TurnServer::OnInternalSocketClose(rtc::AsyncPacketSocket* socket,
int err) {
RTC_DCHECK_RUN_ON(thread_);
DestroyInternalSocket(socket);
}
void TurnServer::OnInternalPacket(rtc::AsyncPacketSocket* socket,
const rtc::ReceivedPacket& packet) {
RTC_DCHECK_RUN_ON(thread_);
// Fail if the packet is too small to even contain a channel header.
if (packet.payload().size() < TURN_CHANNEL_HEADER_SIZE) {
return;
}
InternalSocketMap::iterator iter = server_sockets_.find(socket);
RTC_DCHECK(iter != server_sockets_.end());
TurnServerConnection conn(packet.source_address(), iter->second, socket);
uint16_t msg_type = rtc::GetBE16(packet.payload().data());
if (!IsTurnChannelData(msg_type)) {
// This is a STUN message.
HandleStunMessage(&conn, packet.payload());
} else {
// This is a channel message; let the allocation handle it.
TurnServerAllocation* allocation = FindAllocation(&conn);
if (allocation) {
allocation->HandleChannelData(packet.payload());
}
if (stun_message_observer_ != nullptr) {
stun_message_observer_->ReceivedChannelData(packet.payload());
}
}
}
void TurnServer::HandleStunMessage(TurnServerConnection* conn,
rtc::ArrayView<const uint8_t> payload) {
TurnMessage msg;
rtc::ByteBufferReader buf(payload);
if (!msg.Read(&buf) || (buf.Length() > 0)) {
RTC_LOG(LS_WARNING) << "Received invalid STUN message";
return;
}
if (stun_message_observer_ != nullptr) {
stun_message_observer_->ReceivedMessage(&msg);
}
// If it's a STUN binding request, handle that specially.
if (msg.type() == STUN_BINDING_REQUEST) {
HandleBindingRequest(conn, &msg);
return;
}
if (redirect_hook_ != NULL && msg.type() == STUN_ALLOCATE_REQUEST) {
rtc::SocketAddress address;
if (redirect_hook_->ShouldRedirect(conn->src(), &address)) {
SendErrorResponseWithAlternateServer(conn, &msg, address);
return;
}
}
// Look up the key that we'll use to validate the M-I. If we have an
// existing allocation, the key will already be cached.
TurnServerAllocation* allocation = FindAllocation(conn);
std::string key;
if (!allocation) {
GetKey(&msg, &key);
} else {
key = allocation->key();
}
// Ensure the message is authorized; only needed for requests.
if (IsStunRequestType(msg.type())) {
if (!CheckAuthorization(conn, &msg, key)) {
return;
}
}
if (!allocation && msg.type() == STUN_ALLOCATE_REQUEST) {
HandleAllocateRequest(conn, &msg, key);
} else if (allocation &&
(msg.type() != STUN_ALLOCATE_REQUEST ||
msg.transaction_id() == allocation->transaction_id())) {
// This is a non-allocate request, or a retransmit of an allocate.
// Check that the username matches the previous username used.
if (IsStunRequestType(msg.type()) &&
msg.GetByteString(STUN_ATTR_USERNAME)->string_view() !=
allocation->username()) {
SendErrorResponse(conn, &msg, STUN_ERROR_WRONG_CREDENTIALS,
STUN_ERROR_REASON_WRONG_CREDENTIALS);
return;
}
allocation->HandleTurnMessage(&msg);
} else {
// Allocation mismatch.
SendErrorResponse(conn, &msg, STUN_ERROR_ALLOCATION_MISMATCH,
STUN_ERROR_REASON_ALLOCATION_MISMATCH);
}
}
bool TurnServer::GetKey(const StunMessage* msg, std::string* key) {
const StunByteStringAttribute* username_attr =
msg->GetByteString(STUN_ATTR_USERNAME);
if (!username_attr) {
return false;
}
return (auth_hook_ != NULL &&
auth_hook_->GetKey(std::string(username_attr->string_view()), realm_,
key));
}
bool TurnServer::CheckAuthorization(TurnServerConnection* conn,
StunMessage* msg,
absl::string_view key) {
// RFC 5389, 10.2.2.
RTC_DCHECK(IsStunRequestType(msg->type()));
const StunByteStringAttribute* mi_attr =
msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY);
const StunByteStringAttribute* username_attr =
msg->GetByteString(STUN_ATTR_USERNAME);
const StunByteStringAttribute* realm_attr =
msg->GetByteString(STUN_ATTR_REALM);
const StunByteStringAttribute* nonce_attr =
msg->GetByteString(STUN_ATTR_NONCE);
// Fail if no MESSAGE_INTEGRITY.
if (!mi_attr) {
SendErrorResponseWithRealmAndNonce(conn, msg, STUN_ERROR_UNAUTHORIZED,
STUN_ERROR_REASON_UNAUTHORIZED);
return false;
}
// Fail if there is MESSAGE_INTEGRITY but no username, nonce, or realm.
if (!username_attr || !realm_attr || !nonce_attr) {
SendErrorResponse(conn, msg, STUN_ERROR_BAD_REQUEST,
STUN_ERROR_REASON_BAD_REQUEST);
return false;
}
// Fail if bad nonce.
if (!ValidateNonce(nonce_attr->string_view())) {
SendErrorResponseWithRealmAndNonce(conn, msg, STUN_ERROR_STALE_NONCE,
STUN_ERROR_REASON_STALE_NONCE);
return false;
}
// Fail if bad MESSAGE_INTEGRITY.
if (key.empty() || msg->ValidateMessageIntegrity(std::string(key)) !=
StunMessage::IntegrityStatus::kIntegrityOk) {
SendErrorResponseWithRealmAndNonce(conn, msg, STUN_ERROR_UNAUTHORIZED,
STUN_ERROR_REASON_UNAUTHORIZED);
return false;
}
// Fail if one-time-use nonce feature is enabled.
TurnServerAllocation* allocation = FindAllocation(conn);
if (enable_otu_nonce_ && allocation &&
allocation->last_nonce() == nonce_attr->string_view()) {
SendErrorResponseWithRealmAndNonce(conn, msg, STUN_ERROR_STALE_NONCE,
STUN_ERROR_REASON_STALE_NONCE);
return false;
}
if (allocation) {
allocation->set_last_nonce(nonce_attr->string_view());
}
// Success.
return true;
}
void TurnServer::HandleBindingRequest(TurnServerConnection* conn,
const StunMessage* req) {
StunMessage response(GetStunSuccessResponseTypeOrZero(*req),
req->transaction_id());
// Tell the user the address that we received their request from.
auto mapped_addr_attr = std::make_unique<StunXorAddressAttribute>(
STUN_ATTR_XOR_MAPPED_ADDRESS, conn->src());
response.AddAttribute(std::move(mapped_addr_attr));
SendStun(conn, &response);
}
void TurnServer::HandleAllocateRequest(TurnServerConnection* conn,
const TurnMessage* msg,
absl::string_view key) {
// Check the parameters in the request.
const StunUInt32Attribute* transport_attr =
msg->GetUInt32(STUN_ATTR_REQUESTED_TRANSPORT);
if (!transport_attr) {
SendErrorResponse(conn, msg, STUN_ERROR_BAD_REQUEST,
STUN_ERROR_REASON_BAD_REQUEST);
return;
}
// Only UDP is supported right now.
int proto = transport_attr->value() >> 24;
if (proto != IPPROTO_UDP) {
SendErrorResponse(conn, msg, STUN_ERROR_UNSUPPORTED_PROTOCOL,
STUN_ERROR_REASON_UNSUPPORTED_PROTOCOL);
return;
}
// Create the allocation and let it send the success response.
// If the actual socket allocation fails, send an internal error.
TurnServerAllocation* alloc = CreateAllocation(conn, proto, key);
if (alloc) {
alloc->HandleTurnMessage(msg);
} else {
SendErrorResponse(conn, msg, STUN_ERROR_SERVER_ERROR,
"Failed to allocate socket");
}
}
std::string TurnServer::GenerateNonce(int64_t now) const {
// Generate a nonce of the form hex(now + HMAC-MD5(nonce_key_, now))
std::string input(reinterpret_cast<const char*>(&now), sizeof(now));
std::string nonce = rtc::hex_encode(input);
nonce += rtc::ComputeHmac(rtc::DIGEST_MD5, nonce_key_, input);
RTC_DCHECK(nonce.size() == kNonceSize);
return nonce;
}
bool TurnServer::ValidateNonce(absl::string_view nonce) const {
// Check the size.
if (nonce.size() != kNonceSize) {
return false;
}
// Decode the timestamp.
int64_t then;
char* p = reinterpret_cast<char*>(&then);
size_t len = rtc::hex_decode(rtc::ArrayView<char>(p, sizeof(then)),
nonce.substr(0, sizeof(then) * 2));
if (len != sizeof(then)) {
return false;
}
// Verify the HMAC.
if (nonce.substr(sizeof(then) * 2) !=
rtc::ComputeHmac(rtc::DIGEST_MD5, nonce_key_,
std::string(p, sizeof(then)))) {
return false;
}
// Validate the timestamp.
return TimeDelta::Millis(rtc::TimeMillis() - then) < kNonceTimeout;
}
TurnServerAllocation* TurnServer::FindAllocation(TurnServerConnection* conn) {
AllocationMap::const_iterator it = allocations_.find(*conn);
return (it != allocations_.end()) ? it->second.get() : nullptr;
}
TurnServerAllocation* TurnServer::CreateAllocation(TurnServerConnection* conn,
int proto,
absl::string_view key) {
rtc::AsyncPacketSocket* external_socket =
(external_socket_factory_)
? external_socket_factory_->CreateUdpSocket(external_addr_, 0, 0)
: NULL;
if (!external_socket) {
return NULL;
}
// The Allocation takes ownership of the socket.
TurnServerAllocation* allocation =
new TurnServerAllocation(this, thread_, *conn, external_socket, key);
allocations_[*conn].reset(allocation);
return allocation;
}
void TurnServer::SendErrorResponse(TurnServerConnection* conn,
const StunMessage* req,
int code,
absl::string_view reason) {
RTC_DCHECK_RUN_ON(thread_);
TurnMessage resp(GetStunErrorResponseTypeOrZero(*req), req->transaction_id());
InitErrorResponse(code, reason, &resp);
RTC_LOG(LS_INFO) << "Sending error response, type=" << resp.type()
<< ", code=" << code << ", reason=" << reason;
SendStun(conn, &resp);
}
void TurnServer::SendErrorResponseWithRealmAndNonce(TurnServerConnection* conn,
const StunMessage* msg,
int code,
absl::string_view reason) {
TurnMessage resp(GetStunErrorResponseTypeOrZero(*msg), msg->transaction_id());
InitErrorResponse(code, reason, &resp);
int64_t timestamp = rtc::TimeMillis();
if (ts_for_next_nonce_) {
timestamp = ts_for_next_nonce_;
ts_for_next_nonce_ = 0;
}
resp.AddAttribute(std::make_unique<StunByteStringAttribute>(
STUN_ATTR_NONCE, GenerateNonce(timestamp)));
resp.AddAttribute(
std::make_unique<StunByteStringAttribute>(STUN_ATTR_REALM, realm_));
SendStun(conn, &resp);
}
void TurnServer::SendErrorResponseWithAlternateServer(
TurnServerConnection* conn,
const StunMessage* msg,
const rtc::SocketAddress& addr) {
TurnMessage resp(GetStunErrorResponseTypeOrZero(*msg), msg->transaction_id());
InitErrorResponse(STUN_ERROR_TRY_ALTERNATE,
STUN_ERROR_REASON_TRY_ALTERNATE_SERVER, &resp);
resp.AddAttribute(
std::make_unique<StunAddressAttribute>(STUN_ATTR_ALTERNATE_SERVER, addr));
SendStun(conn, &resp);
}
void TurnServer::SendStun(TurnServerConnection* conn, StunMessage* msg) {
RTC_DCHECK_RUN_ON(thread_);
rtc::ByteBufferWriter buf;
// Add a SOFTWARE attribute if one is set.
if (!software_.empty()) {
msg->AddAttribute(std::make_unique<StunByteStringAttribute>(
STUN_ATTR_SOFTWARE, software_));
}
msg->Write(&buf);
Send(conn, buf);
}
void TurnServer::Send(TurnServerConnection* conn,
const rtc::ByteBufferWriter& buf) {
RTC_DCHECK_RUN_ON(thread_);
rtc::PacketOptions options;
conn->socket()->SendTo(buf.Data(), buf.Length(), conn->src(), options);
}
void TurnServer::DestroyAllocation(TurnServerAllocation* allocation) {
// Removing the internal socket if the connection is not udp.
rtc::AsyncPacketSocket* socket = allocation->conn()->socket();
InternalSocketMap::iterator iter = server_sockets_.find(socket);
// Skip if the socket serving this allocation is UDP, as this will be shared
// by all allocations.
// Note: We may not find a socket if it's a TCP socket that was closed, and
// the allocation is only now timing out.
if (iter != server_sockets_.end() && iter->second != cricket::PROTO_UDP) {
DestroyInternalSocket(socket);
}
allocations_.erase(*(allocation->conn()));
}
void TurnServer::DestroyInternalSocket(rtc::AsyncPacketSocket* socket) {
InternalSocketMap::iterator iter = server_sockets_.find(socket);
if (iter != server_sockets_.end()) {
rtc::AsyncPacketSocket* socket = iter->first;
socket->UnsubscribeCloseEvent(this);
socket->DeregisterReceivedPacketCallback();
server_sockets_.erase(iter);
std::unique_ptr<rtc::AsyncPacketSocket> socket_to_delete =
absl::WrapUnique(socket);
// We must destroy the socket async to avoid invalidating the sigslot
// callback list iterator inside a sigslot callback. (In other words,
// deleting an object from within a callback from that object).
thread_->PostTask([socket_to_delete = std::move(socket_to_delete)] {});
}
}
TurnServerConnection::TurnServerConnection(const rtc::SocketAddress& src,
ProtocolType proto,
rtc::AsyncPacketSocket* socket)
: src_(src),
dst_(socket->GetRemoteAddress()),
proto_(proto),
socket_(socket) {}
bool TurnServerConnection::operator==(const TurnServerConnection& c) const {
return src_ == c.src_ && dst_ == c.dst_ && proto_ == c.proto_;
}
bool TurnServerConnection::operator<(const TurnServerConnection& c) const {
return std::tie(src_, dst_, proto_) < std::tie(c.src_, c.dst_, c.proto_);
}
std::string TurnServerConnection::ToString() const {
const char* const kProtos[] = {"unknown", "udp", "tcp", "ssltcp"};
rtc::StringBuilder ost;
ost << src_.ToSensitiveString() << "-" << dst_.ToSensitiveString() << ":"
<< kProtos[proto_];
return ost.Release();
}
TurnServerAllocation::TurnServerAllocation(TurnServer* server,
webrtc::TaskQueueBase* thread,
const TurnServerConnection& conn,
rtc::AsyncPacketSocket* socket,
absl::string_view key)
: server_(server),
thread_(thread),
conn_(conn),
external_socket_(socket),
key_(key) {
external_socket_->RegisterReceivedPacketCallback(
[&](rtc::AsyncPacketSocket* socket, const rtc::ReceivedPacket& packet) {
RTC_DCHECK_RUN_ON(thread_);
OnExternalPacket(socket, packet);
});
}
TurnServerAllocation::~TurnServerAllocation() {
channels_.clear();
perms_.clear();
RTC_LOG(LS_INFO) << ToString() << ": Allocation destroyed";
}
std::string TurnServerAllocation::ToString() const {
rtc::StringBuilder ost;
ost << "Alloc[" << conn_.ToString() << "]";
return ost.Release();
}
void TurnServerAllocation::HandleTurnMessage(const TurnMessage* msg) {
RTC_DCHECK(msg != NULL);
switch (msg->type()) {
case STUN_ALLOCATE_REQUEST:
HandleAllocateRequest(msg);
break;
case TURN_REFRESH_REQUEST:
HandleRefreshRequest(msg);
break;
case TURN_SEND_INDICATION:
HandleSendIndication(msg);
break;
case TURN_CREATE_PERMISSION_REQUEST:
HandleCreatePermissionRequest(msg);
break;
case TURN_CHANNEL_BIND_REQUEST:
HandleChannelBindRequest(msg);
break;
default:
// Not sure what to do with this, just eat it.
RTC_LOG(LS_WARNING) << ToString()
<< ": Invalid TURN message type received: "
<< msg->type();
}
}
void TurnServerAllocation::HandleAllocateRequest(const TurnMessage* msg) {
// Copy the important info from the allocate request.
transaction_id_ = msg->transaction_id();
const StunByteStringAttribute* username_attr =
msg->GetByteString(STUN_ATTR_USERNAME);
RTC_DCHECK(username_attr != NULL);
username_ = std::string(username_attr->string_view());
// Figure out the lifetime and start the allocation timer.
TimeDelta lifetime = ComputeLifetime(*msg);
PostDeleteSelf(lifetime);
RTC_LOG(LS_INFO) << ToString() << ": Created allocation with lifetime="
<< lifetime.seconds();
// We've already validated all the important bits; just send a response here.
TurnMessage response(GetStunSuccessResponseTypeOrZero(*msg),
msg->transaction_id());
auto mapped_addr_attr = std::make_unique<StunXorAddressAttribute>(
STUN_ATTR_XOR_MAPPED_ADDRESS, conn_.src());
auto relayed_addr_attr = std::make_unique<StunXorAddressAttribute>(
STUN_ATTR_XOR_RELAYED_ADDRESS, external_socket_->GetLocalAddress());
auto lifetime_attr = std::make_unique<StunUInt32Attribute>(
STUN_ATTR_LIFETIME, lifetime.seconds());
response.AddAttribute(std::move(mapped_addr_attr));
response.AddAttribute(std::move(relayed_addr_attr));
response.AddAttribute(std::move(lifetime_attr));
SendResponse(&response);
}
void TurnServerAllocation::HandleRefreshRequest(const TurnMessage* msg) {
// Figure out the new lifetime.
TimeDelta lifetime = ComputeLifetime(*msg);
// Reset the expiration timer.
safety_.reset();
PostDeleteSelf(lifetime);
RTC_LOG(LS_INFO) << ToString()
<< ": Refreshed allocation, lifetime=" << lifetime.seconds();
// Send a success response with a LIFETIME attribute.
TurnMessage response(GetStunSuccessResponseTypeOrZero(*msg),
msg->transaction_id());
auto lifetime_attr = std::make_unique<StunUInt32Attribute>(
STUN_ATTR_LIFETIME, lifetime.seconds());
response.AddAttribute(std::move(lifetime_attr));
SendResponse(&response);
}
void TurnServerAllocation::HandleSendIndication(const TurnMessage* msg) {
// Check mandatory attributes.
const StunByteStringAttribute* data_attr = msg->GetByteString(STUN_ATTR_DATA);
const StunAddressAttribute* peer_attr =
msg->GetAddress(STUN_ATTR_XOR_PEER_ADDRESS);
if (!data_attr || !peer_attr) {
RTC_LOG(LS_WARNING) << ToString() << ": Received invalid send indication";
return;
}
// If a permission exists, send the data on to the peer.
if (HasPermission(peer_attr->GetAddress().ipaddr())) {
SendExternal(reinterpret_cast<char*>(data_attr->array_view().data()),
data_attr->length(), peer_attr->GetAddress());
} else {
RTC_LOG(LS_WARNING) << ToString()
<< ": Received send indication without permission"
" peer="
<< peer_attr->GetAddress().ToSensitiveString();
}
}
void TurnServerAllocation::HandleCreatePermissionRequest(
const TurnMessage* msg) {
// Check mandatory attributes.
const StunAddressAttribute* peer_attr =
msg->GetAddress(STUN_ATTR_XOR_PEER_ADDRESS);
if (!peer_attr) {
SendBadRequestResponse(msg);
return;
}
if (server_->reject_private_addresses_ &&
rtc::IPIsPrivate(peer_attr->GetAddress().ipaddr())) {
SendErrorResponse(msg, STUN_ERROR_FORBIDDEN, STUN_ERROR_REASON_FORBIDDEN);
return;
}
// Add this permission.
AddPermission(peer_attr->GetAddress().ipaddr());
RTC_LOG(LS_INFO) << ToString() << ": Created permission, peer="
<< peer_attr->GetAddress().ToSensitiveString();
// Send a success response.
TurnMessage response(GetStunSuccessResponseTypeOrZero(*msg),
msg->transaction_id());
SendResponse(&response);
}
void TurnServerAllocation::HandleChannelBindRequest(const TurnMessage* msg) {
// Check mandatory attributes.
const StunUInt32Attribute* channel_attr =
msg->GetUInt32(STUN_ATTR_CHANNEL_NUMBER);
const StunAddressAttribute* peer_attr =
msg->GetAddress(STUN_ATTR_XOR_PEER_ADDRESS);
if (!channel_attr || !peer_attr) {
SendBadRequestResponse(msg);
return;
}
// Check that channel id is valid.
int channel_id = channel_attr->value() >> 16;
if (channel_id < kMinChannelNumber || channel_id > kMaxChannelNumber) {
SendBadRequestResponse(msg);
return;
}
// Check that this channel id isn't bound to another transport address, and
// that this transport address isn't bound to another channel id.
auto channel1 = FindChannel(channel_id);
auto channel2 = FindChannel(peer_attr->GetAddress());
if (channel1 != channel2) {
SendBadRequestResponse(msg);
return;
}
// Add or refresh this channel.
if (channel1 == channels_.end()) {
channel1 = channels_.insert(
channels_.end(), {.id = channel_id, .peer = peer_attr->GetAddress()});
} else {
channel1->pending_delete.reset();
}
thread_->PostDelayedTask(
SafeTask(channel1->pending_delete.flag(),
[this, channel1] { channels_.erase(channel1); }),
kChannelTimeout);
// Channel binds also refresh permissions.
AddPermission(peer_attr->GetAddress().ipaddr());
RTC_LOG(LS_INFO) << ToString() << ": Bound channel, id=" << channel_id
<< ", peer=" << peer_attr->GetAddress().ToSensitiveString();
// Send a success response.
TurnMessage response(GetStunSuccessResponseTypeOrZero(*msg),
msg->transaction_id());
SendResponse(&response);
}
void TurnServerAllocation::HandleChannelData(
rtc::ArrayView<const uint8_t> payload) {
// Extract the channel number from the data.
uint16_t channel_id = rtc::GetBE16(payload.data());
auto channel = FindChannel(channel_id);
if (channel != channels_.end()) {
// Send the data to the peer address.
SendExternal(payload.data() + TURN_CHANNEL_HEADER_SIZE,
payload.size() - TURN_CHANNEL_HEADER_SIZE, channel->peer);
} else {
RTC_LOG(LS_WARNING) << ToString()
<< ": Received channel data for invalid channel, id="
<< channel_id;
}
}
void TurnServerAllocation::OnExternalPacket(rtc::AsyncPacketSocket* socket,
const rtc::ReceivedPacket& packet) {
RTC_DCHECK(external_socket_.get() == socket);
auto channel = FindChannel(packet.source_address());
if (channel != channels_.end()) {
// There is a channel bound to this address. Send as a channel message.
rtc::ByteBufferWriter buf;
buf.WriteUInt16(channel->id);
buf.WriteUInt16(static_cast<uint16_t>(packet.payload().size()));
buf.WriteBytes(packet.payload().data(), packet.payload().size());
server_->Send(&conn_, buf);
} else if (!server_->enable_permission_checks_ ||
HasPermission(packet.source_address().ipaddr())) {
// No channel, but a permission exists. Send as a data indication.
TurnMessage msg(TURN_DATA_INDICATION);
msg.AddAttribute(std::make_unique<StunXorAddressAttribute>(
STUN_ATTR_XOR_PEER_ADDRESS, packet.source_address()));
msg.AddAttribute(std::make_unique<StunByteStringAttribute>(
STUN_ATTR_DATA, packet.payload().data(), packet.payload().size()));
server_->SendStun(&conn_, &msg);
} else {
RTC_LOG(LS_WARNING)
<< ToString() << ": Received external packet without permission, peer="
<< packet.source_address().ToSensitiveString();
}
}
TimeDelta TurnServerAllocation::ComputeLifetime(const TurnMessage& msg) {
if (const StunUInt32Attribute* attr = msg.GetUInt32(STUN_ATTR_LIFETIME)) {
return std::min(TimeDelta::Seconds(static_cast<int>(attr->value())),
kDefaultAllocationTimeout);
}
return kDefaultAllocationTimeout;
}
bool TurnServerAllocation::HasPermission(const rtc::IPAddress& addr) {
return FindPermission(addr) != perms_.end();
}
void TurnServerAllocation::AddPermission(const rtc::IPAddress& addr) {
auto perm = FindPermission(addr);
if (perm == perms_.end()) {
perm = perms_.insert(perms_.end(), {.peer = addr});
} else {
perm->pending_delete.reset();
}
thread_->PostDelayedTask(SafeTask(perm->pending_delete.flag(),
[this, perm] { perms_.erase(perm); }),
kPermissionTimeout);
}
TurnServerAllocation::PermissionList::iterator
TurnServerAllocation::FindPermission(const rtc::IPAddress& addr) {
return absl::c_find_if(perms_,
[&](const Permission& p) { return p.peer == addr; });
}
TurnServerAllocation::ChannelList::iterator TurnServerAllocation::FindChannel(
int channel_id) {
return absl::c_find_if(channels_,
[&](const Channel& c) { return c.id == channel_id; });
}
TurnServerAllocation::ChannelList::iterator TurnServerAllocation::FindChannel(
const rtc::SocketAddress& addr) {
return absl::c_find_if(channels_,
[&](const Channel& c) { return c.peer == addr; });
}
void TurnServerAllocation::SendResponse(TurnMessage* msg) {
// Success responses always have M-I.
msg->AddMessageIntegrity(key_);
server_->SendStun(&conn_, msg);
}
void TurnServerAllocation::SendBadRequestResponse(const TurnMessage* req) {
SendErrorResponse(req, STUN_ERROR_BAD_REQUEST, STUN_ERROR_REASON_BAD_REQUEST);
}
void TurnServerAllocation::SendErrorResponse(const TurnMessage* req,
int code,
absl::string_view reason) {
server_->SendErrorResponse(&conn_, req, code, reason);
}
void TurnServerAllocation::SendExternal(const void* data,
size_t size,
const rtc::SocketAddress& peer) {
rtc::PacketOptions options;
external_socket_->SendTo(data, size, peer, options);
}
void TurnServerAllocation::PostDeleteSelf(TimeDelta delay) {
auto delete_self = [this] {
RTC_DCHECK_RUN_ON(server_->thread_);
server_->DestroyAllocation(this);
};
thread_->PostDelayedTask(SafeTask(safety_.flag(), std::move(delete_self)),
delay);
}
} // namespace cricket

View file

@ -0,0 +1,366 @@
/*
* 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.
*/
#ifndef P2P_BASE_TURN_SERVER_H_
#define P2P_BASE_TURN_SERVER_H_
#include <list>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/string_view.h"
#include "api/sequence_checker.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "api/task_queue/task_queue_base.h"
#include "api/units/time_delta.h"
#include "p2p/base/port_interface.h"
#include "rtc_base/async_packet_socket.h"
#include "rtc_base/network/received_packet.h"
#include "rtc_base/socket_address.h"
#include "rtc_base/ssl_adapter.h"
#include "rtc_base/third_party/sigslot/sigslot.h"
namespace rtc {
class ByteBufferWriter;
class PacketSocketFactory;
} // namespace rtc
namespace cricket {
class StunMessage;
class TurnMessage;
class TurnServer;
// The default server port for TURN, as specified in RFC5766.
const int TURN_SERVER_PORT = 3478;
// Encapsulates the client's connection to the server.
class TurnServerConnection {
public:
TurnServerConnection() : proto_(PROTO_UDP), socket_(NULL) {}
TurnServerConnection(const rtc::SocketAddress& src,
ProtocolType proto,
rtc::AsyncPacketSocket* socket);
const rtc::SocketAddress& src() const { return src_; }
rtc::AsyncPacketSocket* socket() { return socket_; }
bool operator==(const TurnServerConnection& t) const;
bool operator<(const TurnServerConnection& t) const;
std::string ToString() const;
private:
rtc::SocketAddress src_;
rtc::SocketAddress dst_;
cricket::ProtocolType proto_;
rtc::AsyncPacketSocket* socket_;
};
// Encapsulates a TURN allocation.
// The object is created when an allocation request is received, and then
// handles TURN messages (via HandleTurnMessage) and channel data messages
// (via HandleChannelData) for this allocation when received by the server.
// The object informs the server when its lifetime timer expires.
class TurnServerAllocation {
public:
TurnServerAllocation(TurnServer* server_,
webrtc::TaskQueueBase* thread,
const TurnServerConnection& conn,
rtc::AsyncPacketSocket* server_socket,
absl::string_view key);
virtual ~TurnServerAllocation();
TurnServerConnection* conn() { return &conn_; }
const std::string& key() const { return key_; }
const std::string& transaction_id() const { return transaction_id_; }
const std::string& username() const { return username_; }
const std::string& last_nonce() const { return last_nonce_; }
void set_last_nonce(absl::string_view nonce) {
last_nonce_ = std::string(nonce);
}
std::string ToString() const;
void HandleTurnMessage(const TurnMessage* msg);
void HandleChannelData(rtc::ArrayView<const uint8_t> payload);
private:
struct Channel {
webrtc::ScopedTaskSafety pending_delete;
int id;
rtc::SocketAddress peer;
};
struct Permission {
webrtc::ScopedTaskSafety pending_delete;
rtc::IPAddress peer;
};
using PermissionList = std::list<Permission>;
using ChannelList = std::list<Channel>;
void PostDeleteSelf(webrtc::TimeDelta delay);
void HandleAllocateRequest(const TurnMessage* msg);
void HandleRefreshRequest(const TurnMessage* msg);
void HandleSendIndication(const TurnMessage* msg);
void HandleCreatePermissionRequest(const TurnMessage* msg);
void HandleChannelBindRequest(const TurnMessage* msg);
void OnExternalPacket(rtc::AsyncPacketSocket* socket,
const rtc::ReceivedPacket& packet);
static webrtc::TimeDelta ComputeLifetime(const TurnMessage& msg);
bool HasPermission(const rtc::IPAddress& addr);
void AddPermission(const rtc::IPAddress& addr);
PermissionList::iterator FindPermission(const rtc::IPAddress& addr);
ChannelList::iterator FindChannel(int channel_id);
ChannelList::iterator FindChannel(const rtc::SocketAddress& addr);
void SendResponse(TurnMessage* msg);
void SendBadRequestResponse(const TurnMessage* req);
void SendErrorResponse(const TurnMessage* req,
int code,
absl::string_view reason);
void SendExternal(const void* data,
size_t size,
const rtc::SocketAddress& peer);
TurnServer* const server_;
webrtc::TaskQueueBase* const thread_;
TurnServerConnection conn_;
std::unique_ptr<rtc::AsyncPacketSocket> external_socket_;
std::string key_;
std::string transaction_id_;
std::string username_;
std::string last_nonce_;
PermissionList perms_;
ChannelList channels_;
webrtc::ScopedTaskSafety safety_;
};
// An interface through which the MD5 credential hash can be retrieved.
class TurnAuthInterface {
public:
// Gets HA1 for the specified user and realm.
// HA1 = MD5(A1) = MD5(username:realm:password).
// Return true if the given username and realm are valid, or false if not.
virtual bool GetKey(absl::string_view username,
absl::string_view realm,
std::string* key) = 0;
virtual ~TurnAuthInterface() = default;
};
// An interface enables Turn Server to control redirection behavior.
class TurnRedirectInterface {
public:
virtual bool ShouldRedirect(const rtc::SocketAddress& address,
rtc::SocketAddress* out) = 0;
virtual ~TurnRedirectInterface() {}
};
class StunMessageObserver {
public:
virtual void ReceivedMessage(const TurnMessage* msg) = 0;
virtual void ReceivedChannelData(rtc::ArrayView<const uint8_t> payload) = 0;
virtual ~StunMessageObserver() {}
};
// The core TURN server class. Give it a socket to listen on via
// AddInternalServerSocket, and a factory to create external sockets via
// SetExternalSocketFactory, and it's ready to go.
// Not yet wired up: TCP support.
class TurnServer : public sigslot::has_slots<> {
public:
typedef std::map<TurnServerConnection, std::unique_ptr<TurnServerAllocation>>
AllocationMap;
explicit TurnServer(webrtc::TaskQueueBase* thread);
~TurnServer() override;
// Gets/sets the realm value to use for the server.
const std::string& realm() const {
RTC_DCHECK_RUN_ON(thread_);
return realm_;
}
void set_realm(absl::string_view realm) {
RTC_DCHECK_RUN_ON(thread_);
realm_ = std::string(realm);
}
// Gets/sets the value for the SOFTWARE attribute for TURN messages.
const std::string& software() const {
RTC_DCHECK_RUN_ON(thread_);
return software_;
}
void set_software(absl::string_view software) {
RTC_DCHECK_RUN_ON(thread_);
software_ = std::string(software);
}
const AllocationMap& allocations() const {
RTC_DCHECK_RUN_ON(thread_);
return allocations_;
}
// Sets the authentication callback; does not take ownership.
void set_auth_hook(TurnAuthInterface* auth_hook) {
RTC_DCHECK_RUN_ON(thread_);
auth_hook_ = auth_hook;
}
void set_redirect_hook(TurnRedirectInterface* redirect_hook) {
RTC_DCHECK_RUN_ON(thread_);
redirect_hook_ = redirect_hook;
}
void set_enable_otu_nonce(bool enable) {
RTC_DCHECK_RUN_ON(thread_);
enable_otu_nonce_ = enable;
}
// If set to true, reject CreatePermission requests to RFC1918 addresses.
void set_reject_private_addresses(bool filter) {
RTC_DCHECK_RUN_ON(thread_);
reject_private_addresses_ = filter;
}
void set_enable_permission_checks(bool enable) {
RTC_DCHECK_RUN_ON(thread_);
enable_permission_checks_ = enable;
}
// Starts listening for packets from internal clients.
void AddInternalSocket(rtc::AsyncPacketSocket* socket, ProtocolType proto);
// Starts listening for the connections on this socket. When someone tries
// to connect, the connection will be accepted and a new internal socket
// will be added.
void AddInternalServerSocket(
rtc::Socket* socket,
ProtocolType proto,
std::unique_ptr<rtc::SSLAdapterFactory> ssl_adapter_factory = nullptr);
// Specifies the factory to use for creating external sockets.
void SetExternalSocketFactory(rtc::PacketSocketFactory* factory,
const rtc::SocketAddress& address);
// For testing only.
std::string SetTimestampForNextNonce(int64_t timestamp) {
RTC_DCHECK_RUN_ON(thread_);
ts_for_next_nonce_ = timestamp;
return GenerateNonce(timestamp);
}
void SetStunMessageObserver(std::unique_ptr<StunMessageObserver> observer) {
RTC_DCHECK_RUN_ON(thread_);
stun_message_observer_ = std::move(observer);
}
private:
// All private member functions and variables should have access restricted to
// thread_. But compile-time annotations are missing for members access from
// TurnServerAllocation (via friend declaration).
std::string GenerateNonce(int64_t now) const RTC_RUN_ON(thread_);
void OnInternalPacket(rtc::AsyncPacketSocket* socket,
const rtc::ReceivedPacket& packet) RTC_RUN_ON(thread_);
void OnNewInternalConnection(rtc::Socket* socket);
// Accept connections on this server socket.
void AcceptConnection(rtc::Socket* server_socket) RTC_RUN_ON(thread_);
void OnInternalSocketClose(rtc::AsyncPacketSocket* socket, int err);
void HandleStunMessage(TurnServerConnection* conn,
rtc::ArrayView<const uint8_t> payload)
RTC_RUN_ON(thread_);
void HandleBindingRequest(TurnServerConnection* conn, const StunMessage* msg)
RTC_RUN_ON(thread_);
void HandleAllocateRequest(TurnServerConnection* conn,
const TurnMessage* msg,
absl::string_view key) RTC_RUN_ON(thread_);
bool GetKey(const StunMessage* msg, std::string* key) RTC_RUN_ON(thread_);
bool CheckAuthorization(TurnServerConnection* conn,
StunMessage* msg,
absl::string_view key) RTC_RUN_ON(thread_);
bool ValidateNonce(absl::string_view nonce) const RTC_RUN_ON(thread_);
TurnServerAllocation* FindAllocation(TurnServerConnection* conn)
RTC_RUN_ON(thread_);
TurnServerAllocation* CreateAllocation(TurnServerConnection* conn,
int proto,
absl::string_view key)
RTC_RUN_ON(thread_);
void SendErrorResponse(TurnServerConnection* conn,
const StunMessage* req,
int code,
absl::string_view reason);
void SendErrorResponseWithRealmAndNonce(TurnServerConnection* conn,
const StunMessage* req,
int code,
absl::string_view reason)
RTC_RUN_ON(thread_);
void SendErrorResponseWithAlternateServer(TurnServerConnection* conn,
const StunMessage* req,
const rtc::SocketAddress& addr)
RTC_RUN_ON(thread_);
void SendStun(TurnServerConnection* conn, StunMessage* msg);
void Send(TurnServerConnection* conn, const rtc::ByteBufferWriter& buf);
void DestroyAllocation(TurnServerAllocation* allocation) RTC_RUN_ON(thread_);
void DestroyInternalSocket(rtc::AsyncPacketSocket* socket)
RTC_RUN_ON(thread_);
typedef std::map<rtc::AsyncPacketSocket*, ProtocolType> InternalSocketMap;
struct ServerSocketInfo {
ProtocolType proto;
// If non-null, used to wrap accepted sockets.
std::unique_ptr<rtc::SSLAdapterFactory> ssl_adapter_factory;
};
typedef std::map<rtc::Socket*, ServerSocketInfo> ServerSocketMap;
webrtc::TaskQueueBase* const thread_;
const std::string nonce_key_;
std::string realm_ RTC_GUARDED_BY(thread_);
std::string software_ RTC_GUARDED_BY(thread_);
TurnAuthInterface* auth_hook_ RTC_GUARDED_BY(thread_);
TurnRedirectInterface* redirect_hook_ RTC_GUARDED_BY(thread_);
// otu - one-time-use. Server will respond with 438 if it's
// sees the same nonce in next transaction.
bool enable_otu_nonce_ RTC_GUARDED_BY(thread_);
bool reject_private_addresses_ = false;
// Check for permission when receiving an external packet.
bool enable_permission_checks_ = true;
InternalSocketMap server_sockets_ RTC_GUARDED_BY(thread_);
ServerSocketMap server_listen_sockets_ RTC_GUARDED_BY(thread_);
std::unique_ptr<rtc::PacketSocketFactory> external_socket_factory_
RTC_GUARDED_BY(thread_);
rtc::SocketAddress external_addr_ RTC_GUARDED_BY(thread_);
AllocationMap allocations_ RTC_GUARDED_BY(thread_);
// For testing only. If this is non-zero, the next NONCE will be generated
// from this value, and it will be reset to 0 after generating the NONCE.
int64_t ts_for_next_nonce_ RTC_GUARDED_BY(thread_) = 0;
// For testing only. Used to observe STUN messages received.
std::unique_ptr<StunMessageObserver> stun_message_observer_
RTC_GUARDED_BY(thread_);
friend class TurnServerAllocation;
};
} // namespace cricket
#endif // P2P_BASE_TURN_SERVER_H_

View file

@ -0,0 +1,17 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef P2P_BASE_UDP_PORT_H_
#define P2P_BASE_UDP_PORT_H_
// StunPort will be handling UDPPort functionality.
#include "p2p/base/stun_port.h"
#endif // P2P_BASE_UDP_PORT_H_

View file

@ -0,0 +1,253 @@
/*
* Copyright 2022 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/wrapping_active_ice_controller.h"
#include <memory>
#include <utility>
#include <vector>
#include "api/sequence_checker.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "api/units/time_delta.h"
#include "p2p/base/basic_ice_controller.h"
#include "p2p/base/connection.h"
#include "p2p/base/ice_agent_interface.h"
#include "p2p/base/ice_controller_interface.h"
#include "p2p/base/ice_switch_reason.h"
#include "p2p/base/ice_transport_internal.h"
#include "p2p/base/transport_description.h"
#include "rtc_base/logging.h"
#include "rtc_base/thread.h"
#include "rtc_base/time_utils.h"
namespace {
using ::webrtc::SafeTask;
using ::webrtc::TimeDelta;
} // unnamed namespace
namespace cricket {
WrappingActiveIceController::WrappingActiveIceController(
IceAgentInterface* ice_agent,
std::unique_ptr<IceControllerInterface> wrapped)
: network_thread_(rtc::Thread::Current()),
wrapped_(std::move(wrapped)),
agent_(*ice_agent) {
RTC_DCHECK(ice_agent != nullptr);
}
WrappingActiveIceController::WrappingActiveIceController(
IceAgentInterface* ice_agent,
IceControllerFactoryInterface* wrapped_factory,
const IceControllerFactoryArgs& wrapped_factory_args)
: network_thread_(rtc::Thread::Current()), agent_(*ice_agent) {
RTC_DCHECK(ice_agent != nullptr);
if (wrapped_factory) {
wrapped_ = wrapped_factory->Create(wrapped_factory_args);
} else {
wrapped_ = std::make_unique<BasicIceController>(wrapped_factory_args);
}
}
WrappingActiveIceController::~WrappingActiveIceController() {}
void WrappingActiveIceController::SetIceConfig(const IceConfig& config) {
RTC_DCHECK_RUN_ON(network_thread_);
wrapped_->SetIceConfig(config);
}
bool WrappingActiveIceController::GetUseCandidateAttribute(
const Connection* connection,
NominationMode mode,
IceMode remote_ice_mode) const {
RTC_DCHECK_RUN_ON(network_thread_);
return wrapped_->GetUseCandidateAttr(connection, mode, remote_ice_mode);
}
void WrappingActiveIceController::OnConnectionAdded(
const Connection* connection) {
RTC_DCHECK_RUN_ON(network_thread_);
wrapped_->AddConnection(connection);
}
void WrappingActiveIceController::OnConnectionPinged(
const Connection* connection) {
RTC_DCHECK_RUN_ON(network_thread_);
wrapped_->MarkConnectionPinged(connection);
}
void WrappingActiveIceController::OnConnectionUpdated(
const Connection* connection) {
RTC_LOG(LS_VERBOSE) << "Connection report for " << connection->ToString();
// Do nothing. Native ICE controllers have direct access to Connection, so no
// need to update connection state separately.
}
void WrappingActiveIceController::OnConnectionSwitched(
const Connection* connection) {
RTC_DCHECK_RUN_ON(network_thread_);
selected_connection_ = connection;
wrapped_->SetSelectedConnection(connection);
}
void WrappingActiveIceController::OnConnectionDestroyed(
const Connection* connection) {
RTC_DCHECK_RUN_ON(network_thread_);
wrapped_->OnConnectionDestroyed(connection);
}
void WrappingActiveIceController::MaybeStartPinging() {
RTC_DCHECK_RUN_ON(network_thread_);
if (started_pinging_) {
return;
}
if (wrapped_->HasPingableConnection()) {
network_thread_->PostTask(
SafeTask(task_safety_.flag(), [this]() { SelectAndPingConnection(); }));
agent_.OnStartedPinging();
started_pinging_ = true;
}
}
void WrappingActiveIceController::SelectAndPingConnection() {
RTC_DCHECK_RUN_ON(network_thread_);
agent_.UpdateConnectionStates();
IceControllerInterface::PingResult result =
wrapped_->SelectConnectionToPing(agent_.GetLastPingSentMs());
HandlePingResult(result);
}
void WrappingActiveIceController::HandlePingResult(
IceControllerInterface::PingResult result) {
RTC_DCHECK_RUN_ON(network_thread_);
if (result.connection.has_value()) {
agent_.SendPingRequest(result.connection.value());
}
network_thread_->PostDelayedTask(
SafeTask(task_safety_.flag(), [this]() { SelectAndPingConnection(); }),
TimeDelta::Millis(result.recheck_delay_ms));
}
void WrappingActiveIceController::OnSortAndSwitchRequest(
IceSwitchReason reason) {
RTC_DCHECK_RUN_ON(network_thread_);
if (!sort_pending_) {
network_thread_->PostTask(SafeTask(task_safety_.flag(), [this, reason]() {
SortAndSwitchToBestConnection(reason);
}));
sort_pending_ = true;
}
}
void WrappingActiveIceController::OnImmediateSortAndSwitchRequest(
IceSwitchReason reason) {
RTC_DCHECK_RUN_ON(network_thread_);
SortAndSwitchToBestConnection(reason);
}
void WrappingActiveIceController::SortAndSwitchToBestConnection(
IceSwitchReason reason) {
RTC_DCHECK_RUN_ON(network_thread_);
// Make sure the connection states are up-to-date since this affects how they
// will be sorted.
agent_.UpdateConnectionStates();
// Any changes after this point will require a re-sort.
sort_pending_ = false;
IceControllerInterface::SwitchResult result =
wrapped_->SortAndSwitchConnection(reason);
HandleSwitchResult(reason, result);
UpdateStateOnConnectionsResorted();
}
bool WrappingActiveIceController::OnImmediateSwitchRequest(
IceSwitchReason reason,
const Connection* selected) {
RTC_DCHECK_RUN_ON(network_thread_);
IceControllerInterface::SwitchResult result =
wrapped_->ShouldSwitchConnection(reason, selected);
HandleSwitchResult(reason, result);
return result.connection.has_value();
}
void WrappingActiveIceController::HandleSwitchResult(
IceSwitchReason reason_for_switch,
IceControllerInterface::SwitchResult result) {
RTC_DCHECK_RUN_ON(network_thread_);
if (result.connection.has_value()) {
RTC_LOG(LS_INFO) << "Switching selected connection due to: "
<< IceSwitchReasonToString(reason_for_switch);
agent_.SwitchSelectedConnection(result.connection.value(),
reason_for_switch);
}
if (result.recheck_event.has_value()) {
// If we do not switch to the connection because it missed the receiving
// threshold, the new connection is in a better receiving state than the
// currently selected connection. So we need to re-check whether it needs
// to be switched at a later time.
network_thread_->PostDelayedTask(
SafeTask(task_safety_.flag(),
[this, recheck_reason = result.recheck_event->reason]() {
SortAndSwitchToBestConnection(recheck_reason);
}),
TimeDelta::Millis(result.recheck_event->recheck_delay_ms));
}
agent_.ForgetLearnedStateForConnections(
result.connections_to_forget_state_on);
}
void WrappingActiveIceController::UpdateStateOnConnectionsResorted() {
RTC_DCHECK_RUN_ON(network_thread_);
PruneConnections();
// Update the internal state of the ICE agentl.
agent_.UpdateState();
// Also possibly start pinging.
// We could start pinging if:
// * The first connection was created.
// * ICE credentials were provided.
// * A TCP connection became connected.
MaybeStartPinging();
}
void WrappingActiveIceController::PruneConnections() {
RTC_DCHECK_RUN_ON(network_thread_);
// The controlled side can prune only if the selected connection has been
// nominated because otherwise it may prune the connection that will be
// selected by the controlling side.
// TODO(honghaiz): This is not enough to prevent a connection from being
// pruned too early because with aggressive nomination, the controlling side
// will nominate every connection until it becomes writable.
if (agent_.GetIceRole() == ICEROLE_CONTROLLING ||
(selected_connection_ && selected_connection_->nominated())) {
std::vector<const Connection*> connections_to_prune =
wrapped_->PruneConnections();
agent_.PruneConnections(connections_to_prune);
}
}
// Only for unit tests
const Connection* WrappingActiveIceController::FindNextPingableConnection() {
RTC_DCHECK_RUN_ON(network_thread_);
return wrapped_->FindNextPingableConnection();
}
} // namespace cricket

View file

@ -0,0 +1,97 @@
/*
* Copyright 2022 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.
*/
#ifndef P2P_BASE_WRAPPING_ACTIVE_ICE_CONTROLLER_H_
#define P2P_BASE_WRAPPING_ACTIVE_ICE_CONTROLLER_H_
#include <memory>
#include "absl/types/optional.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "p2p/base/active_ice_controller_interface.h"
#include "p2p/base/connection.h"
#include "p2p/base/ice_agent_interface.h"
#include "p2p/base/ice_controller_factory_interface.h"
#include "p2p/base/ice_controller_interface.h"
#include "p2p/base/ice_switch_reason.h"
#include "p2p/base/ice_transport_internal.h"
#include "p2p/base/transport_description.h"
#include "rtc_base/thread.h"
#include "rtc_base/thread_annotations.h"
namespace cricket {
// WrappingActiveIceController provides the functionality of a legacy passive
// ICE controller but packaged as an active ICE Controller.
class WrappingActiveIceController : public ActiveIceControllerInterface {
public:
// Constructs an active ICE controller wrapping an already constructed legacy
// ICE controller. Does not take ownership of the ICE agent, which must
// already exist and outlive the ICE controller.
WrappingActiveIceController(IceAgentInterface* ice_agent,
std::unique_ptr<IceControllerInterface> wrapped);
// Constructs an active ICE controller that wraps over a legacy ICE
// controller. The legacy ICE controller is constructed through a factory, if
// one is supplied. If not, a default BasicIceController is wrapped instead.
// Does not take ownership of the ICE agent, which must already exist and
// outlive the ICE controller.
WrappingActiveIceController(
IceAgentInterface* ice_agent,
IceControllerFactoryInterface* wrapped_factory,
const IceControllerFactoryArgs& wrapped_factory_args);
virtual ~WrappingActiveIceController();
void SetIceConfig(const IceConfig& config) override;
bool GetUseCandidateAttribute(const Connection* connection,
NominationMode mode,
IceMode remote_ice_mode) const override;
void OnConnectionAdded(const Connection* connection) override;
void OnConnectionPinged(const Connection* connection) override;
void OnConnectionUpdated(const Connection* connection) override;
void OnConnectionSwitched(const Connection* connection) override;
void OnConnectionDestroyed(const Connection* connection) override;
void OnSortAndSwitchRequest(IceSwitchReason reason) override;
void OnImmediateSortAndSwitchRequest(IceSwitchReason reason) override;
bool OnImmediateSwitchRequest(IceSwitchReason reason,
const Connection* selected) override;
// Only for unit tests
const Connection* FindNextPingableConnection() override;
private:
void MaybeStartPinging();
void SelectAndPingConnection();
void HandlePingResult(IceControllerInterface::PingResult result);
void SortAndSwitchToBestConnection(IceSwitchReason reason);
void HandleSwitchResult(IceSwitchReason reason_for_switch,
IceControllerInterface::SwitchResult result);
void UpdateStateOnConnectionsResorted();
void PruneConnections();
rtc::Thread* const network_thread_;
webrtc::ScopedTaskSafety task_safety_;
bool started_pinging_ RTC_GUARDED_BY(network_thread_) = false;
bool sort_pending_ RTC_GUARDED_BY(network_thread_) = false;
const Connection* selected_connection_ RTC_GUARDED_BY(network_thread_) =
nullptr;
std::unique_ptr<IceControllerInterface> wrapped_
RTC_GUARDED_BY(network_thread_);
IceAgentInterface& agent_ RTC_GUARDED_BY(network_thread_);
};
} // namespace cricket
#endif // P2P_BASE_WRAPPING_ACTIVE_ICE_CONTROLLER_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,415 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef P2P_CLIENT_BASIC_PORT_ALLOCATOR_H_
#define P2P_CLIENT_BASIC_PORT_ALLOCATOR_H_
#include <memory>
#include <string>
#include <vector>
#include "absl/strings/string_view.h"
#include "api/field_trials_view.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "api/turn_customizer.h"
#include "p2p/base/port_allocator.h"
#include "p2p/client/relay_port_factory_interface.h"
#include "p2p/client/turn_port_factory.h"
#include "rtc_base/checks.h"
#include "rtc_base/memory/always_valid_pointer.h"
#include "rtc_base/network.h"
#include "rtc_base/network/received_packet.h"
#include "rtc_base/system/rtc_export.h"
#include "rtc_base/thread.h"
#include "rtc_base/thread_annotations.h"
namespace cricket {
class RTC_EXPORT BasicPortAllocator : public PortAllocator {
public:
// The NetworkManager is a mandatory argument. The other arguments are
// optional. All pointers are owned by caller and must have a life time
// that exceeds that of BasicPortAllocator.
BasicPortAllocator(rtc::NetworkManager* network_manager,
rtc::PacketSocketFactory* socket_factory,
webrtc::TurnCustomizer* customizer = nullptr,
RelayPortFactoryInterface* relay_port_factory = nullptr,
const webrtc::FieldTrialsView* field_trials = nullptr);
BasicPortAllocator(rtc::NetworkManager* network_manager,
rtc::PacketSocketFactory* socket_factory,
const ServerAddresses& stun_servers,
const webrtc::FieldTrialsView* field_trials = nullptr);
~BasicPortAllocator() override;
// Set to kDefaultNetworkIgnoreMask by default.
void SetNetworkIgnoreMask(int network_ignore_mask) override;
int GetNetworkIgnoreMask() const;
rtc::NetworkManager* network_manager() const {
CheckRunOnValidThreadIfInitialized();
return network_manager_;
}
// If socket_factory() is set to NULL each PortAllocatorSession
// creates its own socket factory.
rtc::PacketSocketFactory* socket_factory() {
CheckRunOnValidThreadIfInitialized();
return socket_factory_;
}
PortAllocatorSession* CreateSessionInternal(
absl::string_view content_name,
int component,
absl::string_view ice_ufrag,
absl::string_view ice_pwd) override;
// Convenience method that adds a TURN server to the configuration.
void AddTurnServerForTesting(const RelayServerConfig& turn_server);
RelayPortFactoryInterface* relay_port_factory() {
CheckRunOnValidThreadIfInitialized();
return relay_port_factory_;
}
void SetVpnList(const std::vector<rtc::NetworkMask>& vpn_list) override;
const webrtc::FieldTrialsView* field_trials() const {
return field_trials_.get();
}
private:
bool MdnsObfuscationEnabled() const override;
webrtc::AlwaysValidPointer<const webrtc::FieldTrialsView,
webrtc::FieldTrialBasedConfig>
field_trials_;
rtc::NetworkManager* network_manager_;
// Always externally-owned pointer to a socket factory.
rtc::PacketSocketFactory* const socket_factory_;
int network_ignore_mask_ = rtc::kDefaultNetworkIgnoreMask;
// This instance is created if caller does pass a factory.
const std::unique_ptr<RelayPortFactoryInterface> default_relay_port_factory_;
// This is the factory being used.
RelayPortFactoryInterface* const relay_port_factory_;
};
struct PortConfiguration;
class AllocationSequence;
enum class SessionState {
GATHERING, // Actively allocating ports and gathering candidates.
CLEARED, // Current allocation process has been stopped but may start
// new ones.
STOPPED // This session has completely stopped, no new allocation
// process will be started.
};
// This class is thread-compatible and assumes it's created, operated upon and
// destroyed on the network thread.
class RTC_EXPORT BasicPortAllocatorSession : public PortAllocatorSession {
public:
BasicPortAllocatorSession(BasicPortAllocator* allocator,
absl::string_view content_name,
int component,
absl::string_view ice_ufrag,
absl::string_view ice_pwd);
~BasicPortAllocatorSession() override;
virtual BasicPortAllocator* allocator();
rtc::Thread* network_thread() { return network_thread_; }
rtc::PacketSocketFactory* socket_factory() { return socket_factory_; }
// If the new filter allows new types of candidates compared to the previous
// filter, gathered candidates that were discarded because of not matching the
// previous filter will be signaled if they match the new one.
//
// We do not perform any regathering since the port allocator flags decide
// the type of candidates to gather and the candidate filter only controls the
// signaling of candidates. As a result, with the candidate filter changed
// alone, all newly allowed candidates for signaling should already be
// gathered by the respective cricket::Port.
void SetCandidateFilter(uint32_t filter) override;
void StartGettingPorts() override;
void StopGettingPorts() override;
void ClearGettingPorts() override;
bool IsGettingPorts() override;
bool IsCleared() const override;
bool IsStopped() const override;
// These will all be cricket::Ports.
std::vector<PortInterface*> ReadyPorts() const override;
std::vector<Candidate> ReadyCandidates() const override;
bool CandidatesAllocationDone() const override;
void RegatherOnFailedNetworks() override;
void GetCandidateStatsFromReadyPorts(
CandidateStatsList* candidate_stats_list) const override;
void SetStunKeepaliveIntervalForReadyPorts(
const absl::optional<int>& stun_keepalive_interval) override;
void PruneAllPorts() override;
static std::vector<const rtc::Network*> SelectIPv6Networks(
std::vector<const rtc::Network*>& all_ipv6_networks,
int max_ipv6_networks);
protected:
void UpdateIceParametersInternal() override;
// Starts the process of getting the port configurations.
virtual void GetPortConfigurations();
// Adds a port configuration that is now ready. Once we have one for each
// network (or a timeout occurs), we will start allocating ports.
void ConfigReady(std::unique_ptr<PortConfiguration> config);
// TODO(bugs.webrtc.org/12840) Remove once unused in downstream projects.
ABSL_DEPRECATED(
"Use ConfigReady(std::unique_ptr<PortConfiguration>) instead!")
void ConfigReady(PortConfiguration* config);
private:
class PortData {
public:
enum State {
STATE_INPROGRESS, // Still gathering candidates.
STATE_COMPLETE, // All candidates allocated and ready for process.
STATE_ERROR, // Error in gathering candidates.
STATE_PRUNED // Pruned by higher priority ports on the same network
// interface. Only TURN ports may be pruned.
};
PortData() {}
PortData(Port* port, AllocationSequence* seq)
: port_(port), sequence_(seq) {}
Port* port() const { return port_; }
AllocationSequence* sequence() const { return sequence_; }
bool has_pairable_candidate() const { return has_pairable_candidate_; }
State state() const { return state_; }
bool complete() const { return state_ == STATE_COMPLETE; }
bool error() const { return state_ == STATE_ERROR; }
bool pruned() const { return state_ == STATE_PRUNED; }
bool inprogress() const { return state_ == STATE_INPROGRESS; }
// Returns true if this port is ready to be used.
bool ready() const {
return has_pairable_candidate_ && state_ != STATE_ERROR &&
state_ != STATE_PRUNED;
}
// Sets the state to "PRUNED" and prunes the Port.
void Prune() {
state_ = STATE_PRUNED;
if (port()) {
port()->Prune();
}
}
void set_has_pairable_candidate(bool has_pairable_candidate) {
if (has_pairable_candidate) {
RTC_DCHECK(state_ == STATE_INPROGRESS);
}
has_pairable_candidate_ = has_pairable_candidate;
}
void set_state(State state) {
RTC_DCHECK(state != STATE_ERROR || state_ == STATE_INPROGRESS);
state_ = state;
}
private:
Port* port_ = nullptr;
AllocationSequence* sequence_ = nullptr;
bool has_pairable_candidate_ = false;
State state_ = STATE_INPROGRESS;
};
void OnConfigReady(std::unique_ptr<PortConfiguration> config);
void OnConfigStop();
void AllocatePorts();
void OnAllocate(int allocation_epoch);
void DoAllocate(bool disable_equivalent_phases);
void OnNetworksChanged();
void OnAllocationSequenceObjectsCreated();
void DisableEquivalentPhases(const rtc::Network* network,
PortConfiguration* config,
uint32_t* flags);
void AddAllocatedPort(Port* port, AllocationSequence* seq);
void OnCandidateReady(Port* port, const Candidate& c);
void OnCandidateError(Port* port, const IceCandidateErrorEvent& event);
void OnPortComplete(Port* port);
void OnPortError(Port* port);
void OnProtocolEnabled(AllocationSequence* seq, ProtocolType proto);
void OnPortDestroyed(PortInterface* port);
void MaybeSignalCandidatesAllocationDone();
void OnPortAllocationComplete();
PortData* FindPort(Port* port);
std::vector<const rtc::Network*> GetNetworks();
std::vector<const rtc::Network*> GetFailedNetworks();
void Regather(const std::vector<const rtc::Network*>& networks,
bool disable_equivalent_phases,
IceRegatheringReason reason);
bool CheckCandidateFilter(const Candidate& c) const;
bool CandidatePairable(const Candidate& c, const Port* port) const;
std::vector<PortData*> GetUnprunedPorts(
const std::vector<const rtc::Network*>& networks);
// Prunes ports and signal the remote side to remove the candidates that
// were previously signaled from these ports.
void PrunePortsAndRemoveCandidates(
const std::vector<PortData*>& port_data_list);
// Gets filtered and sanitized candidates generated from a port and
// append to `candidates`.
void GetCandidatesFromPort(const PortData& data,
std::vector<Candidate>* candidates) const;
Port* GetBestTurnPortForNetwork(absl::string_view network_name) const;
// Returns true if at least one TURN port is pruned.
bool PruneTurnPorts(Port* newly_pairable_turn_port);
bool PruneNewlyPairableTurnPort(PortData* newly_pairable_turn_port);
BasicPortAllocator* allocator_;
rtc::Thread* network_thread_;
rtc::PacketSocketFactory* socket_factory_;
bool allocation_started_;
bool network_manager_started_;
bool allocation_sequences_created_;
std::vector<std::unique_ptr<PortConfiguration>> configs_;
std::vector<AllocationSequence*> sequences_;
std::vector<PortData> ports_;
std::vector<IceCandidateErrorEvent> candidate_error_events_;
uint32_t candidate_filter_ = CF_ALL;
// Policy on how to prune turn ports, taken from the port allocator.
webrtc::PortPrunePolicy turn_port_prune_policy_;
SessionState state_ = SessionState::CLEARED;
int allocation_epoch_ RTC_GUARDED_BY(network_thread_) = 0;
webrtc::ScopedTaskSafety network_safety_;
friend class AllocationSequence;
};
// Records configuration information useful in creating ports.
// TODO(deadbeef): Rename "relay" to "turn_server" in this struct.
struct RTC_EXPORT PortConfiguration {
// TODO(jiayl): remove `stun_address` when Chrome is updated.
rtc::SocketAddress stun_address;
ServerAddresses stun_servers;
std::string username;
std::string password;
bool use_turn_server_as_stun_server_disabled = false;
typedef std::vector<RelayServerConfig> RelayList;
RelayList relays;
PortConfiguration(const ServerAddresses& stun_servers,
absl::string_view username,
absl::string_view password,
const webrtc::FieldTrialsView* field_trials = nullptr);
// Returns addresses of both the explicitly configured STUN servers,
// and TURN servers that should be used as STUN servers.
ServerAddresses StunServers();
// Adds another relay server, with the given ports and modifier, to the list.
void AddRelay(const RelayServerConfig& config);
// Determines whether the given relay server supports the given protocol.
bool SupportsProtocol(const RelayServerConfig& relay,
ProtocolType type) const;
bool SupportsProtocol(ProtocolType type) const;
// Helper method returns the server addresses for the matching RelayType and
// Protocol type.
ServerAddresses GetRelayServerAddresses(ProtocolType type) const;
};
class UDPPort;
class TurnPort;
// Performs the allocation of ports, in a sequenced (timed) manner, for a given
// network and IP address.
// This class is thread-compatible.
class AllocationSequence {
public:
enum State {
kInit, // Initial state.
kRunning, // Started allocating ports.
kStopped, // Stopped from running.
kCompleted, // All ports are allocated.
// kInit --> kRunning --> {kCompleted|kStopped}
};
// `port_allocation_complete_callback` is called when AllocationSequence is
// done with allocating ports. This signal is useful when port allocation
// fails which doesn't result in any candidates. Using this signal
// BasicPortAllocatorSession can send its candidate discovery conclusion
// signal. Without this signal, BasicPortAllocatorSession doesn't have any
// event to trigger signal. This can also be achieved by starting a timer in
// BPAS, but this is less deterministic.
AllocationSequence(BasicPortAllocatorSession* session,
const rtc::Network* network,
PortConfiguration* config,
uint32_t flags,
std::function<void()> port_allocation_complete_callback);
void Init();
void Clear();
void OnNetworkFailed();
State state() const { return state_; }
const rtc::Network* network() const { return network_; }
bool network_failed() const { return network_failed_; }
void set_network_failed() { network_failed_ = true; }
// Disables the phases for a new sequence that this one already covers for an
// equivalent network setup.
void DisableEquivalentPhases(const rtc::Network* network,
PortConfiguration* config,
uint32_t* flags);
// Starts and stops the sequence. When started, it will continue allocating
// new ports on its own timed schedule.
void Start();
void Stop();
private:
void CreateTurnPort(const RelayServerConfig& config, int relative_priority);
typedef std::vector<ProtocolType> ProtocolList;
void Process(int epoch);
bool IsFlagSet(uint32_t flag) { return ((flags_ & flag) != 0); }
void CreateUDPPorts();
void CreateTCPPorts();
void CreateStunPorts();
void CreateRelayPorts();
void OnReadPacket(rtc::AsyncPacketSocket* socket,
const rtc::ReceivedPacket& packet);
void OnPortDestroyed(PortInterface* port);
BasicPortAllocatorSession* session_;
bool network_failed_ = false;
const rtc::Network* network_;
// Compared with the new best IP in DisableEquivalentPhases.
rtc::IPAddress previous_best_ip_;
PortConfiguration* config_;
State state_;
uint32_t flags_;
ProtocolList protocols_;
std::unique_ptr<rtc::AsyncPacketSocket> udp_socket_;
// There will be only one udp port per AllocationSequence.
UDPPort* udp_port_;
std::vector<Port*> relay_ports_;
int phase_;
std::function<void()> port_allocation_complete_callback_;
// This counter is sampled and passed together with tasks when tasks are
// posted. If the sampled counter doesn't match `epoch_` on reception, the
// posted task is ignored.
int epoch_ = 0;
webrtc::ScopedTaskSafety safety_;
};
} // namespace cricket
#endif // P2P_CLIENT_BASIC_PORT_ALLOCATOR_H_

View file

@ -0,0 +1,72 @@
/*
* Copyright 2017 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.
*/
#ifndef P2P_CLIENT_RELAY_PORT_FACTORY_INTERFACE_H_
#define P2P_CLIENT_RELAY_PORT_FACTORY_INTERFACE_H_
#include <memory>
#include <string>
#include "p2p/base/port_interface.h"
#include "rtc_base/ref_count.h"
namespace rtc {
class AsyncPacketSocket;
class Network;
class PacketSocketFactory;
class Thread;
} // namespace rtc
namespace webrtc {
class TurnCustomizer;
class FieldTrialsView;
} // namespace webrtc
namespace cricket {
class Port;
struct ProtocolAddress;
struct RelayServerConfig;
// A struct containing arguments to RelayPortFactory::Create()
struct CreateRelayPortArgs {
rtc::Thread* network_thread;
rtc::PacketSocketFactory* socket_factory;
const rtc::Network* network;
const ProtocolAddress* server_address;
const RelayServerConfig* config;
std::string username;
std::string password;
webrtc::TurnCustomizer* turn_customizer = nullptr;
const webrtc::FieldTrialsView* field_trials = nullptr;
// Relative priority of candidates from this TURN server in relation
// to the candidates from other servers. Required because ICE priorities
// need to be unique.
int relative_priority = 0;
};
// A factory for creating RelayPort's.
class RelayPortFactoryInterface {
public:
virtual ~RelayPortFactoryInterface() {}
// This variant is used for UDP connection to the relay server
// using a already existing shared socket.
virtual std::unique_ptr<Port> Create(const CreateRelayPortArgs& args,
rtc::AsyncPacketSocket* udp_socket) = 0;
// This variant is used for the other cases.
virtual std::unique_ptr<Port> Create(const CreateRelayPortArgs& args,
int min_port,
int max_port) = 0;
};
} // namespace cricket
#endif // P2P_CLIENT_RELAY_PORT_FACTORY_INTERFACE_H_

View file

@ -0,0 +1,45 @@
/*
* Copyright 2017 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/client/turn_port_factory.h"
#include <memory>
#include <utility>
#include "p2p/base/port_allocator.h"
#include "p2p/base/turn_port.h"
namespace cricket {
TurnPortFactory::~TurnPortFactory() {}
std::unique_ptr<Port> TurnPortFactory::Create(
const CreateRelayPortArgs& args,
rtc::AsyncPacketSocket* udp_socket) {
auto port = TurnPort::Create(args, udp_socket);
if (!port)
return nullptr;
port->SetTlsCertPolicy(args.config->tls_cert_policy);
port->SetTurnLoggingId(args.config->turn_logging_id);
return std::move(port);
}
std::unique_ptr<Port> TurnPortFactory::Create(const CreateRelayPortArgs& args,
int min_port,
int max_port) {
auto port = TurnPort::Create(args, min_port, max_port);
if (!port)
return nullptr;
port->SetTlsCertPolicy(args.config->tls_cert_policy);
port->SetTurnLoggingId(args.config->turn_logging_id);
return std::move(port);
}
} // namespace cricket

View file

@ -0,0 +1,37 @@
/*
* Copyright 2017 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.
*/
#ifndef P2P_CLIENT_TURN_PORT_FACTORY_H_
#define P2P_CLIENT_TURN_PORT_FACTORY_H_
#include <memory>
#include "p2p/base/port.h"
#include "p2p/client/relay_port_factory_interface.h"
#include "rtc_base/async_packet_socket.h"
namespace cricket {
// This is a RelayPortFactory that produces TurnPorts.
class TurnPortFactory : public RelayPortFactoryInterface {
public:
~TurnPortFactory() override;
std::unique_ptr<Port> Create(const CreateRelayPortArgs& args,
rtc::AsyncPacketSocket* udp_socket) override;
std::unique_ptr<Port> Create(const CreateRelayPortArgs& args,
int min_port,
int max_port) override;
};
} // namespace cricket
#endif // P2P_CLIENT_TURN_PORT_FACTORY_H_

View file

@ -0,0 +1,602 @@
/*
* Copyright 2015 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/stunprober/stun_prober.h"
#include <cstdint>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include "api/array_view.h"
#include "api/packet_socket_factory.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "api/transport/stun.h"
#include "api/units/time_delta.h"
#include "rtc_base/async_packet_socket.h"
#include "rtc_base/checks.h"
#include "rtc_base/network/received_packet.h"
#include "rtc_base/thread.h"
#include "rtc_base/time_utils.h"
namespace stunprober {
namespace {
using ::webrtc::SafeTask;
using ::webrtc::TimeDelta;
const int THREAD_WAKE_UP_INTERVAL_MS = 5;
template <typename T>
void IncrementCounterByAddress(std::map<T, int>* counter_per_ip, const T& ip) {
counter_per_ip->insert(std::make_pair(ip, 0)).first->second++;
}
} // namespace
// A requester tracks the requests and responses from a single socket to many
// STUN servers
class StunProber::Requester : public sigslot::has_slots<> {
public:
// Each Request maps to a request and response.
struct Request {
// Actual time the STUN bind request was sent.
int64_t sent_time_ms = 0;
// Time the response was received.
int64_t received_time_ms = 0;
// Server reflexive address from STUN response for this given request.
rtc::SocketAddress srflx_addr;
rtc::IPAddress server_addr;
int64_t rtt() { return received_time_ms - sent_time_ms; }
void ProcessResponse(rtc::ArrayView<const uint8_t> payload);
};
// StunProber provides `server_ips` for Requester to probe. For shared
// socket mode, it'll be all the resolved IP addresses. For non-shared mode,
// it'll just be a single address.
Requester(StunProber* prober,
rtc::AsyncPacketSocket* socket,
const std::vector<rtc::SocketAddress>& server_ips);
~Requester() override;
Requester(const Requester&) = delete;
Requester& operator=(const Requester&) = delete;
// There is no callback for SendStunRequest as the underneath socket send is
// expected to be completed immediately. Otherwise, it'll skip this request
// and move to the next one.
void SendStunRequest();
void OnStunResponseReceived(rtc::AsyncPacketSocket* socket,
const rtc::ReceivedPacket& packet);
const std::vector<Request*>& requests() { return requests_; }
// Whether this Requester has completed all requests.
bool Done() {
return static_cast<size_t>(num_request_sent_) == server_ips_.size();
}
private:
Request* GetRequestByAddress(const rtc::IPAddress& ip);
StunProber* prober_;
// The socket for this session.
std::unique_ptr<rtc::AsyncPacketSocket> socket_;
// Temporary SocketAddress and buffer for RecvFrom.
rtc::SocketAddress addr_;
std::unique_ptr<rtc::ByteBufferWriter> response_packet_;
std::vector<Request*> requests_;
std::vector<rtc::SocketAddress> server_ips_;
int16_t num_request_sent_ = 0;
int16_t num_response_received_ = 0;
webrtc::SequenceChecker& thread_checker_;
};
StunProber::Requester::Requester(
StunProber* prober,
rtc::AsyncPacketSocket* socket,
const std::vector<rtc::SocketAddress>& server_ips)
: prober_(prober),
socket_(socket),
response_packet_(new rtc::ByteBufferWriter(nullptr, kMaxUdpBufferSize)),
server_ips_(server_ips),
thread_checker_(prober->thread_checker_) {
socket_->RegisterReceivedPacketCallback(
[&](rtc::AsyncPacketSocket* socket, const rtc::ReceivedPacket& packet) {
OnStunResponseReceived(socket, packet);
});
}
StunProber::Requester::~Requester() {
if (socket_) {
socket_->Close();
}
for (auto* req : requests_) {
if (req) {
delete req;
}
}
}
void StunProber::Requester::SendStunRequest() {
RTC_DCHECK(thread_checker_.IsCurrent());
requests_.push_back(new Request());
Request& request = *(requests_.back());
// Random transaction ID, STUN_BINDING_REQUEST
cricket::StunMessage message(cricket::STUN_BINDING_REQUEST);
std::unique_ptr<rtc::ByteBufferWriter> request_packet(
new rtc::ByteBufferWriter(nullptr, kMaxUdpBufferSize));
if (!message.Write(request_packet.get())) {
prober_->ReportOnFinished(WRITE_FAILED);
return;
}
auto addr = server_ips_[num_request_sent_];
request.server_addr = addr.ipaddr();
// The write must succeed immediately. Otherwise, the calculating of the STUN
// request timing could become too complicated. Callback is ignored by passing
// empty AsyncCallback.
rtc::PacketOptions options;
int rv = socket_->SendTo(request_packet->Data(), request_packet->Length(),
addr, options);
if (rv < 0) {
prober_->ReportOnFinished(WRITE_FAILED);
return;
}
request.sent_time_ms = rtc::TimeMillis();
num_request_sent_++;
RTC_DCHECK(static_cast<size_t>(num_request_sent_) <= server_ips_.size());
}
void StunProber::Requester::Request::ProcessResponse(
rtc::ArrayView<const uint8_t> payload) {
int64_t now = rtc::TimeMillis();
rtc::ByteBufferReader message(payload);
cricket::StunMessage stun_response;
if (!stun_response.Read(&message)) {
// Invalid or incomplete STUN packet.
received_time_ms = 0;
return;
}
// Get external address of the socket.
const cricket::StunAddressAttribute* addr_attr =
stun_response.GetAddress(cricket::STUN_ATTR_MAPPED_ADDRESS);
if (addr_attr == nullptr) {
// Addresses not available to detect whether or not behind a NAT.
return;
}
if (addr_attr->family() != cricket::STUN_ADDRESS_IPV4 &&
addr_attr->family() != cricket::STUN_ADDRESS_IPV6) {
return;
}
received_time_ms = now;
srflx_addr = addr_attr->GetAddress();
}
void StunProber::Requester::OnStunResponseReceived(
rtc::AsyncPacketSocket* socket,
const rtc::ReceivedPacket& packet) {
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_DCHECK(socket_);
Request* request = GetRequestByAddress(packet.source_address().ipaddr());
if (!request) {
// Something is wrong, finish the test.
prober_->ReportOnFinished(GENERIC_FAILURE);
return;
}
num_response_received_++;
request->ProcessResponse(packet.payload());
}
StunProber::Requester::Request* StunProber::Requester::GetRequestByAddress(
const rtc::IPAddress& ipaddr) {
RTC_DCHECK(thread_checker_.IsCurrent());
for (auto* request : requests_) {
if (request->server_addr == ipaddr) {
return request;
}
}
return nullptr;
}
StunProber::Stats::Stats() = default;
StunProber::Stats::~Stats() = default;
StunProber::ObserverAdapter::ObserverAdapter() = default;
StunProber::ObserverAdapter::~ObserverAdapter() = default;
void StunProber::ObserverAdapter::OnPrepared(StunProber* stunprober,
Status status) {
if (status == SUCCESS) {
stunprober->Start(this);
} else {
callback_(stunprober, status);
}
}
void StunProber::ObserverAdapter::OnFinished(StunProber* stunprober,
Status status) {
callback_(stunprober, status);
}
StunProber::StunProber(rtc::PacketSocketFactory* socket_factory,
rtc::Thread* thread,
std::vector<const rtc::Network*> networks)
: interval_ms_(0),
socket_factory_(socket_factory),
thread_(thread),
networks_(std::move(networks)) {}
StunProber::~StunProber() {
RTC_DCHECK(thread_checker_.IsCurrent());
for (auto* req : requesters_) {
if (req) {
delete req;
}
}
for (auto* s : sockets_) {
if (s) {
delete s;
}
}
}
bool StunProber::Start(const std::vector<rtc::SocketAddress>& servers,
bool shared_socket_mode,
int interval_ms,
int num_request_per_ip,
int timeout_ms,
const AsyncCallback callback) {
observer_adapter_.set_callback(callback);
return Prepare(servers, shared_socket_mode, interval_ms, num_request_per_ip,
timeout_ms, &observer_adapter_);
}
bool StunProber::Prepare(const std::vector<rtc::SocketAddress>& servers,
bool shared_socket_mode,
int interval_ms,
int num_request_per_ip,
int timeout_ms,
StunProber::Observer* observer) {
RTC_DCHECK(thread_checker_.IsCurrent());
interval_ms_ = interval_ms;
shared_socket_mode_ = shared_socket_mode;
requests_per_ip_ = num_request_per_ip;
if (requests_per_ip_ == 0 || servers.size() == 0) {
return false;
}
timeout_ms_ = timeout_ms;
servers_ = servers;
observer_ = observer;
// Remove addresses that are already resolved.
for (auto it = servers_.begin(); it != servers_.end();) {
if (it->ipaddr().family() != AF_UNSPEC) {
all_servers_addrs_.push_back(*it);
it = servers_.erase(it);
} else {
++it;
}
}
if (servers_.empty()) {
CreateSockets();
return true;
}
return ResolveServerName(servers_.back());
}
bool StunProber::Start(StunProber::Observer* observer) {
observer_ = observer;
if (total_ready_sockets_ != total_socket_required()) {
return false;
}
MaybeScheduleStunRequests();
return true;
}
bool StunProber::ResolveServerName(const rtc::SocketAddress& addr) {
RTC_DCHECK(!resolver_);
resolver_ = socket_factory_->CreateAsyncDnsResolver();
if (!resolver_) {
return false;
}
resolver_->Start(addr, [this] { OnServerResolved(resolver_->result()); });
return true;
}
void StunProber::OnSocketReady(rtc::AsyncPacketSocket* socket,
const rtc::SocketAddress& addr) {
total_ready_sockets_++;
if (total_ready_sockets_ == total_socket_required()) {
ReportOnPrepared(SUCCESS);
}
}
void StunProber::OnServerResolved(
const webrtc::AsyncDnsResolverResult& result) {
RTC_DCHECK(thread_checker_.IsCurrent());
rtc::SocketAddress received_address;
if (result.GetResolvedAddress(AF_INET, &received_address)) {
// Construct an address without the name in it.
rtc::SocketAddress addr(received_address.ipaddr(), received_address.port());
all_servers_addrs_.push_back(addr);
}
resolver_.reset();
servers_.pop_back();
if (servers_.size()) {
if (!ResolveServerName(servers_.back())) {
ReportOnPrepared(RESOLVE_FAILED);
}
return;
}
if (all_servers_addrs_.size() == 0) {
ReportOnPrepared(RESOLVE_FAILED);
return;
}
CreateSockets();
}
void StunProber::CreateSockets() {
// Dedupe.
std::set<rtc::SocketAddress> addrs(all_servers_addrs_.begin(),
all_servers_addrs_.end());
all_servers_addrs_.assign(addrs.begin(), addrs.end());
// Prepare all the sockets beforehand. All of them will bind to "any" address.
while (sockets_.size() < total_socket_required()) {
std::unique_ptr<rtc::AsyncPacketSocket> socket(
socket_factory_->CreateUdpSocket(rtc::SocketAddress(INADDR_ANY, 0), 0,
0));
if (!socket) {
ReportOnPrepared(GENERIC_FAILURE);
return;
}
// Chrome and WebRTC behave differently in terms of the state of a socket
// once returned from PacketSocketFactory::CreateUdpSocket.
if (socket->GetState() == rtc::AsyncPacketSocket::STATE_BINDING) {
socket->SignalAddressReady.connect(this, &StunProber::OnSocketReady);
} else {
OnSocketReady(socket.get(), rtc::SocketAddress(INADDR_ANY, 0));
}
sockets_.push_back(socket.release());
}
}
StunProber::Requester* StunProber::CreateRequester() {
RTC_DCHECK(thread_checker_.IsCurrent());
if (!sockets_.size()) {
return nullptr;
}
StunProber::Requester* requester;
if (shared_socket_mode_) {
requester = new Requester(this, sockets_.back(), all_servers_addrs_);
} else {
std::vector<rtc::SocketAddress> server_ip;
server_ip.push_back(
all_servers_addrs_[(num_request_sent_ % all_servers_addrs_.size())]);
requester = new Requester(this, sockets_.back(), server_ip);
}
sockets_.pop_back();
return requester;
}
bool StunProber::SendNextRequest() {
if (!current_requester_ || current_requester_->Done()) {
current_requester_ = CreateRequester();
requesters_.push_back(current_requester_);
}
if (!current_requester_) {
return false;
}
current_requester_->SendStunRequest();
num_request_sent_++;
return true;
}
bool StunProber::should_send_next_request(int64_t now) {
if (interval_ms_ < THREAD_WAKE_UP_INTERVAL_MS) {
return now >= next_request_time_ms_;
} else {
return (now + (THREAD_WAKE_UP_INTERVAL_MS / 2)) >= next_request_time_ms_;
}
}
int StunProber::get_wake_up_interval_ms() {
if (interval_ms_ < THREAD_WAKE_UP_INTERVAL_MS) {
return 1;
} else {
return THREAD_WAKE_UP_INTERVAL_MS;
}
}
void StunProber::MaybeScheduleStunRequests() {
RTC_DCHECK_RUN_ON(thread_);
int64_t now = rtc::TimeMillis();
if (Done()) {
thread_->PostDelayedTask(
SafeTask(task_safety_.flag(), [this] { ReportOnFinished(SUCCESS); }),
TimeDelta::Millis(timeout_ms_));
return;
}
if (should_send_next_request(now)) {
if (!SendNextRequest()) {
ReportOnFinished(GENERIC_FAILURE);
return;
}
next_request_time_ms_ = now + interval_ms_;
}
thread_->PostDelayedTask(
SafeTask(task_safety_.flag(), [this] { MaybeScheduleStunRequests(); }),
TimeDelta::Millis(get_wake_up_interval_ms()));
}
bool StunProber::GetStats(StunProber::Stats* prob_stats) const {
// No need to be on the same thread.
if (!prob_stats) {
return false;
}
StunProber::Stats stats;
int rtt_sum = 0;
int64_t first_sent_time = 0;
int64_t last_sent_time = 0;
NatType nat_type = NATTYPE_INVALID;
// Track of how many srflx IP that we have seen.
std::set<rtc::IPAddress> srflx_ips;
// If we're not receiving any response on a given IP, all requests sent to
// that IP should be ignored as this could just be an DNS error.
std::map<rtc::IPAddress, int> num_response_per_server;
std::map<rtc::IPAddress, int> num_request_per_server;
for (auto* requester : requesters_) {
std::map<rtc::SocketAddress, int> num_response_per_srflx_addr;
for (auto* request : requester->requests()) {
if (request->sent_time_ms <= 0) {
continue;
}
++stats.raw_num_request_sent;
IncrementCounterByAddress(&num_request_per_server, request->server_addr);
if (!first_sent_time) {
first_sent_time = request->sent_time_ms;
}
last_sent_time = request->sent_time_ms;
if (request->received_time_ms < request->sent_time_ms) {
continue;
}
IncrementCounterByAddress(&num_response_per_server, request->server_addr);
IncrementCounterByAddress(&num_response_per_srflx_addr,
request->srflx_addr);
rtt_sum += request->rtt();
stats.srflx_addrs.insert(request->srflx_addr.ToString());
srflx_ips.insert(request->srflx_addr.ipaddr());
}
// If we're using shared mode and seeing >1 srflx addresses for a single
// requester, it's symmetric NAT.
if (shared_socket_mode_ && num_response_per_srflx_addr.size() > 1) {
nat_type = NATTYPE_SYMMETRIC;
}
}
// We're probably not behind a regular NAT. We have more than 1 distinct
// server reflexive IPs.
if (srflx_ips.size() > 1) {
return false;
}
int num_sent = 0;
int num_received = 0;
int num_server_ip_with_response = 0;
for (const auto& kv : num_response_per_server) {
RTC_DCHECK_GT(kv.second, 0);
num_server_ip_with_response++;
num_received += kv.second;
num_sent += num_request_per_server[kv.first];
}
// Shared mode is only true if we use the shared socket and there are more
// than 1 responding servers.
stats.shared_socket_mode =
shared_socket_mode_ && (num_server_ip_with_response > 1);
if (stats.shared_socket_mode && nat_type == NATTYPE_INVALID) {
nat_type = NATTYPE_NON_SYMMETRIC;
}
// If we could find a local IP matching srflx, we're not behind a NAT.
rtc::SocketAddress srflx_addr;
if (stats.srflx_addrs.size() &&
!srflx_addr.FromString(*(stats.srflx_addrs.begin()))) {
return false;
}
for (const auto* net : networks_) {
if (srflx_addr.ipaddr() == net->GetBestIP()) {
nat_type = stunprober::NATTYPE_NONE;
stats.host_ip = net->GetBestIP().ToString();
break;
}
}
// Finally, we know we're behind a NAT but can't determine which type it is.
if (nat_type == NATTYPE_INVALID) {
nat_type = NATTYPE_UNKNOWN;
}
stats.nat_type = nat_type;
stats.num_request_sent = num_sent;
stats.num_response_received = num_received;
stats.target_request_interval_ns = interval_ms_ * 1000;
if (num_sent) {
stats.success_percent = static_cast<int>(100 * num_received / num_sent);
}
if (stats.raw_num_request_sent > 1) {
stats.actual_request_interval_ns =
(1000 * (last_sent_time - first_sent_time)) /
(stats.raw_num_request_sent - 1);
}
if (num_received) {
stats.average_rtt_ms = static_cast<int>((rtt_sum / num_received));
}
*prob_stats = stats;
return true;
}
void StunProber::ReportOnPrepared(StunProber::Status status) {
if (observer_) {
observer_->OnPrepared(this, status);
}
}
void StunProber::ReportOnFinished(StunProber::Status status) {
if (observer_) {
observer_->OnFinished(this, status);
}
}
} // namespace stunprober

View file

@ -0,0 +1,251 @@
/*
* Copyright 2015 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.
*/
#ifndef P2P_STUNPROBER_STUN_PROBER_H_
#define P2P_STUNPROBER_STUN_PROBER_H_
#include <memory>
#include <set>
#include <string>
#include <vector>
#include "api/async_dns_resolver.h"
#include "api/sequence_checker.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "rtc_base/network.h"
#include "rtc_base/socket_address.h"
#include "rtc_base/system/rtc_export.h"
#include "rtc_base/thread.h"
namespace rtc {
class AsyncPacketSocket;
class PacketSocketFactory;
class Thread;
class NetworkManager;
class AsyncResolverInterface;
} // namespace rtc
namespace stunprober {
class StunProber;
static const int kMaxUdpBufferSize = 1200;
typedef std::function<void(StunProber*, int)> AsyncCallback;
enum NatType {
NATTYPE_INVALID,
NATTYPE_NONE, // Not behind a NAT.
NATTYPE_UNKNOWN, // Behind a NAT but type can't be determine.
NATTYPE_SYMMETRIC, // Behind a symmetric NAT.
NATTYPE_NON_SYMMETRIC // Behind a non-symmetric NAT.
};
class RTC_EXPORT StunProber : public sigslot::has_slots<> {
public:
enum Status { // Used in UMA_HISTOGRAM_ENUMERATION.
SUCCESS, // Successfully received bytes from the server.
GENERIC_FAILURE, // Generic failure.
RESOLVE_FAILED, // Host resolution failed.
WRITE_FAILED, // Sending a message to the server failed.
READ_FAILED, // Reading the reply from the server failed.
};
class Observer {
public:
virtual ~Observer() = default;
virtual void OnPrepared(StunProber* prober, StunProber::Status status) = 0;
virtual void OnFinished(StunProber* prober, StunProber::Status status) = 0;
};
struct RTC_EXPORT Stats {
Stats();
~Stats();
// `raw_num_request_sent` is the total number of requests
// sent. `num_request_sent` is the count of requests against a server where
// we see at least one response. `num_request_sent` is designed to protect
// against DNS resolution failure or the STUN server is not responsive
// which could skew the result.
int raw_num_request_sent = 0;
int num_request_sent = 0;
int num_response_received = 0;
NatType nat_type = NATTYPE_INVALID;
int average_rtt_ms = -1;
int success_percent = 0;
int target_request_interval_ns = 0;
int actual_request_interval_ns = 0;
// Also report whether this trial can't be considered truly as shared
// mode. Share mode only makes sense when we have multiple IP resolved and
// successfully probed.
bool shared_socket_mode = false;
std::string host_ip;
// If the srflx_addrs has more than 1 element, the NAT is symmetric.
std::set<std::string> srflx_addrs;
};
StunProber(rtc::PacketSocketFactory* socket_factory,
rtc::Thread* thread,
std::vector<const rtc::Network*> networks);
~StunProber() override;
StunProber(const StunProber&) = delete;
StunProber& operator=(const StunProber&) = delete;
// Begin performing the probe test against the `servers`. If
// `shared_socket_mode` is false, each request will be done with a new socket.
// Otherwise, a unique socket will be used for a single round of requests
// against all resolved IPs. No single socket will be used against a given IP
// more than once. The interval of requests will be as close to the requested
// inter-probe interval `stun_ta_interval_ms` as possible. After sending out
// the last scheduled request, the probe will wait `timeout_ms` for request
// responses and then call `finish_callback`. `requests_per_ip` indicates how
// many requests should be tried for each resolved IP address. In shared mode,
// (the number of sockets to be created) equals to `requests_per_ip`. In
// non-shared mode, (the number of sockets) equals to requests_per_ip * (the
// number of resolved IP addresses). TODO(guoweis): Remove this once
// everything moved to Prepare() and Run().
bool Start(const std::vector<rtc::SocketAddress>& servers,
bool shared_socket_mode,
int stun_ta_interval_ms,
int requests_per_ip,
int timeout_ms,
AsyncCallback finish_callback);
// TODO(guoweis): The combination of Prepare() and Run() are equivalent to the
// Start() above. Remove Start() once everything is migrated.
bool Prepare(const std::vector<rtc::SocketAddress>& servers,
bool shared_socket_mode,
int stun_ta_interval_ms,
int requests_per_ip,
int timeout_ms,
StunProber::Observer* observer);
// Start to send out the STUN probes.
bool Start(StunProber::Observer* observer);
// Method to retrieve the Stats once `finish_callback` is invoked. Returning
// false when the result is inconclusive, for example, whether it's behind a
// NAT or not.
bool GetStats(Stats* stats) const;
int estimated_execution_time() {
return static_cast<int>(requests_per_ip_ * all_servers_addrs_.size() *
interval_ms_);
}
private:
// A requester tracks the requests and responses from a single socket to many
// STUN servers.
class Requester;
// TODO(guoweis): Remove this once all dependencies move away from
// AsyncCallback.
class ObserverAdapter : public Observer {
public:
ObserverAdapter();
~ObserverAdapter() override;
void set_callback(AsyncCallback callback) { callback_ = callback; }
void OnPrepared(StunProber* stunprober, Status status) override;
void OnFinished(StunProber* stunprober, Status status) override;
private:
AsyncCallback callback_;
};
bool ResolveServerName(const rtc::SocketAddress& addr);
void OnServerResolved(const webrtc::AsyncDnsResolverResult& resolver);
void OnSocketReady(rtc::AsyncPacketSocket* socket,
const rtc::SocketAddress& addr);
void CreateSockets();
bool Done() {
return num_request_sent_ >= requests_per_ip_ * all_servers_addrs_.size();
}
size_t total_socket_required() {
return (shared_socket_mode_ ? 1 : all_servers_addrs_.size()) *
requests_per_ip_;
}
bool should_send_next_request(int64_t now);
int get_wake_up_interval_ms();
bool SendNextRequest();
// Will be invoked in 1ms intervals and schedule the next request from the
// `current_requester_` if the time has passed for another request.
void MaybeScheduleStunRequests();
void ReportOnPrepared(StunProber::Status status);
void ReportOnFinished(StunProber::Status status);
Requester* CreateRequester();
Requester* current_requester_ = nullptr;
// The time when the next request should go out.
int64_t next_request_time_ms_ = 0;
// Total requests sent so far.
uint32_t num_request_sent_ = 0;
bool shared_socket_mode_ = false;
// How many requests should be done against each resolved IP.
uint32_t requests_per_ip_ = 0;
// Milliseconds to pause between each STUN request.
int interval_ms_;
// Timeout period after the last request is sent.
int timeout_ms_;
// STUN server name to be resolved.
std::vector<rtc::SocketAddress> servers_;
// Weak references.
rtc::PacketSocketFactory* socket_factory_;
rtc::Thread* thread_;
// Accumulate all resolved addresses.
std::vector<rtc::SocketAddress> all_servers_addrs_;
// The set of STUN probe sockets and their state.
std::vector<Requester*> requesters_;
webrtc::SequenceChecker thread_checker_;
// Temporary storage for created sockets.
std::vector<rtc::AsyncPacketSocket*> sockets_;
// This tracks how many of the sockets are ready.
size_t total_ready_sockets_ = 0;
Observer* observer_ = nullptr;
// TODO(guoweis): Remove this once all dependencies move away from
// AsyncCallback.
ObserverAdapter observer_adapter_;
const std::vector<const rtc::Network*> networks_;
std::unique_ptr<webrtc::AsyncDnsResolverInterface> resolver_;
webrtc::ScopedTaskSafety task_safety_;
};
} // namespace stunprober
#endif // P2P_STUNPROBER_STUN_PROBER_H_