summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/p2p/base
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/libwebrtc/p2p/base
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/p2p/base')
-rw-r--r--third_party/libwebrtc/p2p/base/active_ice_controller_factory_interface.h39
-rw-r--r--third_party/libwebrtc/p2p/base/active_ice_controller_interface.h84
-rw-r--r--third_party/libwebrtc/p2p/base/async_stun_tcp_socket.cc156
-rw-r--r--third_party/libwebrtc/p2p/base/async_stun_tcp_socket.h51
-rw-r--r--third_party/libwebrtc/p2p/base/async_stun_tcp_socket_unittest.cc288
-rw-r--r--third_party/libwebrtc/p2p/base/basic_async_resolver_factory.cc53
-rw-r--r--third_party/libwebrtc/p2p/base/basic_async_resolver_factory.h62
-rw-r--r--third_party/libwebrtc/p2p/base/basic_async_resolver_factory_unittest.cc111
-rw-r--r--third_party/libwebrtc/p2p/base/basic_ice_controller.cc855
-rw-r--r--third_party/libwebrtc/p2p/base/basic_ice_controller.h165
-rw-r--r--third_party/libwebrtc/p2p/base/basic_packet_socket_factory.cc210
-rw-r--r--third_party/libwebrtc/p2p/base/basic_packet_socket_factory.h69
-rw-r--r--third_party/libwebrtc/p2p/base/candidate_pair_interface.h28
-rw-r--r--third_party/libwebrtc/p2p/base/connection.cc1720
-rw-r--r--third_party/libwebrtc/p2p/base/connection.h498
-rw-r--r--third_party/libwebrtc/p2p/base/connection_info.cc44
-rw-r--r--third_party/libwebrtc/p2p/base/connection_info.h87
-rw-r--r--third_party/libwebrtc/p2p/base/default_ice_transport_factory.cc54
-rw-r--r--third_party/libwebrtc/p2p/base/default_ice_transport_factory.h58
-rw-r--r--third_party/libwebrtc/p2p/base/dtls_transport.cc870
-rw-r--r--third_party/libwebrtc/p2p/base/dtls_transport.h264
-rw-r--r--third_party/libwebrtc/p2p/base/dtls_transport_factory.h40
-rw-r--r--third_party/libwebrtc/p2p/base/dtls_transport_internal.cc19
-rw-r--r--third_party/libwebrtc/p2p/base/dtls_transport_internal.h157
-rw-r--r--third_party/libwebrtc/p2p/base/dtls_transport_unittest.cc748
-rw-r--r--third_party/libwebrtc/p2p/base/fake_dtls_transport.h318
-rw-r--r--third_party/libwebrtc/p2p/base/fake_ice_transport.h446
-rw-r--r--third_party/libwebrtc/p2p/base/fake_packet_transport.h143
-rw-r--r--third_party/libwebrtc/p2p/base/fake_port_allocator.h279
-rw-r--r--third_party/libwebrtc/p2p/base/ice_agent_interface.h80
-rw-r--r--third_party/libwebrtc/p2p/base/ice_controller_factory_interface.h40
-rw-r--r--third_party/libwebrtc/p2p/base/ice_controller_interface.cc27
-rw-r--r--third_party/libwebrtc/p2p/base/ice_controller_interface.h138
-rw-r--r--third_party/libwebrtc/p2p/base/ice_credentials_iterator.cc38
-rw-r--r--third_party/libwebrtc/p2p/base/ice_credentials_iterator.h37
-rw-r--r--third_party/libwebrtc/p2p/base/ice_credentials_iterator_unittest.cc48
-rw-r--r--third_party/libwebrtc/p2p/base/ice_switch_reason.cc44
-rw-r--r--third_party/libwebrtc/p2p/base/ice_switch_reason.h38
-rw-r--r--third_party/libwebrtc/p2p/base/ice_transport_internal.cc140
-rw-r--r--third_party/libwebrtc/p2p/base/ice_transport_internal.h347
-rw-r--r--third_party/libwebrtc/p2p/base/mock_active_ice_controller.h89
-rw-r--r--third_party/libwebrtc/p2p/base/mock_async_resolver.h57
-rw-r--r--third_party/libwebrtc/p2p/base/mock_dns_resolving_packet_socket_factory.h53
-rw-r--r--third_party/libwebrtc/p2p/base/mock_ice_agent.h50
-rw-r--r--third_party/libwebrtc/p2p/base/mock_ice_controller.h90
-rw-r--r--third_party/libwebrtc/p2p/base/mock_ice_transport.h90
-rw-r--r--third_party/libwebrtc/p2p/base/p2p_constants.cc75
-rw-r--r--third_party/libwebrtc/p2p/base/p2p_constants.h114
-rw-r--r--third_party/libwebrtc/p2p/base/p2p_transport_channel.cc2636
-rw-r--r--third_party/libwebrtc/p2p/base/p2p_transport_channel.h590
-rw-r--r--third_party/libwebrtc/p2p/base/p2p_transport_channel_ice_field_trials.h77
-rw-r--r--third_party/libwebrtc/p2p/base/p2p_transport_channel_unittest.cc6630
-rw-r--r--third_party/libwebrtc/p2p/base/packet_transport_internal.cc27
-rw-r--r--third_party/libwebrtc/p2p/base/packet_transport_internal.h108
-rw-r--r--third_party/libwebrtc/p2p/base/port.cc969
-rw-r--r--third_party/libwebrtc/p2p/base/port.h540
-rw-r--r--third_party/libwebrtc/p2p/base/port_allocator.cc349
-rw-r--r--third_party/libwebrtc/p2p/base/port_allocator.h683
-rw-r--r--third_party/libwebrtc/p2p/base/port_allocator_unittest.cc371
-rw-r--r--third_party/libwebrtc/p2p/base/port_interface.cc23
-rw-r--r--third_party/libwebrtc/p2p/base/port_interface.h146
-rw-r--r--third_party/libwebrtc/p2p/base/port_unittest.cc3844
-rw-r--r--third_party/libwebrtc/p2p/base/pseudo_tcp.cc1432
-rw-r--r--third_party/libwebrtc/p2p/base/pseudo_tcp.h295
-rw-r--r--third_party/libwebrtc/p2p/base/pseudo_tcp_unittest.cc880
-rw-r--r--third_party/libwebrtc/p2p/base/regathering_controller.cc80
-rw-r--r--third_party/libwebrtc/p2p/base/regathering_controller.h97
-rw-r--r--third_party/libwebrtc/p2p/base/regathering_controller_unittest.cc189
-rw-r--r--third_party/libwebrtc/p2p/base/stun_port.cc700
-rw-r--r--third_party/libwebrtc/p2p/base/stun_port.h301
-rw-r--r--third_party/libwebrtc/p2p/base/stun_port_unittest.cc765
-rw-r--r--third_party/libwebrtc/p2p/base/stun_request.cc310
-rw-r--r--third_party/libwebrtc/p2p/base/stun_request.h162
-rw-r--r--third_party/libwebrtc/p2p/base/stun_request_unittest.cc219
-rw-r--r--third_party/libwebrtc/p2p/base/stun_server.cc104
-rw-r--r--third_party/libwebrtc/p2p/base/stun_server.h72
-rw-r--r--third_party/libwebrtc/p2p/base/stun_server_unittest.cc145
-rw-r--r--third_party/libwebrtc/p2p/base/tcp_port.cc620
-rw-r--r--third_party/libwebrtc/p2p/base/tcp_port.h203
-rw-r--r--third_party/libwebrtc/p2p/base/tcp_port_unittest.cc259
-rw-r--r--third_party/libwebrtc/p2p/base/test_stun_server.cc37
-rw-r--r--third_party/libwebrtc/p2p/base/test_stun_server.h45
-rw-r--r--third_party/libwebrtc/p2p/base/test_turn_customizer.h59
-rw-r--r--third_party/libwebrtc/p2p/base/test_turn_server.h161
-rw-r--r--third_party/libwebrtc/p2p/base/transport_description.cc196
-rw-r--r--third_party/libwebrtc/p2p/base/transport_description.h154
-rw-r--r--third_party/libwebrtc/p2p/base/transport_description_factory.cc151
-rw-r--r--third_party/libwebrtc/p2p/base/transport_description_factory.h91
-rw-r--r--third_party/libwebrtc/p2p/base/transport_description_factory_unittest.cc406
-rw-r--r--third_party/libwebrtc/p2p/base/transport_description_unittest.cc58
-rw-r--r--third_party/libwebrtc/p2p/base/transport_info.h42
-rw-r--r--third_party/libwebrtc/p2p/base/turn_port.cc1900
-rw-r--r--third_party/libwebrtc/p2p/base/turn_port.h375
-rw-r--r--third_party/libwebrtc/p2p/base/turn_port_unittest.cc2026
-rw-r--r--third_party/libwebrtc/p2p/base/turn_server.cc881
-rw-r--r--third_party/libwebrtc/p2p/base/turn_server.h373
-rw-r--r--third_party/libwebrtc/p2p/base/turn_server_unittest.cc65
-rw-r--r--third_party/libwebrtc/p2p/base/udp_port.h17
-rw-r--r--third_party/libwebrtc/p2p/base/wrapping_active_ice_controller.cc253
-rw-r--r--third_party/libwebrtc/p2p/base/wrapping_active_ice_controller.h97
-rw-r--r--third_party/libwebrtc/p2p/base/wrapping_active_ice_controller_unittest.cc315
101 files changed, 41039 insertions, 0 deletions
diff --git a/third_party/libwebrtc/p2p/base/active_ice_controller_factory_interface.h b/third_party/libwebrtc/p2p/base/active_ice_controller_factory_interface.h
new file mode 100644
index 0000000000..6a47f2253f
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/active_ice_controller_factory_interface.h
@@ -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_
diff --git a/third_party/libwebrtc/p2p/base/active_ice_controller_interface.h b/third_party/libwebrtc/p2p/base/active_ice_controller_interface.h
new file mode 100644
index 0000000000..e54838ee64
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/active_ice_controller_interface.h
@@ -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_
diff --git a/third_party/libwebrtc/p2p/base/async_stun_tcp_socket.cc b/third_party/libwebrtc/p2p/base/async_stun_tcp_socket.cc
new file mode 100644
index 0000000000..5f8f07227f
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/async_stun_tcp_socket.cc
@@ -0,0 +1,156 @@
+/*
+ * 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 "api/transport/stun.h"
+#include "rtc_base/byte_order.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/network/sent_packet.h"
+#include "rtc_base/third_party/sigslot/sigslot.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);
+}
+
+void AsyncStunTCPSocket::ProcessInput(char* data, size_t* len) {
+ 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 |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ while (true) {
+ // We need at least 4 bytes to read the STUN or ChannelData packet length.
+ if (*len < kPacketLenOffset + kPacketLenSize)
+ return;
+
+ int pad_bytes;
+ size_t expected_pkt_len = GetExpectedLength(data, *len, &pad_bytes);
+ size_t actual_length = expected_pkt_len + pad_bytes;
+
+ if (*len < actual_length) {
+ return;
+ }
+
+ SignalReadPacket(this, data, expected_pkt_len, remote_addr,
+ rtc::TimeMicros());
+
+ *len -= actual_length;
+ if (*len > 0) {
+ memmove(data, data + actual_length, *len);
+ }
+ }
+}
+
+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
diff --git a/third_party/libwebrtc/p2p/base/async_stun_tcp_socket.h b/third_party/libwebrtc/p2p/base/async_stun_tcp_socket.h
new file mode 100644
index 0000000000..f0df42b52a
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/async_stun_tcp_socket.h
@@ -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;
+ void ProcessInput(char* data, size_t* len) 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_
diff --git a/third_party/libwebrtc/p2p/base/async_stun_tcp_socket_unittest.cc b/third_party/libwebrtc/p2p/base/async_stun_tcp_socket_unittest.cc
new file mode 100644
index 0000000000..72d6a7fde0
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/async_stun_tcp_socket_unittest.cc
@@ -0,0 +1,288 @@
+/*
+ * 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 <stdint.h>
+#include <string.h>
+
+#include <list>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "absl/memory/memory.h"
+#include "rtc_base/network/sent_packet.h"
+#include "rtc_base/socket.h"
+#include "rtc_base/third_party/sigslot/sigslot.h"
+#include "rtc_base/thread.h"
+#include "rtc_base/virtual_socket_server.h"
+#include "test/gtest.h"
+
+namespace cricket {
+
+static unsigned char kStunMessageWithZeroLength[] = {
+ 0x00, 0x01, 0x00, 0x00, // length of 0 (last 2 bytes)
+ 0x21, 0x12, 0xA4, 0x42, '0', '1', '2', '3',
+ '4', '5', '6', '7', '8', '9', 'a', 'b',
+};
+
+static unsigned char kTurnChannelDataMessageWithZeroLength[] = {
+ 0x40, 0x00, 0x00, 0x00, // length of 0 (last 2 bytes)
+};
+
+static unsigned char kTurnChannelDataMessage[] = {
+ 0x40, 0x00, 0x00, 0x10, 0x21, 0x12, 0xA4, 0x42, '0', '1',
+ '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b',
+};
+
+static unsigned char kStunMessageWithInvalidLength[] = {
+ 0x00, 0x01, 0x00, 0x10, 0x21, 0x12, 0xA4, 0x42, '0', '1',
+ '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b',
+};
+
+static unsigned char kTurnChannelDataMessageWithInvalidLength[] = {
+ 0x80, 0x00, 0x00, 0x20, 0x21, 0x12, 0xA4, 0x42, '0', '1',
+ '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b',
+};
+
+static unsigned char kTurnChannelDataMessageWithOddLength[] = {
+ 0x40, 0x00, 0x00, 0x05, 0x21, 0x12, 0xA4, 0x42, '0',
+};
+
+static const rtc::SocketAddress kClientAddr("11.11.11.11", 0);
+static const rtc::SocketAddress kServerAddr("22.22.22.22", 0);
+
+class AsyncStunServerTCPSocket : public rtc::AsyncTcpListenSocket {
+ public:
+ explicit AsyncStunServerTCPSocket(std::unique_ptr<rtc::Socket> socket)
+ : AsyncTcpListenSocket(std::move(socket)) {}
+ void HandleIncomingConnection(rtc::Socket* socket) override {
+ SignalNewConnection(this, new AsyncStunTCPSocket(socket));
+ }
+};
+
+class AsyncStunTCPSocketTest : public ::testing::Test,
+ public sigslot::has_slots<> {
+ protected:
+ AsyncStunTCPSocketTest()
+ : vss_(new rtc::VirtualSocketServer()), thread_(vss_.get()) {}
+
+ virtual void SetUp() { CreateSockets(); }
+
+ void CreateSockets() {
+ std::unique_ptr<rtc::Socket> server =
+ absl::WrapUnique(vss_->CreateSocket(kServerAddr.family(), SOCK_STREAM));
+ server->Bind(kServerAddr);
+ listen_socket_ =
+ std::make_unique<AsyncStunServerTCPSocket>(std::move(server));
+ listen_socket_->SignalNewConnection.connect(
+ this, &AsyncStunTCPSocketTest::OnNewConnection);
+
+ rtc::Socket* client = vss_->CreateSocket(kClientAddr.family(), SOCK_STREAM);
+ send_socket_.reset(AsyncStunTCPSocket::Create(
+ client, kClientAddr, listen_socket_->GetLocalAddress()));
+ send_socket_->SignalSentPacket.connect(
+ this, &AsyncStunTCPSocketTest::OnSentPacket);
+ ASSERT_TRUE(send_socket_.get() != NULL);
+ vss_->ProcessMessagesUntilIdle();
+ }
+
+ void OnReadPacket(rtc::AsyncPacketSocket* socket,
+ const char* data,
+ size_t len,
+ const rtc::SocketAddress& remote_addr,
+ const int64_t& /* packet_time_us */) {
+ recv_packets_.push_back(std::string(data, len));
+ }
+
+ void OnSentPacket(rtc::AsyncPacketSocket* socket,
+ const rtc::SentPacket& packet) {
+ ++sent_packets_;
+ }
+
+ void OnNewConnection(rtc::AsyncListenSocket* /*server*/,
+ rtc::AsyncPacketSocket* new_socket) {
+ recv_socket_ = absl::WrapUnique(new_socket);
+ new_socket->SignalReadPacket.connect(this,
+ &AsyncStunTCPSocketTest::OnReadPacket);
+ }
+
+ bool Send(const void* data, size_t len) {
+ rtc::PacketOptions options;
+ int ret =
+ send_socket_->Send(reinterpret_cast<const char*>(data), len, options);
+ vss_->ProcessMessagesUntilIdle();
+ return (ret == static_cast<int>(len));
+ }
+
+ bool CheckData(const void* data, int len) {
+ bool ret = false;
+ if (recv_packets_.size()) {
+ std::string packet = recv_packets_.front();
+ recv_packets_.pop_front();
+ ret = (memcmp(data, packet.c_str(), len) == 0);
+ }
+ return ret;
+ }
+
+ std::unique_ptr<rtc::VirtualSocketServer> vss_;
+ rtc::AutoSocketServerThread thread_;
+ std::unique_ptr<AsyncStunTCPSocket> send_socket_;
+ std::unique_ptr<rtc::AsyncListenSocket> listen_socket_;
+ std::unique_ptr<rtc::AsyncPacketSocket> recv_socket_;
+ std::list<std::string> recv_packets_;
+ int sent_packets_ = 0;
+};
+
+// Testing a stun packet sent/recv properly.
+TEST_F(AsyncStunTCPSocketTest, TestSingleStunPacket) {
+ EXPECT_TRUE(
+ Send(kStunMessageWithZeroLength, sizeof(kStunMessageWithZeroLength)));
+ EXPECT_EQ(1u, recv_packets_.size());
+ EXPECT_TRUE(CheckData(kStunMessageWithZeroLength,
+ sizeof(kStunMessageWithZeroLength)));
+}
+
+// Verify sending multiple packets.
+TEST_F(AsyncStunTCPSocketTest, TestMultipleStunPackets) {
+ EXPECT_TRUE(
+ Send(kStunMessageWithZeroLength, sizeof(kStunMessageWithZeroLength)));
+ EXPECT_TRUE(
+ Send(kStunMessageWithZeroLength, sizeof(kStunMessageWithZeroLength)));
+ EXPECT_TRUE(
+ Send(kStunMessageWithZeroLength, sizeof(kStunMessageWithZeroLength)));
+ EXPECT_TRUE(
+ Send(kStunMessageWithZeroLength, sizeof(kStunMessageWithZeroLength)));
+ EXPECT_EQ(4u, recv_packets_.size());
+}
+
+// Verifying TURN channel data message with zero length.
+TEST_F(AsyncStunTCPSocketTest, TestTurnChannelDataWithZeroLength) {
+ EXPECT_TRUE(Send(kTurnChannelDataMessageWithZeroLength,
+ sizeof(kTurnChannelDataMessageWithZeroLength)));
+ EXPECT_EQ(1u, recv_packets_.size());
+ EXPECT_TRUE(CheckData(kTurnChannelDataMessageWithZeroLength,
+ sizeof(kTurnChannelDataMessageWithZeroLength)));
+}
+
+// Verifying TURN channel data message.
+TEST_F(AsyncStunTCPSocketTest, TestTurnChannelData) {
+ EXPECT_TRUE(Send(kTurnChannelDataMessage, sizeof(kTurnChannelDataMessage)));
+ EXPECT_EQ(1u, recv_packets_.size());
+ EXPECT_TRUE(
+ CheckData(kTurnChannelDataMessage, sizeof(kTurnChannelDataMessage)));
+}
+
+// Verifying TURN channel messages which needs padding handled properly.
+TEST_F(AsyncStunTCPSocketTest, TestTurnChannelDataPadding) {
+ EXPECT_TRUE(Send(kTurnChannelDataMessageWithOddLength,
+ sizeof(kTurnChannelDataMessageWithOddLength)));
+ EXPECT_EQ(1u, recv_packets_.size());
+ EXPECT_TRUE(CheckData(kTurnChannelDataMessageWithOddLength,
+ sizeof(kTurnChannelDataMessageWithOddLength)));
+}
+
+// Verifying stun message with invalid length.
+TEST_F(AsyncStunTCPSocketTest, TestStunInvalidLength) {
+ EXPECT_FALSE(Send(kStunMessageWithInvalidLength,
+ sizeof(kStunMessageWithInvalidLength)));
+ EXPECT_EQ(0u, recv_packets_.size());
+
+ // Modify the message length to larger value.
+ kStunMessageWithInvalidLength[2] = 0xFF;
+ kStunMessageWithInvalidLength[3] = 0xFF;
+ EXPECT_FALSE(Send(kStunMessageWithInvalidLength,
+ sizeof(kStunMessageWithInvalidLength)));
+
+ // Modify the message length to smaller value.
+ kStunMessageWithInvalidLength[2] = 0x00;
+ kStunMessageWithInvalidLength[3] = 0x01;
+ EXPECT_FALSE(Send(kStunMessageWithInvalidLength,
+ sizeof(kStunMessageWithInvalidLength)));
+}
+
+// Verifying TURN channel data message with invalid length.
+TEST_F(AsyncStunTCPSocketTest, TestTurnChannelDataWithInvalidLength) {
+ EXPECT_FALSE(Send(kTurnChannelDataMessageWithInvalidLength,
+ sizeof(kTurnChannelDataMessageWithInvalidLength)));
+ // Modify the length to larger value.
+ kTurnChannelDataMessageWithInvalidLength[2] = 0xFF;
+ kTurnChannelDataMessageWithInvalidLength[3] = 0xF0;
+ EXPECT_FALSE(Send(kTurnChannelDataMessageWithInvalidLength,
+ sizeof(kTurnChannelDataMessageWithInvalidLength)));
+
+ // Modify the length to smaller value.
+ kTurnChannelDataMessageWithInvalidLength[2] = 0x00;
+ kTurnChannelDataMessageWithInvalidLength[3] = 0x00;
+ EXPECT_FALSE(Send(kTurnChannelDataMessageWithInvalidLength,
+ sizeof(kTurnChannelDataMessageWithInvalidLength)));
+}
+
+// Verifying a small buffer handled (dropped) properly. This will be
+// a common one for both stun and turn.
+TEST_F(AsyncStunTCPSocketTest, TestTooSmallMessageBuffer) {
+ char data[1];
+ EXPECT_FALSE(Send(data, sizeof(data)));
+}
+
+// Verifying a legal large turn message.
+TEST_F(AsyncStunTCPSocketTest, TestMaximumSizeTurnPacket) {
+ unsigned char packet[65539];
+ packet[0] = 0x40;
+ packet[1] = 0x00;
+ packet[2] = 0xFF;
+ packet[3] = 0xFF;
+ EXPECT_TRUE(Send(packet, sizeof(packet)));
+}
+
+// Verifying a legal large stun message.
+TEST_F(AsyncStunTCPSocketTest, TestMaximumSizeStunPacket) {
+ unsigned char packet[65552];
+ packet[0] = 0x00;
+ packet[1] = 0x01;
+ packet[2] = 0xFF;
+ packet[3] = 0xFC;
+ EXPECT_TRUE(Send(packet, sizeof(packet)));
+}
+
+// Test that a turn message is sent completely even if it exceeds the socket
+// send buffer capacity.
+TEST_F(AsyncStunTCPSocketTest, TestWithSmallSendBuffer) {
+ vss_->set_send_buffer_capacity(1);
+ Send(kTurnChannelDataMessageWithOddLength,
+ sizeof(kTurnChannelDataMessageWithOddLength));
+ EXPECT_EQ(1u, recv_packets_.size());
+ EXPECT_TRUE(CheckData(kTurnChannelDataMessageWithOddLength,
+ sizeof(kTurnChannelDataMessageWithOddLength)));
+}
+
+// Test that SignalSentPacket is fired when a packet is sent.
+TEST_F(AsyncStunTCPSocketTest, SignalSentPacketFiredWhenPacketSent) {
+ ASSERT_TRUE(
+ Send(kStunMessageWithZeroLength, sizeof(kStunMessageWithZeroLength)));
+ EXPECT_EQ(1, sent_packets_);
+ // Send another packet for good measure.
+ ASSERT_TRUE(
+ Send(kStunMessageWithZeroLength, sizeof(kStunMessageWithZeroLength)));
+ EXPECT_EQ(2, sent_packets_);
+}
+
+// Test that SignalSentPacket isn't fired when a packet isn't sent (for
+// example, because it's invalid).
+TEST_F(AsyncStunTCPSocketTest, SignalSentPacketNotFiredWhenPacketNotSent) {
+ // Attempt to send a packet that's too small; since it isn't sent,
+ // SignalSentPacket shouldn't fire.
+ char data[1];
+ ASSERT_FALSE(Send(data, sizeof(data)));
+ EXPECT_EQ(0, sent_packets_);
+}
+
+} // namespace cricket
diff --git a/third_party/libwebrtc/p2p/base/basic_async_resolver_factory.cc b/third_party/libwebrtc/p2p/base/basic_async_resolver_factory.cc
new file mode 100644
index 0000000000..3fdf75b12f
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/basic_async_resolver_factory.cc
@@ -0,0 +1,53 @@
+/*
+ * 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 "api/wrapping_async_dns_resolver.h"
+#include "rtc_base/async_resolver.h"
+#include "rtc_base/logging.h"
+
+namespace webrtc {
+
+rtc::AsyncResolverInterface* BasicAsyncResolverFactory::Create() {
+ return new rtc::AsyncResolver();
+}
+
+
+std::unique_ptr<webrtc::AsyncDnsResolverInterface>
+WrappingAsyncDnsResolverFactory::Create() {
+ return std::make_unique<WrappingAsyncDnsResolver>(wrapped_factory_->Create());
+}
+
+std::unique_ptr<webrtc::AsyncDnsResolverInterface>
+WrappingAsyncDnsResolverFactory::CreateAndResolve(
+ const rtc::SocketAddress& addr,
+ std::function<void()> callback) {
+ std::unique_ptr<webrtc::AsyncDnsResolverInterface> resolver = Create();
+ resolver->Start(addr, std::move(callback));
+ return resolver;
+}
+
+std::unique_ptr<webrtc::AsyncDnsResolverInterface>
+WrappingAsyncDnsResolverFactory::CreateAndResolve(
+ const rtc::SocketAddress& addr,
+ int family,
+ std::function<void()> callback) {
+ std::unique_ptr<webrtc::AsyncDnsResolverInterface> resolver = Create();
+ resolver->Start(addr, family, std::move(callback));
+ return resolver;
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/p2p/base/basic_async_resolver_factory.h b/third_party/libwebrtc/p2p/base/basic_async_resolver_factory.h
new file mode 100644
index 0000000000..9a0ba1ab28
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/basic_async_resolver_factory.h
@@ -0,0 +1,62 @@
+/*
+ * 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"
+#include "api/async_resolver_factory.h"
+#include "rtc_base/async_resolver_interface.h"
+
+namespace webrtc {
+
+class BasicAsyncResolverFactory final : public AsyncResolverFactory {
+ public:
+ rtc::AsyncResolverInterface* Create() override;
+};
+
+// This class wraps a factory using the older webrtc::AsyncResolverFactory API,
+// and produces webrtc::AsyncDnsResolver objects that contain an
+// rtc::AsyncResolver object.
+class WrappingAsyncDnsResolverFactory final
+ : public AsyncDnsResolverFactoryInterface {
+ public:
+ explicit WrappingAsyncDnsResolverFactory(
+ std::unique_ptr<AsyncResolverFactory> wrapped_factory)
+ : owned_factory_(std::move(wrapped_factory)),
+ wrapped_factory_(owned_factory_.get()) {}
+
+ explicit WrappingAsyncDnsResolverFactory(
+ AsyncResolverFactory* non_owned_factory)
+ : wrapped_factory_(non_owned_factory) {}
+
+ std::unique_ptr<webrtc::AsyncDnsResolverInterface> CreateAndResolve(
+ const rtc::SocketAddress& addr,
+ std::function<void()> callback) override;
+
+ std::unique_ptr<webrtc::AsyncDnsResolverInterface> CreateAndResolve(
+ const rtc::SocketAddress& addr,
+ int family,
+ std::function<void()> callback) override;
+
+ std::unique_ptr<webrtc::AsyncDnsResolverInterface> Create() override;
+
+ private:
+ const std::unique_ptr<AsyncResolverFactory> owned_factory_;
+ AsyncResolverFactory* const wrapped_factory_;
+};
+
+} // namespace webrtc
+
+#endif // P2P_BASE_BASIC_ASYNC_RESOLVER_FACTORY_H_
diff --git a/third_party/libwebrtc/p2p/base/basic_async_resolver_factory_unittest.cc b/third_party/libwebrtc/p2p/base/basic_async_resolver_factory_unittest.cc
new file mode 100644
index 0000000000..77b97e75e6
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/basic_async_resolver_factory_unittest.cc
@@ -0,0 +1,111 @@
+/*
+ * 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 "api/test/mock_async_dns_resolver.h"
+#include "p2p/base/mock_async_resolver.h"
+#include "rtc_base/async_resolver.h"
+#include "rtc_base/gunit.h"
+#include "rtc_base/socket_address.h"
+#include "rtc_base/third_party/sigslot/sigslot.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+#include "test/testsupport/rtc_expect_death.h"
+
+namespace webrtc {
+
+class BasicAsyncResolverFactoryTest : public ::testing::Test,
+ public sigslot::has_slots<> {
+ public:
+ void TestCreate() {
+ BasicAsyncResolverFactory factory;
+ rtc::AsyncResolverInterface* resolver = factory.Create();
+ ASSERT_TRUE(resolver);
+ resolver->SignalDone.connect(
+ this, &BasicAsyncResolverFactoryTest::SetAddressResolved);
+
+ rtc::SocketAddress address("", 0);
+ resolver->Start(address);
+ ASSERT_TRUE_WAIT(address_resolved_, 10000 /*ms*/);
+ resolver->Destroy(false);
+ }
+
+ void SetAddressResolved(rtc::AsyncResolverInterface* resolver) {
+ address_resolved_ = true;
+ }
+
+ private:
+ bool address_resolved_ = false;
+};
+
+// This test is primarily intended to let tools check that the created resolver
+// doesn't leak.
+TEST_F(BasicAsyncResolverFactoryTest, TestCreate) {
+ rtc::AutoThread main_thread;
+ TestCreate();
+}
+
+TEST(WrappingAsyncDnsResolverFactoryTest, TestCreateAndResolve) {
+ rtc::AutoThread main_thread;
+ WrappingAsyncDnsResolverFactory factory(
+ std::make_unique<BasicAsyncResolverFactory>());
+
+ std::unique_ptr<AsyncDnsResolverInterface> resolver(factory.Create());
+ ASSERT_TRUE(resolver);
+
+ bool address_resolved = false;
+ rtc::SocketAddress address("", 0);
+ resolver->Start(address, [&address_resolved]() { address_resolved = true; });
+ ASSERT_TRUE_WAIT(address_resolved, 10000 /*ms*/);
+ resolver.reset();
+}
+
+TEST(WrappingAsyncDnsResolverFactoryTest, WrapOtherResolver) {
+ rtc::AutoThread main_thread;
+ BasicAsyncResolverFactory non_owned_factory;
+ WrappingAsyncDnsResolverFactory factory(&non_owned_factory);
+ std::unique_ptr<AsyncDnsResolverInterface> resolver(factory.Create());
+ ASSERT_TRUE(resolver);
+
+ bool address_resolved = false;
+ rtc::SocketAddress address("", 0);
+ resolver->Start(address, [&address_resolved]() { address_resolved = true; });
+ ASSERT_TRUE_WAIT(address_resolved, 10000 /*ms*/);
+ resolver.reset();
+}
+
+#if GTEST_HAS_DEATH_TEST && defined(WEBRTC_LINUX)
+// Tests that the prohibition against deleting the resolver from the callback
+// is enforced. This is required by the use of sigslot in the wrapped resolver.
+// Checking the error message fails on a number of platforms, so run this
+// test only on the platforms where it works.
+void CallResolver(WrappingAsyncDnsResolverFactory& factory) {
+ rtc::SocketAddress address("", 0);
+ std::unique_ptr<AsyncDnsResolverInterface> resolver(factory.Create());
+ resolver->Start(address, [&resolver]() { resolver.reset(); });
+ WAIT(!resolver.get(), 10000 /*ms*/);
+}
+
+TEST(WrappingAsyncDnsResolverFactoryDeathTest, DestroyResolverInCallback) {
+ rtc::AutoThread main_thread;
+ // TODO(bugs.webrtc.org/12652): Rewrite as death test in loop style when it
+ // works.
+ WrappingAsyncDnsResolverFactory factory(
+ std::make_unique<BasicAsyncResolverFactory>());
+
+ // Since EXPECT_DEATH is thread sensitive, and the resolver creates a thread,
+ // we wrap the whole creation section in EXPECT_DEATH.
+ RTC_EXPECT_DEATH(CallResolver(factory),
+ "Check failed: !within_resolve_result_");
+}
+#endif
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/p2p/base/basic_ice_controller.cc b/third_party/libwebrtc/p2p/base/basic_ice_controller.cc
new file mode 100644
index 0000000000..55f187cb9a
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/basic_ice_controller.cc
@@ -0,0 +1,855 @@
+/*
+ * 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().type() == cricket::RELAY_PORT_TYPE &&
+ conn->remote_candidate().type() == cricket::RELAY_PORT_TYPE;
+}
+
+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";
+ 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().type() == RELAY_PORT_TYPE &&
+ (conn->remote_candidate().type() == RELAY_PORT_TYPE ||
+ conn->remote_candidate().type() == PRFLX_PORT_TYPE));
+}
+
+// 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
diff --git a/third_party/libwebrtc/p2p/base/basic_ice_controller.h b/third_party/libwebrtc/p2p/base/basic_ice_controller.h
new file mode 100644
index 0000000000..b941a0dd7e
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/basic_ice_controller.h
@@ -0,0 +1,165 @@
+/*
+ * 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"
+#include "p2p/base/p2p_transport_channel.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*> 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_
diff --git a/third_party/libwebrtc/p2p/base/basic_packet_socket_factory.cc b/third_party/libwebrtc/p2p/base/basic_packet_socket_factory.cc
new file mode 100644
index 0000000000..5bc02dd0f0
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/basic_packet_socket_factory.cc
@@ -0,0 +1,210 @@
+/*
+ * 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 "api/wrapping_async_dns_resolver.h"
+#include "p2p/base/async_stun_tcp_socket.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 using a proxy, wrap the socket in a proxy socket.
+ if (proxy_info.type == PROXY_SOCKS5) {
+ socket = new AsyncSocksProxySocket(
+ socket, proxy_info.address, proxy_info.username, proxy_info.password);
+ } else 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;
+}
+
+AsyncResolverInterface* BasicPacketSocketFactory::CreateAsyncResolver() {
+ return new AsyncResolver();
+}
+
+std::unique_ptr<webrtc::AsyncDnsResolverInterface>
+BasicPacketSocketFactory::CreateAsyncDnsResolver() {
+ return std::make_unique<webrtc::WrappingAsyncDnsResolver>(
+ new AsyncResolver());
+}
+
+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
diff --git a/third_party/libwebrtc/p2p/base/basic_packet_socket_factory.h b/third_party/libwebrtc/p2p/base/basic_packet_socket_factory.h
new file mode 100644
index 0000000000..396a8ba4eb
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/basic_packet_socket_factory.h
@@ -0,0 +1,69 @@
+/*
+ * 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;
+
+ // TODO(bugs.webrtc.org/12598) Remove when downstream stops using it.
+ ABSL_DEPRECATED("Use CreateAsyncDnsResolver")
+ AsyncResolverInterface* CreateAsyncResolver() 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_
diff --git a/third_party/libwebrtc/p2p/base/candidate_pair_interface.h b/third_party/libwebrtc/p2p/base/candidate_pair_interface.h
new file mode 100644
index 0000000000..2b68fd7ea9
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/candidate_pair_interface.h
@@ -0,0 +1,28 @@
+/*
+ * 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;
+};
+
+} // namespace cricket
+
+#endif // P2P_BASE_CANDIDATE_PAIR_INTERFACE_H_
diff --git a/third_party/libwebrtc/p2p/base/connection.cc b/third_party/libwebrtc/p2p/base/connection.cc
new file mode 100644
index 0000000000..c5e6993c87
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/connection.cc
@@ -0,0 +1,1720 @@
+/*
+ * 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.h"
+
+#include <math.h>
+
+#include <algorithm>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "absl/algorithm/container.h"
+#include "absl/strings/escaping.h"
+#include "absl/strings/match.h"
+#include "absl/strings/string_view.h"
+#include "p2p/base/port_allocator.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/crc32.h"
+#include "rtc_base/helpers.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/mdns_responder_interface.h"
+#include "rtc_base/message_digest.h"
+#include "rtc_base/network.h"
+#include "rtc_base/numerics/safe_minmax.h"
+#include "rtc_base/string_encode.h"
+#include "rtc_base/string_utils.h"
+#include "rtc_base/strings/string_builder.h"
+#include "rtc_base/third_party/base64/base64.h"
+
+namespace cricket {
+namespace {
+
+// Determines whether we have seen at least the given maximum number of
+// pings fail to have a response.
+inline bool TooManyFailures(
+ const std::vector<Connection::SentPing>& pings_since_last_response,
+ uint32_t maximum_failures,
+ int rtt_estimate,
+ int64_t now) {
+ // If we haven't sent that many pings, then we can't have failed that many.
+ if (pings_since_last_response.size() < maximum_failures)
+ return false;
+
+ // Check if the window in which we would expect a response to the ping has
+ // already elapsed.
+ int64_t expected_response_time =
+ pings_since_last_response[maximum_failures - 1].sent_time + rtt_estimate;
+ return now > expected_response_time;
+}
+
+// Determines whether we have gone too long without seeing any response.
+inline bool TooLongWithoutResponse(
+ const std::vector<Connection::SentPing>& pings_since_last_response,
+ int64_t maximum_time,
+ int64_t now) {
+ if (pings_since_last_response.size() == 0)
+ return false;
+
+ auto first = pings_since_last_response[0];
+ return now > (first.sent_time + maximum_time);
+}
+
+// Helper methods for converting string values of log description fields to
+// enum.
+webrtc::IceCandidateType GetCandidateTypeByString(absl::string_view type) {
+ if (type == LOCAL_PORT_TYPE) {
+ return webrtc::IceCandidateType::kLocal;
+ } else if (type == STUN_PORT_TYPE) {
+ return webrtc::IceCandidateType::kStun;
+ } else if (type == PRFLX_PORT_TYPE) {
+ return webrtc::IceCandidateType::kPrflx;
+ } else if (type == RELAY_PORT_TYPE) {
+ return webrtc::IceCandidateType::kRelay;
+ }
+ return webrtc::IceCandidateType::kUnknown;
+}
+
+webrtc::IceCandidatePairProtocol GetProtocolByString(
+ absl::string_view protocol) {
+ if (protocol == UDP_PROTOCOL_NAME) {
+ return webrtc::IceCandidatePairProtocol::kUdp;
+ } else if (protocol == TCP_PROTOCOL_NAME) {
+ return webrtc::IceCandidatePairProtocol::kTcp;
+ } else if (protocol == SSLTCP_PROTOCOL_NAME) {
+ return webrtc::IceCandidatePairProtocol::kSsltcp;
+ } else if (protocol == TLS_PROTOCOL_NAME) {
+ return webrtc::IceCandidatePairProtocol::kTls;
+ }
+ return webrtc::IceCandidatePairProtocol::kUnknown;
+}
+
+webrtc::IceCandidatePairAddressFamily GetAddressFamilyByInt(
+ int address_family) {
+ if (address_family == AF_INET) {
+ return webrtc::IceCandidatePairAddressFamily::kIpv4;
+ } else if (address_family == AF_INET6) {
+ return webrtc::IceCandidatePairAddressFamily::kIpv6;
+ }
+ return webrtc::IceCandidatePairAddressFamily::kUnknown;
+}
+
+webrtc::IceCandidateNetworkType ConvertNetworkType(rtc::AdapterType type) {
+ switch (type) {
+ case rtc::ADAPTER_TYPE_ETHERNET:
+ return webrtc::IceCandidateNetworkType::kEthernet;
+ case rtc::ADAPTER_TYPE_LOOPBACK:
+ return webrtc::IceCandidateNetworkType::kLoopback;
+ case rtc::ADAPTER_TYPE_WIFI:
+ return webrtc::IceCandidateNetworkType::kWifi;
+ case rtc::ADAPTER_TYPE_VPN:
+ return webrtc::IceCandidateNetworkType::kVpn;
+ case rtc::ADAPTER_TYPE_CELLULAR:
+ case rtc::ADAPTER_TYPE_CELLULAR_2G:
+ case rtc::ADAPTER_TYPE_CELLULAR_3G:
+ case rtc::ADAPTER_TYPE_CELLULAR_4G:
+ case rtc::ADAPTER_TYPE_CELLULAR_5G:
+ return webrtc::IceCandidateNetworkType::kCellular;
+ default:
+ return webrtc::IceCandidateNetworkType::kUnknown;
+ }
+}
+
+// When we don't have any RTT data, we have to pick something reasonable. We
+// use a large value just in case the connection is really slow.
+const int DEFAULT_RTT = 3000; // 3 seconds
+
+// We will restrict RTT estimates (when used for determining state) to be
+// within a reasonable range.
+const int MINIMUM_RTT = 100; // 0.1 seconds
+const int MAXIMUM_RTT = 60000; // 60 seconds
+
+const int DEFAULT_RTT_ESTIMATE_HALF_TIME_MS = 500;
+
+// Computes our estimate of the RTT given the current estimate.
+inline int ConservativeRTTEstimate(int rtt) {
+ return rtc::SafeClamp(2 * rtt, MINIMUM_RTT, MAXIMUM_RTT);
+}
+
+// Weighting of the old rtt value to new data.
+const int RTT_RATIO = 3; // 3 : 1
+
+constexpr int64_t kMinExtraPingDelayMs = 100;
+
+// Default field trials.
+const IceFieldTrials kDefaultFieldTrials;
+
+constexpr int kSupportGoogPingVersionRequestIndex = static_cast<int>(
+ IceGoogMiscInfoBindingRequestAttributeIndex::SUPPORT_GOOG_PING_VERSION);
+
+constexpr int kSupportGoogPingVersionResponseIndex = static_cast<int>(
+ IceGoogMiscInfoBindingResponseAttributeIndex::SUPPORT_GOOG_PING_VERSION);
+
+} // namespace
+
+// A ConnectionRequest is a STUN binding used to determine writability.
+class Connection::ConnectionRequest : public StunRequest {
+ public:
+ ConnectionRequest(StunRequestManager& manager,
+ Connection* connection,
+ std::unique_ptr<IceMessage> message);
+ void OnResponse(StunMessage* response) override;
+ void OnErrorResponse(StunMessage* response) override;
+ void OnTimeout() override;
+ void OnSent() override;
+ int resend_delay() override;
+
+ private:
+ Connection* const connection_;
+};
+
+Connection::ConnectionRequest::ConnectionRequest(
+ StunRequestManager& manager,
+ Connection* connection,
+ std::unique_ptr<IceMessage> message)
+ : StunRequest(manager, std::move(message)), connection_(connection) {}
+
+void Connection::ConnectionRequest::OnResponse(StunMessage* response) {
+ RTC_DCHECK_RUN_ON(connection_->network_thread_);
+ connection_->OnConnectionRequestResponse(this, response);
+}
+
+void Connection::ConnectionRequest::OnErrorResponse(StunMessage* response) {
+ RTC_DCHECK_RUN_ON(connection_->network_thread_);
+ connection_->OnConnectionRequestErrorResponse(this, response);
+}
+
+void Connection::ConnectionRequest::OnTimeout() {
+ RTC_DCHECK_RUN_ON(connection_->network_thread_);
+ connection_->OnConnectionRequestTimeout(this);
+}
+
+void Connection::ConnectionRequest::OnSent() {
+ RTC_DCHECK_RUN_ON(connection_->network_thread_);
+ connection_->OnConnectionRequestSent(this);
+ // Each request is sent only once. After a single delay , the request will
+ // time out.
+ set_timed_out();
+}
+
+int Connection::ConnectionRequest::resend_delay() {
+ return CONNECTION_RESPONSE_TIMEOUT;
+}
+
+Connection::Connection(rtc::WeakPtr<Port> port,
+ size_t index,
+ const Candidate& remote_candidate)
+ : network_thread_(port->thread()),
+ id_(rtc::CreateRandomId()),
+ port_(std::move(port)),
+ local_candidate_(port_->Candidates()[index]),
+ remote_candidate_(remote_candidate),
+ recv_rate_tracker_(100, 10u),
+ send_rate_tracker_(100, 10u),
+ write_state_(STATE_WRITE_INIT),
+ receiving_(false),
+ connected_(true),
+ pruned_(false),
+ use_candidate_attr_(false),
+ requests_(port_->thread(),
+ [this](const void* data, size_t size, StunRequest* request) {
+ OnSendStunPacket(data, size, request);
+ }),
+ rtt_(DEFAULT_RTT),
+ last_ping_sent_(0),
+ last_ping_received_(0),
+ last_data_received_(0),
+ last_ping_response_received_(0),
+ state_(IceCandidatePairState::WAITING),
+ time_created_ms_(rtc::TimeMillis()),
+ delta_internal_unix_epoch_ms_(rtc::TimeUTCMillis() - rtc::TimeMillis()),
+ field_trials_(&kDefaultFieldTrials),
+ rtt_estimate_(DEFAULT_RTT_ESTIMATE_HALF_TIME_MS) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ RTC_DCHECK(port_);
+ RTC_LOG(LS_INFO) << ToString() << ": Connection created";
+}
+
+Connection::~Connection() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ RTC_DCHECK(!port_);
+}
+
+webrtc::TaskQueueBase* Connection::network_thread() const {
+ return network_thread_;
+}
+
+const Candidate& Connection::local_candidate() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return local_candidate_;
+}
+
+const Candidate& Connection::remote_candidate() const {
+ return remote_candidate_;
+}
+
+const rtc::Network* Connection::network() const {
+ return port()->Network();
+}
+
+int Connection::generation() const {
+ return port()->generation();
+}
+
+uint64_t Connection::priority() const {
+ if (!port_)
+ return 0;
+
+ uint64_t priority = 0;
+ // RFC 5245 - 5.7.2. Computing Pair Priority and Ordering Pairs
+ // Let G be the priority for the candidate provided by the controlling
+ // agent. Let D be the priority for the candidate provided by the
+ // controlled agent.
+ // pair priority = 2^32*MIN(G,D) + 2*MAX(G,D) + (G>D?1:0)
+ IceRole role = port_->GetIceRole();
+ if (role != ICEROLE_UNKNOWN) {
+ uint32_t g = 0;
+ uint32_t d = 0;
+ if (role == ICEROLE_CONTROLLING) {
+ g = local_candidate().priority();
+ d = remote_candidate_.priority();
+ } else {
+ g = remote_candidate_.priority();
+ d = local_candidate().priority();
+ }
+ priority = std::min(g, d);
+ priority = priority << 32;
+ priority += 2 * std::max(g, d) + (g > d ? 1 : 0);
+ }
+ return priority;
+}
+
+void Connection::set_write_state(WriteState value) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ WriteState old_value = write_state_;
+ write_state_ = value;
+ if (value != old_value) {
+ RTC_LOG(LS_VERBOSE) << ToString() << ": set_write_state from: " << old_value
+ << " to " << value;
+ SignalStateChange(this);
+ }
+}
+
+void Connection::UpdateReceiving(int64_t now) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ bool receiving;
+ if (last_ping_sent() < last_ping_response_received()) {
+ // We consider any candidate pair that has its last connectivity check
+ // acknowledged by a response as receiving, particularly for backup
+ // candidate pairs that send checks at a much slower pace than the selected
+ // one. Otherwise, a backup candidate pair constantly becomes not receiving
+ // as a side effect of a long ping interval, since we do not have a separate
+ // receiving timeout for backup candidate pairs. See
+ // IceConfig.ice_backup_candidate_pair_ping_interval,
+ // IceConfig.ice_connection_receiving_timeout and their default value.
+ receiving = true;
+ } else {
+ receiving =
+ last_received() > 0 && now <= last_received() + receiving_timeout();
+ }
+ if (receiving_ == receiving) {
+ return;
+ }
+ RTC_LOG(LS_VERBOSE) << ToString() << ": set_receiving to " << receiving;
+ receiving_ = receiving;
+ receiving_unchanged_since_ = now;
+ SignalStateChange(this);
+}
+
+void Connection::set_state(IceCandidatePairState state) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ IceCandidatePairState old_state = state_;
+ state_ = state;
+ if (state != old_state) {
+ RTC_LOG(LS_VERBOSE) << ToString() << ": set_state";
+ }
+}
+
+void Connection::set_connected(bool value) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ bool old_value = connected_;
+ connected_ = value;
+ if (value != old_value) {
+ RTC_LOG(LS_VERBOSE) << ToString() << ": Change connected_ to " << value;
+ SignalStateChange(this);
+ }
+}
+
+bool Connection::use_candidate_attr() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return use_candidate_attr_;
+}
+
+void Connection::set_use_candidate_attr(bool enable) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ use_candidate_attr_ = enable;
+}
+
+void Connection::set_nomination(uint32_t value) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ nomination_ = value;
+}
+
+uint32_t Connection::remote_nomination() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return remote_nomination_;
+}
+
+bool Connection::nominated() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return acked_nomination_ || remote_nomination_;
+}
+
+int Connection::unwritable_timeout() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return unwritable_timeout_.value_or(CONNECTION_WRITE_CONNECT_TIMEOUT);
+}
+
+void Connection::set_unwritable_timeout(const absl::optional<int>& value_ms) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ unwritable_timeout_ = value_ms;
+}
+
+int Connection::unwritable_min_checks() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return unwritable_min_checks_.value_or(CONNECTION_WRITE_CONNECT_FAILURES);
+}
+
+void Connection::set_unwritable_min_checks(const absl::optional<int>& value) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ unwritable_min_checks_ = value;
+}
+
+int Connection::inactive_timeout() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return inactive_timeout_.value_or(CONNECTION_WRITE_TIMEOUT);
+}
+
+void Connection::set_inactive_timeout(const absl::optional<int>& value) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ inactive_timeout_ = value;
+}
+
+int Connection::receiving_timeout() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return receiving_timeout_.value_or(WEAK_CONNECTION_RECEIVE_TIMEOUT);
+}
+
+void Connection::set_receiving_timeout(
+ absl::optional<int> receiving_timeout_ms) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ receiving_timeout_ = receiving_timeout_ms;
+}
+
+void Connection::SetIceFieldTrials(const IceFieldTrials* field_trials) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ field_trials_ = field_trials;
+ rtt_estimate_.SetHalfTime(field_trials->rtt_estimate_halftime_ms);
+}
+
+void Connection::OnSendStunPacket(const void* data,
+ size_t size,
+ StunRequest* req) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ rtc::PacketOptions options(port_->StunDscpValue());
+ options.info_signaled_after_sent.packet_type =
+ rtc::PacketType::kIceConnectivityCheck;
+ auto err =
+ port_->SendTo(data, size, remote_candidate_.address(), options, false);
+ if (err < 0) {
+ RTC_LOG(LS_WARNING) << ToString()
+ << ": Failed to send STUN ping "
+ " err="
+ << err << " id=" << rtc::hex_encode(req->id());
+ }
+}
+
+void Connection::OnReadPacket(const char* data,
+ size_t size,
+ int64_t packet_time_us) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ std::unique_ptr<IceMessage> msg;
+ std::string remote_ufrag;
+ const rtc::SocketAddress& addr(remote_candidate_.address());
+ if (!port_->GetStunMessage(data, size, addr, &msg, &remote_ufrag)) {
+ // The packet did not parse as a valid STUN message
+ // This is a data packet, pass it along.
+ last_data_received_ = rtc::TimeMillis();
+ UpdateReceiving(last_data_received_);
+ recv_rate_tracker_.AddSamples(size);
+ stats_.packets_received++;
+ SignalReadPacket(this, data, size, packet_time_us);
+
+ // If timed out sending writability checks, start up again
+ if (!pruned_ && (write_state_ == STATE_WRITE_TIMEOUT)) {
+ RTC_LOG(LS_WARNING)
+ << "Received a data packet on a timed-out Connection. "
+ "Resetting state to STATE_WRITE_INIT.";
+ set_write_state(STATE_WRITE_INIT);
+ }
+ return;
+ } else if (!msg) {
+ // The packet was STUN, but failed a check and was handled internally.
+ return;
+ }
+
+ // The packet is STUN and passed the Port checks.
+ // Perform our own checks to ensure this packet is valid.
+ // If this is a STUN request, then update the receiving bit and respond.
+ // If this is a STUN response, then update the writable bit.
+ // Log at LS_INFO if we receive a ping on an unwritable connection.
+
+ // REQUESTs have msg->integrity() already checked in Port
+ // RESPONSEs have msg->integrity() checked below.
+ // INDICATION does not have any integrity.
+ if (IsStunRequestType(msg->type())) {
+ if (msg->integrity() != StunMessage::IntegrityStatus::kIntegrityOk) {
+ // "silently" discard the request.
+ RTC_LOG(LS_VERBOSE) << ToString() << ": Discarding "
+ << StunMethodToString(msg->type())
+ << ", id=" << rtc::hex_encode(msg->transaction_id())
+ << " with invalid message integrity: "
+ << static_cast<int>(msg->integrity());
+ return;
+ }
+ // fall-through
+ } else if (IsStunSuccessResponseType(msg->type()) ||
+ IsStunErrorResponseType(msg->type())) {
+ RTC_DCHECK(msg->integrity() == StunMessage::IntegrityStatus::kNotSet);
+ if (msg->ValidateMessageIntegrity(remote_candidate().password()) !=
+ StunMessage::IntegrityStatus::kIntegrityOk) {
+ // "silently" discard the response.
+ RTC_LOG(LS_VERBOSE) << ToString() << ": Discarding "
+ << StunMethodToString(msg->type())
+ << ", id=" << rtc::hex_encode(msg->transaction_id())
+ << " with invalid message integrity: "
+ << static_cast<int>(msg->integrity());
+ return;
+ }
+ } else {
+ RTC_DCHECK(IsStunIndicationType(msg->type()));
+ // No message integrity.
+ }
+
+ rtc::LoggingSeverity sev = (!writable() ? rtc::LS_INFO : rtc::LS_VERBOSE);
+ switch (msg->type()) {
+ case STUN_BINDING_REQUEST:
+ RTC_LOG_V(sev) << ToString() << ": Received "
+ << StunMethodToString(msg->type())
+ << ", id=" << rtc::hex_encode(msg->transaction_id());
+ if (remote_ufrag == remote_candidate_.username()) {
+ HandleStunBindingOrGoogPingRequest(msg.get());
+ } else {
+ // The packet had the right local username, but the remote username
+ // was not the right one for the remote address.
+ RTC_LOG(LS_ERROR) << ToString()
+ << ": Received STUN request with bad remote username "
+ << remote_ufrag;
+ port_->SendBindingErrorResponse(msg.get(), addr,
+ STUN_ERROR_UNAUTHORIZED,
+ STUN_ERROR_REASON_UNAUTHORIZED);
+ }
+ break;
+
+ // Response from remote peer. Does it match request sent?
+ // This doesn't just check, it makes callbacks if transaction
+ // id's match.
+ case STUN_BINDING_RESPONSE:
+ case STUN_BINDING_ERROR_RESPONSE:
+ requests_.CheckResponse(msg.get());
+ break;
+
+ // Remote end point sent an STUN indication instead of regular binding
+ // request. In this case `last_ping_received_` will be updated but no
+ // response will be sent.
+ case STUN_BINDING_INDICATION:
+ ReceivedPing(msg->transaction_id());
+ break;
+ case GOOG_PING_REQUEST:
+ // Checked in Port::GetStunMessage.
+ HandleStunBindingOrGoogPingRequest(msg.get());
+ break;
+ case GOOG_PING_RESPONSE:
+ case GOOG_PING_ERROR_RESPONSE:
+ requests_.CheckResponse(msg.get());
+ break;
+ default:
+ RTC_DCHECK_NOTREACHED();
+ break;
+ }
+}
+
+void Connection::HandleStunBindingOrGoogPingRequest(IceMessage* msg) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ // This connection should now be receiving.
+ ReceivedPing(msg->transaction_id());
+ if (field_trials_->extra_ice_ping && last_ping_response_received_ == 0) {
+ if (local_candidate().type() == RELAY_PORT_TYPE ||
+ local_candidate().type() == PRFLX_PORT_TYPE ||
+ remote_candidate().type() == RELAY_PORT_TYPE ||
+ remote_candidate().type() == PRFLX_PORT_TYPE) {
+ const int64_t now = rtc::TimeMillis();
+ if (last_ping_sent_ + kMinExtraPingDelayMs <= now) {
+ RTC_LOG(LS_INFO) << ToString()
+ << "WebRTC-ExtraICEPing/Sending extra ping"
+ " last_ping_sent_: "
+ << last_ping_sent_ << " now: " << now
+ << " (diff: " << (now - last_ping_sent_) << ")";
+ Ping(now);
+ } else {
+ RTC_LOG(LS_INFO) << ToString()
+ << "WebRTC-ExtraICEPing/Not sending extra ping"
+ " last_ping_sent_: "
+ << last_ping_sent_ << " now: " << now
+ << " (diff: " << (now - last_ping_sent_) << ")";
+ }
+ }
+ }
+
+ const rtc::SocketAddress& remote_addr = remote_candidate_.address();
+ if (msg->type() == STUN_BINDING_REQUEST) {
+ // Check for role conflicts.
+ const std::string& remote_ufrag = remote_candidate_.username();
+ if (!port_->MaybeIceRoleConflict(remote_addr, msg, remote_ufrag)) {
+ // Received conflicting role from the peer.
+ RTC_LOG(LS_INFO) << "Received conflicting role from the peer.";
+ return;
+ }
+ }
+
+ stats_.recv_ping_requests++;
+ LogCandidatePairEvent(webrtc::IceCandidatePairEventType::kCheckReceived,
+ msg->reduced_transaction_id());
+
+ // This is a validated stun request from remote peer.
+ if (msg->type() == STUN_BINDING_REQUEST) {
+ SendStunBindingResponse(msg);
+ } else {
+ RTC_DCHECK(msg->type() == GOOG_PING_REQUEST);
+ SendGoogPingResponse(msg);
+ }
+
+ // If it timed out on writing check, start up again
+ if (!pruned_ && write_state_ == STATE_WRITE_TIMEOUT) {
+ set_write_state(STATE_WRITE_INIT);
+ }
+
+ if (port_->GetIceRole() == ICEROLE_CONTROLLED) {
+ const StunUInt32Attribute* nomination_attr =
+ msg->GetUInt32(STUN_ATTR_NOMINATION);
+ uint32_t nomination = 0;
+ if (nomination_attr) {
+ nomination = nomination_attr->value();
+ if (nomination == 0) {
+ RTC_LOG(LS_ERROR) << "Invalid nomination: " << nomination;
+ }
+ } else {
+ const StunByteStringAttribute* use_candidate_attr =
+ msg->GetByteString(STUN_ATTR_USE_CANDIDATE);
+ if (use_candidate_attr) {
+ nomination = 1;
+ }
+ }
+ // We don't un-nominate a connection, so we only keep a larger nomination.
+ if (nomination > remote_nomination_) {
+ set_remote_nomination(nomination);
+ SignalNominated(this);
+ }
+ }
+ // Set the remote cost if the network_info attribute is available.
+ // Note: If packets are re-ordered, we may get incorrect network cost
+ // temporarily, but it should get the correct value shortly after that.
+ const StunUInt32Attribute* network_attr =
+ msg->GetUInt32(STUN_ATTR_GOOG_NETWORK_INFO);
+ if (network_attr) {
+ uint32_t network_info = network_attr->value();
+ uint16_t network_cost = static_cast<uint16_t>(network_info);
+ if (network_cost != remote_candidate_.network_cost()) {
+ remote_candidate_.set_network_cost(network_cost);
+ // Network cost change will affect the connection ranking, so signal
+ // state change to force a re-sort in P2PTransportChannel.
+ SignalStateChange(this);
+ }
+ }
+
+ if (field_trials_->piggyback_ice_check_acknowledgement) {
+ HandlePiggybackCheckAcknowledgementIfAny(msg);
+ }
+}
+
+void Connection::SendStunBindingResponse(const StunMessage* message) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ RTC_DCHECK_EQ(message->type(), STUN_BINDING_REQUEST);
+
+ // Retrieve the username from the `message`.
+ const StunByteStringAttribute* username_attr =
+ message->GetByteString(STUN_ATTR_USERNAME);
+ RTC_DCHECK(username_attr != NULL);
+ if (username_attr == NULL) {
+ // No valid username, skip the response.
+ return;
+ }
+
+ // Fill in the response.
+ StunMessage response(STUN_BINDING_RESPONSE, message->transaction_id());
+ const StunUInt32Attribute* retransmit_attr =
+ message->GetUInt32(STUN_ATTR_RETRANSMIT_COUNT);
+ if (retransmit_attr) {
+ // Inherit the incoming retransmit value in the response so the other side
+ // can see our view of lost pings.
+ response.AddAttribute(std::make_unique<StunUInt32Attribute>(
+ STUN_ATTR_RETRANSMIT_COUNT, retransmit_attr->value()));
+
+ if (retransmit_attr->value() > CONNECTION_WRITE_CONNECT_FAILURES) {
+ RTC_LOG(LS_INFO)
+ << ToString()
+ << ": Received a remote ping with high retransmit count: "
+ << retransmit_attr->value();
+ }
+ }
+
+ response.AddAttribute(std::make_unique<StunXorAddressAttribute>(
+ STUN_ATTR_XOR_MAPPED_ADDRESS, remote_candidate_.address()));
+
+ if (field_trials_->announce_goog_ping) {
+ // Check if message contains a announce-request.
+ auto goog_misc = message->GetUInt16List(STUN_ATTR_GOOG_MISC_INFO);
+ if (goog_misc != nullptr &&
+ goog_misc->Size() >= kSupportGoogPingVersionRequestIndex &&
+ // Which version can we handle...currently any >= 1
+ goog_misc->GetType(kSupportGoogPingVersionRequestIndex) >= 1) {
+ auto list =
+ StunAttribute::CreateUInt16ListAttribute(STUN_ATTR_GOOG_MISC_INFO);
+ list->AddTypeAtIndex(kSupportGoogPingVersionResponseIndex,
+ kGoogPingVersion);
+ response.AddAttribute(std::move(list));
+ }
+ }
+
+ response.AddMessageIntegrity(local_candidate().password());
+ response.AddFingerprint();
+
+ SendResponseMessage(response);
+}
+
+void Connection::SendGoogPingResponse(const StunMessage* message) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ RTC_DCHECK(message->type() == GOOG_PING_REQUEST);
+
+ // Fill in the response.
+ StunMessage response(GOOG_PING_RESPONSE, message->transaction_id());
+ response.AddMessageIntegrity32(local_candidate().password());
+ SendResponseMessage(response);
+}
+
+void Connection::SendResponseMessage(const StunMessage& response) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ // Where I send the response.
+ const rtc::SocketAddress& addr = remote_candidate_.address();
+
+ // Send the response.
+ rtc::ByteBufferWriter buf;
+ response.Write(&buf);
+ rtc::PacketOptions options(port_->StunDscpValue());
+ options.info_signaled_after_sent.packet_type =
+ rtc::PacketType::kIceConnectivityCheckResponse;
+ auto err = port_->SendTo(buf.Data(), buf.Length(), addr, options, false);
+ if (err < 0) {
+ RTC_LOG(LS_ERROR) << ToString() << ": Failed to send "
+ << StunMethodToString(response.type())
+ << ", to=" << addr.ToSensitiveString() << ", err=" << err
+ << ", id=" << rtc::hex_encode(response.transaction_id());
+ } else {
+ // Log at LS_INFO if we send a stun ping response on an unwritable
+ // connection.
+ rtc::LoggingSeverity sev = (!writable()) ? rtc::LS_INFO : rtc::LS_VERBOSE;
+ RTC_LOG_V(sev) << ToString() << ": Sent "
+ << StunMethodToString(response.type())
+ << ", to=" << addr.ToSensitiveString()
+ << ", id=" << rtc::hex_encode(response.transaction_id());
+
+ stats_.sent_ping_responses++;
+ LogCandidatePairEvent(webrtc::IceCandidatePairEventType::kCheckResponseSent,
+ response.reduced_transaction_id());
+ }
+}
+
+uint32_t Connection::acked_nomination() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return acked_nomination_;
+}
+
+void Connection::set_remote_nomination(uint32_t remote_nomination) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ remote_nomination_ = remote_nomination;
+}
+
+void Connection::OnReadyToSend() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ SignalReadyToSend(this);
+}
+
+bool Connection::pruned() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return pruned_;
+}
+
+void Connection::Prune() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (!pruned_ || active()) {
+ RTC_LOG(LS_INFO) << ToString() << ": Connection pruned";
+ pruned_ = true;
+ requests_.Clear();
+ set_write_state(STATE_WRITE_TIMEOUT);
+ }
+}
+
+void Connection::Destroy() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ RTC_DCHECK(port_) << "Calling Destroy() twice?";
+ if (port_)
+ port_->DestroyConnection(this);
+}
+
+bool Connection::Shutdown() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (!port_)
+ return false; // already shut down.
+
+ RTC_DLOG(LS_VERBOSE) << ToString() << ": Connection destroyed";
+
+ // Fire the 'destroyed' event before deleting the object. This is done
+ // intentionally to avoid a situation whereby the signal might have dangling
+ // pointers to objects that have been deleted by the time the async task
+ // that deletes the connection object runs.
+ auto destroyed_signals = SignalDestroyed;
+ SignalDestroyed.disconnect_all();
+ destroyed_signals(this);
+
+ LogCandidatePairConfig(webrtc::IceCandidatePairConfigType::kDestroyed);
+
+ // Reset the `port_` after logging and firing the destroyed signal since
+ // information required for logging needs access to `port_`.
+ port_.reset();
+
+ return true;
+}
+
+void Connection::FailAndPrune() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+
+ // TODO(bugs.webrtc.org/13865): There's a circular dependency between Port
+ // and Connection. In some cases (Port dtor), a Connection object is deleted
+ // without using the `Destroy` method (port_ won't be nulled and some
+ // functionality won't run as expected), while in other cases
+ // the Connection object is deleted asynchronously and in that case `port_`
+ // will be nulled.
+ // In such a case, there's a chance that the Port object gets
+ // deleted before the Connection object ends up being deleted.
+ if (!port_)
+ return;
+
+ set_state(IceCandidatePairState::FAILED);
+ Prune();
+}
+
+void Connection::PrintPingsSinceLastResponse(std::string* s, size_t max) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ rtc::StringBuilder oss;
+ if (pings_since_last_response_.size() > max) {
+ for (size_t i = 0; i < max; i++) {
+ const SentPing& ping = pings_since_last_response_[i];
+ oss << rtc::hex_encode(ping.id) << " ";
+ }
+ oss << "... " << (pings_since_last_response_.size() - max) << " more";
+ } else {
+ for (const SentPing& ping : pings_since_last_response_) {
+ oss << rtc::hex_encode(ping.id) << " ";
+ }
+ }
+ *s = oss.str();
+}
+
+bool Connection::selected() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return selected_;
+}
+
+void Connection::set_selected(bool selected) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ selected_ = selected;
+}
+
+void Connection::UpdateState(int64_t now) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (!port_)
+ return;
+
+ int rtt = ConservativeRTTEstimate(rtt_);
+
+ if (RTC_LOG_CHECK_LEVEL(LS_VERBOSE)) {
+ std::string pings;
+ PrintPingsSinceLastResponse(&pings, 5);
+ RTC_LOG(LS_VERBOSE) << ToString()
+ << ": UpdateState()"
+ ", ms since last received response="
+ << now - last_ping_response_received_
+ << ", ms since last received data="
+ << now - last_data_received_ << ", rtt=" << rtt
+ << ", pings_since_last_response=" << pings;
+ }
+
+ // Check the writable state. (The order of these checks is important.)
+ //
+ // Before becoming unwritable, we allow for a fixed number of pings to fail
+ // (i.e., receive no response). We also have to give the response time to
+ // get back, so we include a conservative estimate of this.
+ //
+ // Before timing out writability, we give a fixed amount of time. This is to
+ // allow for changes in network conditions.
+
+ if ((write_state_ == STATE_WRITABLE) &&
+ TooManyFailures(pings_since_last_response_, unwritable_min_checks(), rtt,
+ now) &&
+ TooLongWithoutResponse(pings_since_last_response_, unwritable_timeout(),
+ now)) {
+ uint32_t max_pings = unwritable_min_checks();
+ RTC_LOG(LS_INFO) << ToString() << ": Unwritable after " << max_pings
+ << " ping failures and "
+ << now - pings_since_last_response_[0].sent_time
+ << " ms without a response,"
+ " ms since last received ping="
+ << now - last_ping_received_
+ << " ms since last received data="
+ << now - last_data_received_ << " rtt=" << rtt;
+ set_write_state(STATE_WRITE_UNRELIABLE);
+ }
+ if ((write_state_ == STATE_WRITE_UNRELIABLE ||
+ write_state_ == STATE_WRITE_INIT) &&
+ TooLongWithoutResponse(pings_since_last_response_, inactive_timeout(),
+ now)) {
+ RTC_LOG(LS_INFO) << ToString() << ": Timed out after "
+ << now - pings_since_last_response_[0].sent_time
+ << " ms without a response, rtt=" << rtt;
+ set_write_state(STATE_WRITE_TIMEOUT);
+ }
+
+ // Update the receiving state.
+ UpdateReceiving(now);
+ if (dead(now)) {
+ port_->DestroyConnectionAsync(this);
+ }
+}
+
+void Connection::UpdateLocalIceParameters(int component,
+ absl::string_view username_fragment,
+ absl::string_view password) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ local_candidate_.set_component(component);
+ local_candidate_.set_username(username_fragment);
+ local_candidate_.set_password(password);
+}
+
+int64_t Connection::last_ping_sent() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return last_ping_sent_;
+}
+
+void Connection::Ping(int64_t now) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (!port_)
+ return;
+
+ last_ping_sent_ = now;
+
+ // If not using renomination, we use "1" to mean "nominated" and "0" to mean
+ // "not nominated". If using renomination, values greater than 1 are used for
+ // re-nominated pairs.
+ int nomination = use_candidate_attr_ ? 1 : 0;
+ if (nomination_ > 0) {
+ nomination = nomination_;
+ }
+
+ auto req =
+ std::make_unique<ConnectionRequest>(requests_, this, BuildPingRequest());
+
+ if (ShouldSendGoogPing(req->msg())) {
+ auto message = std::make_unique<IceMessage>(GOOG_PING_REQUEST, req->id());
+ message->AddMessageIntegrity32(remote_candidate_.password());
+ req.reset(new ConnectionRequest(requests_, this, std::move(message)));
+ }
+
+ pings_since_last_response_.push_back(SentPing(req->id(), now, nomination));
+ RTC_LOG(LS_VERBOSE) << ToString() << ": Sending STUN ping, id="
+ << rtc::hex_encode(req->id())
+ << ", nomination=" << nomination_;
+ requests_.Send(req.release());
+ state_ = IceCandidatePairState::IN_PROGRESS;
+ num_pings_sent_++;
+}
+
+std::unique_ptr<IceMessage> Connection::BuildPingRequest() {
+ auto message = std::make_unique<IceMessage>(STUN_BINDING_REQUEST);
+ // Note that the order of attributes does not impact the parsing on the
+ // receiver side. The attribute is retrieved then by iterating and matching
+ // over all parsed attributes. See StunMessage::GetAttribute.
+ message->AddAttribute(std::make_unique<StunByteStringAttribute>(
+ STUN_ATTR_USERNAME,
+ port()->CreateStunUsername(remote_candidate_.username())));
+ message->AddAttribute(std::make_unique<StunUInt32Attribute>(
+ STUN_ATTR_GOOG_NETWORK_INFO,
+ (port_->Network()->id() << 16) | port_->network_cost()));
+
+ if (field_trials_->piggyback_ice_check_acknowledgement &&
+ last_ping_id_received_) {
+ message->AddAttribute(std::make_unique<StunByteStringAttribute>(
+ STUN_ATTR_GOOG_LAST_ICE_CHECK_RECEIVED, *last_ping_id_received_));
+ }
+
+ // Adding ICE_CONTROLLED or ICE_CONTROLLING attribute based on the role.
+ IceRole ice_role = port_->GetIceRole();
+ RTC_DCHECK(ice_role == ICEROLE_CONTROLLING || ice_role == ICEROLE_CONTROLLED);
+ message->AddAttribute(std::make_unique<StunUInt64Attribute>(
+ ice_role == ICEROLE_CONTROLLING ? STUN_ATTR_ICE_CONTROLLING
+ : STUN_ATTR_ICE_CONTROLLED,
+ port_->IceTiebreaker()));
+
+ if (ice_role == ICEROLE_CONTROLLING) {
+ // We should have either USE_CANDIDATE attribute or ICE_NOMINATION
+ // attribute but not both. That was enforced in p2ptransportchannel.
+ if (use_candidate_attr()) {
+ message->AddAttribute(
+ std::make_unique<StunByteStringAttribute>(STUN_ATTR_USE_CANDIDATE));
+ }
+ if (nomination_ && nomination_ != acked_nomination()) {
+ message->AddAttribute(std::make_unique<StunUInt32Attribute>(
+ STUN_ATTR_NOMINATION, nomination_));
+ }
+ }
+
+ message->AddAttribute(std::make_unique<StunUInt32Attribute>(
+ STUN_ATTR_PRIORITY, prflx_priority()));
+
+ if (port()->send_retransmit_count_attribute()) {
+ message->AddAttribute(std::make_unique<StunUInt32Attribute>(
+ STUN_ATTR_RETRANSMIT_COUNT, pings_since_last_response_.size()));
+ }
+ if (field_trials_->enable_goog_ping &&
+ !remote_support_goog_ping_.has_value()) {
+ // Check if remote supports GOOG PING by announcing which version we
+ // support. This is sent on all STUN_BINDING_REQUEST until we get a
+ // STUN_BINDING_RESPONSE.
+ auto list =
+ StunAttribute::CreateUInt16ListAttribute(STUN_ATTR_GOOG_MISC_INFO);
+ list->AddTypeAtIndex(kSupportGoogPingVersionRequestIndex, kGoogPingVersion);
+ message->AddAttribute(std::move(list));
+ }
+ message->AddMessageIntegrity(remote_candidate_.password());
+ message->AddFingerprint();
+
+ return message;
+}
+
+int64_t Connection::last_ping_response_received() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return last_ping_response_received_;
+}
+
+const absl::optional<std::string>& Connection::last_ping_id_received() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return last_ping_id_received_;
+}
+
+// Used to check if any STUN ping response has been received.
+int Connection::rtt_samples() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return rtt_samples_;
+}
+
+// Called whenever a valid ping is received on this connection. This is
+// public because the connection intercepts the first ping for us.
+int64_t Connection::last_ping_received() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return last_ping_received_;
+}
+
+void Connection::ReceivedPing(const absl::optional<std::string>& request_id) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ last_ping_received_ = rtc::TimeMillis();
+ last_ping_id_received_ = request_id;
+ UpdateReceiving(last_ping_received_);
+}
+
+void Connection::HandlePiggybackCheckAcknowledgementIfAny(StunMessage* msg) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ RTC_DCHECK(msg->type() == STUN_BINDING_REQUEST ||
+ msg->type() == GOOG_PING_REQUEST);
+ const StunByteStringAttribute* last_ice_check_received_attr =
+ msg->GetByteString(STUN_ATTR_GOOG_LAST_ICE_CHECK_RECEIVED);
+ if (last_ice_check_received_attr) {
+ const absl::string_view request_id =
+ last_ice_check_received_attr->string_view();
+ auto iter = absl::c_find_if(
+ pings_since_last_response_,
+ [&request_id](const SentPing& ping) { return ping.id == request_id; });
+ if (iter != pings_since_last_response_.end()) {
+ rtc::LoggingSeverity sev = !writable() ? rtc::LS_INFO : rtc::LS_VERBOSE;
+ RTC_LOG_V(sev) << ToString()
+ << ": Received piggyback STUN ping response, id="
+ << rtc::hex_encode(request_id);
+ const int64_t rtt = rtc::TimeMillis() - iter->sent_time;
+ ReceivedPingResponse(rtt, request_id, iter->nomination);
+ }
+ }
+}
+
+int64_t Connection::last_send_data() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return last_send_data_;
+}
+
+int64_t Connection::last_data_received() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return last_data_received_;
+}
+
+void Connection::ReceivedPingResponse(
+ int rtt,
+ absl::string_view request_id,
+ const absl::optional<uint32_t>& nomination) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ RTC_DCHECK_GE(rtt, 0);
+ // We've already validated that this is a STUN binding response with
+ // the correct local and remote username for this connection.
+ // So if we're not already, become writable. We may be bringing a pruned
+ // connection back to life, but if we don't really want it, we can always
+ // prune it again.
+ if (nomination && nomination.value() > acked_nomination_) {
+ acked_nomination_ = nomination.value();
+ }
+
+ int64_t now = rtc::TimeMillis();
+ total_round_trip_time_ms_ += rtt;
+ current_round_trip_time_ms_ = static_cast<uint32_t>(rtt);
+ rtt_estimate_.AddSample(now, rtt);
+
+ pings_since_last_response_.clear();
+ last_ping_response_received_ = now;
+ UpdateReceiving(last_ping_response_received_);
+ set_write_state(STATE_WRITABLE);
+ set_state(IceCandidatePairState::SUCCEEDED);
+ if (rtt_samples_ > 0) {
+ rtt_ = rtc::GetNextMovingAverage(rtt_, rtt, RTT_RATIO);
+ } else {
+ rtt_ = rtt;
+ }
+ rtt_samples_++;
+}
+
+Connection::WriteState Connection::write_state() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return write_state_;
+}
+
+bool Connection::writable() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return write_state_ == STATE_WRITABLE;
+}
+
+bool Connection::receiving() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return receiving_;
+}
+
+// Determines whether the connection has finished connecting. This can only
+// be false for TCP connections.
+bool Connection::connected() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return connected_;
+}
+
+bool Connection::weak() const {
+ return !(writable() && receiving() && connected());
+}
+
+bool Connection::active() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return write_state_ != STATE_WRITE_TIMEOUT;
+}
+
+bool Connection::dead(int64_t now) const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (last_received() > 0) {
+ // If it has ever received anything, we keep it alive
+ // - if it has recevied last DEAD_CONNECTION_RECEIVE_TIMEOUT (30s)
+ // - if it has a ping outstanding shorter than
+ // DEAD_CONNECTION_RECEIVE_TIMEOUT (30s)
+ // - else if IDLE let it live field_trials_->dead_connection_timeout_ms
+ //
+ // This covers the normal case of a successfully used connection that stops
+ // working. This also allows a remote peer to continue pinging over a
+ // locally inactive (pruned) connection. This also allows the local agent to
+ // ping with longer interval than 30s as long as it shorter than
+ // `dead_connection_timeout_ms`.
+ if (now <= (last_received() + DEAD_CONNECTION_RECEIVE_TIMEOUT)) {
+ // Not dead since we have received the last 30s.
+ return false;
+ }
+ if (!pings_since_last_response_.empty()) {
+ // Outstanding pings: let it live until the ping is unreplied for
+ // DEAD_CONNECTION_RECEIVE_TIMEOUT.
+ return now > (pings_since_last_response_[0].sent_time +
+ DEAD_CONNECTION_RECEIVE_TIMEOUT);
+ }
+
+ // No outstanding pings: let it live until
+ // field_trials_->dead_connection_timeout_ms has passed.
+ return now > (last_received() + field_trials_->dead_connection_timeout_ms);
+ }
+
+ if (active()) {
+ // If it has never received anything, keep it alive as long as it is
+ // actively pinging and not pruned. Otherwise, the connection might be
+ // deleted before it has a chance to ping. This is the normal case for a
+ // new connection that is pinging but hasn't received anything yet.
+ return false;
+ }
+
+ // If it has never received anything and is not actively pinging (pruned), we
+ // keep it around for at least MIN_CONNECTION_LIFETIME to prevent connections
+ // from being pruned too quickly during a network change event when two
+ // networks would be up simultaneously but only for a brief period.
+ return now > (time_created_ms_ + MIN_CONNECTION_LIFETIME);
+}
+
+int Connection::rtt() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return rtt_;
+}
+
+bool Connection::stable(int64_t now) const {
+ // A connection is stable if it's RTT has converged and it isn't missing any
+ // responses. We should send pings at a higher rate until the RTT converges
+ // and whenever a ping response is missing (so that we can detect
+ // unwritability faster)
+ return rtt_converged() && !missing_responses(now);
+}
+
+std::string Connection::ToDebugId() const {
+ return rtc::ToHex(reinterpret_cast<uintptr_t>(this));
+}
+
+uint32_t Connection::ComputeNetworkCost() const {
+ // TODO(honghaiz): Will add rtt as part of the network cost.
+ return port()->network_cost() + remote_candidate_.network_cost();
+}
+
+std::string Connection::ToString() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ constexpr absl::string_view CONNECT_STATE_ABBREV[2] = {
+ "-", // not connected (false)
+ "C", // connected (true)
+ };
+ constexpr absl::string_view RECEIVE_STATE_ABBREV[2] = {
+ "-", // not receiving (false)
+ "R", // receiving (true)
+ };
+ constexpr absl::string_view WRITE_STATE_ABBREV[4] = {
+ "W", // STATE_WRITABLE
+ "w", // STATE_WRITE_UNRELIABLE
+ "-", // STATE_WRITE_INIT
+ "x", // STATE_WRITE_TIMEOUT
+ };
+ constexpr absl::string_view ICESTATE[4] = {
+ "W", // STATE_WAITING
+ "I", // STATE_INPROGRESS
+ "S", // STATE_SUCCEEDED
+ "F" // STATE_FAILED
+ };
+ constexpr absl::string_view SELECTED_STATE_ABBREV[2] = {
+ "-", // candidate pair not selected (false)
+ "S", // selected (true)
+ };
+ rtc::StringBuilder ss;
+ ss << "Conn[" << ToDebugId();
+
+ if (!port_) {
+ // No content or network names for pending delete. Temporarily substitute
+ // the names with a hash (rhyming with trash).
+ ss << ":#:#:";
+ } else {
+ ss << ":" << port_->content_name() << ":" << port_->Network()->ToString()
+ << ":";
+ }
+
+ const Candidate& local = local_candidate();
+ const Candidate& remote = remote_candidate();
+ ss << local.id() << ":" << local.component() << ":" << local.generation()
+ << ":" << local.type() << ":" << local.protocol() << ":"
+ << local.address().ToSensitiveString() << "->" << remote.id() << ":"
+ << remote.component() << ":" << remote.priority() << ":" << remote.type()
+ << ":" << remote.protocol() << ":" << remote.address().ToSensitiveString()
+ << "|";
+
+ ss << CONNECT_STATE_ABBREV[connected_] << RECEIVE_STATE_ABBREV[receiving_]
+ << WRITE_STATE_ABBREV[write_state_] << ICESTATE[static_cast<int>(state_)]
+ << "|" << SELECTED_STATE_ABBREV[selected_] << "|" << remote_nomination_
+ << "|" << nomination_ << "|";
+
+ if (port_)
+ ss << priority() << "|";
+
+ if (rtt_ < DEFAULT_RTT) {
+ ss << rtt_ << "]";
+ } else {
+ ss << "-]";
+ }
+
+ return ss.Release();
+}
+
+std::string Connection::ToSensitiveString() const {
+ return ToString();
+}
+
+const webrtc::IceCandidatePairDescription& Connection::ToLogDescription() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (log_description_.has_value()) {
+ return log_description_.value();
+ }
+ const Candidate& local = local_candidate();
+ const Candidate& remote = remote_candidate();
+ const rtc::Network* network = port()->Network();
+ log_description_ = webrtc::IceCandidatePairDescription();
+ log_description_->local_candidate_type =
+ GetCandidateTypeByString(local.type());
+ log_description_->local_relay_protocol =
+ GetProtocolByString(local.relay_protocol());
+ log_description_->local_network_type = ConvertNetworkType(network->type());
+ log_description_->local_address_family =
+ GetAddressFamilyByInt(local.address().family());
+ log_description_->remote_candidate_type =
+ GetCandidateTypeByString(remote.type());
+ log_description_->remote_address_family =
+ GetAddressFamilyByInt(remote.address().family());
+ log_description_->candidate_pair_protocol =
+ GetProtocolByString(local.protocol());
+ return log_description_.value();
+}
+
+void Connection::set_ice_event_log(webrtc::IceEventLog* ice_event_log) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ ice_event_log_ = ice_event_log;
+}
+
+void Connection::LogCandidatePairConfig(
+ webrtc::IceCandidatePairConfigType type) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (ice_event_log_ == nullptr) {
+ return;
+ }
+ ice_event_log_->LogCandidatePairConfig(type, id(), ToLogDescription());
+}
+
+void Connection::LogCandidatePairEvent(webrtc::IceCandidatePairEventType type,
+ uint32_t transaction_id) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (ice_event_log_ == nullptr) {
+ return;
+ }
+ ice_event_log_->LogCandidatePairEvent(type, id(), transaction_id);
+}
+
+void Connection::OnConnectionRequestResponse(StunRequest* request,
+ StunMessage* response) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ // Log at LS_INFO if we receive a ping response on an unwritable
+ // connection.
+ rtc::LoggingSeverity sev = !writable() ? rtc::LS_INFO : rtc::LS_VERBOSE;
+
+ int rtt = request->Elapsed();
+
+ if (RTC_LOG_CHECK_LEVEL_V(sev)) {
+ std::string pings;
+ PrintPingsSinceLastResponse(&pings, 5);
+ RTC_LOG_V(sev) << ToString() << ": Received "
+ << StunMethodToString(response->type())
+ << ", id=" << rtc::hex_encode(request->id())
+ << ", code=0" // Makes logging easier to parse.
+ ", rtt="
+ << rtt << ", pings_since_last_response=" << pings;
+ }
+ absl::optional<uint32_t> nomination;
+ const std::string request_id = request->id();
+ auto iter = absl::c_find_if(
+ pings_since_last_response_,
+ [&request_id](const SentPing& ping) { return ping.id == request_id; });
+ if (iter != pings_since_last_response_.end()) {
+ nomination.emplace(iter->nomination);
+ }
+ ReceivedPingResponse(rtt, request_id, nomination);
+
+ stats_.recv_ping_responses++;
+ LogCandidatePairEvent(
+ webrtc::IceCandidatePairEventType::kCheckResponseReceived,
+ response->reduced_transaction_id());
+
+ if (request->msg()->type() == STUN_BINDING_REQUEST) {
+ if (!remote_support_goog_ping_.has_value()) {
+ auto goog_misc = response->GetUInt16List(STUN_ATTR_GOOG_MISC_INFO);
+ if (goog_misc != nullptr &&
+ goog_misc->Size() >= kSupportGoogPingVersionResponseIndex) {
+ // The remote peer has indicated that it {does/does not} supports
+ // GOOG_PING.
+ remote_support_goog_ping_ =
+ goog_misc->GetType(kSupportGoogPingVersionResponseIndex) >=
+ kGoogPingVersion;
+ } else {
+ remote_support_goog_ping_ = false;
+ }
+ }
+
+ MaybeUpdateLocalCandidate(request, response);
+
+ if (field_trials_->enable_goog_ping && remote_support_goog_ping_) {
+ cached_stun_binding_ = request->msg()->Clone();
+ }
+ }
+}
+
+void Connection::OnConnectionRequestErrorResponse(ConnectionRequest* request,
+ StunMessage* response) {
+ if (!port_)
+ return;
+
+ int error_code = response->GetErrorCodeValue();
+ RTC_LOG(LS_WARNING) << ToString() << ": Received "
+ << StunMethodToString(response->type())
+ << " id=" << rtc::hex_encode(request->id())
+ << " code=" << error_code
+ << " rtt=" << request->Elapsed();
+
+ cached_stun_binding_.reset();
+ if (error_code == STUN_ERROR_UNKNOWN_ATTRIBUTE ||
+ error_code == STUN_ERROR_SERVER_ERROR ||
+ error_code == STUN_ERROR_UNAUTHORIZED) {
+ // Recoverable error, retry
+ } else if (error_code == STUN_ERROR_ROLE_CONFLICT) {
+ port_->SignalRoleConflict(port_.get());
+ } else if (request->msg()->type() == GOOG_PING_REQUEST) {
+ // Race, retry.
+ } else {
+ // This is not a valid connection.
+ RTC_LOG(LS_ERROR) << ToString()
+ << ": Received STUN error response, code=" << error_code
+ << "; killing connection";
+ set_state(IceCandidatePairState::FAILED);
+ port_->DestroyConnectionAsync(this);
+ }
+}
+
+void Connection::OnConnectionRequestTimeout(ConnectionRequest* request) {
+ // Log at LS_INFO if we miss a ping on a writable connection.
+ rtc::LoggingSeverity sev = writable() ? rtc::LS_INFO : rtc::LS_VERBOSE;
+ RTC_LOG_V(sev) << ToString() << ": Timing-out STUN ping "
+ << rtc::hex_encode(request->id()) << " after "
+ << request->Elapsed() << " ms";
+}
+
+void Connection::OnConnectionRequestSent(ConnectionRequest* request) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ // Log at LS_INFO if we send a ping on an unwritable connection.
+ rtc::LoggingSeverity sev = !writable() ? rtc::LS_INFO : rtc::LS_VERBOSE;
+ RTC_LOG_V(sev) << ToString() << ": Sent "
+ << StunMethodToString(request->msg()->type())
+ << ", id=" << rtc::hex_encode(request->id())
+ << ", use_candidate=" << use_candidate_attr()
+ << ", nomination=" << nomination_;
+ stats_.sent_ping_requests_total++;
+ LogCandidatePairEvent(webrtc::IceCandidatePairEventType::kCheckSent,
+ request->reduced_transaction_id());
+ if (stats_.recv_ping_responses == 0) {
+ stats_.sent_ping_requests_before_first_response++;
+ }
+}
+
+IceCandidatePairState Connection::state() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return state_;
+}
+
+int Connection::num_pings_sent() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return num_pings_sent_;
+}
+
+void Connection::MaybeSetRemoteIceParametersAndGeneration(
+ const IceParameters& ice_params,
+ int generation) {
+ if (remote_candidate_.username() == ice_params.ufrag &&
+ remote_candidate_.password().empty()) {
+ remote_candidate_.set_password(ice_params.pwd);
+ }
+ // TODO(deadbeef): A value of '0' for the generation is used for both
+ // generation 0 and "generation unknown". It should be changed to an
+ // absl::optional to fix this.
+ if (remote_candidate_.username() == ice_params.ufrag &&
+ remote_candidate_.password() == ice_params.pwd &&
+ remote_candidate_.generation() == 0) {
+ remote_candidate_.set_generation(generation);
+ }
+}
+
+void Connection::MaybeUpdatePeerReflexiveCandidate(
+ const Candidate& new_candidate) {
+ if (remote_candidate_.type() == PRFLX_PORT_TYPE &&
+ new_candidate.type() != PRFLX_PORT_TYPE &&
+ remote_candidate_.protocol() == new_candidate.protocol() &&
+ remote_candidate_.address() == new_candidate.address() &&
+ remote_candidate_.username() == new_candidate.username() &&
+ remote_candidate_.password() == new_candidate.password() &&
+ remote_candidate_.generation() == new_candidate.generation()) {
+ remote_candidate_ = new_candidate;
+ }
+}
+
+int64_t Connection::last_received() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return std::max(last_data_received_,
+ std::max(last_ping_received_, last_ping_response_received_));
+}
+
+int64_t Connection::receiving_unchanged_since() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return receiving_unchanged_since_;
+}
+
+uint32_t Connection::prflx_priority() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ // PRIORITY Attribute.
+ // Changing the type preference to Peer Reflexive and local preference
+ // and component id information is unchanged from the original priority.
+ // priority = (2^24)*(type preference) +
+ // (2^8)*(local preference) +
+ // (2^0)*(256 - component ID)
+ IcePriorityValue type_preference =
+ (local_candidate_.protocol() == TCP_PROTOCOL_NAME)
+ ? ICE_TYPE_PREFERENCE_PRFLX_TCP
+ : ICE_TYPE_PREFERENCE_PRFLX;
+ return type_preference << 24 | (local_candidate_.priority() & 0x00FFFFFF);
+}
+
+ConnectionInfo Connection::stats() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ stats_.recv_bytes_second = round(recv_rate_tracker_.ComputeRate());
+ stats_.recv_total_bytes = recv_rate_tracker_.TotalSampleCount();
+ stats_.sent_bytes_second = round(send_rate_tracker_.ComputeRate());
+ stats_.sent_total_bytes = send_rate_tracker_.TotalSampleCount();
+ stats_.receiving = receiving_;
+ stats_.writable = write_state_ == STATE_WRITABLE;
+ stats_.timeout = write_state_ == STATE_WRITE_TIMEOUT;
+ stats_.rtt = rtt_;
+ stats_.key = this;
+ stats_.state = state_;
+ if (port_) {
+ stats_.priority = priority();
+ stats_.local_candidate = local_candidate();
+ }
+ stats_.nominated = nominated();
+ stats_.total_round_trip_time_ms = total_round_trip_time_ms_;
+ stats_.current_round_trip_time_ms = current_round_trip_time_ms_;
+ stats_.remote_candidate = remote_candidate();
+ if (last_data_received_ > 0) {
+ stats_.last_data_received = webrtc::Timestamp::Millis(
+ last_data_received_ + delta_internal_unix_epoch_ms_);
+ }
+ if (last_send_data_ > 0) {
+ stats_.last_data_sent = webrtc::Timestamp::Millis(
+ last_send_data_ + delta_internal_unix_epoch_ms_);
+ }
+ return stats_;
+}
+
+void Connection::MaybeUpdateLocalCandidate(StunRequest* request,
+ StunMessage* response) {
+ if (!port_)
+ return;
+
+ // RFC 5245
+ // The agent checks the mapped address from the STUN response. If the
+ // transport address does not match any of the local candidates that the
+ // agent knows about, the mapped address represents a new candidate -- a
+ // peer reflexive candidate.
+ const StunAddressAttribute* addr =
+ response->GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+ if (!addr) {
+ RTC_LOG(LS_WARNING)
+ << "Connection::OnConnectionRequestResponse - "
+ "No MAPPED-ADDRESS or XOR-MAPPED-ADDRESS found in the "
+ "stun response message";
+ return;
+ }
+
+ for (const Candidate& candidate : port_->Candidates()) {
+ if (candidate.address() == addr->GetAddress()) {
+ if (local_candidate_ != candidate) {
+ RTC_LOG(LS_INFO) << ToString()
+ << ": Updating local candidate type to srflx.";
+ local_candidate_ = candidate;
+ // SignalStateChange to force a re-sort in P2PTransportChannel as this
+ // Connection's local candidate has changed.
+ SignalStateChange(this);
+ }
+ return;
+ }
+ }
+
+ // RFC 5245
+ // Its priority is set equal to the value of the PRIORITY attribute
+ // in the Binding request.
+ const StunUInt32Attribute* priority_attr =
+ request->msg()->GetUInt32(STUN_ATTR_PRIORITY);
+ if (!priority_attr) {
+ RTC_LOG(LS_WARNING) << "Connection::OnConnectionRequestResponse - "
+ "No STUN_ATTR_PRIORITY found in the "
+ "stun response message";
+ return;
+ }
+ const uint32_t priority = priority_attr->value();
+ std::string id = rtc::CreateRandomString(8);
+
+ // Create a peer-reflexive candidate based on the local candidate.
+ local_candidate_.set_id(id);
+ local_candidate_.set_type(PRFLX_PORT_TYPE);
+ // Set the related address and foundation attributes before changing the
+ // address.
+ local_candidate_.set_related_address(local_candidate_.address());
+ local_candidate_.set_foundation(port()->ComputeFoundation(
+ PRFLX_PORT_TYPE, local_candidate_.protocol(),
+ local_candidate_.relay_protocol(), local_candidate_.address()));
+ local_candidate_.set_priority(priority);
+ local_candidate_.set_address(addr->GetAddress());
+
+ // Change the local candidate of this Connection to the new prflx candidate.
+ RTC_LOG(LS_INFO) << ToString() << ": Updating local candidate type to prflx.";
+ port_->AddPrflxCandidate(local_candidate_);
+
+ // SignalStateChange to force a re-sort in P2PTransportChannel as this
+ // Connection's local candidate has changed.
+ SignalStateChange(this);
+}
+
+bool Connection::rtt_converged() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return rtt_samples_ > (RTT_RATIO + 1);
+}
+
+bool Connection::missing_responses(int64_t now) const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (pings_since_last_response_.empty()) {
+ return false;
+ }
+
+ int64_t waiting = now - pings_since_last_response_[0].sent_time;
+ return waiting > 2 * rtt();
+}
+
+bool Connection::TooManyOutstandingPings(
+ const absl::optional<int>& max_outstanding_pings) const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (!max_outstanding_pings.has_value()) {
+ return false;
+ }
+ if (static_cast<int>(pings_since_last_response_.size()) <
+ *max_outstanding_pings) {
+ return false;
+ }
+ return true;
+}
+
+void Connection::SetLocalCandidateNetworkCost(uint16_t cost) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+
+ if (cost == local_candidate_.network_cost())
+ return;
+
+ local_candidate_.set_network_cost(cost);
+
+ // Network cost change will affect the connection selection criteria.
+ // Signal the connection state change to force a re-sort in
+ // P2PTransportChannel.
+ SignalStateChange(this);
+}
+
+bool Connection::ShouldSendGoogPing(const StunMessage* message) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (remote_support_goog_ping_ == true && cached_stun_binding_ &&
+ cached_stun_binding_->EqualAttributes(message, [](int type) {
+ // Ignore these attributes.
+ // NOTE: Consider what to do if adding more content to
+ // STUN_ATTR_GOOG_MISC_INFO
+ return type != STUN_ATTR_FINGERPRINT &&
+ type != STUN_ATTR_MESSAGE_INTEGRITY &&
+ type != STUN_ATTR_RETRANSMIT_COUNT &&
+ type != STUN_ATTR_GOOG_MISC_INFO;
+ })) {
+ return true;
+ }
+ return false;
+}
+
+void Connection::ForgetLearnedState() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ RTC_LOG(LS_INFO) << ToString() << ": Connection forget learned state";
+ requests_.Clear();
+ receiving_ = false;
+ write_state_ = STATE_WRITE_INIT;
+ rtt_estimate_.Reset();
+ pings_since_last_response_.clear();
+}
+
+ProxyConnection::ProxyConnection(rtc::WeakPtr<Port> port,
+ size_t index,
+ const Candidate& remote_candidate)
+ : Connection(std::move(port), index, remote_candidate) {}
+
+int ProxyConnection::Send(const void* data,
+ size_t size,
+ const rtc::PacketOptions& options) {
+ if (!port_)
+ return SOCKET_ERROR;
+
+ stats_.sent_total_packets++;
+ int sent =
+ port_->SendTo(data, size, remote_candidate_.address(), options, true);
+ int64_t now = rtc::TimeMillis();
+ if (sent <= 0) {
+ RTC_DCHECK(sent < 0);
+ error_ = port_->GetError();
+ stats_.sent_discarded_packets++;
+ stats_.sent_discarded_bytes += size;
+ } else {
+ send_rate_tracker_.AddSamplesAtTime(now, sent);
+ }
+ last_send_data_ = now;
+ return sent;
+}
+
+int ProxyConnection::GetError() {
+ return error_;
+}
+
+} // namespace cricket
diff --git a/third_party/libwebrtc/p2p/base/connection.h b/third_party/libwebrtc/p2p/base/connection.h
new file mode 100644
index 0000000000..4e6c7d91be
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/connection.h
@@ -0,0 +1,498 @@
+/*
+ * 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 <memory>
+#include <string>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "api/candidate.h"
+#include "api/transport/stun.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/stun_request.h"
+#include "p2p/base/transport_description.h"
+#include "rtc_base/async_packet_socket.h"
+#include "rtc_base/network.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/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;
+
+// Connection and Port has circular dependencies.
+// So we use forward declaration rather than include.
+class Port;
+
+// Forward declaration so that a ConnectionRequest can contain a Connection.
+class Connection;
+
+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;
+};
+
+// 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 Port* 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;
+
+ // 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;
+
+ sigslot::signal4<Connection*, const char*, size_t, int64_t> SignalReadPacket;
+
+ sigslot::signal1<Connection*> SignalReadyToSend;
+
+ // Called when a packet is received on this connection.
+ 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);
+ void ReceivedPingResponse(
+ int rtt,
+ absl::string_view request_id,
+ const absl::optional<uint32_t>& nomination = absl::nullopt);
+ std::unique_ptr<IceMessage> BuildPingRequest() 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.
+ Port* PortForTest() { return port_.get(); }
+ const Port* PortForTest() const { return port_.get(); }
+
+ std::unique_ptr<IceMessage> BuildPingRequestForTest() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return BuildPingRequest();
+ }
+
+ // 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);
+ }
+
+ 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<Port> 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.
+ Port* 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<Port> 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_);
+};
+
+// ProxyConnection defers all the interesting work to the port.
+class ProxyConnection : public Connection {
+ public:
+ ProxyConnection(rtc::WeakPtr<Port> 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_
diff --git a/third_party/libwebrtc/p2p/base/connection_info.cc b/third_party/libwebrtc/p2p/base/connection_info.cc
new file mode 100644
index 0000000000..363d32954e
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/connection_info.cc
@@ -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
diff --git a/third_party/libwebrtc/p2p/base/connection_info.h b/third_party/libwebrtc/p2p/base/connection_info.h
new file mode 100644
index 0000000000..cd2a913451
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/connection_info.h
@@ -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_
diff --git a/third_party/libwebrtc/p2p/base/default_ice_transport_factory.cc b/third_party/libwebrtc/p2p/base/default_ice_transport_factory.cc
new file mode 100644
index 0000000000..313d608750
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/default_ice_transport_factory.cc
@@ -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
diff --git a/third_party/libwebrtc/p2p/base/default_ice_transport_factory.h b/third_party/libwebrtc/p2p/base/default_ice_transport_factory.h
new file mode 100644
index 0000000000..e46680d480
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/default_ice_transport_factory.h
@@ -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_
diff --git a/third_party/libwebrtc/p2p/base/dtls_transport.cc b/third_party/libwebrtc/p2p/base/dtls_transport.cc
new file mode 100644
index 0000000000..af16efad78
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/dtls_transport.cc
@@ -0,0 +1,870 @@
+/*
+ * 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));
+ 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);
+ dtls_->SignalSSLHandshakeError.connect(this,
+ &DtlsTransport::OnDtlsHandshakeError);
+ 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);
+}
+
+// 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
diff --git a/third_party/libwebrtc/p2p/base/dtls_transport.h b/third_party/libwebrtc/p2p/base/dtls_transport.h
new file mode 100644
index 0000000000..4e21410b76
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/dtls_transport.h
@@ -0,0 +1,264 @@
+/*
+ * 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;
+
+ 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_
diff --git a/third_party/libwebrtc/p2p/base/dtls_transport_factory.h b/third_party/libwebrtc/p2p/base/dtls_transport_factory.h
new file mode 100644
index 0000000000..7c4a24adc8
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/dtls_transport_factory.h
@@ -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_
diff --git a/third_party/libwebrtc/p2p/base/dtls_transport_internal.cc b/third_party/libwebrtc/p2p/base/dtls_transport_internal.cc
new file mode 100644
index 0000000000..6997dbc702
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/dtls_transport_internal.cc
@@ -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
diff --git a/third_party/libwebrtc/p2p/base/dtls_transport_internal.h b/third_party/libwebrtc/p2p/base/dtls_transport_internal.h
new file mode 100644
index 0000000000..3d20d1bfd6
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/dtls_transport_internal.h
@@ -0,0 +1,157 @@
+/*
+ * 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;
+
+ // 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_
diff --git a/third_party/libwebrtc/p2p/base/dtls_transport_unittest.cc b/third_party/libwebrtc/p2p/base/dtls_transport_unittest.cc
new file mode 100644
index 0000000000..e338ab6a49
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/dtls_transport_unittest.cc
@@ -0,0 +1,748 @@
+/*
+ * 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 <set>
+#include <utility>
+
+#include "absl/strings/string_view.h"
+#include "api/dtls_transport_interface.h"
+#include "p2p/base/fake_ice_transport.h"
+#include "p2p/base/packet_transport_internal.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/dscp.h"
+#include "rtc_base/gunit.h"
+#include "rtc_base/helpers.h"
+#include "rtc_base/rtc_certificate.h"
+#include "rtc_base/ssl_adapter.h"
+#include "rtc_base/ssl_identity.h"
+#include "rtc_base/ssl_stream_adapter.h"
+
+#define MAYBE_SKIP_TEST(feature) \
+ if (!(rtc::SSLStreamAdapter::feature())) { \
+ RTC_LOG(LS_INFO) << #feature " feature disabled... skipping"; \
+ return; \
+ }
+
+namespace cricket {
+
+static const size_t kPacketNumOffset = 8;
+static const size_t kPacketHeaderLen = 12;
+static const int kFakePacketId = 0x1234;
+static const int kTimeout = 10000;
+
+static bool IsRtpLeadByte(uint8_t b) {
+ return ((b & 0xC0) == 0x80);
+}
+
+// `modify_digest` is used to set modified fingerprints that are meant to fail
+// validation.
+void SetRemoteFingerprintFromCert(
+ DtlsTransport* transport,
+ const rtc::scoped_refptr<rtc::RTCCertificate>& cert,
+ bool modify_digest = false) {
+ std::unique_ptr<rtc::SSLFingerprint> fingerprint =
+ rtc::SSLFingerprint::CreateFromCertificate(*cert);
+ if (modify_digest) {
+ ++fingerprint->digest.MutableData()[0];
+ }
+
+ // Even if digest is verified to be incorrect, should fail asynchronously.
+ EXPECT_TRUE(
+ transport
+ ->SetRemoteParameters(
+ fingerprint->algorithm,
+ reinterpret_cast<const uint8_t*>(fingerprint->digest.data()),
+ fingerprint->digest.size(), absl::nullopt)
+ .ok());
+}
+
+class DtlsTestClient : public sigslot::has_slots<> {
+ public:
+ explicit DtlsTestClient(absl::string_view name) : name_(name) {}
+ void CreateCertificate(rtc::KeyType key_type) {
+ certificate_ =
+ rtc::RTCCertificate::Create(rtc::SSLIdentity::Create(name_, key_type));
+ }
+ const rtc::scoped_refptr<rtc::RTCCertificate>& certificate() {
+ return certificate_;
+ }
+ void SetupMaxProtocolVersion(rtc::SSLProtocolVersion version) {
+ ssl_max_version_ = version;
+ }
+ // Set up fake ICE transport and real DTLS transport under test.
+ void SetupTransports(IceRole role, int async_delay_ms = 0) {
+ fake_ice_transport_.reset(new FakeIceTransport("fake", 0));
+ fake_ice_transport_->SetAsync(true);
+ fake_ice_transport_->SetAsyncDelay(async_delay_ms);
+ fake_ice_transport_->SetIceRole(role);
+ fake_ice_transport_->SetIceTiebreaker((role == ICEROLE_CONTROLLING) ? 1
+ : 2);
+ // Hook the raw packets so that we can verify they are encrypted.
+ fake_ice_transport_->SignalReadPacket.connect(
+ this, &DtlsTestClient::OnFakeIceTransportReadPacket);
+
+ dtls_transport_ = std::make_unique<DtlsTransport>(
+ fake_ice_transport_.get(), webrtc::CryptoOptions(),
+ /*event_log=*/nullptr, ssl_max_version_);
+ // Note: Certificate may be null here if testing passthrough.
+ dtls_transport_->SetLocalCertificate(certificate_);
+ dtls_transport_->SignalWritableState.connect(
+ this, &DtlsTestClient::OnTransportWritableState);
+ dtls_transport_->SignalReadPacket.connect(
+ this, &DtlsTestClient::OnTransportReadPacket);
+ dtls_transport_->SignalSentPacket.connect(
+ this, &DtlsTestClient::OnTransportSentPacket);
+ }
+
+ FakeIceTransport* fake_ice_transport() {
+ return static_cast<FakeIceTransport*>(dtls_transport_->ice_transport());
+ }
+
+ DtlsTransport* dtls_transport() { return dtls_transport_.get(); }
+
+ // Simulate fake ICE transports connecting.
+ bool Connect(DtlsTestClient* peer, bool asymmetric) {
+ fake_ice_transport()->SetDestination(peer->fake_ice_transport(),
+ asymmetric);
+ return true;
+ }
+
+ int received_dtls_client_hellos() const {
+ return received_dtls_client_hellos_;
+ }
+
+ int received_dtls_server_hellos() const {
+ return received_dtls_server_hellos_;
+ }
+
+ void CheckRole(rtc::SSLRole role) {
+ if (role == rtc::SSL_CLIENT) {
+ ASSERT_EQ(0, received_dtls_client_hellos_);
+ ASSERT_GT(received_dtls_server_hellos_, 0);
+ } else {
+ ASSERT_GT(received_dtls_client_hellos_, 0);
+ ASSERT_EQ(0, received_dtls_server_hellos_);
+ }
+ }
+
+ void CheckSrtp(int expected_crypto_suite) {
+ int crypto_suite;
+ bool rv = dtls_transport_->GetSrtpCryptoSuite(&crypto_suite);
+ if (dtls_transport_->IsDtlsActive() && expected_crypto_suite) {
+ ASSERT_TRUE(rv);
+ ASSERT_EQ(crypto_suite, expected_crypto_suite);
+ } else {
+ ASSERT_FALSE(rv);
+ }
+ }
+
+ void CheckSsl() {
+ int cipher;
+ bool rv = dtls_transport_->GetSslCipherSuite(&cipher);
+ if (dtls_transport_->IsDtlsActive()) {
+ ASSERT_TRUE(rv);
+ EXPECT_TRUE(
+ rtc::SSLStreamAdapter::IsAcceptableCipher(cipher, rtc::KT_DEFAULT));
+ } else {
+ ASSERT_FALSE(rv);
+ }
+ }
+
+ void SendPackets(size_t size, size_t count, bool srtp) {
+ std::unique_ptr<char[]> packet(new char[size]);
+ size_t sent = 0;
+ do {
+ // Fill the packet with a known value and a sequence number to check
+ // against, and make sure that it doesn't look like DTLS.
+ memset(packet.get(), sent & 0xff, size);
+ packet[0] = (srtp) ? 0x80 : 0x00;
+ rtc::SetBE32(packet.get() + kPacketNumOffset,
+ static_cast<uint32_t>(sent));
+
+ // Only set the bypass flag if we've activated DTLS.
+ int flags = (certificate_ && srtp) ? PF_SRTP_BYPASS : 0;
+ rtc::PacketOptions packet_options;
+ packet_options.packet_id = kFakePacketId;
+ int rv = dtls_transport_->SendPacket(packet.get(), size, packet_options,
+ flags);
+ ASSERT_GT(rv, 0);
+ ASSERT_EQ(size, static_cast<size_t>(rv));
+ ++sent;
+ } while (sent < count);
+ }
+
+ int SendInvalidSrtpPacket(size_t size) {
+ std::unique_ptr<char[]> packet(new char[size]);
+ // Fill the packet with 0 to form an invalid SRTP packet.
+ memset(packet.get(), 0, size);
+
+ rtc::PacketOptions packet_options;
+ return dtls_transport_->SendPacket(packet.get(), size, packet_options,
+ PF_SRTP_BYPASS);
+ }
+
+ void ExpectPackets(size_t size) {
+ packet_size_ = size;
+ received_.clear();
+ }
+
+ size_t NumPacketsReceived() { return received_.size(); }
+
+ // Inverse of SendPackets.
+ bool VerifyPacket(const char* data, size_t size, uint32_t* out_num) {
+ if (size != packet_size_ ||
+ (data[0] != 0 && static_cast<uint8_t>(data[0]) != 0x80)) {
+ return false;
+ }
+ uint32_t packet_num = rtc::GetBE32(data + kPacketNumOffset);
+ for (size_t i = kPacketHeaderLen; i < size; ++i) {
+ if (static_cast<uint8_t>(data[i]) != (packet_num & 0xff)) {
+ return false;
+ }
+ }
+ if (out_num) {
+ *out_num = packet_num;
+ }
+ return true;
+ }
+ bool VerifyEncryptedPacket(const char* data, size_t size) {
+ // This is an encrypted data packet; let's make sure it's mostly random;
+ // less than 10% of the bytes should be equal to the cleartext packet.
+ if (size <= packet_size_) {
+ return false;
+ }
+ uint32_t packet_num = rtc::GetBE32(data + kPacketNumOffset);
+ int num_matches = 0;
+ for (size_t i = kPacketNumOffset; i < size; ++i) {
+ if (static_cast<uint8_t>(data[i]) == (packet_num & 0xff)) {
+ ++num_matches;
+ }
+ }
+ return (num_matches < ((static_cast<int>(size) - 5) / 10));
+ }
+
+ // Transport callbacks
+ void OnTransportWritableState(rtc::PacketTransportInternal* transport) {
+ RTC_LOG(LS_INFO) << name_ << ": Transport '" << transport->transport_name()
+ << "' is writable";
+ }
+
+ void OnTransportReadPacket(rtc::PacketTransportInternal* transport,
+ const char* data,
+ size_t size,
+ const int64_t& /* packet_time_us */,
+ int flags) {
+ uint32_t packet_num = 0;
+ ASSERT_TRUE(VerifyPacket(data, size, &packet_num));
+ received_.insert(packet_num);
+ // Only DTLS-SRTP packets should have the bypass flag set.
+ int expected_flags =
+ (certificate_ && IsRtpLeadByte(data[0])) ? PF_SRTP_BYPASS : 0;
+ ASSERT_EQ(expected_flags, flags);
+ }
+
+ void OnTransportSentPacket(rtc::PacketTransportInternal* transport,
+ const rtc::SentPacket& sent_packet) {
+ sent_packet_ = sent_packet;
+ }
+
+ rtc::SentPacket sent_packet() const { return sent_packet_; }
+
+ // Hook into the raw packet stream to make sure DTLS packets are encrypted.
+ void OnFakeIceTransportReadPacket(rtc::PacketTransportInternal* transport,
+ const char* data,
+ size_t size,
+ const int64_t& /* packet_time_us */,
+ int flags) {
+ // Flags shouldn't be set on the underlying Transport packets.
+ ASSERT_EQ(0, flags);
+
+ // Look at the handshake packets to see what role we played.
+ // Check that non-handshake packets are DTLS data or SRTP bypass.
+ if (data[0] == 22 && size > 17) {
+ if (data[13] == 1) {
+ ++received_dtls_client_hellos_;
+ } else if (data[13] == 2) {
+ ++received_dtls_server_hellos_;
+ }
+ } else if (dtls_transport_->IsDtlsActive() &&
+ !(data[0] >= 20 && data[0] <= 22)) {
+ ASSERT_TRUE(data[0] == 23 || IsRtpLeadByte(data[0]));
+ if (data[0] == 23) {
+ ASSERT_TRUE(VerifyEncryptedPacket(data, size));
+ } else if (IsRtpLeadByte(data[0])) {
+ ASSERT_TRUE(VerifyPacket(data, size, NULL));
+ }
+ }
+ }
+
+ private:
+ std::string name_;
+ rtc::scoped_refptr<rtc::RTCCertificate> certificate_;
+ std::unique_ptr<FakeIceTransport> fake_ice_transport_;
+ std::unique_ptr<DtlsTransport> dtls_transport_;
+ size_t packet_size_ = 0u;
+ std::set<int> received_;
+ rtc::SSLProtocolVersion ssl_max_version_ = rtc::SSL_PROTOCOL_DTLS_12;
+ int received_dtls_client_hellos_ = 0;
+ int received_dtls_server_hellos_ = 0;
+ rtc::SentPacket sent_packet_;
+};
+
+// Base class for DtlsTransportTest and DtlsEventOrderingTest, which
+// inherit from different variants of ::testing::Test.
+//
+// Note that this test always uses a FakeClock, due to the `fake_clock_` member
+// variable.
+class DtlsTransportTestBase {
+ public:
+ DtlsTransportTestBase() : client1_("P1"), client2_("P2"), use_dtls_(false) {}
+
+ void SetMaxProtocolVersions(rtc::SSLProtocolVersion c1,
+ rtc::SSLProtocolVersion c2) {
+ client1_.SetupMaxProtocolVersion(c1);
+ client2_.SetupMaxProtocolVersion(c2);
+ }
+ // If not called, DtlsTransport will be used in SRTP bypass mode.
+ void PrepareDtls(rtc::KeyType key_type) {
+ client1_.CreateCertificate(key_type);
+ client2_.CreateCertificate(key_type);
+ use_dtls_ = true;
+ }
+
+ // This test negotiates DTLS parameters before the underlying transports are
+ // writable. DtlsEventOrderingTest is responsible for exercising differerent
+ // orderings.
+ bool Connect(bool client1_server = true) {
+ Negotiate(client1_server);
+ EXPECT_TRUE(client1_.Connect(&client2_, false));
+
+ EXPECT_TRUE_SIMULATED_WAIT(client1_.dtls_transport()->writable() &&
+ client2_.dtls_transport()->writable(),
+ kTimeout, fake_clock_);
+ if (!client1_.dtls_transport()->writable() ||
+ !client2_.dtls_transport()->writable())
+ return false;
+
+ // Check that we used the right roles.
+ if (use_dtls_) {
+ client1_.CheckRole(client1_server ? rtc::SSL_SERVER : rtc::SSL_CLIENT);
+ client2_.CheckRole(client1_server ? rtc::SSL_CLIENT : rtc::SSL_SERVER);
+ }
+
+ if (use_dtls_) {
+ // Check that we negotiated the right ciphers. Since GCM ciphers are not
+ // negotiated by default, we should end up with kSrtpAes128CmSha1_80.
+ client1_.CheckSrtp(rtc::kSrtpAes128CmSha1_80);
+ client2_.CheckSrtp(rtc::kSrtpAes128CmSha1_80);
+ } else {
+ // If DTLS isn't actually being used, GetSrtpCryptoSuite should return
+ // false.
+ client1_.CheckSrtp(rtc::kSrtpInvalidCryptoSuite);
+ client2_.CheckSrtp(rtc::kSrtpInvalidCryptoSuite);
+ }
+
+ client1_.CheckSsl();
+ client2_.CheckSsl();
+
+ return true;
+ }
+
+ void Negotiate(bool client1_server = true) {
+ client1_.SetupTransports(ICEROLE_CONTROLLING);
+ client2_.SetupTransports(ICEROLE_CONTROLLED);
+ client1_.dtls_transport()->SetDtlsRole(client1_server ? rtc::SSL_SERVER
+ : rtc::SSL_CLIENT);
+ client2_.dtls_transport()->SetDtlsRole(client1_server ? rtc::SSL_CLIENT
+ : rtc::SSL_SERVER);
+ if (client2_.certificate()) {
+ SetRemoteFingerprintFromCert(client1_.dtls_transport(),
+ client2_.certificate());
+ }
+ if (client1_.certificate()) {
+ SetRemoteFingerprintFromCert(client2_.dtls_transport(),
+ client1_.certificate());
+ }
+ }
+
+ void TestTransfer(size_t size, size_t count, bool srtp) {
+ RTC_LOG(LS_INFO) << "Expect packets, size=" << size;
+ client2_.ExpectPackets(size);
+ client1_.SendPackets(size, count, srtp);
+ EXPECT_EQ_SIMULATED_WAIT(count, client2_.NumPacketsReceived(), kTimeout,
+ fake_clock_);
+ }
+
+ protected:
+ rtc::AutoThread main_thread_;
+ rtc::ScopedFakeClock fake_clock_;
+ DtlsTestClient client1_;
+ DtlsTestClient client2_;
+ bool use_dtls_;
+ rtc::SSLProtocolVersion ssl_expected_version_;
+};
+
+class DtlsTransportTest : public DtlsTransportTestBase,
+ public ::testing::Test {};
+
+// Connect without DTLS, and transfer RTP data.
+TEST_F(DtlsTransportTest, TestTransferRtp) {
+ ASSERT_TRUE(Connect());
+ TestTransfer(1000, 100, /*srtp=*/false);
+}
+
+// Test that the SignalSentPacket signal is wired up.
+TEST_F(DtlsTransportTest, TestSignalSentPacket) {
+ ASSERT_TRUE(Connect());
+ // Sanity check default value (-1).
+ ASSERT_EQ(client1_.sent_packet().send_time_ms, -1);
+ TestTransfer(1000, 100, false);
+ // Check that we get the expected fake packet ID, and a time of 0 from the
+ // fake clock.
+ EXPECT_EQ(kFakePacketId, client1_.sent_packet().packet_id);
+ EXPECT_GE(client1_.sent_packet().send_time_ms, 0);
+}
+
+// Connect without DTLS, and transfer SRTP data.
+TEST_F(DtlsTransportTest, TestTransferSrtp) {
+ ASSERT_TRUE(Connect());
+ TestTransfer(1000, 100, /*srtp=*/true);
+}
+
+// Connect with DTLS, and transfer data over DTLS.
+TEST_F(DtlsTransportTest, TestTransferDtls) {
+ PrepareDtls(rtc::KT_DEFAULT);
+ ASSERT_TRUE(Connect());
+ TestTransfer(1000, 100, /*srtp=*/false);
+}
+
+// Connect with DTLS, combine multiple DTLS records into one packet.
+// Our DTLS implementation doesn't do this, but other implementations may;
+// see https://tools.ietf.org/html/rfc6347#section-4.1.1.
+// This has caused interoperability problems with ORTCLib in the past.
+TEST_F(DtlsTransportTest, TestTransferDtlsCombineRecords) {
+ PrepareDtls(rtc::KT_DEFAULT);
+ ASSERT_TRUE(Connect());
+ // Our DTLS implementation always sends one record per packet, so to simulate
+ // an endpoint that sends multiple records per packet, we configure the fake
+ // ICE transport to combine every two consecutive packets into a single
+ // packet.
+ FakeIceTransport* transport = client1_.fake_ice_transport();
+ transport->combine_outgoing_packets(true);
+ TestTransfer(500, 100, /*srtp=*/false);
+}
+
+class DtlsTransportVersionTest
+ : public DtlsTransportTestBase,
+ public ::testing::TestWithParam<
+ ::testing::tuple<rtc::SSLProtocolVersion, rtc::SSLProtocolVersion>> {
+};
+
+// Test that an acceptable cipher suite is negotiated when different versions
+// of DTLS are supported. Note that it's IsAcceptableCipher that does the actual
+// work.
+TEST_P(DtlsTransportVersionTest, TestCipherSuiteNegotiation) {
+ PrepareDtls(rtc::KT_DEFAULT);
+ SetMaxProtocolVersions(::testing::get<0>(GetParam()),
+ ::testing::get<1>(GetParam()));
+ ASSERT_TRUE(Connect());
+}
+
+// Will test every combination of 1.0/1.2 on the client and server.
+INSTANTIATE_TEST_SUITE_P(
+ TestCipherSuiteNegotiation,
+ DtlsTransportVersionTest,
+ ::testing::Combine(::testing::Values(rtc::SSL_PROTOCOL_DTLS_10,
+ rtc::SSL_PROTOCOL_DTLS_12),
+ ::testing::Values(rtc::SSL_PROTOCOL_DTLS_10,
+ rtc::SSL_PROTOCOL_DTLS_12)));
+
+// Connect with DTLS, negotiating DTLS-SRTP, and transfer SRTP using bypass.
+TEST_F(DtlsTransportTest, TestTransferDtlsSrtp) {
+ PrepareDtls(rtc::KT_DEFAULT);
+ ASSERT_TRUE(Connect());
+ TestTransfer(1000, 100, /*srtp=*/true);
+}
+
+// Connect with DTLS-SRTP, transfer an invalid SRTP packet, and expects -1
+// returned.
+TEST_F(DtlsTransportTest, TestTransferDtlsInvalidSrtpPacket) {
+ PrepareDtls(rtc::KT_DEFAULT);
+ ASSERT_TRUE(Connect());
+ EXPECT_EQ(-1, client1_.SendInvalidSrtpPacket(100));
+}
+
+// Create a single transport with DTLS, and send normal data and SRTP data on
+// it.
+TEST_F(DtlsTransportTest, TestTransferDtlsSrtpDemux) {
+ PrepareDtls(rtc::KT_DEFAULT);
+ ASSERT_TRUE(Connect());
+ TestTransfer(1000, 100, /*srtp=*/false);
+ TestTransfer(1000, 100, /*srtp=*/true);
+}
+
+// Test transferring when the "answerer" has the server role.
+TEST_F(DtlsTransportTest, TestTransferDtlsSrtpAnswererIsPassive) {
+ PrepareDtls(rtc::KT_DEFAULT);
+ ASSERT_TRUE(Connect(/*client1_server=*/false));
+ TestTransfer(1000, 100, /*srtp=*/true);
+}
+
+// Test that renegotiation (setting same role and fingerprint again) can be
+// started before the clients become connected in the first negotiation.
+TEST_F(DtlsTransportTest, TestRenegotiateBeforeConnect) {
+ PrepareDtls(rtc::KT_DEFAULT);
+ // Note: This is doing the same thing Connect normally does, minus some
+ // additional checks not relevant for this test.
+ Negotiate();
+ Negotiate();
+ EXPECT_TRUE(client1_.Connect(&client2_, false));
+ EXPECT_TRUE_SIMULATED_WAIT(client1_.dtls_transport()->writable() &&
+ client2_.dtls_transport()->writable(),
+ kTimeout, fake_clock_);
+ TestTransfer(1000, 100, true);
+}
+
+// Test Certificates state after negotiation but before connection.
+TEST_F(DtlsTransportTest, TestCertificatesBeforeConnect) {
+ PrepareDtls(rtc::KT_DEFAULT);
+ Negotiate();
+
+ // After negotiation, each side has a distinct local certificate, but still no
+ // remote certificate, because connection has not yet occurred.
+ auto certificate1 = client1_.dtls_transport()->GetLocalCertificate();
+ auto certificate2 = client2_.dtls_transport()->GetLocalCertificate();
+ ASSERT_NE(certificate1->GetSSLCertificate().ToPEMString(),
+ certificate2->GetSSLCertificate().ToPEMString());
+ ASSERT_FALSE(client1_.dtls_transport()->GetRemoteSSLCertChain());
+ ASSERT_FALSE(client2_.dtls_transport()->GetRemoteSSLCertChain());
+}
+
+// Test Certificates state after connection.
+TEST_F(DtlsTransportTest, TestCertificatesAfterConnect) {
+ PrepareDtls(rtc::KT_DEFAULT);
+ ASSERT_TRUE(Connect());
+
+ // After connection, each side has a distinct local certificate.
+ auto certificate1 = client1_.dtls_transport()->GetLocalCertificate();
+ auto certificate2 = client2_.dtls_transport()->GetLocalCertificate();
+ ASSERT_NE(certificate1->GetSSLCertificate().ToPEMString(),
+ certificate2->GetSSLCertificate().ToPEMString());
+
+ // Each side's remote certificate is the other side's local certificate.
+ std::unique_ptr<rtc::SSLCertChain> remote_cert1 =
+ client1_.dtls_transport()->GetRemoteSSLCertChain();
+ ASSERT_TRUE(remote_cert1);
+ ASSERT_EQ(1u, remote_cert1->GetSize());
+ ASSERT_EQ(remote_cert1->Get(0).ToPEMString(),
+ certificate2->GetSSLCertificate().ToPEMString());
+ std::unique_ptr<rtc::SSLCertChain> remote_cert2 =
+ client2_.dtls_transport()->GetRemoteSSLCertChain();
+ ASSERT_TRUE(remote_cert2);
+ ASSERT_EQ(1u, remote_cert2->GetSize());
+ ASSERT_EQ(remote_cert2->Get(0).ToPEMString(),
+ certificate1->GetSSLCertificate().ToPEMString());
+}
+
+// Test that packets are retransmitted according to the expected schedule.
+// Each time a timeout occurs, the retransmission timer should be doubled up to
+// 60 seconds. The timer defaults to 1 second, but for WebRTC we should be
+// initializing it to 50ms.
+TEST_F(DtlsTransportTest, TestRetransmissionSchedule) {
+ // We can only change the retransmission schedule with a recently-added
+ // BoringSSL API. Skip the test if not built with BoringSSL.
+ MAYBE_SKIP_TEST(IsBoringSsl);
+
+ PrepareDtls(rtc::KT_DEFAULT);
+ // Exchange fingerprints and set SSL roles.
+ Negotiate();
+
+ // Make client2_ writable, but not client1_.
+ // This means client1_ will send DTLS client hellos but get no response.
+ EXPECT_TRUE(client2_.Connect(&client1_, true));
+ EXPECT_TRUE_SIMULATED_WAIT(client2_.fake_ice_transport()->writable(),
+ kTimeout, fake_clock_);
+
+ // Wait for the first client hello to be sent.
+ EXPECT_EQ_WAIT(1, client1_.received_dtls_client_hellos(), kTimeout);
+ EXPECT_FALSE(client1_.fake_ice_transport()->writable());
+
+ static int timeout_schedule_ms[] = {50, 100, 200, 400, 800, 1600,
+ 3200, 6400, 12800, 25600, 51200, 60000};
+
+ int expected_hellos = 1;
+ for (size_t i = 0;
+ i < (sizeof(timeout_schedule_ms) / sizeof(timeout_schedule_ms[0]));
+ ++i) {
+ // For each expected retransmission time, advance the fake clock a
+ // millisecond before the expected time and verify that no unexpected
+ // retransmissions were sent. Then advance it the final millisecond and
+ // verify that the expected retransmission was sent.
+ fake_clock_.AdvanceTime(
+ webrtc::TimeDelta::Millis(timeout_schedule_ms[i] - 1));
+ EXPECT_EQ(expected_hellos, client1_.received_dtls_client_hellos());
+ fake_clock_.AdvanceTime(webrtc::TimeDelta::Millis(1));
+ EXPECT_EQ(++expected_hellos, client1_.received_dtls_client_hellos());
+ }
+}
+
+// The following events can occur in many different orders:
+// 1. Caller receives remote fingerprint.
+// 2. Caller is writable.
+// 3. Caller receives ClientHello.
+// 4. DTLS handshake finishes.
+//
+// The tests below cover all causally consistent permutations of these events;
+// the caller must be writable and receive a ClientHello before the handshake
+// finishes, but otherwise any ordering is possible.
+//
+// For each permutation, the test verifies that a connection is established and
+// fingerprint verified without any DTLS packet needing to be retransmitted.
+//
+// Each permutation is also tested with valid and invalid fingerprints,
+// ensuring that the handshake fails with an invalid fingerprint.
+enum DtlsTransportEvent {
+ CALLER_RECEIVES_FINGERPRINT,
+ CALLER_WRITABLE,
+ CALLER_RECEIVES_CLIENTHELLO,
+ HANDSHAKE_FINISHES
+};
+
+class DtlsEventOrderingTest
+ : public DtlsTransportTestBase,
+ public ::testing::TestWithParam<
+ ::testing::tuple<std::vector<DtlsTransportEvent>, bool>> {
+ protected:
+ // If `valid_fingerprint` is false, the caller will receive a fingerprint
+ // that doesn't match the callee's certificate, so the handshake should fail.
+ void TestEventOrdering(const std::vector<DtlsTransportEvent>& events,
+ bool valid_fingerprint) {
+ // Pre-setup: Set local certificate on both caller and callee, and
+ // remote fingerprint on callee, but neither is writable and the caller
+ // doesn't have the callee's fingerprint.
+ PrepareDtls(rtc::KT_DEFAULT);
+ // Simulate packets being sent and arriving asynchronously.
+ // Otherwise the entire DTLS handshake would occur in one clock tick, and
+ // we couldn't inject method calls in the middle of it.
+ int simulated_delay_ms = 10;
+ client1_.SetupTransports(ICEROLE_CONTROLLING, simulated_delay_ms);
+ client2_.SetupTransports(ICEROLE_CONTROLLED, simulated_delay_ms);
+ // Similar to how NegotiateOrdering works.
+ client1_.dtls_transport()->SetDtlsRole(rtc::SSL_SERVER);
+ client2_.dtls_transport()->SetDtlsRole(rtc::SSL_CLIENT);
+ SetRemoteFingerprintFromCert(client2_.dtls_transport(),
+ client1_.certificate());
+
+ for (DtlsTransportEvent e : events) {
+ switch (e) {
+ case CALLER_RECEIVES_FINGERPRINT:
+ if (valid_fingerprint) {
+ SetRemoteFingerprintFromCert(client1_.dtls_transport(),
+ client2_.certificate());
+ } else {
+ SetRemoteFingerprintFromCert(client1_.dtls_transport(),
+ client2_.certificate(),
+ true /*modify_digest*/);
+ }
+ break;
+ case CALLER_WRITABLE:
+ EXPECT_TRUE(client1_.Connect(&client2_, true));
+ EXPECT_TRUE_SIMULATED_WAIT(client1_.fake_ice_transport()->writable(),
+ kTimeout, fake_clock_);
+ break;
+ case CALLER_RECEIVES_CLIENTHELLO:
+ // Sanity check that a ClientHello hasn't already been received.
+ EXPECT_EQ(0, client1_.received_dtls_client_hellos());
+ // Making client2_ writable will cause it to send the ClientHello.
+ EXPECT_TRUE(client2_.Connect(&client1_, true));
+ EXPECT_TRUE_SIMULATED_WAIT(client2_.fake_ice_transport()->writable(),
+ kTimeout, fake_clock_);
+ EXPECT_EQ_SIMULATED_WAIT(1, client1_.received_dtls_client_hellos(),
+ kTimeout, fake_clock_);
+ break;
+ case HANDSHAKE_FINISHES:
+ // Sanity check that the handshake hasn't already finished.
+ EXPECT_FALSE(client1_.dtls_transport()->IsDtlsConnected() ||
+ client1_.dtls_transport()->dtls_state() ==
+ webrtc::DtlsTransportState::kFailed);
+ EXPECT_TRUE_SIMULATED_WAIT(
+ client1_.dtls_transport()->IsDtlsConnected() ||
+ client1_.dtls_transport()->dtls_state() ==
+ webrtc::DtlsTransportState::kFailed,
+ kTimeout, fake_clock_);
+ break;
+ }
+ }
+
+ webrtc::DtlsTransportState expected_final_state =
+ valid_fingerprint ? webrtc::DtlsTransportState::kConnected
+ : webrtc::DtlsTransportState::kFailed;
+ EXPECT_EQ_SIMULATED_WAIT(expected_final_state,
+ client1_.dtls_transport()->dtls_state(), kTimeout,
+ fake_clock_);
+ EXPECT_EQ_SIMULATED_WAIT(expected_final_state,
+ client2_.dtls_transport()->dtls_state(), kTimeout,
+ fake_clock_);
+
+ // Transports should be writable iff there was a valid fingerprint.
+ EXPECT_EQ(valid_fingerprint, client1_.dtls_transport()->writable());
+ EXPECT_EQ(valid_fingerprint, client2_.dtls_transport()->writable());
+
+ // Check that no hello needed to be retransmitted.
+ EXPECT_EQ(1, client1_.received_dtls_client_hellos());
+ EXPECT_EQ(1, client2_.received_dtls_server_hellos());
+
+ if (valid_fingerprint) {
+ TestTransfer(1000, 100, false);
+ }
+ }
+};
+
+TEST_P(DtlsEventOrderingTest, TestEventOrdering) {
+ TestEventOrdering(::testing::get<0>(GetParam()),
+ ::testing::get<1>(GetParam()));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ TestEventOrdering,
+ DtlsEventOrderingTest,
+ ::testing::Combine(
+ ::testing::Values(
+ std::vector<DtlsTransportEvent>{
+ CALLER_RECEIVES_FINGERPRINT, CALLER_WRITABLE,
+ CALLER_RECEIVES_CLIENTHELLO, HANDSHAKE_FINISHES},
+ std::vector<DtlsTransportEvent>{
+ CALLER_WRITABLE, CALLER_RECEIVES_FINGERPRINT,
+ CALLER_RECEIVES_CLIENTHELLO, HANDSHAKE_FINISHES},
+ std::vector<DtlsTransportEvent>{
+ CALLER_WRITABLE, CALLER_RECEIVES_CLIENTHELLO,
+ CALLER_RECEIVES_FINGERPRINT, HANDSHAKE_FINISHES},
+ std::vector<DtlsTransportEvent>{
+ CALLER_WRITABLE, CALLER_RECEIVES_CLIENTHELLO,
+ HANDSHAKE_FINISHES, CALLER_RECEIVES_FINGERPRINT},
+ std::vector<DtlsTransportEvent>{
+ CALLER_RECEIVES_FINGERPRINT, CALLER_RECEIVES_CLIENTHELLO,
+ CALLER_WRITABLE, HANDSHAKE_FINISHES},
+ std::vector<DtlsTransportEvent>{
+ CALLER_RECEIVES_CLIENTHELLO, CALLER_RECEIVES_FINGERPRINT,
+ CALLER_WRITABLE, HANDSHAKE_FINISHES},
+ std::vector<DtlsTransportEvent>{
+ CALLER_RECEIVES_CLIENTHELLO, CALLER_WRITABLE,
+ CALLER_RECEIVES_FINGERPRINT, HANDSHAKE_FINISHES},
+ std::vector<DtlsTransportEvent>{CALLER_RECEIVES_CLIENTHELLO,
+ CALLER_WRITABLE, HANDSHAKE_FINISHES,
+ CALLER_RECEIVES_FINGERPRINT}),
+ ::testing::Bool()));
+
+} // namespace cricket
diff --git a/third_party/libwebrtc/p2p/base/fake_dtls_transport.h b/third_party/libwebrtc/p2p/base/fake_dtls_transport.h
new file mode 100644
index 0000000000..283488bc38
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/fake_dtls_transport.h
@@ -0,0 +1,318 @@
+/*
+ * 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;
+ }
+ 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_
diff --git a/third_party/libwebrtc/p2p/base/fake_ice_transport.h b/third_party/libwebrtc/p2p/base/fake_ice_transport.h
new file mode 100644
index 0000000000..ae7bf8947e
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/fake_ice_transport.h
@@ -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;
+ SignalGatheringState(this);
+ }
+ }
+
+ // 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;
+ SignalGatheringState(this);
+ }
+ }
+
+ 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_
diff --git a/third_party/libwebrtc/p2p/base/fake_packet_transport.h b/third_party/libwebrtc/p2p/base/fake_packet_transport.h
new file mode 100644
index 0000000000..e80af0e008
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/fake_packet_transport.h
@@ -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_
diff --git a/third_party/libwebrtc/p2p/base/fake_port_allocator.h b/third_party/libwebrtc/p2p/base/fake_port_allocator.h
new file mode 100644
index 0000000000..05c631361f
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/fake_port_allocator.h
@@ -0,0 +1,279 @@
+/*
+ * 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/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,
+ 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()),
+ 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));
+ set_ice_tiebreaker(/*kTiebreakerDefault = */ 44444);
+ }
+
+ 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(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();
+ }
+
+ 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_
diff --git a/third_party/libwebrtc/p2p/base/ice_agent_interface.h b/third_party/libwebrtc/p2p/base/ice_agent_interface.h
new file mode 100644
index 0000000000..30b6ade6e6
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/ice_agent_interface.h
@@ -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_
diff --git a/third_party/libwebrtc/p2p/base/ice_controller_factory_interface.h b/third_party/libwebrtc/p2p/base/ice_controller_factory_interface.h
new file mode 100644
index 0000000000..bae8b8f19d
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/ice_controller_factory_interface.h
@@ -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_
diff --git a/third_party/libwebrtc/p2p/base/ice_controller_interface.cc b/third_party/libwebrtc/p2p/base/ice_controller_interface.cc
new file mode 100644
index 0000000000..9fb3b055f9
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/ice_controller_interface.cc
@@ -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
diff --git a/third_party/libwebrtc/p2p/base/ice_controller_interface.h b/third_party/libwebrtc/p2p/base/ice_controller_interface.h
new file mode 100644
index 0000000000..482043ef35
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/ice_controller_interface.h
@@ -0,0 +1,138 @@
+/*
+ * 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"
+
+namespace cricket {
+
+struct IceFieldTrials; // Forward declaration to avoid circular dependency.
+
+struct 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 connections()-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*> connections() const = 0;
+
+ // 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_
diff --git a/third_party/libwebrtc/p2p/base/ice_credentials_iterator.cc b/third_party/libwebrtc/p2p/base/ice_credentials_iterator.cc
new file mode 100644
index 0000000000..373c8510ac
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/ice_credentials_iterator.cc
@@ -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
diff --git a/third_party/libwebrtc/p2p/base/ice_credentials_iterator.h b/third_party/libwebrtc/p2p/base/ice_credentials_iterator.h
new file mode 100644
index 0000000000..fa331cc6eb
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/ice_credentials_iterator.h
@@ -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_
diff --git a/third_party/libwebrtc/p2p/base/ice_credentials_iterator_unittest.cc b/third_party/libwebrtc/p2p/base/ice_credentials_iterator_unittest.cc
new file mode 100644
index 0000000000..470efe3e45
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/ice_credentials_iterator_unittest.cc
@@ -0,0 +1,48 @@
+/*
+ * 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 <vector>
+
+#include "test/gtest.h"
+
+using cricket::IceCredentialsIterator;
+using cricket::IceParameters;
+
+TEST(IceCredentialsIteratorTest, GetEmpty) {
+ std::vector<IceParameters> empty;
+ IceCredentialsIterator iterator(empty);
+ // Verify that we can get credentials even if input is empty.
+ IceParameters credentials1 = iterator.GetIceCredentials();
+}
+
+TEST(IceCredentialsIteratorTest, GetOne) {
+ std::vector<IceParameters> one = {
+ IceCredentialsIterator::CreateRandomIceCredentials()};
+ IceCredentialsIterator iterator(one);
+ EXPECT_EQ(iterator.GetIceCredentials(), one[0]);
+ auto random = iterator.GetIceCredentials();
+ EXPECT_NE(random, one[0]);
+ EXPECT_NE(random, iterator.GetIceCredentials());
+}
+
+TEST(IceCredentialsIteratorTest, GetTwo) {
+ std::vector<IceParameters> two = {
+ IceCredentialsIterator::CreateRandomIceCredentials(),
+ IceCredentialsIterator::CreateRandomIceCredentials()};
+ IceCredentialsIterator iterator(two);
+ EXPECT_EQ(iterator.GetIceCredentials(), two[1]);
+ EXPECT_EQ(iterator.GetIceCredentials(), two[0]);
+ auto random = iterator.GetIceCredentials();
+ EXPECT_NE(random, two[0]);
+ EXPECT_NE(random, two[1]);
+ EXPECT_NE(random, iterator.GetIceCredentials());
+}
diff --git a/third_party/libwebrtc/p2p/base/ice_switch_reason.cc b/third_party/libwebrtc/p2p/base/ice_switch_reason.cc
new file mode 100644
index 0000000000..61f0fa7d5b
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/ice_switch_reason.cc
@@ -0,0 +1,44 @@
+/*
+ * 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";
+ default:
+ return "unknown";
+ }
+}
+
+} // namespace cricket
diff --git a/third_party/libwebrtc/p2p/base/ice_switch_reason.h b/third_party/libwebrtc/p2p/base/ice_switch_reason.h
new file mode 100644
index 0000000000..2c4fe31884
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/ice_switch_reason.h
@@ -0,0 +1,38 @@
+/*
+ * 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>
+
+namespace cricket {
+
+enum class IceSwitchReason {
+ 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,
+};
+
+std::string IceSwitchReasonToString(IceSwitchReason reason);
+
+} // namespace cricket
+
+#endif // P2P_BASE_ICE_SWITCH_REASON_H_
diff --git a/third_party/libwebrtc/p2p/base/ice_transport_internal.cc b/third_party/libwebrtc/p2p/base/ice_transport_internal.cc
new file mode 100644
index 0000000000..fab6f2037a
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/ice_transport_internal.cc
@@ -0,0 +1,140 @@
+/*
+ * 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() = default;
+
+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
diff --git a/third_party/libwebrtc/p2p/base/ice_transport_internal.h b/third_party/libwebrtc/p2p/base/ice_transport_internal.h
new file mode 100644
index 0000000000..3a93ab0484
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/ice_transport_internal.h
@@ -0,0 +1,347 @@
+/*
+ * 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 <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/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.
+struct 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
+};
+
+// TODO(zhihuang): Remove this once it's no longer used in
+// remoting/protocol/libjingle_transport_factory.cc
+enum IceProtocolType {
+ ICEPROTO_RFC5245 // Standard RFC 5245 version of ICE.
+};
+
+// 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.
+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;
+
+ // TODO(zhihuang): Remove this once it's no longer called in
+ // remoting/protocol/libjingle_transport_factory.cc
+ virtual void SetIceProtocolType(IceProtocolType type) {}
+
+ 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;
+
+ sigslot::signal1<IceTransportInternal*> SignalGatheringState;
+
+ // Handles sending and receiving of candidates.
+ sigslot::signal2<IceTransportInternal*, const Candidate&>
+ SignalCandidateGathered;
+
+ sigslot::signal2<IceTransportInternal*, const IceCandidateErrorEvent&>
+ SignalCandidateError;
+
+ sigslot::signal2<IceTransportInternal*, const Candidates&>
+ SignalCandidatesRemoved;
+
+ // 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;
+
+ sigslot::signal1<const cricket::CandidatePairChangeEvent&>
+ SignalCandidatePairChanged;
+
+ // 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;
+};
+
+} // namespace cricket
+
+#endif // P2P_BASE_ICE_TRANSPORT_INTERNAL_H_
diff --git a/third_party/libwebrtc/p2p/base/mock_active_ice_controller.h b/third_party/libwebrtc/p2p/base/mock_active_ice_controller.h
new file mode 100644
index 0000000000..908967bd1d
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/mock_active_ice_controller.h
@@ -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_
diff --git a/third_party/libwebrtc/p2p/base/mock_async_resolver.h b/third_party/libwebrtc/p2p/base/mock_async_resolver.h
new file mode 100644
index 0000000000..44164716b2
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/mock_async_resolver.h
@@ -0,0 +1,57 @@
+/*
+ * 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_ASYNC_RESOLVER_H_
+#define P2P_BASE_MOCK_ASYNC_RESOLVER_H_
+
+#include "api/async_resolver_factory.h"
+#include "rtc_base/async_resolver_interface.h"
+#include "test/gmock.h"
+
+namespace rtc {
+
+using ::testing::_;
+using ::testing::InvokeWithoutArgs;
+
+class MockAsyncResolver : public AsyncResolverInterface {
+ public:
+ MockAsyncResolver() {
+ ON_CALL(*this, Start(_)).WillByDefault(InvokeWithoutArgs([this] {
+ SignalDone(this);
+ }));
+ }
+ ~MockAsyncResolver() = default;
+
+ MOCK_METHOD(void, Start, (const rtc::SocketAddress&), (override));
+ MOCK_METHOD(void, Start, (const rtc::SocketAddress&, int family), (override));
+ MOCK_METHOD(bool,
+ GetResolvedAddress,
+ (int family, SocketAddress* addr),
+ (const, override));
+ MOCK_METHOD(int, GetError, (), (const, override));
+
+ // Note that this won't delete the object like AsyncResolverInterface says in
+ // order to avoid sanitizer failures caused by this being a synchronous
+ // implementation. The test code should delete the object instead.
+ MOCK_METHOD(void, Destroy, (bool), (override));
+};
+
+} // namespace rtc
+
+namespace webrtc {
+
+class MockAsyncResolverFactory : public AsyncResolverFactory {
+ public:
+ MOCK_METHOD(rtc::AsyncResolverInterface*, Create, (), (override));
+};
+
+} // namespace webrtc
+
+#endif // P2P_BASE_MOCK_ASYNC_RESOLVER_H_
diff --git a/third_party/libwebrtc/p2p/base/mock_dns_resolving_packet_socket_factory.h b/third_party/libwebrtc/p2p/base/mock_dns_resolving_packet_socket_factory.h
new file mode 100644
index 0000000000..8f18e9b0e1
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/mock_dns_resolving_packet_socket_factory.h
@@ -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_
diff --git a/third_party/libwebrtc/p2p/base/mock_ice_agent.h b/third_party/libwebrtc/p2p/base/mock_ice_agent.h
new file mode 100644
index 0000000000..a1c0ebffbf
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/mock_ice_agent.h
@@ -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_
diff --git a/third_party/libwebrtc/p2p/base/mock_ice_controller.h b/third_party/libwebrtc/p2p/base/mock_ice_controller.h
new file mode 100644
index 0000000000..bde9254e7d
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/mock_ice_controller.h
@@ -0,0 +1,90 @@
+/*
+ * 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*>,
+ 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_
diff --git a/third_party/libwebrtc/p2p/base/mock_ice_transport.h b/third_party/libwebrtc/p2p/base/mock_ice_transport.h
new file mode 100644
index 0000000000..ef6bdce3c0
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/mock_ice_transport.h
@@ -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_
diff --git a/third_party/libwebrtc/p2p/base/p2p_constants.cc b/third_party/libwebrtc/p2p/base/p2p_constants.cc
new file mode 100644
index 0000000000..3414939a6f
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/p2p_constants.cc
@@ -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
diff --git a/third_party/libwebrtc/p2p/base/p2p_constants.h b/third_party/libwebrtc/p2p/base/p2p_constants.h
new file mode 100644
index 0000000000..d51ee17a07
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/p2p_constants.h
@@ -0,0 +1,114 @@
+/*
+ * 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;
+
+// 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;
+
+} // namespace cricket
+
+#endif // P2P_BASE_P2P_CONSTANTS_H_
diff --git a/third_party/libwebrtc/p2p/base/p2p_transport_channel.cc b/third_party/libwebrtc/p2p/base/p2p_transport_channel.cc
new file mode 100644
index 0000000000..15b1cf6f87
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/p2p_transport_channel.cc
@@ -0,0 +1,2636 @@
+/*
+ * 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_transport_channel.h"
+
+#include <errno.h>
+#include <stdlib.h>
+
+#include <algorithm>
+#include <functional>
+#include <memory>
+#include <set>
+#include <utility>
+
+#include "absl/algorithm/container.h"
+#include "absl/memory/memory.h"
+#include "absl/strings/match.h"
+#include "absl/strings/string_view.h"
+#include "api/async_dns_resolver.h"
+#include "api/candidate.h"
+#include "api/field_trials_view.h"
+#include "api/units/time_delta.h"
+#include "logging/rtc_event_log/ice_logger.h"
+#include "p2p/base/basic_async_resolver_factory.h"
+#include "p2p/base/basic_ice_controller.h"
+#include "p2p/base/connection.h"
+#include "p2p/base/connection_info.h"
+#include "p2p/base/port.h"
+#include "p2p/base/wrapping_active_ice_controller.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/crc32.h"
+#include "rtc_base/experiments/struct_parameters_parser.h"
+#include "rtc_base/ip_address.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/net_helper.h"
+#include "rtc_base/network.h"
+#include "rtc_base/network_constants.h"
+#include "rtc_base/string_encode.h"
+#include "rtc_base/third_party/sigslot/sigslot.h"
+#include "rtc_base/time_utils.h"
+#include "rtc_base/trace_event.h"
+#include "system_wrappers/include/metrics.h"
+
+namespace {
+
+cricket::PortInterface::CandidateOrigin GetOrigin(
+ cricket::PortInterface* port,
+ cricket::PortInterface* origin_port) {
+ if (!origin_port)
+ return cricket::PortInterface::ORIGIN_MESSAGE;
+ else if (port == origin_port)
+ return cricket::PortInterface::ORIGIN_THIS_PORT;
+ else
+ return cricket::PortInterface::ORIGIN_OTHER_PORT;
+}
+
+uint32_t GetWeakPingIntervalInFieldTrial(
+ const webrtc::FieldTrialsView* field_trials) {
+ if (field_trials != nullptr) {
+ uint32_t weak_ping_interval =
+ ::strtoul(field_trials->Lookup("WebRTC-StunInterPacketDelay").c_str(),
+ nullptr, 10);
+ if (weak_ping_interval) {
+ return static_cast<int>(weak_ping_interval);
+ }
+ }
+ return cricket::WEAK_PING_INTERVAL;
+}
+
+rtc::RouteEndpoint CreateRouteEndpointFromCandidate(
+ bool local,
+ const cricket::Candidate& candidate,
+ bool uses_turn) {
+ auto adapter_type = candidate.network_type();
+ if (!local && adapter_type == rtc::ADAPTER_TYPE_UNKNOWN) {
+ bool vpn;
+ std::tie(adapter_type, vpn) =
+ rtc::Network::GuessAdapterFromNetworkCost(candidate.network_cost());
+ }
+
+ // TODO(bugs.webrtc.org/9446) : Rewrite if information about remote network
+ // adapter becomes available. The implication of this implementation is that
+ // we will only ever report 1 adapter per type. In practice this is probably
+ // fine, since the endpoint also contains network-id.
+ uint16_t adapter_id = static_cast<int>(adapter_type);
+ return rtc::RouteEndpoint(adapter_type, adapter_id, candidate.network_id(),
+ uses_turn);
+}
+
+bool UseActiveIceControllerFieldTrialEnabled(
+ const webrtc::FieldTrialsView* field_trials) {
+ // Feature to refactor ICE controller and enable active ICE controllers.
+ // Field trial key reserved in bugs.webrtc.org/14367
+ return field_trials &&
+ field_trials->IsEnabled("WebRTC-UseActiveIceController");
+}
+
+using ::webrtc::RTCError;
+using ::webrtc::RTCErrorType;
+using ::webrtc::SafeTask;
+using ::webrtc::TimeDelta;
+
+} // unnamed namespace
+
+namespace cricket {
+
+bool IceCredentialsChanged(absl::string_view old_ufrag,
+ absl::string_view old_pwd,
+ absl::string_view new_ufrag,
+ absl::string_view new_pwd) {
+ // The standard (RFC 5245 Section 9.1.1.1) says that ICE restarts MUST change
+ // both the ufrag and password. However, section 9.2.1.1 says changing the
+ // ufrag OR password indicates an ICE restart. So, to keep compatibility with
+ // endpoints that only change one, we'll treat this as an ICE restart.
+ return (old_ufrag != new_ufrag) || (old_pwd != new_pwd);
+}
+
+std::unique_ptr<P2PTransportChannel> P2PTransportChannel::Create(
+ absl::string_view transport_name,
+ int component,
+ webrtc::IceTransportInit init) {
+ if (init.async_resolver_factory()) {
+ return absl::WrapUnique(new P2PTransportChannel(
+ transport_name, component, init.port_allocator(), nullptr,
+ std::make_unique<webrtc::WrappingAsyncDnsResolverFactory>(
+ init.async_resolver_factory()),
+ init.event_log(), init.ice_controller_factory(),
+ init.active_ice_controller_factory(), init.field_trials()));
+ } else {
+ return absl::WrapUnique(new P2PTransportChannel(
+ transport_name, component, init.port_allocator(),
+ init.async_dns_resolver_factory(), nullptr, init.event_log(),
+ init.ice_controller_factory(), init.active_ice_controller_factory(),
+ init.field_trials()));
+ }
+}
+
+P2PTransportChannel::P2PTransportChannel(
+ absl::string_view transport_name,
+ int component,
+ PortAllocator* allocator,
+ const webrtc::FieldTrialsView* field_trials)
+ : P2PTransportChannel(transport_name,
+ component,
+ allocator,
+ /* async_dns_resolver_factory= */ nullptr,
+ /* owned_dns_resolver_factory= */ nullptr,
+ /* event_log= */ nullptr,
+ /* ice_controller_factory= */ nullptr,
+ /* active_ice_controller_factory= */ nullptr,
+ field_trials) {}
+
+// Private constructor, called from Create()
+P2PTransportChannel::P2PTransportChannel(
+ absl::string_view transport_name,
+ int component,
+ PortAllocator* allocator,
+ webrtc::AsyncDnsResolverFactoryInterface* async_dns_resolver_factory,
+ 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)
+ : transport_name_(transport_name),
+ component_(component),
+ allocator_(allocator),
+ // If owned_dns_resolver_factory is given, async_dns_resolver_factory is
+ // ignored.
+ async_dns_resolver_factory_(owned_dns_resolver_factory
+ ? owned_dns_resolver_factory.get()
+ : async_dns_resolver_factory),
+ owned_dns_resolver_factory_(std::move(owned_dns_resolver_factory)),
+ network_thread_(rtc::Thread::Current()),
+ incoming_only_(false),
+ error_(0),
+ sort_dirty_(false),
+ remote_ice_mode_(ICEMODE_FULL),
+ ice_role_(ICEROLE_UNKNOWN),
+ tiebreaker_(0),
+ gathering_state_(kIceGatheringNew),
+ weak_ping_interval_(GetWeakPingIntervalInFieldTrial(field_trials)),
+ config_(RECEIVING_TIMEOUT,
+ BACKUP_CONNECTION_PING_INTERVAL,
+ GATHER_ONCE /* continual_gathering_policy */,
+ false /* prioritize_most_likely_candidate_pairs */,
+ STRONG_AND_STABLE_WRITABLE_CONNECTION_PING_INTERVAL,
+ true /* presume_writable_when_fully_relayed */,
+ REGATHER_ON_FAILED_NETWORKS_INTERVAL,
+ RECEIVING_SWITCHING_DELAY) {
+ TRACE_EVENT0("webrtc", "P2PTransportChannel::P2PTransportChannel");
+ RTC_DCHECK(allocator_ != nullptr);
+ // Validate IceConfig even for mostly built-in constant default values in case
+ // we change them.
+ RTC_DCHECK(ValidateIceConfig(config_).ok());
+ webrtc::BasicRegatheringController::Config regathering_config;
+ regathering_config.regather_on_failed_networks_interval =
+ config_.regather_on_failed_networks_interval_or_default();
+ regathering_controller_ =
+ std::make_unique<webrtc::BasicRegatheringController>(
+ regathering_config, this, network_thread_);
+ // We populate the change in the candidate filter to the session taken by
+ // the transport.
+ allocator_->SignalCandidateFilterChanged.connect(
+ this, &P2PTransportChannel::OnCandidateFilterChanged);
+ ice_event_log_.set_event_log(event_log);
+
+ ParseFieldTrials(field_trials);
+
+ IceControllerFactoryArgs args{
+ [this] { return GetState(); }, [this] { return GetIceRole(); },
+ [this](const Connection* connection) {
+ return IsPortPruned(connection->port()) ||
+ IsRemoteCandidatePruned(connection->remote_candidate());
+ },
+ &ice_field_trials_,
+ field_trials ? field_trials->Lookup("WebRTC-IceControllerFieldTrials")
+ : ""};
+ ice_adapter_ = std::make_unique<IceControllerAdapter>(
+ args, ice_controller_factory, active_ice_controller_factory, field_trials,
+ /* transport= */ this);
+}
+
+P2PTransportChannel::~P2PTransportChannel() {
+ TRACE_EVENT0("webrtc", "P2PTransportChannel::~P2PTransportChannel");
+ RTC_DCHECK_RUN_ON(network_thread_);
+ std::vector<Connection*> copy(connections().begin(), connections().end());
+ for (Connection* connection : copy) {
+ connection->SignalDestroyed.disconnect(this);
+ RemoveConnection(connection);
+ connection->Destroy();
+ }
+ resolvers_.clear();
+}
+
+// Add the allocator session to our list so that we know which sessions
+// are still active.
+void P2PTransportChannel::AddAllocatorSession(
+ std::unique_ptr<PortAllocatorSession> session) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+
+ session->set_generation(static_cast<uint32_t>(allocator_sessions_.size()));
+ session->SignalPortReady.connect(this, &P2PTransportChannel::OnPortReady);
+ session->SignalPortsPruned.connect(this, &P2PTransportChannel::OnPortsPruned);
+ session->SignalCandidatesReady.connect(
+ this, &P2PTransportChannel::OnCandidatesReady);
+ session->SignalCandidateError.connect(this,
+ &P2PTransportChannel::OnCandidateError);
+ session->SignalCandidatesRemoved.connect(
+ this, &P2PTransportChannel::OnCandidatesRemoved);
+ session->SignalCandidatesAllocationDone.connect(
+ this, &P2PTransportChannel::OnCandidatesAllocationDone);
+ if (!allocator_sessions_.empty()) {
+ allocator_session()->PruneAllPorts();
+ }
+ allocator_sessions_.push_back(std::move(session));
+ regathering_controller_->set_allocator_session(allocator_session());
+
+ // We now only want to apply new candidates that we receive to the ports
+ // created by this new session because these are replacing those of the
+ // previous sessions.
+ PruneAllPorts();
+}
+
+void P2PTransportChannel::AddConnection(Connection* connection) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ connection->set_receiving_timeout(config_.receiving_timeout);
+ connection->set_unwritable_timeout(config_.ice_unwritable_timeout);
+ connection->set_unwritable_min_checks(config_.ice_unwritable_min_checks);
+ connection->set_inactive_timeout(config_.ice_inactive_timeout);
+ connection->SignalReadPacket.connect(this,
+ &P2PTransportChannel::OnReadPacket);
+ connection->SignalReadyToSend.connect(this,
+ &P2PTransportChannel::OnReadyToSend);
+ connection->SignalStateChange.connect(
+ this, &P2PTransportChannel::OnConnectionStateChange);
+ connection->SignalDestroyed.connect(
+ this, &P2PTransportChannel::OnConnectionDestroyed);
+ connection->SignalNominated.connect(this, &P2PTransportChannel::OnNominated);
+
+ had_connection_ = true;
+
+ connection->set_ice_event_log(&ice_event_log_);
+ connection->SetIceFieldTrials(&ice_field_trials_);
+ LogCandidatePairConfig(connection,
+ webrtc::IceCandidatePairConfigType::kAdded);
+
+ connections_.push_back(connection);
+ ice_adapter_->OnConnectionAdded(connection);
+}
+
+// TODO(bugs.webrtc.org/14367) remove once refactor lands.
+bool P2PTransportChannel::MaybeSwitchSelectedConnection(
+ const Connection* new_connection,
+ IceSwitchReason reason) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+
+ return MaybeSwitchSelectedConnection(
+ reason,
+ ice_adapter_->LegacyShouldSwitchConnection(reason, new_connection));
+}
+
+// TODO(bugs.webrtc.org/14367) remove once refactor lands.
+bool P2PTransportChannel::MaybeSwitchSelectedConnection(
+ IceSwitchReason reason,
+ 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);
+ SwitchSelectedConnection(FromIceController(*result.connection), reason);
+ }
+
+ 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, reason = result.recheck_event->reason]() {
+ SortConnectionsAndUpdateState(reason);
+ }),
+ TimeDelta::Millis(result.recheck_event->recheck_delay_ms));
+ }
+
+ for (const auto* con : result.connections_to_forget_state_on) {
+ FromIceController(con)->ForgetLearnedState();
+ }
+
+ return result.connection.has_value();
+}
+
+void P2PTransportChannel::ForgetLearnedStateForConnections(
+ rtc::ArrayView<const Connection* const> connections) {
+ for (const Connection* con : connections) {
+ FromIceController(con)->ForgetLearnedState();
+ }
+}
+
+void P2PTransportChannel::SetIceRole(IceRole ice_role) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (ice_role_ != ice_role) {
+ ice_role_ = ice_role;
+ for (PortInterface* port : ports_) {
+ port->SetIceRole(ice_role);
+ }
+ // Update role on pruned ports as well, because they may still have
+ // connections alive that should be using the correct role.
+ for (PortInterface* port : pruned_ports_) {
+ port->SetIceRole(ice_role);
+ }
+ }
+}
+
+IceRole P2PTransportChannel::GetIceRole() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return ice_role_;
+}
+
+void P2PTransportChannel::SetIceTiebreaker(uint64_t tiebreaker) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (!ports_.empty() || !pruned_ports_.empty()) {
+ RTC_LOG(LS_ERROR)
+ << "Attempt to change tiebreaker after Port has been allocated.";
+ return;
+ }
+
+ tiebreaker_ = tiebreaker;
+}
+
+IceTransportState P2PTransportChannel::GetState() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return state_;
+}
+
+webrtc::IceTransportState P2PTransportChannel::GetIceTransportState() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return standardized_state_;
+}
+
+const std::string& P2PTransportChannel::transport_name() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return transport_name_;
+}
+
+int P2PTransportChannel::component() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return component_;
+}
+
+bool P2PTransportChannel::writable() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return writable_;
+}
+
+bool P2PTransportChannel::receiving() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return receiving_;
+}
+
+IceGatheringState P2PTransportChannel::gathering_state() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return gathering_state_;
+}
+
+absl::optional<int> P2PTransportChannel::GetRttEstimate() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (selected_connection_ != nullptr &&
+ selected_connection_->rtt_samples() > 0) {
+ return selected_connection_->rtt();
+ } else {
+ return absl::nullopt;
+ }
+}
+
+absl::optional<const CandidatePair>
+P2PTransportChannel::GetSelectedCandidatePair() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (selected_connection_ == nullptr) {
+ return absl::nullopt;
+ }
+
+ CandidatePair pair;
+ pair.local = SanitizeLocalCandidate(selected_connection_->local_candidate());
+ pair.remote =
+ SanitizeRemoteCandidate(selected_connection_->remote_candidate());
+ return pair;
+}
+
+// A channel is considered ICE completed once there is at most one active
+// connection per network and at least one active connection.
+IceTransportState P2PTransportChannel::ComputeState() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (!had_connection_) {
+ return IceTransportState::STATE_INIT;
+ }
+
+ std::vector<Connection*> active_connections;
+ for (Connection* connection : connections()) {
+ if (connection->active()) {
+ active_connections.push_back(connection);
+ }
+ }
+ if (active_connections.empty()) {
+ return IceTransportState::STATE_FAILED;
+ }
+
+ std::set<const rtc::Network*> networks;
+ for (Connection* connection : active_connections) {
+ const rtc::Network* network = connection->network();
+ if (networks.find(network) == networks.end()) {
+ networks.insert(network);
+ } else {
+ RTC_LOG(LS_VERBOSE) << ToString()
+ << ": Ice not completed yet for this channel as "
+ << network->ToString()
+ << " has more than 1 connection.";
+ return IceTransportState::STATE_CONNECTING;
+ }
+ }
+
+ ice_event_log_.DumpCandidatePairDescriptionToMemoryAsConfigEvents();
+ return IceTransportState::STATE_COMPLETED;
+}
+
+// Compute the current RTCIceTransportState as described in
+// https://www.w3.org/TR/webrtc/#dom-rtcicetransportstate
+// TODO(bugs.webrtc.org/9218): Start signaling kCompleted once we have
+// implemented end-of-candidates signalling.
+webrtc::IceTransportState P2PTransportChannel::ComputeIceTransportState()
+ const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ bool has_connection = false;
+ for (Connection* connection : connections()) {
+ if (connection->active()) {
+ has_connection = true;
+ break;
+ }
+ }
+
+ if (had_connection_ && !has_connection) {
+ return webrtc::IceTransportState::kFailed;
+ }
+
+ if (!writable() && has_been_writable_) {
+ return webrtc::IceTransportState::kDisconnected;
+ }
+
+ if (!had_connection_ && !has_connection) {
+ return webrtc::IceTransportState::kNew;
+ }
+
+ if (has_connection && !writable()) {
+ // A candidate pair has been formed by adding a remote candidate
+ // and gathering a local candidate.
+ return webrtc::IceTransportState::kChecking;
+ }
+
+ return webrtc::IceTransportState::kConnected;
+}
+
+void P2PTransportChannel::SetIceParameters(const IceParameters& ice_params) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ RTC_LOG(LS_INFO) << "Set ICE ufrag: " << ice_params.ufrag
+ << " pwd: " << ice_params.pwd << " on transport "
+ << transport_name();
+ ice_parameters_ = ice_params;
+ // Note: Candidate gathering will restart when MaybeStartGathering is next
+ // called.
+}
+
+void P2PTransportChannel::SetRemoteIceParameters(
+ const IceParameters& ice_params) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ RTC_LOG(LS_INFO) << "Received remote ICE parameters: ufrag="
+ << ice_params.ufrag << ", renomination "
+ << (ice_params.renomination ? "enabled" : "disabled");
+ IceParameters* current_ice = remote_ice();
+ if (!current_ice || *current_ice != ice_params) {
+ // Keep the ICE credentials so that newer connections
+ // are prioritized over the older ones.
+ remote_ice_parameters_.push_back(ice_params);
+ }
+
+ // Update the pwd of remote candidate if needed.
+ for (RemoteCandidate& candidate : remote_candidates_) {
+ if (candidate.username() == ice_params.ufrag &&
+ candidate.password().empty()) {
+ candidate.set_password(ice_params.pwd);
+ }
+ }
+ // We need to update the credentials and generation for any peer reflexive
+ // candidates.
+ for (Connection* conn : connections()) {
+ conn->MaybeSetRemoteIceParametersAndGeneration(
+ ice_params, static_cast<int>(remote_ice_parameters_.size() - 1));
+ }
+ // Updating the remote ICE candidate generation could change the sort order.
+ ice_adapter_->OnSortAndSwitchRequest(
+ IceSwitchReason::REMOTE_CANDIDATE_GENERATION_CHANGE);
+}
+
+void P2PTransportChannel::SetRemoteIceMode(IceMode mode) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ remote_ice_mode_ = mode;
+}
+
+// TODO(qingsi): We apply the convention that setting a absl::optional parameter
+// to null restores its default value in the implementation. However, some
+// absl::optional parameters are only processed below if non-null, e.g.,
+// regather_on_failed_networks_interval, and thus there is no way to restore the
+// defaults. Fix this issue later for consistency.
+void P2PTransportChannel::SetIceConfig(const IceConfig& config) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (config_.continual_gathering_policy != config.continual_gathering_policy) {
+ if (!allocator_sessions_.empty()) {
+ RTC_LOG(LS_ERROR) << "Trying to change continual gathering policy "
+ "when gathering has already started!";
+ } else {
+ config_.continual_gathering_policy = config.continual_gathering_policy;
+ RTC_LOG(LS_INFO) << "Set continual_gathering_policy to "
+ << config_.continual_gathering_policy;
+ }
+ }
+
+ if (config_.backup_connection_ping_interval !=
+ config.backup_connection_ping_interval) {
+ config_.backup_connection_ping_interval =
+ config.backup_connection_ping_interval;
+ RTC_LOG(LS_INFO) << "Set backup connection ping interval to "
+ << config_.backup_connection_ping_interval_or_default()
+ << " milliseconds.";
+ }
+ if (config_.receiving_timeout != config.receiving_timeout) {
+ config_.receiving_timeout = config.receiving_timeout;
+ for (Connection* connection : connections()) {
+ connection->set_receiving_timeout(config_.receiving_timeout);
+ }
+ RTC_LOG(LS_INFO) << "Set ICE receiving timeout to "
+ << config_.receiving_timeout_or_default()
+ << " milliseconds";
+ }
+
+ config_.prioritize_most_likely_candidate_pairs =
+ config.prioritize_most_likely_candidate_pairs;
+ RTC_LOG(LS_INFO) << "Set ping most likely connection to "
+ << config_.prioritize_most_likely_candidate_pairs;
+
+ if (config_.stable_writable_connection_ping_interval !=
+ config.stable_writable_connection_ping_interval) {
+ config_.stable_writable_connection_ping_interval =
+ config.stable_writable_connection_ping_interval;
+ RTC_LOG(LS_INFO)
+ << "Set stable_writable_connection_ping_interval to "
+ << config_.stable_writable_connection_ping_interval_or_default();
+ }
+
+ if (config_.presume_writable_when_fully_relayed !=
+ config.presume_writable_when_fully_relayed) {
+ if (!connections().empty()) {
+ RTC_LOG(LS_ERROR) << "Trying to change 'presume writable' "
+ "while connections already exist!";
+ } else {
+ config_.presume_writable_when_fully_relayed =
+ config.presume_writable_when_fully_relayed;
+ RTC_LOG(LS_INFO) << "Set presume writable when fully relayed to "
+ << config_.presume_writable_when_fully_relayed;
+ }
+ }
+
+ config_.surface_ice_candidates_on_ice_transport_type_changed =
+ config.surface_ice_candidates_on_ice_transport_type_changed;
+ if (config_.surface_ice_candidates_on_ice_transport_type_changed &&
+ config_.continual_gathering_policy != GATHER_CONTINUALLY) {
+ RTC_LOG(LS_WARNING)
+ << "surface_ice_candidates_on_ice_transport_type_changed is "
+ "ineffective since we do not gather continually.";
+ }
+
+ if (config_.regather_on_failed_networks_interval !=
+ config.regather_on_failed_networks_interval) {
+ config_.regather_on_failed_networks_interval =
+ config.regather_on_failed_networks_interval;
+ RTC_LOG(LS_INFO)
+ << "Set regather_on_failed_networks_interval to "
+ << config_.regather_on_failed_networks_interval_or_default();
+ }
+
+ if (config_.receiving_switching_delay != config.receiving_switching_delay) {
+ config_.receiving_switching_delay = config.receiving_switching_delay;
+ RTC_LOG(LS_INFO) << "Set receiving_switching_delay to "
+ << config_.receiving_switching_delay_or_default();
+ }
+
+ if (config_.default_nomination_mode != config.default_nomination_mode) {
+ config_.default_nomination_mode = config.default_nomination_mode;
+ RTC_LOG(LS_INFO) << "Set default nomination mode to "
+ << static_cast<int>(config_.default_nomination_mode);
+ }
+
+ if (config_.ice_check_interval_strong_connectivity !=
+ config.ice_check_interval_strong_connectivity) {
+ config_.ice_check_interval_strong_connectivity =
+ config.ice_check_interval_strong_connectivity;
+ RTC_LOG(LS_INFO)
+ << "Set strong ping interval to "
+ << config_.ice_check_interval_strong_connectivity_or_default();
+ }
+
+ if (config_.ice_check_interval_weak_connectivity !=
+ config.ice_check_interval_weak_connectivity) {
+ config_.ice_check_interval_weak_connectivity =
+ config.ice_check_interval_weak_connectivity;
+ RTC_LOG(LS_INFO)
+ << "Set weak ping interval to "
+ << config_.ice_check_interval_weak_connectivity_or_default();
+ }
+
+ if (config_.ice_check_min_interval != config.ice_check_min_interval) {
+ config_.ice_check_min_interval = config.ice_check_min_interval;
+ RTC_LOG(LS_INFO) << "Set min ping interval to "
+ << config_.ice_check_min_interval_or_default();
+ }
+
+ if (config_.ice_unwritable_timeout != config.ice_unwritable_timeout) {
+ config_.ice_unwritable_timeout = config.ice_unwritable_timeout;
+ for (Connection* conn : connections()) {
+ conn->set_unwritable_timeout(config_.ice_unwritable_timeout);
+ }
+ RTC_LOG(LS_INFO) << "Set unwritable timeout to "
+ << config_.ice_unwritable_timeout_or_default();
+ }
+
+ if (config_.ice_unwritable_min_checks != config.ice_unwritable_min_checks) {
+ config_.ice_unwritable_min_checks = config.ice_unwritable_min_checks;
+ for (Connection* conn : connections()) {
+ conn->set_unwritable_min_checks(config_.ice_unwritable_min_checks);
+ }
+ RTC_LOG(LS_INFO) << "Set unwritable min checks to "
+ << config_.ice_unwritable_min_checks_or_default();
+ }
+
+ if (config_.ice_inactive_timeout != config.ice_inactive_timeout) {
+ config_.ice_inactive_timeout = config.ice_inactive_timeout;
+ for (Connection* conn : connections()) {
+ conn->set_inactive_timeout(config_.ice_inactive_timeout);
+ }
+ RTC_LOG(LS_INFO) << "Set inactive timeout to "
+ << config_.ice_inactive_timeout_or_default();
+ }
+
+ if (config_.network_preference != config.network_preference) {
+ config_.network_preference = config.network_preference;
+ ice_adapter_->OnSortAndSwitchRequest(
+ IceSwitchReason::NETWORK_PREFERENCE_CHANGE);
+ RTC_LOG(LS_INFO) << "Set network preference to "
+ << (config_.network_preference.has_value()
+ ? config_.network_preference.value()
+ : -1); // network_preference cannot be bound to
+ // int with value_or.
+ }
+
+ // TODO(qingsi): Resolve the naming conflict of stun_keepalive_delay in
+ // UDPPort and stun_keepalive_interval.
+ if (config_.stun_keepalive_interval != config.stun_keepalive_interval) {
+ config_.stun_keepalive_interval = config.stun_keepalive_interval;
+ allocator_session()->SetStunKeepaliveIntervalForReadyPorts(
+ config_.stun_keepalive_interval);
+ RTC_LOG(LS_INFO) << "Set STUN keepalive interval to "
+ << config.stun_keepalive_interval_or_default();
+ }
+
+ webrtc::BasicRegatheringController::Config regathering_config;
+ regathering_config.regather_on_failed_networks_interval =
+ config_.regather_on_failed_networks_interval_or_default();
+ regathering_controller_->SetConfig(regathering_config);
+
+ config_.vpn_preference = config.vpn_preference;
+ allocator_->SetVpnPreference(config_.vpn_preference);
+
+ ice_adapter_->SetIceConfig(config_);
+
+ RTC_DCHECK(ValidateIceConfig(config_).ok());
+}
+
+void P2PTransportChannel::ParseFieldTrials(
+ const webrtc::FieldTrialsView* field_trials) {
+ if (field_trials == nullptr) {
+ return;
+ }
+
+ if (field_trials->IsEnabled("WebRTC-ExtraICEPing")) {
+ RTC_LOG(LS_INFO) << "Set WebRTC-ExtraICEPing: Enabled";
+ }
+
+ webrtc::StructParametersParser::Create(
+ // go/skylift-light
+ "skip_relay_to_non_relay_connections",
+ &ice_field_trials_.skip_relay_to_non_relay_connections,
+ // Limiting pings sent.
+ "max_outstanding_pings", &ice_field_trials_.max_outstanding_pings,
+ // Delay initial selection of connection.
+ "initial_select_dampening", &ice_field_trials_.initial_select_dampening,
+ // Delay initial selection of connections, that are receiving.
+ "initial_select_dampening_ping_received",
+ &ice_field_trials_.initial_select_dampening_ping_received,
+ // Reply that we support goog ping.
+ "announce_goog_ping", &ice_field_trials_.announce_goog_ping,
+ // Use goog ping if remote support it.
+ "enable_goog_ping", &ice_field_trials_.enable_goog_ping,
+ // How fast does a RTT sample decay.
+ "rtt_estimate_halftime_ms", &ice_field_trials_.rtt_estimate_halftime_ms,
+ // Make sure that nomination reaching ICE controlled asap.
+ "send_ping_on_switch_ice_controlling",
+ &ice_field_trials_.send_ping_on_switch_ice_controlling,
+ // Make sure that nomination reaching ICE controlled asap.
+ "send_ping_on_selected_ice_controlling",
+ &ice_field_trials_.send_ping_on_selected_ice_controlling,
+ // Reply to nomination ASAP.
+ "send_ping_on_nomination_ice_controlled",
+ &ice_field_trials_.send_ping_on_nomination_ice_controlled,
+ // Allow connections to live untouched longer that 30s.
+ "dead_connection_timeout_ms",
+ &ice_field_trials_.dead_connection_timeout_ms,
+ // Stop gathering on strongly connected.
+ "stop_gather_on_strongly_connected",
+ &ice_field_trials_.stop_gather_on_strongly_connected)
+ ->Parse(field_trials->Lookup("WebRTC-IceFieldTrials"));
+
+ if (ice_field_trials_.dead_connection_timeout_ms < 30000) {
+ RTC_LOG(LS_WARNING) << "dead_connection_timeout_ms set to "
+ << ice_field_trials_.dead_connection_timeout_ms
+ << " increasing it to 30000";
+ ice_field_trials_.dead_connection_timeout_ms = 30000;
+ }
+
+ if (ice_field_trials_.skip_relay_to_non_relay_connections) {
+ RTC_LOG(LS_INFO) << "Set skip_relay_to_non_relay_connections";
+ }
+
+ if (ice_field_trials_.max_outstanding_pings.has_value()) {
+ RTC_LOG(LS_INFO) << "Set max_outstanding_pings: "
+ << *ice_field_trials_.max_outstanding_pings;
+ }
+
+ if (ice_field_trials_.initial_select_dampening.has_value()) {
+ RTC_LOG(LS_INFO) << "Set initial_select_dampening: "
+ << *ice_field_trials_.initial_select_dampening;
+ }
+
+ if (ice_field_trials_.initial_select_dampening_ping_received.has_value()) {
+ RTC_LOG(LS_INFO)
+ << "Set initial_select_dampening_ping_received: "
+ << *ice_field_trials_.initial_select_dampening_ping_received;
+ }
+
+ // DSCP override, allow user to specify (any) int value
+ // that will be used for tagging all packets.
+ webrtc::StructParametersParser::Create("override_dscp",
+ &ice_field_trials_.override_dscp)
+ ->Parse(field_trials->Lookup("WebRTC-DscpFieldTrial"));
+
+ if (ice_field_trials_.override_dscp) {
+ SetOption(rtc::Socket::OPT_DSCP, *ice_field_trials_.override_dscp);
+ }
+
+ std::string field_trial_string =
+ field_trials->Lookup("WebRTC-SetSocketReceiveBuffer");
+ int receive_buffer_size_kb = 0;
+ sscanf(field_trial_string.c_str(), "Enabled-%d", &receive_buffer_size_kb);
+ if (receive_buffer_size_kb > 0) {
+ RTC_LOG(LS_INFO) << "Set WebRTC-SetSocketReceiveBuffer: Enabled and set to "
+ << receive_buffer_size_kb << "kb";
+ SetOption(rtc::Socket::OPT_RCVBUF, receive_buffer_size_kb * 1024);
+ }
+
+ ice_field_trials_.piggyback_ice_check_acknowledgement =
+ field_trials->IsEnabled("WebRTC-PiggybackIceCheckAcknowledgement");
+
+ ice_field_trials_.extra_ice_ping =
+ field_trials->IsEnabled("WebRTC-ExtraICEPing");
+}
+
+const IceConfig& P2PTransportChannel::config() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return config_;
+}
+
+// TODO(qingsi): Add tests for the config validation starting from
+// PeerConnection::SetConfiguration.
+// Static
+RTCError P2PTransportChannel::ValidateIceConfig(const IceConfig& config) {
+ if (config.ice_check_interval_strong_connectivity_or_default() <
+ config.ice_check_interval_weak_connectivity.value_or(
+ GetWeakPingIntervalInFieldTrial(nullptr))) {
+ return RTCError(RTCErrorType::INVALID_PARAMETER,
+ "Ping interval of candidate pairs is shorter when ICE is "
+ "strongly connected than that when ICE is weakly "
+ "connected");
+ }
+
+ if (config.receiving_timeout_or_default() <
+ std::max(config.ice_check_interval_strong_connectivity_or_default(),
+ config.ice_check_min_interval_or_default())) {
+ return RTCError(
+ RTCErrorType::INVALID_PARAMETER,
+ "Receiving timeout is shorter than the minimal ping interval.");
+ }
+
+ if (config.backup_connection_ping_interval_or_default() <
+ config.ice_check_interval_strong_connectivity_or_default()) {
+ return RTCError(RTCErrorType::INVALID_PARAMETER,
+ "Ping interval of backup candidate pairs is shorter than "
+ "that of general candidate pairs when ICE is strongly "
+ "connected");
+ }
+
+ if (config.stable_writable_connection_ping_interval_or_default() <
+ config.ice_check_interval_strong_connectivity_or_default()) {
+ return RTCError(RTCErrorType::INVALID_PARAMETER,
+ "Ping interval of stable and writable candidate pairs is "
+ "shorter than that of general candidate pairs when ICE is "
+ "strongly connected");
+ }
+
+ if (config.ice_unwritable_timeout_or_default() >
+ config.ice_inactive_timeout_or_default()) {
+ return RTCError(RTCErrorType::INVALID_PARAMETER,
+ "The timeout period for the writability state to become "
+ "UNRELIABLE is longer than that to become TIMEOUT.");
+ }
+
+ return RTCError::OK();
+}
+
+const Connection* P2PTransportChannel::selected_connection() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return selected_connection_;
+}
+
+int P2PTransportChannel::check_receiving_interval() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return std::max(MIN_CHECK_RECEIVING_INTERVAL,
+ config_.receiving_timeout_or_default() / 10);
+}
+
+void P2PTransportChannel::MaybeStartGathering() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ // TODO(bugs.webrtc.org/14605): ensure tie_breaker_ is set.
+ if (ice_parameters_.ufrag.empty() || ice_parameters_.pwd.empty()) {
+ RTC_LOG(LS_ERROR)
+ << "Cannot gather candidates because ICE parameters are empty"
+ " ufrag: "
+ << ice_parameters_.ufrag << " pwd: " << ice_parameters_.pwd;
+ return;
+ }
+ // Start gathering if we never started before, or if an ICE restart occurred.
+ if (allocator_sessions_.empty() ||
+ IceCredentialsChanged(allocator_sessions_.back()->ice_ufrag(),
+ allocator_sessions_.back()->ice_pwd(),
+ ice_parameters_.ufrag, ice_parameters_.pwd)) {
+ if (gathering_state_ != kIceGatheringGathering) {
+ gathering_state_ = kIceGatheringGathering;
+ SignalGatheringState(this);
+ }
+
+ if (!allocator_sessions_.empty()) {
+ IceRestartState state;
+ if (writable()) {
+ state = IceRestartState::CONNECTED;
+ } else if (IsGettingPorts()) {
+ state = IceRestartState::CONNECTING;
+ } else {
+ state = IceRestartState::DISCONNECTED;
+ }
+ RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.IceRestartState",
+ static_cast<int>(state),
+ static_cast<int>(IceRestartState::MAX_VALUE));
+ }
+
+ for (const auto& session : allocator_sessions_) {
+ if (session->IsStopped()) {
+ continue;
+ }
+ session->StopGettingPorts();
+ }
+
+ // Time for a new allocator.
+ std::unique_ptr<PortAllocatorSession> pooled_session =
+ allocator_->TakePooledSession(transport_name(), component(),
+ ice_parameters_.ufrag,
+ ice_parameters_.pwd);
+ if (pooled_session) {
+ pooled_session->set_ice_tiebreaker(tiebreaker_);
+ AddAllocatorSession(std::move(pooled_session));
+ PortAllocatorSession* raw_pooled_session =
+ allocator_sessions_.back().get();
+ // Process the pooled session's existing candidates/ports, if they exist.
+ OnCandidatesReady(raw_pooled_session,
+ raw_pooled_session->ReadyCandidates());
+ for (PortInterface* port : allocator_sessions_.back()->ReadyPorts()) {
+ OnPortReady(raw_pooled_session, port);
+ }
+ if (allocator_sessions_.back()->CandidatesAllocationDone()) {
+ OnCandidatesAllocationDone(raw_pooled_session);
+ }
+ } else {
+ AddAllocatorSession(allocator_->CreateSession(
+ transport_name(), component(), ice_parameters_.ufrag,
+ ice_parameters_.pwd));
+ allocator_sessions_.back()->set_ice_tiebreaker(tiebreaker_);
+ allocator_sessions_.back()->StartGettingPorts();
+ }
+ }
+}
+
+// A new port is available, attempt to make connections for it
+void P2PTransportChannel::OnPortReady(PortAllocatorSession* session,
+ PortInterface* port) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+
+ // Set in-effect options on the new port
+ for (OptionMap::const_iterator it = options_.begin(); it != options_.end();
+ ++it) {
+ int val = port->SetOption(it->first, it->second);
+ if (val < 0) {
+ // Errors are frequent, so use LS_INFO. bugs.webrtc.org/9221
+ RTC_LOG(LS_INFO) << port->ToString() << ": SetOption(" << it->first
+ << ", " << it->second
+ << ") failed: " << port->GetError();
+ }
+ }
+
+ // Remember the ports and candidates, and signal that candidates are ready.
+ // The session will handle this, and send an initiate/accept/modify message
+ // if one is pending.
+
+ port->SetIceRole(ice_role_);
+ port->SetIceTiebreaker(tiebreaker_);
+ ports_.push_back(port);
+ port->SignalUnknownAddress.connect(this,
+ &P2PTransportChannel::OnUnknownAddress);
+ port->SubscribePortDestroyed(
+ [this](PortInterface* port) { OnPortDestroyed(port); });
+
+ port->SignalRoleConflict.connect(this, &P2PTransportChannel::OnRoleConflict);
+ port->SignalSentPacket.connect(this, &P2PTransportChannel::OnSentPacket);
+
+ // Attempt to create a connection from this new port to all of the remote
+ // candidates that we were given so far.
+
+ std::vector<RemoteCandidate>::iterator iter;
+ for (iter = remote_candidates_.begin(); iter != remote_candidates_.end();
+ ++iter) {
+ CreateConnection(port, *iter, iter->origin_port());
+ }
+
+ ice_adapter_->OnImmediateSortAndSwitchRequest(
+ IceSwitchReason::NEW_CONNECTION_FROM_LOCAL_CANDIDATE);
+}
+
+// A new candidate is available, let listeners know
+void P2PTransportChannel::OnCandidatesReady(
+ PortAllocatorSession* session,
+ const std::vector<Candidate>& candidates) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ for (size_t i = 0; i < candidates.size(); ++i) {
+ SignalCandidateGathered(this, candidates[i]);
+ }
+}
+
+void P2PTransportChannel::OnCandidateError(
+ PortAllocatorSession* session,
+ const IceCandidateErrorEvent& event) {
+ RTC_DCHECK(network_thread_ == rtc::Thread::Current());
+ SignalCandidateError(this, event);
+}
+
+void P2PTransportChannel::OnCandidatesAllocationDone(
+ PortAllocatorSession* session) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (config_.gather_continually()) {
+ RTC_LOG(LS_INFO) << "P2PTransportChannel: " << transport_name()
+ << ", component " << component()
+ << " gathering complete, but using continual "
+ "gathering so not changing gathering state.";
+ return;
+ }
+ gathering_state_ = kIceGatheringComplete;
+ RTC_LOG(LS_INFO) << "P2PTransportChannel: " << transport_name()
+ << ", component " << component() << " gathering complete";
+ SignalGatheringState(this);
+}
+
+// Handle stun packets
+void P2PTransportChannel::OnUnknownAddress(PortInterface* port,
+ const rtc::SocketAddress& address,
+ ProtocolType proto,
+ IceMessage* stun_msg,
+ const std::string& remote_username,
+ bool port_muxed) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+
+ // Port has received a valid stun packet from an address that no Connection
+ // is currently available for. See if we already have a candidate with the
+ // address. If it isn't we need to create new candidate for it.
+ //
+ // TODO(qingsi): There is a caveat of the logic below if we have remote
+ // candidates with hostnames. We could create a prflx candidate that is
+ // identical to a host candidate that are currently in the process of name
+ // resolution. We would not have a duplicate candidate since when adding the
+ // resolved host candidate, FinishingAddingRemoteCandidate does
+ // MaybeUpdatePeerReflexiveCandidate, and the prflx candidate would be updated
+ // to a host candidate. As a result, for a brief moment we would have a prflx
+ // candidate showing a private IP address, though we do not signal prflx
+ // candidates to applications and we could obfuscate the IP addresses of prflx
+ // candidates in P2PTransportChannel::GetStats. The difficulty of preventing
+ // creating the prflx from the beginning is that we do not have a reliable way
+ // to claim two candidates are identical without the address information. If
+ // we always pause the addition of a prflx candidate when there is ongoing
+ // name resolution and dedup after we have a resolved address, we run into the
+ // risk of losing/delaying the addition of a non-identical candidate that
+ // could be the only way to have a connection, if the resolution never
+ // completes or is significantly delayed.
+ const Candidate* candidate = nullptr;
+ for (const Candidate& c : remote_candidates_) {
+ if (c.username() == remote_username && c.address() == address &&
+ c.protocol() == ProtoToString(proto)) {
+ candidate = &c;
+ break;
+ }
+ }
+
+ uint32_t remote_generation = 0;
+ std::string remote_password;
+ // The STUN binding request may arrive after setRemoteDescription and before
+ // adding remote candidate, so we need to set the password to the shared
+ // password and set the generation if the user name matches.
+ const IceParameters* ice_param =
+ FindRemoteIceFromUfrag(remote_username, &remote_generation);
+ // Note: if not found, the remote_generation will still be 0.
+ if (ice_param != nullptr) {
+ remote_password = ice_param->pwd;
+ }
+
+ Candidate remote_candidate;
+ bool remote_candidate_is_new = (candidate == nullptr);
+ if (!remote_candidate_is_new) {
+ remote_candidate = *candidate;
+ } else {
+ // Create a new candidate with this address.
+ // The priority of the candidate is set to the PRIORITY attribute
+ // from the request.
+ const StunUInt32Attribute* priority_attr =
+ stun_msg->GetUInt32(STUN_ATTR_PRIORITY);
+ if (!priority_attr) {
+ RTC_LOG(LS_WARNING) << "P2PTransportChannel::OnUnknownAddress - "
+ "No STUN_ATTR_PRIORITY found in the "
+ "stun request message";
+ port->SendBindingErrorResponse(stun_msg, address, STUN_ERROR_BAD_REQUEST,
+ STUN_ERROR_REASON_BAD_REQUEST);
+ return;
+ }
+ int remote_candidate_priority = priority_attr->value();
+
+ uint16_t network_id = 0;
+ uint16_t network_cost = 0;
+ const StunUInt32Attribute* network_attr =
+ stun_msg->GetUInt32(STUN_ATTR_GOOG_NETWORK_INFO);
+ if (network_attr) {
+ uint32_t network_info = network_attr->value();
+ network_id = static_cast<uint16_t>(network_info >> 16);
+ network_cost = static_cast<uint16_t>(network_info);
+ }
+
+ // RFC 5245
+ // If the source transport address of the request does not match any
+ // existing remote candidates, it represents a new peer reflexive remote
+ // candidate.
+ remote_candidate = Candidate(
+ component(), ProtoToString(proto), address, remote_candidate_priority,
+ remote_username, remote_password, PRFLX_PORT_TYPE, remote_generation,
+ "", network_id, network_cost);
+ if (proto == PROTO_TCP) {
+ remote_candidate.set_tcptype(TCPTYPE_ACTIVE_STR);
+ }
+
+ // From RFC 5245, section-7.2.1.3:
+ // The foundation of the candidate is set to an arbitrary value, different
+ // from the foundation for all other remote candidates.
+ remote_candidate.set_foundation(
+ rtc::ToString(rtc::ComputeCrc32(remote_candidate.id())));
+ }
+
+ // RFC5245, the agent constructs a pair whose local candidate is equal to
+ // the transport address on which the STUN request was received, and a
+ // remote candidate equal to the source transport address where the
+ // request came from.
+
+ // There shouldn't be an existing connection with this remote address.
+ // When ports are muxed, this channel might get multiple unknown address
+ // signals. In that case if the connection is already exists, we should
+ // simply ignore the signal otherwise send server error.
+ if (port->GetConnection(remote_candidate.address())) {
+ if (port_muxed) {
+ RTC_LOG(LS_INFO) << "Connection already exists for peer reflexive "
+ "candidate: "
+ << remote_candidate.ToSensitiveString();
+ return;
+ } else {
+ RTC_DCHECK_NOTREACHED();
+ port->SendBindingErrorResponse(stun_msg, address, STUN_ERROR_SERVER_ERROR,
+ STUN_ERROR_REASON_SERVER_ERROR);
+ return;
+ }
+ }
+
+ Connection* connection =
+ port->CreateConnection(remote_candidate, PortInterface::ORIGIN_THIS_PORT);
+ if (!connection) {
+ // This could happen in some scenarios. For example, a TurnPort may have
+ // had a refresh request timeout, so it won't create connections.
+ port->SendBindingErrorResponse(stun_msg, address, STUN_ERROR_SERVER_ERROR,
+ STUN_ERROR_REASON_SERVER_ERROR);
+ return;
+ }
+
+ RTC_LOG(LS_INFO) << "Adding connection from "
+ << (remote_candidate_is_new ? "peer reflexive"
+ : "resurrected")
+ << " candidate: " << remote_candidate.ToSensitiveString();
+ AddConnection(connection);
+ connection->HandleStunBindingOrGoogPingRequest(stun_msg);
+
+ // Update the list of connections since we just added another. We do this
+ // after sending the response since it could (in principle) delete the
+ // connection in question.
+ ice_adapter_->OnImmediateSortAndSwitchRequest(
+ IceSwitchReason::NEW_CONNECTION_FROM_UNKNOWN_REMOTE_ADDRESS);
+}
+
+void P2PTransportChannel::OnCandidateFilterChanged(uint32_t prev_filter,
+ uint32_t cur_filter) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (prev_filter == cur_filter || allocator_session() == nullptr) {
+ return;
+ }
+ if (config_.surface_ice_candidates_on_ice_transport_type_changed) {
+ allocator_session()->SetCandidateFilter(cur_filter);
+ }
+}
+
+void P2PTransportChannel::OnRoleConflict(PortInterface* port) {
+ SignalRoleConflict(this); // STUN ping will be sent when SetRole is called
+ // from Transport.
+}
+
+const IceParameters* P2PTransportChannel::FindRemoteIceFromUfrag(
+ absl::string_view ufrag,
+ uint32_t* generation) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ const auto& params = remote_ice_parameters_;
+ auto it = std::find_if(
+ params.rbegin(), params.rend(),
+ [ufrag](const IceParameters& param) { return param.ufrag == ufrag; });
+ if (it == params.rend()) {
+ // Not found.
+ return nullptr;
+ }
+ *generation = params.rend() - it - 1;
+ return &(*it);
+}
+
+void P2PTransportChannel::OnNominated(Connection* conn) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ RTC_DCHECK(ice_role_ == ICEROLE_CONTROLLED);
+
+ if (selected_connection_ == conn) {
+ return;
+ }
+
+ if (ice_field_trials_.send_ping_on_nomination_ice_controlled &&
+ conn != nullptr) {
+ SendPingRequestInternal(conn);
+ }
+
+ // TODO(qingsi): RequestSortAndStateUpdate will eventually call
+ // MaybeSwitchSelectedConnection again. Rewrite this logic.
+ if (ice_adapter_->OnImmediateSwitchRequest(
+ IceSwitchReason::NOMINATION_ON_CONTROLLED_SIDE, conn)) {
+ // Now that we have selected a connection, it is time to prune other
+ // connections and update the read/write state of the channel.
+ ice_adapter_->OnSortAndSwitchRequest(
+ IceSwitchReason::NOMINATION_ON_CONTROLLED_SIDE);
+ } else {
+ RTC_LOG(LS_INFO)
+ << "Not switching the selected connection on controlled side yet: "
+ << conn->ToString();
+ }
+}
+
+void P2PTransportChannel::ResolveHostnameCandidate(const Candidate& candidate) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (!async_dns_resolver_factory_) {
+ RTC_LOG(LS_WARNING) << "Dropping ICE candidate with hostname address "
+ "(no AsyncResolverFactory)";
+ return;
+ }
+
+ auto resolver = async_dns_resolver_factory_->Create();
+ auto resptr = resolver.get();
+ resolvers_.emplace_back(candidate, std::move(resolver));
+ resptr->Start(candidate.address(),
+ [this, resptr]() { OnCandidateResolved(resptr); });
+ RTC_LOG(LS_INFO) << "Asynchronously resolving ICE candidate hostname "
+ << candidate.address().HostAsSensitiveURIString();
+}
+
+void P2PTransportChannel::AddRemoteCandidate(const Candidate& candidate) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+
+ uint32_t generation = GetRemoteCandidateGeneration(candidate);
+ // If a remote candidate with a previous generation arrives, drop it.
+ if (generation < remote_ice_generation()) {
+ RTC_LOG(LS_WARNING) << "Dropping a remote candidate because its ufrag "
+ << candidate.username()
+ << " indicates it was for a previous generation.";
+ return;
+ }
+
+ Candidate new_remote_candidate(candidate);
+ new_remote_candidate.set_generation(generation);
+ // ICE candidates don't need to have username and password set, but
+ // the code below this (specifically, ConnectionRequest::Prepare in
+ // port.cc) uses the remote candidates's username. So, we set it
+ // here.
+ if (remote_ice()) {
+ if (candidate.username().empty()) {
+ new_remote_candidate.set_username(remote_ice()->ufrag);
+ }
+ if (new_remote_candidate.username() == remote_ice()->ufrag) {
+ if (candidate.password().empty()) {
+ new_remote_candidate.set_password(remote_ice()->pwd);
+ }
+ } else {
+ // The candidate belongs to the next generation. Its pwd will be set
+ // when the new remote ICE credentials arrive.
+ RTC_LOG(LS_WARNING)
+ << "A remote candidate arrives with an unknown ufrag: "
+ << candidate.username();
+ }
+ }
+
+ if (new_remote_candidate.address().IsUnresolvedIP()) {
+ // Don't do DNS lookups if the IceTransportPolicy is "none" or "relay".
+ bool sharing_host = ((allocator_->candidate_filter() & CF_HOST) != 0);
+ bool sharing_stun = ((allocator_->candidate_filter() & CF_REFLEXIVE) != 0);
+ if (sharing_host || sharing_stun) {
+ ResolveHostnameCandidate(new_remote_candidate);
+ }
+ return;
+ }
+
+ FinishAddingRemoteCandidate(new_remote_candidate);
+}
+
+P2PTransportChannel::CandidateAndResolver::CandidateAndResolver(
+ const Candidate& candidate,
+ std::unique_ptr<webrtc::AsyncDnsResolverInterface>&& resolver)
+ : candidate_(candidate), resolver_(std::move(resolver)) {}
+
+P2PTransportChannel::CandidateAndResolver::~CandidateAndResolver() {}
+
+void P2PTransportChannel::OnCandidateResolved(
+ webrtc::AsyncDnsResolverInterface* resolver) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ auto p =
+ absl::c_find_if(resolvers_, [resolver](const CandidateAndResolver& cr) {
+ return cr.resolver_.get() == resolver;
+ });
+ if (p == resolvers_.end()) {
+ RTC_LOG(LS_ERROR) << "Unexpected AsyncDnsResolver return";
+ RTC_DCHECK_NOTREACHED();
+ return;
+ }
+ Candidate candidate = p->candidate_;
+ AddRemoteCandidateWithResult(candidate, resolver->result());
+ // Now we can delete the resolver.
+ // TODO(bugs.webrtc.org/12651): Replace the stuff below with
+ // resolvers_.erase(p);
+ std::unique_ptr<webrtc::AsyncDnsResolverInterface> to_delete =
+ std::move(p->resolver_);
+ // Delay the actual deletion of the resolver until the lambda executes.
+ network_thread_->PostTask([to_delete = std::move(to_delete)] {});
+ resolvers_.erase(p);
+}
+
+void P2PTransportChannel::AddRemoteCandidateWithResult(
+ Candidate candidate,
+ const webrtc::AsyncDnsResolverResult& result) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (result.GetError()) {
+ RTC_LOG(LS_WARNING) << "Failed to resolve ICE candidate hostname "
+ << candidate.address().HostAsSensitiveURIString()
+ << " with error " << result.GetError();
+ return;
+ }
+
+ rtc::SocketAddress resolved_address;
+ // Prefer IPv6 to IPv4 if we have it (see RFC 5245 Section 15.1).
+ // TODO(zstein): This won't work if we only have IPv4 locally but receive an
+ // AAAA DNS record.
+ bool have_address = result.GetResolvedAddress(AF_INET6, &resolved_address) ||
+ result.GetResolvedAddress(AF_INET, &resolved_address);
+ if (!have_address) {
+ RTC_LOG(LS_INFO) << "ICE candidate hostname "
+ << candidate.address().HostAsSensitiveURIString()
+ << " could not be resolved";
+ return;
+ }
+
+ RTC_LOG(LS_INFO) << "Resolved ICE candidate hostname "
+ << candidate.address().HostAsSensitiveURIString() << " to "
+ << resolved_address.ipaddr().ToSensitiveString();
+ candidate.set_address(resolved_address);
+ FinishAddingRemoteCandidate(candidate);
+}
+
+void P2PTransportChannel::FinishAddingRemoteCandidate(
+ const Candidate& new_remote_candidate) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ // If this candidate matches what was thought to be a peer reflexive
+ // candidate, we need to update the candidate priority/etc.
+ for (Connection* conn : connections()) {
+ conn->MaybeUpdatePeerReflexiveCandidate(new_remote_candidate);
+ }
+
+ // Create connections to this remote candidate.
+ CreateConnections(new_remote_candidate, NULL);
+
+ // Resort the connections list, which may have new elements.
+ ice_adapter_->OnImmediateSortAndSwitchRequest(
+ IceSwitchReason::NEW_CONNECTION_FROM_REMOTE_CANDIDATE);
+}
+
+void P2PTransportChannel::RemoveRemoteCandidate(
+ const Candidate& cand_to_remove) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ auto iter =
+ std::remove_if(remote_candidates_.begin(), remote_candidates_.end(),
+ [cand_to_remove](const Candidate& candidate) {
+ return cand_to_remove.MatchesForRemoval(candidate);
+ });
+ if (iter != remote_candidates_.end()) {
+ RTC_LOG(LS_VERBOSE) << "Removed remote candidate "
+ << cand_to_remove.ToSensitiveString();
+ remote_candidates_.erase(iter, remote_candidates_.end());
+ }
+}
+
+void P2PTransportChannel::RemoveAllRemoteCandidates() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ remote_candidates_.clear();
+}
+
+// Creates connections from all of the ports that we care about to the given
+// remote candidate. The return value is true if we created a connection from
+// the origin port.
+bool P2PTransportChannel::CreateConnections(const Candidate& remote_candidate,
+ PortInterface* origin_port) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+
+ // If we've already seen the new remote candidate (in the current candidate
+ // generation), then we shouldn't try creating connections for it.
+ // We either already have a connection for it, or we previously created one
+ // and then later pruned it. If we don't return, the channel will again
+ // re-create any connections that were previously pruned, which will then
+ // immediately be re-pruned, churning the network for no purpose.
+ // This only applies to candidates received over signaling (i.e. origin_port
+ // is NULL).
+ if (!origin_port && IsDuplicateRemoteCandidate(remote_candidate)) {
+ // return true to indicate success, without creating any new connections.
+ return true;
+ }
+
+ // Add a new connection for this candidate to every port that allows such a
+ // connection (i.e., if they have compatible protocols) and that does not
+ // already have a connection to an equivalent candidate. We must be careful
+ // to make sure that the origin port is included, even if it was pruned,
+ // since that may be the only port that can create this connection.
+ bool created = false;
+ std::vector<PortInterface*>::reverse_iterator it;
+ for (it = ports_.rbegin(); it != ports_.rend(); ++it) {
+ if (CreateConnection(*it, remote_candidate, origin_port)) {
+ if (*it == origin_port)
+ created = true;
+ }
+ }
+
+ if ((origin_port != NULL) && !absl::c_linear_search(ports_, origin_port)) {
+ if (CreateConnection(origin_port, remote_candidate, origin_port))
+ created = true;
+ }
+
+ // Remember this remote candidate so that we can add it to future ports.
+ RememberRemoteCandidate(remote_candidate, origin_port);
+
+ return created;
+}
+
+// Setup a connection object for the local and remote candidate combination.
+// And then listen to connection object for changes.
+bool P2PTransportChannel::CreateConnection(PortInterface* port,
+ const Candidate& remote_candidate,
+ PortInterface* origin_port) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (!port->SupportsProtocol(remote_candidate.protocol())) {
+ return false;
+ }
+
+ if (ice_field_trials_.skip_relay_to_non_relay_connections) {
+ if ((port->Type() != remote_candidate.type()) &&
+ (port->Type() == RELAY_PORT_TYPE ||
+ remote_candidate.type() == RELAY_PORT_TYPE)) {
+ RTC_LOG(LS_INFO) << ToString() << ": skip creating connection "
+ << port->Type() << " to " << remote_candidate.type();
+ return false;
+ }
+ }
+
+ // Look for an existing connection with this remote address. If one is not
+ // found or it is found but the existing remote candidate has an older
+ // generation, then we can create a new connection for this address.
+ Connection* connection = port->GetConnection(remote_candidate.address());
+ if (connection == nullptr || connection->remote_candidate().generation() <
+ remote_candidate.generation()) {
+ // Don't create a connection if this is a candidate we received in a
+ // message and we are not allowed to make outgoing connections.
+ PortInterface::CandidateOrigin origin = GetOrigin(port, origin_port);
+ if (origin == PortInterface::ORIGIN_MESSAGE && incoming_only_) {
+ return false;
+ }
+ Connection* connection = port->CreateConnection(remote_candidate, origin);
+ if (!connection) {
+ return false;
+ }
+ AddConnection(connection);
+ RTC_LOG(LS_INFO) << ToString()
+ << ": Created connection with origin: " << origin
+ << ", total: " << connections().size();
+ return true;
+ }
+
+ // No new connection was created.
+ // It is not legal to try to change any of the parameters of an existing
+ // connection; however, the other side can send a duplicate candidate.
+ if (!remote_candidate.IsEquivalent(connection->remote_candidate())) {
+ RTC_LOG(LS_INFO) << "Attempt to change a remote candidate."
+ " Existing remote candidate: "
+ << connection->remote_candidate().ToSensitiveString()
+ << "New remote candidate: "
+ << remote_candidate.ToSensitiveString();
+ }
+ return false;
+}
+
+bool P2PTransportChannel::FindConnection(const Connection* connection) const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return absl::c_linear_search(connections(), connection);
+}
+
+uint32_t P2PTransportChannel::GetRemoteCandidateGeneration(
+ const Candidate& candidate) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ // If the candidate has a ufrag, use it to find the generation.
+ if (!candidate.username().empty()) {
+ uint32_t generation = 0;
+ if (!FindRemoteIceFromUfrag(candidate.username(), &generation)) {
+ // If the ufrag is not found, assume the next/future generation.
+ generation = static_cast<uint32_t>(remote_ice_parameters_.size());
+ }
+ return generation;
+ }
+ // If candidate generation is set, use that.
+ if (candidate.generation() > 0) {
+ return candidate.generation();
+ }
+ // Otherwise, assume the generation from remote ice parameters.
+ return remote_ice_generation();
+}
+
+// Check if remote candidate is already cached.
+bool P2PTransportChannel::IsDuplicateRemoteCandidate(
+ const Candidate& candidate) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ for (size_t i = 0; i < remote_candidates_.size(); ++i) {
+ if (remote_candidates_[i].IsEquivalent(candidate)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Maintain our remote candidate list, adding this new remote one.
+void P2PTransportChannel::RememberRemoteCandidate(
+ const Candidate& remote_candidate,
+ PortInterface* origin_port) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ // Remove any candidates whose generation is older than this one. The
+ // presence of a new generation indicates that the old ones are not useful.
+ size_t i = 0;
+ while (i < remote_candidates_.size()) {
+ if (remote_candidates_[i].generation() < remote_candidate.generation()) {
+ RTC_LOG(LS_INFO) << "Pruning candidate from old generation: "
+ << remote_candidates_[i].address().ToSensitiveString();
+ remote_candidates_.erase(remote_candidates_.begin() + i);
+ } else {
+ i += 1;
+ }
+ }
+
+ // Make sure this candidate is not a duplicate.
+ if (IsDuplicateRemoteCandidate(remote_candidate)) {
+ RTC_LOG(LS_INFO) << "Duplicate candidate: "
+ << remote_candidate.ToSensitiveString();
+ return;
+ }
+
+ // Try this candidate for all future ports.
+ remote_candidates_.push_back(RemoteCandidate(remote_candidate, origin_port));
+}
+
+// Set options on ourselves is simply setting options on all of our available
+// port objects.
+int P2PTransportChannel::SetOption(rtc::Socket::Option opt, int value) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (ice_field_trials_.override_dscp && opt == rtc::Socket::OPT_DSCP) {
+ value = *ice_field_trials_.override_dscp;
+ }
+
+ OptionMap::iterator it = options_.find(opt);
+ if (it == options_.end()) {
+ options_.insert(std::make_pair(opt, value));
+ } else if (it->second == value) {
+ return 0;
+ } else {
+ it->second = value;
+ }
+
+ for (PortInterface* port : ports_) {
+ int val = port->SetOption(opt, value);
+ if (val < 0) {
+ // Because this also occurs deferred, probably no point in reporting an
+ // error
+ RTC_LOG(LS_WARNING) << "SetOption(" << opt << ", " << value
+ << ") failed: " << port->GetError();
+ }
+ }
+ return 0;
+}
+
+bool P2PTransportChannel::GetOption(rtc::Socket::Option opt, int* value) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+
+ const auto& found = options_.find(opt);
+ if (found == options_.end()) {
+ return false;
+ }
+ *value = found->second;
+ return true;
+}
+
+int P2PTransportChannel::GetError() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return error_;
+}
+
+// Send data to the other side, using our selected connection.
+int P2PTransportChannel::SendPacket(const char* data,
+ size_t len,
+ const rtc::PacketOptions& options,
+ int flags) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (flags != 0) {
+ error_ = EINVAL;
+ return -1;
+ }
+ // If we don't think the connection is working yet, return ENOTCONN
+ // instead of sending a packet that will probably be dropped.
+ if (!ReadyToSend(selected_connection_)) {
+ error_ = ENOTCONN;
+ return -1;
+ }
+
+ packets_sent_++;
+ last_sent_packet_id_ = options.packet_id;
+ rtc::PacketOptions modified_options(options);
+ modified_options.info_signaled_after_sent.packet_type =
+ rtc::PacketType::kData;
+ int sent = selected_connection_->Send(data, len, modified_options);
+ if (sent <= 0) {
+ RTC_DCHECK(sent < 0);
+ error_ = selected_connection_->GetError();
+ return sent;
+ }
+
+ bytes_sent_ += sent;
+ return sent;
+}
+
+bool P2PTransportChannel::GetStats(IceTransportStats* ice_transport_stats) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ // Gather candidate and candidate pair stats.
+ ice_transport_stats->candidate_stats_list.clear();
+ ice_transport_stats->connection_infos.clear();
+
+ if (!allocator_sessions_.empty()) {
+ allocator_session()->GetCandidateStatsFromReadyPorts(
+ &ice_transport_stats->candidate_stats_list);
+ }
+
+ // TODO(qingsi): Remove naming inconsistency for candidate pair/connection.
+ for (Connection* connection : connections()) {
+ ConnectionInfo stats = connection->stats();
+ stats.local_candidate = SanitizeLocalCandidate(stats.local_candidate);
+ stats.remote_candidate = SanitizeRemoteCandidate(stats.remote_candidate);
+ stats.best_connection = (selected_connection_ == connection);
+ ice_transport_stats->connection_infos.push_back(std::move(stats));
+ }
+
+ ice_transport_stats->selected_candidate_pair_changes =
+ selected_candidate_pair_changes_;
+
+ ice_transport_stats->bytes_sent = bytes_sent_;
+ ice_transport_stats->bytes_received = bytes_received_;
+ ice_transport_stats->packets_sent = packets_sent_;
+ ice_transport_stats->packets_received = packets_received_;
+
+ ice_transport_stats->ice_role = GetIceRole();
+ ice_transport_stats->ice_local_username_fragment = ice_parameters_.ufrag;
+ ice_transport_stats->ice_state = ComputeIceTransportState();
+
+ return true;
+}
+
+absl::optional<rtc::NetworkRoute> P2PTransportChannel::network_route() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return network_route_;
+}
+
+rtc::DiffServCodePoint P2PTransportChannel::DefaultDscpValue() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ OptionMap::const_iterator it = options_.find(rtc::Socket::OPT_DSCP);
+ if (it == options_.end()) {
+ return rtc::DSCP_NO_CHANGE;
+ }
+ return static_cast<rtc::DiffServCodePoint>(it->second);
+}
+
+rtc::ArrayView<Connection*> P2PTransportChannel::connections() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return ice_adapter_->LegacyConnections();
+}
+
+void P2PTransportChannel::RemoveConnectionForTest(Connection* connection) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ RTC_DCHECK(FindConnection(connection));
+ connection->SignalDestroyed.disconnect(this);
+ RemoveConnection(connection);
+ RTC_DCHECK(!FindConnection(connection));
+ if (selected_connection_ == connection)
+ selected_connection_ = nullptr;
+ connection->Destroy();
+}
+
+// Monitor connection states.
+void P2PTransportChannel::UpdateConnectionStates() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ int64_t now = rtc::TimeMillis();
+
+ // We need to copy the list of connections since some may delete themselves
+ // when we call UpdateState.
+ // NOTE: We copy the connections() vector in case `UpdateState` triggers the
+ // Connection to be destroyed (which will cause a callback that alters
+ // the connections() vector).
+ std::vector<Connection*> copy(connections().begin(), connections().end());
+ for (Connection* c : copy) {
+ c->UpdateState(now);
+ }
+}
+
+// Prepare for best candidate sorting.
+// TODO(bugs.webrtc.org/14367) remove once refactor lands.
+void P2PTransportChannel::RequestSortAndStateUpdate(
+ IceSwitchReason reason_to_sort) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (!sort_dirty_) {
+ network_thread_->PostTask(
+ SafeTask(task_safety_.flag(), [this, reason_to_sort]() {
+ SortConnectionsAndUpdateState(reason_to_sort);
+ }));
+ sort_dirty_ = true;
+ }
+}
+
+// TODO(bugs.webrtc.org/14367) remove once refactor lands.
+void P2PTransportChannel::MaybeStartPinging() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (started_pinging_) {
+ return;
+ }
+
+ if (ice_adapter_->LegacyHasPingableConnection()) {
+ RTC_LOG(LS_INFO) << ToString()
+ << ": Have a pingable connection for the first time; "
+ "starting to ping.";
+ network_thread_->PostTask(
+ SafeTask(task_safety_.flag(), [this]() { CheckAndPing(); }));
+ regathering_controller_->Start();
+ started_pinging_ = true;
+ }
+}
+
+void P2PTransportChannel::OnStartedPinging() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ RTC_LOG(LS_INFO) << ToString()
+ << ": Have a pingable connection for the first time; "
+ "starting to ping.";
+ regathering_controller_->Start();
+}
+
+bool P2PTransportChannel::IsPortPruned(const Port* port) const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return !absl::c_linear_search(ports_, port);
+}
+
+bool P2PTransportChannel::IsRemoteCandidatePruned(const Candidate& cand) const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return !absl::c_linear_search(remote_candidates_, cand);
+}
+
+bool P2PTransportChannel::PresumedWritable(const Connection* conn) const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return (conn->write_state() == Connection::STATE_WRITE_INIT &&
+ config_.presume_writable_when_fully_relayed &&
+ conn->local_candidate().type() == RELAY_PORT_TYPE &&
+ (conn->remote_candidate().type() == RELAY_PORT_TYPE ||
+ conn->remote_candidate().type() == PRFLX_PORT_TYPE));
+}
+
+// Sort the available connections to find the best one. We also monitor
+// the number of available connections and the current state.
+// TODO(bugs.webrtc.org/14367) remove once refactor lands.
+void P2PTransportChannel::SortConnectionsAndUpdateState(
+ IceSwitchReason reason_to_sort) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+
+ // Make sure the connection states are up-to-date since this affects how they
+ // will be sorted.
+ UpdateConnectionStates();
+
+ // Any changes after this point will require a re-sort.
+ sort_dirty_ = false;
+
+ // If necessary, switch to the new choice. Note that `top_connection` doesn't
+ // have to be writable to become the selected connection although it will
+ // have higher priority if it is writable.
+ MaybeSwitchSelectedConnection(
+ reason_to_sort,
+ ice_adapter_->LegacySortAndSwitchConnection(reason_to_sort));
+
+ // 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 (AllowedToPruneConnections()) {
+ PruneConnections();
+ }
+
+ // Check if all connections are timedout.
+ bool all_connections_timedout = true;
+ for (const Connection* conn : connections()) {
+ if (conn->write_state() != Connection::STATE_WRITE_TIMEOUT) {
+ all_connections_timedout = false;
+ break;
+ }
+ }
+
+ // Now update the writable state of the channel with the information we have
+ // so far.
+ if (all_connections_timedout) {
+ HandleAllTimedOut();
+ }
+
+ // Update the state of this channel.
+ UpdateTransportState();
+
+ // 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 P2PTransportChannel::UpdateState() {
+ // Check if all connections are timedout.
+ bool all_connections_timedout = true;
+ for (const Connection* conn : connections()) {
+ if (conn->write_state() != Connection::STATE_WRITE_TIMEOUT) {
+ all_connections_timedout = false;
+ break;
+ }
+ }
+
+ // Now update the writable state of the channel with the information we have
+ // so far.
+ if (all_connections_timedout) {
+ HandleAllTimedOut();
+ }
+
+ // Update the state of this channel.
+ UpdateTransportState();
+}
+
+bool P2PTransportChannel::AllowedToPruneConnections() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return ice_role_ == ICEROLE_CONTROLLING ||
+ (selected_connection_ && selected_connection_->nominated());
+}
+
+// TODO(bugs.webrtc.org/14367) remove once refactor lands.
+void P2PTransportChannel::PruneConnections() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ std::vector<const Connection*> connections_to_prune =
+ ice_adapter_->LegacyPruneConnections();
+ PruneConnections(connections_to_prune);
+}
+
+bool P2PTransportChannel::PruneConnections(
+ rtc::ArrayView<const Connection* const> connections) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (!AllowedToPruneConnections()) {
+ RTC_LOG(LS_WARNING) << "Not allowed to prune connections";
+ return false;
+ }
+ for (const Connection* conn : connections) {
+ FromIceController(conn)->Prune();
+ }
+ return true;
+}
+
+rtc::NetworkRoute P2PTransportChannel::ConfigureNetworkRoute(
+ const Connection* conn) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return {
+ .connected = ReadyToSend(conn),
+ .local = CreateRouteEndpointFromCandidate(
+ /* local= */ true, conn->local_candidate(),
+ /* uses_turn= */
+ conn->port()->Type() == RELAY_PORT_TYPE),
+ .remote = CreateRouteEndpointFromCandidate(
+ /* local= */ false, conn->remote_candidate(),
+ /* uses_turn= */ conn->remote_candidate().type() == RELAY_PORT_TYPE),
+ .last_sent_packet_id = last_sent_packet_id_,
+ .packet_overhead =
+ conn->local_candidate().address().ipaddr().overhead() +
+ GetProtocolOverhead(conn->local_candidate().protocol())};
+}
+
+void P2PTransportChannel::SwitchSelectedConnection(
+ const Connection* new_connection,
+ IceSwitchReason reason) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ SwitchSelectedConnectionInternal(FromIceController(new_connection), reason);
+}
+
+// Change the selected connection, and let listeners know.
+void P2PTransportChannel::SwitchSelectedConnectionInternal(
+ Connection* conn,
+ IceSwitchReason reason) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ // Note: if conn is NULL, the previous `selected_connection_` has been
+ // destroyed, so don't use it.
+ Connection* old_selected_connection = selected_connection_;
+ selected_connection_ = conn;
+ LogCandidatePairConfig(conn, webrtc::IceCandidatePairConfigType::kSelected);
+ network_route_.reset();
+ if (old_selected_connection) {
+ old_selected_connection->set_selected(false);
+ }
+ if (selected_connection_) {
+ ++nomination_;
+ selected_connection_->set_selected(true);
+ if (old_selected_connection) {
+ RTC_LOG(LS_INFO) << ToString() << ": Previous selected connection: "
+ << old_selected_connection->ToString();
+ }
+ RTC_LOG(LS_INFO) << ToString() << ": New selected connection: "
+ << selected_connection_->ToString();
+ SignalRouteChange(this, selected_connection_->remote_candidate());
+ // This is a temporary, but safe fix to webrtc issue 5705.
+ // TODO(honghaiz): Make all ENOTCONN error routed through the transport
+ // channel so that it knows whether the media channel is allowed to
+ // send; then it will only signal ready-to-send if the media channel
+ // has been disallowed to send.
+ if (selected_connection_->writable() ||
+ PresumedWritable(selected_connection_)) {
+ SignalReadyToSend(this);
+ }
+
+ network_route_.emplace(ConfigureNetworkRoute(selected_connection_));
+ } else {
+ RTC_LOG(LS_INFO) << ToString() << ": No selected connection";
+ }
+
+ if (conn != nullptr && ice_role_ == ICEROLE_CONTROLLING &&
+ ((ice_field_trials_.send_ping_on_switch_ice_controlling &&
+ old_selected_connection != nullptr) ||
+ ice_field_trials_.send_ping_on_selected_ice_controlling)) {
+ SendPingRequestInternal(conn);
+ }
+
+ SignalNetworkRouteChanged(network_route_);
+
+ // Create event for candidate pair change.
+ if (selected_connection_) {
+ CandidatePairChangeEvent pair_change;
+ pair_change.reason = IceSwitchReasonToString(reason);
+ pair_change.selected_candidate_pair = *GetSelectedCandidatePair();
+ pair_change.last_data_received_ms =
+ selected_connection_->last_data_received();
+
+ if (old_selected_connection) {
+ pair_change.estimated_disconnected_time_ms =
+ ComputeEstimatedDisconnectedTimeMs(rtc::TimeMillis(),
+ old_selected_connection);
+ } else {
+ pair_change.estimated_disconnected_time_ms = 0;
+ }
+
+ SignalCandidatePairChanged(pair_change);
+ }
+
+ ++selected_candidate_pair_changes_;
+
+ ice_adapter_->OnConnectionSwitched(selected_connection_);
+}
+
+int64_t P2PTransportChannel::ComputeEstimatedDisconnectedTimeMs(
+ int64_t now_ms,
+ Connection* old_connection) {
+ // TODO(jonaso): nicer keeps estimate of how frequently data _should_ be
+ // received, this could be used to give better estimate (if needed).
+ int64_t last_data_or_old_ping =
+ std::max(old_connection->last_received(), last_data_received_ms_);
+ return (now_ms - last_data_or_old_ping);
+}
+
+// Warning: UpdateTransportState should eventually be called whenever a
+// connection is added, deleted, or the write state of any connection changes so
+// that the transport controller will get the up-to-date channel state. However
+// it should not be called too often; in the case that multiple connection
+// states change, it should be called after all the connection states have
+// changed. For example, we call this at the end of
+// SortConnectionsAndUpdateState.
+void P2PTransportChannel::UpdateTransportState() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ // If our selected connection is "presumed writable" (TURN-TURN with no
+ // CreatePermission required), act like we're already writable to the upper
+ // layers, so they can start media quicker.
+ bool writable =
+ selected_connection_ && (selected_connection_->writable() ||
+ PresumedWritable(selected_connection_));
+ SetWritable(writable);
+
+ bool receiving = false;
+ for (const Connection* connection : connections()) {
+ if (connection->receiving()) {
+ receiving = true;
+ break;
+ }
+ }
+ SetReceiving(receiving);
+
+ IceTransportState state = ComputeState();
+ webrtc::IceTransportState current_standardized_state =
+ ComputeIceTransportState();
+
+ if (state_ != state) {
+ RTC_LOG(LS_INFO) << ToString() << ": Transport channel state changed from "
+ << static_cast<int>(state_) << " to "
+ << static_cast<int>(state);
+ // Check that the requested transition is allowed. Note that
+ // P2PTransportChannel does not (yet) implement a direct mapping of the
+ // ICE states from the standard; the difference is covered by
+ // TransportController and PeerConnection.
+ switch (state_) {
+ case IceTransportState::STATE_INIT:
+ // TODO(deadbeef): Once we implement end-of-candidates signaling,
+ // we shouldn't go from INIT to COMPLETED.
+ RTC_DCHECK(state == IceTransportState::STATE_CONNECTING ||
+ state == IceTransportState::STATE_COMPLETED ||
+ state == IceTransportState::STATE_FAILED);
+ break;
+ case IceTransportState::STATE_CONNECTING:
+ RTC_DCHECK(state == IceTransportState::STATE_COMPLETED ||
+ state == IceTransportState::STATE_FAILED);
+ break;
+ case IceTransportState::STATE_COMPLETED:
+ // TODO(deadbeef): Once we implement end-of-candidates signaling,
+ // we shouldn't go from COMPLETED to CONNECTING.
+ // Though we *can* go from COMPlETED to FAILED, if consent expires.
+ RTC_DCHECK(state == IceTransportState::STATE_CONNECTING ||
+ state == IceTransportState::STATE_FAILED);
+ break;
+ case IceTransportState::STATE_FAILED:
+ // TODO(deadbeef): Once we implement end-of-candidates signaling,
+ // we shouldn't go from FAILED to CONNECTING or COMPLETED.
+ RTC_DCHECK(state == IceTransportState::STATE_CONNECTING ||
+ state == IceTransportState::STATE_COMPLETED);
+ break;
+ default:
+ RTC_DCHECK_NOTREACHED();
+ break;
+ }
+ state_ = state;
+ SignalStateChanged(this);
+ }
+
+ if (standardized_state_ != current_standardized_state) {
+ standardized_state_ = current_standardized_state;
+ SignalIceTransportStateChanged(this);
+ }
+}
+
+void P2PTransportChannel::MaybeStopPortAllocatorSessions() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (!IsGettingPorts()) {
+ return;
+ }
+
+ for (const auto& session : allocator_sessions_) {
+ if (session->IsStopped()) {
+ continue;
+ }
+ // If gathering continually, keep the last session running so that
+ // it can gather candidates if the networks change.
+ if (config_.gather_continually() && session == allocator_sessions_.back()) {
+ session->ClearGettingPorts();
+ } else {
+ session->StopGettingPorts();
+ }
+ }
+}
+
+void P2PTransportChannel::OnSelectedConnectionDestroyed() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ RTC_LOG(LS_INFO) << "Selected connection destroyed. Will choose a new one.";
+ IceSwitchReason reason = IceSwitchReason::SELECTED_CONNECTION_DESTROYED;
+ SwitchSelectedConnectionInternal(nullptr, reason);
+ ice_adapter_->OnSortAndSwitchRequest(reason);
+}
+
+// If all connections timed out, delete them all.
+void P2PTransportChannel::HandleAllTimedOut() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ bool update_selected_connection = false;
+ std::vector<Connection*> copy(connections().begin(), connections().end());
+ for (Connection* connection : copy) {
+ if (selected_connection_ == connection) {
+ selected_connection_ = nullptr;
+ update_selected_connection = true;
+ }
+ connection->SignalDestroyed.disconnect(this);
+ RemoveConnection(connection);
+ connection->Destroy();
+ }
+
+ if (update_selected_connection)
+ OnSelectedConnectionDestroyed();
+}
+
+bool P2PTransportChannel::ReadyToSend(const Connection* connection) const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ // 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));
+}
+
+// Handle queued up check-and-ping request
+// TODO(bugs.webrtc.org/14367) remove once refactor lands.
+void P2PTransportChannel::CheckAndPing() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ // Make sure the states of the connections are up-to-date (since this
+ // affects which ones are pingable).
+ UpdateConnectionStates();
+
+ auto result = ice_adapter_->LegacySelectConnectionToPing(last_ping_sent_ms_);
+ TimeDelta delay = TimeDelta::Millis(result.recheck_delay_ms);
+
+ if (result.connection.value_or(nullptr)) {
+ SendPingRequest(result.connection.value());
+ }
+
+ network_thread_->PostDelayedTask(
+ SafeTask(task_safety_.flag(), [this]() { CheckAndPing(); }), delay);
+}
+
+// This method is only for unit testing.
+Connection* P2PTransportChannel::FindNextPingableConnection() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ const Connection* conn = ice_adapter_->FindNextPingableConnection();
+ if (conn) {
+ return FromIceController(conn);
+ } else {
+ return nullptr;
+ }
+}
+
+int64_t P2PTransportChannel::GetLastPingSentMs() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return last_ping_sent_ms_;
+}
+
+void P2PTransportChannel::SendPingRequest(const Connection* connection) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ SendPingRequestInternal(FromIceController(connection));
+}
+
+void P2PTransportChannel::SendPingRequestInternal(Connection* connection) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ PingConnection(connection);
+ MarkConnectionPinged(connection);
+}
+
+// A connection is considered a backup connection if the channel state
+// is completed, the connection is not the selected connection and it is
+// active.
+void P2PTransportChannel::MarkConnectionPinged(Connection* conn) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ ice_adapter_->OnConnectionPinged(conn);
+}
+
+// Apart from sending ping from `conn` this method also updates
+// `use_candidate_attr` and `nomination` flags. One of the flags is set to
+// nominate `conn` if this channel is in CONTROLLING.
+void P2PTransportChannel::PingConnection(Connection* conn) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ bool use_candidate_attr = false;
+ uint32_t nomination = 0;
+ if (ice_role_ == ICEROLE_CONTROLLING) {
+ bool renomination_supported = ice_parameters_.renomination &&
+ !remote_ice_parameters_.empty() &&
+ remote_ice_parameters_.back().renomination;
+ if (renomination_supported) {
+ nomination = GetNominationAttr(conn);
+ } else {
+ use_candidate_attr = GetUseCandidateAttr(conn);
+ }
+ }
+ conn->set_nomination(nomination);
+ conn->set_use_candidate_attr(use_candidate_attr);
+ last_ping_sent_ms_ = rtc::TimeMillis();
+ conn->Ping(last_ping_sent_ms_);
+}
+
+uint32_t P2PTransportChannel::GetNominationAttr(Connection* conn) const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return (conn == selected_connection_) ? nomination_ : 0;
+}
+
+// Nominate a connection based on the NominationMode.
+bool P2PTransportChannel::GetUseCandidateAttr(Connection* conn) const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return ice_adapter_->GetUseCandidateAttribute(
+ conn, config_.default_nomination_mode, remote_ice_mode_);
+}
+
+// When a connection's state changes, we need to figure out who to use as
+// the selected connection again. It could have become usable, or become
+// unusable.
+void P2PTransportChannel::OnConnectionStateChange(Connection* connection) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+
+ // May stop the allocator session when at least one connection becomes
+ // strongly connected after starting to get ports and the local candidate of
+ // the connection is at the latest generation. It is not enough to check
+ // that the connection becomes weakly connected because the connection may
+ // be changing from (writable, receiving) to (writable, not receiving).
+ if (ice_field_trials_.stop_gather_on_strongly_connected) {
+ bool strongly_connected = !connection->weak();
+ bool latest_generation = connection->local_candidate().generation() >=
+ allocator_session()->generation();
+ if (strongly_connected && latest_generation) {
+ MaybeStopPortAllocatorSessions();
+ }
+ }
+ // We have to unroll the stack before doing this because we may be changing
+ // the state of connections while sorting.
+ ice_adapter_->OnSortAndSwitchRequest(
+ IceSwitchReason::CONNECT_STATE_CHANGE); // "candidate pair state
+ // changed");
+}
+
+// When a connection is removed, edit it out, and then update our best
+// connection.
+void P2PTransportChannel::OnConnectionDestroyed(Connection* connection) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+
+ // Note: the previous selected_connection_ may be destroyed by now, so don't
+ // use it.
+
+ // Remove this connection from the list.
+ RemoveConnection(connection);
+
+ RTC_LOG(LS_INFO) << ToString() << ": Removed connection " << connection
+ << " (" << connections().size() << " remaining)";
+
+ // If this is currently the selected connection, then we need to pick a new
+ // one. The call to SortConnectionsAndUpdateState will pick a new one. It
+ // looks at the current selected connection in order to avoid switching
+ // between fairly similar ones. Since this connection is no longer an
+ // option, we can just set selected to nullptr and re-choose a best assuming
+ // that there was no selected connection.
+ if (selected_connection_ == connection) {
+ OnSelectedConnectionDestroyed();
+ } else {
+ // If a non-selected connection was destroyed, we don't need to re-sort but
+ // we do need to update state, because we could be switching to "failed" or
+ // "completed".
+ UpdateTransportState();
+ }
+}
+
+void P2PTransportChannel::RemoveConnection(const Connection* connection) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ auto it = absl::c_find(connections_, connection);
+ RTC_DCHECK(it != connections_.end());
+ connections_.erase(it);
+ ice_adapter_->OnConnectionDestroyed(connection);
+}
+
+// When a port is destroyed, remove it from our list of ports to use for
+// connection attempts.
+void P2PTransportChannel::OnPortDestroyed(PortInterface* port) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+
+ ports_.erase(std::remove(ports_.begin(), ports_.end(), port), ports_.end());
+ pruned_ports_.erase(
+ std::remove(pruned_ports_.begin(), pruned_ports_.end(), port),
+ pruned_ports_.end());
+ RTC_LOG(LS_INFO) << "Removed port because it is destroyed: " << ports_.size()
+ << " remaining";
+}
+
+void P2PTransportChannel::OnPortsPruned(
+ PortAllocatorSession* session,
+ const std::vector<PortInterface*>& ports) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ for (PortInterface* port : ports) {
+ if (PrunePort(port)) {
+ RTC_LOG(LS_INFO) << "Removed port: " << port->ToString() << " "
+ << ports_.size() << " remaining";
+ }
+ }
+}
+
+void P2PTransportChannel::OnCandidatesRemoved(
+ PortAllocatorSession* session,
+ const std::vector<Candidate>& candidates) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ // Do not signal candidate removals if continual gathering is not enabled,
+ // or if this is not the last session because an ICE restart would have
+ // signaled the remote side to remove all candidates in previous sessions.
+ if (!config_.gather_continually() || session != allocator_session()) {
+ return;
+ }
+
+ std::vector<Candidate> candidates_to_remove;
+ for (Candidate candidate : candidates) {
+ candidate.set_transport_name(transport_name());
+ candidates_to_remove.push_back(candidate);
+ }
+ SignalCandidatesRemoved(this, candidates_to_remove);
+}
+
+void P2PTransportChannel::PruneAllPorts() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ pruned_ports_.insert(pruned_ports_.end(), ports_.begin(), ports_.end());
+ ports_.clear();
+}
+
+bool P2PTransportChannel::PrunePort(PortInterface* port) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ auto it = absl::c_find(ports_, port);
+ // Don't need to do anything if the port has been deleted from the port
+ // list.
+ if (it == ports_.end()) {
+ return false;
+ }
+ ports_.erase(it);
+ pruned_ports_.push_back(port);
+ return true;
+}
+
+// We data is available, let listeners know
+void P2PTransportChannel::OnReadPacket(Connection* connection,
+ const char* data,
+ size_t len,
+ int64_t packet_time_us) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+
+ if (connection == selected_connection_) {
+ // Let the client know of an incoming packet
+ packets_received_++;
+ bytes_received_ += len;
+ RTC_DCHECK(connection->last_data_received() >= last_data_received_ms_);
+ last_data_received_ms_ =
+ std::max(last_data_received_ms_, connection->last_data_received());
+ SignalReadPacket(this, data, len, packet_time_us, 0);
+ return;
+ }
+
+ // Do not deliver, if packet doesn't belong to the correct transport
+ // channel.
+ if (!FindConnection(connection))
+ return;
+
+ packets_received_++;
+ bytes_received_ += len;
+ RTC_DCHECK(connection->last_data_received() >= last_data_received_ms_);
+ last_data_received_ms_ =
+ std::max(last_data_received_ms_, connection->last_data_received());
+
+ // Let the client know of an incoming packet
+ SignalReadPacket(this, data, len, packet_time_us, 0);
+
+ // May need to switch the sending connection based on the receiving media
+ // path if this is the controlled side.
+ if (ice_role_ == ICEROLE_CONTROLLED) {
+ ice_adapter_->OnImmediateSwitchRequest(IceSwitchReason::DATA_RECEIVED,
+ connection);
+ }
+}
+
+void P2PTransportChannel::OnSentPacket(const rtc::SentPacket& sent_packet) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+
+ SignalSentPacket(this, sent_packet);
+}
+
+void P2PTransportChannel::OnReadyToSend(Connection* connection) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (connection == selected_connection_ && writable()) {
+ SignalReadyToSend(this);
+ }
+}
+
+void P2PTransportChannel::SetWritable(bool writable) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (writable_ == writable) {
+ return;
+ }
+ RTC_LOG(LS_VERBOSE) << ToString() << ": Changed writable_ to " << writable;
+ writable_ = writable;
+ if (writable_) {
+ has_been_writable_ = true;
+ SignalReadyToSend(this);
+ }
+ SignalWritableState(this);
+}
+
+void P2PTransportChannel::SetReceiving(bool receiving) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (receiving_ == receiving) {
+ return;
+ }
+ receiving_ = receiving;
+ SignalReceivingState(this);
+}
+
+Candidate P2PTransportChannel::SanitizeLocalCandidate(
+ const Candidate& c) const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ // Delegates to the port allocator.
+ return allocator_->SanitizeCandidate(c);
+}
+
+Candidate P2PTransportChannel::SanitizeRemoteCandidate(
+ const Candidate& c) const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ // If the remote endpoint signaled us an mDNS candidate, we assume it
+ // is supposed to be sanitized.
+ bool use_hostname_address = absl::EndsWith(c.address().hostname(), LOCAL_TLD);
+ // Remove the address for prflx remote candidates. See
+ // https://w3c.github.io/webrtc-stats/#dom-rtcicecandidatestats.
+ use_hostname_address |= c.type() == PRFLX_PORT_TYPE;
+ return c.ToSanitizedCopy(use_hostname_address,
+ false /* filter_related_address */);
+}
+
+void P2PTransportChannel::LogCandidatePairConfig(
+ Connection* conn,
+ webrtc::IceCandidatePairConfigType type) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (conn == nullptr) {
+ return;
+ }
+ ice_event_log_.LogCandidatePairConfig(type, conn->id(),
+ conn->ToLogDescription());
+}
+
+P2PTransportChannel::IceControllerAdapter::IceControllerAdapter(
+ const IceControllerFactoryArgs& args,
+ IceControllerFactoryInterface* ice_controller_factory,
+ ActiveIceControllerFactoryInterface* active_ice_controller_factory,
+ const webrtc::FieldTrialsView* field_trials,
+ P2PTransportChannel* transport)
+ : transport_(transport) {
+ if (UseActiveIceControllerFieldTrialEnabled(field_trials)) {
+ if (active_ice_controller_factory) {
+ ActiveIceControllerFactoryArgs active_args{args,
+ /* ice_agent= */ transport};
+ active_ice_controller_ =
+ active_ice_controller_factory->Create(active_args);
+ } else {
+ active_ice_controller_ = std::make_unique<WrappingActiveIceController>(
+ /* ice_agent= */ transport, ice_controller_factory, args);
+ }
+ } else {
+ if (ice_controller_factory != nullptr) {
+ legacy_ice_controller_ = ice_controller_factory->Create(args);
+ } else {
+ legacy_ice_controller_ = std::make_unique<BasicIceController>(args);
+ }
+ }
+}
+
+P2PTransportChannel::IceControllerAdapter::~IceControllerAdapter() = default;
+
+void P2PTransportChannel::IceControllerAdapter::SetIceConfig(
+ const IceConfig& config) {
+ active_ice_controller_ ? active_ice_controller_->SetIceConfig(config)
+ : legacy_ice_controller_->SetIceConfig(config);
+}
+
+void P2PTransportChannel::IceControllerAdapter::OnConnectionAdded(
+ const Connection* connection) {
+ active_ice_controller_ ? active_ice_controller_->OnConnectionAdded(connection)
+ : legacy_ice_controller_->AddConnection(connection);
+}
+
+void P2PTransportChannel::IceControllerAdapter::OnConnectionSwitched(
+ const Connection* connection) {
+ active_ice_controller_
+ ? active_ice_controller_->OnConnectionSwitched(connection)
+ : legacy_ice_controller_->SetSelectedConnection(connection);
+}
+
+void P2PTransportChannel::IceControllerAdapter::OnConnectionPinged(
+ const Connection* connection) {
+ active_ice_controller_
+ ? active_ice_controller_->OnConnectionPinged(connection)
+ : legacy_ice_controller_->MarkConnectionPinged(connection);
+}
+
+void P2PTransportChannel::IceControllerAdapter::OnConnectionDestroyed(
+ const Connection* connection) {
+ active_ice_controller_
+ ? active_ice_controller_->OnConnectionDestroyed(connection)
+ : legacy_ice_controller_->OnConnectionDestroyed(connection);
+}
+
+void P2PTransportChannel::IceControllerAdapter::OnConnectionUpdated(
+ const Connection* connection) {
+ if (active_ice_controller_) {
+ active_ice_controller_->OnConnectionUpdated(connection);
+ return;
+ }
+ RTC_DCHECK_NOTREACHED();
+}
+
+void P2PTransportChannel::IceControllerAdapter::OnSortAndSwitchRequest(
+ IceSwitchReason reason) {
+ active_ice_controller_
+ ? active_ice_controller_->OnSortAndSwitchRequest(reason)
+ : transport_->RequestSortAndStateUpdate(reason);
+}
+
+void P2PTransportChannel::IceControllerAdapter::OnImmediateSortAndSwitchRequest(
+ IceSwitchReason reason) {
+ active_ice_controller_
+ ? active_ice_controller_->OnImmediateSortAndSwitchRequest(reason)
+ : transport_->SortConnectionsAndUpdateState(reason);
+}
+
+bool P2PTransportChannel::IceControllerAdapter::OnImmediateSwitchRequest(
+ IceSwitchReason reason,
+ const Connection* connection) {
+ return active_ice_controller_
+ ? active_ice_controller_->OnImmediateSwitchRequest(reason,
+ connection)
+ : transport_->MaybeSwitchSelectedConnection(connection, reason);
+}
+
+bool P2PTransportChannel::IceControllerAdapter::GetUseCandidateAttribute(
+ const cricket::Connection* connection,
+ cricket::NominationMode mode,
+ cricket::IceMode remote_ice_mode) const {
+ return active_ice_controller_
+ ? active_ice_controller_->GetUseCandidateAttribute(
+ connection, mode, remote_ice_mode)
+ : legacy_ice_controller_->GetUseCandidateAttr(connection, mode,
+ remote_ice_mode);
+}
+
+const Connection*
+P2PTransportChannel::IceControllerAdapter::FindNextPingableConnection() {
+ return active_ice_controller_
+ ? active_ice_controller_->FindNextPingableConnection()
+ : legacy_ice_controller_->FindNextPingableConnection();
+}
+
+rtc::ArrayView<Connection*>
+P2PTransportChannel::IceControllerAdapter::LegacyConnections() const {
+ RTC_DCHECK_RUN_ON(transport_->network_thread_);
+ if (active_ice_controller_) {
+ return rtc::ArrayView<Connection*>(transport_->connections_.data(),
+ transport_->connections_.size());
+ }
+
+ rtc::ArrayView<const Connection*> res = legacy_ice_controller_->connections();
+ return rtc::ArrayView<Connection*>(const_cast<Connection**>(res.data()),
+ res.size());
+}
+
+bool P2PTransportChannel::IceControllerAdapter::LegacyHasPingableConnection()
+ const {
+ if (active_ice_controller_) {
+ RTC_DCHECK_NOTREACHED();
+ }
+ return legacy_ice_controller_->HasPingableConnection();
+}
+
+IceControllerInterface::PingResult
+P2PTransportChannel::IceControllerAdapter::LegacySelectConnectionToPing(
+ int64_t last_ping_sent_ms) {
+ if (active_ice_controller_) {
+ RTC_DCHECK_NOTREACHED();
+ }
+ return legacy_ice_controller_->SelectConnectionToPing(last_ping_sent_ms);
+}
+
+IceControllerInterface::SwitchResult
+P2PTransportChannel::IceControllerAdapter::LegacyShouldSwitchConnection(
+ IceSwitchReason reason,
+ const Connection* connection) {
+ if (active_ice_controller_) {
+ RTC_DCHECK_NOTREACHED();
+ }
+ return legacy_ice_controller_->ShouldSwitchConnection(reason, connection);
+}
+
+IceControllerInterface::SwitchResult
+P2PTransportChannel::IceControllerAdapter::LegacySortAndSwitchConnection(
+ IceSwitchReason reason) {
+ if (active_ice_controller_) {
+ RTC_DCHECK_NOTREACHED();
+ }
+ return legacy_ice_controller_->SortAndSwitchConnection(reason);
+}
+
+std::vector<const Connection*>
+P2PTransportChannel::IceControllerAdapter::LegacyPruneConnections() {
+ if (active_ice_controller_) {
+ RTC_DCHECK_NOTREACHED();
+ }
+ return legacy_ice_controller_->PruneConnections();
+}
+
+} // namespace cricket
diff --git a/third_party/libwebrtc/p2p/base/p2p_transport_channel.h b/third_party/libwebrtc/p2p/base/p2p_transport_channel.h
new file mode 100644
index 0000000000..f7bfce0e17
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/p2p_transport_channel.h
@@ -0,0 +1,590 @@
+/*
+ * 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/async_resolver_factory.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/basic_async_resolver_factory.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/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/third_party/sigslot/sigslot.h"
+#include "rtc_base/thread.h"
+#include "rtc_base/thread_annotations.h"
+
+namespace webrtc {
+class RtcEventLog;
+} // namespace webrtc
+
+namespace cricket {
+
+// Enum for UMA metrics, used to record whether the channel is
+// connected/connecting/disconnected when ICE restart happens.
+enum class IceRestartState { CONNECTING, CONNECTED, DISCONNECTED, MAX_VALUE };
+
+static const int MIN_PINGS_AT_WEAK_PING_INTERVAL = 3;
+
+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(const 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*> 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();
+ }
+
+ 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;
+ // TODO(bugs.webrtc.org/14367) remove once refactor lands.
+ void RequestSortAndStateUpdate(IceSwitchReason reason_to_sort);
+ // Start pinging if we haven't already started, and we now have a connection
+ // that's pingable.
+ // TODO(bugs.webrtc.org/14367) remove once refactor lands.
+ void MaybeStartPinging();
+ void SendPingRequestInternal(Connection* connection);
+
+ // TODO(bugs.webrtc.org/14367) remove once refactor lands.
+ void SortConnectionsAndUpdateState(IceSwitchReason reason_to_sort);
+ 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 char* data,
+ size_t len,
+ int64_t packet_time_us);
+ void OnSentPacket(const rtc::SentPacket& sent_packet);
+ void OnReadyToSend(Connection* connection);
+ void OnConnectionDestroyed(Connection* connection);
+
+ void OnNominated(Connection* conn);
+
+ // TODO(bugs.webrtc.org/14367) remove once refactor lands.
+ void CheckAndPing();
+
+ void LogCandidatePairConfig(Connection* conn,
+ webrtc::IceCandidatePairConfigType type);
+
+ uint32_t GetNominationAttr(Connection* conn) const;
+ bool GetUseCandidateAttr(Connection* conn) const;
+
+ // Returns true if the new_connection is selected for transmission.
+ // TODO(bugs.webrtc.org/14367) remove once refactor lands.
+ bool MaybeSwitchSelectedConnection(const Connection* new_connection,
+ IceSwitchReason reason);
+ // TODO(bugs.webrtc.org/14367) remove once refactor lands.
+ bool MaybeSwitchSelectedConnection(
+ IceSwitchReason reason,
+ IceControllerInterface::SwitchResult result);
+ bool AllowedToPruneConnections() const;
+ // TODO(bugs.webrtc.org/14367) remove once refactor lands.
+ void PruneConnections();
+
+ // 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 Port* 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);
+
+ // TODO(bugs.webrtc.org/14367) remove once refactor lands.
+ webrtc::ScopedTaskSafety task_safety_;
+ 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_);
+ // TODO(bugs.webrtc.org/14367) remove once refactor lands.
+ bool sort_dirty_ RTC_GUARDED_BY(
+ network_thread_); // indicates whether another sort is needed right now
+ 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 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.
+ // TODO(bugs.webrtc.org/14367) remove once refactor lands.
+ bool started_pinging_ RTC_GUARDED_BY(network_thread_) = false;
+ // 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_);
+
+ // The adapter transparently delegates ICE controller interactions to either
+ // the legacy or the active ICE controller depending on field trials.
+ // TODO(bugs.webrtc.org/14367) replace with active ICE controller eventually.
+ class IceControllerAdapter : public ActiveIceControllerInterface {
+ public:
+ IceControllerAdapter(
+ const IceControllerFactoryArgs& args,
+ IceControllerFactoryInterface* ice_controller_factory,
+ ActiveIceControllerFactoryInterface* active_ice_controller_factory,
+ const webrtc::FieldTrialsView* field_trials,
+ P2PTransportChannel* transport);
+ ~IceControllerAdapter() override;
+
+ // ActiveIceControllerInterface overrides
+ void SetIceConfig(const IceConfig& config) override;
+ void OnConnectionAdded(const Connection* connection) override;
+ void OnConnectionSwitched(const Connection* connection) override;
+ void OnConnectionPinged(const Connection* connection) override;
+ void OnConnectionDestroyed(const Connection* connection) override;
+ void OnConnectionUpdated(const Connection* connection) override;
+ void OnSortAndSwitchRequest(IceSwitchReason reason) override;
+ void OnImmediateSortAndSwitchRequest(IceSwitchReason reason) override;
+ bool OnImmediateSwitchRequest(IceSwitchReason reason,
+ const Connection* connection) override;
+ bool GetUseCandidateAttribute(const Connection* connection,
+ NominationMode mode,
+ IceMode remote_ice_mode) const override;
+ const Connection* FindNextPingableConnection() override;
+
+ // Methods only available with legacy ICE controller.
+ rtc::ArrayView<Connection*> LegacyConnections() const;
+ bool LegacyHasPingableConnection() const;
+ IceControllerInterface::PingResult LegacySelectConnectionToPing(
+ int64_t last_ping_sent_ms);
+ IceControllerInterface::SwitchResult LegacyShouldSwitchConnection(
+ IceSwitchReason reason,
+ const Connection* connection);
+ IceControllerInterface::SwitchResult LegacySortAndSwitchConnection(
+ IceSwitchReason reason);
+ std::vector<const Connection*> LegacyPruneConnections();
+
+ private:
+ P2PTransportChannel* transport_;
+ std::unique_ptr<IceControllerInterface> legacy_ice_controller_;
+ std::unique_ptr<ActiveIceControllerInterface> active_ice_controller_;
+ };
+ std::unique_ptr<IceControllerAdapter> ice_adapter_
+ 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);
+
+ // 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_;
+};
+
+} // namespace cricket
+
+#endif // P2P_BASE_P2P_TRANSPORT_CHANNEL_H_
diff --git a/third_party/libwebrtc/p2p/base/p2p_transport_channel_ice_field_trials.h b/third_party/libwebrtc/p2p/base/p2p_transport_channel_ice_field_trials.h
new file mode 100644
index 0000000000..f19823b21e
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/p2p_transport_channel_ice_field_trials.h
@@ -0,0 +1,77 @@
+/*
+ * 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;
+};
+
+} // namespace cricket
+
+#endif // P2P_BASE_P2P_TRANSPORT_CHANNEL_ICE_FIELD_TRIALS_H_
diff --git a/third_party/libwebrtc/p2p/base/p2p_transport_channel_unittest.cc b/third_party/libwebrtc/p2p/base/p2p_transport_channel_unittest.cc
new file mode 100644
index 0000000000..4d73f013fb
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/p2p_transport_channel_unittest.cc
@@ -0,0 +1,6630 @@
+/*
+ * Copyright 2009 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_transport_channel.h"
+
+#include <list>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+
+#include "absl/algorithm/container.h"
+#include "absl/strings/string_view.h"
+#include "api/test/mock_async_dns_resolver.h"
+#include "p2p/base/active_ice_controller_factory_interface.h"
+#include "p2p/base/active_ice_controller_interface.h"
+#include "p2p/base/basic_ice_controller.h"
+#include "p2p/base/connection.h"
+#include "p2p/base/fake_port_allocator.h"
+#include "p2p/base/ice_transport_internal.h"
+#include "p2p/base/mock_active_ice_controller.h"
+#include "p2p/base/mock_async_resolver.h"
+#include "p2p/base/mock_ice_controller.h"
+#include "p2p/base/packet_transport_internal.h"
+#include "p2p/base/test_stun_server.h"
+#include "p2p/base/test_turn_server.h"
+#include "p2p/client/basic_port_allocator.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/dscp.h"
+#include "rtc_base/fake_clock.h"
+#include "rtc_base/fake_mdns_responder.h"
+#include "rtc_base/fake_network.h"
+#include "rtc_base/firewall_socket_server.h"
+#include "rtc_base/gunit.h"
+#include "rtc_base/helpers.h"
+#include "rtc_base/internal/default_socket_server.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/mdns_responder_interface.h"
+#include "rtc_base/nat_server.h"
+#include "rtc_base/nat_socket_factory.h"
+#include "rtc_base/proxy_server.h"
+#include "rtc_base/socket_address.h"
+#include "rtc_base/ssl_adapter.h"
+#include "rtc_base/strings/string_builder.h"
+#include "rtc_base/thread.h"
+#include "rtc_base/virtual_socket_server.h"
+#include "system_wrappers/include/metrics.h"
+#include "test/scoped_key_value_config.h"
+
+namespace {
+
+using rtc::SocketAddress;
+using ::testing::_;
+using ::testing::Assign;
+using ::testing::Combine;
+using ::testing::Contains;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::InvokeArgument;
+using ::testing::InvokeWithoutArgs;
+using ::testing::Return;
+using ::testing::ReturnRef;
+using ::testing::SaveArg;
+using ::testing::SetArgPointee;
+using ::testing::SizeIs;
+using ::testing::TestWithParam;
+using ::testing::Values;
+using ::testing::WithParamInterface;
+using ::webrtc::PendingTaskSafetyFlag;
+using ::webrtc::SafeTask;
+
+// Default timeout for tests in this file.
+// Should be large enough for slow buildbots to run the tests reliably.
+static const int kDefaultTimeout = 10000;
+static const int kMediumTimeout = 3000;
+static const int kShortTimeout = 1000;
+
+static const int kOnlyLocalPorts = cricket::PORTALLOCATOR_DISABLE_STUN |
+ cricket::PORTALLOCATOR_DISABLE_RELAY |
+ cricket::PORTALLOCATOR_DISABLE_TCP;
+static const int LOW_RTT = 20;
+// Addresses on the public internet.
+static const SocketAddress kPublicAddrs[2] = {SocketAddress("11.11.11.11", 0),
+ SocketAddress("22.22.22.22", 0)};
+// IPv6 Addresses on the public internet.
+static const SocketAddress kIPv6PublicAddrs[2] = {
+ SocketAddress("2400:4030:1:2c00:be30:abcd:efab:cdef", 0),
+ SocketAddress("2600:0:1000:1b03:2e41:38ff:fea6:f2a4", 0)};
+// For configuring multihomed clients.
+static const SocketAddress kAlternateAddrs[2] = {
+ SocketAddress("101.101.101.101", 0), SocketAddress("202.202.202.202", 0)};
+static const SocketAddress kIPv6AlternateAddrs[2] = {
+ SocketAddress("2401:4030:1:2c00:be30:abcd:efab:cdef", 0),
+ SocketAddress("2601:0:1000:1b03:2e41:38ff:fea6:f2a4", 0)};
+// Addresses for HTTP proxy servers.
+static const SocketAddress kHttpsProxyAddrs[2] = {
+ SocketAddress("11.11.11.1", 443), SocketAddress("22.22.22.1", 443)};
+// Addresses for SOCKS proxy servers.
+static const SocketAddress kSocksProxyAddrs[2] = {
+ SocketAddress("11.11.11.1", 1080), SocketAddress("22.22.22.1", 1080)};
+// Internal addresses for NAT boxes.
+static const SocketAddress kNatAddrs[2] = {SocketAddress("192.168.1.1", 0),
+ SocketAddress("192.168.2.1", 0)};
+// Private addresses inside the NAT private networks.
+static const SocketAddress kPrivateAddrs[2] = {
+ SocketAddress("192.168.1.11", 0), SocketAddress("192.168.2.22", 0)};
+// For cascaded NATs, the internal addresses of the inner NAT boxes.
+static const SocketAddress kCascadedNatAddrs[2] = {
+ SocketAddress("192.168.10.1", 0), SocketAddress("192.168.20.1", 0)};
+// For cascaded NATs, private addresses inside the inner private networks.
+static const SocketAddress kCascadedPrivateAddrs[2] = {
+ SocketAddress("192.168.10.11", 0), SocketAddress("192.168.20.22", 0)};
+// The address of the public STUN server.
+static const SocketAddress kStunAddr("99.99.99.1", cricket::STUN_SERVER_PORT);
+// The addresses for the public turn server.
+static const SocketAddress kTurnUdpIntAddr("99.99.99.3",
+ cricket::STUN_SERVER_PORT);
+static const SocketAddress kTurnTcpIntAddr("99.99.99.4",
+ cricket::STUN_SERVER_PORT + 1);
+static const SocketAddress kTurnUdpExtAddr("99.99.99.5", 0);
+static const cricket::RelayCredentials kRelayCredentials("test", "test");
+
+// Based on ICE_UFRAG_LENGTH
+const char* kIceUfrag[4] = {"UF00", "UF01", "UF02", "UF03"};
+// Based on ICE_PWD_LENGTH
+const char* kIcePwd[4] = {
+ "TESTICEPWD00000000000000", "TESTICEPWD00000000000001",
+ "TESTICEPWD00000000000002", "TESTICEPWD00000000000003"};
+const cricket::IceParameters kIceParams[4] = {
+ {kIceUfrag[0], kIcePwd[0], false},
+ {kIceUfrag[1], kIcePwd[1], false},
+ {kIceUfrag[2], kIcePwd[2], false},
+ {kIceUfrag[3], kIcePwd[3], false}};
+
+const uint64_t kLowTiebreaker = 11111;
+const uint64_t kHighTiebreaker = 22222;
+const uint64_t kTiebreakerDefault = 44444;
+
+cricket::IceConfig CreateIceConfig(
+ int receiving_timeout,
+ cricket::ContinualGatheringPolicy continual_gathering_policy,
+ absl::optional<int> backup_ping_interval = absl::nullopt) {
+ cricket::IceConfig config;
+ config.receiving_timeout = receiving_timeout;
+ config.continual_gathering_policy = continual_gathering_policy;
+ config.backup_connection_ping_interval = backup_ping_interval;
+ return config;
+}
+
+cricket::Candidate CreateUdpCandidate(absl::string_view type,
+ absl::string_view ip,
+ int port,
+ int priority,
+ absl::string_view ufrag = "") {
+ cricket::Candidate c;
+ c.set_address(rtc::SocketAddress(ip, port));
+ c.set_component(cricket::ICE_CANDIDATE_COMPONENT_DEFAULT);
+ c.set_protocol(cricket::UDP_PROTOCOL_NAME);
+ c.set_priority(priority);
+ c.set_username(ufrag);
+ c.set_type(type);
+ return c;
+}
+
+cricket::BasicPortAllocator* CreateBasicPortAllocator(
+ rtc::NetworkManager* network_manager,
+ rtc::SocketServer* socket_server,
+ const cricket::ServerAddresses& stun_servers,
+ const rtc::SocketAddress& turn_server_udp,
+ const rtc::SocketAddress& turn_server_tcp) {
+ cricket::RelayServerConfig turn_server;
+ turn_server.credentials = kRelayCredentials;
+ if (!turn_server_udp.IsNil()) {
+ turn_server.ports.push_back(
+ cricket::ProtocolAddress(turn_server_udp, cricket::PROTO_UDP));
+ }
+ if (!turn_server_tcp.IsNil()) {
+ turn_server.ports.push_back(
+ cricket::ProtocolAddress(turn_server_tcp, cricket::PROTO_TCP));
+ }
+ std::vector<cricket::RelayServerConfig> turn_servers(1, turn_server);
+
+ std::unique_ptr<cricket::BasicPortAllocator> allocator =
+ std::make_unique<cricket::BasicPortAllocator>(
+ network_manager,
+ std::make_unique<rtc::BasicPacketSocketFactory>(socket_server));
+ allocator->Initialize();
+ allocator->SetConfiguration(stun_servers, turn_servers, 0, webrtc::NO_PRUNE);
+ return allocator.release();
+}
+
+// An one-shot resolver factory with default return arguments.
+// Resolution is immediate, always succeeds, and returns nonsense.
+class ResolverFactoryFixture : public webrtc::MockAsyncDnsResolverFactory {
+ public:
+ ResolverFactoryFixture() {
+ mock_async_dns_resolver_ = std::make_unique<webrtc::MockAsyncDnsResolver>();
+ EXPECT_CALL(*mock_async_dns_resolver_, Start(_, _))
+ .WillRepeatedly(InvokeArgument<1>());
+ EXPECT_CALL(*mock_async_dns_resolver_, result())
+ .WillOnce(ReturnRef(mock_async_dns_resolver_result_));
+
+ // A default action for GetResolvedAddress. Will be overruled
+ // by SetAddressToReturn.
+ EXPECT_CALL(mock_async_dns_resolver_result_, GetResolvedAddress(_, _))
+ .WillRepeatedly(Return(true));
+
+ EXPECT_CALL(mock_async_dns_resolver_result_, GetError())
+ .WillOnce(Return(0));
+ EXPECT_CALL(*this, Create()).WillOnce([this]() {
+ return std::move(mock_async_dns_resolver_);
+ });
+ }
+
+ void SetAddressToReturn(rtc::SocketAddress address_to_return) {
+ EXPECT_CALL(mock_async_dns_resolver_result_, GetResolvedAddress(_, _))
+ .WillOnce(DoAll(SetArgPointee<1>(address_to_return), Return(true)));
+ }
+ void DelayResolution() {
+ // This function must be called before Create().
+ ASSERT_TRUE(!!mock_async_dns_resolver_);
+ EXPECT_CALL(*mock_async_dns_resolver_, Start(_, _))
+ .WillOnce(SaveArg<1>(&saved_callback_));
+ }
+ void FireDelayedResolution() {
+ // This function must be called after Create().
+ ASSERT_TRUE(saved_callback_);
+ saved_callback_();
+ }
+
+ private:
+ std::unique_ptr<webrtc::MockAsyncDnsResolver> mock_async_dns_resolver_;
+ webrtc::MockAsyncDnsResolverResult mock_async_dns_resolver_result_;
+ std::function<void()> saved_callback_;
+};
+
+bool HasLocalAddress(const cricket::CandidatePairInterface* pair,
+ const SocketAddress& address) {
+ return pair->local_candidate().address().EqualIPs(address);
+}
+
+bool HasRemoteAddress(const cricket::CandidatePairInterface* pair,
+ const SocketAddress& address) {
+ return pair->remote_candidate().address().EqualIPs(address);
+}
+
+} // namespace
+
+namespace cricket {
+
+// This test simulates 2 P2P endpoints that want to establish connectivity
+// with each other over various network topologies and conditions, which can be
+// specified in each individial test.
+// A virtual network (via VirtualSocketServer) along with virtual firewalls and
+// NATs (via Firewall/NATSocketServer) are used to simulate the various network
+// conditions. We can configure the IP addresses of the endpoints,
+// block various types of connectivity, or add arbitrary levels of NAT.
+// We also run a STUN server and a relay server on the virtual network to allow
+// our typical P2P mechanisms to do their thing.
+// For each case, we expect the P2P stack to eventually settle on a specific
+// form of connectivity to the other side. The test checks that the P2P
+// negotiation successfully establishes connectivity within a certain time,
+// and that the result is what we expect.
+// Note that this class is a base class for use by other tests, who will provide
+// specialized test behavior.
+class P2PTransportChannelTestBase : public ::testing::Test,
+ public sigslot::has_slots<> {
+ public:
+ explicit P2PTransportChannelTestBase(absl::string_view field_trials)
+ : field_trials_(field_trials),
+ vss_(new rtc::VirtualSocketServer()),
+ nss_(new rtc::NATSocketServer(vss_.get())),
+ ss_(new rtc::FirewallSocketServer(nss_.get())),
+ main_(ss_.get()),
+ stun_server_(TestStunServer::Create(ss_.get(), kStunAddr)),
+ turn_server_(&main_, ss_.get(), kTurnUdpIntAddr, kTurnUdpExtAddr),
+ socks_server1_(ss_.get(),
+ kSocksProxyAddrs[0],
+ ss_.get(),
+ kSocksProxyAddrs[0]),
+ socks_server2_(ss_.get(),
+ kSocksProxyAddrs[1],
+ ss_.get(),
+ kSocksProxyAddrs[1]),
+ force_relay_(false) {
+ ep1_.role_ = ICEROLE_CONTROLLING;
+ ep2_.role_ = ICEROLE_CONTROLLED;
+
+ ServerAddresses stun_servers;
+ stun_servers.insert(kStunAddr);
+ ep1_.allocator_.reset(CreateBasicPortAllocator(
+ &ep1_.network_manager_, ss_.get(), stun_servers, kTurnUdpIntAddr,
+ rtc::SocketAddress()));
+ ep2_.allocator_.reset(CreateBasicPortAllocator(
+ &ep2_.network_manager_, ss_.get(), stun_servers, kTurnUdpIntAddr,
+ rtc::SocketAddress()));
+
+ ep1_.SetIceTiebreaker(kTiebreakerDefault);
+ ep1_.allocator_->SetIceTiebreaker(kTiebreakerDefault);
+ ep2_.SetIceTiebreaker(kTiebreakerDefault);
+ ep2_.allocator_->SetIceTiebreaker(kTiebreakerDefault);
+ webrtc::metrics::Reset();
+ }
+
+ P2PTransportChannelTestBase()
+ : P2PTransportChannelTestBase(absl::string_view()) {}
+
+ protected:
+ enum Config {
+ OPEN, // Open to the Internet
+ NAT_FULL_CONE, // NAT, no filtering
+ NAT_ADDR_RESTRICTED, // NAT, must send to an addr to recv
+ NAT_PORT_RESTRICTED, // NAT, must send to an addr+port to recv
+ NAT_SYMMETRIC, // NAT, endpoint-dependent bindings
+ NAT_DOUBLE_CONE, // Double NAT, both cone
+ NAT_SYMMETRIC_THEN_CONE, // Double NAT, symmetric outer, cone inner
+ BLOCK_UDP, // Firewall, UDP in/out blocked
+ BLOCK_UDP_AND_INCOMING_TCP, // Firewall, UDP in/out and TCP in blocked
+ BLOCK_ALL_BUT_OUTGOING_HTTP, // Firewall, only TCP out on 80/443
+ PROXY_HTTPS, // All traffic through HTTPS proxy
+ PROXY_SOCKS, // All traffic through SOCKS proxy
+ NUM_CONFIGS
+ };
+
+ struct Result {
+ Result(absl::string_view controlling_type,
+ absl::string_view controlling_protocol,
+ absl::string_view controlled_type,
+ absl::string_view controlled_protocol,
+ int wait)
+ : controlling_type(controlling_type),
+ controlling_protocol(controlling_protocol),
+ controlled_type(controlled_type),
+ controlled_protocol(controlled_protocol),
+ connect_wait(wait) {}
+
+ // The expected candidate type and protocol of the controlling ICE agent.
+ std::string controlling_type;
+ std::string controlling_protocol;
+ // The expected candidate type and protocol of the controlled ICE agent.
+ std::string controlled_type;
+ std::string controlled_protocol;
+ // How long to wait before the correct candidate pair is selected.
+ int connect_wait;
+ };
+
+ struct ChannelData {
+ bool CheckData(const char* data, int len) {
+ bool ret = false;
+ if (!ch_packets_.empty()) {
+ std::string packet = ch_packets_.front();
+ ret = (packet == std::string(data, len));
+ ch_packets_.pop_front();
+ }
+ return ret;
+ }
+
+ std::string name_; // TODO(?) - Currently not used.
+ std::list<std::string> ch_packets_;
+ std::unique_ptr<P2PTransportChannel> ch_;
+ };
+
+ struct CandidateData {
+ IceTransportInternal* channel;
+ Candidate candidate;
+ };
+
+ struct Endpoint : public sigslot::has_slots<> {
+ Endpoint()
+ : role_(ICEROLE_UNKNOWN),
+ tiebreaker_(0),
+ role_conflict_(false),
+ save_candidates_(false) {}
+ bool HasTransport(const rtc::PacketTransportInternal* transport) {
+ return (transport == cd1_.ch_.get() || transport == cd2_.ch_.get());
+ }
+ ChannelData* GetChannelData(rtc::PacketTransportInternal* transport) {
+ if (!HasTransport(transport))
+ return NULL;
+ if (cd1_.ch_.get() == transport)
+ return &cd1_;
+ else
+ return &cd2_;
+ }
+
+ void SetIceRole(IceRole role) { role_ = role; }
+ IceRole ice_role() { return role_; }
+ void SetIceTiebreaker(uint64_t tiebreaker) { tiebreaker_ = tiebreaker; }
+ uint64_t GetIceTiebreaker() { return tiebreaker_; }
+ void OnRoleConflict(bool role_conflict) { role_conflict_ = role_conflict; }
+ bool role_conflict() { return role_conflict_; }
+ void SetAllocationStepDelay(uint32_t delay) {
+ allocator_->set_step_delay(delay);
+ }
+ void SetAllowTcpListen(bool allow_tcp_listen) {
+ allocator_->set_allow_tcp_listen(allow_tcp_listen);
+ }
+
+ void OnIceRegathering(PortAllocatorSession*, IceRegatheringReason reason) {
+ ++ice_regathering_counter_[reason];
+ }
+
+ int GetIceRegatheringCountForReason(IceRegatheringReason reason) {
+ return ice_regathering_counter_[reason];
+ }
+
+ rtc::FakeNetworkManager network_manager_;
+ std::unique_ptr<BasicPortAllocator> allocator_;
+ webrtc::AsyncDnsResolverFactoryInterface* async_dns_resolver_factory_;
+ ChannelData cd1_;
+ ChannelData cd2_;
+ IceRole role_;
+ uint64_t tiebreaker_;
+ bool role_conflict_;
+ bool save_candidates_;
+ std::vector<CandidateData> saved_candidates_;
+ bool ready_to_send_ = false;
+ std::map<IceRegatheringReason, int> ice_regathering_counter_;
+ };
+
+ ChannelData* GetChannelData(rtc::PacketTransportInternal* transport) {
+ if (ep1_.HasTransport(transport))
+ return ep1_.GetChannelData(transport);
+ else
+ return ep2_.GetChannelData(transport);
+ }
+
+ IceParameters IceParamsWithRenomination(const IceParameters& ice,
+ bool renomination) {
+ IceParameters new_ice = ice;
+ new_ice.renomination = renomination;
+ return new_ice;
+ }
+
+ void CreateChannels(const IceConfig& ep1_config,
+ const IceConfig& ep2_config,
+ bool renomination = false) {
+ IceParameters ice_ep1_cd1_ch =
+ IceParamsWithRenomination(kIceParams[0], renomination);
+ IceParameters ice_ep2_cd1_ch =
+ IceParamsWithRenomination(kIceParams[1], renomination);
+ ep1_.cd1_.ch_ = CreateChannel(0, ICE_CANDIDATE_COMPONENT_DEFAULT,
+ ice_ep1_cd1_ch, ice_ep2_cd1_ch);
+ ep2_.cd1_.ch_ = CreateChannel(1, ICE_CANDIDATE_COMPONENT_DEFAULT,
+ ice_ep2_cd1_ch, ice_ep1_cd1_ch);
+ ep1_.cd1_.ch_->SetIceConfig(ep1_config);
+ ep2_.cd1_.ch_->SetIceConfig(ep2_config);
+ ep1_.cd1_.ch_->MaybeStartGathering();
+ ep2_.cd1_.ch_->MaybeStartGathering();
+ ep1_.cd1_.ch_->allocator_session()->SignalIceRegathering.connect(
+ &ep1_, &Endpoint::OnIceRegathering);
+ ep2_.cd1_.ch_->allocator_session()->SignalIceRegathering.connect(
+ &ep2_, &Endpoint::OnIceRegathering);
+ }
+
+ void CreateChannels() {
+ IceConfig default_config;
+ CreateChannels(default_config, default_config, false);
+ }
+
+ std::unique_ptr<P2PTransportChannel> CreateChannel(
+ int endpoint,
+ int component,
+ const IceParameters& local_ice,
+ const IceParameters& remote_ice) {
+ webrtc::IceTransportInit init;
+ init.set_port_allocator(GetAllocator(endpoint));
+ init.set_async_dns_resolver_factory(
+ GetEndpoint(endpoint)->async_dns_resolver_factory_);
+ init.set_field_trials(&field_trials_);
+ auto channel = P2PTransportChannel::Create("test content name", component,
+ std::move(init));
+ channel->SignalReadyToSend.connect(
+ this, &P2PTransportChannelTestBase::OnReadyToSend);
+ channel->SignalCandidateGathered.connect(
+ this, &P2PTransportChannelTestBase::OnCandidateGathered);
+ channel->SignalCandidatesRemoved.connect(
+ this, &P2PTransportChannelTestBase::OnCandidatesRemoved);
+ channel->SignalReadPacket.connect(
+ this, &P2PTransportChannelTestBase::OnReadPacket);
+ channel->SignalRoleConflict.connect(
+ this, &P2PTransportChannelTestBase::OnRoleConflict);
+ channel->SignalNetworkRouteChanged.connect(
+ this, &P2PTransportChannelTestBase::OnNetworkRouteChanged);
+ channel->SignalSentPacket.connect(
+ this, &P2PTransportChannelTestBase::OnSentPacket);
+ channel->SetIceParameters(local_ice);
+ if (remote_ice_parameter_source_ == FROM_SETICEPARAMETERS) {
+ channel->SetRemoteIceParameters(remote_ice);
+ }
+ channel->SetIceRole(GetEndpoint(endpoint)->ice_role());
+ channel->SetIceTiebreaker(GetEndpoint(endpoint)->GetIceTiebreaker());
+ return channel;
+ }
+
+ void DestroyChannels() {
+ safety_->SetNotAlive();
+ ep1_.cd1_.ch_.reset();
+ ep2_.cd1_.ch_.reset();
+ ep1_.cd2_.ch_.reset();
+ ep2_.cd2_.ch_.reset();
+ // Process pending tasks that need to run for cleanup purposes such as
+ // pending deletion of Connection objects (see Connection::Destroy).
+ rtc::Thread::Current()->ProcessMessages(0);
+ }
+ P2PTransportChannel* ep1_ch1() { return ep1_.cd1_.ch_.get(); }
+ P2PTransportChannel* ep1_ch2() { return ep1_.cd2_.ch_.get(); }
+ P2PTransportChannel* ep2_ch1() { return ep2_.cd1_.ch_.get(); }
+ P2PTransportChannel* ep2_ch2() { return ep2_.cd2_.ch_.get(); }
+
+ TestTurnServer* test_turn_server() { return &turn_server_; }
+ rtc::VirtualSocketServer* virtual_socket_server() { return vss_.get(); }
+
+ // Common results.
+ static const Result kLocalUdpToLocalUdp;
+ static const Result kLocalUdpToStunUdp;
+ static const Result kLocalUdpToPrflxUdp;
+ static const Result kPrflxUdpToLocalUdp;
+ static const Result kStunUdpToLocalUdp;
+ static const Result kStunUdpToStunUdp;
+ static const Result kStunUdpToPrflxUdp;
+ static const Result kPrflxUdpToStunUdp;
+ static const Result kLocalUdpToRelayUdp;
+ static const Result kPrflxUdpToRelayUdp;
+ static const Result kRelayUdpToPrflxUdp;
+ static const Result kLocalTcpToLocalTcp;
+ static const Result kLocalTcpToPrflxTcp;
+ static const Result kPrflxTcpToLocalTcp;
+
+ rtc::NATSocketServer* nat() { return nss_.get(); }
+ rtc::FirewallSocketServer* fw() { return ss_.get(); }
+
+ Endpoint* GetEndpoint(int endpoint) {
+ if (endpoint == 0) {
+ return &ep1_;
+ } else if (endpoint == 1) {
+ return &ep2_;
+ } else {
+ return NULL;
+ }
+ }
+ BasicPortAllocator* GetAllocator(int endpoint) {
+ return GetEndpoint(endpoint)->allocator_.get();
+ }
+ void AddAddress(int endpoint, const SocketAddress& addr) {
+ GetEndpoint(endpoint)->network_manager_.AddInterface(addr);
+ }
+ void AddAddress(int endpoint,
+ const SocketAddress& addr,
+ absl::string_view ifname,
+ rtc::AdapterType adapter_type,
+ absl::optional<rtc::AdapterType> underlying_vpn_adapter_type =
+ absl::nullopt) {
+ GetEndpoint(endpoint)->network_manager_.AddInterface(
+ addr, ifname, adapter_type, underlying_vpn_adapter_type);
+ }
+ void RemoveAddress(int endpoint, const SocketAddress& addr) {
+ GetEndpoint(endpoint)->network_manager_.RemoveInterface(addr);
+ fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, addr);
+ }
+ void SetProxy(int endpoint, rtc::ProxyType type) {
+ rtc::ProxyInfo info;
+ info.type = type;
+ info.address = (type == rtc::PROXY_HTTPS) ? kHttpsProxyAddrs[endpoint]
+ : kSocksProxyAddrs[endpoint];
+ GetAllocator(endpoint)->set_proxy("unittest/1.0", info);
+ }
+ void SetAllocatorFlags(int endpoint, int flags) {
+ GetAllocator(endpoint)->set_flags(flags);
+ }
+ void SetIceRole(int endpoint, IceRole role) {
+ GetEndpoint(endpoint)->SetIceRole(role);
+ }
+ void SetIceTiebreaker(int endpoint, uint64_t tiebreaker) {
+ GetEndpoint(endpoint)->SetIceTiebreaker(tiebreaker);
+ }
+ bool GetRoleConflict(int endpoint) {
+ return GetEndpoint(endpoint)->role_conflict();
+ }
+ void SetAllocationStepDelay(int endpoint, uint32_t delay) {
+ return GetEndpoint(endpoint)->SetAllocationStepDelay(delay);
+ }
+ void SetAllowTcpListen(int endpoint, bool allow_tcp_listen) {
+ return GetEndpoint(endpoint)->SetAllowTcpListen(allow_tcp_listen);
+ }
+
+ // Return true if the approprite parts of the expected Result, based
+ // on the local and remote candidate of ep1_ch1, match. This can be
+ // used in an EXPECT_TRUE_WAIT.
+ bool CheckCandidate1(const Result& expected) {
+ const std::string& local_type = LocalCandidate(ep1_ch1())->type();
+ const std::string& local_protocol = LocalCandidate(ep1_ch1())->protocol();
+ const std::string& remote_type = RemoteCandidate(ep1_ch1())->type();
+ const std::string& remote_protocol = RemoteCandidate(ep1_ch1())->protocol();
+ return (local_protocol == expected.controlling_protocol &&
+ remote_protocol == expected.controlled_protocol &&
+ local_type == expected.controlling_type &&
+ remote_type == expected.controlled_type);
+ }
+
+ // EXPECT_EQ on the approprite parts of the expected Result, based
+ // on the local and remote candidate of ep1_ch1. This is like
+ // CheckCandidate1, except that it will provide more detail about
+ // what didn't match.
+ void ExpectCandidate1(const Result& expected) {
+ if (CheckCandidate1(expected)) {
+ return;
+ }
+
+ const std::string& local_type = LocalCandidate(ep1_ch1())->type();
+ const std::string& local_protocol = LocalCandidate(ep1_ch1())->protocol();
+ const std::string& remote_type = RemoteCandidate(ep1_ch1())->type();
+ const std::string& remote_protocol = RemoteCandidate(ep1_ch1())->protocol();
+ EXPECT_EQ(expected.controlling_type, local_type);
+ EXPECT_EQ(expected.controlled_type, remote_type);
+ EXPECT_EQ(expected.controlling_protocol, local_protocol);
+ EXPECT_EQ(expected.controlled_protocol, remote_protocol);
+ }
+
+ // Return true if the approprite parts of the expected Result, based
+ // on the local and remote candidate of ep2_ch1, match. This can be
+ // used in an EXPECT_TRUE_WAIT.
+ bool CheckCandidate2(const Result& expected) {
+ const std::string& local_type = LocalCandidate(ep2_ch1())->type();
+ const std::string& local_protocol = LocalCandidate(ep2_ch1())->protocol();
+ const std::string& remote_type = RemoteCandidate(ep2_ch1())->type();
+ const std::string& remote_protocol = RemoteCandidate(ep2_ch1())->protocol();
+ return (local_protocol == expected.controlled_protocol &&
+ remote_protocol == expected.controlling_protocol &&
+ local_type == expected.controlled_type &&
+ remote_type == expected.controlling_type);
+ }
+
+ // EXPECT_EQ on the approprite parts of the expected Result, based
+ // on the local and remote candidate of ep2_ch1. This is like
+ // CheckCandidate2, except that it will provide more detail about
+ // what didn't match.
+ void ExpectCandidate2(const Result& expected) {
+ if (CheckCandidate2(expected)) {
+ return;
+ }
+
+ const std::string& local_type = LocalCandidate(ep2_ch1())->type();
+ const std::string& local_protocol = LocalCandidate(ep2_ch1())->protocol();
+ const std::string& remote_type = RemoteCandidate(ep2_ch1())->type();
+ const std::string& remote_protocol = RemoteCandidate(ep2_ch1())->protocol();
+ EXPECT_EQ(expected.controlled_type, local_type);
+ EXPECT_EQ(expected.controlling_type, remote_type);
+ EXPECT_EQ(expected.controlled_protocol, local_protocol);
+ EXPECT_EQ(expected.controlling_protocol, remote_protocol);
+ }
+
+ static bool CheckCandidate(P2PTransportChannel* channel,
+ SocketAddress from,
+ SocketAddress to) {
+ auto local_candidate = LocalCandidate(channel);
+ auto remote_candidate = RemoteCandidate(channel);
+ return local_candidate != nullptr &&
+ local_candidate->address().EqualIPs(from) &&
+ remote_candidate != nullptr &&
+ remote_candidate->address().EqualIPs(to);
+ }
+
+ static bool CheckCandidatePair(P2PTransportChannel* ch1,
+ P2PTransportChannel* ch2,
+ SocketAddress from,
+ SocketAddress to) {
+ return CheckCandidate(ch1, from, to) && CheckCandidate(ch2, to, from);
+ }
+
+ static bool CheckConnected(P2PTransportChannel* ch1,
+ P2PTransportChannel* ch2) {
+ return ch1 != nullptr && ch1->receiving() && ch1->writable() &&
+ ch2 != nullptr && ch2->receiving() && ch2->writable();
+ }
+
+ static bool CheckCandidatePairAndConnected(P2PTransportChannel* ch1,
+ P2PTransportChannel* ch2,
+ SocketAddress from,
+ SocketAddress to) {
+ return CheckConnected(ch1, ch2) && CheckCandidatePair(ch1, ch2, from, to);
+ }
+
+ virtual void Test(const Result& expected) {
+ rtc::ScopedFakeClock clock;
+ int64_t connect_start = rtc::TimeMillis();
+ int64_t connect_time;
+
+ // Create the channels and wait for them to connect.
+ CreateChannels();
+ EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()),
+ expected.connect_wait + kShortTimeout, clock);
+ connect_time = rtc::TimeMillis() - connect_start;
+ if (connect_time < expected.connect_wait) {
+ RTC_LOG(LS_INFO) << "Connect time: " << connect_time << " ms";
+ } else {
+ RTC_LOG(LS_INFO) << "Connect time: TIMEOUT (" << expected.connect_wait
+ << " ms)";
+ }
+
+ // Allow a few turns of the crank for the selected connections to emerge.
+ // This may take up to 2 seconds.
+ if (ep1_ch1()->selected_connection() && ep2_ch1()->selected_connection()) {
+ int64_t converge_start = rtc::TimeMillis();
+ int64_t converge_time;
+ // Verifying local and remote channel selected connection information.
+ // This is done only for the RFC 5245 as controlled agent will use
+ // USE-CANDIDATE from controlling (ep1) agent. We can easily predict from
+ // EP1 result matrix.
+ EXPECT_TRUE_SIMULATED_WAIT(
+ CheckCandidate1(expected) && CheckCandidate2(expected),
+ kDefaultTimeout, clock);
+ // Also do EXPECT_EQ on each part so that failures are more verbose.
+ ExpectCandidate1(expected);
+ ExpectCandidate2(expected);
+
+ converge_time = rtc::TimeMillis() - converge_start;
+ int64_t converge_wait = 2000;
+ if (converge_time < converge_wait) {
+ RTC_LOG(LS_INFO) << "Converge time: " << converge_time << " ms";
+ } else {
+ RTC_LOG(LS_INFO) << "Converge time: TIMEOUT (" << converge_time
+ << " ms)";
+ }
+ }
+ // Try sending some data to other end.
+ TestSendRecv(&clock);
+
+ // Destroy the channels, and wait for them to be fully cleaned up.
+ DestroyChannels();
+ }
+
+ void TestSendRecv(rtc::ThreadProcessingFakeClock* clock) {
+ for (int i = 0; i < 10; ++i) {
+ const char* data = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
+ int len = static_cast<int>(strlen(data));
+ // local_channel1 <==> remote_channel1
+ EXPECT_EQ_SIMULATED_WAIT(len, SendData(ep1_ch1(), data, len),
+ kMediumTimeout, *clock);
+ EXPECT_TRUE_SIMULATED_WAIT(CheckDataOnChannel(ep2_ch1(), data, len),
+ kMediumTimeout, *clock);
+ EXPECT_EQ_SIMULATED_WAIT(len, SendData(ep2_ch1(), data, len),
+ kMediumTimeout, *clock);
+ EXPECT_TRUE_SIMULATED_WAIT(CheckDataOnChannel(ep1_ch1(), data, len),
+ kMediumTimeout, *clock);
+ }
+ }
+
+ // This test waits for the transport to become receiving and writable on both
+ // end points. Once they are, the end points set new local ice parameters and
+ // restart the ice gathering. Finally it waits for the transport to select a
+ // new connection using the newly generated ice candidates.
+ // Before calling this function the end points must be configured.
+ void TestHandleIceUfragPasswordChanged() {
+ rtc::ScopedFakeClock clock;
+ ep1_ch1()->SetRemoteIceParameters(kIceParams[1]);
+ ep2_ch1()->SetRemoteIceParameters(kIceParams[0]);
+ EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()),
+ kMediumTimeout, clock);
+
+ const Candidate* old_local_candidate1 = LocalCandidate(ep1_ch1());
+ const Candidate* old_local_candidate2 = LocalCandidate(ep2_ch1());
+ const Candidate* old_remote_candidate1 = RemoteCandidate(ep1_ch1());
+ const Candidate* old_remote_candidate2 = RemoteCandidate(ep2_ch1());
+
+ ep1_ch1()->SetIceParameters(kIceParams[2]);
+ ep1_ch1()->SetRemoteIceParameters(kIceParams[3]);
+ ep1_ch1()->MaybeStartGathering();
+ ep2_ch1()->SetIceParameters(kIceParams[3]);
+
+ ep2_ch1()->SetRemoteIceParameters(kIceParams[2]);
+ ep2_ch1()->MaybeStartGathering();
+
+ EXPECT_TRUE_SIMULATED_WAIT(LocalCandidate(ep1_ch1())->generation() !=
+ old_local_candidate1->generation(),
+ kMediumTimeout, clock);
+ EXPECT_TRUE_SIMULATED_WAIT(LocalCandidate(ep2_ch1())->generation() !=
+ old_local_candidate2->generation(),
+ kMediumTimeout, clock);
+ EXPECT_TRUE_SIMULATED_WAIT(RemoteCandidate(ep1_ch1())->generation() !=
+ old_remote_candidate1->generation(),
+ kMediumTimeout, clock);
+ EXPECT_TRUE_SIMULATED_WAIT(RemoteCandidate(ep2_ch1())->generation() !=
+ old_remote_candidate2->generation(),
+ kMediumTimeout, clock);
+ EXPECT_EQ(1u, RemoteCandidate(ep2_ch1())->generation());
+ EXPECT_EQ(1u, RemoteCandidate(ep1_ch1())->generation());
+ }
+
+ void TestSignalRoleConflict() {
+ rtc::ScopedFakeClock clock;
+ // Default EP1 is in controlling state.
+ SetIceTiebreaker(0, kLowTiebreaker);
+
+ SetIceRole(1, ICEROLE_CONTROLLING);
+ SetIceTiebreaker(1, kHighTiebreaker);
+
+ // Creating channels with both channels role set to CONTROLLING.
+ CreateChannels();
+ // Since both the channels initiated with controlling state and channel2
+ // has higher tiebreaker value, channel1 should receive SignalRoleConflict.
+ EXPECT_TRUE_SIMULATED_WAIT(GetRoleConflict(0), kShortTimeout, clock);
+ EXPECT_FALSE(GetRoleConflict(1));
+
+ EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()),
+ kShortTimeout, clock);
+
+ EXPECT_TRUE(ep1_ch1()->selected_connection() &&
+ ep2_ch1()->selected_connection());
+
+ TestSendRecv(&clock);
+ DestroyChannels();
+ }
+
+ void TestPacketInfoIsSet(rtc::PacketInfo info) {
+ EXPECT_NE(info.packet_type, rtc::PacketType::kUnknown);
+ EXPECT_NE(info.protocol, rtc::PacketInfoProtocolType::kUnknown);
+ EXPECT_TRUE(info.network_id.has_value());
+ }
+
+ void OnReadyToSend(rtc::PacketTransportInternal* transport) {
+ GetEndpoint(transport)->ready_to_send_ = true;
+ }
+
+ // We pass the candidates directly to the other side.
+ void OnCandidateGathered(IceTransportInternal* ch, const Candidate& c) {
+ if (force_relay_ && c.type() != RELAY_PORT_TYPE)
+ return;
+
+ if (GetEndpoint(ch)->save_candidates_) {
+ GetEndpoint(ch)->saved_candidates_.push_back(
+ {.channel = ch, .candidate = c});
+ } else {
+ main_.PostTask(SafeTask(
+ safety_, [this, ch, c = c]() mutable { AddCandidate(ch, c); }));
+ }
+ }
+
+ void OnNetworkRouteChanged(absl::optional<rtc::NetworkRoute> network_route) {
+ // If the `network_route` is unset, don't count. This is used in the case
+ // when the network on remote side is down, the signal will be fired with an
+ // unset network route and it shouldn't trigger a connection switch.
+ if (network_route) {
+ ++selected_candidate_pair_switches_;
+ }
+ }
+
+ int reset_selected_candidate_pair_switches() {
+ int switches = selected_candidate_pair_switches_;
+ selected_candidate_pair_switches_ = 0;
+ return switches;
+ }
+
+ void PauseCandidates(int endpoint) {
+ GetEndpoint(endpoint)->save_candidates_ = true;
+ }
+
+ void OnCandidatesRemoved(IceTransportInternal* ch,
+ const std::vector<Candidate>& candidates) {
+ // Candidate removals are not paused.
+ main_.PostTask(SafeTask(safety_, [this, ch, candidates]() mutable {
+ P2PTransportChannel* rch = GetRemoteChannel(ch);
+ if (rch == nullptr) {
+ return;
+ }
+ for (const Candidate& c : candidates) {
+ RTC_LOG(LS_INFO) << "Removed remote candidate " << c.ToString();
+ rch->RemoveRemoteCandidate(c);
+ }
+ }));
+ }
+
+ // Tcp candidate verification has to be done when they are generated.
+ void VerifySavedTcpCandidates(int endpoint, absl::string_view tcptype) {
+ for (auto& data : GetEndpoint(endpoint)->saved_candidates_) {
+ EXPECT_EQ(data.candidate.protocol(), TCP_PROTOCOL_NAME);
+ EXPECT_EQ(data.candidate.tcptype(), tcptype);
+ if (data.candidate.tcptype() == TCPTYPE_ACTIVE_STR) {
+ EXPECT_EQ(data.candidate.address().port(), DISCARD_PORT);
+ } else if (data.candidate.tcptype() == TCPTYPE_PASSIVE_STR) {
+ EXPECT_NE(data.candidate.address().port(), DISCARD_PORT);
+ } else {
+ FAIL() << "Unknown tcptype: " << data.candidate.tcptype();
+ }
+ }
+ }
+
+ void ResumeCandidates(int endpoint) {
+ Endpoint* ed = GetEndpoint(endpoint);
+ std::vector<CandidateData> candidates = std::move(ed->saved_candidates_);
+ if (!candidates.empty()) {
+ main_.PostTask(SafeTask(
+ safety_, [this, candidates = std::move(candidates)]() mutable {
+ for (CandidateData& data : candidates) {
+ AddCandidate(data.channel, data.candidate);
+ }
+ }));
+ }
+ ed->saved_candidates_.clear();
+ ed->save_candidates_ = false;
+ }
+
+ void AddCandidate(IceTransportInternal* channel, Candidate& candidate) {
+ P2PTransportChannel* rch = GetRemoteChannel(channel);
+ if (rch == nullptr) {
+ return;
+ }
+ if (remote_ice_parameter_source_ != FROM_CANDIDATE) {
+ candidate.set_username("");
+ candidate.set_password("");
+ }
+ RTC_LOG(LS_INFO) << "Candidate(" << channel->component() << "->"
+ << rch->component() << "): " << candidate.ToString();
+ rch->AddRemoteCandidate(candidate);
+ }
+
+ void OnReadPacket(rtc::PacketTransportInternal* transport,
+ const char* data,
+ size_t len,
+ const int64_t& /* packet_time_us */,
+ int flags) {
+ std::list<std::string>& packets = GetPacketList(transport);
+ packets.push_front(std::string(data, len));
+ }
+
+ void OnRoleConflict(IceTransportInternal* channel) {
+ GetEndpoint(channel)->OnRoleConflict(true);
+ IceRole new_role = GetEndpoint(channel)->ice_role() == ICEROLE_CONTROLLING
+ ? ICEROLE_CONTROLLED
+ : ICEROLE_CONTROLLING;
+ channel->SetIceRole(new_role);
+ }
+
+ void OnSentPacket(rtc::PacketTransportInternal* transport,
+ const rtc::SentPacket& packet) {
+ TestPacketInfoIsSet(packet.info);
+ }
+
+ int SendData(IceTransportInternal* channel, const char* data, size_t len) {
+ rtc::PacketOptions options;
+ return channel->SendPacket(data, len, options, 0);
+ }
+ bool CheckDataOnChannel(IceTransportInternal* channel,
+ const char* data,
+ int len) {
+ return GetChannelData(channel)->CheckData(data, len);
+ }
+ static const Candidate* LocalCandidate(P2PTransportChannel* ch) {
+ return (ch && ch->selected_connection())
+ ? &ch->selected_connection()->local_candidate()
+ : NULL;
+ }
+ static const Candidate* RemoteCandidate(P2PTransportChannel* ch) {
+ return (ch && ch->selected_connection())
+ ? &ch->selected_connection()->remote_candidate()
+ : NULL;
+ }
+ Endpoint* GetEndpoint(rtc::PacketTransportInternal* transport) {
+ if (ep1_.HasTransport(transport)) {
+ return &ep1_;
+ } else if (ep2_.HasTransport(transport)) {
+ return &ep2_;
+ } else {
+ return NULL;
+ }
+ }
+ P2PTransportChannel* GetRemoteChannel(IceTransportInternal* ch) {
+ if (ch == ep1_ch1())
+ return ep2_ch1();
+ else if (ch == ep1_ch2())
+ return ep2_ch2();
+ else if (ch == ep2_ch1())
+ return ep1_ch1();
+ else if (ch == ep2_ch2())
+ return ep1_ch2();
+ else
+ return NULL;
+ }
+ std::list<std::string>& GetPacketList(
+ rtc::PacketTransportInternal* transport) {
+ return GetChannelData(transport)->ch_packets_;
+ }
+
+ enum RemoteIceParameterSource { FROM_CANDIDATE, FROM_SETICEPARAMETERS };
+
+ // How does the test pass ICE parameters to the P2PTransportChannel?
+ // On the candidate itself, or through SetRemoteIceParameters?
+ // Goes through the candidate itself by default.
+ void set_remote_ice_parameter_source(RemoteIceParameterSource source) {
+ remote_ice_parameter_source_ = source;
+ }
+
+ void set_force_relay(bool relay) { force_relay_ = relay; }
+
+ void ConnectSignalNominated(Connection* conn) {
+ conn->SignalNominated.connect(this,
+ &P2PTransportChannelTestBase::OnNominated);
+ }
+
+ void OnNominated(Connection* conn) { nominated_ = true; }
+ bool nominated() { return nominated_; }
+
+ webrtc::test::ScopedKeyValueConfig field_trials_;
+
+ private:
+ std::unique_ptr<rtc::VirtualSocketServer> vss_;
+ std::unique_ptr<rtc::NATSocketServer> nss_;
+ std::unique_ptr<rtc::FirewallSocketServer> ss_;
+ rtc::AutoSocketServerThread main_;
+ rtc::scoped_refptr<PendingTaskSafetyFlag> safety_ =
+ PendingTaskSafetyFlag::Create();
+ std::unique_ptr<TestStunServer> stun_server_;
+ TestTurnServer turn_server_;
+ rtc::SocksProxyServer socks_server1_;
+ rtc::SocksProxyServer socks_server2_;
+ Endpoint ep1_;
+ Endpoint ep2_;
+ RemoteIceParameterSource remote_ice_parameter_source_ = FROM_CANDIDATE;
+ bool force_relay_;
+ int selected_candidate_pair_switches_ = 0;
+
+ bool nominated_ = false;
+};
+
+// The tests have only a few outcomes, which we predefine.
+const P2PTransportChannelTestBase::Result
+ P2PTransportChannelTestBase::kLocalUdpToLocalUdp("local",
+ "udp",
+ "local",
+ "udp",
+ 1000);
+const P2PTransportChannelTestBase::Result
+ P2PTransportChannelTestBase::kLocalUdpToStunUdp("local",
+ "udp",
+ "stun",
+ "udp",
+ 1000);
+const P2PTransportChannelTestBase::Result
+ P2PTransportChannelTestBase::kLocalUdpToPrflxUdp("local",
+ "udp",
+ "prflx",
+ "udp",
+ 1000);
+const P2PTransportChannelTestBase::Result
+ P2PTransportChannelTestBase::kPrflxUdpToLocalUdp("prflx",
+ "udp",
+ "local",
+ "udp",
+ 1000);
+const P2PTransportChannelTestBase::Result
+ P2PTransportChannelTestBase::kStunUdpToLocalUdp("stun",
+ "udp",
+ "local",
+ "udp",
+ 1000);
+const P2PTransportChannelTestBase::Result
+ P2PTransportChannelTestBase::kStunUdpToStunUdp("stun",
+ "udp",
+ "stun",
+ "udp",
+ 1000);
+const P2PTransportChannelTestBase::Result
+ P2PTransportChannelTestBase::kStunUdpToPrflxUdp("stun",
+ "udp",
+ "prflx",
+ "udp",
+ 1000);
+const P2PTransportChannelTestBase::Result
+ P2PTransportChannelTestBase::kPrflxUdpToStunUdp("prflx",
+ "udp",
+ "stun",
+ "udp",
+ 1000);
+const P2PTransportChannelTestBase::Result
+ P2PTransportChannelTestBase::kLocalUdpToRelayUdp("local",
+ "udp",
+ "relay",
+ "udp",
+ 2000);
+const P2PTransportChannelTestBase::Result
+ P2PTransportChannelTestBase::kPrflxUdpToRelayUdp("prflx",
+ "udp",
+ "relay",
+ "udp",
+ 2000);
+const P2PTransportChannelTestBase::Result
+ P2PTransportChannelTestBase::kRelayUdpToPrflxUdp("relay",
+ "udp",
+ "prflx",
+ "udp",
+ 2000);
+const P2PTransportChannelTestBase::Result
+ P2PTransportChannelTestBase::kLocalTcpToLocalTcp("local",
+ "tcp",
+ "local",
+ "tcp",
+ 3000);
+const P2PTransportChannelTestBase::Result
+ P2PTransportChannelTestBase::kLocalTcpToPrflxTcp("local",
+ "tcp",
+ "prflx",
+ "tcp",
+ 3000);
+const P2PTransportChannelTestBase::Result
+ P2PTransportChannelTestBase::kPrflxTcpToLocalTcp("prflx",
+ "tcp",
+ "local",
+ "tcp",
+ 3000);
+
+// Test the matrix of all the connectivity types we expect to see in the wild.
+// Just test every combination of the configs in the Config enum.
+class P2PTransportChannelTest : public P2PTransportChannelTestBase {
+ public:
+ P2PTransportChannelTest() : P2PTransportChannelTestBase() {}
+ explicit P2PTransportChannelTest(absl::string_view field_trials)
+ : P2PTransportChannelTestBase(field_trials) {}
+
+ protected:
+ void ConfigureEndpoints(Config config1,
+ Config config2,
+ int allocator_flags1,
+ int allocator_flags2) {
+ ConfigureEndpoint(0, config1);
+ SetAllocatorFlags(0, allocator_flags1);
+ SetAllocationStepDelay(0, kMinimumStepDelay);
+ ConfigureEndpoint(1, config2);
+ SetAllocatorFlags(1, allocator_flags2);
+ SetAllocationStepDelay(1, kMinimumStepDelay);
+
+ set_remote_ice_parameter_source(FROM_SETICEPARAMETERS);
+ }
+ void ConfigureEndpoint(int endpoint, Config config) {
+ switch (config) {
+ case OPEN:
+ AddAddress(endpoint, kPublicAddrs[endpoint]);
+ break;
+ case NAT_FULL_CONE:
+ case NAT_ADDR_RESTRICTED:
+ case NAT_PORT_RESTRICTED:
+ case NAT_SYMMETRIC:
+ AddAddress(endpoint, kPrivateAddrs[endpoint]);
+ // Add a single NAT of the desired type
+ nat()
+ ->AddTranslator(kPublicAddrs[endpoint], kNatAddrs[endpoint],
+ static_cast<rtc::NATType>(config - NAT_FULL_CONE))
+ ->AddClient(kPrivateAddrs[endpoint]);
+ break;
+ case NAT_DOUBLE_CONE:
+ case NAT_SYMMETRIC_THEN_CONE:
+ AddAddress(endpoint, kCascadedPrivateAddrs[endpoint]);
+ // Add a two cascaded NATs of the desired types
+ nat()
+ ->AddTranslator(kPublicAddrs[endpoint], kNatAddrs[endpoint],
+ (config == NAT_DOUBLE_CONE) ? rtc::NAT_OPEN_CONE
+ : rtc::NAT_SYMMETRIC)
+ ->AddTranslator(kPrivateAddrs[endpoint],
+ kCascadedNatAddrs[endpoint], rtc::NAT_OPEN_CONE)
+ ->AddClient(kCascadedPrivateAddrs[endpoint]);
+ break;
+ case BLOCK_UDP:
+ case BLOCK_UDP_AND_INCOMING_TCP:
+ case BLOCK_ALL_BUT_OUTGOING_HTTP:
+ case PROXY_HTTPS:
+ case PROXY_SOCKS:
+ AddAddress(endpoint, kPublicAddrs[endpoint]);
+ // Block all UDP
+ fw()->AddRule(false, rtc::FP_UDP, rtc::FD_ANY, kPublicAddrs[endpoint]);
+ if (config == BLOCK_UDP_AND_INCOMING_TCP) {
+ // Block TCP inbound to the endpoint
+ fw()->AddRule(false, rtc::FP_TCP, SocketAddress(),
+ kPublicAddrs[endpoint]);
+ } else if (config == BLOCK_ALL_BUT_OUTGOING_HTTP) {
+ // Block all TCP to/from the endpoint except 80/443 out
+ fw()->AddRule(true, rtc::FP_TCP, kPublicAddrs[endpoint],
+ SocketAddress(rtc::IPAddress(INADDR_ANY), 80));
+ fw()->AddRule(true, rtc::FP_TCP, kPublicAddrs[endpoint],
+ SocketAddress(rtc::IPAddress(INADDR_ANY), 443));
+ fw()->AddRule(false, rtc::FP_TCP, rtc::FD_ANY,
+ kPublicAddrs[endpoint]);
+ } else if (config == PROXY_HTTPS) {
+ // Block all TCP to/from the endpoint except to the proxy server
+ fw()->AddRule(true, rtc::FP_TCP, kPublicAddrs[endpoint],
+ kHttpsProxyAddrs[endpoint]);
+ fw()->AddRule(false, rtc::FP_TCP, rtc::FD_ANY,
+ kPublicAddrs[endpoint]);
+ SetProxy(endpoint, rtc::PROXY_HTTPS);
+ } else if (config == PROXY_SOCKS) {
+ // Block all TCP to/from the endpoint except to the proxy server
+ fw()->AddRule(true, rtc::FP_TCP, kPublicAddrs[endpoint],
+ kSocksProxyAddrs[endpoint]);
+ fw()->AddRule(false, rtc::FP_TCP, rtc::FD_ANY,
+ kPublicAddrs[endpoint]);
+ SetProxy(endpoint, rtc::PROXY_SOCKS5);
+ }
+ break;
+ default:
+ RTC_DCHECK_NOTREACHED();
+ break;
+ }
+ }
+};
+
+class P2PTransportChannelTestWithFieldTrials
+ : public P2PTransportChannelTest,
+ public WithParamInterface<std::string> {
+ public:
+ P2PTransportChannelTestWithFieldTrials()
+ : P2PTransportChannelTest(GetParam()) {}
+};
+
+class P2PTransportChannelMatrixTest
+ : public P2PTransportChannelTestWithFieldTrials {
+ protected:
+ static const Result* kMatrix[NUM_CONFIGS][NUM_CONFIGS];
+};
+
+// Shorthands for use in the test matrix.
+#define LULU &kLocalUdpToLocalUdp
+#define LUSU &kLocalUdpToStunUdp
+#define LUPU &kLocalUdpToPrflxUdp
+#define PULU &kPrflxUdpToLocalUdp
+#define SULU &kStunUdpToLocalUdp
+#define SUSU &kStunUdpToStunUdp
+#define SUPU &kStunUdpToPrflxUdp
+#define PUSU &kPrflxUdpToStunUdp
+#define LURU &kLocalUdpToRelayUdp
+#define PURU &kPrflxUdpToRelayUdp
+#define RUPU &kRelayUdpToPrflxUdp
+#define LTLT &kLocalTcpToLocalTcp
+#define LTPT &kLocalTcpToPrflxTcp
+#define PTLT &kPrflxTcpToLocalTcp
+// TODO(?): Enable these once TestRelayServer can accept external TCP.
+#define LTRT NULL
+#define LSRS NULL
+
+// Test matrix. Originator behavior defined by rows, receiever by columns.
+
+// TODO(?): Fix NULLs caused by lack of TCP support in NATSocket.
+// TODO(?): Fix NULLs caused by no HTTP proxy support.
+// TODO(?): Rearrange rows/columns from best to worst.
+const P2PTransportChannelMatrixTest::Result*
+ P2PTransportChannelMatrixTest::kMatrix[NUM_CONFIGS][NUM_CONFIGS] = {
+ // OPEN CONE ADDR PORT SYMM 2CON SCON !UDP !TCP HTTP PRXH
+ // PRXS
+ /*OP*/ {LULU, LUSU, LUSU, LUSU, LUPU, LUSU, LUPU, LTPT, LTPT, LSRS,
+ NULL, LTPT},
+ /*CO*/
+ {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL, LSRS, NULL,
+ LTRT},
+ /*AD*/
+ {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL, LSRS, NULL,
+ LTRT},
+ /*PO*/
+ {SULU, SUSU, SUSU, SUSU, RUPU, SUSU, RUPU, NULL, NULL, LSRS, NULL,
+ LTRT},
+ /*SY*/
+ {PULU, PUSU, PUSU, PURU, PURU, PUSU, PURU, NULL, NULL, LSRS, NULL,
+ LTRT},
+ /*2C*/
+ {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL, LSRS, NULL,
+ LTRT},
+ /*SC*/
+ {PULU, PUSU, PUSU, PURU, PURU, PUSU, PURU, NULL, NULL, LSRS, NULL,
+ LTRT},
+ /*!U*/
+ {LTPT, NULL, NULL, NULL, NULL, NULL, NULL, LTPT, LTPT, LSRS, NULL,
+ LTRT},
+ /*!T*/
+ {PTLT, NULL, NULL, NULL, NULL, NULL, NULL, PTLT, LTRT, LSRS, NULL,
+ LTRT},
+ /*HT*/
+ {LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, NULL,
+ LSRS},
+ /*PR*/
+ {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL},
+ /*PR*/
+ {LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LSRS, NULL,
+ LTRT},
+};
+
+// The actual tests that exercise all the various configurations.
+// Test names are of the form P2PTransportChannelTest_TestOPENToNAT_FULL_CONE
+#define P2P_TEST_DECLARATION(x, y, z) \
+ TEST_P(P2PTransportChannelMatrixTest, z##Test##x##To##y) { \
+ ConfigureEndpoints(x, y, PORTALLOCATOR_ENABLE_SHARED_SOCKET, \
+ PORTALLOCATOR_ENABLE_SHARED_SOCKET); \
+ if (kMatrix[x][y] != NULL) \
+ Test(*kMatrix[x][y]); \
+ else \
+ RTC_LOG(LS_WARNING) << "Not yet implemented"; \
+ }
+
+#define P2P_TEST(x, y) P2P_TEST_DECLARATION(x, y, /* empty argument */)
+
+#define P2P_TEST_SET(x) \
+ P2P_TEST(x, OPEN) \
+ P2P_TEST(x, NAT_FULL_CONE) \
+ P2P_TEST(x, NAT_ADDR_RESTRICTED) \
+ P2P_TEST(x, NAT_PORT_RESTRICTED) \
+ P2P_TEST(x, NAT_SYMMETRIC) \
+ P2P_TEST(x, NAT_DOUBLE_CONE) \
+ P2P_TEST(x, NAT_SYMMETRIC_THEN_CONE) \
+ P2P_TEST(x, BLOCK_UDP) \
+ P2P_TEST(x, BLOCK_UDP_AND_INCOMING_TCP) \
+ P2P_TEST(x, BLOCK_ALL_BUT_OUTGOING_HTTP) \
+ P2P_TEST(x, PROXY_HTTPS) \
+ P2P_TEST(x, PROXY_SOCKS)
+
+P2P_TEST_SET(OPEN)
+P2P_TEST_SET(NAT_FULL_CONE)
+P2P_TEST_SET(NAT_ADDR_RESTRICTED)
+P2P_TEST_SET(NAT_PORT_RESTRICTED)
+P2P_TEST_SET(NAT_SYMMETRIC)
+P2P_TEST_SET(NAT_DOUBLE_CONE)
+P2P_TEST_SET(NAT_SYMMETRIC_THEN_CONE)
+P2P_TEST_SET(BLOCK_UDP)
+P2P_TEST_SET(BLOCK_UDP_AND_INCOMING_TCP)
+P2P_TEST_SET(BLOCK_ALL_BUT_OUTGOING_HTTP)
+P2P_TEST_SET(PROXY_HTTPS)
+P2P_TEST_SET(PROXY_SOCKS)
+
+INSTANTIATE_TEST_SUITE_P(
+ Legacy,
+ P2PTransportChannelMatrixTest,
+ // Each field-trial is ~144 tests (some return not-yet-implemented).
+ Values("", "WebRTC-IceFieldTrials/enable_goog_ping:true/"));
+INSTANTIATE_TEST_SUITE_P(
+ Active,
+ P2PTransportChannelMatrixTest,
+ // Each field-trial is ~144 tests (some return not-yet-implemented).
+ Values("WebRTC-UseActiveIceController/Enabled/",
+ "WebRTC-IceFieldTrials/enable_goog_ping:true/"
+ "WebRTC-UseActiveIceController/Enabled/"));
+
+INSTANTIATE_TEST_SUITE_P(Legacy,
+ P2PTransportChannelTestWithFieldTrials,
+ Values(""));
+INSTANTIATE_TEST_SUITE_P(Active,
+ P2PTransportChannelTestWithFieldTrials,
+ Values("WebRTC-UseActiveIceController/Enabled/"));
+
+// Test that we restart candidate allocation when local ufrag&pwd changed.
+// Standard Ice protocol is used.
+TEST_P(P2PTransportChannelTestWithFieldTrials, HandleUfragPwdChange) {
+ ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags,
+ kDefaultPortAllocatorFlags);
+ CreateChannels();
+ TestHandleIceUfragPasswordChanged();
+ DestroyChannels();
+}
+
+// Same as above test, but with a symmetric NAT.
+// We should end up with relay<->prflx candidate pairs, with generation "1".
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ HandleUfragPwdChangeSymmetricNat) {
+ ConfigureEndpoints(NAT_SYMMETRIC, NAT_SYMMETRIC, kDefaultPortAllocatorFlags,
+ kDefaultPortAllocatorFlags);
+ CreateChannels();
+ TestHandleIceUfragPasswordChanged();
+ DestroyChannels();
+}
+
+// Test the operation of GetStats.
+TEST_P(P2PTransportChannelTestWithFieldTrials, GetStats) {
+ rtc::ScopedFakeClock clock;
+ ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags,
+ kDefaultPortAllocatorFlags);
+ CreateChannels();
+ EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->receiving() && ep1_ch1()->writable() &&
+ ep2_ch1()->receiving() &&
+ ep2_ch1()->writable(),
+ kMediumTimeout, clock);
+ // Sends and receives 10 packets.
+ TestSendRecv(&clock);
+
+ // Try sending a packet which is discarded due to the socket being blocked.
+ virtual_socket_server()->SetSendingBlocked(true);
+ const char* data = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
+ int len = static_cast<int>(strlen(data));
+ EXPECT_EQ(-1, SendData(ep1_ch1(), data, len));
+
+ IceTransportStats ice_transport_stats;
+ ASSERT_TRUE(ep1_ch1()->GetStats(&ice_transport_stats));
+ ASSERT_GE(ice_transport_stats.connection_infos.size(), 1u);
+ ASSERT_GE(ice_transport_stats.candidate_stats_list.size(), 1u);
+ EXPECT_EQ(ice_transport_stats.selected_candidate_pair_changes, 1u);
+ ConnectionInfo* best_conn_info = nullptr;
+ for (ConnectionInfo& info : ice_transport_stats.connection_infos) {
+ if (info.best_connection) {
+ best_conn_info = &info;
+ break;
+ }
+ }
+ ASSERT_TRUE(best_conn_info != nullptr);
+ EXPECT_TRUE(best_conn_info->receiving);
+ EXPECT_TRUE(best_conn_info->writable);
+ EXPECT_FALSE(best_conn_info->timeout);
+ // Note that discarded packets are counted in sent_total_packets but not
+ // sent_total_bytes.
+ EXPECT_EQ(11U, best_conn_info->sent_total_packets);
+ EXPECT_EQ(1U, best_conn_info->sent_discarded_packets);
+ EXPECT_EQ(10 * 36U, best_conn_info->sent_total_bytes);
+ EXPECT_EQ(36U, best_conn_info->sent_discarded_bytes);
+ EXPECT_EQ(10 * 36U, best_conn_info->recv_total_bytes);
+ EXPECT_EQ(10U, best_conn_info->packets_received);
+
+ EXPECT_EQ(10 * 36U, ice_transport_stats.bytes_sent);
+ EXPECT_EQ(10 * 36U, ice_transport_stats.bytes_received);
+
+ DestroyChannels();
+}
+
+TEST_P(P2PTransportChannelTestWithFieldTrials, GetStatsSwitchConnection) {
+ rtc::ScopedFakeClock clock;
+ IceConfig continual_gathering_config =
+ CreateIceConfig(1000, GATHER_CONTINUALLY);
+
+ ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags,
+ kDefaultPortAllocatorFlags);
+
+ AddAddress(0, kAlternateAddrs[1], "rmnet0", rtc::ADAPTER_TYPE_CELLULAR);
+
+ CreateChannels(continual_gathering_config, continual_gathering_config);
+ EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->receiving() && ep1_ch1()->writable() &&
+ ep2_ch1()->receiving() &&
+ ep2_ch1()->writable(),
+ kMediumTimeout, clock);
+ // Sends and receives 10 packets.
+ TestSendRecv(&clock);
+
+ IceTransportStats ice_transport_stats;
+ ASSERT_TRUE(ep1_ch1()->GetStats(&ice_transport_stats));
+ ASSERT_GE(ice_transport_stats.connection_infos.size(), 2u);
+ ASSERT_GE(ice_transport_stats.candidate_stats_list.size(), 2u);
+ EXPECT_EQ(ice_transport_stats.selected_candidate_pair_changes, 1u);
+
+ ConnectionInfo* best_conn_info = nullptr;
+ for (ConnectionInfo& info : ice_transport_stats.connection_infos) {
+ if (info.best_connection) {
+ best_conn_info = &info;
+ break;
+ }
+ }
+ ASSERT_TRUE(best_conn_info != nullptr);
+ EXPECT_TRUE(best_conn_info->receiving);
+ EXPECT_TRUE(best_conn_info->writable);
+ EXPECT_FALSE(best_conn_info->timeout);
+
+ EXPECT_EQ(10 * 36U, best_conn_info->sent_total_bytes);
+ EXPECT_EQ(10 * 36U, best_conn_info->recv_total_bytes);
+ EXPECT_EQ(10 * 36U, ice_transport_stats.bytes_sent);
+ EXPECT_EQ(10 * 36U, ice_transport_stats.bytes_received);
+
+ auto old_selected_connection = ep1_ch1()->selected_connection();
+ ep1_ch1()->RemoveConnectionForTest(
+ const_cast<Connection*>(old_selected_connection));
+
+ EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->selected_connection() != nullptr,
+ kMediumTimeout, clock);
+
+ // Sends and receives 10 packets.
+ TestSendRecv(&clock);
+
+ IceTransportStats ice_transport_stats2;
+ ASSERT_TRUE(ep1_ch1()->GetStats(&ice_transport_stats2));
+
+ int64_t sum_bytes_sent = 0;
+ int64_t sum_bytes_received = 0;
+ for (ConnectionInfo& info : ice_transport_stats.connection_infos) {
+ sum_bytes_sent += info.sent_total_bytes;
+ sum_bytes_received += info.recv_total_bytes;
+ }
+
+ EXPECT_EQ(10 * 36U, sum_bytes_sent);
+ EXPECT_EQ(10 * 36U, sum_bytes_received);
+
+ EXPECT_EQ(20 * 36U, ice_transport_stats2.bytes_sent);
+ EXPECT_EQ(20 * 36U, ice_transport_stats2.bytes_received);
+
+ DestroyChannels();
+}
+
+// Tests that UMAs are recorded when ICE restarts while the channel
+// is disconnected.
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ TestUMAIceRestartWhileDisconnected) {
+ rtc::ScopedFakeClock clock;
+ ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts);
+
+ CreateChannels();
+ EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()),
+ kDefaultTimeout, clock);
+
+ // Drop all packets so that both channels become not writable.
+ fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[0]);
+ const int kWriteTimeoutDelay = 8000;
+ EXPECT_TRUE_SIMULATED_WAIT(!ep1_ch1()->writable() && !ep2_ch1()->writable(),
+ kWriteTimeoutDelay, clock);
+
+ ep1_ch1()->SetIceParameters(kIceParams[2]);
+ ep1_ch1()->SetRemoteIceParameters(kIceParams[3]);
+ ep1_ch1()->MaybeStartGathering();
+ EXPECT_METRIC_EQ(1, webrtc::metrics::NumEvents(
+ "WebRTC.PeerConnection.IceRestartState",
+ static_cast<int>(IceRestartState::DISCONNECTED)));
+
+ ep2_ch1()->SetIceParameters(kIceParams[3]);
+ ep2_ch1()->SetRemoteIceParameters(kIceParams[2]);
+ ep2_ch1()->MaybeStartGathering();
+ EXPECT_METRIC_EQ(2, webrtc::metrics::NumEvents(
+ "WebRTC.PeerConnection.IceRestartState",
+ static_cast<int>(IceRestartState::DISCONNECTED)));
+
+ DestroyChannels();
+}
+
+// Tests that UMAs are recorded when ICE restarts while the channel
+// is connected.
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ TestUMAIceRestartWhileConnected) {
+ rtc::ScopedFakeClock clock;
+ ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts);
+
+ CreateChannels();
+ EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()),
+ kDefaultTimeout, clock);
+
+ ep1_ch1()->SetIceParameters(kIceParams[2]);
+ ep1_ch1()->SetRemoteIceParameters(kIceParams[3]);
+ ep1_ch1()->MaybeStartGathering();
+ EXPECT_METRIC_EQ(1, webrtc::metrics::NumEvents(
+ "WebRTC.PeerConnection.IceRestartState",
+ static_cast<int>(IceRestartState::CONNECTED)));
+
+ ep2_ch1()->SetIceParameters(kIceParams[3]);
+ ep2_ch1()->SetRemoteIceParameters(kIceParams[2]);
+ ep2_ch1()->MaybeStartGathering();
+ EXPECT_METRIC_EQ(2, webrtc::metrics::NumEvents(
+ "WebRTC.PeerConnection.IceRestartState",
+ static_cast<int>(IceRestartState::CONNECTED)));
+
+ DestroyChannels();
+}
+
+// Tests that UMAs are recorded when ICE restarts while the channel
+// is connecting.
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ TestUMAIceRestartWhileConnecting) {
+ rtc::ScopedFakeClock clock;
+ ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts);
+
+ // Create the channels without waiting for them to become connected.
+ CreateChannels();
+
+ ep1_ch1()->SetIceParameters(kIceParams[2]);
+ ep1_ch1()->SetRemoteIceParameters(kIceParams[3]);
+ ep1_ch1()->MaybeStartGathering();
+ EXPECT_METRIC_EQ(1, webrtc::metrics::NumEvents(
+ "WebRTC.PeerConnection.IceRestartState",
+ static_cast<int>(IceRestartState::CONNECTING)));
+
+ ep2_ch1()->SetIceParameters(kIceParams[3]);
+ ep2_ch1()->SetRemoteIceParameters(kIceParams[2]);
+ ep2_ch1()->MaybeStartGathering();
+ EXPECT_METRIC_EQ(2, webrtc::metrics::NumEvents(
+ "WebRTC.PeerConnection.IceRestartState",
+ static_cast<int>(IceRestartState::CONNECTING)));
+
+ DestroyChannels();
+}
+
+// Tests that a UMA on ICE regathering is recorded when there is a network
+// change if and only if continual gathering is enabled.
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ TestIceRegatheringReasonContinualGatheringByNetworkChange) {
+ rtc::ScopedFakeClock clock;
+ ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts);
+
+ // ep1 gathers continually but ep2 does not.
+ IceConfig continual_gathering_config =
+ CreateIceConfig(1000, GATHER_CONTINUALLY);
+ IceConfig default_config;
+ CreateChannels(continual_gathering_config, default_config);
+
+ EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()),
+ kDefaultTimeout, clock);
+
+ // Adding address in ep1 will trigger continual gathering.
+ AddAddress(0, kAlternateAddrs[0]);
+ EXPECT_EQ_SIMULATED_WAIT(1,
+ GetEndpoint(0)->GetIceRegatheringCountForReason(
+ IceRegatheringReason::NETWORK_CHANGE),
+ kDefaultTimeout, clock);
+
+ ep2_ch1()->SetIceParameters(kIceParams[3]);
+ ep2_ch1()->SetRemoteIceParameters(kIceParams[2]);
+ ep2_ch1()->MaybeStartGathering();
+
+ AddAddress(1, kAlternateAddrs[1]);
+ SIMULATED_WAIT(false, kDefaultTimeout, clock);
+ // ep2 has not enabled continual gathering.
+ EXPECT_EQ(0, GetEndpoint(1)->GetIceRegatheringCountForReason(
+ IceRegatheringReason::NETWORK_CHANGE));
+
+ DestroyChannels();
+}
+
+// Tests that a UMA on ICE regathering is recorded when there is a network
+// failure if and only if continual gathering is enabled.
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ TestIceRegatheringReasonContinualGatheringByNetworkFailure) {
+ rtc::ScopedFakeClock clock;
+ ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts);
+
+ // ep1 gathers continually but ep2 does not.
+ IceConfig config1 = CreateIceConfig(1000, GATHER_CONTINUALLY);
+ config1.regather_on_failed_networks_interval = 2000;
+ IceConfig config2;
+ config2.regather_on_failed_networks_interval = 2000;
+ CreateChannels(config1, config2);
+
+ EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()),
+ kDefaultTimeout, clock);
+
+ fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[0]);
+ // Timeout value such that all connections are deleted.
+ const int kNetworkFailureTimeout = 35000;
+ SIMULATED_WAIT(false, kNetworkFailureTimeout, clock);
+ EXPECT_LE(1, GetEndpoint(0)->GetIceRegatheringCountForReason(
+ IceRegatheringReason::NETWORK_FAILURE));
+ EXPECT_METRIC_LE(
+ 1, webrtc::metrics::NumEvents(
+ "WebRTC.PeerConnection.IceRegatheringReason",
+ static_cast<int>(IceRegatheringReason::NETWORK_FAILURE)));
+ EXPECT_EQ(0, GetEndpoint(1)->GetIceRegatheringCountForReason(
+ IceRegatheringReason::NETWORK_FAILURE));
+
+ DestroyChannels();
+}
+
+// Test that we properly create a connection on a STUN ping from unknown address
+// when the signaling is slow.
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ PeerReflexiveCandidateBeforeSignaling) {
+ ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags,
+ kDefaultPortAllocatorFlags);
+ // Emulate no remote parameters coming in.
+ set_remote_ice_parameter_source(FROM_CANDIDATE);
+ CreateChannels();
+ // Only have remote parameters come in for ep2, not ep1.
+ ep2_ch1()->SetRemoteIceParameters(kIceParams[0]);
+
+ // Pause sending ep2's candidates to ep1 until ep1 receives the peer reflexive
+ // candidate.
+ PauseCandidates(1);
+
+ // Wait until the callee becomes writable to make sure that a ping request is
+ // received by the caller before their remote ICE credentials are set.
+ ASSERT_TRUE_WAIT(ep2_ch1()->selected_connection() != nullptr, kMediumTimeout);
+ // Add two sets of remote ICE credentials, so that the ones used by the
+ // candidate will be generation 1 instead of 0.
+ ep1_ch1()->SetRemoteIceParameters(kIceParams[3]);
+ ep1_ch1()->SetRemoteIceParameters(kIceParams[1]);
+ // The caller should have the selected connection connected to the peer
+ // reflexive candidate.
+ const Connection* selected_connection = nullptr;
+ ASSERT_TRUE_WAIT(
+ (selected_connection = ep1_ch1()->selected_connection()) != nullptr,
+ kMediumTimeout);
+ EXPECT_EQ(PRFLX_PORT_TYPE, selected_connection->remote_candidate().type());
+ EXPECT_EQ(kIceUfrag[1], selected_connection->remote_candidate().username());
+ EXPECT_EQ(kIcePwd[1], selected_connection->remote_candidate().password());
+ EXPECT_EQ(1u, selected_connection->remote_candidate().generation());
+
+ ResumeCandidates(1);
+ // Verify ep1's selected connection is updated to use the 'local' candidate.
+ EXPECT_EQ_WAIT(LOCAL_PORT_TYPE,
+ ep1_ch1()->selected_connection()->remote_candidate().type(),
+ kMediumTimeout);
+ EXPECT_EQ(selected_connection, ep1_ch1()->selected_connection());
+ DestroyChannels();
+}
+
+// Test that if we learn a prflx remote candidate, its address is concealed in
+// 1. the selected candidate pair accessed via the public API, and
+// 2. the candidate pair stats
+// until we learn the same address from signaling.
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ PeerReflexiveRemoteCandidateIsSanitized) {
+ ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts);
+ // Emulate no remote parameters coming in.
+ set_remote_ice_parameter_source(FROM_CANDIDATE);
+ CreateChannels();
+ // Only have remote parameters come in for ep2, not ep1.
+ ep2_ch1()->SetRemoteIceParameters(kIceParams[0]);
+
+ // Pause sending ep2's candidates to ep1 until ep1 receives the peer reflexive
+ // candidate.
+ PauseCandidates(1);
+
+ ASSERT_TRUE_WAIT(ep2_ch1()->selected_connection() != nullptr, kMediumTimeout);
+ ep1_ch1()->SetRemoteIceParameters(kIceParams[1]);
+ ASSERT_TRUE_WAIT(ep1_ch1()->selected_connection() != nullptr, kMediumTimeout);
+
+ // Check the selected candidate pair.
+ auto pair_ep1 = ep1_ch1()->GetSelectedCandidatePair();
+ ASSERT_TRUE(pair_ep1.has_value());
+ EXPECT_EQ(PRFLX_PORT_TYPE, pair_ep1->remote_candidate().type());
+ EXPECT_TRUE(pair_ep1->remote_candidate().address().ipaddr().IsNil());
+
+ IceTransportStats ice_transport_stats;
+ ep1_ch1()->GetStats(&ice_transport_stats);
+ // Check the candidate pair stats.
+ ASSERT_EQ(1u, ice_transport_stats.connection_infos.size());
+ EXPECT_EQ(PRFLX_PORT_TYPE,
+ ice_transport_stats.connection_infos[0].remote_candidate.type());
+ EXPECT_TRUE(ice_transport_stats.connection_infos[0]
+ .remote_candidate.address()
+ .ipaddr()
+ .IsNil());
+
+ // Let ep1 receive the remote candidate to update its type from prflx to host.
+ ResumeCandidates(1);
+ ASSERT_TRUE_WAIT(
+ ep1_ch1()->selected_connection() != nullptr &&
+ ep1_ch1()->selected_connection()->remote_candidate().type() ==
+ LOCAL_PORT_TYPE,
+ kMediumTimeout);
+
+ // We should be able to reveal the address after it is learnt via
+ // AddIceCandidate.
+ //
+ // Check the selected candidate pair.
+ auto updated_pair_ep1 = ep1_ch1()->GetSelectedCandidatePair();
+ ASSERT_TRUE(updated_pair_ep1.has_value());
+ EXPECT_EQ(LOCAL_PORT_TYPE, updated_pair_ep1->remote_candidate().type());
+ EXPECT_TRUE(HasRemoteAddress(&updated_pair_ep1.value(), kPublicAddrs[1]));
+
+ ep1_ch1()->GetStats(&ice_transport_stats);
+ // Check the candidate pair stats.
+ ASSERT_EQ(1u, ice_transport_stats.connection_infos.size());
+ EXPECT_EQ(LOCAL_PORT_TYPE,
+ ice_transport_stats.connection_infos[0].remote_candidate.type());
+ EXPECT_TRUE(ice_transport_stats.connection_infos[0]
+ .remote_candidate.address()
+ .EqualIPs(kPublicAddrs[1]));
+
+ DestroyChannels();
+}
+
+// Test that we properly create a connection on a STUN ping from unknown address
+// when the signaling is slow and the end points are behind NAT.
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ PeerReflexiveCandidateBeforeSignalingWithNAT) {
+ ConfigureEndpoints(OPEN, NAT_SYMMETRIC, kDefaultPortAllocatorFlags,
+ kDefaultPortAllocatorFlags);
+ // Emulate no remote parameters coming in.
+ set_remote_ice_parameter_source(FROM_CANDIDATE);
+ CreateChannels();
+ // Only have remote parameters come in for ep2, not ep1.
+ ep2_ch1()->SetRemoteIceParameters(kIceParams[0]);
+ // Pause sending ep2's candidates to ep1 until ep1 receives the peer reflexive
+ // candidate.
+ PauseCandidates(1);
+
+ // Wait until the callee becomes writable to make sure that a ping request is
+ // received by the caller before their remote ICE credentials are set.
+ ASSERT_TRUE_WAIT(ep2_ch1()->selected_connection() != nullptr, kMediumTimeout);
+ // Add two sets of remote ICE credentials, so that the ones used by the
+ // candidate will be generation 1 instead of 0.
+ ep1_ch1()->SetRemoteIceParameters(kIceParams[3]);
+ ep1_ch1()->SetRemoteIceParameters(kIceParams[1]);
+
+ // The caller's selected connection should be connected to the peer reflexive
+ // candidate.
+ const Connection* selected_connection = nullptr;
+ ASSERT_TRUE_WAIT(
+ (selected_connection = ep1_ch1()->selected_connection()) != nullptr,
+ kMediumTimeout);
+ EXPECT_EQ(PRFLX_PORT_TYPE, selected_connection->remote_candidate().type());
+ EXPECT_EQ(kIceUfrag[1], selected_connection->remote_candidate().username());
+ EXPECT_EQ(kIcePwd[1], selected_connection->remote_candidate().password());
+ EXPECT_EQ(1u, selected_connection->remote_candidate().generation());
+
+ ResumeCandidates(1);
+
+ EXPECT_EQ_WAIT(PRFLX_PORT_TYPE,
+ ep1_ch1()->selected_connection()->remote_candidate().type(),
+ kMediumTimeout);
+ EXPECT_EQ(selected_connection, ep1_ch1()->selected_connection());
+ DestroyChannels();
+}
+
+// Test that we properly create a connection on a STUN ping from unknown address
+// when the signaling is slow, even if the new candidate is created due to the
+// remote peer doing an ICE restart, pairing this candidate across generations.
+//
+// Previously this wasn't working due to a bug where the peer reflexive
+// candidate was only updated for the newest generation candidate pairs, and
+// not older-generation candidate pairs created by pairing candidates across
+// generations. This resulted in the old-generation prflx candidate being
+// prioritized above new-generation candidate pairs.
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ PeerReflexiveCandidateBeforeSignalingWithIceRestart) {
+ ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags,
+ kDefaultPortAllocatorFlags);
+ // Only gather relay candidates, so that when the prflx candidate arrives
+ // it's prioritized above the current candidate pair.
+ GetEndpoint(0)->allocator_->SetCandidateFilter(CF_RELAY);
+ GetEndpoint(1)->allocator_->SetCandidateFilter(CF_RELAY);
+ // Setting this allows us to control when SetRemoteIceParameters is called.
+ set_remote_ice_parameter_source(FROM_CANDIDATE);
+ CreateChannels();
+ // Wait for the initial connection to be made.
+ ep1_ch1()->SetRemoteIceParameters(kIceParams[1]);
+ ep2_ch1()->SetRemoteIceParameters(kIceParams[0]);
+ EXPECT_TRUE_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()), kDefaultTimeout);
+
+ // Simulate an ICE restart on ep2, but don't signal the candidate or new
+ // ICE parameters until after a prflx connection has been made.
+ PauseCandidates(1);
+ ep2_ch1()->SetIceParameters(kIceParams[3]);
+
+ ep1_ch1()->SetRemoteIceParameters(kIceParams[3]);
+ ep2_ch1()->MaybeStartGathering();
+
+ // The caller should have the selected connection connected to the peer
+ // reflexive candidate.
+ EXPECT_EQ_WAIT(PRFLX_PORT_TYPE,
+ ep1_ch1()->selected_connection()->remote_candidate().type(),
+ kDefaultTimeout);
+ const Connection* prflx_selected_connection =
+ ep1_ch1()->selected_connection();
+
+ // Now simulate the ICE restart on ep1.
+ ep1_ch1()->SetIceParameters(kIceParams[2]);
+
+ ep2_ch1()->SetRemoteIceParameters(kIceParams[2]);
+ ep1_ch1()->MaybeStartGathering();
+
+ // Finally send the candidates from ep2's ICE restart and verify that ep1 uses
+ // their information to update the peer reflexive candidate.
+ ResumeCandidates(1);
+
+ EXPECT_EQ_WAIT(RELAY_PORT_TYPE,
+ ep1_ch1()->selected_connection()->remote_candidate().type(),
+ kDefaultTimeout);
+ EXPECT_EQ(prflx_selected_connection, ep1_ch1()->selected_connection());
+ DestroyChannels();
+}
+
+// Test that if remote candidates don't have ufrag and pwd, we still work.
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ RemoteCandidatesWithoutUfragPwd) {
+ rtc::ScopedFakeClock clock;
+ set_remote_ice_parameter_source(FROM_SETICEPARAMETERS);
+ ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags,
+ kDefaultPortAllocatorFlags);
+ CreateChannels();
+ const Connection* selected_connection = NULL;
+ // Wait until the callee's connections are created.
+ EXPECT_TRUE_SIMULATED_WAIT(
+ (selected_connection = ep2_ch1()->selected_connection()) != NULL,
+ kMediumTimeout, clock);
+ // Wait to make sure the selected connection is not changed.
+ SIMULATED_WAIT(ep2_ch1()->selected_connection() != selected_connection,
+ kShortTimeout, clock);
+ EXPECT_TRUE(ep2_ch1()->selected_connection() == selected_connection);
+ DestroyChannels();
+}
+
+// Test that a host behind NAT cannot be reached when incoming_only
+// is set to true.
+TEST_P(P2PTransportChannelTestWithFieldTrials, IncomingOnlyBlocked) {
+ rtc::ScopedFakeClock clock;
+ ConfigureEndpoints(NAT_FULL_CONE, OPEN, kDefaultPortAllocatorFlags,
+ kDefaultPortAllocatorFlags);
+
+ SetAllocatorFlags(0, kOnlyLocalPorts);
+ CreateChannels();
+ ep1_ch1()->set_incoming_only(true);
+
+ // Pump for 1 second and verify that the channels are not connected.
+ SIMULATED_WAIT(false, kShortTimeout, clock);
+
+ EXPECT_FALSE(ep1_ch1()->receiving());
+ EXPECT_FALSE(ep1_ch1()->writable());
+ EXPECT_FALSE(ep2_ch1()->receiving());
+ EXPECT_FALSE(ep2_ch1()->writable());
+
+ DestroyChannels();
+}
+
+// Test that a peer behind NAT can connect to a peer that has
+// incoming_only flag set.
+TEST_P(P2PTransportChannelTestWithFieldTrials, IncomingOnlyOpen) {
+ rtc::ScopedFakeClock clock;
+ ConfigureEndpoints(OPEN, NAT_FULL_CONE, kDefaultPortAllocatorFlags,
+ kDefaultPortAllocatorFlags);
+
+ SetAllocatorFlags(0, kOnlyLocalPorts);
+ CreateChannels();
+ ep1_ch1()->set_incoming_only(true);
+
+ EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()),
+ kMediumTimeout, clock);
+
+ DestroyChannels();
+}
+
+// Test that two peers can connect when one can only make outgoing TCP
+// connections. This has been observed in some scenarios involving
+// VPNs/firewalls.
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ CanOnlyMakeOutgoingTcpConnections) {
+ // The PORTALLOCATOR_ENABLE_ANY_ADDRESS_PORTS flag is required if the
+ // application needs this use case to work, since the application must accept
+ // the tradeoff that more candidates need to be allocated.
+ //
+ // TODO(deadbeef): Later, make this flag the default, and do more elegant
+ // things to ensure extra candidates don't waste resources?
+ ConfigureEndpoints(
+ OPEN, OPEN,
+ kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_ANY_ADDRESS_PORTS,
+ kDefaultPortAllocatorFlags);
+ // In order to simulate nothing working but outgoing TCP connections, prevent
+ // the endpoint from binding to its interface's address as well as the
+ // "any" addresses. It can then only make a connection by using "Connect()".
+ fw()->SetUnbindableIps({rtc::GetAnyIP(AF_INET), rtc::GetAnyIP(AF_INET6),
+ kPublicAddrs[0].ipaddr()});
+ CreateChannels();
+ // Expect a "prflx" candidate on the side that can only make outgoing
+ // connections, endpoint 0.
+ Test(kPrflxTcpToLocalTcp);
+ DestroyChannels();
+}
+
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ TestTcpConnectionsFromActiveToPassive) {
+ rtc::ScopedFakeClock clock;
+ AddAddress(0, kPublicAddrs[0]);
+ AddAddress(1, kPublicAddrs[1]);
+
+ SetAllocationStepDelay(0, kMinimumStepDelay);
+ SetAllocationStepDelay(1, kMinimumStepDelay);
+
+ int kOnlyLocalTcpPorts = PORTALLOCATOR_DISABLE_UDP |
+ PORTALLOCATOR_DISABLE_STUN |
+ PORTALLOCATOR_DISABLE_RELAY;
+ // Disable all protocols except TCP.
+ SetAllocatorFlags(0, kOnlyLocalTcpPorts);
+ SetAllocatorFlags(1, kOnlyLocalTcpPorts);
+
+ SetAllowTcpListen(0, true); // actpass.
+ SetAllowTcpListen(1, false); // active.
+
+ // We want SetRemoteIceParameters to be called as it normally would.
+ // Otherwise we won't know what parameters to use for the expected
+ // prflx TCP candidates.
+ set_remote_ice_parameter_source(FROM_SETICEPARAMETERS);
+
+ // Pause candidate so we could verify the candidate properties.
+ PauseCandidates(0);
+ PauseCandidates(1);
+ CreateChannels();
+
+ // Verify tcp candidates.
+ VerifySavedTcpCandidates(0, TCPTYPE_PASSIVE_STR);
+ VerifySavedTcpCandidates(1, TCPTYPE_ACTIVE_STR);
+
+ // Resume candidates.
+ ResumeCandidates(0);
+ ResumeCandidates(1);
+
+ EXPECT_TRUE_SIMULATED_WAIT(
+ CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), kPublicAddrs[0],
+ kPublicAddrs[1]),
+ kShortTimeout, clock);
+
+ TestSendRecv(&clock);
+ DestroyChannels();
+}
+
+// Test that tcptype is set on all candidates for a connection running over TCP.
+TEST_P(P2PTransportChannelTestWithFieldTrials, TestTcpConnectionTcptypeSet) {
+ rtc::ScopedFakeClock clock;
+ ConfigureEndpoints(BLOCK_UDP_AND_INCOMING_TCP, OPEN,
+ PORTALLOCATOR_ENABLE_SHARED_SOCKET,
+ PORTALLOCATOR_ENABLE_SHARED_SOCKET);
+
+ SetAllowTcpListen(0, false); // active.
+ SetAllowTcpListen(1, true); // actpass.
+ CreateChannels();
+
+ EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()),
+ kMediumTimeout, clock);
+ SIMULATED_WAIT(false, kDefaultTimeout, clock);
+
+ EXPECT_EQ(RemoteCandidate(ep1_ch1())->tcptype(), "passive");
+ EXPECT_EQ(LocalCandidate(ep1_ch1())->tcptype(), "active");
+ EXPECT_EQ(RemoteCandidate(ep2_ch1())->tcptype(), "active");
+ EXPECT_EQ(LocalCandidate(ep2_ch1())->tcptype(), "passive");
+
+ DestroyChannels();
+}
+
+TEST_P(P2PTransportChannelTestWithFieldTrials, TestIceRoleConflict) {
+ AddAddress(0, kPublicAddrs[0]);
+ AddAddress(1, kPublicAddrs[1]);
+ TestSignalRoleConflict();
+}
+
+// Tests that the ice configs (protocol, tiebreaker and role) can be passed
+// down to ports.
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ TestIceConfigWillPassDownToPort) {
+ rtc::ScopedFakeClock clock;
+ AddAddress(0, kPublicAddrs[0]);
+ AddAddress(1, kPublicAddrs[1]);
+
+ // Give the first connection the higher tiebreaker so its role won't
+ // change unless we tell it to.
+ SetIceRole(0, ICEROLE_CONTROLLING);
+ SetIceTiebreaker(0, kHighTiebreaker);
+ SetIceRole(1, ICEROLE_CONTROLLING);
+ SetIceTiebreaker(1, kLowTiebreaker);
+
+ CreateChannels();
+
+ EXPECT_EQ_SIMULATED_WAIT(2u, ep1_ch1()->ports().size(), kShortTimeout, clock);
+
+ const std::vector<PortInterface*> ports_before = ep1_ch1()->ports();
+ for (size_t i = 0; i < ports_before.size(); ++i) {
+ EXPECT_EQ(ICEROLE_CONTROLLING, ports_before[i]->GetIceRole());
+ EXPECT_EQ(kHighTiebreaker, ports_before[i]->IceTiebreaker());
+ }
+
+ ep1_ch1()->SetIceRole(ICEROLE_CONTROLLED);
+ ep1_ch1()->SetIceTiebreaker(kLowTiebreaker);
+
+ const std::vector<PortInterface*> ports_after = ep1_ch1()->ports();
+ for (size_t i = 0; i < ports_after.size(); ++i) {
+ EXPECT_EQ(ICEROLE_CONTROLLED, ports_before[i]->GetIceRole());
+ // SetIceTiebreaker after ports have been created will fail. So expect the
+ // original value.
+ EXPECT_EQ(kHighTiebreaker, ports_before[i]->IceTiebreaker());
+ }
+
+ EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()),
+ kShortTimeout, clock);
+
+ EXPECT_TRUE(ep1_ch1()->selected_connection() &&
+ ep2_ch1()->selected_connection());
+
+ TestSendRecv(&clock);
+ DestroyChannels();
+}
+
+// Verify that we can set DSCP value and retrieve properly from P2PTC.
+TEST_P(P2PTransportChannelTestWithFieldTrials, TestDefaultDscpValue) {
+ AddAddress(0, kPublicAddrs[0]);
+ AddAddress(1, kPublicAddrs[1]);
+
+ CreateChannels();
+ EXPECT_EQ(rtc::DSCP_NO_CHANGE, GetEndpoint(0)->cd1_.ch_->DefaultDscpValue());
+ EXPECT_EQ(rtc::DSCP_NO_CHANGE, GetEndpoint(1)->cd1_.ch_->DefaultDscpValue());
+ GetEndpoint(0)->cd1_.ch_->SetOption(rtc::Socket::OPT_DSCP, rtc::DSCP_CS6);
+ GetEndpoint(1)->cd1_.ch_->SetOption(rtc::Socket::OPT_DSCP, rtc::DSCP_CS6);
+ EXPECT_EQ(rtc::DSCP_CS6, GetEndpoint(0)->cd1_.ch_->DefaultDscpValue());
+ EXPECT_EQ(rtc::DSCP_CS6, GetEndpoint(1)->cd1_.ch_->DefaultDscpValue());
+ GetEndpoint(0)->cd1_.ch_->SetOption(rtc::Socket::OPT_DSCP, rtc::DSCP_AF41);
+ GetEndpoint(1)->cd1_.ch_->SetOption(rtc::Socket::OPT_DSCP, rtc::DSCP_AF41);
+ EXPECT_EQ(rtc::DSCP_AF41, GetEndpoint(0)->cd1_.ch_->DefaultDscpValue());
+ EXPECT_EQ(rtc::DSCP_AF41, GetEndpoint(1)->cd1_.ch_->DefaultDscpValue());
+ DestroyChannels();
+}
+
+// Verify IPv6 connection is preferred over IPv4.
+TEST_P(P2PTransportChannelTestWithFieldTrials, TestIPv6Connections) {
+ rtc::ScopedFakeClock clock;
+ AddAddress(0, kIPv6PublicAddrs[0]);
+ AddAddress(0, kPublicAddrs[0]);
+ AddAddress(1, kIPv6PublicAddrs[1]);
+ AddAddress(1, kPublicAddrs[1]);
+
+ SetAllocationStepDelay(0, kMinimumStepDelay);
+ SetAllocationStepDelay(1, kMinimumStepDelay);
+
+ // Enable IPv6
+ SetAllocatorFlags(
+ 0, PORTALLOCATOR_ENABLE_IPV6 | PORTALLOCATOR_ENABLE_IPV6_ON_WIFI);
+ SetAllocatorFlags(
+ 1, PORTALLOCATOR_ENABLE_IPV6 | PORTALLOCATOR_ENABLE_IPV6_ON_WIFI);
+
+ CreateChannels();
+
+ EXPECT_TRUE_SIMULATED_WAIT(
+ CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), kIPv6PublicAddrs[0],
+ kIPv6PublicAddrs[1]),
+ kShortTimeout, clock);
+
+ TestSendRecv(&clock);
+ DestroyChannels();
+}
+
+// Testing forceful TURN connections.
+TEST_P(P2PTransportChannelTestWithFieldTrials, TestForceTurn) {
+ rtc::ScopedFakeClock clock;
+ ConfigureEndpoints(
+ NAT_PORT_RESTRICTED, NAT_SYMMETRIC,
+ kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_SHARED_SOCKET,
+ kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_SHARED_SOCKET);
+ set_force_relay(true);
+
+ SetAllocationStepDelay(0, kMinimumStepDelay);
+ SetAllocationStepDelay(1, kMinimumStepDelay);
+
+ CreateChannels();
+
+ EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()),
+ kMediumTimeout, clock);
+
+ EXPECT_TRUE(ep1_ch1()->selected_connection() &&
+ ep2_ch1()->selected_connection());
+
+ EXPECT_EQ(RELAY_PORT_TYPE, RemoteCandidate(ep1_ch1())->type());
+ EXPECT_EQ(RELAY_PORT_TYPE, LocalCandidate(ep1_ch1())->type());
+ EXPECT_EQ(RELAY_PORT_TYPE, RemoteCandidate(ep2_ch1())->type());
+ EXPECT_EQ(RELAY_PORT_TYPE, LocalCandidate(ep2_ch1())->type());
+
+ TestSendRecv(&clock);
+ DestroyChannels();
+}
+
+// Test that if continual gathering is set to true, ICE gathering state will
+// not change to "Complete", and vice versa.
+TEST_P(P2PTransportChannelTestWithFieldTrials, TestContinualGathering) {
+ rtc::ScopedFakeClock clock;
+ ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags,
+ kDefaultPortAllocatorFlags);
+ SetAllocationStepDelay(0, kDefaultStepDelay);
+ SetAllocationStepDelay(1, kDefaultStepDelay);
+ IceConfig continual_gathering_config =
+ CreateIceConfig(1000, GATHER_CONTINUALLY);
+ // By default, ep2 does not gather continually.
+ IceConfig default_config;
+ CreateChannels(continual_gathering_config, default_config);
+
+ EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()),
+ kMediumTimeout, clock);
+ SIMULATED_WAIT(
+ IceGatheringState::kIceGatheringComplete == ep1_ch1()->gathering_state(),
+ kShortTimeout, clock);
+ EXPECT_EQ(IceGatheringState::kIceGatheringGathering,
+ ep1_ch1()->gathering_state());
+ // By now, ep2 should have completed gathering.
+ EXPECT_EQ(IceGatheringState::kIceGatheringComplete,
+ ep2_ch1()->gathering_state());
+
+ DestroyChannels();
+}
+
+// Test that a connection succeeds when the P2PTransportChannel uses a pooled
+// PortAllocatorSession that has not yet finished gathering candidates.
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ TestUsingPooledSessionBeforeDoneGathering) {
+ rtc::ScopedFakeClock clock;
+ ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags,
+ kDefaultPortAllocatorFlags);
+ // First create a pooled session for each endpoint.
+ auto& allocator_1 = GetEndpoint(0)->allocator_;
+ auto& allocator_2 = GetEndpoint(1)->allocator_;
+ int pool_size = 1;
+ allocator_1->SetConfiguration(allocator_1->stun_servers(),
+ allocator_1->turn_servers(), pool_size,
+ webrtc::NO_PRUNE);
+ allocator_2->SetConfiguration(allocator_2->stun_servers(),
+ allocator_2->turn_servers(), pool_size,
+ webrtc::NO_PRUNE);
+ const PortAllocatorSession* pooled_session_1 =
+ allocator_1->GetPooledSession();
+ const PortAllocatorSession* pooled_session_2 =
+ allocator_2->GetPooledSession();
+ ASSERT_NE(nullptr, pooled_session_1);
+ ASSERT_NE(nullptr, pooled_session_2);
+ // Sanity check that pooled sessions haven't gathered anything yet.
+ EXPECT_TRUE(pooled_session_1->ReadyPorts().empty());
+ EXPECT_TRUE(pooled_session_1->ReadyCandidates().empty());
+ EXPECT_TRUE(pooled_session_2->ReadyPorts().empty());
+ EXPECT_TRUE(pooled_session_2->ReadyCandidates().empty());
+ // Now let the endpoints connect and try exchanging some data.
+ CreateChannels();
+ EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()),
+ kMediumTimeout, clock);
+ TestSendRecv(&clock);
+ // Make sure the P2PTransportChannels are actually using ports from the
+ // pooled sessions.
+ auto pooled_ports_1 = pooled_session_1->ReadyPorts();
+ auto pooled_ports_2 = pooled_session_2->ReadyPorts();
+ EXPECT_THAT(pooled_ports_1,
+ Contains(ep1_ch1()->selected_connection()->PortForTest()));
+ EXPECT_THAT(pooled_ports_2,
+ Contains(ep2_ch1()->selected_connection()->PortForTest()));
+ DestroyChannels();
+}
+
+// Test that a connection succeeds when the P2PTransportChannel uses a pooled
+// PortAllocatorSession that already finished gathering candidates.
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ TestUsingPooledSessionAfterDoneGathering) {
+ rtc::ScopedFakeClock clock;
+ ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags,
+ kDefaultPortAllocatorFlags);
+ // First create a pooled session for each endpoint.
+ auto& allocator_1 = GetEndpoint(0)->allocator_;
+ auto& allocator_2 = GetEndpoint(1)->allocator_;
+ int pool_size = 1;
+ allocator_1->SetConfiguration(allocator_1->stun_servers(),
+ allocator_1->turn_servers(), pool_size,
+ webrtc::NO_PRUNE);
+ allocator_2->SetConfiguration(allocator_2->stun_servers(),
+ allocator_2->turn_servers(), pool_size,
+ webrtc::NO_PRUNE);
+ const PortAllocatorSession* pooled_session_1 =
+ allocator_1->GetPooledSession();
+ const PortAllocatorSession* pooled_session_2 =
+ allocator_2->GetPooledSession();
+ ASSERT_NE(nullptr, pooled_session_1);
+ ASSERT_NE(nullptr, pooled_session_2);
+ // Wait for the pooled sessions to finish gathering before the
+ // P2PTransportChannels try to use them.
+ EXPECT_TRUE_SIMULATED_WAIT(pooled_session_1->CandidatesAllocationDone() &&
+ pooled_session_2->CandidatesAllocationDone(),
+ kDefaultTimeout, clock);
+ // Now let the endpoints connect and try exchanging some data.
+ CreateChannels();
+ EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()),
+ kMediumTimeout, clock);
+ TestSendRecv(&clock);
+ // Make sure the P2PTransportChannels are actually using ports from the
+ // pooled sessions.
+ auto pooled_ports_1 = pooled_session_1->ReadyPorts();
+ auto pooled_ports_2 = pooled_session_2->ReadyPorts();
+ EXPECT_THAT(pooled_ports_1,
+ Contains(ep1_ch1()->selected_connection()->PortForTest()));
+ EXPECT_THAT(pooled_ports_2,
+ Contains(ep2_ch1()->selected_connection()->PortForTest()));
+ DestroyChannels();
+}
+
+// Test that when the "presume_writable_when_fully_relayed" flag is set to
+// true and there's a TURN-TURN candidate pair, it's presumed to be writable
+// as soon as it's created.
+// TODO(deadbeef): Move this and other "presumed writable" tests into a test
+// class that operates on a single P2PTransportChannel, once an appropriate one
+// (which supports TURN servers and TURN candidate gathering) is available.
+TEST_P(P2PTransportChannelTestWithFieldTrials, TurnToTurnPresumedWritable) {
+ ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags,
+ kDefaultPortAllocatorFlags);
+ // Only configure one channel so we can control when the remote candidate
+ // is added.
+ GetEndpoint(0)->cd1_.ch_ = CreateChannel(0, ICE_CANDIDATE_COMPONENT_DEFAULT,
+ kIceParams[0], kIceParams[1]);
+ IceConfig config;
+ config.presume_writable_when_fully_relayed = true;
+ ep1_ch1()->SetIceConfig(config);
+ ep1_ch1()->MaybeStartGathering();
+ EXPECT_EQ_WAIT(IceGatheringState::kIceGatheringComplete,
+ ep1_ch1()->gathering_state(), kDefaultTimeout);
+ // Add two remote candidates; a host candidate (with higher priority)
+ // and TURN candidate.
+ ep1_ch1()->AddRemoteCandidate(
+ CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 100));
+ ep1_ch1()->AddRemoteCandidate(
+ CreateUdpCandidate(RELAY_PORT_TYPE, "2.2.2.2", 2, 0));
+ // Expect that the TURN-TURN candidate pair will be prioritized since it's
+ // "probably writable".
+ EXPECT_TRUE_WAIT(ep1_ch1()->selected_connection() != nullptr, kShortTimeout);
+ EXPECT_EQ(RELAY_PORT_TYPE, LocalCandidate(ep1_ch1())->type());
+ EXPECT_EQ(RELAY_PORT_TYPE, RemoteCandidate(ep1_ch1())->type());
+ // Also expect that the channel instantly indicates that it's writable since
+ // it has a TURN-TURN pair.
+ EXPECT_TRUE(ep1_ch1()->writable());
+ EXPECT_TRUE(GetEndpoint(0)->ready_to_send_);
+ // Also make sure we can immediately send packets.
+ const char* data = "test";
+ int len = static_cast<int>(strlen(data));
+ EXPECT_EQ(len, SendData(ep1_ch1(), data, len));
+ // Prevent pending messages to access endpoints after their destruction.
+ DestroyChannels();
+}
+
+// Test that a TURN/peer reflexive candidate pair is also presumed writable.
+TEST_P(P2PTransportChannelTestWithFieldTrials, TurnToPrflxPresumedWritable) {
+ rtc::ScopedFakeClock fake_clock;
+
+ // We need to add artificial network delay to verify that the connection
+ // is presumed writable before it's actually writable. Without this delay
+ // it would become writable instantly.
+ virtual_socket_server()->set_delay_mean(50);
+ virtual_socket_server()->UpdateDelayDistribution();
+
+ ConfigureEndpoints(NAT_SYMMETRIC, NAT_SYMMETRIC, kDefaultPortAllocatorFlags,
+ kDefaultPortAllocatorFlags);
+ // We want the remote TURN candidate to show up as prflx. To do this we need
+ // to configure the server to accept packets from an address we haven't
+ // explicitly installed permission for.
+ test_turn_server()->set_enable_permission_checks(false);
+ IceConfig config;
+ config.presume_writable_when_fully_relayed = true;
+ GetEndpoint(0)->cd1_.ch_ = CreateChannel(0, ICE_CANDIDATE_COMPONENT_DEFAULT,
+ kIceParams[0], kIceParams[1]);
+ GetEndpoint(1)->cd1_.ch_ = CreateChannel(1, ICE_CANDIDATE_COMPONENT_DEFAULT,
+ kIceParams[1], kIceParams[0]);
+ ep1_ch1()->SetIceConfig(config);
+ ep2_ch1()->SetIceConfig(config);
+ // Don't signal candidates from channel 2, so that channel 1 sees the TURN
+ // candidate as peer reflexive.
+ PauseCandidates(1);
+ ep1_ch1()->MaybeStartGathering();
+ ep2_ch1()->MaybeStartGathering();
+
+ // Wait for the TURN<->prflx connection.
+ EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->receiving() && ep1_ch1()->writable(),
+ kShortTimeout, fake_clock);
+ ASSERT_NE(nullptr, ep1_ch1()->selected_connection());
+ EXPECT_EQ(RELAY_PORT_TYPE, LocalCandidate(ep1_ch1())->type());
+ EXPECT_EQ(PRFLX_PORT_TYPE, RemoteCandidate(ep1_ch1())->type());
+ // Make sure that at this point the connection is only presumed writable,
+ // not fully writable.
+ EXPECT_FALSE(ep1_ch1()->selected_connection()->writable());
+
+ // Now wait for it to actually become writable.
+ EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->selected_connection()->writable(),
+ kShortTimeout, fake_clock);
+
+ // Explitly destroy channels, before fake clock is destroyed.
+ DestroyChannels();
+}
+
+// Test that a presumed-writable TURN<->TURN connection is preferred above an
+// unreliable connection (one that has failed to be pinged for some time).
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ PresumedWritablePreferredOverUnreliable) {
+ rtc::ScopedFakeClock fake_clock;
+
+ ConfigureEndpoints(NAT_SYMMETRIC, NAT_SYMMETRIC, kDefaultPortAllocatorFlags,
+ kDefaultPortAllocatorFlags);
+ IceConfig config;
+ config.presume_writable_when_fully_relayed = true;
+ GetEndpoint(0)->cd1_.ch_ = CreateChannel(0, ICE_CANDIDATE_COMPONENT_DEFAULT,
+ kIceParams[0], kIceParams[1]);
+ GetEndpoint(1)->cd1_.ch_ = CreateChannel(1, ICE_CANDIDATE_COMPONENT_DEFAULT,
+ kIceParams[1], kIceParams[0]);
+ ep1_ch1()->SetIceConfig(config);
+ ep2_ch1()->SetIceConfig(config);
+ ep1_ch1()->MaybeStartGathering();
+ ep2_ch1()->MaybeStartGathering();
+ // Wait for initial connection as usual.
+ EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()),
+ kShortTimeout, fake_clock);
+ const Connection* old_selected_connection = ep1_ch1()->selected_connection();
+ // Destroy the second channel and wait for the current connection on the
+ // first channel to become "unreliable", making it no longer writable.
+ GetEndpoint(1)->cd1_.ch_.reset();
+ EXPECT_TRUE_SIMULATED_WAIT(!ep1_ch1()->writable(), kDefaultTimeout,
+ fake_clock);
+ EXPECT_NE(nullptr, ep1_ch1()->selected_connection());
+ // Add a remote TURN candidate. The first channel should still have a TURN
+ // port available to make a TURN<->TURN pair that's presumed writable.
+ ep1_ch1()->AddRemoteCandidate(
+ CreateUdpCandidate(RELAY_PORT_TYPE, "2.2.2.2", 2, 0));
+ EXPECT_EQ(RELAY_PORT_TYPE, LocalCandidate(ep1_ch1())->type());
+ EXPECT_EQ(RELAY_PORT_TYPE, RemoteCandidate(ep1_ch1())->type());
+ EXPECT_TRUE(ep1_ch1()->writable());
+ EXPECT_TRUE(GetEndpoint(0)->ready_to_send_);
+ EXPECT_NE(old_selected_connection, ep1_ch1()->selected_connection());
+ // Explitly destroy channels, before fake clock is destroyed.
+ DestroyChannels();
+}
+
+// Ensure that "SignalReadyToSend" is fired as expected with a "presumed
+// writable" connection. Previously this did not work.
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ SignalReadyToSendWithPresumedWritable) {
+ ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags,
+ kDefaultPortAllocatorFlags);
+ // Only test one endpoint, so we can ensure the connection doesn't receive a
+ // binding response and advance beyond being "presumed" writable.
+ GetEndpoint(0)->cd1_.ch_ = CreateChannel(0, ICE_CANDIDATE_COMPONENT_DEFAULT,
+ kIceParams[0], kIceParams[1]);
+ IceConfig config;
+ config.presume_writable_when_fully_relayed = true;
+ ep1_ch1()->SetIceConfig(config);
+ ep1_ch1()->MaybeStartGathering();
+ EXPECT_EQ_WAIT(IceGatheringState::kIceGatheringComplete,
+ ep1_ch1()->gathering_state(), kDefaultTimeout);
+ ep1_ch1()->AddRemoteCandidate(
+ CreateUdpCandidate(RELAY_PORT_TYPE, "1.1.1.1", 1, 0));
+ // Sanity checking the type of the connection.
+ EXPECT_TRUE_WAIT(ep1_ch1()->selected_connection() != nullptr, kShortTimeout);
+ EXPECT_EQ(RELAY_PORT_TYPE, LocalCandidate(ep1_ch1())->type());
+ EXPECT_EQ(RELAY_PORT_TYPE, RemoteCandidate(ep1_ch1())->type());
+
+ // Tell the socket server to block packets (returning EWOULDBLOCK).
+ virtual_socket_server()->SetSendingBlocked(true);
+ const char* data = "test";
+ int len = static_cast<int>(strlen(data));
+ EXPECT_EQ(-1, SendData(ep1_ch1(), data, len));
+
+ // Reset `ready_to_send_` flag, which is set to true if the event fires as it
+ // should.
+ GetEndpoint(0)->ready_to_send_ = false;
+ virtual_socket_server()->SetSendingBlocked(false);
+ EXPECT_TRUE(GetEndpoint(0)->ready_to_send_);
+ EXPECT_EQ(len, SendData(ep1_ch1(), data, len));
+ DestroyChannels();
+}
+
+// Test that role conflict error responses are sent as expected when receiving a
+// ping from an unknown address over a TURN connection. Regression test for
+// crbug.com/webrtc/9034.
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ TurnToPrflxSelectedAfterResolvingIceControllingRoleConflict) {
+ rtc::ScopedFakeClock clock;
+ // Gather only relay candidates.
+ ConfigureEndpoints(NAT_SYMMETRIC, NAT_SYMMETRIC,
+ kDefaultPortAllocatorFlags | PORTALLOCATOR_DISABLE_UDP |
+ PORTALLOCATOR_DISABLE_STUN | PORTALLOCATOR_DISABLE_TCP,
+ kDefaultPortAllocatorFlags | PORTALLOCATOR_DISABLE_UDP |
+ PORTALLOCATOR_DISABLE_STUN |
+ PORTALLOCATOR_DISABLE_TCP);
+ // With conflicting ICE roles, endpoint 1 has the higher tie breaker and will
+ // send a binding error response.
+ SetIceRole(0, ICEROLE_CONTROLLING);
+ SetIceTiebreaker(0, kHighTiebreaker);
+ SetIceRole(1, ICEROLE_CONTROLLING);
+ SetIceTiebreaker(1, kLowTiebreaker);
+ // We want the remote TURN candidate to show up as prflx. To do this we need
+ // to configure the server to accept packets from an address we haven't
+ // explicitly installed permission for.
+ test_turn_server()->set_enable_permission_checks(false);
+ GetEndpoint(0)->cd1_.ch_ = CreateChannel(0, ICE_CANDIDATE_COMPONENT_DEFAULT,
+ kIceParams[0], kIceParams[1]);
+ GetEndpoint(1)->cd1_.ch_ = CreateChannel(1, ICE_CANDIDATE_COMPONENT_DEFAULT,
+ kIceParams[1], kIceParams[0]);
+ // Don't signal candidates from channel 2, so that channel 1 sees the TURN
+ // candidate as peer reflexive.
+ PauseCandidates(1);
+ ep1_ch1()->MaybeStartGathering();
+ ep2_ch1()->MaybeStartGathering();
+
+ EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->receiving() && ep1_ch1()->writable(),
+ kMediumTimeout, clock);
+
+ ASSERT_NE(nullptr, ep1_ch1()->selected_connection());
+
+ EXPECT_EQ(RELAY_PORT_TYPE, LocalCandidate(ep1_ch1())->type());
+ EXPECT_EQ(PRFLX_PORT_TYPE, RemoteCandidate(ep1_ch1())->type());
+
+ DestroyChannels();
+}
+
+// Test that the writability can be established with the piggyback
+// acknowledgement in the connectivity check from the remote peer.
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ CanConnectWithPiggybackCheckAcknowledgementWhenCheckResponseBlocked) {
+ webrtc::test::ScopedKeyValueConfig field_trials(
+ field_trials_, "WebRTC-PiggybackIceCheckAcknowledgement/Enabled/");
+ rtc::ScopedFakeClock clock;
+ ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts);
+ IceConfig ep1_config;
+ IceConfig ep2_config = CreateIceConfig(1000, GATHER_CONTINUALLY);
+ // Let ep2 be tolerable of the loss of connectivity checks, so that it keeps
+ // sending pings even after ep1 becomes unwritable as we configure the
+ // firewall below.
+ ep2_config.receiving_timeout = 30 * 1000;
+ ep2_config.ice_unwritable_timeout = 30 * 1000;
+ ep2_config.ice_unwritable_min_checks = 30;
+ ep2_config.ice_inactive_timeout = 60 * 1000;
+
+ CreateChannels(ep1_config, ep2_config);
+
+ // Wait until both sides become writable for the first time.
+ EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()),
+ kDefaultTimeout, clock);
+ // Block the ingress traffic to ep1 so that there is no check response from
+ // ep2.
+ ASSERT_NE(nullptr, LocalCandidate(ep1_ch1()));
+ fw()->AddRule(false, rtc::FP_ANY, rtc::FD_IN,
+ LocalCandidate(ep1_ch1())->address());
+ // Wait until ep1 becomes unwritable. At the same time ep2 should be still
+ // fine so that it will keep sending pings.
+ EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1() != nullptr && !ep1_ch1()->writable(),
+ kDefaultTimeout, clock);
+ EXPECT_TRUE(ep2_ch1() != nullptr && ep2_ch1()->writable());
+ // Now let the pings from ep2 to flow but block any pings from ep1, so that
+ // ep1 can only become writable again after receiving an incoming ping from
+ // ep2 with piggyback acknowledgement of its previously sent pings. Note
+ // though that ep1 should have stopped sending pings after becoming unwritable
+ // in the current design.
+ fw()->ClearRules();
+ fw()->AddRule(false, rtc::FP_ANY, rtc::FD_OUT,
+ LocalCandidate(ep1_ch1())->address());
+ EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1() != nullptr && ep1_ch1()->writable(),
+ kDefaultTimeout, clock);
+ DestroyChannels();
+}
+
+// Test what happens when we have 2 users behind the same NAT. This can lead
+// to interesting behavior because the STUN server will only give out the
+// address of the outermost NAT.
+class P2PTransportChannelSameNatTest : public P2PTransportChannelTestBase,
+ public WithParamInterface<std::string> {
+ public:
+ P2PTransportChannelSameNatTest() : P2PTransportChannelTestBase(GetParam()) {}
+
+ protected:
+ void ConfigureEndpoints(Config nat_type, Config config1, Config config2) {
+ RTC_CHECK_GE(nat_type, NAT_FULL_CONE);
+ RTC_CHECK_LE(nat_type, NAT_SYMMETRIC);
+ rtc::NATSocketServer::Translator* outer_nat = nat()->AddTranslator(
+ kPublicAddrs[0], kNatAddrs[0],
+ static_cast<rtc::NATType>(nat_type - NAT_FULL_CONE));
+ ConfigureEndpoint(outer_nat, 0, config1);
+ ConfigureEndpoint(outer_nat, 1, config2);
+ set_remote_ice_parameter_source(FROM_SETICEPARAMETERS);
+ }
+ void ConfigureEndpoint(rtc::NATSocketServer::Translator* nat,
+ int endpoint,
+ Config config) {
+ RTC_CHECK(config <= NAT_SYMMETRIC);
+ if (config == OPEN) {
+ AddAddress(endpoint, kPrivateAddrs[endpoint]);
+ nat->AddClient(kPrivateAddrs[endpoint]);
+ } else {
+ AddAddress(endpoint, kCascadedPrivateAddrs[endpoint]);
+ nat->AddTranslator(kPrivateAddrs[endpoint], kCascadedNatAddrs[endpoint],
+ static_cast<rtc::NATType>(config - NAT_FULL_CONE))
+ ->AddClient(kCascadedPrivateAddrs[endpoint]);
+ }
+ }
+};
+
+INSTANTIATE_TEST_SUITE_P(Legacy, P2PTransportChannelSameNatTest, Values(""));
+INSTANTIATE_TEST_SUITE_P(Active,
+ P2PTransportChannelSameNatTest,
+ Values("WebRTC-UseActiveIceController/Enabled/"));
+
+TEST_P(P2PTransportChannelSameNatTest, TestConesBehindSameCone) {
+ ConfigureEndpoints(NAT_FULL_CONE, NAT_FULL_CONE, NAT_FULL_CONE);
+ Test(
+ P2PTransportChannelTestBase::Result("prflx", "udp", "stun", "udp", 1000));
+}
+
+// Test what happens when we have multiple available pathways.
+// In the future we will try different RTTs and configs for the different
+// interfaces, so that we can simulate a user with Ethernet and VPN networks.
+class P2PTransportChannelMultihomedTest
+ : public P2PTransportChannelTestWithFieldTrials {
+ public:
+ const Connection* GetConnectionWithRemoteAddress(
+ P2PTransportChannel* channel,
+ const SocketAddress& address) {
+ for (Connection* conn : channel->connections()) {
+ if (HasRemoteAddress(conn, address)) {
+ return conn;
+ }
+ }
+ return nullptr;
+ }
+
+ Connection* GetConnectionWithLocalAddress(P2PTransportChannel* channel,
+ const SocketAddress& address) {
+ for (Connection* conn : channel->connections()) {
+ if (HasLocalAddress(conn, address)) {
+ return conn;
+ }
+ }
+ return nullptr;
+ }
+
+ Connection* GetConnection(P2PTransportChannel* channel,
+ const SocketAddress& local,
+ const SocketAddress& remote) {
+ for (Connection* conn : channel->connections()) {
+ if (HasLocalAddress(conn, local) && HasRemoteAddress(conn, remote)) {
+ return conn;
+ }
+ }
+ return nullptr;
+ }
+
+ Connection* GetBestConnection(P2PTransportChannel* channel) {
+ rtc::ArrayView<Connection*> connections = channel->connections();
+ auto it = absl::c_find(connections, channel->selected_connection());
+ if (it == connections.end()) {
+ return nullptr;
+ }
+ return *it;
+ }
+
+ Connection* GetBackupConnection(P2PTransportChannel* channel) {
+ rtc::ArrayView<Connection*> connections = channel->connections();
+ auto it = absl::c_find_if_not(connections, [channel](Connection* conn) {
+ return conn == channel->selected_connection();
+ });
+ if (it == connections.end()) {
+ return nullptr;
+ }
+ return *it;
+ }
+
+ void DestroyAllButBestConnection(P2PTransportChannel* channel) {
+ const Connection* selected_connection = channel->selected_connection();
+ // Copy the list of connections since the original will be modified.
+ rtc::ArrayView<Connection*> view = channel->connections();
+ std::vector<Connection*> connections(view.begin(), view.end());
+ for (Connection* conn : connections) {
+ if (conn != selected_connection)
+ channel->RemoveConnectionForTest(conn);
+ }
+ }
+};
+
+INSTANTIATE_TEST_SUITE_P(Legacy, P2PTransportChannelMultihomedTest, Values(""));
+INSTANTIATE_TEST_SUITE_P(Active,
+ P2PTransportChannelMultihomedTest,
+ Values("WebRTC-UseActiveIceController/Enabled/"));
+
+// Test that we can establish connectivity when both peers are multihomed.
+TEST_P(P2PTransportChannelMultihomedTest, TestBasic) {
+ AddAddress(0, kPublicAddrs[0]);
+ AddAddress(0, kAlternateAddrs[0]);
+ AddAddress(1, kPublicAddrs[1]);
+ AddAddress(1, kAlternateAddrs[1]);
+ Test(kLocalUdpToLocalUdp);
+}
+
+// Test that we can quickly switch links if an interface goes down.
+// The controlled side has two interfaces and one will die.
+TEST_P(P2PTransportChannelMultihomedTest, TestFailoverControlledSide) {
+ rtc::ScopedFakeClock clock;
+ AddAddress(0, kPublicAddrs[0]);
+ // Simulate failing over from Wi-Fi to cell interface.
+ AddAddress(1, kPublicAddrs[1], "eth0", rtc::ADAPTER_TYPE_WIFI);
+ AddAddress(1, kAlternateAddrs[1], "wlan0", rtc::ADAPTER_TYPE_CELLULAR);
+
+ // Use only local ports for simplicity.
+ SetAllocatorFlags(0, kOnlyLocalPorts);
+ SetAllocatorFlags(1, kOnlyLocalPorts);
+
+ // Make the receiving timeout shorter for testing.
+ IceConfig config = CreateIceConfig(1000, GATHER_ONCE);
+ // Create channels and let them go writable, as usual.
+ CreateChannels(config, config);
+
+ EXPECT_TRUE_SIMULATED_WAIT(
+ CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), kPublicAddrs[0],
+ kPublicAddrs[1]),
+ kMediumTimeout, clock);
+
+ // Blackhole any traffic to or from the public addrs.
+ RTC_LOG(LS_INFO) << "Failing over...";
+ fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[1]);
+ // The selected connections may switch, so keep references to them.
+ const Connection* selected_connection1 = ep1_ch1()->selected_connection();
+ // We should detect loss of receiving within 1 second or so.
+ EXPECT_TRUE_SIMULATED_WAIT(!selected_connection1->receiving(), kMediumTimeout,
+ clock);
+
+ // We should switch over to use the alternate addr on both sides
+ // when we are not receiving.
+ EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->selected_connection()->receiving() &&
+ ep2_ch1()->selected_connection()->receiving(),
+ kMediumTimeout, clock);
+ EXPECT_TRUE(LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0]));
+ EXPECT_TRUE(
+ RemoteCandidate(ep1_ch1())->address().EqualIPs(kAlternateAddrs[1]));
+ EXPECT_TRUE(
+ LocalCandidate(ep2_ch1())->address().EqualIPs(kAlternateAddrs[1]));
+
+ DestroyChannels();
+}
+
+// Test that we can quickly switch links if an interface goes down.
+// The controlling side has two interfaces and one will die.
+TEST_P(P2PTransportChannelMultihomedTest, TestFailoverControllingSide) {
+ rtc::ScopedFakeClock clock;
+ // Simulate failing over from Wi-Fi to cell interface.
+ AddAddress(0, kPublicAddrs[0], "eth0", rtc::ADAPTER_TYPE_WIFI);
+ AddAddress(0, kAlternateAddrs[0], "wlan0", rtc::ADAPTER_TYPE_CELLULAR);
+ AddAddress(1, kPublicAddrs[1]);
+
+ // Use only local ports for simplicity.
+ SetAllocatorFlags(0, kOnlyLocalPorts);
+ SetAllocatorFlags(1, kOnlyLocalPorts);
+
+ // Make the receiving timeout shorter for testing.
+ IceConfig config = CreateIceConfig(1000, GATHER_ONCE);
+ // Create channels and let them go writable, as usual.
+ CreateChannels(config, config);
+ EXPECT_TRUE_SIMULATED_WAIT(
+ CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), kPublicAddrs[0],
+ kPublicAddrs[1]),
+ kMediumTimeout, clock);
+
+ // Blackhole any traffic to or from the public addrs.
+ RTC_LOG(LS_INFO) << "Failing over...";
+ fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[0]);
+
+ // We should detect loss of receiving within 1 second or so.
+ // We should switch over to use the alternate addr on both sides
+ // when we are not receiving.
+ EXPECT_TRUE_SIMULATED_WAIT(
+ CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), kAlternateAddrs[0],
+ kPublicAddrs[1]),
+ kMediumTimeout, clock);
+
+ DestroyChannels();
+}
+
+// Tests that we can quickly switch links if an interface goes down when
+// there are many connections.
+TEST_P(P2PTransportChannelMultihomedTest, TestFailoverWithManyConnections) {
+ rtc::ScopedFakeClock clock;
+ test_turn_server()->AddInternalSocket(kTurnTcpIntAddr, PROTO_TCP);
+ RelayServerConfig turn_server;
+ turn_server.credentials = kRelayCredentials;
+ turn_server.ports.push_back(ProtocolAddress(kTurnTcpIntAddr, PROTO_TCP));
+ GetAllocator(0)->AddTurnServerForTesting(turn_server);
+ GetAllocator(1)->AddTurnServerForTesting(turn_server);
+ // Enable IPv6
+ SetAllocatorFlags(
+ 0, PORTALLOCATOR_ENABLE_IPV6 | PORTALLOCATOR_ENABLE_IPV6_ON_WIFI);
+ SetAllocatorFlags(
+ 1, PORTALLOCATOR_ENABLE_IPV6 | PORTALLOCATOR_ENABLE_IPV6_ON_WIFI);
+ SetAllocationStepDelay(0, kMinimumStepDelay);
+ SetAllocationStepDelay(1, kMinimumStepDelay);
+
+ auto& wifi = kPublicAddrs;
+ auto& cellular = kAlternateAddrs;
+ auto& wifiIpv6 = kIPv6PublicAddrs;
+ auto& cellularIpv6 = kIPv6AlternateAddrs;
+ AddAddress(0, wifi[0], "wifi0", rtc::ADAPTER_TYPE_WIFI);
+ AddAddress(0, wifiIpv6[0], "wifi0", rtc::ADAPTER_TYPE_WIFI);
+ AddAddress(0, cellular[0], "cellular0", rtc::ADAPTER_TYPE_CELLULAR);
+ AddAddress(0, cellularIpv6[0], "cellular0", rtc::ADAPTER_TYPE_CELLULAR);
+ AddAddress(1, wifi[1], "wifi1", rtc::ADAPTER_TYPE_WIFI);
+ AddAddress(1, wifiIpv6[1], "wifi1", rtc::ADAPTER_TYPE_WIFI);
+ AddAddress(1, cellular[1], "cellular1", rtc::ADAPTER_TYPE_CELLULAR);
+ AddAddress(1, cellularIpv6[1], "cellular1", rtc::ADAPTER_TYPE_CELLULAR);
+
+ // Set smaller delay on the TCP TURN server so that TCP TURN candidates
+ // will be created in time.
+ virtual_socket_server()->SetDelayOnAddress(kTurnTcpIntAddr, 1);
+ virtual_socket_server()->SetDelayOnAddress(kTurnUdpExtAddr, 1);
+ virtual_socket_server()->set_delay_mean(500);
+ virtual_socket_server()->UpdateDelayDistribution();
+
+ // Make the receiving timeout shorter for testing.
+ IceConfig config = CreateIceConfig(1000, GATHER_CONTINUALLY);
+ // Create channels and let them go writable, as usual.
+ CreateChannels(config, config, true /* ice_renomination */);
+ EXPECT_TRUE_SIMULATED_WAIT(
+ CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), wifiIpv6[0],
+ wifiIpv6[1]),
+ kMediumTimeout, clock);
+
+ // Blackhole any traffic to or from the wifi on endpoint 1.
+ RTC_LOG(LS_INFO) << "Failing over...";
+ fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, wifi[0]);
+ fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, wifiIpv6[0]);
+
+ // The selected connections may switch, so keep references to them.
+ const Connection* selected_connection1 = ep1_ch1()->selected_connection();
+ const Connection* selected_connection2 = ep2_ch1()->selected_connection();
+ EXPECT_TRUE_SIMULATED_WAIT(
+ !selected_connection1->receiving() && !selected_connection2->receiving(),
+ kMediumTimeout, clock);
+
+ // Per-network best connections will be pinged at relatively higher rate when
+ // the selected connection becomes not receiving.
+ Connection* per_network_best_connection1 =
+ GetConnection(ep1_ch1(), cellularIpv6[0], wifiIpv6[1]);
+ ASSERT_NE(nullptr, per_network_best_connection1);
+ int64_t last_ping_sent1 = per_network_best_connection1->last_ping_sent();
+ int num_pings_sent1 = per_network_best_connection1->num_pings_sent();
+ EXPECT_TRUE_SIMULATED_WAIT(
+ num_pings_sent1 < per_network_best_connection1->num_pings_sent(),
+ kMediumTimeout, clock);
+ ASSERT_GT(per_network_best_connection1->num_pings_sent() - num_pings_sent1,
+ 0);
+ int64_t ping_interval1 =
+ (per_network_best_connection1->last_ping_sent() - last_ping_sent1) /
+ (per_network_best_connection1->num_pings_sent() - num_pings_sent1);
+ constexpr int SCHEDULING_DELAY = 200;
+ EXPECT_LT(
+ ping_interval1,
+ WEAK_OR_STABILIZING_WRITABLE_CONNECTION_PING_INTERVAL + SCHEDULING_DELAY);
+
+ // It should switch over to use the cellular IPv6 addr on endpoint 1 before
+ // it timed out on writing.
+ EXPECT_TRUE_SIMULATED_WAIT(
+ CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), cellularIpv6[0],
+ wifiIpv6[1]),
+ kMediumTimeout, clock);
+
+ DestroyChannels();
+}
+
+// Test that when the controlling side switches the selected connection,
+// the nomination of the selected connection on the controlled side will
+// increase.
+TEST_P(P2PTransportChannelMultihomedTest, TestIceRenomination) {
+ rtc::ScopedFakeClock clock;
+ // Simulate failing over from Wi-Fi to cell interface.
+ AddAddress(0, kPublicAddrs[0], "eth0", rtc::ADAPTER_TYPE_WIFI);
+ AddAddress(0, kAlternateAddrs[0], "wlan0", rtc::ADAPTER_TYPE_CELLULAR);
+ AddAddress(1, kPublicAddrs[1]);
+
+ // Use only local ports for simplicity.
+ SetAllocatorFlags(0, kOnlyLocalPorts);
+ SetAllocatorFlags(1, kOnlyLocalPorts);
+
+ // We want it to set the remote ICE parameters when creating channels.
+ set_remote_ice_parameter_source(FROM_SETICEPARAMETERS);
+ // Make the receiving timeout shorter for testing.
+ IceConfig config = CreateIceConfig(1000, GATHER_ONCE);
+ // Create channels with ICE renomination and let them go writable as usual.
+ CreateChannels(config, config, true);
+ ASSERT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()),
+ kMediumTimeout, clock);
+ EXPECT_TRUE_SIMULATED_WAIT(
+ ep2_ch1()->selected_connection()->remote_nomination() > 0 &&
+ ep1_ch1()->selected_connection()->acked_nomination() > 0,
+ kDefaultTimeout, clock);
+ const Connection* selected_connection1 = ep1_ch1()->selected_connection();
+ Connection* selected_connection2 =
+ const_cast<Connection*>(ep2_ch1()->selected_connection());
+ uint32_t remote_nomination2 = selected_connection2->remote_nomination();
+ // `selected_connection2` should not be nominated any more since the previous
+ // nomination has been acknowledged.
+ ConnectSignalNominated(selected_connection2);
+ SIMULATED_WAIT(nominated(), kMediumTimeout, clock);
+ EXPECT_FALSE(nominated());
+
+ // Blackhole any traffic to or from the public addrs.
+ RTC_LOG(LS_INFO) << "Failing over...";
+ fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[0]);
+
+ // The selected connection on the controlling side should switch.
+ EXPECT_TRUE_SIMULATED_WAIT(
+ ep1_ch1()->selected_connection() != selected_connection1, kMediumTimeout,
+ clock);
+ // The connection on the controlled side should be nominated again
+ // and have an increased nomination.
+ EXPECT_TRUE_SIMULATED_WAIT(
+ ep2_ch1()->selected_connection()->remote_nomination() >
+ remote_nomination2,
+ kDefaultTimeout, clock);
+
+ DestroyChannels();
+}
+
+// Test that if an interface fails temporarily and then recovers quickly,
+// the selected connection will not switch.
+// The case that it will switch over to the backup connection if the selected
+// connection does not recover after enough time is covered in
+// TestFailoverControlledSide and TestFailoverControllingSide.
+TEST_P(P2PTransportChannelMultihomedTest,
+ TestConnectionSwitchDampeningControlledSide) {
+ rtc::ScopedFakeClock clock;
+ AddAddress(0, kPublicAddrs[0]);
+ // Simulate failing over from Wi-Fi to cell interface.
+ AddAddress(1, kPublicAddrs[1], "eth0", rtc::ADAPTER_TYPE_WIFI);
+ AddAddress(1, kAlternateAddrs[1], "wlan0", rtc::ADAPTER_TYPE_CELLULAR);
+
+ // Use only local ports for simplicity.
+ SetAllocatorFlags(0, kOnlyLocalPorts);
+ SetAllocatorFlags(1, kOnlyLocalPorts);
+
+ // Create channels and let them go writable, as usual.
+ CreateChannels();
+
+ EXPECT_TRUE_SIMULATED_WAIT(
+ CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), kPublicAddrs[0],
+ kPublicAddrs[1]),
+ kMediumTimeout, clock);
+
+ // Make the receiving timeout shorter for testing.
+ IceConfig config = CreateIceConfig(1000, GATHER_ONCE);
+ ep1_ch1()->SetIceConfig(config);
+ ep2_ch1()->SetIceConfig(config);
+ reset_selected_candidate_pair_switches();
+
+ // Blackhole any traffic to or from the public addrs.
+ RTC_LOG(LS_INFO) << "Failing over...";
+ fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[1]);
+
+ // The selected connections may switch, so keep references to them.
+ const Connection* selected_connection1 = ep1_ch1()->selected_connection();
+ // We should detect loss of receiving within 1 second or so.
+ EXPECT_TRUE_SIMULATED_WAIT(!selected_connection1->receiving(), kMediumTimeout,
+ clock);
+ // After a short while, the link recovers itself.
+ SIMULATED_WAIT(false, 10, clock);
+ fw()->ClearRules();
+
+ // We should remain on the public address on both sides and no connection
+ // switches should have happened.
+ EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->selected_connection()->receiving() &&
+ ep2_ch1()->selected_connection()->receiving(),
+ kMediumTimeout, clock);
+ EXPECT_TRUE(RemoteCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[1]));
+ EXPECT_TRUE(LocalCandidate(ep2_ch1())->address().EqualIPs(kPublicAddrs[1]));
+ EXPECT_EQ(0, reset_selected_candidate_pair_switches());
+
+ DestroyChannels();
+}
+
+// Test that if an interface fails temporarily and then recovers quickly,
+// the selected connection will not switch.
+TEST_P(P2PTransportChannelMultihomedTest,
+ TestConnectionSwitchDampeningControllingSide) {
+ rtc::ScopedFakeClock clock;
+ // Simulate failing over from Wi-Fi to cell interface.
+ AddAddress(0, kPublicAddrs[0], "eth0", rtc::ADAPTER_TYPE_WIFI);
+ AddAddress(0, kAlternateAddrs[0], "wlan0", rtc::ADAPTER_TYPE_CELLULAR);
+ AddAddress(1, kPublicAddrs[1]);
+
+ // Use only local ports for simplicity.
+ SetAllocatorFlags(0, kOnlyLocalPorts);
+ SetAllocatorFlags(1, kOnlyLocalPorts);
+
+ // Create channels and let them go writable, as usual.
+ CreateChannels();
+ EXPECT_TRUE_SIMULATED_WAIT(
+ CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), kPublicAddrs[0],
+ kPublicAddrs[1]),
+ kMediumTimeout, clock);
+
+ // Make the receiving timeout shorter for testing.
+ IceConfig config = CreateIceConfig(1000, GATHER_ONCE);
+ ep1_ch1()->SetIceConfig(config);
+ ep2_ch1()->SetIceConfig(config);
+ reset_selected_candidate_pair_switches();
+
+ // Blackhole any traffic to or from the public addrs.
+ RTC_LOG(LS_INFO) << "Failing over...";
+ fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[0]);
+ // The selected connections may switch, so keep references to them.
+ const Connection* selected_connection1 = ep1_ch1()->selected_connection();
+ // We should detect loss of receiving within 1 second or so.
+ EXPECT_TRUE_SIMULATED_WAIT(!selected_connection1->receiving(), kMediumTimeout,
+ clock);
+ // The link recovers after a short while.
+ SIMULATED_WAIT(false, 10, clock);
+ fw()->ClearRules();
+
+ // We should not switch to the alternate addr on both sides because of the
+ // dampening.
+ EXPECT_TRUE_SIMULATED_WAIT(
+ CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), kPublicAddrs[0],
+ kPublicAddrs[1]),
+ kMediumTimeout, clock);
+ EXPECT_EQ(0, reset_selected_candidate_pair_switches());
+ DestroyChannels();
+}
+
+// Tests that if the remote side's network failed, it won't cause the local
+// side to switch connections and networks.
+TEST_P(P2PTransportChannelMultihomedTest, TestRemoteFailover) {
+ rtc::ScopedFakeClock clock;
+ // The interface names are chosen so that `cellular` would have higher
+ // candidate priority and higher cost.
+ auto& wifi = kPublicAddrs;
+ auto& cellular = kAlternateAddrs;
+ AddAddress(0, wifi[0], "wifi0", rtc::ADAPTER_TYPE_WIFI);
+ AddAddress(0, cellular[0], "cellular0", rtc::ADAPTER_TYPE_CELLULAR);
+ AddAddress(1, wifi[1], "wifi0", rtc::ADAPTER_TYPE_WIFI);
+
+ // Use only local ports for simplicity.
+ SetAllocatorFlags(0, kOnlyLocalPorts);
+ SetAllocatorFlags(1, kOnlyLocalPorts);
+ // Create channels and let them go writable, as usual.
+ CreateChannels();
+ // Make the receiving timeout shorter for testing.
+ // Set the backup connection ping interval to 25s.
+ IceConfig config = CreateIceConfig(1000, GATHER_ONCE, 25000);
+ // Ping the best connection more frequently since we don't have traffic.
+ config.stable_writable_connection_ping_interval = 900;
+ ep1_ch1()->SetIceConfig(config);
+ ep2_ch1()->SetIceConfig(config);
+ // Need to wait to make sure the connections on both networks are writable.
+ EXPECT_TRUE_SIMULATED_WAIT(
+ CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), wifi[0], wifi[1]),
+ kDefaultTimeout, clock);
+ Connection* backup_conn =
+ GetConnectionWithLocalAddress(ep1_ch1(), cellular[0]);
+ ASSERT_NE(nullptr, backup_conn);
+ // After a short while, the backup connection will be writable but not
+ // receiving because backup connection is pinged at a slower rate.
+ EXPECT_TRUE_SIMULATED_WAIT(
+ backup_conn->writable() && !backup_conn->receiving(), kDefaultTimeout,
+ clock);
+ reset_selected_candidate_pair_switches();
+ // Blackhole any traffic to or from the remote WiFi networks.
+ RTC_LOG(LS_INFO) << "Failing over...";
+ fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, wifi[1]);
+
+ int num_switches = 0;
+ SIMULATED_WAIT((num_switches = reset_selected_candidate_pair_switches()) > 0,
+ 20000, clock);
+ EXPECT_EQ(0, num_switches);
+ DestroyChannels();
+}
+
+// Tests that a Wifi-Wifi connection has the highest precedence.
+TEST_P(P2PTransportChannelMultihomedTest, TestPreferWifiToWifiConnection) {
+ // The interface names are chosen so that `cellular` would have higher
+ // candidate priority if it is not for the network type.
+ auto& wifi = kAlternateAddrs;
+ auto& cellular = kPublicAddrs;
+ AddAddress(0, wifi[0], "test0", rtc::ADAPTER_TYPE_WIFI);
+ AddAddress(0, cellular[0], "test1", rtc::ADAPTER_TYPE_CELLULAR);
+ AddAddress(1, wifi[1], "test0", rtc::ADAPTER_TYPE_WIFI);
+ AddAddress(1, cellular[1], "test1", rtc::ADAPTER_TYPE_CELLULAR);
+
+ // Use only local ports for simplicity.
+ SetAllocatorFlags(0, kOnlyLocalPorts);
+ SetAllocatorFlags(1, kOnlyLocalPorts);
+
+ // Create channels and let them go writable, as usual.
+ CreateChannels();
+
+ EXPECT_TRUE_WAIT_MARGIN(CheckConnected(ep1_ch1(), ep2_ch1()), 1000, 1000);
+ // Need to wait to make sure the connections on both networks are writable.
+ EXPECT_TRUE_WAIT(
+ CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), wifi[0], wifi[1]),
+ 1000);
+ DestroyChannels();
+}
+
+// Tests that a Wifi-Cellular connection has higher precedence than
+// a Cellular-Cellular connection.
+TEST_P(P2PTransportChannelMultihomedTest, TestPreferWifiOverCellularNetwork) {
+ // The interface names are chosen so that `cellular` would have higher
+ // candidate priority if it is not for the network type.
+ auto& wifi = kAlternateAddrs;
+ auto& cellular = kPublicAddrs;
+ AddAddress(0, cellular[0], "test1", rtc::ADAPTER_TYPE_CELLULAR);
+ AddAddress(1, wifi[1], "test0", rtc::ADAPTER_TYPE_WIFI);
+ AddAddress(1, cellular[1], "test1", rtc::ADAPTER_TYPE_CELLULAR);
+
+ // Use only local ports for simplicity.
+ SetAllocatorFlags(0, kOnlyLocalPorts);
+ SetAllocatorFlags(1, kOnlyLocalPorts);
+
+ // Create channels and let them go writable, as usual.
+ CreateChannels();
+
+ EXPECT_TRUE_WAIT_MARGIN(CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(),
+ cellular[0], wifi[1]),
+ 1000, 1000);
+ DestroyChannels();
+}
+
+// Test that the backup connection is pinged at a rate no faster than
+// what was configured.
+TEST_P(P2PTransportChannelMultihomedTest, TestPingBackupConnectionRate) {
+ AddAddress(0, kPublicAddrs[0]);
+ // Adding alternate address will make sure `kPublicAddrs` has the higher
+ // priority than others. This is due to FakeNetwork::AddInterface method.
+ AddAddress(1, kAlternateAddrs[1]);
+ AddAddress(1, kPublicAddrs[1]);
+
+ // Use only local ports for simplicity.
+ SetAllocatorFlags(0, kOnlyLocalPorts);
+ SetAllocatorFlags(1, kOnlyLocalPorts);
+
+ // Create channels and let them go writable, as usual.
+ CreateChannels();
+ EXPECT_TRUE_WAIT_MARGIN(CheckConnected(ep1_ch1(), ep2_ch1()), 1000, 1000);
+ int backup_ping_interval = 2000;
+ ep2_ch1()->SetIceConfig(
+ CreateIceConfig(2000, GATHER_ONCE, backup_ping_interval));
+ // After the state becomes COMPLETED, the backup connection will be pinged
+ // once every `backup_ping_interval` milliseconds.
+ ASSERT_TRUE_WAIT(ep2_ch1()->GetState() == IceTransportState::STATE_COMPLETED,
+ 1000);
+ auto connections = ep2_ch1()->connections();
+ ASSERT_EQ(2U, connections.size());
+ Connection* backup_conn = GetBackupConnection(ep2_ch1());
+ EXPECT_TRUE_WAIT(backup_conn->writable(), kMediumTimeout);
+ int64_t last_ping_response_ms = backup_conn->last_ping_response_received();
+ EXPECT_TRUE_WAIT(
+ last_ping_response_ms < backup_conn->last_ping_response_received(),
+ kDefaultTimeout);
+ int time_elapsed =
+ backup_conn->last_ping_response_received() - last_ping_response_ms;
+ RTC_LOG(LS_INFO) << "Time elapsed: " << time_elapsed;
+ EXPECT_GE(time_elapsed, backup_ping_interval);
+
+ DestroyChannels();
+}
+
+// Test that the connection is pinged at a rate no faster than
+// what was configured when stable and writable.
+TEST_P(P2PTransportChannelMultihomedTest, TestStableWritableRate) {
+ AddAddress(0, kPublicAddrs[0]);
+ // Adding alternate address will make sure `kPublicAddrs` has the higher
+ // priority than others. This is due to FakeNetwork::AddInterface method.
+ AddAddress(1, kAlternateAddrs[1]);
+ AddAddress(1, kPublicAddrs[1]);
+
+ // Use only local ports for simplicity.
+ SetAllocatorFlags(0, kOnlyLocalPorts);
+ SetAllocatorFlags(1, kOnlyLocalPorts);
+
+ // Create channels and let them go writable, as usual.
+ CreateChannels();
+ EXPECT_TRUE_WAIT_MARGIN(CheckConnected(ep1_ch1(), ep2_ch1()), 1000, 1000);
+ // Set a value larger than the default value of 2500 ms
+ int ping_interval_ms = 3456;
+ IceConfig config = CreateIceConfig(2 * ping_interval_ms, GATHER_ONCE);
+ config.stable_writable_connection_ping_interval = ping_interval_ms;
+ ep2_ch1()->SetIceConfig(config);
+ // After the state becomes COMPLETED and is stable and writable, the
+ // connection will be pinged once every `ping_interval_ms` milliseconds.
+ ASSERT_TRUE_WAIT(ep2_ch1()->GetState() == IceTransportState::STATE_COMPLETED,
+ 1000);
+ auto connections = ep2_ch1()->connections();
+ ASSERT_EQ(2U, connections.size());
+ Connection* conn = GetBestConnection(ep2_ch1());
+ EXPECT_TRUE_WAIT(conn->writable(), kMediumTimeout);
+
+ int64_t last_ping_response_ms;
+ // Burn through some pings so the connection is stable.
+ for (int i = 0; i < 5; i++) {
+ last_ping_response_ms = conn->last_ping_response_received();
+ EXPECT_TRUE_WAIT(
+ last_ping_response_ms < conn->last_ping_response_received(),
+ kDefaultTimeout);
+ }
+ EXPECT_TRUE(conn->stable(last_ping_response_ms)) << "Connection not stable";
+ int time_elapsed =
+ conn->last_ping_response_received() - last_ping_response_ms;
+ RTC_LOG(LS_INFO) << "Time elapsed: " << time_elapsed;
+ EXPECT_GE(time_elapsed, ping_interval_ms);
+
+ DestroyChannels();
+}
+
+TEST_P(P2PTransportChannelMultihomedTest, TestGetState) {
+ rtc::ScopedFakeClock clock;
+ AddAddress(0, kAlternateAddrs[0]);
+ AddAddress(0, kPublicAddrs[0]);
+ AddAddress(1, kPublicAddrs[1]);
+ // Create channels and let them go writable, as usual.
+ CreateChannels();
+
+ // Both transport channels will reach STATE_COMPLETED quickly.
+ EXPECT_EQ_SIMULATED_WAIT(IceTransportState::STATE_COMPLETED,
+ ep1_ch1()->GetState(), kShortTimeout, clock);
+ EXPECT_EQ_SIMULATED_WAIT(IceTransportState::STATE_COMPLETED,
+ ep2_ch1()->GetState(), kShortTimeout, clock);
+ DestroyChannels();
+}
+
+// Tests that when a network interface becomes inactive, if Continual Gathering
+// policy is GATHER_CONTINUALLY, the ports associated with that network
+// will be removed from the port list of the channel, and the respective
+// remote candidates on the other participant will be removed eventually.
+TEST_P(P2PTransportChannelMultihomedTest, TestNetworkBecomesInactive) {
+ rtc::ScopedFakeClock clock;
+ AddAddress(0, kPublicAddrs[0]);
+ AddAddress(1, kPublicAddrs[1]);
+ // Create channels and let them go writable, as usual.
+ IceConfig ep1_config = CreateIceConfig(2000, GATHER_CONTINUALLY);
+ IceConfig ep2_config = CreateIceConfig(2000, GATHER_ONCE);
+ CreateChannels(ep1_config, ep2_config);
+
+ SetAllocatorFlags(0, kOnlyLocalPorts);
+ SetAllocatorFlags(1, kOnlyLocalPorts);
+ ASSERT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()),
+ kDefaultTimeout, clock);
+ // More than one port has been created.
+ EXPECT_LE(1U, ep1_ch1()->ports().size());
+ // Endpoint 1 enabled continual gathering; the port will be removed
+ // when the interface is removed.
+ RemoveAddress(0, kPublicAddrs[0]);
+ EXPECT_TRUE(ep1_ch1()->ports().empty());
+ // The remote candidates will be removed eventually.
+ EXPECT_TRUE_SIMULATED_WAIT(ep2_ch1()->remote_candidates().empty(), 1000,
+ clock);
+
+ size_t num_ports = ep2_ch1()->ports().size();
+ EXPECT_LE(1U, num_ports);
+ size_t num_remote_candidates = ep1_ch1()->remote_candidates().size();
+ // Endpoint 2 did not enable continual gathering; the local port will still be
+ // removed when the interface is removed but the remote candidates on the
+ // other participant will not be removed.
+ RemoveAddress(1, kPublicAddrs[1]);
+
+ EXPECT_EQ_SIMULATED_WAIT(0U, ep2_ch1()->ports().size(), kDefaultTimeout,
+ clock);
+ SIMULATED_WAIT(0U == ep1_ch1()->remote_candidates().size(), 500, clock);
+ EXPECT_EQ(num_remote_candidates, ep1_ch1()->remote_candidates().size());
+
+ DestroyChannels();
+}
+
+// Tests that continual gathering will create new connections when a new
+// interface is added.
+TEST_P(P2PTransportChannelMultihomedTest,
+ TestContinualGatheringOnNewInterface) {
+ auto& wifi = kAlternateAddrs;
+ auto& cellular = kPublicAddrs;
+ AddAddress(0, wifi[0], "test_wifi0", rtc::ADAPTER_TYPE_WIFI);
+ AddAddress(1, cellular[1], "test_cell1", rtc::ADAPTER_TYPE_CELLULAR);
+ // Set continual gathering policy.
+ IceConfig continual_gathering_config =
+ CreateIceConfig(1000, GATHER_CONTINUALLY);
+ CreateChannels(continual_gathering_config, continual_gathering_config);
+ SetAllocatorFlags(0, kOnlyLocalPorts);
+ SetAllocatorFlags(1, kOnlyLocalPorts);
+ EXPECT_TRUE_WAIT_MARGIN(CheckConnected(ep1_ch1(), ep2_ch1()), kDefaultTimeout,
+ kDefaultTimeout);
+
+ // Add a new wifi interface on end point 2. We should expect a new connection
+ // to be created and the new one will be the best connection.
+ AddAddress(1, wifi[1], "test_wifi1", rtc::ADAPTER_TYPE_WIFI);
+ const Connection* conn;
+ EXPECT_TRUE_WAIT((conn = ep1_ch1()->selected_connection()) != nullptr &&
+ HasRemoteAddress(conn, wifi[1]),
+ kDefaultTimeout);
+ EXPECT_TRUE_WAIT((conn = ep2_ch1()->selected_connection()) != nullptr &&
+ HasLocalAddress(conn, wifi[1]),
+ kDefaultTimeout);
+
+ // Add a new cellular interface on end point 1, we should expect a new
+ // backup connection created using this new interface.
+ AddAddress(0, cellular[0], "test_cellular0", rtc::ADAPTER_TYPE_CELLULAR);
+ EXPECT_TRUE_WAIT(
+ ep1_ch1()->GetState() == IceTransportState::STATE_COMPLETED &&
+ absl::c_any_of(ep1_ch1()->connections(),
+ [channel = ep1_ch1(),
+ address = cellular[0]](const Connection* conn) {
+ return HasLocalAddress(conn, address) &&
+ conn != channel->selected_connection() &&
+ conn->writable();
+ }),
+ kDefaultTimeout);
+ EXPECT_TRUE_WAIT(
+ ep2_ch1()->GetState() == IceTransportState::STATE_COMPLETED &&
+ absl::c_any_of(ep2_ch1()->connections(),
+ [channel = ep2_ch1(),
+ address = cellular[0]](const Connection* conn) {
+ return HasRemoteAddress(conn, address) &&
+ conn != channel->selected_connection() &&
+ conn->receiving();
+ }),
+ kDefaultTimeout);
+
+ DestroyChannels();
+}
+
+// Tests that we can switch links via continual gathering.
+TEST_P(P2PTransportChannelMultihomedTest,
+ TestSwitchLinksViaContinualGathering) {
+ rtc::ScopedFakeClock clock;
+ AddAddress(0, kPublicAddrs[0]);
+ AddAddress(1, kPublicAddrs[1]);
+ // Use only local ports for simplicity.
+ SetAllocatorFlags(0, kOnlyLocalPorts);
+ SetAllocatorFlags(1, kOnlyLocalPorts);
+
+ // Set continual gathering policy.
+ IceConfig continual_gathering_config =
+ CreateIceConfig(1000, GATHER_CONTINUALLY);
+ // Create channels and let them go writable, as usual.
+ CreateChannels(continual_gathering_config, continual_gathering_config);
+ EXPECT_TRUE_SIMULATED_WAIT(
+ CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), kPublicAddrs[0],
+ kPublicAddrs[1]),
+ kMediumTimeout, clock);
+
+ // Add the new address first and then remove the other one.
+ RTC_LOG(LS_INFO) << "Draining...";
+ AddAddress(1, kAlternateAddrs[1]);
+ RemoveAddress(1, kPublicAddrs[1]);
+ // We should switch to use the alternate address after an exchange of pings.
+ EXPECT_TRUE_SIMULATED_WAIT(
+ CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), kPublicAddrs[0],
+ kAlternateAddrs[1]),
+ kMediumTimeout, clock);
+
+ // Remove one address first and then add another address.
+ RTC_LOG(LS_INFO) << "Draining again...";
+ RemoveAddress(1, kAlternateAddrs[1]);
+ AddAddress(1, kAlternateAddrs[0]);
+ EXPECT_TRUE_SIMULATED_WAIT(
+ CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), kPublicAddrs[0],
+ kAlternateAddrs[0]),
+ kMediumTimeout, clock);
+
+ DestroyChannels();
+}
+
+// Tests that the backup connection will be restored after it is destroyed.
+TEST_P(P2PTransportChannelMultihomedTest, TestRestoreBackupConnection) {
+ rtc::ScopedFakeClock clock;
+ auto& wifi = kAlternateAddrs;
+ auto& cellular = kPublicAddrs;
+ AddAddress(0, wifi[0], "test_wifi0", rtc::ADAPTER_TYPE_WIFI);
+ AddAddress(0, cellular[0], "test_cell0", rtc::ADAPTER_TYPE_CELLULAR);
+ AddAddress(1, wifi[1], "test_wifi1", rtc::ADAPTER_TYPE_WIFI);
+ AddAddress(1, cellular[1], "test_cell1", rtc::ADAPTER_TYPE_CELLULAR);
+ // Use only local ports for simplicity.
+ SetAllocatorFlags(0, kOnlyLocalPorts);
+ SetAllocatorFlags(1, kOnlyLocalPorts);
+
+ // Create channels and let them go writable, as usual.
+ IceConfig config = CreateIceConfig(1000, GATHER_CONTINUALLY);
+ config.regather_on_failed_networks_interval = 2000;
+ CreateChannels(config, config);
+ EXPECT_TRUE_SIMULATED_WAIT(
+ CheckCandidatePairAndConnected(ep1_ch1(), ep2_ch1(), wifi[0], wifi[1]),
+ kMediumTimeout, clock);
+
+ // Destroy all backup connections.
+ DestroyAllButBestConnection(ep1_ch1());
+ // Ensure the backup connection is removed first.
+ EXPECT_TRUE_SIMULATED_WAIT(
+ GetConnectionWithLocalAddress(ep1_ch1(), cellular[0]) == nullptr,
+ kDefaultTimeout, clock);
+ const Connection* conn;
+ EXPECT_TRUE_SIMULATED_WAIT(
+ (conn = GetConnectionWithLocalAddress(ep1_ch1(), cellular[0])) !=
+ nullptr &&
+ conn != ep1_ch1()->selected_connection() && conn->writable(),
+ kDefaultTimeout, clock);
+
+ DestroyChannels();
+}
+
+TEST_P(P2PTransportChannelMultihomedTest, TestVpnDefault) {
+ rtc::ScopedFakeClock clock;
+ AddAddress(0, kPublicAddrs[0], "eth0", rtc::ADAPTER_TYPE_ETHERNET);
+ AddAddress(0, kAlternateAddrs[0], "vpn0", rtc::ADAPTER_TYPE_VPN);
+ AddAddress(1, kPublicAddrs[1]);
+
+ IceConfig config;
+ CreateChannels(config, config, false);
+ EXPECT_TRUE_SIMULATED_WAIT(
+ CheckConnected(ep1_ch1(), ep2_ch1()) &&
+ !ep1_ch1()->selected_connection()->network()->IsVpn(),
+ kDefaultTimeout, clock);
+}
+
+TEST_P(P2PTransportChannelMultihomedTest, TestVpnPreferVpn) {
+ rtc::ScopedFakeClock clock;
+ AddAddress(0, kPublicAddrs[0], "eth0", rtc::ADAPTER_TYPE_ETHERNET);
+ AddAddress(0, kAlternateAddrs[0], "vpn0", rtc::ADAPTER_TYPE_VPN,
+ rtc::ADAPTER_TYPE_CELLULAR);
+ AddAddress(1, kPublicAddrs[1]);
+
+ IceConfig config;
+ config.vpn_preference = webrtc::VpnPreference::kPreferVpn;
+ RTC_LOG(LS_INFO) << "KESO: config.vpn_preference: " << config.vpn_preference;
+ CreateChannels(config, config, false);
+ EXPECT_TRUE_SIMULATED_WAIT(
+ CheckConnected(ep1_ch1(), ep2_ch1()) &&
+ ep1_ch1()->selected_connection()->network()->IsVpn(),
+ kDefaultTimeout, clock);
+
+ // Block VPN.
+ fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kAlternateAddrs[0]);
+
+ // Check that it switches to non-VPN
+ EXPECT_TRUE_SIMULATED_WAIT(
+ CheckConnected(ep1_ch1(), ep2_ch1()) &&
+ !ep1_ch1()->selected_connection()->network()->IsVpn(),
+ kDefaultTimeout, clock);
+}
+
+TEST_P(P2PTransportChannelMultihomedTest, TestVpnAvoidVpn) {
+ rtc::ScopedFakeClock clock;
+ AddAddress(0, kPublicAddrs[0], "eth0", rtc::ADAPTER_TYPE_CELLULAR);
+ AddAddress(0, kAlternateAddrs[0], "vpn0", rtc::ADAPTER_TYPE_VPN,
+ rtc::ADAPTER_TYPE_ETHERNET);
+ AddAddress(1, kPublicAddrs[1]);
+
+ IceConfig config;
+ config.vpn_preference = webrtc::VpnPreference::kAvoidVpn;
+ CreateChannels(config, config, false);
+ EXPECT_TRUE_SIMULATED_WAIT(
+ CheckConnected(ep1_ch1(), ep2_ch1()) &&
+ !ep1_ch1()->selected_connection()->network()->IsVpn(),
+ kDefaultTimeout, clock);
+
+ // Block non-VPN.
+ fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[0]);
+
+ // Check that it switches to VPN
+ EXPECT_TRUE_SIMULATED_WAIT(
+ CheckConnected(ep1_ch1(), ep2_ch1()) &&
+ ep1_ch1()->selected_connection()->network()->IsVpn(),
+ kDefaultTimeout, clock);
+}
+
+TEST_P(P2PTransportChannelMultihomedTest, TestVpnNeverVpn) {
+ rtc::ScopedFakeClock clock;
+ AddAddress(0, kPublicAddrs[0], "eth0", rtc::ADAPTER_TYPE_CELLULAR);
+ AddAddress(0, kAlternateAddrs[0], "vpn0", rtc::ADAPTER_TYPE_VPN,
+ rtc::ADAPTER_TYPE_ETHERNET);
+ AddAddress(1, kPublicAddrs[1]);
+
+ IceConfig config;
+ config.vpn_preference = webrtc::VpnPreference::kNeverUseVpn;
+ CreateChannels(config, config, false);
+ EXPECT_TRUE_SIMULATED_WAIT(
+ CheckConnected(ep1_ch1(), ep2_ch1()) &&
+ !ep1_ch1()->selected_connection()->network()->IsVpn(),
+ kDefaultTimeout, clock);
+
+ // Block non-VPN.
+ fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[0]);
+
+ // Check that it does not switches to VPN
+ clock.AdvanceTime(webrtc::TimeDelta::Millis(kDefaultTimeout));
+ EXPECT_TRUE_SIMULATED_WAIT(!CheckConnected(ep1_ch1(), ep2_ch1()),
+ kDefaultTimeout, clock);
+}
+
+TEST_P(P2PTransportChannelMultihomedTest, TestVpnOnlyVpn) {
+ rtc::ScopedFakeClock clock;
+ AddAddress(0, kPublicAddrs[0], "eth0", rtc::ADAPTER_TYPE_CELLULAR);
+ AddAddress(0, kAlternateAddrs[0], "vpn0", rtc::ADAPTER_TYPE_VPN,
+ rtc::ADAPTER_TYPE_ETHERNET);
+ AddAddress(1, kPublicAddrs[1]);
+
+ IceConfig config;
+ config.vpn_preference = webrtc::VpnPreference::kOnlyUseVpn;
+ CreateChannels(config, config, false);
+ EXPECT_TRUE_SIMULATED_WAIT(
+ CheckConnected(ep1_ch1(), ep2_ch1()) &&
+ ep1_ch1()->selected_connection()->network()->IsVpn(),
+ kDefaultTimeout, clock);
+
+ // Block VPN.
+ fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kAlternateAddrs[0]);
+
+ // Check that it does not switch to non-VPN
+ clock.AdvanceTime(webrtc::TimeDelta::Millis(kDefaultTimeout));
+ EXPECT_TRUE_SIMULATED_WAIT(!CheckConnected(ep1_ch1(), ep2_ch1()),
+ kDefaultTimeout, clock);
+}
+
+// A collection of tests which tests a single P2PTransportChannel by sending
+// pings.
+class P2PTransportChannelPingTest : public TestWithParam<std::string>,
+ public sigslot::has_slots<> {
+ public:
+ P2PTransportChannelPingTest()
+ : field_trials_(GetParam()),
+ vss_(std::make_unique<rtc::VirtualSocketServer>()),
+ packet_socket_factory_(
+ std::make_unique<rtc::BasicPacketSocketFactory>(vss_.get())),
+ thread_(vss_.get()) {}
+
+ protected:
+ void PrepareChannel(P2PTransportChannel* ch) {
+ ch->SetIceRole(ICEROLE_CONTROLLING);
+ ch->SetIceTiebreaker(kTiebreakerDefault);
+ ch->SetIceParameters(kIceParams[0]);
+ ch->SetRemoteIceParameters(kIceParams[1]);
+ ch->SignalNetworkRouteChanged.connect(
+ this, &P2PTransportChannelPingTest::OnNetworkRouteChanged);
+ ch->SignalReadyToSend.connect(this,
+ &P2PTransportChannelPingTest::OnReadyToSend);
+ ch->SignalStateChanged.connect(
+ this, &P2PTransportChannelPingTest::OnChannelStateChanged);
+ ch->SignalCandidatePairChanged.connect(
+ this, &P2PTransportChannelPingTest::OnCandidatePairChanged);
+ }
+
+ Connection* WaitForConnectionTo(
+ P2PTransportChannel* ch,
+ absl::string_view ip,
+ int port_num,
+ rtc::ThreadProcessingFakeClock* clock = nullptr) {
+ if (clock == nullptr) {
+ EXPECT_TRUE_WAIT(GetConnectionTo(ch, ip, port_num) != nullptr,
+ kMediumTimeout);
+ } else {
+ EXPECT_TRUE_SIMULATED_WAIT(GetConnectionTo(ch, ip, port_num) != nullptr,
+ kMediumTimeout, *clock);
+ }
+ return GetConnectionTo(ch, ip, port_num);
+ }
+
+ Port* GetPort(P2PTransportChannel* ch) {
+ if (ch->ports().empty()) {
+ return nullptr;
+ }
+ return static_cast<Port*>(ch->ports()[0]);
+ }
+
+ Port* GetPrunedPort(P2PTransportChannel* ch) {
+ if (ch->pruned_ports().empty()) {
+ return nullptr;
+ }
+ return static_cast<Port*>(ch->pruned_ports()[0]);
+ }
+
+ Connection* GetConnectionTo(P2PTransportChannel* ch,
+ absl::string_view ip,
+ int port_num) {
+ Port* port = GetPort(ch);
+ if (!port) {
+ return nullptr;
+ }
+ return port->GetConnection(rtc::SocketAddress(ip, port_num));
+ }
+
+ Connection* FindNextPingableConnectionAndPingIt(P2PTransportChannel* ch) {
+ Connection* conn = ch->FindNextPingableConnection();
+ if (conn) {
+ ch->MarkConnectionPinged(conn);
+ }
+ return conn;
+ }
+
+ int SendData(IceTransportInternal* channel,
+ const char* data,
+ size_t len,
+ int packet_id) {
+ rtc::PacketOptions options;
+ options.packet_id = packet_id;
+ return channel->SendPacket(data, len, options, 0);
+ }
+
+ Connection* CreateConnectionWithCandidate(P2PTransportChannel* channel,
+ rtc::ScopedFakeClock* clock,
+ absl::string_view ip_addr,
+ int port,
+ int priority,
+ bool writable) {
+ channel->AddRemoteCandidate(
+ CreateUdpCandidate(LOCAL_PORT_TYPE, ip_addr, port, priority));
+ EXPECT_TRUE_SIMULATED_WAIT(
+ GetConnectionTo(channel, ip_addr, port) != nullptr, kMediumTimeout,
+ *clock);
+ Connection* conn = GetConnectionTo(channel, ip_addr, port);
+
+ if (conn && writable) {
+ conn->ReceivedPingResponse(LOW_RTT, "id"); // make it writable
+ }
+ return conn;
+ }
+
+ void NominateConnection(Connection* conn, uint32_t remote_nomination = 1U) {
+ conn->set_remote_nomination(remote_nomination);
+ conn->SignalNominated(conn);
+ }
+
+ void OnNetworkRouteChanged(absl::optional<rtc::NetworkRoute> network_route) {
+ last_network_route_ = network_route;
+ if (last_network_route_) {
+ last_sent_packet_id_ = last_network_route_->last_sent_packet_id;
+ }
+ ++selected_candidate_pair_switches_;
+ }
+
+ void ReceivePingOnConnection(
+ Connection* conn,
+ absl::string_view remote_ufrag,
+ int priority,
+ uint32_t nomination,
+ const absl::optional<std::string>& piggyback_ping_id) {
+ IceMessage msg(STUN_BINDING_REQUEST);
+ msg.AddAttribute(std::make_unique<StunByteStringAttribute>(
+ STUN_ATTR_USERNAME,
+ conn->local_candidate().username() + ":" + std::string(remote_ufrag)));
+ msg.AddAttribute(
+ std::make_unique<StunUInt32Attribute>(STUN_ATTR_PRIORITY, priority));
+ if (nomination != 0) {
+ msg.AddAttribute(std::make_unique<StunUInt32Attribute>(
+ STUN_ATTR_NOMINATION, nomination));
+ }
+ if (piggyback_ping_id) {
+ msg.AddAttribute(std::make_unique<StunByteStringAttribute>(
+ STUN_ATTR_GOOG_LAST_ICE_CHECK_RECEIVED, piggyback_ping_id.value()));
+ }
+ msg.AddMessageIntegrity(conn->local_candidate().password());
+ msg.AddFingerprint();
+ rtc::ByteBufferWriter buf;
+ msg.Write(&buf);
+ conn->OnReadPacket(buf.Data(), buf.Length(), rtc::TimeMicros());
+ }
+
+ void ReceivePingOnConnection(Connection* conn,
+ absl::string_view remote_ufrag,
+ int priority,
+ uint32_t nomination = 0) {
+ ReceivePingOnConnection(conn, remote_ufrag, priority, nomination,
+ absl::nullopt);
+ }
+
+ void OnReadyToSend(rtc::PacketTransportInternal* transport) {
+ channel_ready_to_send_ = true;
+ }
+ void OnChannelStateChanged(IceTransportInternal* channel) {
+ channel_state_ = channel->GetState();
+ }
+ void OnCandidatePairChanged(const CandidatePairChangeEvent& event) {
+ last_candidate_change_event_ = event;
+ }
+
+ int last_sent_packet_id() { return last_sent_packet_id_; }
+ bool channel_ready_to_send() { return channel_ready_to_send_; }
+ void reset_channel_ready_to_send() { channel_ready_to_send_ = false; }
+ IceTransportState channel_state() { return channel_state_; }
+ int reset_selected_candidate_pair_switches() {
+ int switches = selected_candidate_pair_switches_;
+ selected_candidate_pair_switches_ = 0;
+ return switches;
+ }
+
+ // Return true if the `pair` matches the last network route.
+ bool CandidatePairMatchesNetworkRoute(CandidatePairInterface* pair) {
+ if (!pair) {
+ return !last_network_route_.has_value();
+ } else {
+ return pair->local_candidate().network_id() ==
+ last_network_route_->local.network_id() &&
+ pair->remote_candidate().network_id() ==
+ last_network_route_->remote.network_id();
+ }
+ }
+
+ bool ConnectionMatchesChangeEvent(Connection* conn,
+ absl::string_view reason) {
+ if (!conn) {
+ return !last_candidate_change_event_.has_value();
+ } else {
+ const auto& last_selected_pair =
+ last_candidate_change_event_->selected_candidate_pair;
+ return last_selected_pair.local_candidate().IsEquivalent(
+ conn->local_candidate()) &&
+ last_selected_pair.remote_candidate().IsEquivalent(
+ conn->remote_candidate()) &&
+ last_candidate_change_event_->last_data_received_ms ==
+ conn->last_data_received() &&
+ last_candidate_change_event_->reason == reason;
+ }
+ }
+
+ int64_t LastEstimatedDisconnectedTimeMs() const {
+ if (!last_candidate_change_event_.has_value()) {
+ return 0;
+ } else {
+ return last_candidate_change_event_->estimated_disconnected_time_ms;
+ }
+ }
+
+ rtc::SocketServer* ss() const { return vss_.get(); }
+
+ rtc::PacketSocketFactory* packet_socket_factory() const {
+ return packet_socket_factory_.get();
+ }
+
+ webrtc::test::ScopedKeyValueConfig field_trials_;
+
+ private:
+ std::unique_ptr<rtc::VirtualSocketServer> vss_;
+ std::unique_ptr<rtc::PacketSocketFactory> packet_socket_factory_;
+ rtc::AutoSocketServerThread thread_;
+ int selected_candidate_pair_switches_ = 0;
+ int last_sent_packet_id_ = -1;
+ bool channel_ready_to_send_ = false;
+ absl::optional<CandidatePairChangeEvent> last_candidate_change_event_;
+ IceTransportState channel_state_ = IceTransportState::STATE_INIT;
+ absl::optional<rtc::NetworkRoute> last_network_route_;
+};
+
+INSTANTIATE_TEST_SUITE_P(Legacy, P2PTransportChannelPingTest, Values(""));
+INSTANTIATE_TEST_SUITE_P(Active,
+ P2PTransportChannelPingTest,
+ Values("WebRTC-UseActiveIceController/Enabled/"));
+
+TEST_P(P2PTransportChannelPingTest, TestTriggeredChecks) {
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("trigger checks", 1, &pa, &field_trials_);
+ PrepareChannel(&ch);
+ ch.MaybeStartGathering();
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1));
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 2));
+
+ Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1);
+ Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2);
+ ASSERT_TRUE(conn1 != nullptr);
+ ASSERT_TRUE(conn2 != nullptr);
+
+ // Before a triggered check, the first connection to ping is the
+ // highest priority one.
+ EXPECT_EQ(conn2, FindNextPingableConnectionAndPingIt(&ch));
+
+ // Receiving a ping causes a triggered check which should make conn1
+ // be pinged first instead of conn2, even though conn2 has a higher
+ // priority.
+ conn1->ReceivedPing();
+ EXPECT_EQ(conn1, FindNextPingableConnectionAndPingIt(&ch));
+}
+
+TEST_P(P2PTransportChannelPingTest, TestAllConnectionsPingedSufficiently) {
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("ping sufficiently", 1, &pa, &field_trials_);
+ PrepareChannel(&ch);
+ ch.MaybeStartGathering();
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1));
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 2));
+
+ Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1);
+ Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2);
+ ASSERT_TRUE(conn1 != nullptr);
+ ASSERT_TRUE(conn2 != nullptr);
+
+ // Low-priority connection becomes writable so that the other connection
+ // is not pruned.
+ conn1->ReceivedPingResponse(LOW_RTT, "id");
+ EXPECT_TRUE_WAIT(
+ conn1->num_pings_sent() >= MIN_PINGS_AT_WEAK_PING_INTERVAL &&
+ conn2->num_pings_sent() >= MIN_PINGS_AT_WEAK_PING_INTERVAL,
+ kDefaultTimeout);
+}
+
+// Verify that the connections are pinged at the right time.
+TEST_P(P2PTransportChannelPingTest, TestStunPingIntervals) {
+ rtc::ScopedFakeClock clock;
+ int RTT_RATIO = 4;
+ int SCHEDULING_RANGE = 200;
+ int RTT_RANGE = 10;
+
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("TestChannel", 1, &pa, &field_trials_);
+ PrepareChannel(&ch);
+ ch.MaybeStartGathering();
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1));
+ Connection* conn = WaitForConnectionTo(&ch, "1.1.1.1", 1);
+
+ ASSERT_TRUE(conn != nullptr);
+ SIMULATED_WAIT(conn->num_pings_sent() == 1, kDefaultTimeout, clock);
+
+ // Initializing.
+
+ int64_t start = clock.TimeNanos();
+ SIMULATED_WAIT(conn->num_pings_sent() >= MIN_PINGS_AT_WEAK_PING_INTERVAL,
+ kDefaultTimeout, clock);
+ int64_t ping_interval_ms = (clock.TimeNanos() - start) /
+ rtc::kNumNanosecsPerMillisec /
+ (MIN_PINGS_AT_WEAK_PING_INTERVAL - 1);
+ EXPECT_EQ(ping_interval_ms, WEAK_PING_INTERVAL);
+
+ // Stabilizing.
+
+ conn->ReceivedPingResponse(LOW_RTT, "id");
+ int ping_sent_before = conn->num_pings_sent();
+ start = clock.TimeNanos();
+ // The connection becomes strong but not stable because we haven't been able
+ // to converge the RTT.
+ SIMULATED_WAIT(conn->num_pings_sent() == ping_sent_before + 1, kMediumTimeout,
+ clock);
+ ping_interval_ms = (clock.TimeNanos() - start) / rtc::kNumNanosecsPerMillisec;
+ EXPECT_GE(ping_interval_ms,
+ WEAK_OR_STABILIZING_WRITABLE_CONNECTION_PING_INTERVAL);
+ EXPECT_LE(
+ ping_interval_ms,
+ WEAK_OR_STABILIZING_WRITABLE_CONNECTION_PING_INTERVAL + SCHEDULING_RANGE);
+
+ // Stabilized.
+
+ // The connection becomes stable after receiving more than RTT_RATIO rtt
+ // samples.
+ for (int i = 0; i < RTT_RATIO; i++) {
+ conn->ReceivedPingResponse(LOW_RTT, "id");
+ }
+ ping_sent_before = conn->num_pings_sent();
+ start = clock.TimeNanos();
+ SIMULATED_WAIT(conn->num_pings_sent() == ping_sent_before + 1, kMediumTimeout,
+ clock);
+ ping_interval_ms = (clock.TimeNanos() - start) / rtc::kNumNanosecsPerMillisec;
+ EXPECT_GE(ping_interval_ms,
+ STRONG_AND_STABLE_WRITABLE_CONNECTION_PING_INTERVAL);
+ EXPECT_LE(
+ ping_interval_ms,
+ STRONG_AND_STABLE_WRITABLE_CONNECTION_PING_INTERVAL + SCHEDULING_RANGE);
+
+ // Destabilized.
+
+ conn->ReceivedPingResponse(LOW_RTT, "id");
+ // Create a in-flight ping.
+ conn->Ping(clock.TimeNanos() / rtc::kNumNanosecsPerMillisec);
+ start = clock.TimeNanos();
+ // In-flight ping timeout and the connection will be unstable.
+ SIMULATED_WAIT(
+ !conn->stable(clock.TimeNanos() / rtc::kNumNanosecsPerMillisec),
+ kMediumTimeout, clock);
+ int64_t duration_ms =
+ (clock.TimeNanos() - start) / rtc::kNumNanosecsPerMillisec;
+ EXPECT_GE(duration_ms, 2 * conn->rtt() - RTT_RANGE);
+ EXPECT_LE(duration_ms, 2 * conn->rtt() + RTT_RANGE);
+ // The connection become unstable due to not receiving ping responses.
+ ping_sent_before = conn->num_pings_sent();
+ SIMULATED_WAIT(conn->num_pings_sent() == ping_sent_before + 1, kMediumTimeout,
+ clock);
+ // The interval is expected to be
+ // WEAK_OR_STABILIZING_WRITABLE_CONNECTION_PING_INTERVAL.
+ start = clock.TimeNanos();
+ ping_sent_before = conn->num_pings_sent();
+ SIMULATED_WAIT(conn->num_pings_sent() == ping_sent_before + 1, kMediumTimeout,
+ clock);
+ ping_interval_ms = (clock.TimeNanos() - start) / rtc::kNumNanosecsPerMillisec;
+ EXPECT_GE(ping_interval_ms,
+ WEAK_OR_STABILIZING_WRITABLE_CONNECTION_PING_INTERVAL);
+ EXPECT_LE(
+ ping_interval_ms,
+ WEAK_OR_STABILIZING_WRITABLE_CONNECTION_PING_INTERVAL + SCHEDULING_RANGE);
+}
+
+// Test that we start pinging as soon as we have a connection and remote ICE
+// parameters.
+TEST_P(P2PTransportChannelPingTest, PingingStartedAsSoonAsPossible) {
+ rtc::ScopedFakeClock clock;
+
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("TestChannel", 1, &pa, &field_trials_);
+ ch.SetIceRole(ICEROLE_CONTROLLING);
+ ch.SetIceTiebreaker(kTiebreakerDefault);
+ ch.SetIceParameters(kIceParams[0]);
+ ch.MaybeStartGathering();
+ EXPECT_EQ_WAIT(IceGatheringState::kIceGatheringComplete, ch.gathering_state(),
+ kDefaultTimeout);
+
+ // Simulate a binding request being received, creating a peer reflexive
+ // candidate pair while we still don't have remote ICE parameters.
+ IceMessage request(STUN_BINDING_REQUEST);
+ request.AddAttribute(std::make_unique<StunByteStringAttribute>(
+ STUN_ATTR_USERNAME, kIceUfrag[1]));
+ uint32_t prflx_priority = ICE_TYPE_PREFERENCE_PRFLX << 24;
+ request.AddAttribute(std::make_unique<StunUInt32Attribute>(STUN_ATTR_PRIORITY,
+ prflx_priority));
+ Port* port = GetPort(&ch);
+ ASSERT_NE(nullptr, port);
+ port->SignalUnknownAddress(port, rtc::SocketAddress("1.1.1.1", 1), PROTO_UDP,
+ &request, kIceUfrag[1], false);
+ Connection* conn = GetConnectionTo(&ch, "1.1.1.1", 1);
+ ASSERT_NE(nullptr, conn);
+
+ // Simulate waiting for a second (and change) and verify that no pings were
+ // sent, since we don't yet have remote ICE parameters.
+ SIMULATED_WAIT(conn->num_pings_sent() > 0, 1025, clock);
+ EXPECT_EQ(0, conn->num_pings_sent());
+
+ // Set remote ICE parameters. Now we should be able to ping. Ensure that
+ // the first ping is sent as soon as possible, within one simulated clock
+ // tick.
+ ch.SetRemoteIceParameters(kIceParams[1]);
+ EXPECT_TRUE_SIMULATED_WAIT(conn->num_pings_sent() > 0, 1, clock);
+}
+
+TEST_P(P2PTransportChannelPingTest, TestNoTriggeredChecksWhenWritable) {
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("trigger checks", 1, &pa, &field_trials_);
+ PrepareChannel(&ch);
+ ch.MaybeStartGathering();
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1));
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 2));
+
+ Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1);
+ Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2);
+ ASSERT_TRUE(conn1 != nullptr);
+ ASSERT_TRUE(conn2 != nullptr);
+
+ EXPECT_EQ(conn2, FindNextPingableConnectionAndPingIt(&ch));
+ EXPECT_EQ(conn1, FindNextPingableConnectionAndPingIt(&ch));
+ conn1->ReceivedPingResponse(LOW_RTT, "id");
+ ASSERT_TRUE(conn1->writable());
+ conn1->ReceivedPing();
+
+ // Ping received, but the connection is already writable, so no
+ // "triggered check" and conn2 is pinged before conn1 because it has
+ // a higher priority.
+ EXPECT_EQ(conn2, FindNextPingableConnectionAndPingIt(&ch));
+}
+
+TEST_P(P2PTransportChannelPingTest, TestFailedConnectionNotPingable) {
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("Do not ping failed connections", 1, &pa,
+ &field_trials_);
+ PrepareChannel(&ch);
+ ch.MaybeStartGathering();
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1));
+
+ Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1);
+ ASSERT_TRUE(conn1 != nullptr);
+
+ EXPECT_EQ(conn1, ch.FindNextPingableConnection());
+ conn1->Prune(); // A pruned connection may still be pingable.
+ EXPECT_EQ(conn1, ch.FindNextPingableConnection());
+ conn1->FailAndPrune();
+ EXPECT_TRUE(nullptr == ch.FindNextPingableConnection());
+}
+
+TEST_P(P2PTransportChannelPingTest, TestSignalStateChanged) {
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("state change", 1, &pa, &field_trials_);
+ PrepareChannel(&ch);
+ ch.MaybeStartGathering();
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1));
+ Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1);
+ ASSERT_TRUE(conn1 != nullptr);
+ // Pruning the connection reduces the set of active connections and changes
+ // the channel state.
+ conn1->Prune();
+ EXPECT_EQ_WAIT(IceTransportState::STATE_FAILED, channel_state(),
+ kDefaultTimeout);
+}
+
+// Test adding remote candidates with different ufrags. If a remote candidate
+// is added with an old ufrag, it will be discarded. If it is added with a
+// ufrag that was not seen before, it will be used to create connections
+// although the ICE pwd in the remote candidate will be set when the ICE
+// parameters arrive. If a remote candidate is added with the current ICE
+// ufrag, its pwd and generation will be set properly.
+TEST_P(P2PTransportChannelPingTest, TestAddRemoteCandidateWithVariousUfrags) {
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("add candidate", 1, &pa, &field_trials_);
+ PrepareChannel(&ch);
+ ch.MaybeStartGathering();
+ // Add a candidate with a future ufrag.
+ ch.AddRemoteCandidate(
+ CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1, kIceUfrag[2]));
+ Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1);
+ ASSERT_TRUE(conn1 != nullptr);
+ const Candidate& candidate = conn1->remote_candidate();
+ EXPECT_EQ(kIceUfrag[2], candidate.username());
+ EXPECT_TRUE(candidate.password().empty());
+ EXPECT_TRUE(FindNextPingableConnectionAndPingIt(&ch) == nullptr);
+
+ // Set the remote ICE parameters with the "future" ufrag.
+ // This should set the ICE pwd in the remote candidate of `conn1`, making
+ // it pingable.
+ ch.SetRemoteIceParameters(kIceParams[2]);
+ EXPECT_EQ(kIceUfrag[2], candidate.username());
+ EXPECT_EQ(kIcePwd[2], candidate.password());
+ EXPECT_EQ(conn1, FindNextPingableConnectionAndPingIt(&ch));
+
+ // Add a candidate with an old ufrag. No connection will be created.
+ ch.AddRemoteCandidate(
+ CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 2, kIceUfrag[1]));
+ rtc::Thread::Current()->ProcessMessages(500);
+ EXPECT_TRUE(GetConnectionTo(&ch, "2.2.2.2", 2) == nullptr);
+
+ // Add a candidate with the current ufrag, its pwd and generation will be
+ // assigned, even if the generation is not set.
+ ch.AddRemoteCandidate(
+ CreateUdpCandidate(LOCAL_PORT_TYPE, "3.3.3.3", 3, 0, kIceUfrag[2]));
+ Connection* conn3 = nullptr;
+ ASSERT_TRUE_WAIT((conn3 = GetConnectionTo(&ch, "3.3.3.3", 3)) != nullptr,
+ kMediumTimeout);
+ const Candidate& new_candidate = conn3->remote_candidate();
+ EXPECT_EQ(kIcePwd[2], new_candidate.password());
+ EXPECT_EQ(1U, new_candidate.generation());
+
+ // Check that the pwd of all remote candidates are properly assigned.
+ for (const RemoteCandidate& candidate : ch.remote_candidates()) {
+ EXPECT_TRUE(candidate.username() == kIceUfrag[1] ||
+ candidate.username() == kIceUfrag[2]);
+ if (candidate.username() == kIceUfrag[1]) {
+ EXPECT_EQ(kIcePwd[1], candidate.password());
+ } else if (candidate.username() == kIceUfrag[2]) {
+ EXPECT_EQ(kIcePwd[2], candidate.password());
+ }
+ }
+}
+
+TEST_P(P2PTransportChannelPingTest, ConnectionResurrection) {
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("connection resurrection", 1, &pa, &field_trials_);
+ PrepareChannel(&ch);
+ ch.MaybeStartGathering();
+
+ // Create conn1 and keep track of original candidate priority.
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1));
+ Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1);
+ ASSERT_TRUE(conn1 != nullptr);
+ uint32_t remote_priority = conn1->remote_candidate().priority();
+
+ // Create a higher priority candidate and make the connection
+ // receiving/writable. This will prune conn1.
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 2));
+ Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2);
+ ASSERT_TRUE(conn2 != nullptr);
+ conn2->ReceivedPing();
+ conn2->ReceivedPingResponse(LOW_RTT, "id");
+
+ // Wait for conn2 to be selected.
+ EXPECT_EQ_WAIT(conn2, ch.selected_connection(), kMediumTimeout);
+ // Destroy the connection to test SignalUnknownAddress.
+ ch.RemoveConnectionForTest(conn1);
+ EXPECT_TRUE_WAIT(GetConnectionTo(&ch, "1.1.1.1", 1) == nullptr,
+ kMediumTimeout);
+
+ // Create a minimal STUN message with prflx priority.
+ IceMessage request(STUN_BINDING_REQUEST);
+ request.AddAttribute(std::make_unique<StunByteStringAttribute>(
+ STUN_ATTR_USERNAME, kIceUfrag[1]));
+ uint32_t prflx_priority = ICE_TYPE_PREFERENCE_PRFLX << 24;
+ request.AddAttribute(std::make_unique<StunUInt32Attribute>(STUN_ATTR_PRIORITY,
+ prflx_priority));
+ EXPECT_NE(prflx_priority, remote_priority);
+
+ Port* port = GetPort(&ch);
+ // conn1 should be resurrected with original priority.
+ port->SignalUnknownAddress(port, rtc::SocketAddress("1.1.1.1", 1), PROTO_UDP,
+ &request, kIceUfrag[1], false);
+ conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1);
+ ASSERT_TRUE(conn1 != nullptr);
+ EXPECT_EQ(conn1->remote_candidate().priority(), remote_priority);
+
+ // conn3, a real prflx connection, should have prflx priority.
+ port->SignalUnknownAddress(port, rtc::SocketAddress("3.3.3.3", 1), PROTO_UDP,
+ &request, kIceUfrag[1], false);
+ Connection* conn3 = WaitForConnectionTo(&ch, "3.3.3.3", 1);
+ ASSERT_TRUE(conn3 != nullptr);
+ EXPECT_EQ(conn3->remote_candidate().priority(), prflx_priority);
+}
+
+TEST_P(P2PTransportChannelPingTest, TestReceivingStateChange) {
+ rtc::ScopedFakeClock clock;
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("receiving state change", 1, &pa, &field_trials_);
+ PrepareChannel(&ch);
+ // Default receiving timeout and checking receiving interval should not be too
+ // small.
+ EXPECT_LE(1000, ch.config().receiving_timeout_or_default());
+ EXPECT_LE(200, ch.check_receiving_interval());
+ ch.SetIceConfig(CreateIceConfig(500, GATHER_ONCE));
+ EXPECT_EQ(500, ch.config().receiving_timeout_or_default());
+ EXPECT_EQ(50, ch.check_receiving_interval());
+ ch.MaybeStartGathering();
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1));
+ Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1, &clock);
+ ASSERT_TRUE(conn1 != nullptr);
+
+ clock.AdvanceTime(webrtc::TimeDelta::Seconds(1));
+ conn1->ReceivedPing();
+ conn1->OnReadPacket("ABC", 3, rtc::TimeMicros());
+ EXPECT_TRUE_SIMULATED_WAIT(ch.receiving(), kShortTimeout, clock);
+ EXPECT_TRUE_SIMULATED_WAIT(!ch.receiving(), kShortTimeout, clock);
+}
+
+// The controlled side will select a connection as the "selected connection"
+// based on priority until the controlling side nominates a connection, at which
+// point the controlled side will select that connection as the
+// "selected connection". Plus, SignalNetworkRouteChanged will be fired if the
+// selected connection changes and SignalReadyToSend will be fired if the new
+// selected connection is writable.
+TEST_P(P2PTransportChannelPingTest, TestSelectConnectionBeforeNomination) {
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("receiving state change", 1, &pa, &field_trials_);
+ PrepareChannel(&ch);
+ ch.SetIceRole(ICEROLE_CONTROLLED);
+ ch.MaybeStartGathering();
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1));
+ Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1);
+ ASSERT_TRUE(conn1 != nullptr);
+ // Channel is not ready to send because it is not writable.
+ EXPECT_FALSE(channel_ready_to_send());
+ int last_packet_id = 0;
+ const char* data = "ABCDEFGH";
+ int len = static_cast<int>(strlen(data));
+ EXPECT_EQ(-1, SendData(&ch, data, len, ++last_packet_id));
+ EXPECT_EQ(-1, last_sent_packet_id());
+
+ // A connection needs to be writable before it is selected for transmission.
+ conn1->ReceivedPingResponse(LOW_RTT, "id");
+ EXPECT_EQ_WAIT(conn1, ch.selected_connection(), kDefaultTimeout);
+ EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn1));
+ EXPECT_TRUE(ConnectionMatchesChangeEvent(
+ conn1, "remote candidate generation maybe changed"));
+ EXPECT_EQ(len, SendData(&ch, data, len, ++last_packet_id));
+
+ // When a higher priority candidate comes in, the new connection is chosen
+ // as the selected connection.
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 10));
+ Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2);
+ ASSERT_TRUE(conn2 != nullptr);
+ conn2->ReceivedPingResponse(LOW_RTT, "id");
+ EXPECT_EQ_WAIT(conn2, ch.selected_connection(), kDefaultTimeout);
+ EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn2));
+ EXPECT_TRUE(
+ ConnectionMatchesChangeEvent(conn2, "candidate pair state changed"));
+ EXPECT_TRUE(channel_ready_to_send());
+ EXPECT_EQ(last_packet_id, last_sent_packet_id());
+
+ // If a stun request with use-candidate attribute arrives, the receiving
+ // connection will be set as the selected connection, even though
+ // its priority is lower.
+ EXPECT_EQ(len, SendData(&ch, data, len, ++last_packet_id));
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "3.3.3.3", 3, 1));
+ Connection* conn3 = WaitForConnectionTo(&ch, "3.3.3.3", 3);
+ ASSERT_TRUE(conn3 != nullptr);
+ // Because it has a lower priority, the selected connection is still conn2.
+ EXPECT_EQ(conn2, ch.selected_connection());
+ conn3->ReceivedPingResponse(LOW_RTT, "id"); // Become writable.
+ // But if it is nominated via use_candidate, it is chosen as the selected
+ // connection.
+ NominateConnection(conn3);
+ ASSERT_EQ(conn3, ch.selected_connection());
+
+ EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn3));
+ EXPECT_TRUE(
+ ConnectionMatchesChangeEvent(conn3, "nomination on the controlled side"));
+ EXPECT_EQ(last_packet_id, last_sent_packet_id());
+ EXPECT_TRUE(channel_ready_to_send());
+
+ // Even if another higher priority candidate arrives, it will not be set as
+ // the selected connection because the selected connection is nominated by
+ // the controlling side.
+ EXPECT_EQ(len, SendData(&ch, data, len, ++last_packet_id));
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "4.4.4.4", 4, 100));
+ Connection* conn4 = WaitForConnectionTo(&ch, "4.4.4.4", 4);
+ ASSERT_TRUE(conn4 != nullptr);
+ EXPECT_EQ(conn3, ch.selected_connection());
+ // But if it is nominated via use_candidate and writable, it will be set as
+ // the selected connection.
+ NominateConnection(conn4);
+ // Not switched yet because conn4 is not writable.
+ EXPECT_EQ(conn3, ch.selected_connection());
+ reset_channel_ready_to_send();
+ // The selected connection switches after conn4 becomes writable.
+ conn4->ReceivedPingResponse(LOW_RTT, "id");
+ EXPECT_EQ_WAIT(conn4, ch.selected_connection(), kDefaultTimeout);
+ EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn4));
+ EXPECT_TRUE(
+ ConnectionMatchesChangeEvent(conn4, "candidate pair state changed"));
+ EXPECT_EQ(last_packet_id, last_sent_packet_id());
+ // SignalReadyToSend is fired again because conn4 is writable.
+ EXPECT_TRUE(channel_ready_to_send());
+}
+
+// Test the field trial send_ping_on_nomination_ice_controlled
+// that sends a ping directly when a connection has been nominated
+// i.e on the ICE_CONTROLLED-side.
+TEST_P(P2PTransportChannelPingTest, TestPingOnNomination) {
+ webrtc::test::ScopedKeyValueConfig field_trials(
+ field_trials_,
+ "WebRTC-IceFieldTrials/send_ping_on_nomination_ice_controlled:true/");
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("receiving state change", 1, &pa, &field_trials);
+ PrepareChannel(&ch);
+ ch.SetIceConfig(ch.config());
+ ch.SetIceRole(ICEROLE_CONTROLLED);
+ ch.MaybeStartGathering();
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1));
+ Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1);
+ ASSERT_TRUE(conn1 != nullptr);
+
+ // A connection needs to be writable before it is selected for transmission.
+ conn1->ReceivedPingResponse(LOW_RTT, "id");
+ EXPECT_EQ_WAIT(conn1, ch.selected_connection(), kDefaultTimeout);
+ EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn1));
+
+ // When a higher priority candidate comes in, the new connection is chosen
+ // as the selected connection.
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 10));
+ Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2);
+ ASSERT_TRUE(conn2 != nullptr);
+ conn2->ReceivedPingResponse(LOW_RTT, "id");
+ EXPECT_EQ_WAIT(conn2, ch.selected_connection(), kDefaultTimeout);
+ EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn2));
+
+ // Now nominate conn1 (low prio), it shall be choosen.
+ const int before = conn1->num_pings_sent();
+ NominateConnection(conn1);
+ ASSERT_EQ(conn1, ch.selected_connection());
+ EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn1));
+
+ // And the additional ping should have been sent directly.
+ EXPECT_EQ(conn1->num_pings_sent(), before + 1);
+}
+
+// Test the field trial send_ping_on_switch_ice_controlling
+// that sends a ping directly when switching to a new connection
+// on the ICE_CONTROLLING-side.
+TEST_P(P2PTransportChannelPingTest, TestPingOnSwitch) {
+ webrtc::test::ScopedKeyValueConfig field_trials(
+ field_trials_,
+ "WebRTC-IceFieldTrials/send_ping_on_switch_ice_controlling:true/");
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("receiving state change", 1, &pa, &field_trials);
+ PrepareChannel(&ch);
+ ch.SetIceConfig(ch.config());
+ ch.SetIceRole(ICEROLE_CONTROLLING);
+ ch.MaybeStartGathering();
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1));
+ Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1);
+ ASSERT_TRUE(conn1 != nullptr);
+
+ // A connection needs to be writable before it is selected for transmission.
+ conn1->ReceivedPingResponse(LOW_RTT, "id");
+ EXPECT_EQ_WAIT(conn1, ch.selected_connection(), kDefaultTimeout);
+ EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn1));
+
+ // When a higher priority candidate comes in, the new connection is chosen
+ // as the selected connection.
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 10));
+ Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2);
+ ASSERT_TRUE(conn2 != nullptr);
+
+ const int before = conn2->num_pings_sent();
+
+ conn2->ReceivedPingResponse(LOW_RTT, "id");
+ EXPECT_EQ_WAIT(conn2, ch.selected_connection(), kDefaultTimeout);
+ EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn2));
+
+ // And the additional ping should have been sent directly.
+ EXPECT_EQ(conn2->num_pings_sent(), before + 1);
+}
+
+// Test the field trial send_ping_on_switch_ice_controlling
+// that sends a ping directly when selecteing a new connection
+// on the ICE_CONTROLLING-side (i.e also initial selection).
+TEST_P(P2PTransportChannelPingTest, TestPingOnSelected) {
+ webrtc::test::ScopedKeyValueConfig field_trials(
+ field_trials_,
+ "WebRTC-IceFieldTrials/send_ping_on_selected_ice_controlling:true/");
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("receiving state change", 1, &pa, &field_trials);
+ PrepareChannel(&ch);
+ ch.SetIceConfig(ch.config());
+ ch.SetIceRole(ICEROLE_CONTROLLING);
+ ch.MaybeStartGathering();
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1));
+ Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1);
+ ASSERT_TRUE(conn1 != nullptr);
+
+ const int before = conn1->num_pings_sent();
+
+ // A connection needs to be writable before it is selected for transmission.
+ conn1->ReceivedPingResponse(LOW_RTT, "id");
+ EXPECT_EQ_WAIT(conn1, ch.selected_connection(), kDefaultTimeout);
+ EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn1));
+
+ // And the additional ping should have been sent directly.
+ EXPECT_EQ(conn1->num_pings_sent(), before + 1);
+}
+
+// The controlled side will select a connection as the "selected connection"
+// based on requests from an unknown address before the controlling side
+// nominates a connection, and will nominate a connection from an unknown
+// address if the request contains the use_candidate attribute. Plus, it will
+// also sends back a ping response and set the ICE pwd in the remote candidate
+// appropriately.
+TEST_P(P2PTransportChannelPingTest, TestSelectConnectionFromUnknownAddress) {
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("receiving state change", 1, &pa, &field_trials_);
+ PrepareChannel(&ch);
+ ch.SetIceRole(ICEROLE_CONTROLLED);
+ ch.MaybeStartGathering();
+ // A minimal STUN message with prflx priority.
+ IceMessage request(STUN_BINDING_REQUEST);
+ request.AddAttribute(std::make_unique<StunByteStringAttribute>(
+ STUN_ATTR_USERNAME, kIceUfrag[1]));
+ uint32_t prflx_priority = ICE_TYPE_PREFERENCE_PRFLX << 24;
+ request.AddAttribute(std::make_unique<StunUInt32Attribute>(STUN_ATTR_PRIORITY,
+ prflx_priority));
+ TestUDPPort* port = static_cast<TestUDPPort*>(GetPort(&ch));
+ port->SignalUnknownAddress(port, rtc::SocketAddress("1.1.1.1", 1), PROTO_UDP,
+ &request, kIceUfrag[1], false);
+ Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1);
+ ASSERT_TRUE(conn1 != nullptr);
+ EXPECT_EQ(conn1->stats().sent_ping_responses, 1u);
+ EXPECT_NE(conn1, ch.selected_connection());
+ conn1->ReceivedPingResponse(LOW_RTT, "id");
+ EXPECT_EQ_WAIT(conn1, ch.selected_connection(), kDefaultTimeout);
+
+ // Another connection is nominated via use_candidate.
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 1));
+ Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2);
+ ASSERT_TRUE(conn2 != nullptr);
+ // Because it has a lower priority, the selected connection is still conn1.
+ EXPECT_EQ(conn1, ch.selected_connection());
+ // When it is nominated via use_candidate and writable, it is chosen as the
+ // selected connection.
+ conn2->ReceivedPingResponse(LOW_RTT, "id"); // Become writable.
+ NominateConnection(conn2);
+ EXPECT_EQ(conn2, ch.selected_connection());
+
+ // Another request with unknown address, it will not be set as the selected
+ // connection because the selected connection was nominated by the controlling
+ // side.
+ port->SignalUnknownAddress(port, rtc::SocketAddress("3.3.3.3", 3), PROTO_UDP,
+ &request, kIceUfrag[1], false);
+ Connection* conn3 = WaitForConnectionTo(&ch, "3.3.3.3", 3);
+ ASSERT_TRUE(conn3 != nullptr);
+ EXPECT_EQ(conn3->stats().sent_ping_responses, 1u);
+ conn3->ReceivedPingResponse(LOW_RTT, "id"); // Become writable.
+ EXPECT_EQ(conn2, ch.selected_connection());
+
+ // However if the request contains use_candidate attribute, it will be
+ // selected as the selected connection.
+ request.AddAttribute(
+ std::make_unique<StunByteStringAttribute>(STUN_ATTR_USE_CANDIDATE));
+ port->SignalUnknownAddress(port, rtc::SocketAddress("4.4.4.4", 4), PROTO_UDP,
+ &request, kIceUfrag[1], false);
+ Connection* conn4 = WaitForConnectionTo(&ch, "4.4.4.4", 4);
+ ASSERT_TRUE(conn4 != nullptr);
+ EXPECT_EQ(conn4->stats().sent_ping_responses, 1u);
+ // conn4 is not the selected connection yet because it is not writable.
+ EXPECT_EQ(conn2, ch.selected_connection());
+ conn4->ReceivedPingResponse(LOW_RTT, "id"); // Become writable.
+ EXPECT_EQ_WAIT(conn4, ch.selected_connection(), kDefaultTimeout);
+
+ // Test that the request from an unknown address contains a ufrag from an old
+ // generation.
+ // port->set_sent_binding_response(false);
+ ch.SetRemoteIceParameters(kIceParams[2]);
+ ch.SetRemoteIceParameters(kIceParams[3]);
+ port->SignalUnknownAddress(port, rtc::SocketAddress("5.5.5.5", 5), PROTO_UDP,
+ &request, kIceUfrag[2], false);
+ Connection* conn5 = WaitForConnectionTo(&ch, "5.5.5.5", 5);
+ ASSERT_TRUE(conn5 != nullptr);
+ EXPECT_EQ(conn5->stats().sent_ping_responses, 1u);
+ EXPECT_EQ(kIcePwd[2], conn5->remote_candidate().password());
+}
+
+// The controlled side will select a connection as the "selected connection"
+// based on media received until the controlling side nominates a connection,
+// at which point the controlled side will select that connection as
+// the "selected connection".
+TEST_P(P2PTransportChannelPingTest, TestSelectConnectionBasedOnMediaReceived) {
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("receiving state change", 1, &pa, &field_trials_);
+ PrepareChannel(&ch);
+ ch.SetIceRole(ICEROLE_CONTROLLED);
+ ch.MaybeStartGathering();
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 10));
+ Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1);
+ ASSERT_TRUE(conn1 != nullptr);
+ conn1->ReceivedPingResponse(LOW_RTT, "id");
+ EXPECT_EQ_WAIT(conn1, ch.selected_connection(), kDefaultTimeout);
+
+ // If a data packet is received on conn2, the selected connection should
+ // switch to conn2 because the controlled side must mirror the media path
+ // chosen by the controlling side.
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 1));
+ Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2);
+ ASSERT_TRUE(conn2 != nullptr);
+ conn2->ReceivedPingResponse(LOW_RTT, "id"); // Become writable and receiving.
+ conn2->OnReadPacket("ABC", 3, rtc::TimeMicros());
+ EXPECT_EQ(conn2, ch.selected_connection());
+ conn2->ReceivedPingResponse(LOW_RTT, "id"); // Become writable.
+
+ // Now another STUN message with an unknown address and use_candidate will
+ // nominate the selected connection.
+ IceMessage request(STUN_BINDING_REQUEST);
+ request.AddAttribute(std::make_unique<StunByteStringAttribute>(
+ STUN_ATTR_USERNAME, kIceUfrag[1]));
+ uint32_t prflx_priority = ICE_TYPE_PREFERENCE_PRFLX << 24;
+ request.AddAttribute(std::make_unique<StunUInt32Attribute>(STUN_ATTR_PRIORITY,
+ prflx_priority));
+ request.AddAttribute(
+ std::make_unique<StunByteStringAttribute>(STUN_ATTR_USE_CANDIDATE));
+ Port* port = GetPort(&ch);
+ port->SignalUnknownAddress(port, rtc::SocketAddress("3.3.3.3", 3), PROTO_UDP,
+ &request, kIceUfrag[1], false);
+ Connection* conn3 = WaitForConnectionTo(&ch, "3.3.3.3", 3);
+ ASSERT_TRUE(conn3 != nullptr);
+ EXPECT_NE(conn3, ch.selected_connection()); // Not writable yet.
+ conn3->ReceivedPingResponse(LOW_RTT, "id"); // Become writable.
+ EXPECT_EQ_WAIT(conn3, ch.selected_connection(), kDefaultTimeout);
+
+ // Now another data packet will not switch the selected connection because the
+ // selected connection was nominated by the controlling side.
+ conn2->ReceivedPing();
+ conn2->ReceivedPingResponse(LOW_RTT, "id");
+ conn2->OnReadPacket("XYZ", 3, rtc::TimeMicros());
+ EXPECT_EQ_WAIT(conn3, ch.selected_connection(), kDefaultTimeout);
+}
+
+TEST_P(P2PTransportChannelPingTest,
+ TestControlledAgentDataReceivingTakesHigherPrecedenceThanPriority) {
+ rtc::ScopedFakeClock clock;
+ clock.AdvanceTime(webrtc::TimeDelta::Seconds(1));
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("SwitchSelectedConnection", 1, &pa, &field_trials_);
+ PrepareChannel(&ch);
+ ch.SetIceRole(ICEROLE_CONTROLLED);
+ ch.MaybeStartGathering();
+ // The connections have decreasing priority.
+ Connection* conn1 =
+ CreateConnectionWithCandidate(&ch, &clock, "1.1.1.1", 1, 10, true);
+ ASSERT_TRUE(conn1 != nullptr);
+ Connection* conn2 =
+ CreateConnectionWithCandidate(&ch, &clock, "2.2.2.2", 2, 9, true);
+ ASSERT_TRUE(conn2 != nullptr);
+
+ // Initially, connections are selected based on priority.
+ EXPECT_EQ(1, reset_selected_candidate_pair_switches());
+ EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn1));
+
+ // conn2 receives data; it becomes selected.
+ // Advance the clock by 1ms so that the last data receiving timestamp of
+ // conn2 is larger.
+ SIMULATED_WAIT(false, 1, clock);
+ conn2->OnReadPacket("XYZ", 3, rtc::TimeMicros());
+ EXPECT_EQ(1, reset_selected_candidate_pair_switches());
+ EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn2));
+
+ // conn1 also receives data; it becomes selected due to priority again.
+ conn1->OnReadPacket("XYZ", 3, rtc::TimeMicros());
+ EXPECT_EQ(1, reset_selected_candidate_pair_switches());
+ EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn2));
+
+ // conn2 received data more recently; it is selected now because it
+ // received data more recently.
+ SIMULATED_WAIT(false, 1, clock);
+ // Need to become writable again because it was pruned.
+ conn2->ReceivedPingResponse(LOW_RTT, "id");
+ conn2->OnReadPacket("XYZ", 3, rtc::TimeMicros());
+ EXPECT_EQ(1, reset_selected_candidate_pair_switches());
+ EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn2));
+
+ // Make sure sorting won't reselect candidate pair.
+ SIMULATED_WAIT(false, 10, clock);
+ EXPECT_EQ(0, reset_selected_candidate_pair_switches());
+}
+
+TEST_P(P2PTransportChannelPingTest,
+ TestControlledAgentNominationTakesHigherPrecedenceThanDataReceiving) {
+ rtc::ScopedFakeClock clock;
+ clock.AdvanceTime(webrtc::TimeDelta::Seconds(1));
+
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("SwitchSelectedConnection", 1, &pa, &field_trials_);
+ PrepareChannel(&ch);
+ ch.SetIceRole(ICEROLE_CONTROLLED);
+ ch.MaybeStartGathering();
+ // The connections have decreasing priority.
+ Connection* conn1 =
+ CreateConnectionWithCandidate(&ch, &clock, "1.1.1.1", 1, 10, true);
+ ASSERT_TRUE(conn1 != nullptr);
+ Connection* conn2 =
+ CreateConnectionWithCandidate(&ch, &clock, "2.2.2.2", 2, 9, true);
+ ASSERT_TRUE(conn2 != nullptr);
+
+ // conn1 received data; it is the selected connection.
+ // Advance the clock to have a non-zero last-data-receiving time.
+ SIMULATED_WAIT(false, 1, clock);
+ conn1->OnReadPacket("XYZ", 3, rtc::TimeMicros());
+ EXPECT_EQ(1, reset_selected_candidate_pair_switches());
+ EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn1));
+
+ // conn2 is nominated; it becomes the selected connection.
+ NominateConnection(conn2);
+ EXPECT_EQ(1, reset_selected_candidate_pair_switches());
+ EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn2));
+
+ // conn1 is selected because it has higher priority and also nominated.
+ NominateConnection(conn1);
+ EXPECT_EQ(1, reset_selected_candidate_pair_switches());
+ EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn2));
+
+ // Make sure sorting won't reselect candidate pair.
+ SIMULATED_WAIT(false, 10, clock);
+ EXPECT_EQ(0, reset_selected_candidate_pair_switches());
+}
+
+TEST_P(P2PTransportChannelPingTest,
+ TestControlledAgentSelectsConnectionWithHigherNomination) {
+ rtc::ScopedFakeClock clock;
+ clock.AdvanceTime(webrtc::TimeDelta::Seconds(1));
+
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("test", 1, &pa, &field_trials_);
+ PrepareChannel(&ch);
+ ch.SetIceRole(ICEROLE_CONTROLLED);
+ ch.MaybeStartGathering();
+ // The connections have decreasing priority.
+ Connection* conn1 =
+ CreateConnectionWithCandidate(&ch, &clock, "1.1.1.1", 1, 10, true);
+ ASSERT_TRUE(conn1 != nullptr);
+ Connection* conn2 =
+ CreateConnectionWithCandidate(&ch, &clock, "2.2.2.2", 2, 9, true);
+ ASSERT_TRUE(conn2 != nullptr);
+
+ // conn1 is the selected connection because it has a higher priority,
+ EXPECT_EQ_SIMULATED_WAIT(conn1, ch.selected_connection(), kDefaultTimeout,
+ clock);
+ EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn1));
+ reset_selected_candidate_pair_switches();
+
+ // conn2 is nominated; it becomes selected.
+ NominateConnection(conn2);
+ EXPECT_EQ(1, reset_selected_candidate_pair_switches());
+ EXPECT_EQ(conn2, ch.selected_connection());
+ EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn2));
+
+ // conn1 is selected because of its priority.
+ NominateConnection(conn1);
+ EXPECT_EQ(1, reset_selected_candidate_pair_switches());
+ EXPECT_EQ(conn1, ch.selected_connection());
+ EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn1));
+
+ // conn2 gets higher remote nomination; it is selected again.
+ NominateConnection(conn2, 2U);
+ EXPECT_EQ(1, reset_selected_candidate_pair_switches());
+ EXPECT_EQ(conn2, ch.selected_connection());
+ EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn2));
+
+ // Make sure sorting won't reselect candidate pair.
+ SIMULATED_WAIT(false, 100, clock);
+ EXPECT_EQ(0, reset_selected_candidate_pair_switches());
+}
+
+TEST_P(P2PTransportChannelPingTest, TestEstimatedDisconnectedTime) {
+ rtc::ScopedFakeClock clock;
+ clock.AdvanceTime(webrtc::TimeDelta::Seconds(1));
+
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("test", 1, &pa, &field_trials_);
+ PrepareChannel(&ch);
+ ch.SetIceRole(ICEROLE_CONTROLLED);
+ ch.MaybeStartGathering();
+ // The connections have decreasing priority.
+ Connection* conn1 =
+ CreateConnectionWithCandidate(&ch, &clock, "1.1.1.1", /* port= */ 1,
+ /* priority= */ 10, /* writable= */ true);
+ ASSERT_TRUE(conn1 != nullptr);
+ Connection* conn2 =
+ CreateConnectionWithCandidate(&ch, &clock, "2.2.2.2", /* port= */ 2,
+ /* priority= */ 9, /* writable= */ true);
+ ASSERT_TRUE(conn2 != nullptr);
+
+ // conn1 is the selected connection because it has a higher priority,
+ EXPECT_EQ_SIMULATED_WAIT(conn1, ch.selected_connection(), kDefaultTimeout,
+ clock);
+ EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn1));
+ // No estimateded disconnect time at first connect <=> value is 0.
+ EXPECT_EQ(LastEstimatedDisconnectedTimeMs(), 0);
+
+ // Use nomination to force switching of selected connection.
+ int nomination = 1;
+
+ {
+ clock.AdvanceTime(webrtc::TimeDelta::Seconds(1));
+ // This will not parse as STUN, and is considered data
+ conn1->OnReadPacket("XYZ", 3, rtc::TimeMicros());
+ clock.AdvanceTime(webrtc::TimeDelta::Seconds(2));
+
+ // conn2 is nominated; it becomes selected.
+ NominateConnection(conn2, nomination++);
+ EXPECT_EQ(conn2, ch.selected_connection());
+ // We got data 2s ago...guess that we lost 2s of connectivity.
+ EXPECT_EQ(LastEstimatedDisconnectedTimeMs(), 2000);
+ }
+
+ {
+ clock.AdvanceTime(webrtc::TimeDelta::Seconds(1));
+ conn2->OnReadPacket("XYZ", 3, rtc::TimeMicros());
+
+ clock.AdvanceTime(webrtc::TimeDelta::Seconds(2));
+ ReceivePingOnConnection(conn2, kIceUfrag[1], 1, nomination++);
+
+ clock.AdvanceTime(webrtc::TimeDelta::Millis(500));
+
+ ReceivePingOnConnection(conn1, kIceUfrag[1], 1, nomination++);
+ EXPECT_EQ(conn1, ch.selected_connection());
+ // We got ping 500ms ago...guess that we lost 500ms of connectivity.
+ EXPECT_EQ(LastEstimatedDisconnectedTimeMs(), 500);
+ }
+}
+
+TEST_P(P2PTransportChannelPingTest,
+ TestControlledAgentIgnoresSmallerNomination) {
+ rtc::ScopedFakeClock clock;
+ clock.AdvanceTime(webrtc::TimeDelta::Seconds(1));
+
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("test", 1, &pa, &field_trials_);
+ PrepareChannel(&ch);
+ ch.SetIceRole(ICEROLE_CONTROLLED);
+ ch.MaybeStartGathering();
+ Connection* conn =
+ CreateConnectionWithCandidate(&ch, &clock, "1.1.1.1", 1, 10, false);
+ ReceivePingOnConnection(conn, kIceUfrag[1], 1, 2U);
+ EXPECT_EQ(2U, conn->remote_nomination());
+ // Smaller nomination is ignored.
+ ReceivePingOnConnection(conn, kIceUfrag[1], 1, 1U);
+ EXPECT_EQ(2U, conn->remote_nomination());
+}
+
+TEST_P(P2PTransportChannelPingTest,
+ TestControlledAgentWriteStateTakesHigherPrecedenceThanNomination) {
+ rtc::ScopedFakeClock clock;
+
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("SwitchSelectedConnection", 1, &pa, &field_trials_);
+ PrepareChannel(&ch);
+ ch.SetIceRole(ICEROLE_CONTROLLED);
+ ch.MaybeStartGathering();
+ // The connections have decreasing priority.
+ Connection* conn1 =
+ CreateConnectionWithCandidate(&ch, &clock, "1.1.1.1", 1, 10, false);
+ ASSERT_TRUE(conn1 != nullptr);
+ Connection* conn2 =
+ CreateConnectionWithCandidate(&ch, &clock, "2.2.2.2", 2, 9, false);
+ ASSERT_TRUE(conn2 != nullptr);
+
+ NominateConnection(conn1);
+ // There is no selected connection because no connection is writable.
+ EXPECT_EQ(0, reset_selected_candidate_pair_switches());
+
+ // conn2 becomes writable; it is selected even though it is not nominated.
+ conn2->ReceivedPingResponse(LOW_RTT, "id");
+ EXPECT_EQ_SIMULATED_WAIT(1, reset_selected_candidate_pair_switches(),
+ kDefaultTimeout, clock);
+ EXPECT_EQ_SIMULATED_WAIT(conn2, ch.selected_connection(), kDefaultTimeout,
+ clock);
+ EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn2));
+
+ // If conn1 is also writable, it will become selected.
+ conn1->ReceivedPingResponse(LOW_RTT, "id");
+ EXPECT_EQ_SIMULATED_WAIT(1, reset_selected_candidate_pair_switches(),
+ kDefaultTimeout, clock);
+ EXPECT_EQ_SIMULATED_WAIT(conn1, ch.selected_connection(), kDefaultTimeout,
+ clock);
+ EXPECT_TRUE(CandidatePairMatchesNetworkRoute(conn1));
+
+ // Make sure sorting won't reselect candidate pair.
+ SIMULATED_WAIT(false, 10, clock);
+ EXPECT_EQ(0, reset_selected_candidate_pair_switches());
+}
+
+// Test that if a new remote candidate has the same address and port with
+// an old one, it will be used to create a new connection.
+TEST_P(P2PTransportChannelPingTest, TestAddRemoteCandidateWithAddressReuse) {
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("candidate reuse", 1, &pa, &field_trials_);
+ PrepareChannel(&ch);
+ ch.MaybeStartGathering();
+ const std::string host_address = "1.1.1.1";
+ const int port_num = 1;
+
+ // kIceUfrag[1] is the current generation ufrag.
+ Candidate candidate = CreateUdpCandidate(LOCAL_PORT_TYPE, host_address,
+ port_num, 1, kIceUfrag[1]);
+ ch.AddRemoteCandidate(candidate);
+ Connection* conn1 = WaitForConnectionTo(&ch, host_address, port_num);
+ ASSERT_TRUE(conn1 != nullptr);
+ EXPECT_EQ(0u, conn1->remote_candidate().generation());
+
+ // Simply adding the same candidate again won't create a new connection.
+ ch.AddRemoteCandidate(candidate);
+ Connection* conn2 = GetConnectionTo(&ch, host_address, port_num);
+ EXPECT_EQ(conn1, conn2);
+
+ // Update the ufrag of the candidate and add it again.
+ candidate.set_username(kIceUfrag[2]);
+ ch.AddRemoteCandidate(candidate);
+ conn2 = GetConnectionTo(&ch, host_address, port_num);
+ EXPECT_NE(conn1, conn2);
+ EXPECT_EQ(kIceUfrag[2], conn2->remote_candidate().username());
+ EXPECT_EQ(1u, conn2->remote_candidate().generation());
+
+ // Verify that a ping with the new ufrag can be received on the new
+ // connection.
+ EXPECT_EQ(0, conn2->last_ping_received());
+ ReceivePingOnConnection(conn2, kIceUfrag[2], 1 /* priority */);
+ EXPECT_GT(conn2->last_ping_received(), 0);
+}
+
+// When the current selected connection is strong, lower-priority connections
+// will be pruned. Otherwise, lower-priority connections are kept.
+TEST_P(P2PTransportChannelPingTest, TestDontPruneWhenWeak) {
+ rtc::ScopedFakeClock clock;
+ clock.AdvanceTime(webrtc::TimeDelta::Seconds(1));
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("test channel", 1, &pa, &field_trials_);
+ PrepareChannel(&ch);
+ ch.SetIceRole(ICEROLE_CONTROLLED);
+ ch.MaybeStartGathering();
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1));
+ Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1);
+ ASSERT_TRUE(conn1 != nullptr);
+ EXPECT_EQ(nullptr, ch.selected_connection());
+ conn1->ReceivedPingResponse(LOW_RTT, "id"); // Becomes writable and receiving
+
+ // When a higher-priority, nominated candidate comes in, the connections with
+ // lower-priority are pruned.
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 10));
+ Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2, &clock);
+ ASSERT_TRUE(conn2 != nullptr);
+ conn2->ReceivedPingResponse(LOW_RTT, "id"); // Becomes writable and receiving
+ NominateConnection(conn2);
+ EXPECT_TRUE_SIMULATED_WAIT(conn1->pruned(), kMediumTimeout, clock);
+
+ ch.SetIceConfig(CreateIceConfig(500, GATHER_ONCE));
+ // Wait until conn2 becomes not receiving.
+ EXPECT_TRUE_SIMULATED_WAIT(!conn2->receiving(), kMediumTimeout, clock);
+
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "3.3.3.3", 3, 1));
+ Connection* conn3 = WaitForConnectionTo(&ch, "3.3.3.3", 3, &clock);
+ ASSERT_TRUE(conn3 != nullptr);
+ // The selected connection should still be conn2. Even through conn3 has lower
+ // priority and is not receiving/writable, it is not pruned because the
+ // selected connection is not receiving.
+ SIMULATED_WAIT(conn3->pruned(), kShortTimeout, clock);
+ EXPECT_FALSE(conn3->pruned());
+}
+
+TEST_P(P2PTransportChannelPingTest, TestDontPruneHighPriorityConnections) {
+ rtc::ScopedFakeClock clock;
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("test channel", 1, &pa, &field_trials_);
+ PrepareChannel(&ch);
+ ch.SetIceRole(ICEROLE_CONTROLLED);
+ ch.MaybeStartGathering();
+ Connection* conn1 =
+ CreateConnectionWithCandidate(&ch, &clock, "1.1.1.1", 1, 100, true);
+ ASSERT_TRUE(conn1 != nullptr);
+ Connection* conn2 =
+ CreateConnectionWithCandidate(&ch, &clock, "2.2.2.2", 2, 200, false);
+ ASSERT_TRUE(conn2 != nullptr);
+ // Even if conn1 is writable, nominated, receiving data, it should not prune
+ // conn2.
+ NominateConnection(conn1);
+ SIMULATED_WAIT(false, 1, clock);
+ conn1->OnReadPacket("XYZ", 3, rtc::TimeMicros());
+ SIMULATED_WAIT(conn2->pruned(), 100, clock);
+ EXPECT_FALSE(conn2->pruned());
+}
+
+// Test that GetState returns the state correctly.
+TEST_P(P2PTransportChannelPingTest, TestGetState) {
+ rtc::ScopedFakeClock clock;
+ clock.AdvanceTime(webrtc::TimeDelta::Seconds(1));
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("test channel", 1, &pa, &field_trials_);
+ EXPECT_EQ(webrtc::IceTransportState::kNew, ch.GetIceTransportState());
+ PrepareChannel(&ch);
+ ch.MaybeStartGathering();
+ // After gathering we are still in the kNew state because we aren't checking
+ // any connections yet.
+ EXPECT_EQ(webrtc::IceTransportState::kNew, ch.GetIceTransportState());
+ EXPECT_EQ(IceTransportState::STATE_INIT, ch.GetState());
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 100));
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 1));
+ // Checking candidates that have been added with gathered candidates.
+ ASSERT_GT(ch.connections().size(), 0u);
+ EXPECT_EQ(webrtc::IceTransportState::kChecking, ch.GetIceTransportState());
+ Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1, &clock);
+ Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2, &clock);
+ ASSERT_TRUE(conn1 != nullptr);
+ ASSERT_TRUE(conn2 != nullptr);
+ // Now there are two connections, so the transport channel is connecting.
+ EXPECT_EQ(IceTransportState::STATE_CONNECTING, ch.GetState());
+ // No connections are writable yet, so we should still be in the kChecking
+ // state.
+ EXPECT_EQ(webrtc::IceTransportState::kChecking, ch.GetIceTransportState());
+ // `conn1` becomes writable and receiving; it then should prune `conn2`.
+ conn1->ReceivedPingResponse(LOW_RTT, "id");
+ EXPECT_TRUE_SIMULATED_WAIT(conn2->pruned(), kShortTimeout, clock);
+ EXPECT_EQ(IceTransportState::STATE_COMPLETED, ch.GetState());
+ EXPECT_EQ(webrtc::IceTransportState::kConnected, ch.GetIceTransportState());
+ conn1->Prune(); // All connections are pruned.
+ // Need to wait until the channel state is updated.
+ EXPECT_EQ_SIMULATED_WAIT(IceTransportState::STATE_FAILED, ch.GetState(),
+ kShortTimeout, clock);
+ EXPECT_EQ(webrtc::IceTransportState::kFailed, ch.GetIceTransportState());
+}
+
+// Test that when a low-priority connection is pruned, it is not deleted
+// right away, and it can become active and be pruned again.
+TEST_P(P2PTransportChannelPingTest, TestConnectionPrunedAgain) {
+ rtc::ScopedFakeClock clock;
+ clock.AdvanceTime(webrtc::TimeDelta::Seconds(1));
+
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("test channel", 1, &pa, &field_trials_);
+ PrepareChannel(&ch);
+ IceConfig config = CreateIceConfig(1000, GATHER_ONCE);
+ config.receiving_switching_delay = 800;
+ ch.SetIceConfig(config);
+ ch.MaybeStartGathering();
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 100));
+ Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1, &clock);
+ ASSERT_TRUE(conn1 != nullptr);
+ EXPECT_EQ(nullptr, ch.selected_connection());
+ conn1->ReceivedPingResponse(LOW_RTT, "id"); // Becomes writable and receiving
+ EXPECT_EQ_SIMULATED_WAIT(conn1, ch.selected_connection(), kDefaultTimeout,
+ clock);
+
+ // Add a low-priority connection `conn2`, which will be pruned, but it will
+ // not be deleted right away. Once the current selected connection becomes not
+ // receiving, `conn2` will start to ping and upon receiving the ping response,
+ // it will become the selected connection.
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 1));
+ Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2, &clock);
+ ASSERT_TRUE(conn2 != nullptr);
+ EXPECT_TRUE_SIMULATED_WAIT(!conn2->active(), kDefaultTimeout, clock);
+ // `conn2` should not send a ping yet.
+ EXPECT_EQ(IceCandidatePairState::WAITING, conn2->state());
+ EXPECT_EQ(IceTransportState::STATE_COMPLETED, ch.GetState());
+ // Wait for `conn1` becoming not receiving.
+ EXPECT_TRUE_SIMULATED_WAIT(!conn1->receiving(), kMediumTimeout, clock);
+ // Make sure conn2 is not deleted.
+ conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2, &clock);
+ ASSERT_TRUE(conn2 != nullptr);
+ EXPECT_EQ_SIMULATED_WAIT(IceCandidatePairState::IN_PROGRESS, conn2->state(),
+ kDefaultTimeout, clock);
+ conn2->ReceivedPingResponse(LOW_RTT, "id");
+ EXPECT_EQ_SIMULATED_WAIT(conn2, ch.selected_connection(), kDefaultTimeout,
+ clock);
+ EXPECT_EQ(IceTransportState::STATE_CONNECTING, ch.GetState());
+
+ // When `conn1` comes back again, `conn2` will be pruned again.
+ conn1->ReceivedPingResponse(LOW_RTT, "id");
+ EXPECT_EQ_SIMULATED_WAIT(conn1, ch.selected_connection(), kDefaultTimeout,
+ clock);
+ EXPECT_TRUE_SIMULATED_WAIT(!conn2->active(), kDefaultTimeout, clock);
+ EXPECT_EQ(IceTransportState::STATE_COMPLETED, ch.GetState());
+}
+
+// Test that if all connections in a channel has timed out on writing, they
+// will all be deleted. We use Prune to simulate write_time_out.
+TEST_P(P2PTransportChannelPingTest, TestDeleteConnectionsIfAllWriteTimedout) {
+ rtc::ScopedFakeClock clock;
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("test channel", 1, &pa, &field_trials_);
+ PrepareChannel(&ch);
+ ch.MaybeStartGathering();
+ // Have one connection only but later becomes write-time-out.
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 100));
+ Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1, &clock);
+ ASSERT_TRUE(conn1 != nullptr);
+ conn1->ReceivedPing(); // Becomes receiving
+ conn1->Prune();
+ EXPECT_TRUE_SIMULATED_WAIT(ch.connections().empty(), kShortTimeout, clock);
+
+ // Have two connections but both become write-time-out later.
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 1));
+ Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2, &clock);
+ ASSERT_TRUE(conn2 != nullptr);
+ conn2->ReceivedPing(); // Becomes receiving
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "3.3.3.3", 3, 2));
+ Connection* conn3 = WaitForConnectionTo(&ch, "3.3.3.3", 3, &clock);
+ ASSERT_TRUE(conn3 != nullptr);
+ conn3->ReceivedPing(); // Becomes receiving
+ // Now prune both conn2 and conn3; they will be deleted soon.
+ conn2->Prune();
+ conn3->Prune();
+ EXPECT_TRUE_SIMULATED_WAIT(ch.connections().empty(), kShortTimeout, clock);
+}
+
+// Tests that after a port allocator session is started, it will be stopped
+// when a new connection becomes writable and receiving. Also tests that if a
+// connection belonging to an old session becomes writable, it won't stop
+// the current port allocator session.
+TEST_P(P2PTransportChannelPingTest, TestStopPortAllocatorSessions) {
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("test channel", 1, &pa, &field_trials_);
+ PrepareChannel(&ch);
+ ch.SetIceConfig(CreateIceConfig(2000, GATHER_ONCE));
+ ch.MaybeStartGathering();
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 100));
+ Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1);
+ ASSERT_TRUE(conn1 != nullptr);
+ conn1->ReceivedPingResponse(LOW_RTT, "id"); // Becomes writable and receiving
+ EXPECT_TRUE(!ch.allocator_session()->IsGettingPorts());
+
+ // Start a new session. Even though conn1, which belongs to an older
+ // session, becomes unwritable and writable again, it should not stop the
+ // current session.
+ ch.SetIceParameters(kIceParams[1]);
+ ch.MaybeStartGathering();
+ conn1->Prune();
+ conn1->ReceivedPingResponse(LOW_RTT, "id");
+ EXPECT_TRUE(ch.allocator_session()->IsGettingPorts());
+
+ // But if a new connection created from the new session becomes writable,
+ // it will stop the current session.
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 100));
+ Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2);
+ ASSERT_TRUE(conn2 != nullptr);
+ conn2->ReceivedPingResponse(LOW_RTT, "id"); // Becomes writable and receiving
+ EXPECT_TRUE(!ch.allocator_session()->IsGettingPorts());
+}
+
+// Test that the ICE role is updated even on ports that has been removed.
+// These ports may still have connections that need a correct role, in case that
+// the connections on it may still receive stun pings.
+TEST_P(P2PTransportChannelPingTest, TestIceRoleUpdatedOnRemovedPort) {
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("test channel", ICE_CANDIDATE_COMPONENT_DEFAULT, &pa,
+ &field_trials_);
+ // Starts with ICEROLE_CONTROLLING.
+ PrepareChannel(&ch);
+ IceConfig config = CreateIceConfig(1000, GATHER_CONTINUALLY);
+ ch.SetIceConfig(config);
+ ch.MaybeStartGathering();
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1));
+
+ Connection* conn = WaitForConnectionTo(&ch, "1.1.1.1", 1);
+ ASSERT_TRUE(conn != nullptr);
+
+ // Make a fake signal to remove the ports in the p2ptransportchannel. then
+ // change the ICE role and expect it to be updated.
+ std::vector<PortInterface*> ports(1, conn->PortForTest());
+ ch.allocator_session()->SignalPortsPruned(ch.allocator_session(), ports);
+ ch.SetIceRole(ICEROLE_CONTROLLED);
+ EXPECT_EQ(ICEROLE_CONTROLLED, conn->PortForTest()->GetIceRole());
+}
+
+// Test that the ICE role is updated even on ports with inactive networks.
+// These ports may still have connections that need a correct role, for the
+// pings sent by those connections until they're replaced by newer-generation
+// connections.
+TEST_P(P2PTransportChannelPingTest, TestIceRoleUpdatedOnPortAfterIceRestart) {
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("test channel", ICE_CANDIDATE_COMPONENT_DEFAULT, &pa,
+ &field_trials_);
+ // Starts with ICEROLE_CONTROLLING.
+ PrepareChannel(&ch);
+ ch.MaybeStartGathering();
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1));
+
+ Connection* conn = WaitForConnectionTo(&ch, "1.1.1.1", 1);
+ ASSERT_TRUE(conn != nullptr);
+
+ // Do an ICE restart, change the role, and expect the old port to have its
+ // role updated.
+ ch.SetIceParameters(kIceParams[1]);
+ ch.MaybeStartGathering();
+ ch.SetIceRole(ICEROLE_CONTROLLED);
+ EXPECT_EQ(ICEROLE_CONTROLLED, conn->PortForTest()->GetIceRole());
+}
+
+// Test that after some amount of time without receiving data, the connection
+// will be destroyed. The port will only be destroyed after it is marked as
+// "pruned."
+TEST_P(P2PTransportChannelPingTest, TestPortDestroyedAfterTimeoutAndPruned) {
+ rtc::ScopedFakeClock fake_clock;
+
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("test channel", ICE_CANDIDATE_COMPONENT_DEFAULT, &pa,
+ &field_trials_);
+ PrepareChannel(&ch);
+ ch.SetIceRole(ICEROLE_CONTROLLED);
+ ch.MaybeStartGathering();
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1));
+
+ Connection* conn = WaitForConnectionTo(&ch, "1.1.1.1", 1);
+ ASSERT_TRUE(conn != nullptr);
+
+ // Simulate 2 minutes going by. This should be enough time for the port to
+ // time out.
+ for (int second = 0; second < 120; ++second) {
+ fake_clock.AdvanceTime(webrtc::TimeDelta::Seconds(1));
+ }
+ EXPECT_EQ(nullptr, GetConnectionTo(&ch, "1.1.1.1", 1));
+ // Port will not be removed because it is not pruned yet.
+ PortInterface* port = GetPort(&ch);
+ ASSERT_NE(nullptr, port);
+
+ // If the session prunes all ports, the port will be destroyed.
+ ch.allocator_session()->PruneAllPorts();
+ EXPECT_EQ_SIMULATED_WAIT(nullptr, GetPort(&ch), 1, fake_clock);
+ EXPECT_EQ_SIMULATED_WAIT(nullptr, GetPrunedPort(&ch), 1, fake_clock);
+}
+
+TEST_P(P2PTransportChannelPingTest, TestMaxOutstandingPingsFieldTrial) {
+ webrtc::test::ScopedKeyValueConfig field_trials(
+ field_trials_, "WebRTC-IceFieldTrials/max_outstanding_pings:3/");
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("max", 1, &pa, &field_trials);
+ ch.SetIceConfig(ch.config());
+ PrepareChannel(&ch);
+ ch.MaybeStartGathering();
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1));
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 2));
+
+ Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1);
+ Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2);
+ ASSERT_TRUE(conn1 != nullptr);
+ ASSERT_TRUE(conn2 != nullptr);
+
+ EXPECT_TRUE_WAIT(conn1->num_pings_sent() == 3 && conn2->num_pings_sent() == 3,
+ kDefaultTimeout);
+
+ // Check that these connections don't send any more pings.
+ EXPECT_EQ(nullptr, ch.FindNextPingableConnection());
+}
+
+class P2PTransportChannelMostLikelyToWorkFirstTest
+ : public P2PTransportChannelPingTest {
+ public:
+ P2PTransportChannelMostLikelyToWorkFirstTest()
+ : turn_server_(rtc::Thread::Current(),
+ ss(),
+ kTurnUdpIntAddr,
+ kTurnUdpExtAddr) {
+ network_manager_.AddInterface(kPublicAddrs[0]);
+ allocator_.reset(
+ CreateBasicPortAllocator(&network_manager_, ss(), ServerAddresses(),
+ kTurnUdpIntAddr, rtc::SocketAddress()));
+ allocator_->set_flags(allocator_->flags() | PORTALLOCATOR_DISABLE_STUN |
+ PORTALLOCATOR_DISABLE_TCP);
+ allocator_->set_step_delay(kMinimumStepDelay);
+ }
+
+ P2PTransportChannel& StartTransportChannel(
+ bool prioritize_most_likely_to_work,
+ int stable_writable_connection_ping_interval,
+ const webrtc::FieldTrialsView* field_trials = nullptr) {
+ channel_.reset(
+ new P2PTransportChannel("checks", 1, allocator(), field_trials));
+ IceConfig config = channel_->config();
+ config.prioritize_most_likely_candidate_pairs =
+ prioritize_most_likely_to_work;
+ config.stable_writable_connection_ping_interval =
+ stable_writable_connection_ping_interval;
+ channel_->SetIceConfig(config);
+ PrepareChannel(channel_.get());
+ channel_->MaybeStartGathering();
+ return *channel_.get();
+ }
+
+ BasicPortAllocator* allocator() { return allocator_.get(); }
+ TestTurnServer* turn_server() { return &turn_server_; }
+
+ // This verifies the next pingable connection has the expected candidates'
+ // types and, for relay local candidate, the expected relay protocol and ping
+ // it.
+ void VerifyNextPingableConnection(
+ absl::string_view local_candidate_type,
+ absl::string_view remote_candidate_type,
+ absl::string_view relay_protocol_type = UDP_PROTOCOL_NAME) {
+ Connection* conn = FindNextPingableConnectionAndPingIt(channel_.get());
+ ASSERT_TRUE(conn != nullptr);
+ EXPECT_EQ(conn->local_candidate().type(), local_candidate_type);
+ if (conn->local_candidate().type() == RELAY_PORT_TYPE) {
+ EXPECT_EQ(conn->local_candidate().relay_protocol(), relay_protocol_type);
+ }
+ EXPECT_EQ(conn->remote_candidate().type(), remote_candidate_type);
+ }
+
+ private:
+ std::unique_ptr<BasicPortAllocator> allocator_;
+ rtc::FakeNetworkManager network_manager_;
+ TestTurnServer turn_server_;
+ std::unique_ptr<P2PTransportChannel> channel_;
+};
+
+INSTANTIATE_TEST_SUITE_P(Legacy,
+ P2PTransportChannelMostLikelyToWorkFirstTest,
+ Values(""));
+INSTANTIATE_TEST_SUITE_P(Active,
+ P2PTransportChannelMostLikelyToWorkFirstTest,
+ Values("WebRTC-UseActiveIceController/Enabled/"));
+
+// Test that Relay/Relay connections will be pinged first when no other
+// connections have been pinged yet, unless we need to ping a trigger check or
+// we have a selected connection.
+TEST_P(P2PTransportChannelMostLikelyToWorkFirstTest,
+ TestRelayRelayFirstWhenNothingPingedYet) {
+ const int max_strong_interval = 500;
+ P2PTransportChannel& ch =
+ StartTransportChannel(true, max_strong_interval, &field_trials_);
+ EXPECT_TRUE_WAIT(ch.ports().size() == 2, kDefaultTimeout);
+ EXPECT_EQ(ch.ports()[0]->Type(), LOCAL_PORT_TYPE);
+ EXPECT_EQ(ch.ports()[1]->Type(), RELAY_PORT_TYPE);
+
+ ch.AddRemoteCandidate(CreateUdpCandidate(RELAY_PORT_TYPE, "1.1.1.1", 1, 1));
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 2));
+
+ EXPECT_TRUE_WAIT(ch.connections().size() == 4, kDefaultTimeout);
+
+ // Relay/Relay should be the first pingable connection.
+ Connection* conn = FindNextPingableConnectionAndPingIt(&ch);
+ ASSERT_TRUE(conn != nullptr);
+ EXPECT_EQ(conn->local_candidate().type(), RELAY_PORT_TYPE);
+ EXPECT_EQ(conn->remote_candidate().type(), RELAY_PORT_TYPE);
+
+ // Unless that we have a trigger check waiting to be pinged.
+ Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2);
+ ASSERT_TRUE(conn2 != nullptr);
+ EXPECT_EQ(conn2->local_candidate().type(), LOCAL_PORT_TYPE);
+ EXPECT_EQ(conn2->remote_candidate().type(), LOCAL_PORT_TYPE);
+ conn2->ReceivedPing();
+ EXPECT_EQ(conn2, FindNextPingableConnectionAndPingIt(&ch));
+
+ // Make conn3 the selected connection.
+ Connection* conn3 = WaitForConnectionTo(&ch, "1.1.1.1", 1);
+ ASSERT_TRUE(conn3 != nullptr);
+ EXPECT_EQ(conn3->local_candidate().type(), LOCAL_PORT_TYPE);
+ EXPECT_EQ(conn3->remote_candidate().type(), RELAY_PORT_TYPE);
+ conn3->ReceivedPingResponse(LOW_RTT, "id");
+ ASSERT_TRUE(conn3->writable());
+ conn3->ReceivedPing();
+
+ /*
+
+ TODO(honghaiz): Re-enable this once we use fake clock for this test to fix
+ the flakiness. The following test becomes flaky because we now ping the
+ connections with fast rates until every connection is pinged at least three
+ times. The selected connection may have been pinged before
+ `max_strong_interval`, so it may not be the next connection to be pinged as
+ expected in the test.
+
+ // Verify that conn3 will be the "selected connection" since it is readable
+ // and writable. After `MAX_CURRENT_STRONG_INTERVAL`, it should be the next
+ // pingable connection.
+ EXPECT_TRUE_WAIT(conn3 == ch.selected_connection(), kDefaultTimeout);
+ WAIT(false, max_strong_interval + 100);
+ conn3->ReceivedPingResponse(LOW_RTT, "id");
+ ASSERT_TRUE(conn3->writable());
+ EXPECT_EQ(conn3, FindNextPingableConnectionAndPingIt(&ch));
+
+ */
+}
+
+// Test that Relay/Relay connections will be pinged first when everything has
+// been pinged even if the Relay/Relay connection wasn't the first to be pinged
+// in the first round.
+TEST_P(P2PTransportChannelMostLikelyToWorkFirstTest,
+ TestRelayRelayFirstWhenEverythingPinged) {
+ P2PTransportChannel& ch = StartTransportChannel(true, 500, &field_trials_);
+ EXPECT_TRUE_WAIT(ch.ports().size() == 2, kDefaultTimeout);
+ EXPECT_EQ(ch.ports()[0]->Type(), LOCAL_PORT_TYPE);
+ EXPECT_EQ(ch.ports()[1]->Type(), RELAY_PORT_TYPE);
+
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1));
+ EXPECT_TRUE_WAIT(ch.connections().size() == 2, kDefaultTimeout);
+
+ // Initially, only have Local/Local and Local/Relay.
+ VerifyNextPingableConnection(LOCAL_PORT_TYPE, LOCAL_PORT_TYPE);
+ VerifyNextPingableConnection(RELAY_PORT_TYPE, LOCAL_PORT_TYPE);
+
+ // Remote Relay candidate arrives.
+ ch.AddRemoteCandidate(CreateUdpCandidate(RELAY_PORT_TYPE, "2.2.2.2", 2, 2));
+ EXPECT_TRUE_WAIT(ch.connections().size() == 4, kDefaultTimeout);
+
+ // Relay/Relay should be the first since it hasn't been pinged before.
+ VerifyNextPingableConnection(RELAY_PORT_TYPE, RELAY_PORT_TYPE);
+
+ // Local/Relay is the final one.
+ VerifyNextPingableConnection(LOCAL_PORT_TYPE, RELAY_PORT_TYPE);
+
+ // Now, every connection has been pinged once. The next one should be
+ // Relay/Relay.
+ VerifyNextPingableConnection(RELAY_PORT_TYPE, RELAY_PORT_TYPE);
+}
+
+// Test that when we receive a new remote candidate, they will be tried first
+// before we re-ping Relay/Relay connections again.
+TEST_P(P2PTransportChannelMostLikelyToWorkFirstTest,
+ TestNoStarvationOnNonRelayConnection) {
+ P2PTransportChannel& ch = StartTransportChannel(true, 500, &field_trials_);
+ EXPECT_TRUE_WAIT(ch.ports().size() == 2, kDefaultTimeout);
+ EXPECT_EQ(ch.ports()[0]->Type(), LOCAL_PORT_TYPE);
+ EXPECT_EQ(ch.ports()[1]->Type(), RELAY_PORT_TYPE);
+
+ ch.AddRemoteCandidate(CreateUdpCandidate(RELAY_PORT_TYPE, "1.1.1.1", 1, 1));
+ EXPECT_TRUE_WAIT(ch.connections().size() == 2, kDefaultTimeout);
+
+ // Initially, only have Relay/Relay and Local/Relay. Ping Relay/Relay first.
+ VerifyNextPingableConnection(RELAY_PORT_TYPE, RELAY_PORT_TYPE);
+
+ // Next, ping Local/Relay.
+ VerifyNextPingableConnection(LOCAL_PORT_TYPE, RELAY_PORT_TYPE);
+
+ // Remote Local candidate arrives.
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 2));
+ EXPECT_TRUE_WAIT(ch.connections().size() == 4, kDefaultTimeout);
+
+ // Local/Local should be the first since it hasn't been pinged before.
+ VerifyNextPingableConnection(LOCAL_PORT_TYPE, LOCAL_PORT_TYPE);
+
+ // Relay/Local is the final one.
+ VerifyNextPingableConnection(RELAY_PORT_TYPE, LOCAL_PORT_TYPE);
+
+ // Now, every connection has been pinged once. The next one should be
+ // Relay/Relay.
+ VerifyNextPingableConnection(RELAY_PORT_TYPE, RELAY_PORT_TYPE);
+}
+
+// Test skip_relay_to_non_relay_connections field-trial.
+// I.e that we never create connection between relay and non-relay.
+TEST_P(P2PTransportChannelMostLikelyToWorkFirstTest,
+ TestSkipRelayToNonRelayConnectionsFieldTrial) {
+ webrtc::test::ScopedKeyValueConfig field_trials(
+ field_trials_,
+ "WebRTC-IceFieldTrials/skip_relay_to_non_relay_connections:true/");
+ P2PTransportChannel& ch = StartTransportChannel(true, 500, &field_trials);
+ EXPECT_TRUE_WAIT(ch.ports().size() == 2, kDefaultTimeout);
+ EXPECT_EQ(ch.ports()[0]->Type(), LOCAL_PORT_TYPE);
+ EXPECT_EQ(ch.ports()[1]->Type(), RELAY_PORT_TYPE);
+
+ // Remote Relay candidate arrives.
+ ch.AddRemoteCandidate(CreateUdpCandidate(RELAY_PORT_TYPE, "1.1.1.1", 1, 1));
+ EXPECT_TRUE_WAIT(ch.connections().size() == 1, kDefaultTimeout);
+
+ // Remote Local candidate arrives.
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 2));
+ EXPECT_TRUE_WAIT(ch.connections().size() == 2, kDefaultTimeout);
+}
+
+// Test the ping sequence is UDP Relay/Relay followed by TCP Relay/Relay,
+// followed by the rest.
+TEST_P(P2PTransportChannelMostLikelyToWorkFirstTest, TestTcpTurn) {
+ // Add a Tcp Turn server.
+ turn_server()->AddInternalSocket(kTurnTcpIntAddr, PROTO_TCP);
+ RelayServerConfig config;
+ config.credentials = kRelayCredentials;
+ config.ports.push_back(ProtocolAddress(kTurnTcpIntAddr, PROTO_TCP));
+ allocator()->AddTurnServerForTesting(config);
+
+ P2PTransportChannel& ch = StartTransportChannel(true, 500, &field_trials_);
+ EXPECT_TRUE_WAIT(ch.ports().size() == 3, kDefaultTimeout);
+ EXPECT_EQ(ch.ports()[0]->Type(), LOCAL_PORT_TYPE);
+ EXPECT_EQ(ch.ports()[1]->Type(), RELAY_PORT_TYPE);
+ EXPECT_EQ(ch.ports()[2]->Type(), RELAY_PORT_TYPE);
+
+ // Remote Relay candidate arrives.
+ ch.AddRemoteCandidate(CreateUdpCandidate(RELAY_PORT_TYPE, "1.1.1.1", 1, 1));
+ EXPECT_TRUE_WAIT(ch.connections().size() == 3, kDefaultTimeout);
+
+ // UDP Relay/Relay should be pinged first.
+ VerifyNextPingableConnection(RELAY_PORT_TYPE, RELAY_PORT_TYPE);
+
+ // TCP Relay/Relay is the next.
+ VerifyNextPingableConnection(RELAY_PORT_TYPE, RELAY_PORT_TYPE,
+ TCP_PROTOCOL_NAME);
+
+ // Finally, Local/Relay will be pinged.
+ VerifyNextPingableConnection(LOCAL_PORT_TYPE, RELAY_PORT_TYPE);
+}
+
+class P2PTransportChannelResolverTest : public TestWithParam<std::string> {};
+
+INSTANTIATE_TEST_SUITE_P(Legacy, P2PTransportChannelResolverTest, Values(""));
+INSTANTIATE_TEST_SUITE_P(Active,
+ P2PTransportChannelResolverTest,
+ Values("WebRTC-UseActiveIceController/Enabled/"));
+
+// Test that a resolver is created, asked for a result, and destroyed
+// when the address is a hostname. The destruction should happen even
+// if the channel is not destroyed.
+TEST_P(P2PTransportChannelResolverTest, HostnameCandidateIsResolved) {
+ webrtc::test::ScopedKeyValueConfig field_trials(GetParam());
+ ResolverFactoryFixture resolver_fixture;
+ std::unique_ptr<rtc::SocketServer> socket_server =
+ rtc::CreateDefaultSocketServer();
+ rtc::AutoSocketServerThread main_thread(socket_server.get());
+ rtc::BasicPacketSocketFactory packet_socket_factory(socket_server.get());
+ FakePortAllocator allocator(rtc::Thread::Current(), &packet_socket_factory,
+ &field_trials);
+ webrtc::IceTransportInit init;
+ init.set_port_allocator(&allocator);
+ init.set_async_dns_resolver_factory(&resolver_fixture);
+ init.set_field_trials(&field_trials);
+ auto channel = P2PTransportChannel::Create("tn", 0, std::move(init));
+ Candidate hostname_candidate;
+ SocketAddress hostname_address("fake.test", 1000);
+ hostname_candidate.set_address(hostname_address);
+ channel->AddRemoteCandidate(hostname_candidate);
+
+ ASSERT_EQ_WAIT(1u, channel->remote_candidates().size(), kDefaultTimeout);
+ const RemoteCandidate& candidate = channel->remote_candidates()[0];
+ EXPECT_FALSE(candidate.address().IsUnresolvedIP());
+}
+
+// Test that if we signal a hostname candidate after the remote endpoint
+// discovers a prflx remote candidate with the same underlying IP address, the
+// prflx candidate is updated to a host candidate after the name resolution is
+// done.
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ PeerReflexiveCandidateBeforeSignalingWithMdnsName) {
+ // ep1 and ep2 will only gather host candidates with addresses
+ // kPublicAddrs[0] and kPublicAddrs[1], respectively.
+ ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts);
+ // ICE parameter will be set up when creating the channels.
+ set_remote_ice_parameter_source(FROM_SETICEPARAMETERS);
+ GetEndpoint(0)->network_manager_.set_mdns_responder(
+ std::make_unique<webrtc::FakeMdnsResponder>(rtc::Thread::Current()));
+
+ ResolverFactoryFixture resolver_fixture;
+ GetEndpoint(1)->async_dns_resolver_factory_ = &resolver_fixture;
+ CreateChannels();
+ // Pause sending candidates from both endpoints until we find out what port
+ // number is assgined to ep1's host candidate.
+ PauseCandidates(0);
+ PauseCandidates(1);
+ ASSERT_EQ_WAIT(1u, GetEndpoint(0)->saved_candidates_.size(), kMediumTimeout);
+ const auto& local_candidate = GetEndpoint(0)->saved_candidates_[0].candidate;
+ // The IP address of ep1's host candidate should be obfuscated.
+ EXPECT_TRUE(local_candidate.address().IsUnresolvedIP());
+ // This is the underlying private IP address of the same candidate at ep1.
+ const auto local_address = rtc::SocketAddress(
+ kPublicAddrs[0].ipaddr(), local_candidate.address().port());
+
+ // Let ep2 signal its candidate to ep1. ep1 should form a candidate
+ // pair and start to ping. After receiving the ping, ep2 discovers a prflx
+ // remote candidate and form a candidate pair as well.
+ ResumeCandidates(1);
+ ASSERT_TRUE_WAIT(ep1_ch1()->selected_connection() != nullptr, kMediumTimeout);
+ // ep2 should have the selected connection connected to the prflx remote
+ // candidate.
+ const Connection* selected_connection = nullptr;
+ ASSERT_TRUE_WAIT(
+ (selected_connection = ep2_ch1()->selected_connection()) != nullptr,
+ kMediumTimeout);
+ EXPECT_EQ(PRFLX_PORT_TYPE, selected_connection->remote_candidate().type());
+ EXPECT_EQ(kIceUfrag[0], selected_connection->remote_candidate().username());
+ EXPECT_EQ(kIcePwd[0], selected_connection->remote_candidate().password());
+ // Set expectation before ep1 signals a hostname candidate.
+ resolver_fixture.SetAddressToReturn(local_address);
+ ResumeCandidates(0);
+ // Verify ep2's selected connection is updated to use the 'local' candidate.
+ EXPECT_EQ_WAIT(LOCAL_PORT_TYPE,
+ ep2_ch1()->selected_connection()->remote_candidate().type(),
+ kMediumTimeout);
+ EXPECT_EQ(selected_connection, ep2_ch1()->selected_connection());
+
+ DestroyChannels();
+}
+
+// Test that if we discover a prflx candidate during the process of name
+// resolution for a remote hostname candidate, we update the prflx candidate to
+// a host candidate if the hostname candidate turns out to have the same IP
+// address after the resolution completes.
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ PeerReflexiveCandidateDuringResolvingHostCandidateWithMdnsName) {
+ ResolverFactoryFixture resolver_fixture;
+ // Prevent resolution until triggered by FireDelayedResolution.
+ resolver_fixture.DelayResolution();
+
+ // ep1 and ep2 will only gather host candidates with addresses
+ // kPublicAddrs[0] and kPublicAddrs[1], respectively.
+ ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts);
+ // ICE parameter will be set up when creating the channels.
+ set_remote_ice_parameter_source(FROM_SETICEPARAMETERS);
+ GetEndpoint(0)->network_manager_.set_mdns_responder(
+ std::make_unique<webrtc::FakeMdnsResponder>(rtc::Thread::Current()));
+ GetEndpoint(1)->async_dns_resolver_factory_ = &resolver_fixture;
+ CreateChannels();
+ // Pause sending candidates from both endpoints until we find out what port
+ // number is assgined to ep1's host candidate.
+ PauseCandidates(0);
+ PauseCandidates(1);
+
+ ASSERT_EQ_WAIT(1u, GetEndpoint(0)->saved_candidates_.size(), kMediumTimeout);
+ const auto& local_candidate = GetEndpoint(0)->saved_candidates_[0].candidate;
+ // The IP address of ep1's host candidate should be obfuscated.
+ ASSERT_TRUE(local_candidate.address().IsUnresolvedIP());
+ // This is the underlying private IP address of the same candidate at ep1.
+ const auto local_address = rtc::SocketAddress(
+ kPublicAddrs[0].ipaddr(), local_candidate.address().port());
+ // Let ep1 signal its hostname candidate to ep2.
+ ResumeCandidates(0);
+ // Now that ep2 is in the process of resolving the hostname candidate signaled
+ // by ep1. Let ep2 signal its host candidate with an IP address to ep1, so
+ // that ep1 can form a candidate pair, select it and start to ping ep2.
+ ResumeCandidates(1);
+ ASSERT_TRUE_WAIT(ep1_ch1()->selected_connection() != nullptr, kMediumTimeout);
+ // Let the mock resolver of ep2 receives the correct resolution.
+ resolver_fixture.SetAddressToReturn(local_address);
+
+ // Upon receiving a ping from ep1, ep2 adds a prflx candidate from the
+ // unknown address and establishes a connection.
+ //
+ // There is a caveat in our implementation associated with this expectation.
+ // See the big comment in P2PTransportChannel::OnUnknownAddress.
+ ASSERT_TRUE_WAIT(ep2_ch1()->selected_connection() != nullptr, kMediumTimeout);
+ EXPECT_EQ(PRFLX_PORT_TYPE,
+ ep2_ch1()->selected_connection()->remote_candidate().type());
+ // ep2 should also be able resolve the hostname candidate. The resolved remote
+ // host candidate should be merged with the prflx remote candidate.
+
+ resolver_fixture.FireDelayedResolution();
+
+ EXPECT_EQ_WAIT(LOCAL_PORT_TYPE,
+ ep2_ch1()->selected_connection()->remote_candidate().type(),
+ kMediumTimeout);
+ EXPECT_EQ(1u, ep2_ch1()->remote_candidates().size());
+
+ DestroyChannels();
+}
+
+// Test that if we only gather and signal a host candidate, the IP address of
+// which is obfuscated by an mDNS name, and if the peer can complete the name
+// resolution with the correct IP address, we can have a p2p connection.
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ CanConnectWithHostCandidateWithMdnsName) {
+ ResolverFactoryFixture resolver_fixture;
+
+ // ep1 and ep2 will only gather host candidates with addresses
+ // kPublicAddrs[0] and kPublicAddrs[1], respectively.
+ ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts);
+ // ICE parameter will be set up when creating the channels.
+ set_remote_ice_parameter_source(FROM_SETICEPARAMETERS);
+ GetEndpoint(0)->network_manager_.set_mdns_responder(
+ std::make_unique<webrtc::FakeMdnsResponder>(rtc::Thread::Current()));
+ GetEndpoint(1)->async_dns_resolver_factory_ = &resolver_fixture;
+ CreateChannels();
+ // Pause sending candidates from both endpoints until we find out what port
+ // number is assgined to ep1's host candidate.
+ PauseCandidates(0);
+ PauseCandidates(1);
+ ASSERT_EQ_WAIT(1u, GetEndpoint(0)->saved_candidates_.size(), kMediumTimeout);
+ const auto& local_candidate_ep1 =
+ GetEndpoint(0)->saved_candidates_[0].candidate;
+ // The IP address of ep1's host candidate should be obfuscated.
+ EXPECT_TRUE(local_candidate_ep1.address().IsUnresolvedIP());
+ // This is the underlying private IP address of the same candidate at ep1,
+ // and let the mock resolver of ep2 receive the correct resolution.
+ rtc::SocketAddress resolved_address_ep1(local_candidate_ep1.address());
+ resolved_address_ep1.SetResolvedIP(kPublicAddrs[0].ipaddr());
+
+ resolver_fixture.SetAddressToReturn(resolved_address_ep1);
+ // Let ep1 signal its hostname candidate to ep2.
+ ResumeCandidates(0);
+
+ // We should be able to receive a ping from ep2 and establish a connection
+ // with a peer reflexive candidate from ep2.
+ ASSERT_TRUE_WAIT((ep1_ch1()->selected_connection()) != nullptr,
+ kMediumTimeout);
+ EXPECT_EQ(LOCAL_PORT_TYPE,
+ ep1_ch1()->selected_connection()->local_candidate().type());
+ EXPECT_EQ(PRFLX_PORT_TYPE,
+ ep1_ch1()->selected_connection()->remote_candidate().type());
+
+ DestroyChannels();
+}
+
+// Test that when the IP of a host candidate is concealed by an mDNS name, the
+// stats from the gathering ICE endpoint do not reveal the address of this local
+// host candidate or the related address of a local srflx candidate from the
+// same endpoint. Also, the remote ICE endpoint that successfully resolves a
+// signaled host candidate with an mDNS name should not reveal the address of
+// this remote host candidate in stats.
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ CandidatesSanitizedInStatsWhenMdnsObfuscationEnabled) {
+ ResolverFactoryFixture resolver_fixture;
+
+ // ep1 and ep2 will gather host candidates with addresses
+ // kPublicAddrs[0] and kPublicAddrs[1], respectively. ep1 also gathers a srflx
+ // and a relay candidates.
+ ConfigureEndpoints(OPEN, OPEN,
+ kDefaultPortAllocatorFlags | PORTALLOCATOR_DISABLE_TCP,
+ kOnlyLocalPorts);
+ // ICE parameter will be set up when creating the channels.
+ set_remote_ice_parameter_source(FROM_SETICEPARAMETERS);
+ GetEndpoint(0)->network_manager_.set_mdns_responder(
+ std::make_unique<webrtc::FakeMdnsResponder>(rtc::Thread::Current()));
+ GetEndpoint(1)->async_dns_resolver_factory_ = &resolver_fixture;
+ CreateChannels();
+ // Pause sending candidates from both endpoints until we find out what port
+ // number is assigned to ep1's host candidate.
+ PauseCandidates(0);
+ PauseCandidates(1);
+ // Ep1 has a UDP host, a srflx and a relay candidates.
+ ASSERT_EQ_WAIT(3u, GetEndpoint(0)->saved_candidates_.size(), kMediumTimeout);
+ ASSERT_EQ_WAIT(1u, GetEndpoint(1)->saved_candidates_.size(), kMediumTimeout);
+
+ for (const auto& candidates_data : GetEndpoint(0)->saved_candidates_) {
+ const auto& local_candidate_ep1 = candidates_data.candidate;
+ if (local_candidate_ep1.type() == LOCAL_PORT_TYPE) {
+ // This is the underlying private IP address of the same candidate at ep1,
+ // and let the mock resolver of ep2 receive the correct resolution.
+ rtc::SocketAddress resolved_address_ep1(local_candidate_ep1.address());
+ resolved_address_ep1.SetResolvedIP(kPublicAddrs[0].ipaddr());
+ resolver_fixture.SetAddressToReturn(resolved_address_ep1);
+ break;
+ }
+ }
+ ResumeCandidates(0);
+ ResumeCandidates(1);
+
+ ASSERT_EQ_WAIT(kIceGatheringComplete, ep1_ch1()->gathering_state(),
+ kMediumTimeout);
+ // We should have the following candidate pairs on both endpoints:
+ // ep1_host <-> ep2_host, ep1_srflx <-> ep2_host, ep1_relay <-> ep2_host
+ ASSERT_EQ_WAIT(3u, ep1_ch1()->connections().size(), kMediumTimeout);
+ ASSERT_EQ_WAIT(3u, ep2_ch1()->connections().size(), kMediumTimeout);
+
+ IceTransportStats ice_transport_stats1;
+ IceTransportStats ice_transport_stats2;
+ ep1_ch1()->GetStats(&ice_transport_stats1);
+ ep2_ch1()->GetStats(&ice_transport_stats2);
+ EXPECT_EQ(3u, ice_transport_stats1.connection_infos.size());
+ EXPECT_EQ(3u, ice_transport_stats1.candidate_stats_list.size());
+ EXPECT_EQ(3u, ice_transport_stats2.connection_infos.size());
+ // Check the stats of ep1 seen by ep1.
+ for (const auto& connection_info : ice_transport_stats1.connection_infos) {
+ const auto& local_candidate = connection_info.local_candidate;
+ if (local_candidate.type() == LOCAL_PORT_TYPE) {
+ EXPECT_TRUE(local_candidate.address().IsUnresolvedIP());
+ } else if (local_candidate.type() == STUN_PORT_TYPE) {
+ EXPECT_TRUE(local_candidate.related_address().IsAnyIP());
+ } else if (local_candidate.type() == RELAY_PORT_TYPE) {
+ // The related address of the relay candidate should be equal to the
+ // srflx address. Note that NAT is not configured, hence the following
+ // expectation.
+ EXPECT_EQ(kPublicAddrs[0].ipaddr(),
+ local_candidate.related_address().ipaddr());
+ } else {
+ FAIL();
+ }
+ }
+ // Check the stats of ep1 seen by ep2.
+ for (const auto& connection_info : ice_transport_stats2.connection_infos) {
+ const auto& remote_candidate = connection_info.remote_candidate;
+ if (remote_candidate.type() == LOCAL_PORT_TYPE) {
+ EXPECT_TRUE(remote_candidate.address().IsUnresolvedIP());
+ } else if (remote_candidate.type() == STUN_PORT_TYPE) {
+ EXPECT_TRUE(remote_candidate.related_address().IsAnyIP());
+ } else if (remote_candidate.type() == RELAY_PORT_TYPE) {
+ EXPECT_EQ(kPublicAddrs[0].ipaddr(),
+ remote_candidate.related_address().ipaddr());
+ } else {
+ FAIL();
+ }
+ }
+ DestroyChannels();
+}
+
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ ConnectingIncreasesSelectedCandidatePairChanges) {
+ rtc::ScopedFakeClock clock;
+ ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags,
+ kDefaultPortAllocatorFlags);
+ CreateChannels();
+
+ IceTransportStats ice_transport_stats;
+ ASSERT_TRUE(ep1_ch1()->GetStats(&ice_transport_stats));
+ EXPECT_EQ(0u, ice_transport_stats.selected_candidate_pair_changes);
+
+ // Let the channels connect.
+ EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->selected_connection() != nullptr,
+ kMediumTimeout, clock);
+
+ ASSERT_TRUE(ep1_ch1()->GetStats(&ice_transport_stats));
+ EXPECT_EQ(1u, ice_transport_stats.selected_candidate_pair_changes);
+
+ DestroyChannels();
+}
+
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ DisconnectedIncreasesSelectedCandidatePairChanges) {
+ rtc::ScopedFakeClock clock;
+ ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags,
+ kDefaultPortAllocatorFlags);
+ CreateChannels();
+
+ IceTransportStats ice_transport_stats;
+ ASSERT_TRUE(ep1_ch1()->GetStats(&ice_transport_stats));
+ EXPECT_EQ(0u, ice_transport_stats.selected_candidate_pair_changes);
+
+ // Let the channels connect.
+ EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->selected_connection() != nullptr,
+ kMediumTimeout, clock);
+
+ ASSERT_TRUE(ep1_ch1()->GetStats(&ice_transport_stats));
+ EXPECT_EQ(1u, ice_transport_stats.selected_candidate_pair_changes);
+
+ // Prune connections and wait for disconnect.
+ for (Connection* con : ep1_ch1()->connections()) {
+ con->Prune();
+ }
+ EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->selected_connection() == nullptr,
+ kMediumTimeout, clock);
+
+ ASSERT_TRUE(ep1_ch1()->GetStats(&ice_transport_stats));
+ EXPECT_EQ(2u, ice_transport_stats.selected_candidate_pair_changes);
+
+ DestroyChannels();
+}
+
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ NewSelectionIncreasesSelectedCandidatePairChanges) {
+ rtc::ScopedFakeClock clock;
+ ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags,
+ kDefaultPortAllocatorFlags);
+ CreateChannels();
+
+ IceTransportStats ice_transport_stats;
+ ASSERT_TRUE(ep1_ch1()->GetStats(&ice_transport_stats));
+ EXPECT_EQ(0u, ice_transport_stats.selected_candidate_pair_changes);
+
+ // Let the channels connect.
+ EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->selected_connection() != nullptr,
+ kMediumTimeout, clock);
+
+ ASSERT_TRUE(ep1_ch1()->GetStats(&ice_transport_stats));
+ EXPECT_EQ(1u, ice_transport_stats.selected_candidate_pair_changes);
+
+ // Prune the currently selected connection and wait for selection
+ // of a new one.
+ const Connection* selected_connection = ep1_ch1()->selected_connection();
+ for (Connection* con : ep1_ch1()->connections()) {
+ if (con == selected_connection) {
+ con->Prune();
+ }
+ }
+ EXPECT_TRUE_SIMULATED_WAIT(
+ ep1_ch1()->selected_connection() != nullptr &&
+ (ep1_ch1()->GetStats(&ice_transport_stats),
+ ice_transport_stats.selected_candidate_pair_changes >= 2u),
+ kMediumTimeout, clock);
+
+ ASSERT_TRUE(ep1_ch1()->GetStats(&ice_transport_stats));
+ EXPECT_GE(ice_transport_stats.selected_candidate_pair_changes, 2u);
+
+ DestroyChannels();
+}
+
+// A similar test as above to check the selected candidate pair is sanitized
+// when it is queried via GetSelectedCandidatePair.
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ SelectedCandidatePairSanitizedWhenMdnsObfuscationEnabled) {
+ ResolverFactoryFixture resolver_fixture;
+
+ // ep1 and ep2 will gather host candidates with addresses
+ // kPublicAddrs[0] and kPublicAddrs[1], respectively.
+ ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts);
+ // ICE parameter will be set up when creating the channels.
+ set_remote_ice_parameter_source(FROM_SETICEPARAMETERS);
+ GetEndpoint(0)->network_manager_.set_mdns_responder(
+ std::make_unique<webrtc::FakeMdnsResponder>(rtc::Thread::Current()));
+ GetEndpoint(1)->async_dns_resolver_factory_ = &resolver_fixture;
+ CreateChannels();
+ // Pause sending candidates from both endpoints until we find out what port
+ // number is assigned to ep1's host candidate.
+ PauseCandidates(0);
+ PauseCandidates(1);
+ ASSERT_EQ_WAIT(1u, GetEndpoint(0)->saved_candidates_.size(), kMediumTimeout);
+ const auto& candidates_data = GetEndpoint(0)->saved_candidates_[0];
+ const auto& local_candidate_ep1 = candidates_data.candidate;
+ ASSERT_TRUE(local_candidate_ep1.type() == LOCAL_PORT_TYPE);
+ // This is the underlying private IP address of the same candidate at ep1,
+ // and let the mock resolver of ep2 receive the correct resolution.
+ rtc::SocketAddress resolved_address_ep1(local_candidate_ep1.address());
+ resolved_address_ep1.SetResolvedIP(kPublicAddrs[0].ipaddr());
+ resolver_fixture.SetAddressToReturn(resolved_address_ep1);
+
+ ResumeCandidates(0);
+ ResumeCandidates(1);
+
+ ASSERT_TRUE_WAIT(ep1_ch1()->selected_connection() != nullptr &&
+ ep2_ch1()->selected_connection() != nullptr,
+ kMediumTimeout);
+
+ const auto pair_ep1 = ep1_ch1()->GetSelectedCandidatePair();
+ ASSERT_TRUE(pair_ep1.has_value());
+ EXPECT_EQ(LOCAL_PORT_TYPE, pair_ep1->local_candidate().type());
+ EXPECT_TRUE(pair_ep1->local_candidate().address().IsUnresolvedIP());
+
+ const auto pair_ep2 = ep2_ch1()->GetSelectedCandidatePair();
+ ASSERT_TRUE(pair_ep2.has_value());
+ EXPECT_EQ(LOCAL_PORT_TYPE, pair_ep2->remote_candidate().type());
+ EXPECT_TRUE(pair_ep2->remote_candidate().address().IsUnresolvedIP());
+
+ DestroyChannels();
+}
+
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ NoPairOfLocalRelayCandidateWithRemoteMdnsCandidate) {
+ const int kOnlyRelayPorts = cricket::PORTALLOCATOR_DISABLE_UDP |
+ cricket::PORTALLOCATOR_DISABLE_STUN |
+ cricket::PORTALLOCATOR_DISABLE_TCP;
+ // We use one endpoint to test the behavior of adding remote candidates, and
+ // this endpoint only gathers relay candidates.
+ ConfigureEndpoints(OPEN, OPEN, kOnlyRelayPorts, kDefaultPortAllocatorFlags);
+ GetEndpoint(0)->cd1_.ch_ = CreateChannel(0, ICE_CANDIDATE_COMPONENT_DEFAULT,
+ kIceParams[0], kIceParams[1]);
+ IceConfig config;
+ // Start gathering and we should have only a single relay port.
+ ep1_ch1()->SetIceConfig(config);
+ ep1_ch1()->MaybeStartGathering();
+ EXPECT_EQ_WAIT(IceGatheringState::kIceGatheringComplete,
+ ep1_ch1()->gathering_state(), kDefaultTimeout);
+ EXPECT_EQ(1u, ep1_ch1()->ports().size());
+ // Add a plain remote host candidate and three remote mDNS candidates with the
+ // host, srflx and relay types. Note that the candidates differ in their
+ // ports.
+ cricket::Candidate host_candidate = CreateUdpCandidate(
+ LOCAL_PORT_TYPE, "1.1.1.1", 1 /* port */, 0 /* priority */);
+ ep1_ch1()->AddRemoteCandidate(host_candidate);
+
+ std::vector<cricket::Candidate> mdns_candidates;
+ mdns_candidates.push_back(CreateUdpCandidate(LOCAL_PORT_TYPE, "example.local",
+ 2 /* port */, 0 /* priority */));
+ mdns_candidates.push_back(CreateUdpCandidate(STUN_PORT_TYPE, "example.local",
+ 3 /* port */, 0 /* priority */));
+ mdns_candidates.push_back(CreateUdpCandidate(RELAY_PORT_TYPE, "example.local",
+ 4 /* port */, 0 /* priority */));
+ // We just resolve the hostname to 1.1.1.1, and add the candidates with this
+ // address directly to simulate the process of adding remote candidates with
+ // the name resolution.
+ for (auto& mdns_candidate : mdns_candidates) {
+ rtc::SocketAddress resolved_address(mdns_candidate.address());
+ resolved_address.SetResolvedIP(0x1111); // 1.1.1.1
+ mdns_candidate.set_address(resolved_address);
+ EXPECT_FALSE(mdns_candidate.address().IsUnresolvedIP());
+ ep1_ch1()->AddRemoteCandidate(mdns_candidate);
+ }
+
+ // All remote candidates should have been successfully added.
+ EXPECT_EQ(4u, ep1_ch1()->remote_candidates().size());
+
+ // Expect that there is no connection paired with any mDNS candidate.
+ ASSERT_EQ(1u, ep1_ch1()->connections().size());
+ ASSERT_NE(nullptr, ep1_ch1()->connections()[0]);
+ EXPECT_EQ(
+ "1.1.1.1:1",
+ ep1_ch1()->connections()[0]->remote_candidate().address().ToString());
+ DestroyChannels();
+}
+
+class MockMdnsResponder : public webrtc::MdnsResponderInterface {
+ public:
+ MOCK_METHOD(void,
+ CreateNameForAddress,
+ (const rtc::IPAddress&, NameCreatedCallback),
+ (override));
+ MOCK_METHOD(void,
+ RemoveNameForAddress,
+ (const rtc::IPAddress&, NameRemovedCallback),
+ (override));
+};
+
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ SrflxCandidateCanBeGatheredBeforeMdnsCandidateToCreateConnection) {
+ // ep1 and ep2 will only gather host and srflx candidates with base addresses
+ // kPublicAddrs[0] and kPublicAddrs[1], respectively, and we use a shared
+ // socket in gathering.
+ const auto kOnlyLocalAndStunPorts =
+ cricket::PORTALLOCATOR_DISABLE_RELAY |
+ cricket::PORTALLOCATOR_DISABLE_TCP |
+ cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET;
+ // ep1 is configured with a NAT so that we do gather a srflx candidate.
+ ConfigureEndpoints(NAT_FULL_CONE, OPEN, kOnlyLocalAndStunPorts,
+ kOnlyLocalAndStunPorts);
+ // ICE parameter will be set up when creating the channels.
+ set_remote_ice_parameter_source(FROM_SETICEPARAMETERS);
+ // Use a mock mDNS responder, which does not complete the name registration by
+ // ignoring the completion callback.
+ auto mock_mdns_responder = std::make_unique<MockMdnsResponder>();
+ EXPECT_CALL(*mock_mdns_responder, CreateNameForAddress(_, _))
+ .Times(1)
+ .WillOnce(Return());
+ GetEndpoint(0)->network_manager_.set_mdns_responder(
+ std::move(mock_mdns_responder));
+
+ CreateChannels();
+
+ // We should be able to form a srflx-host connection to ep2.
+ ASSERT_TRUE_WAIT((ep1_ch1()->selected_connection()) != nullptr,
+ kMediumTimeout);
+ EXPECT_EQ(STUN_PORT_TYPE,
+ ep1_ch1()->selected_connection()->local_candidate().type());
+ EXPECT_EQ(LOCAL_PORT_TYPE,
+ ep1_ch1()->selected_connection()->remote_candidate().type());
+
+ DestroyChannels();
+}
+
+// Test that after changing the candidate filter from relay-only to allowing all
+// types of candidates when doing continual gathering, we can gather without ICE
+// restart the other types of candidates that are now enabled and form candidate
+// pairs. Also, we verify that the relay candidates gathered previously are not
+// removed and are still usable for necessary route switching.
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ SurfaceHostCandidateOnCandidateFilterChangeFromRelayToAll) {
+ rtc::ScopedFakeClock clock;
+
+ ConfigureEndpoints(
+ OPEN, OPEN,
+ kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_SHARED_SOCKET,
+ kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_SHARED_SOCKET);
+ auto* ep1 = GetEndpoint(0);
+ auto* ep2 = GetEndpoint(1);
+ ep1->allocator_->SetCandidateFilter(CF_RELAY);
+ ep2->allocator_->SetCandidateFilter(CF_RELAY);
+ // Enable continual gathering and also resurfacing gathered candidates upon
+ // the candidate filter changed in the ICE configuration.
+ IceConfig ice_config = CreateIceConfig(1000, GATHER_CONTINUALLY);
+ ice_config.surface_ice_candidates_on_ice_transport_type_changed = true;
+ CreateChannels(ice_config, ice_config);
+ ASSERT_TRUE_SIMULATED_WAIT(ep1_ch1()->selected_connection() != nullptr,
+ kDefaultTimeout, clock);
+ ASSERT_TRUE_SIMULATED_WAIT(ep2_ch1()->selected_connection() != nullptr,
+ kDefaultTimeout, clock);
+ EXPECT_EQ(RELAY_PORT_TYPE,
+ ep1_ch1()->selected_connection()->local_candidate().type());
+ EXPECT_EQ(RELAY_PORT_TYPE,
+ ep2_ch1()->selected_connection()->local_candidate().type());
+
+ // Loosen the candidate filter at ep1.
+ ep1->allocator_->SetCandidateFilter(CF_ALL);
+ EXPECT_TRUE_SIMULATED_WAIT(
+ ep1_ch1()->selected_connection() != nullptr &&
+ ep1_ch1()->selected_connection()->local_candidate().type() ==
+ LOCAL_PORT_TYPE,
+ kDefaultTimeout, clock);
+ EXPECT_EQ(RELAY_PORT_TYPE,
+ ep1_ch1()->selected_connection()->remote_candidate().type());
+
+ // Loosen the candidate filter at ep2.
+ ep2->allocator_->SetCandidateFilter(CF_ALL);
+ EXPECT_TRUE_SIMULATED_WAIT(
+ ep2_ch1()->selected_connection() != nullptr &&
+ ep2_ch1()->selected_connection()->local_candidate().type() ==
+ LOCAL_PORT_TYPE,
+ kDefaultTimeout, clock);
+ // We have migrated to a host-host candidate pair.
+ EXPECT_EQ(LOCAL_PORT_TYPE,
+ ep2_ch1()->selected_connection()->remote_candidate().type());
+
+ // Block the traffic over non-relay-to-relay routes and expect a route change.
+ fw()->AddRule(false, rtc::FP_ANY, kPublicAddrs[0], kPublicAddrs[1]);
+ fw()->AddRule(false, rtc::FP_ANY, kPublicAddrs[1], kPublicAddrs[0]);
+ fw()->AddRule(false, rtc::FP_ANY, kPublicAddrs[0], kTurnUdpExtAddr);
+ fw()->AddRule(false, rtc::FP_ANY, kPublicAddrs[1], kTurnUdpExtAddr);
+
+ // We should be able to reuse the previously gathered relay candidates.
+ EXPECT_EQ_SIMULATED_WAIT(
+ RELAY_PORT_TYPE,
+ ep1_ch1()->selected_connection()->local_candidate().type(),
+ kDefaultTimeout, clock);
+ EXPECT_EQ(RELAY_PORT_TYPE,
+ ep1_ch1()->selected_connection()->remote_candidate().type());
+ DestroyChannels();
+}
+
+// A similar test as SurfaceHostCandidateOnCandidateFilterChangeFromRelayToAll,
+// and we should surface server-reflexive candidates that are enabled after
+// changing the candidate filter.
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ SurfaceSrflxCandidateOnCandidateFilterChangeFromRelayToNoHost) {
+ rtc::ScopedFakeClock clock;
+ // We need an actual NAT so that the host candidate is not equivalent to the
+ // srflx candidate; otherwise, the host candidate would still surface even
+ // though we disable it via the candidate filter below. This is a result of
+ // the following limitation in the current implementation:
+ // 1. We don't generate the srflx candidate when we have public IP.
+ // 2. We keep the host candidate in this case in CheckCandidateFilter even
+ // though we intend to filter them.
+ ConfigureEndpoints(
+ NAT_FULL_CONE, NAT_FULL_CONE,
+ kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_SHARED_SOCKET,
+ kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_SHARED_SOCKET);
+ auto* ep1 = GetEndpoint(0);
+ auto* ep2 = GetEndpoint(1);
+ ep1->allocator_->SetCandidateFilter(CF_RELAY);
+ ep2->allocator_->SetCandidateFilter(CF_RELAY);
+ // Enable continual gathering and also resurfacing gathered candidates upon
+ // the candidate filter changed in the ICE configuration.
+ IceConfig ice_config = CreateIceConfig(1000, GATHER_CONTINUALLY);
+ ice_config.surface_ice_candidates_on_ice_transport_type_changed = true;
+ CreateChannels(ice_config, ice_config);
+ ASSERT_TRUE_SIMULATED_WAIT(ep1_ch1()->selected_connection() != nullptr,
+ kDefaultTimeout, clock);
+ ASSERT_TRUE_SIMULATED_WAIT(ep2_ch1()->selected_connection() != nullptr,
+ kDefaultTimeout, clock);
+ const uint32_t kCandidateFilterNoHost = CF_ALL & ~CF_HOST;
+ // Loosen the candidate filter at ep1.
+ ep1->allocator_->SetCandidateFilter(kCandidateFilterNoHost);
+ EXPECT_TRUE_SIMULATED_WAIT(
+ ep1_ch1()->selected_connection() != nullptr &&
+ ep1_ch1()->selected_connection()->local_candidate().type() ==
+ STUN_PORT_TYPE,
+ kDefaultTimeout, clock);
+ EXPECT_EQ(RELAY_PORT_TYPE,
+ ep1_ch1()->selected_connection()->remote_candidate().type());
+
+ // Loosen the candidate filter at ep2.
+ ep2->allocator_->SetCandidateFilter(kCandidateFilterNoHost);
+ EXPECT_TRUE_SIMULATED_WAIT(
+ ep2_ch1()->selected_connection() != nullptr &&
+ ep2_ch1()->selected_connection()->local_candidate().type() ==
+ STUN_PORT_TYPE,
+ kDefaultTimeout, clock);
+ // We have migrated to a srflx-srflx candidate pair.
+ EXPECT_EQ(STUN_PORT_TYPE,
+ ep2_ch1()->selected_connection()->remote_candidate().type());
+
+ // Block the traffic over non-relay-to-relay routes and expect a route change.
+ fw()->AddRule(false, rtc::FP_ANY, kPrivateAddrs[0], kPublicAddrs[1]);
+ fw()->AddRule(false, rtc::FP_ANY, kPrivateAddrs[1], kPublicAddrs[0]);
+ fw()->AddRule(false, rtc::FP_ANY, kPrivateAddrs[0], kTurnUdpExtAddr);
+ fw()->AddRule(false, rtc::FP_ANY, kPrivateAddrs[1], kTurnUdpExtAddr);
+ // We should be able to reuse the previously gathered relay candidates.
+ EXPECT_EQ_SIMULATED_WAIT(
+ RELAY_PORT_TYPE,
+ ep1_ch1()->selected_connection()->local_candidate().type(),
+ kDefaultTimeout, clock);
+ EXPECT_EQ(RELAY_PORT_TYPE,
+ ep1_ch1()->selected_connection()->remote_candidate().type());
+ DestroyChannels();
+}
+
+// This is the complement to
+// SurfaceHostCandidateOnCandidateFilterChangeFromRelayToAll, and instead of
+// gathering continually we only gather once, which makes the config
+// `surface_ice_candidates_on_ice_transport_type_changed` ineffective after the
+// gathering stopped.
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ CannotSurfaceTheNewlyAllowedOnFilterChangeIfNotGatheringContinually) {
+ rtc::ScopedFakeClock clock;
+
+ ConfigureEndpoints(
+ OPEN, OPEN,
+ kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_SHARED_SOCKET,
+ kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_SHARED_SOCKET);
+ auto* ep1 = GetEndpoint(0);
+ auto* ep2 = GetEndpoint(1);
+ ep1->allocator_->SetCandidateFilter(CF_RELAY);
+ ep2->allocator_->SetCandidateFilter(CF_RELAY);
+ // Only gather once.
+ IceConfig ice_config = CreateIceConfig(1000, GATHER_ONCE);
+ ice_config.surface_ice_candidates_on_ice_transport_type_changed = true;
+ CreateChannels(ice_config, ice_config);
+ ASSERT_TRUE_SIMULATED_WAIT(ep1_ch1()->selected_connection() != nullptr,
+ kDefaultTimeout, clock);
+ ASSERT_TRUE_SIMULATED_WAIT(ep2_ch1()->selected_connection() != nullptr,
+ kDefaultTimeout, clock);
+ // Loosen the candidate filter at ep1.
+ ep1->allocator_->SetCandidateFilter(CF_ALL);
+ // Wait for a period for any potential surfacing of new candidates.
+ SIMULATED_WAIT(false, kDefaultTimeout, clock);
+ EXPECT_EQ(RELAY_PORT_TYPE,
+ ep1_ch1()->selected_connection()->local_candidate().type());
+
+ // Loosen the candidate filter at ep2.
+ ep2->allocator_->SetCandidateFilter(CF_ALL);
+ EXPECT_EQ(RELAY_PORT_TYPE,
+ ep2_ch1()->selected_connection()->local_candidate().type());
+ DestroyChannels();
+}
+
+// Test that when the candidate filter is updated to be more restrictive,
+// candidates that 1) have already been gathered and signaled 2) but no longer
+// match the filter, are not removed.
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ RestrictingCandidateFilterDoesNotRemoveRegatheredCandidates) {
+ rtc::ScopedFakeClock clock;
+
+ ConfigureEndpoints(
+ OPEN, OPEN,
+ kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_SHARED_SOCKET,
+ kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_SHARED_SOCKET);
+ auto* ep1 = GetEndpoint(0);
+ auto* ep2 = GetEndpoint(1);
+ ep1->allocator_->SetCandidateFilter(CF_ALL);
+ ep2->allocator_->SetCandidateFilter(CF_ALL);
+ // Enable continual gathering and also resurfacing gathered candidates upon
+ // the candidate filter changed in the ICE configuration.
+ IceConfig ice_config = CreateIceConfig(1000, GATHER_CONTINUALLY);
+ ice_config.surface_ice_candidates_on_ice_transport_type_changed = true;
+ // Pause candidates so we can gather all types of candidates. See
+ // P2PTransportChannel::OnConnectionStateChange, where we would stop the
+ // gathering when we have a strongly connected candidate pair.
+ PauseCandidates(0);
+ PauseCandidates(1);
+ CreateChannels(ice_config, ice_config);
+
+ // We have gathered host, srflx and relay candidates.
+ EXPECT_TRUE_SIMULATED_WAIT(ep1->saved_candidates_.size() == 3u,
+ kDefaultTimeout, clock);
+ ResumeCandidates(0);
+ ResumeCandidates(1);
+ ASSERT_TRUE_SIMULATED_WAIT(
+ ep1_ch1()->selected_connection() != nullptr &&
+ LOCAL_PORT_TYPE ==
+ ep1_ch1()->selected_connection()->local_candidate().type() &&
+ ep2_ch1()->selected_connection() != nullptr &&
+ LOCAL_PORT_TYPE ==
+ ep1_ch1()->selected_connection()->remote_candidate().type(),
+ kDefaultTimeout, clock);
+ ASSERT_TRUE_SIMULATED_WAIT(ep2_ch1()->selected_connection() != nullptr,
+ kDefaultTimeout, clock);
+ // Test that we have a host-host candidate pair selected and the number of
+ // candidates signaled to the remote peer stays the same.
+ auto test_invariants = [this]() {
+ EXPECT_EQ(LOCAL_PORT_TYPE,
+ ep1_ch1()->selected_connection()->local_candidate().type());
+ EXPECT_EQ(LOCAL_PORT_TYPE,
+ ep1_ch1()->selected_connection()->remote_candidate().type());
+ EXPECT_THAT(ep2_ch1()->remote_candidates(), SizeIs(3));
+ };
+
+ test_invariants();
+
+ // Set a more restrictive candidate filter at ep1.
+ ep1->allocator_->SetCandidateFilter(CF_HOST | CF_REFLEXIVE);
+ SIMULATED_WAIT(false, kDefaultTimeout, clock);
+ test_invariants();
+
+ ep1->allocator_->SetCandidateFilter(CF_HOST);
+ SIMULATED_WAIT(false, kDefaultTimeout, clock);
+ test_invariants();
+
+ ep1->allocator_->SetCandidateFilter(CF_NONE);
+ SIMULATED_WAIT(false, kDefaultTimeout, clock);
+ test_invariants();
+ DestroyChannels();
+}
+
+// Verify that things break unless
+// - both parties use the surface_ice_candidates_on_ice_transport_type_changed
+// - both parties loosen candidate filter at the same time (approx.).
+//
+// i.e surface_ice_candidates_on_ice_transport_type_changed requires
+// coordination outside of webrtc to function properly.
+TEST_P(P2PTransportChannelTestWithFieldTrials, SurfaceRequiresCoordination) {
+ webrtc::test::ScopedKeyValueConfig field_trials(
+ field_trials_,
+ "WebRTC-IceFieldTrials/skip_relay_to_non_relay_connections:true/");
+ rtc::ScopedFakeClock clock;
+
+ ConfigureEndpoints(
+ OPEN, OPEN,
+ kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_SHARED_SOCKET,
+ kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_SHARED_SOCKET);
+ auto* ep1 = GetEndpoint(0);
+ auto* ep2 = GetEndpoint(1);
+ ep1->allocator_->SetCandidateFilter(CF_RELAY);
+ ep2->allocator_->SetCandidateFilter(CF_ALL);
+ // Enable continual gathering and also resurfacing gathered candidates upon
+ // the candidate filter changed in the ICE configuration.
+ IceConfig ice_config = CreateIceConfig(1000, GATHER_CONTINUALLY);
+ ice_config.surface_ice_candidates_on_ice_transport_type_changed = true;
+ // Pause candidates gathering so we can gather all types of candidates. See
+ // P2PTransportChannel::OnConnectionStateChange, where we would stop the
+ // gathering when we have a strongly connected candidate pair.
+ PauseCandidates(0);
+ PauseCandidates(1);
+ CreateChannels(ice_config, ice_config);
+
+ // On the caller we only have relay,
+ // on the callee we have host, srflx and relay.
+ EXPECT_TRUE_SIMULATED_WAIT(ep1->saved_candidates_.size() == 1u,
+ kDefaultTimeout, clock);
+ EXPECT_TRUE_SIMULATED_WAIT(ep2->saved_candidates_.size() == 3u,
+ kDefaultTimeout, clock);
+
+ ResumeCandidates(0);
+ ResumeCandidates(1);
+ ASSERT_TRUE_SIMULATED_WAIT(
+ ep1_ch1()->selected_connection() != nullptr &&
+ RELAY_PORT_TYPE ==
+ ep1_ch1()->selected_connection()->local_candidate().type() &&
+ ep2_ch1()->selected_connection() != nullptr &&
+ RELAY_PORT_TYPE ==
+ ep1_ch1()->selected_connection()->remote_candidate().type(),
+ kDefaultTimeout, clock);
+ ASSERT_TRUE_SIMULATED_WAIT(ep2_ch1()->selected_connection() != nullptr,
+ kDefaultTimeout, clock);
+
+ // Wait until the callee discards it's candidates
+ // since they don't manage to connect.
+ SIMULATED_WAIT(false, 300000, clock);
+
+ // And then loosen caller candidate filter.
+ ep1->allocator_->SetCandidateFilter(CF_ALL);
+
+ SIMULATED_WAIT(false, kDefaultTimeout, clock);
+
+ // No p2p connection will be made, it will remain on relay.
+ EXPECT_TRUE(ep1_ch1()->selected_connection() != nullptr &&
+ RELAY_PORT_TYPE ==
+ ep1_ch1()->selected_connection()->local_candidate().type() &&
+ ep2_ch1()->selected_connection() != nullptr &&
+ RELAY_PORT_TYPE ==
+ ep1_ch1()->selected_connection()->remote_candidate().type());
+
+ DestroyChannels();
+}
+
+TEST_P(P2PTransportChannelPingTest, TestInitialSelectDampening0) {
+ webrtc::test::ScopedKeyValueConfig field_trials(
+ field_trials_, "WebRTC-IceFieldTrials/initial_select_dampening:0/");
+
+ constexpr int kMargin = 10;
+ rtc::ScopedFakeClock clock;
+ clock.AdvanceTime(webrtc::TimeDelta::Seconds(1));
+
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("test channel", 1, &pa, &field_trials);
+ PrepareChannel(&ch);
+ ch.SetIceConfig(ch.config());
+ ch.MaybeStartGathering();
+
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 100));
+ Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1, &clock);
+ ASSERT_TRUE(conn1 != nullptr);
+ EXPECT_EQ(nullptr, ch.selected_connection());
+ conn1->ReceivedPingResponse(LOW_RTT, "id"); // Becomes writable and receiving
+ // It shall not be selected until 0ms has passed....i.e it should be connected
+ // directly.
+ EXPECT_EQ_SIMULATED_WAIT(conn1, ch.selected_connection(), kMargin, clock);
+}
+
+TEST_P(P2PTransportChannelPingTest, TestInitialSelectDampening) {
+ webrtc::test::ScopedKeyValueConfig field_trials(
+ field_trials_, "WebRTC-IceFieldTrials/initial_select_dampening:100/");
+
+ constexpr int kMargin = 10;
+ rtc::ScopedFakeClock clock;
+ clock.AdvanceTime(webrtc::TimeDelta::Seconds(1));
+
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("test channel", 1, &pa, &field_trials);
+ PrepareChannel(&ch);
+ ch.SetIceConfig(ch.config());
+ ch.MaybeStartGathering();
+
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 100));
+ Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1, &clock);
+ ASSERT_TRUE(conn1 != nullptr);
+ EXPECT_EQ(nullptr, ch.selected_connection());
+ conn1->ReceivedPingResponse(LOW_RTT, "id"); // Becomes writable and receiving
+ // It shall not be selected until 100ms has passed.
+ SIMULATED_WAIT(conn1 == ch.selected_connection(), 100 - kMargin, clock);
+ EXPECT_EQ_SIMULATED_WAIT(conn1, ch.selected_connection(), 2 * kMargin, clock);
+}
+
+TEST_P(P2PTransportChannelPingTest, TestInitialSelectDampeningPingReceived) {
+ webrtc::test::ScopedKeyValueConfig field_trials(
+ field_trials_,
+ "WebRTC-IceFieldTrials/initial_select_dampening_ping_received:100/");
+
+ constexpr int kMargin = 10;
+ rtc::ScopedFakeClock clock;
+ clock.AdvanceTime(webrtc::TimeDelta::Seconds(1));
+
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("test channel", 1, &pa, &field_trials);
+ PrepareChannel(&ch);
+ ch.SetIceConfig(ch.config());
+ ch.MaybeStartGathering();
+
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 100));
+ Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1, &clock);
+ ASSERT_TRUE(conn1 != nullptr);
+ EXPECT_EQ(nullptr, ch.selected_connection());
+ conn1->ReceivedPingResponse(LOW_RTT, "id"); // Becomes writable and receiving
+ conn1->ReceivedPing("id1"); //
+ // It shall not be selected until 100ms has passed.
+ SIMULATED_WAIT(conn1 == ch.selected_connection(), 100 - kMargin, clock);
+ EXPECT_EQ_SIMULATED_WAIT(conn1, ch.selected_connection(), 2 * kMargin, clock);
+}
+
+TEST_P(P2PTransportChannelPingTest, TestInitialSelectDampeningBoth) {
+ webrtc::test::ScopedKeyValueConfig field_trials(
+ field_trials_,
+ "WebRTC-IceFieldTrials/"
+ "initial_select_dampening:100,initial_select_dampening_ping_received:"
+ "50/");
+
+ constexpr int kMargin = 10;
+ rtc::ScopedFakeClock clock;
+ clock.AdvanceTime(webrtc::TimeDelta::Seconds(1));
+
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ P2PTransportChannel ch("test channel", 1, &pa, &field_trials);
+ PrepareChannel(&ch);
+ ch.SetIceConfig(ch.config());
+ ch.MaybeStartGathering();
+
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 100));
+ Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1, &clock);
+ ASSERT_TRUE(conn1 != nullptr);
+ EXPECT_EQ(nullptr, ch.selected_connection());
+ conn1->ReceivedPingResponse(LOW_RTT, "id"); // Becomes writable and receiving
+ // It shall not be selected until 100ms has passed....but only wait ~50 now.
+ SIMULATED_WAIT(conn1 == ch.selected_connection(), 50 - kMargin, clock);
+ // Now receiving ping and new timeout should kick in.
+ conn1->ReceivedPing("id1"); //
+ EXPECT_EQ_SIMULATED_WAIT(conn1, ch.selected_connection(), 2 * kMargin, clock);
+}
+
+class P2PTransportChannelIceControllerTest : public TestWithParam<std::string> {
+};
+
+INSTANTIATE_TEST_SUITE_P(Legacy,
+ P2PTransportChannelIceControllerTest,
+ Values(""));
+INSTANTIATE_TEST_SUITE_P(Active,
+ P2PTransportChannelIceControllerTest,
+ Values("WebRTC-UseActiveIceController/Enabled/"));
+
+TEST_P(P2PTransportChannelIceControllerTest, InjectIceController) {
+ webrtc::test::ScopedKeyValueConfig field_trials(GetParam());
+ std::unique_ptr<rtc::SocketServer> socket_server =
+ rtc::CreateDefaultSocketServer();
+ rtc::AutoSocketServerThread main_thread(socket_server.get());
+ rtc::BasicPacketSocketFactory packet_socket_factory(socket_server.get());
+ MockIceControllerFactory factory;
+ FakePortAllocator pa(rtc::Thread::Current(), &packet_socket_factory,
+ &field_trials);
+ EXPECT_CALL(factory, RecordIceControllerCreated()).Times(1);
+ webrtc::IceTransportInit init;
+ init.set_port_allocator(&pa);
+ init.set_ice_controller_factory(&factory);
+ init.set_field_trials(&field_trials);
+ auto dummy =
+ P2PTransportChannel::Create("transport_name",
+ /* component= */ 77, std::move(init));
+}
+
+TEST(P2PTransportChannel, InjectActiveIceController) {
+ webrtc::test::ScopedKeyValueConfig field_trials(
+ "WebRTC-UseActiveIceController/Enabled/");
+ std::unique_ptr<rtc::SocketServer> socket_server =
+ rtc::CreateDefaultSocketServer();
+ rtc::AutoSocketServerThread main_thread(socket_server.get());
+ rtc::BasicPacketSocketFactory packet_socket_factory(socket_server.get());
+ MockActiveIceControllerFactory factory;
+ FakePortAllocator pa(rtc::Thread::Current(), &packet_socket_factory,
+ &field_trials);
+ EXPECT_CALL(factory, RecordActiveIceControllerCreated()).Times(1);
+ webrtc::IceTransportInit init;
+ init.set_port_allocator(&pa);
+ init.set_active_ice_controller_factory(&factory);
+ init.set_field_trials(&field_trials);
+ auto dummy =
+ P2PTransportChannel::Create("transport_name",
+ /* component= */ 77, std::move(init));
+}
+
+class ForgetLearnedStateController : public cricket::BasicIceController {
+ public:
+ explicit ForgetLearnedStateController(
+ const cricket::IceControllerFactoryArgs& args)
+ : cricket::BasicIceController(args) {}
+
+ SwitchResult SortAndSwitchConnection(IceSwitchReason reason) override {
+ auto result = cricket::BasicIceController::SortAndSwitchConnection(reason);
+ if (forget_connnection_) {
+ result.connections_to_forget_state_on.push_back(forget_connnection_);
+ forget_connnection_ = nullptr;
+ }
+ result.recheck_event.emplace(IceSwitchReason::ICE_CONTROLLER_RECHECK, 100);
+ return result;
+ }
+
+ void ForgetThisConnectionNextTimeSortAndSwitchConnectionIsCalled(
+ Connection* con) {
+ forget_connnection_ = con;
+ }
+
+ private:
+ Connection* forget_connnection_ = nullptr;
+};
+
+class ForgetLearnedStateControllerFactory
+ : public cricket::IceControllerFactoryInterface {
+ public:
+ std::unique_ptr<cricket::IceControllerInterface> Create(
+ const cricket::IceControllerFactoryArgs& args) override {
+ auto controller = std::make_unique<ForgetLearnedStateController>(args);
+ // Keep a pointer to allow modifying calls.
+ // Must not be used after the p2ptransportchannel has been destructed.
+ controller_ = controller.get();
+ return controller;
+ }
+ virtual ~ForgetLearnedStateControllerFactory() = default;
+
+ ForgetLearnedStateController* controller_;
+};
+
+TEST_P(P2PTransportChannelPingTest, TestForgetLearnedState) {
+ ForgetLearnedStateControllerFactory factory;
+ FakePortAllocator pa(rtc::Thread::Current(), packet_socket_factory(),
+ &field_trials_);
+ webrtc::IceTransportInit init;
+ init.set_port_allocator(&pa);
+ init.set_ice_controller_factory(&factory);
+ init.set_field_trials(&field_trials_);
+ auto ch =
+ P2PTransportChannel::Create("ping sufficiently", 1, std::move(init));
+
+ PrepareChannel(ch.get());
+ ch->MaybeStartGathering();
+ ch->AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 1));
+ ch->AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "2.2.2.2", 2, 2));
+
+ Connection* conn1 = WaitForConnectionTo(ch.get(), "1.1.1.1", 1);
+ Connection* conn2 = WaitForConnectionTo(ch.get(), "2.2.2.2", 2);
+ ASSERT_TRUE(conn1 != nullptr);
+ ASSERT_TRUE(conn2 != nullptr);
+
+ // Wait for conn1 to be selected.
+ conn1->ReceivedPingResponse(LOW_RTT, "id");
+ EXPECT_EQ_WAIT(conn1, ch->selected_connection(), kMediumTimeout);
+
+ conn2->ReceivedPingResponse(LOW_RTT, "id");
+ EXPECT_TRUE(conn2->writable());
+
+ // Now let the ice controller signal to P2PTransportChannel that it
+ // should Forget conn2.
+ factory.controller_
+ ->ForgetThisConnectionNextTimeSortAndSwitchConnectionIsCalled(conn2);
+
+ // We don't have a mock Connection, so verify this by checking that it
+ // is no longer writable.
+ EXPECT_EQ_WAIT(false, conn2->writable(), kMediumTimeout);
+}
+
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ DisableDnsLookupsWithTransportPolicyRelay) {
+ ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags,
+ kDefaultPortAllocatorFlags);
+ auto* ep1 = GetEndpoint(0);
+ ep1->allocator_->SetCandidateFilter(CF_RELAY);
+
+ std::unique_ptr<webrtc::MockAsyncDnsResolver> mock_async_resolver =
+ std::make_unique<webrtc::MockAsyncDnsResolver>();
+ // This test expects resolution to not be started.
+ EXPECT_CALL(*mock_async_resolver, Start(_, _)).Times(0);
+
+ webrtc::MockAsyncDnsResolverFactory mock_async_resolver_factory;
+ ON_CALL(mock_async_resolver_factory, Create())
+ .WillByDefault(
+ [&mock_async_resolver]() { return std::move(mock_async_resolver); });
+
+ ep1->async_dns_resolver_factory_ = &mock_async_resolver_factory;
+
+ CreateChannels();
+
+ ep1_ch1()->AddRemoteCandidate(
+ CreateUdpCandidate(LOCAL_PORT_TYPE, "hostname.test", 1, 100));
+
+ DestroyChannels();
+}
+
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ DisableDnsLookupsWithTransportPolicyNone) {
+ ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags,
+ kDefaultPortAllocatorFlags);
+ auto* ep1 = GetEndpoint(0);
+ ep1->allocator_->SetCandidateFilter(CF_NONE);
+
+ std::unique_ptr<webrtc::MockAsyncDnsResolver> mock_async_resolver =
+ std::make_unique<webrtc::MockAsyncDnsResolver>();
+ // This test expects resolution to not be started.
+ EXPECT_CALL(*mock_async_resolver, Start(_, _)).Times(0);
+
+ webrtc::MockAsyncDnsResolverFactory mock_async_resolver_factory;
+ ON_CALL(mock_async_resolver_factory, Create())
+ .WillByDefault(
+ [&mock_async_resolver]() { return std::move(mock_async_resolver); });
+
+ ep1->async_dns_resolver_factory_ = &mock_async_resolver_factory;
+
+ CreateChannels();
+
+ ep1_ch1()->AddRemoteCandidate(
+ CreateUdpCandidate(LOCAL_PORT_TYPE, "hostname.test", 1, 100));
+
+ DestroyChannels();
+}
+
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ EnableDnsLookupsWithTransportPolicyNoHost) {
+ ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags,
+ kDefaultPortAllocatorFlags);
+ auto* ep1 = GetEndpoint(0);
+ ep1->allocator_->SetCandidateFilter(CF_ALL & ~CF_HOST);
+
+ std::unique_ptr<webrtc::MockAsyncDnsResolver> mock_async_resolver =
+ std::make_unique<webrtc::MockAsyncDnsResolver>();
+ bool lookup_started = false;
+ EXPECT_CALL(*mock_async_resolver, Start(_, _))
+ .WillOnce(Assign(&lookup_started, true));
+
+ webrtc::MockAsyncDnsResolverFactory mock_async_resolver_factory;
+ EXPECT_CALL(mock_async_resolver_factory, Create())
+ .WillOnce(
+ [&mock_async_resolver]() { return std::move(mock_async_resolver); });
+
+ ep1->async_dns_resolver_factory_ = &mock_async_resolver_factory;
+
+ CreateChannels();
+
+ ep1_ch1()->AddRemoteCandidate(
+ CreateUdpCandidate(LOCAL_PORT_TYPE, "hostname.test", 1, 100));
+
+ EXPECT_TRUE(lookup_started);
+
+ DestroyChannels();
+}
+
+class GatherAfterConnectedTest
+ : public P2PTransportChannelTest,
+ public WithParamInterface<std::tuple<bool, std::string>> {
+ public:
+ GatherAfterConnectedTest()
+ : P2PTransportChannelTest(std::get<1>(GetParam())) {}
+};
+
+TEST_P(GatherAfterConnectedTest, GatherAfterConnected) {
+ const bool stop_gather_on_strongly_connected = std::get<0>(GetParam());
+ const std::string field_trial =
+ std::string("WebRTC-IceFieldTrials/stop_gather_on_strongly_connected:") +
+ (stop_gather_on_strongly_connected ? "true/" : "false/");
+ webrtc::test::ScopedKeyValueConfig field_trials(field_trials_, field_trial);
+
+ rtc::ScopedFakeClock clock;
+ // Use local + relay
+ constexpr uint32_t flags =
+ kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_SHARED_SOCKET |
+ PORTALLOCATOR_DISABLE_STUN | PORTALLOCATOR_DISABLE_TCP;
+ ConfigureEndpoints(OPEN, OPEN, flags, flags);
+ auto* ep1 = GetEndpoint(0);
+ auto* ep2 = GetEndpoint(1);
+ ep1->allocator_->SetCandidateFilter(CF_ALL);
+ ep2->allocator_->SetCandidateFilter(CF_ALL);
+
+ // Use step delay 3s which is long enough for
+ // connection to be established before managing to gather relay candidates.
+ int delay = 3000;
+ SetAllocationStepDelay(0, delay);
+ SetAllocationStepDelay(1, delay);
+ IceConfig ice_config = CreateIceConfig(1000, GATHER_CONTINUALLY);
+ CreateChannels(ice_config, ice_config);
+
+ PauseCandidates(0);
+ PauseCandidates(1);
+
+ // We have gathered host candidates but not relay.
+ ASSERT_TRUE_SIMULATED_WAIT(ep1->saved_candidates_.size() == 1u &&
+ ep2->saved_candidates_.size() == 1u,
+ kDefaultTimeout, clock);
+
+ ResumeCandidates(0);
+ ResumeCandidates(1);
+
+ PauseCandidates(0);
+ PauseCandidates(1);
+
+ ASSERT_TRUE_SIMULATED_WAIT(ep1_ch1()->remote_candidates().size() == 1 &&
+ ep2_ch1()->remote_candidates().size() == 1,
+ kDefaultTimeout, clock);
+
+ ASSERT_TRUE_SIMULATED_WAIT(
+ ep1_ch1()->selected_connection() && ep2_ch1()->selected_connection(),
+ kDefaultTimeout, clock);
+
+ clock.AdvanceTime(webrtc::TimeDelta::Millis(10 * delay));
+
+ if (stop_gather_on_strongly_connected) {
+ // The relay candidates gathered has not been propagated to channel.
+ EXPECT_EQ(ep1->saved_candidates_.size(), 0u);
+ EXPECT_EQ(ep2->saved_candidates_.size(), 0u);
+ } else {
+ // The relay candidates gathered has been propagated to channel.
+ EXPECT_EQ(ep1->saved_candidates_.size(), 1u);
+ EXPECT_EQ(ep2->saved_candidates_.size(), 1u);
+ }
+}
+
+TEST_P(GatherAfterConnectedTest, GatherAfterConnectedMultiHomed) {
+ const bool stop_gather_on_strongly_connected = std::get<0>(GetParam());
+ const std::string field_trial =
+ std::string("WebRTC-IceFieldTrials/stop_gather_on_strongly_connected:") +
+ (stop_gather_on_strongly_connected ? "true/" : "false/");
+ webrtc::test::ScopedKeyValueConfig field_trials(field_trials_, field_trial);
+
+ rtc::ScopedFakeClock clock;
+ // Use local + relay
+ constexpr uint32_t flags =
+ kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_SHARED_SOCKET |
+ PORTALLOCATOR_DISABLE_STUN | PORTALLOCATOR_DISABLE_TCP;
+ AddAddress(0, kAlternateAddrs[0]);
+ ConfigureEndpoints(OPEN, OPEN, flags, flags);
+ auto* ep1 = GetEndpoint(0);
+ auto* ep2 = GetEndpoint(1);
+ ep1->allocator_->SetCandidateFilter(CF_ALL);
+ ep2->allocator_->SetCandidateFilter(CF_ALL);
+
+ // Use step delay 3s which is long enough for
+ // connection to be established before managing to gather relay candidates.
+ int delay = 3000;
+ SetAllocationStepDelay(0, delay);
+ SetAllocationStepDelay(1, delay);
+ IceConfig ice_config = CreateIceConfig(1000, GATHER_CONTINUALLY);
+ CreateChannels(ice_config, ice_config);
+
+ PauseCandidates(0);
+ PauseCandidates(1);
+
+ // We have gathered host candidates but not relay.
+ ASSERT_TRUE_SIMULATED_WAIT(ep1->saved_candidates_.size() == 2u &&
+ ep2->saved_candidates_.size() == 1u,
+ kDefaultTimeout, clock);
+
+ ResumeCandidates(0);
+ ResumeCandidates(1);
+
+ PauseCandidates(0);
+ PauseCandidates(1);
+
+ ASSERT_TRUE_SIMULATED_WAIT(ep1_ch1()->remote_candidates().size() == 1 &&
+ ep2_ch1()->remote_candidates().size() == 2,
+ kDefaultTimeout, clock);
+
+ ASSERT_TRUE_SIMULATED_WAIT(
+ ep1_ch1()->selected_connection() && ep2_ch1()->selected_connection(),
+ kDefaultTimeout, clock);
+
+ clock.AdvanceTime(webrtc::TimeDelta::Millis(10 * delay));
+
+ if (stop_gather_on_strongly_connected) {
+ // The relay candidates gathered has not been propagated to channel.
+ EXPECT_EQ(ep1->saved_candidates_.size(), 0u);
+ EXPECT_EQ(ep2->saved_candidates_.size(), 0u);
+ } else {
+ // The relay candidates gathered has been propagated.
+ EXPECT_EQ(ep1->saved_candidates_.size(), 2u);
+ EXPECT_EQ(ep2->saved_candidates_.size(), 1u);
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(Legacy,
+ GatherAfterConnectedTest,
+ Combine(Values(true, false), Values("")));
+INSTANTIATE_TEST_SUITE_P(
+ Active,
+ GatherAfterConnectedTest,
+ Combine(Values(true, false),
+ Values("WebRTC-UseActiveIceController/Enabled/")));
+
+// Tests no candidates are generated with old ice ufrag/passwd after an ice
+// restart even if continual gathering is enabled.
+TEST_P(P2PTransportChannelTestWithFieldTrials,
+ TestIceNoOldCandidatesAfterIceRestart) {
+ rtc::ScopedFakeClock clock;
+ AddAddress(0, kAlternateAddrs[0]);
+ ConfigureEndpoints(OPEN, OPEN, kDefaultPortAllocatorFlags,
+ kDefaultPortAllocatorFlags);
+
+ // gathers continually.
+ IceConfig config = CreateIceConfig(1000, GATHER_CONTINUALLY);
+ CreateChannels(config, config);
+
+ EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()),
+ kDefaultTimeout, clock);
+
+ PauseCandidates(0);
+
+ ep1_ch1()->SetIceParameters(kIceParams[3]);
+ ep1_ch1()->MaybeStartGathering();
+
+ EXPECT_TRUE_SIMULATED_WAIT(GetEndpoint(0)->saved_candidates_.size() > 0,
+ kDefaultTimeout, clock);
+
+ for (const auto& cd : GetEndpoint(0)->saved_candidates_) {
+ EXPECT_EQ(cd.candidate.username(), kIceUfrag[3]);
+ }
+
+ DestroyChannels();
+}
+
+} // namespace cricket
diff --git a/third_party/libwebrtc/p2p/base/packet_transport_internal.cc b/third_party/libwebrtc/p2p/base/packet_transport_internal.cc
new file mode 100644
index 0000000000..0904cb2d3e
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/packet_transport_internal.cc
@@ -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
diff --git a/third_party/libwebrtc/p2p/base/packet_transport_internal.h b/third_party/libwebrtc/p2p/base/packet_transport_internal.h
new file mode 100644
index 0000000000..2ca47d533d
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/packet_transport_internal.h
@@ -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_
diff --git a/third_party/libwebrtc/p2p/base/port.cc b/third_party/libwebrtc/p2p/base/port.cc
new file mode 100644
index 0000000000..5c5ac6319c
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/port.cc
@@ -0,0 +1,969 @@
+/*
+ * 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 <math.h>
+
+#include <algorithm>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "absl/algorithm/container.h"
+#include "absl/memory/memory.h"
+#include "absl/strings/match.h"
+#include "absl/strings/string_view.h"
+#include "p2p/base/connection.h"
+#include "p2p/base/port_allocator.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/crc32.h"
+#include "rtc_base/helpers.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/mdns_responder_interface.h"
+#include "rtc_base/message_digest.h"
+#include "rtc_base/network.h"
+#include "rtc_base/numerics/safe_minmax.h"
+#include "rtc_base/string_encode.h"
+#include "rtc_base/string_utils.h"
+#include "rtc_base/strings/string_builder.h"
+#include "rtc_base/third_party/base64/base64.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
+
+// TODO(ronghuawu): Use "local", "srflx", "prflx" and "relay". But this requires
+// the signaling part be updated correspondingly as well.
+const char LOCAL_PORT_TYPE[] = "local";
+const char STUN_PORT_TYPE[] = "stun";
+const char PRFLX_PORT_TYPE[] = "prflx";
+const char RELAY_PORT_TYPE[] = "relay";
+
+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() {
+ // 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_);
+ CancelPendingTasks();
+ DestroyAllConnections();
+}
+
+const std::string& 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_);
+ if (protocol == TCP_PROTOCOL_NAME && type == LOCAL_PORT_TYPE) {
+ RTC_DCHECK(!tcptype.empty());
+ }
+
+ 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_);
+ c.set_priority(
+ c.GetPriority(type_preference, network_->preference(), relay_preference));
+ c.set_relay_protocol(relay_protocol);
+ 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, type, is_final);
+
+ if (!pending) {
+ FinishAddingAddress(c, is_final);
+ }
+}
+
+bool Port::MaybeObfuscateAddress(Candidate* c,
+ absl::string_view type,
+ 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 (type != LOCAL_PORT_TYPE) {
+ 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 char* data,
+ size_t size,
+ const rtc::SocketAddress& addr,
+ ProtocolType proto) {
+ // 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) {
+ // 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(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) {
+ // 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 {
+ return std::string(remote_username) + ":" + username_fragment();
+}
+
+bool Port::HandleIncomingPacket(rtc::AsyncPacketSocket* socket,
+ const char* data,
+ size_t size,
+ const rtc::SocketAddress& remote_addr,
+ int64_t packet_time_us) {
+ 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(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(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 {
+ return ice_username_fragment_;
+}
+
+void Port::CopyPortInformationToPacketInfo(rtc::PacketInfo* info) const {
+ info->protocol = ConvertProtocolTypeToPacketInfoProtocolType(GetProtocol());
+ info->network_id = Network()->id();
+}
+
+} // namespace cricket
diff --git a/third_party/libwebrtc/p2p/base/port.h b/third_party/libwebrtc/p2p/base/port.h
new file mode 100644
index 0000000000..31091eb273
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/port.h
@@ -0,0 +1,540 @@
+/*
+ * 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 <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#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/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 "rtc_base/async_packet_socket.h"
+#include "rtc_base/callback_list.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/memory/always_valid_pointer.h"
+#include "rtc_base/net_helper.h"
+#include "rtc_base/network.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/weak_ptr.h"
+
+namespace cricket {
+
+RTC_EXPORT extern const char LOCAL_PORT_TYPE[];
+RTC_EXPORT extern const char STUN_PORT_TYPE[];
+RTC_EXPORT extern const char PRFLX_PORT_TYPE[];
+RTC_EXPORT extern const char RELAY_PORT_TYPE[];
+
+// 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[];
+
+// 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
+};
+
+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,
+ 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,
+ 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
+ // conflit in the value of the 2-tuple, make sure that the implementation that
+ // relies on this 2-tuple for RTTI is properly changed.
+ const std::string& 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() { return thread_; }
+
+ // The factory used to create the sockets of this port.
+ rtc::PacketSocketFactory* socket_factory() const { return factory_; }
+
+ // For debugging purposes.
+ const std::string& content_name() const { 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 {
+ 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 { return generation_; }
+ void set_generation(uint32_t generation) { 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) {
+ DestroyConnectionInternal(conn, false);
+ }
+
+ void DestroyConnectionAsync(Connection* conn) {
+ 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 char* data,
+ size_t size,
+ const rtc::SocketAddress& remote_addr,
+ int64_t packet_time_us);
+
+ // 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() { return user_agent_; }
+ const rtc::ProxyInfo& proxy() { 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;
+ std::string CreateStunUsername(absl::string_view remote_username) const;
+
+ bool MaybeIceRoleConflict(const rtc::SocketAddress& addr,
+ IceMessage* stun_msg,
+ absl::string_view remote_ufrag);
+
+ // 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);
+
+ int16_t network_cost() const { 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);
+
+ protected:
+ virtual void UpdateNetworkCost();
+
+ void set_type(absl::string_view type) { type_ = std::string(type); }
+
+ 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 char* data,
+ size_t size,
+ const rtc::SocketAddress& addr,
+ ProtocolType 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);
+
+ // 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.
+ virtual rtc::DiffServCodePoint StunDscpValue() const;
+
+ // 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_;
+ std::string 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.
+ //
+ // Note: we should always use username_fragment() instead of using
+ // `ice_username_fragment_` directly. For the details see the comment on
+ // username_fragment().
+ std::string ice_username_fragment_;
+ std::string password_;
+ 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(Candidate* c,
+ absl::string_view type,
+ bool is_final) RTC_RUN_ON(thread_);
+
+ friend class Connection;
+ webrtc::CallbackList<PortInterface*> port_destroyed_callback_list_;
+};
+
+} // namespace cricket
+
+#endif // P2P_BASE_PORT_H_
diff --git a/third_party/libwebrtc/p2p/base/port_allocator.cc b/third_party/libwebrtc/p2p/base/port_allocator.cc
new file mode 100644
index 0000000000..522f0beb98
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/port_allocator.cc
@@ -0,0 +1,349 @@
+/*
+ * 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 <set>
+#include <utility>
+
+#include "absl/strings/string_view.h"
+#include "p2p/base/ice_credentials_iterator.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.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),
+ tiebreaker_(0) {
+ // 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_(0) {
+ // 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) {
+ 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;
+
+ if (candidate_pool_frozen_) {
+ if (candidate_pool_size != candidate_pool_size_) {
+ RTC_LOG(LS_ERROR)
+ << "Trying to change candidate pool size after pool was frozen.";
+ return false;
+ }
+ return true;
+ }
+
+ if (candidate_pool_size < 0) {
+ RTC_LOG(LS_ERROR) << "Can't set negative pool size.";
+ return false;
+ }
+
+ 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->set_ice_tiebreaker(tiebreaker_);
+ pooled_session->StartGettingPorts();
+ pooled_sessions_.push_back(
+ std::unique_ptr<PortAllocatorSession>(pooled_session));
+ }
+ return true;
+}
+
+void PortAllocator::SetIceTiebreaker(uint64_t tiebreaker) {
+ tiebreaker_ = tiebreaker;
+ for (auto& pooled_session : pooled_sessions_) {
+ pooled_session->set_ice_tiebreaker(tiebreaker_);
+ }
+}
+
+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());
+ session->set_ice_tiebreaker(tiebreaker_);
+ 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::FreezeCandidatePool() {
+ CheckRunOnValidThreadAndInitialized();
+ candidate_pool_frozen_ = true;
+}
+
+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.type() == LOCAL_PORT_TYPE || c.type() == PRFLX_PORT_TYPE) &&
+ 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);
+ bool filter_related_address =
+ ((c.type() == STUN_PORT_TYPE && filter_stun_related_address) ||
+ (c.type() == RELAY_PORT_TYPE && filter_turn_related_address));
+ return c.ToSanitizedCopy(use_hostname_address, filter_related_address);
+}
+
+} // namespace cricket
diff --git a/third_party/libwebrtc/p2p/base/port_allocator.h b/third_party/libwebrtc/p2p/base/port_allocator.h
new file mode 100644
index 0000000000..3ca63cbd41
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/port_allocator.h
@@ -0,0 +1,683 @@
+/*
+ * 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 <deque>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "api/sequence_checker.h"
+#include "api/transport/enums.h"
+#include "p2p/base/port.h"
+#include "p2p/base/port_interface.h"
+#include "rtc_base/helpers.h"
+#include "rtc_base/proxy_info.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_; }
+
+ // TODO(bugs.webrtc.org/14605): move this to the constructor
+ void set_ice_tiebreaker(uint64_t tiebreaker) { tiebreaker_ = tiebreaker; }
+ uint64_t ice_tiebreaker() const { return tiebreaker_; }
+
+ // 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;
+
+ // TODO(bugs.webrtc.org/14605): move this to the constructor
+ uint64_t tiebreaker_;
+
+ // 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);
+
+ void SetIceTiebreaker(uint64_t tiebreaker);
+ uint64_t IceTiebreaker() const { return tiebreaker_; }
+
+ 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;
+
+ // After FreezeCandidatePool is called, changing the candidate pool size will
+ // no longer be allowed, and changing ICE servers will not cause pooled
+ // sessions to be recreated.
+ //
+ // Expected to be called when SetLocalDescription is called on a
+ // PeerConnection. Can be called safely on any thread as long as not
+ // simultaneously with SetConfiguration.
+ void FreezeCandidatePool();
+
+ // 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;
+
+ 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_;
+ bool candidate_pool_frozen_ = false;
+ 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_
diff --git a/third_party/libwebrtc/p2p/base/port_allocator_unittest.cc b/third_party/libwebrtc/p2p/base/port_allocator_unittest.cc
new file mode 100644
index 0000000000..f70997179e
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/port_allocator_unittest.cc
@@ -0,0 +1,371 @@
+/*
+ * 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.
+ */
+
+#include "p2p/base/port_allocator.h"
+
+#include <memory>
+
+#include "absl/strings/string_view.h"
+#include "p2p/base/fake_port_allocator.h"
+#include "rtc_base/thread.h"
+#include "rtc_base/virtual_socket_server.h"
+#include "test/gtest.h"
+#include "test/scoped_key_value_config.h"
+
+static const char kContentName[] = "test content";
+// Based on ICE_UFRAG_LENGTH
+static const char kIceUfrag[] = "UF00";
+// Based on ICE_PWD_LENGTH
+static const char kIcePwd[] = "TESTICEPWD00000000000000";
+static const char kTurnUsername[] = "test";
+static const char kTurnPassword[] = "test";
+constexpr uint64_t kTiebreakerDefault = 44444;
+
+class PortAllocatorTest : public ::testing::Test, public sigslot::has_slots<> {
+ public:
+ PortAllocatorTest()
+ : vss_(std::make_unique<rtc::VirtualSocketServer>()),
+ main_(vss_.get()),
+ packet_socket_factory_(
+ std::make_unique<rtc::BasicPacketSocketFactory>(vss_.get())),
+ allocator_(std::make_unique<cricket::FakePortAllocator>(
+ rtc::Thread::Current(),
+ packet_socket_factory_.get(),
+ &field_trials_)) {
+ allocator_->SetIceTiebreaker(kTiebreakerDefault);
+ }
+
+ protected:
+ void SetConfigurationWithPoolSize(int candidate_pool_size) {
+ EXPECT_TRUE(allocator_->SetConfiguration(
+ cricket::ServerAddresses(), std::vector<cricket::RelayServerConfig>(),
+ candidate_pool_size, webrtc::NO_PRUNE));
+ }
+
+ void SetConfigurationWithPoolSizeExpectFailure(int candidate_pool_size) {
+ EXPECT_FALSE(allocator_->SetConfiguration(
+ cricket::ServerAddresses(), std::vector<cricket::RelayServerConfig>(),
+ candidate_pool_size, webrtc::NO_PRUNE));
+ }
+
+ std::unique_ptr<cricket::FakePortAllocatorSession> CreateSession(
+ absl::string_view content_name,
+ int component,
+ absl::string_view ice_ufrag,
+ absl::string_view ice_pwd) {
+ return std::unique_ptr<cricket::FakePortAllocatorSession>(
+ static_cast<cricket::FakePortAllocatorSession*>(
+ allocator_
+ ->CreateSession(content_name, component, ice_ufrag, ice_pwd)
+ .release()));
+ }
+
+ const cricket::FakePortAllocatorSession* GetPooledSession() const {
+ return static_cast<const cricket::FakePortAllocatorSession*>(
+ allocator_->GetPooledSession());
+ }
+
+ std::unique_ptr<cricket::FakePortAllocatorSession> TakePooledSession() {
+ return std::unique_ptr<cricket::FakePortAllocatorSession>(
+ static_cast<cricket::FakePortAllocatorSession*>(
+ allocator_->TakePooledSession(kContentName, 0, kIceUfrag, kIcePwd)
+ .release()));
+ }
+
+ int GetAllPooledSessionsReturnCount() {
+ int count = 0;
+ while (TakePooledSession() != nullptr) {
+ ++count;
+ }
+ return count;
+ }
+
+ webrtc::test::ScopedKeyValueConfig field_trials_;
+ std::unique_ptr<rtc::VirtualSocketServer> vss_;
+ rtc::AutoSocketServerThread main_;
+ std::unique_ptr<rtc::PacketSocketFactory> packet_socket_factory_;
+ std::unique_ptr<cricket::FakePortAllocator> allocator_;
+ rtc::SocketAddress stun_server_1{"11.11.11.11", 3478};
+ rtc::SocketAddress stun_server_2{"22.22.22.22", 3478};
+ cricket::RelayServerConfig turn_server_1{"11.11.11.11", 3478,
+ kTurnUsername, kTurnPassword,
+ cricket::PROTO_UDP, false};
+ cricket::RelayServerConfig turn_server_2{"22.22.22.22", 3478,
+ kTurnUsername, kTurnPassword,
+ cricket::PROTO_UDP, false};
+};
+
+TEST_F(PortAllocatorTest, TestDefaults) {
+ EXPECT_EQ(0UL, allocator_->stun_servers().size());
+ EXPECT_EQ(0UL, allocator_->turn_servers().size());
+ EXPECT_EQ(0, allocator_->candidate_pool_size());
+ EXPECT_EQ(0, GetAllPooledSessionsReturnCount());
+}
+
+// Call CreateSession and verify that the parameters passed in and the
+// candidate filter are applied as expected.
+TEST_F(PortAllocatorTest, CreateSession) {
+ allocator_->SetCandidateFilter(cricket::CF_RELAY);
+ auto session = CreateSession(kContentName, 1, kIceUfrag, kIcePwd);
+ ASSERT_NE(nullptr, session);
+ EXPECT_EQ(cricket::CF_RELAY, session->candidate_filter());
+ EXPECT_EQ(kContentName, session->content_name());
+ EXPECT_EQ(1, session->component());
+ EXPECT_EQ(kIceUfrag, session->ice_ufrag());
+ EXPECT_EQ(kIcePwd, session->ice_pwd());
+}
+
+TEST_F(PortAllocatorTest, SetConfigurationUpdatesIceServers) {
+ cricket::ServerAddresses stun_servers_1 = {stun_server_1};
+ std::vector<cricket::RelayServerConfig> turn_servers_1 = {turn_server_1};
+ EXPECT_TRUE(allocator_->SetConfiguration(stun_servers_1, turn_servers_1, 0,
+ webrtc::NO_PRUNE));
+ EXPECT_EQ(stun_servers_1, allocator_->stun_servers());
+ EXPECT_EQ(turn_servers_1, allocator_->turn_servers());
+
+ // Update with a different set of servers.
+ cricket::ServerAddresses stun_servers_2 = {stun_server_2};
+ std::vector<cricket::RelayServerConfig> turn_servers_2 = {turn_server_2};
+ EXPECT_TRUE(allocator_->SetConfiguration(stun_servers_2, turn_servers_2, 0,
+ webrtc::NO_PRUNE));
+ EXPECT_EQ(stun_servers_2, allocator_->stun_servers());
+ EXPECT_EQ(turn_servers_2, allocator_->turn_servers());
+}
+
+TEST_F(PortAllocatorTest, SetConfigurationUpdatesCandidatePoolSize) {
+ SetConfigurationWithPoolSize(2);
+ EXPECT_EQ(2, allocator_->candidate_pool_size());
+ SetConfigurationWithPoolSize(3);
+ EXPECT_EQ(3, allocator_->candidate_pool_size());
+ SetConfigurationWithPoolSize(1);
+ EXPECT_EQ(1, allocator_->candidate_pool_size());
+ SetConfigurationWithPoolSize(4);
+ EXPECT_EQ(4, allocator_->candidate_pool_size());
+}
+
+// A negative pool size should just be treated as zero.
+TEST_F(PortAllocatorTest, SetConfigurationWithNegativePoolSizeFails) {
+ SetConfigurationWithPoolSizeExpectFailure(-1);
+}
+
+// Test that if the candidate pool size is nonzero, pooled sessions are
+// created, and StartGettingPorts is called on them.
+TEST_F(PortAllocatorTest, SetConfigurationCreatesPooledSessions) {
+ SetConfigurationWithPoolSize(2);
+ auto session_1 = TakePooledSession();
+ auto session_2 = TakePooledSession();
+ ASSERT_NE(nullptr, session_1.get());
+ ASSERT_NE(nullptr, session_2.get());
+ EXPECT_EQ(1, session_1->port_config_count());
+ EXPECT_EQ(1, session_2->port_config_count());
+ EXPECT_EQ(0, GetAllPooledSessionsReturnCount());
+}
+
+// Test that if the candidate pool size is increased, pooled sessions are
+// created as necessary.
+TEST_F(PortAllocatorTest, SetConfigurationCreatesMorePooledSessions) {
+ SetConfigurationWithPoolSize(1);
+ SetConfigurationWithPoolSize(2);
+ EXPECT_EQ(2, GetAllPooledSessionsReturnCount());
+}
+
+// Test that if the candidate pool size is reduced, extra sessions are
+// destroyed.
+TEST_F(PortAllocatorTest, SetConfigurationDestroysPooledSessions) {
+ SetConfigurationWithPoolSize(2);
+ SetConfigurationWithPoolSize(1);
+ EXPECT_EQ(1, GetAllPooledSessionsReturnCount());
+}
+
+// According to JSEP, existing pooled sessions should be destroyed and new
+// ones created when the ICE servers change.
+TEST_F(PortAllocatorTest,
+ SetConfigurationRecreatesPooledSessionsWhenIceServersChange) {
+ cricket::ServerAddresses stun_servers_1 = {stun_server_1};
+ std::vector<cricket::RelayServerConfig> turn_servers_1 = {turn_server_1};
+ allocator_->SetConfiguration(stun_servers_1, turn_servers_1, 1,
+ webrtc::NO_PRUNE);
+ EXPECT_EQ(stun_servers_1, allocator_->stun_servers());
+ EXPECT_EQ(turn_servers_1, allocator_->turn_servers());
+
+ // Update with a different set of servers (and also change pool size).
+ cricket::ServerAddresses stun_servers_2 = {stun_server_2};
+ std::vector<cricket::RelayServerConfig> turn_servers_2 = {turn_server_2};
+ allocator_->SetConfiguration(stun_servers_2, turn_servers_2, 2,
+ webrtc::NO_PRUNE);
+ EXPECT_EQ(stun_servers_2, allocator_->stun_servers());
+ EXPECT_EQ(turn_servers_2, allocator_->turn_servers());
+ auto session_1 = TakePooledSession();
+ auto session_2 = TakePooledSession();
+ ASSERT_NE(nullptr, session_1.get());
+ ASSERT_NE(nullptr, session_2.get());
+ EXPECT_EQ(stun_servers_2, session_1->stun_servers());
+ EXPECT_EQ(turn_servers_2, session_1->turn_servers());
+ EXPECT_EQ(stun_servers_2, session_2->stun_servers());
+ EXPECT_EQ(turn_servers_2, session_2->turn_servers());
+ EXPECT_EQ(0, GetAllPooledSessionsReturnCount());
+}
+
+// According to JSEP, after SetLocalDescription, setting different ICE servers
+// will not cause the pool to be refilled. This is implemented by the
+// PeerConnection calling FreezeCandidatePool when a local description is set.
+TEST_F(PortAllocatorTest,
+ SetConfigurationDoesNotRecreatePooledSessionsAfterFreezeCandidatePool) {
+ cricket::ServerAddresses stun_servers_1 = {stun_server_1};
+ std::vector<cricket::RelayServerConfig> turn_servers_1 = {turn_server_1};
+ allocator_->SetConfiguration(stun_servers_1, turn_servers_1, 1,
+ webrtc::NO_PRUNE);
+ EXPECT_EQ(stun_servers_1, allocator_->stun_servers());
+ EXPECT_EQ(turn_servers_1, allocator_->turn_servers());
+
+ // Update with a different set of servers, but first freeze the pool.
+ allocator_->FreezeCandidatePool();
+ cricket::ServerAddresses stun_servers_2 = {stun_server_2};
+ std::vector<cricket::RelayServerConfig> turn_servers_2 = {turn_server_2};
+ allocator_->SetConfiguration(stun_servers_2, turn_servers_2, 2,
+ webrtc::NO_PRUNE);
+ EXPECT_EQ(stun_servers_2, allocator_->stun_servers());
+ EXPECT_EQ(turn_servers_2, allocator_->turn_servers());
+ auto session = TakePooledSession();
+ ASSERT_NE(nullptr, session.get());
+ EXPECT_EQ(stun_servers_1, session->stun_servers());
+ EXPECT_EQ(turn_servers_1, session->turn_servers());
+ EXPECT_EQ(0, GetAllPooledSessionsReturnCount());
+}
+
+TEST_F(PortAllocatorTest, GetPooledSessionReturnsNextSession) {
+ SetConfigurationWithPoolSize(2);
+ auto peeked_session_1 = GetPooledSession();
+ auto session_1 = TakePooledSession();
+ EXPECT_EQ(session_1.get(), peeked_session_1);
+ auto peeked_session_2 = GetPooledSession();
+ auto session_2 = TakePooledSession();
+ EXPECT_EQ(session_2.get(), peeked_session_2);
+}
+
+// Verify that subclasses of PortAllocatorSession are given a chance to update
+// ICE parameters when TakePooledSession is called, and the base class updates
+// the info itself.
+TEST_F(PortAllocatorTest, TakePooledSessionUpdatesIceParameters) {
+ SetConfigurationWithPoolSize(1);
+ auto peeked_session = GetPooledSession();
+ ASSERT_NE(nullptr, peeked_session);
+ EXPECT_EQ(0, peeked_session->transport_info_update_count());
+ std::unique_ptr<cricket::FakePortAllocatorSession> session(
+ static_cast<cricket::FakePortAllocatorSession*>(
+ allocator_->TakePooledSession(kContentName, 1, kIceUfrag, kIcePwd)
+ .release()));
+ EXPECT_EQ(1, session->transport_info_update_count());
+ EXPECT_EQ(kContentName, session->content_name());
+ EXPECT_EQ(1, session->component());
+ EXPECT_EQ(kIceUfrag, session->ice_ufrag());
+ EXPECT_EQ(kIcePwd, session->ice_pwd());
+}
+
+// According to JSEP, candidate filtering should be done when the pooled
+// candidates are surfaced to the application. This means when a pooled
+// session is taken. So a pooled session should gather candidates
+// unfiltered until it's returned by TakePooledSession.
+TEST_F(PortAllocatorTest, TakePooledSessionUpdatesCandidateFilter) {
+ allocator_->SetCandidateFilter(cricket::CF_RELAY);
+ SetConfigurationWithPoolSize(1);
+ auto peeked_session = GetPooledSession();
+ ASSERT_NE(nullptr, peeked_session);
+ EXPECT_EQ(cricket::CF_ALL, peeked_session->candidate_filter());
+ auto session = TakePooledSession();
+ EXPECT_EQ(cricket::CF_RELAY, session->candidate_filter());
+}
+
+// Verify that after DiscardCandidatePool, TakePooledSession doesn't return
+// anything.
+TEST_F(PortAllocatorTest, DiscardCandidatePool) {
+ SetConfigurationWithPoolSize(1);
+ allocator_->DiscardCandidatePool();
+ EXPECT_EQ(0, GetAllPooledSessionsReturnCount());
+}
+
+TEST_F(PortAllocatorTest, RestrictIceCredentialsChange) {
+ SetConfigurationWithPoolSize(1);
+ EXPECT_EQ(1, GetAllPooledSessionsReturnCount());
+ allocator_->DiscardCandidatePool();
+
+ // Only return pooled sessions with the ice credentials that
+ // match those requested in TakePooledSession().
+ allocator_->set_restrict_ice_credentials_change(true);
+ SetConfigurationWithPoolSize(1);
+ EXPECT_EQ(0, GetAllPooledSessionsReturnCount());
+ allocator_->DiscardCandidatePool();
+
+ SetConfigurationWithPoolSize(1);
+ auto credentials = allocator_->GetPooledIceCredentials();
+ ASSERT_EQ(1u, credentials.size());
+ EXPECT_EQ(nullptr,
+ allocator_->TakePooledSession(kContentName, 0, kIceUfrag, kIcePwd));
+ EXPECT_NE(nullptr,
+ allocator_->TakePooledSession(kContentName, 0, credentials[0].ufrag,
+ credentials[0].pwd));
+ EXPECT_EQ(nullptr,
+ allocator_->TakePooledSession(kContentName, 0, credentials[0].ufrag,
+ credentials[0].pwd));
+ allocator_->DiscardCandidatePool();
+}
+
+// Constants for testing candidates
+const char kIpv4Address[] = "12.34.56.78";
+const char kIpv4AddressWithPort[] = "12.34.56.78:443";
+
+TEST_F(PortAllocatorTest, SanitizeEmptyCandidateDefaultConfig) {
+ cricket::Candidate input;
+ cricket::Candidate output = allocator_->SanitizeCandidate(input);
+ EXPECT_EQ("", output.address().ipaddr().ToString());
+}
+
+TEST_F(PortAllocatorTest, SanitizeIpv4CandidateDefaultConfig) {
+ cricket::Candidate input(1, "udp", rtc::SocketAddress(kIpv4Address, 443), 1,
+ "username", "password", cricket::LOCAL_PORT_TYPE, 1,
+ "foundation", 1, 1);
+ cricket::Candidate output = allocator_->SanitizeCandidate(input);
+ EXPECT_EQ(kIpv4AddressWithPort, output.address().ToString());
+ EXPECT_EQ(kIpv4Address, output.address().ipaddr().ToString());
+}
+
+TEST_F(PortAllocatorTest, SanitizeIpv4CandidateMdnsObfuscationEnabled) {
+ allocator_->SetMdnsObfuscationEnabledForTesting(true);
+ cricket::Candidate input(1, "udp", rtc::SocketAddress(kIpv4Address, 443), 1,
+ "username", "password", cricket::LOCAL_PORT_TYPE, 1,
+ "foundation", 1, 1);
+ cricket::Candidate output = allocator_->SanitizeCandidate(input);
+ EXPECT_NE(kIpv4AddressWithPort, output.address().ToString());
+ EXPECT_EQ("", output.address().ipaddr().ToString());
+}
+
+TEST_F(PortAllocatorTest, SanitizePrflxCandidateMdnsObfuscationEnabled) {
+ allocator_->SetMdnsObfuscationEnabledForTesting(true);
+ // Create the candidate from an IP literal. This populates the hostname.
+ cricket::Candidate input(1, "udp", rtc::SocketAddress(kIpv4Address, 443), 1,
+ "username", "password", cricket::PRFLX_PORT_TYPE, 1,
+ "foundation", 1, 1);
+ cricket::Candidate output = allocator_->SanitizeCandidate(input);
+ EXPECT_NE(kIpv4AddressWithPort, output.address().ToString());
+ EXPECT_EQ("", output.address().ipaddr().ToString());
+}
+
+TEST_F(PortAllocatorTest, SanitizeIpv4NonLiteralMdnsObfuscationEnabled) {
+ // Create the candidate with an empty hostname.
+ allocator_->SetMdnsObfuscationEnabledForTesting(true);
+ rtc::IPAddress ip;
+ EXPECT_TRUE(IPFromString(kIpv4Address, &ip));
+ cricket::Candidate input(1, "udp", rtc::SocketAddress(ip, 443), 1, "username",
+ "password", cricket::LOCAL_PORT_TYPE, 1,
+ "foundation", 1, 1);
+ cricket::Candidate output = allocator_->SanitizeCandidate(input);
+ EXPECT_NE(kIpv4AddressWithPort, output.address().ToString());
+ EXPECT_EQ("", output.address().ipaddr().ToString());
+}
diff --git a/third_party/libwebrtc/p2p/base/port_interface.cc b/third_party/libwebrtc/p2p/base/port_interface.cc
new file mode 100644
index 0000000000..b07cdf9ee6
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/port_interface.cc
@@ -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
diff --git a/third_party/libwebrtc/p2p/base/port_interface.h b/third_party/libwebrtc/p2p/base/port_interface.h
new file mode 100644
index 0000000000..29c2741bab
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/port_interface.h
@@ -0,0 +1,146 @@
+/*
+ * 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 <string>
+#include <utility>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "api/candidate.h"
+#include "p2p/base/transport_description.h"
+#include "rtc_base/async_packet_socket.h"
+#include "rtc_base/callback_list.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 std::string& 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;
+
+ protected:
+ PortInterface();
+};
+
+} // namespace cricket
+
+#endif // P2P_BASE_PORT_INTERFACE_H_
diff --git a/third_party/libwebrtc/p2p/base/port_unittest.cc b/third_party/libwebrtc/p2p/base/port_unittest.cc
new file mode 100644
index 0000000000..3a0021699c
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/port_unittest.cc
@@ -0,0 +1,3844 @@
+/*
+ * 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 <string.h>
+
+#include <cstdint>
+#include <limits>
+#include <list>
+#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 "api/transport/stun.h"
+#include "api/units/time_delta.h"
+#include "p2p/base/basic_packet_socket_factory.h"
+#include "p2p/base/p2p_constants.h"
+#include "p2p/base/port_allocator.h"
+#include "p2p/base/port_interface.h"
+#include "p2p/base/stun_port.h"
+#include "p2p/base/stun_server.h"
+#include "p2p/base/tcp_port.h"
+#include "p2p/base/test_stun_server.h"
+#include "p2p/base/test_turn_server.h"
+#include "p2p/base/transport_description.h"
+#include "p2p/base/turn_port.h"
+#include "p2p/base/turn_server.h"
+#include "p2p/client/relay_port_factory_interface.h"
+#include "rtc_base/arraysize.h"
+#include "rtc_base/async_packet_socket.h"
+#include "rtc_base/buffer.h"
+#include "rtc_base/byte_buffer.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/dscp.h"
+#include "rtc_base/fake_clock.h"
+#include "rtc_base/gunit.h"
+#include "rtc_base/helpers.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/nat_server.h"
+#include "rtc_base/nat_socket_factory.h"
+#include "rtc_base/nat_types.h"
+#include "rtc_base/net_helper.h"
+#include "rtc_base/network.h"
+#include "rtc_base/network/sent_packet.h"
+#include "rtc_base/network_constants.h"
+#include "rtc_base/proxy_info.h"
+#include "rtc_base/socket.h"
+#include "rtc_base/socket_adapters.h"
+#include "rtc_base/socket_address.h"
+#include "rtc_base/third_party/sigslot/sigslot.h"
+#include "rtc_base/thread.h"
+#include "rtc_base/time_utils.h"
+#include "rtc_base/virtual_socket_server.h"
+#include "test/gtest.h"
+#include "test/scoped_key_value_config.h"
+
+using rtc::AsyncListenSocket;
+using rtc::AsyncPacketSocket;
+using rtc::ByteBufferReader;
+using rtc::ByteBufferWriter;
+using rtc::NAT_ADDR_RESTRICTED;
+using rtc::NAT_OPEN_CONE;
+using rtc::NAT_PORT_RESTRICTED;
+using rtc::NAT_SYMMETRIC;
+using rtc::NATType;
+using rtc::PacketSocketFactory;
+using rtc::Socket;
+using rtc::SocketAddress;
+
+namespace cricket {
+namespace {
+
+constexpr int kDefaultTimeout = 3000;
+constexpr int kShortTimeout = 1000;
+constexpr int kMaxExpectedSimulatedRtt = 200;
+const SocketAddress kLocalAddr1("192.168.1.2", 0);
+const SocketAddress kLocalAddr2("192.168.1.3", 0);
+const SocketAddress kLinkLocalIPv6Addr("fe80::aabb:ccff:fedd:eeff", 0);
+const SocketAddress kNatAddr1("77.77.77.77", rtc::NAT_SERVER_UDP_PORT);
+const SocketAddress kNatAddr2("88.88.88.88", rtc::NAT_SERVER_UDP_PORT);
+const SocketAddress kStunAddr("99.99.99.1", STUN_SERVER_PORT);
+const SocketAddress kTurnUdpIntAddr("99.99.99.4", STUN_SERVER_PORT);
+const SocketAddress kTurnTcpIntAddr("99.99.99.4", 5010);
+const SocketAddress kTurnUdpExtAddr("99.99.99.5", 0);
+const RelayCredentials kRelayCredentials("test", "test");
+
+// TODO(?): Update these when RFC5245 is completely supported.
+// Magic value of 30 is from RFC3484, for IPv4 addresses.
+const uint32_t kDefaultPrflxPriority = ICE_TYPE_PREFERENCE_PRFLX << 24 |
+ 30 << 8 |
+ (256 - ICE_CANDIDATE_COMPONENT_DEFAULT);
+
+constexpr int kTiebreaker1 = 11111;
+constexpr int kTiebreaker2 = 22222;
+constexpr int kTiebreakerDefault = 44444;
+
+const char* data = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
+
+Candidate GetCandidate(Port* port) {
+ RTC_DCHECK_GE(port->Candidates().size(), 1);
+ return port->Candidates()[0];
+}
+
+SocketAddress GetAddress(Port* port) {
+ return GetCandidate(port).address();
+}
+
+std::unique_ptr<IceMessage> CopyStunMessage(const IceMessage& src) {
+ auto dst = std::make_unique<IceMessage>();
+ ByteBufferWriter buf;
+ src.Write(&buf);
+ ByteBufferReader read_buf(buf);
+ dst->Read(&read_buf);
+ return dst;
+}
+
+bool WriteStunMessage(const StunMessage& msg, ByteBufferWriter* buf) {
+ buf->Resize(0); // clear out any existing buffer contents
+ return msg.Write(buf);
+}
+
+} // namespace
+
+// Stub port class for testing STUN generation and processing.
+class TestPort : public Port {
+ public:
+ TestPort(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_fragment,
+ absl::string_view password)
+ : Port(thread,
+ type,
+ factory,
+ network,
+ min_port,
+ max_port,
+ username_fragment,
+ password) {}
+ ~TestPort() {}
+
+ // Expose GetStunMessage so that we can test it.
+ using cricket::Port::GetStunMessage;
+
+ // The last StunMessage that was sent on this Port.
+ // TODO(?): Make these const; requires changes to SendXXXXResponse.
+ rtc::BufferT<uint8_t>* last_stun_buf() { return last_stun_buf_.get(); }
+ IceMessage* last_stun_msg() { return last_stun_msg_.get(); }
+ int last_stun_error_code() {
+ int code = 0;
+ if (last_stun_msg_) {
+ const StunErrorCodeAttribute* error_attr = last_stun_msg_->GetErrorCode();
+ if (error_attr) {
+ code = error_attr->code();
+ }
+ }
+ return code;
+ }
+
+ virtual void PrepareAddress() {
+ // Act as if the socket was bound to the best IP on the network, to the
+ // first port in the allowed range.
+ rtc::SocketAddress addr(Network()->GetBestIP(), min_port());
+ AddAddress(addr, addr, rtc::SocketAddress(), "udp", "", "", Type(),
+ ICE_TYPE_PREFERENCE_HOST, 0, "", true);
+ }
+
+ virtual bool SupportsProtocol(absl::string_view protocol) const {
+ return true;
+ }
+
+ virtual ProtocolType GetProtocol() const { return PROTO_UDP; }
+
+ // Exposed for testing candidate building.
+ void AddCandidateAddress(const rtc::SocketAddress& addr) {
+ AddAddress(addr, addr, rtc::SocketAddress(), "udp", "", "", Type(),
+ type_preference_, 0, "", false);
+ }
+ void AddCandidateAddress(const rtc::SocketAddress& addr,
+ const rtc::SocketAddress& base_address,
+ absl::string_view type,
+ int type_preference,
+ bool final) {
+ AddAddress(addr, base_address, rtc::SocketAddress(), "udp", "", "", type,
+ type_preference, 0, "", final);
+ }
+
+ virtual Connection* CreateConnection(const Candidate& remote_candidate,
+ CandidateOrigin origin) {
+ Connection* conn = new ProxyConnection(NewWeakPtr(), 0, remote_candidate);
+ AddOrReplaceConnection(conn);
+ // Set use-candidate attribute flag as this will add USE-CANDIDATE attribute
+ // in STUN binding requests.
+ conn->set_use_candidate_attr(true);
+ return conn;
+ }
+ virtual int SendTo(const void* data,
+ size_t size,
+ const rtc::SocketAddress& addr,
+ const rtc::PacketOptions& options,
+ bool payload) {
+ if (!payload) {
+ auto msg = std::make_unique<IceMessage>();
+ auto buf = std::make_unique<rtc::BufferT<uint8_t>>(
+ static_cast<const char*>(data), size);
+ ByteBufferReader read_buf(*buf);
+ if (!msg->Read(&read_buf)) {
+ return -1;
+ }
+ last_stun_buf_ = std::move(buf);
+ last_stun_msg_ = std::move(msg);
+ }
+ return static_cast<int>(size);
+ }
+ virtual int SetOption(rtc::Socket::Option opt, int value) { return 0; }
+ virtual int GetOption(rtc::Socket::Option opt, int* value) { return -1; }
+ virtual int GetError() { return 0; }
+ void Reset() {
+ last_stun_buf_.reset();
+ last_stun_msg_.reset();
+ }
+ void set_type_preference(int type_preference) {
+ type_preference_ = type_preference;
+ }
+
+ private:
+ void OnSentPacket(rtc::AsyncPacketSocket* socket,
+ const rtc::SentPacket& sent_packet) {
+ PortInterface::SignalSentPacket(sent_packet);
+ }
+ std::unique_ptr<rtc::BufferT<uint8_t>> last_stun_buf_;
+ std::unique_ptr<IceMessage> last_stun_msg_;
+ int type_preference_ = 0;
+};
+
+static void SendPingAndReceiveResponse(Connection* lconn,
+ TestPort* lport,
+ Connection* rconn,
+ TestPort* rport,
+ rtc::ScopedFakeClock* clock,
+ int64_t ms) {
+ lconn->Ping(rtc::TimeMillis());
+ ASSERT_TRUE_WAIT(lport->last_stun_msg(), kDefaultTimeout);
+ ASSERT_TRUE(lport->last_stun_buf());
+ rconn->OnReadPacket(lport->last_stun_buf()->data<char>(),
+ lport->last_stun_buf()->size(), /* packet_time_us */ -1);
+ clock->AdvanceTime(webrtc::TimeDelta::Millis(ms));
+ ASSERT_TRUE_WAIT(rport->last_stun_msg(), kDefaultTimeout);
+ ASSERT_TRUE(rport->last_stun_buf());
+ lconn->OnReadPacket(rport->last_stun_buf()->data<char>(),
+ rport->last_stun_buf()->size(), /* packet_time_us */ -1);
+}
+
+class TestChannel : public sigslot::has_slots<> {
+ public:
+ // Takes ownership of `p1` (but not `p2`).
+ explicit TestChannel(std::unique_ptr<Port> p1) : port_(std::move(p1)) {
+ port_->SignalPortComplete.connect(this, &TestChannel::OnPortComplete);
+ port_->SignalUnknownAddress.connect(this, &TestChannel::OnUnknownAddress);
+ port_->SubscribePortDestroyed(
+ [this](PortInterface* port) { OnSrcPortDestroyed(port); });
+ }
+
+ ~TestChannel() { Stop(); }
+
+ int complete_count() { return complete_count_; }
+ Connection* conn() { return conn_; }
+ const SocketAddress& remote_address() { return remote_address_; }
+ const std::string remote_fragment() { return remote_frag_; }
+
+ void Start() { port_->PrepareAddress(); }
+ void CreateConnection(const Candidate& remote_candidate) {
+ RTC_DCHECK(!conn_);
+ conn_ = port_->CreateConnection(remote_candidate, Port::ORIGIN_MESSAGE);
+ IceMode remote_ice_mode =
+ (ice_mode_ == ICEMODE_FULL) ? ICEMODE_LITE : ICEMODE_FULL;
+ conn_->set_use_candidate_attr(remote_ice_mode == ICEMODE_FULL);
+ conn_->SignalStateChange.connect(this,
+ &TestChannel::OnConnectionStateChange);
+ conn_->SignalDestroyed.connect(this, &TestChannel::OnDestroyed);
+ conn_->SignalReadyToSend.connect(this,
+ &TestChannel::OnConnectionReadyToSend);
+ connection_ready_to_send_ = false;
+ }
+
+ void OnConnectionStateChange(Connection* conn) {
+ if (conn->write_state() == Connection::STATE_WRITABLE) {
+ conn->set_use_candidate_attr(true);
+ nominated_ = true;
+ }
+ }
+ void AcceptConnection(const Candidate& remote_candidate) {
+ if (conn_) {
+ conn_->SignalDestroyed.disconnect(this);
+ conn_ = nullptr;
+ }
+ ASSERT_TRUE(remote_request_.get() != NULL);
+ Candidate c = remote_candidate;
+ c.set_address(remote_address_);
+ conn_ = port_->CreateConnection(c, Port::ORIGIN_MESSAGE);
+ conn_->SignalDestroyed.connect(this, &TestChannel::OnDestroyed);
+ conn_->SendStunBindingResponse(remote_request_.get());
+ remote_request_.reset();
+ }
+ void Ping() { Ping(0); }
+ void Ping(int64_t now) { conn_->Ping(now); }
+ void Stop() {
+ if (conn_) {
+ port_->DestroyConnection(conn_);
+ conn_ = nullptr;
+ }
+ }
+
+ void OnPortComplete(Port* port) { complete_count_++; }
+ void SetIceMode(IceMode ice_mode) { ice_mode_ = ice_mode; }
+
+ int SendData(const char* data, size_t len) {
+ rtc::PacketOptions options;
+ return conn_->Send(data, len, options);
+ }
+
+ void OnUnknownAddress(PortInterface* port,
+ const SocketAddress& addr,
+ ProtocolType proto,
+ IceMessage* msg,
+ const std::string& rf,
+ bool /*port_muxed*/) {
+ ASSERT_EQ(port_.get(), port);
+ if (!remote_address_.IsNil()) {
+ ASSERT_EQ(remote_address_, addr);
+ }
+ const cricket::StunUInt32Attribute* priority_attr =
+ msg->GetUInt32(STUN_ATTR_PRIORITY);
+ const cricket::StunByteStringAttribute* mi_attr =
+ msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY);
+ const cricket::StunUInt32Attribute* fingerprint_attr =
+ msg->GetUInt32(STUN_ATTR_FINGERPRINT);
+ EXPECT_TRUE(priority_attr != NULL);
+ EXPECT_TRUE(mi_attr != NULL);
+ EXPECT_TRUE(fingerprint_attr != NULL);
+ remote_address_ = addr;
+ remote_request_ = CopyStunMessage(*msg);
+ remote_frag_ = rf;
+ }
+
+ void OnDestroyed(Connection* conn) {
+ ASSERT_EQ(conn_, conn);
+ RTC_LOG(LS_INFO) << "OnDestroy connection " << conn << " deleted";
+ conn_ = nullptr;
+ // When the connection is destroyed, also clear these fields so future
+ // connections are possible.
+ remote_request_.reset();
+ remote_address_.Clear();
+ }
+
+ void OnSrcPortDestroyed(PortInterface* port) {
+ Port* destroyed_src = port_.release();
+ ASSERT_EQ(destroyed_src, port);
+ }
+
+ Port* port() { return port_.get(); }
+
+ bool nominated() const { return nominated_; }
+
+ void set_connection_ready_to_send(bool ready) {
+ connection_ready_to_send_ = ready;
+ }
+ bool connection_ready_to_send() const { return connection_ready_to_send_; }
+
+ private:
+ // ReadyToSend will only issue after a Connection recovers from ENOTCONN
+ void OnConnectionReadyToSend(Connection* conn) {
+ ASSERT_EQ(conn, conn_);
+ connection_ready_to_send_ = true;
+ }
+
+ IceMode ice_mode_ = ICEMODE_FULL;
+ std::unique_ptr<Port> port_;
+
+ int complete_count_ = 0;
+ Connection* conn_ = nullptr;
+ SocketAddress remote_address_;
+ std::unique_ptr<StunMessage> remote_request_;
+ std::string remote_frag_;
+ bool nominated_ = false;
+ bool connection_ready_to_send_ = false;
+};
+
+class PortTest : public ::testing::Test, public sigslot::has_slots<> {
+ public:
+ PortTest()
+ : ss_(new rtc::VirtualSocketServer()),
+ main_(ss_.get()),
+ socket_factory_(ss_.get()),
+ nat_factory1_(ss_.get(), kNatAddr1, SocketAddress()),
+ nat_factory2_(ss_.get(), kNatAddr2, SocketAddress()),
+ nat_socket_factory1_(&nat_factory1_),
+ nat_socket_factory2_(&nat_factory2_),
+ stun_server_(TestStunServer::Create(ss_.get(), kStunAddr)),
+ turn_server_(&main_, ss_.get(), kTurnUdpIntAddr, kTurnUdpExtAddr),
+ username_(rtc::CreateRandomString(ICE_UFRAG_LENGTH)),
+ password_(rtc::CreateRandomString(ICE_PWD_LENGTH)),
+ role_conflict_(false),
+ ports_destroyed_(0) {}
+
+ ~PortTest() {
+ // Workaround for tests that trigger async destruction of objects that we
+ // need to give an opportunity here to run, before proceeding with other
+ // teardown.
+ rtc::Thread::Current()->ProcessMessages(0);
+ }
+
+ protected:
+ std::string password() { return password_; }
+
+ void TestLocalToLocal() {
+ auto port1 = CreateUdpPort(kLocalAddr1);
+ port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ auto port2 = CreateUdpPort(kLocalAddr2);
+ port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ TestConnectivity("udp", std::move(port1), "udp", std::move(port2), true,
+ true, true, true);
+ }
+ void TestLocalToStun(NATType ntype) {
+ auto port1 = CreateUdpPort(kLocalAddr1);
+ port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ nat_server2_ = CreateNatServer(kNatAddr2, ntype);
+ auto port2 = CreateStunPort(kLocalAddr2, &nat_socket_factory2_);
+ port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ TestConnectivity("udp", std::move(port1), StunName(ntype), std::move(port2),
+ ntype == NAT_OPEN_CONE, true, ntype != NAT_SYMMETRIC,
+ true);
+ }
+ void TestLocalToRelay(ProtocolType proto) {
+ auto port1 = CreateUdpPort(kLocalAddr1);
+ port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ auto port2 = CreateRelayPort(kLocalAddr2, proto, PROTO_UDP);
+ port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ TestConnectivity("udp", std::move(port1), RelayName(proto),
+ std::move(port2), false, true, true, true);
+ }
+ void TestStunToLocal(NATType ntype) {
+ nat_server1_ = CreateNatServer(kNatAddr1, ntype);
+ auto port1 = CreateStunPort(kLocalAddr1, &nat_socket_factory1_);
+ port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ auto port2 = CreateUdpPort(kLocalAddr2);
+ port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ TestConnectivity(StunName(ntype), std::move(port1), "udp", std::move(port2),
+ true, ntype != NAT_SYMMETRIC, true, true);
+ }
+ void TestStunToStun(NATType ntype1, NATType ntype2) {
+ nat_server1_ = CreateNatServer(kNatAddr1, ntype1);
+ auto port1 = CreateStunPort(kLocalAddr1, &nat_socket_factory1_);
+ port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ nat_server2_ = CreateNatServer(kNatAddr2, ntype2);
+ auto port2 = CreateStunPort(kLocalAddr2, &nat_socket_factory2_);
+ port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ TestConnectivity(StunName(ntype1), std::move(port1), StunName(ntype2),
+ std::move(port2), ntype2 == NAT_OPEN_CONE,
+ ntype1 != NAT_SYMMETRIC, ntype2 != NAT_SYMMETRIC,
+ ntype1 + ntype2 < (NAT_PORT_RESTRICTED + NAT_SYMMETRIC));
+ }
+ void TestStunToRelay(NATType ntype, ProtocolType proto) {
+ nat_server1_ = CreateNatServer(kNatAddr1, ntype);
+ auto port1 = CreateStunPort(kLocalAddr1, &nat_socket_factory1_);
+ port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ auto port2 = CreateRelayPort(kLocalAddr2, proto, PROTO_UDP);
+ port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ TestConnectivity(StunName(ntype), std::move(port1), RelayName(proto),
+ std::move(port2), false, ntype != NAT_SYMMETRIC, true,
+ true);
+ }
+ void TestTcpToTcp() {
+ auto port1 = CreateTcpPort(kLocalAddr1);
+ port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ auto port2 = CreateTcpPort(kLocalAddr2);
+ port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ TestConnectivity("tcp", std::move(port1), "tcp", std::move(port2), true,
+ false, true, true);
+ }
+ void TestTcpToRelay(ProtocolType proto) {
+ auto port1 = CreateTcpPort(kLocalAddr1);
+ port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ auto port2 = CreateRelayPort(kLocalAddr2, proto, PROTO_TCP);
+ port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ TestConnectivity("tcp", std::move(port1), RelayName(proto),
+ std::move(port2), false, false, true, true);
+ }
+ void TestSslTcpToRelay(ProtocolType proto) {
+ auto port1 = CreateTcpPort(kLocalAddr1);
+ port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ auto port2 = CreateRelayPort(kLocalAddr2, proto, PROTO_SSLTCP);
+ port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ TestConnectivity("ssltcp", std::move(port1), RelayName(proto),
+ std::move(port2), false, false, true, true);
+ }
+
+ rtc::Network* MakeNetwork(const SocketAddress& addr) {
+ networks_.emplace_back("unittest", "unittest", addr.ipaddr(), 32);
+ networks_.back().AddIP(addr.ipaddr());
+ return &networks_.back();
+ }
+
+ rtc::Network* MakeNetworkMultipleAddrs(
+ const SocketAddress& global_addr,
+ const SocketAddress& link_local_addr,
+ const webrtc::FieldTrialsView* field_trials) {
+ networks_.emplace_back("unittest", "unittest", global_addr.ipaddr(), 32,
+ rtc::ADAPTER_TYPE_UNKNOWN, field_trials);
+ networks_.back().AddIP(link_local_addr.ipaddr());
+ networks_.back().AddIP(global_addr.ipaddr());
+ networks_.back().AddIP(link_local_addr.ipaddr());
+ return &networks_.back();
+ }
+
+ // helpers for above functions
+ std::unique_ptr<UDPPort> CreateUdpPort(const SocketAddress& addr) {
+ return CreateUdpPort(addr, &socket_factory_);
+ }
+ std::unique_ptr<UDPPort> CreateUdpPort(const SocketAddress& addr,
+ PacketSocketFactory* socket_factory) {
+ auto port = UDPPort::Create(&main_, socket_factory, MakeNetwork(addr), 0, 0,
+ username_, password_, true, absl::nullopt,
+ &field_trials_);
+ port->SetIceTiebreaker(kTiebreakerDefault);
+ return port;
+ }
+
+ std::unique_ptr<UDPPort> CreateUdpPortMultipleAddrs(
+ const SocketAddress& global_addr,
+ const SocketAddress& link_local_addr,
+ PacketSocketFactory* socket_factory,
+ const webrtc::test::ScopedKeyValueConfig& field_trials) {
+ auto port = UDPPort::Create(
+ &main_, socket_factory,
+ MakeNetworkMultipleAddrs(global_addr, link_local_addr, &field_trials),
+ 0, 0, username_, password_, true, absl::nullopt, &field_trials);
+ port->SetIceTiebreaker(kTiebreakerDefault);
+ return port;
+ }
+ std::unique_ptr<TCPPort> CreateTcpPort(const SocketAddress& addr) {
+ return CreateTcpPort(addr, &socket_factory_);
+ }
+ std::unique_ptr<TCPPort> CreateTcpPort(const SocketAddress& addr,
+ PacketSocketFactory* socket_factory) {
+ auto port = TCPPort::Create(&main_, socket_factory, MakeNetwork(addr), 0, 0,
+ username_, password_, true, &field_trials_);
+ port->SetIceTiebreaker(kTiebreakerDefault);
+ return port;
+ }
+ std::unique_ptr<StunPort> CreateStunPort(const SocketAddress& addr,
+ rtc::PacketSocketFactory* factory) {
+ ServerAddresses stun_servers;
+ stun_servers.insert(kStunAddr);
+ auto port = StunPort::Create(&main_, factory, MakeNetwork(addr), 0, 0,
+ username_, password_, stun_servers,
+ absl::nullopt, &field_trials_);
+ port->SetIceTiebreaker(kTiebreakerDefault);
+ return port;
+ }
+ std::unique_ptr<Port> CreateRelayPort(const SocketAddress& addr,
+ ProtocolType int_proto,
+ ProtocolType ext_proto) {
+ return CreateTurnPort(addr, &socket_factory_, int_proto, ext_proto);
+ }
+ std::unique_ptr<TurnPort> CreateTurnPort(const SocketAddress& addr,
+ PacketSocketFactory* socket_factory,
+ ProtocolType int_proto,
+ ProtocolType ext_proto) {
+ SocketAddress server_addr =
+ int_proto == PROTO_TCP ? kTurnTcpIntAddr : kTurnUdpIntAddr;
+ return CreateTurnPort(addr, socket_factory, int_proto, ext_proto,
+ server_addr);
+ }
+ std::unique_ptr<TurnPort> CreateTurnPort(
+ const SocketAddress& addr,
+ PacketSocketFactory* socket_factory,
+ ProtocolType int_proto,
+ ProtocolType ext_proto,
+ const rtc::SocketAddress& server_addr) {
+ RelayServerConfig config;
+ config.credentials = kRelayCredentials;
+ ProtocolAddress server_address(server_addr, int_proto);
+ CreateRelayPortArgs args;
+ args.network_thread = &main_;
+ args.socket_factory = socket_factory;
+ args.network = MakeNetwork(addr);
+ args.username = username_;
+ args.password = password_;
+ args.server_address = &server_address;
+ args.config = &config;
+ args.field_trials = &field_trials_;
+
+ auto port = TurnPort::Create(args, 0, 0);
+ port->SetIceTiebreaker(kTiebreakerDefault);
+ return port;
+ }
+
+ std::unique_ptr<rtc::NATServer> CreateNatServer(const SocketAddress& addr,
+ rtc::NATType type) {
+ return std::make_unique<rtc::NATServer>(type, ss_.get(), addr, addr,
+ ss_.get(), addr);
+ }
+ static const char* StunName(NATType type) {
+ switch (type) {
+ case NAT_OPEN_CONE:
+ return "stun(open cone)";
+ case NAT_ADDR_RESTRICTED:
+ return "stun(addr restricted)";
+ case NAT_PORT_RESTRICTED:
+ return "stun(port restricted)";
+ case NAT_SYMMETRIC:
+ return "stun(symmetric)";
+ default:
+ return "stun(?)";
+ }
+ }
+ static const char* RelayName(ProtocolType proto) {
+ switch (proto) {
+ case PROTO_UDP:
+ return "turn(udp)";
+ case PROTO_TCP:
+ return "turn(tcp)";
+ case PROTO_SSLTCP:
+ return "turn(ssltcp)";
+ case PROTO_TLS:
+ return "turn(tls)";
+ default:
+ return "turn(?)";
+ }
+ }
+
+ void TestCrossFamilyPorts(int type);
+
+ void ExpectPortsCanConnect(bool can_connect, Port* p1, Port* p2);
+
+ // This does all the work and then deletes `port1` and `port2`.
+ void TestConnectivity(absl::string_view name1,
+ std::unique_ptr<Port> port1,
+ absl::string_view name2,
+ std::unique_ptr<Port> port2,
+ bool accept,
+ bool same_addr1,
+ bool same_addr2,
+ bool possible);
+
+ // This connects the provided channels which have already started. `ch1`
+ // should have its Connection created (either through CreateConnection() or
+ // TCP reconnecting mechanism before entering this function.
+ void ConnectStartedChannels(TestChannel* ch1, TestChannel* ch2) {
+ ASSERT_TRUE(ch1->conn());
+ EXPECT_TRUE_WAIT(ch1->conn()->connected(),
+ kDefaultTimeout); // for TCP connect
+ ch1->Ping();
+ WAIT(!ch2->remote_address().IsNil(), kShortTimeout);
+
+ // Send a ping from dst to src.
+ ch2->AcceptConnection(GetCandidate(ch1->port()));
+ ch2->Ping();
+ EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch2->conn()->write_state(),
+ kDefaultTimeout);
+ }
+
+ // This connects and disconnects the provided channels in the same sequence as
+ // TestConnectivity with all options set to `true`. It does not delete either
+ // channel.
+ void StartConnectAndStopChannels(TestChannel* ch1, TestChannel* ch2) {
+ // Acquire addresses.
+ ch1->Start();
+ ch2->Start();
+
+ ch1->CreateConnection(GetCandidate(ch2->port()));
+ ConnectStartedChannels(ch1, ch2);
+
+ // Destroy the connections.
+ ch1->Stop();
+ ch2->Stop();
+ }
+
+ // This disconnects both end's Connection and make sure ch2 ready for new
+ // connection.
+ void DisconnectTcpTestChannels(TestChannel* ch1, TestChannel* ch2) {
+ TCPConnection* tcp_conn1 = static_cast<TCPConnection*>(ch1->conn());
+ TCPConnection* tcp_conn2 = static_cast<TCPConnection*>(ch2->conn());
+ ASSERT_TRUE(
+ ss_->CloseTcpConnections(tcp_conn1->socket()->GetLocalAddress(),
+ tcp_conn2->socket()->GetLocalAddress()));
+
+ // Wait for both OnClose are delivered.
+ EXPECT_TRUE_WAIT(!ch1->conn()->connected(), kDefaultTimeout);
+ EXPECT_TRUE_WAIT(!ch2->conn()->connected(), kDefaultTimeout);
+
+ // Ensure redundant SignalClose events on TcpConnection won't break tcp
+ // reconnection. Chromium will fire SignalClose for all outstanding IPC
+ // packets during reconnection.
+ tcp_conn1->socket()->NotifyClosedForTest(0);
+ tcp_conn2->socket()->NotifyClosedForTest(0);
+
+ // Speed up destroying ch2's connection such that the test is ready to
+ // accept a new connection from ch1 before ch1's connection destroys itself.
+ ch2->Stop();
+ EXPECT_TRUE_WAIT(ch2->conn() == NULL, kDefaultTimeout);
+ }
+
+ void TestTcpReconnect(bool ping_after_disconnected,
+ bool send_after_disconnected) {
+ auto port1 = CreateTcpPort(kLocalAddr1);
+ port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ auto port2 = CreateTcpPort(kLocalAddr2);
+ port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
+
+ port1->set_component(cricket::ICE_CANDIDATE_COMPONENT_DEFAULT);
+ port2->set_component(cricket::ICE_CANDIDATE_COMPONENT_DEFAULT);
+
+ // Set up channels and ensure both ports will be deleted.
+ TestChannel ch1(std::move(port1));
+ TestChannel ch2(std::move(port2));
+ EXPECT_EQ(0, ch1.complete_count());
+ EXPECT_EQ(0, ch2.complete_count());
+
+ ch1.Start();
+ ch2.Start();
+ ASSERT_EQ_WAIT(1, ch1.complete_count(), kDefaultTimeout);
+ ASSERT_EQ_WAIT(1, ch2.complete_count(), kDefaultTimeout);
+
+ // Initial connecting the channel, create connection on channel1.
+ ch1.CreateConnection(GetCandidate(ch2.port()));
+ ConnectStartedChannels(&ch1, &ch2);
+
+ // Shorten the timeout period.
+ const int kTcpReconnectTimeout = kDefaultTimeout;
+ static_cast<TCPConnection*>(ch1.conn())
+ ->set_reconnection_timeout(kTcpReconnectTimeout);
+ static_cast<TCPConnection*>(ch2.conn())
+ ->set_reconnection_timeout(kTcpReconnectTimeout);
+
+ EXPECT_FALSE(ch1.connection_ready_to_send());
+ EXPECT_FALSE(ch2.connection_ready_to_send());
+
+ // Once connected, disconnect them.
+ DisconnectTcpTestChannels(&ch1, &ch2);
+
+ if (send_after_disconnected || ping_after_disconnected) {
+ if (send_after_disconnected) {
+ // First SendData after disconnect should fail but will trigger
+ // reconnect.
+ EXPECT_EQ(-1, ch1.SendData(data, static_cast<int>(strlen(data))));
+ }
+
+ if (ping_after_disconnected) {
+ // Ping should trigger reconnect.
+ ch1.Ping();
+ }
+
+ // Wait for channel's outgoing TCPConnection connected.
+ EXPECT_TRUE_WAIT(ch1.conn()->connected(), kDefaultTimeout);
+
+ // Verify that we could still connect channels.
+ ConnectStartedChannels(&ch1, &ch2);
+ EXPECT_TRUE_WAIT(ch1.connection_ready_to_send(), kTcpReconnectTimeout);
+ // Channel2 is the passive one so a new connection is created during
+ // reconnect. This new connection should never have issued ENOTCONN
+ // hence the connection_ready_to_send() should be false.
+ EXPECT_FALSE(ch2.connection_ready_to_send());
+ } else {
+ EXPECT_EQ(ch1.conn()->write_state(), Connection::STATE_WRITABLE);
+ // Since the reconnection never happens, the connections should have been
+ // destroyed after the timeout.
+ EXPECT_TRUE_WAIT(!ch1.conn(), kTcpReconnectTimeout + kDefaultTimeout);
+ EXPECT_TRUE(!ch2.conn());
+ }
+
+ // Tear down and ensure that goes smoothly.
+ ch1.Stop();
+ ch2.Stop();
+ EXPECT_TRUE_WAIT(ch1.conn() == NULL, kDefaultTimeout);
+ EXPECT_TRUE_WAIT(ch2.conn() == NULL, kDefaultTimeout);
+ }
+
+ std::unique_ptr<IceMessage> CreateStunMessage(StunMessageType type) {
+ auto msg = std::make_unique<IceMessage>(type, "TESTTESTTEST");
+ return msg;
+ }
+ std::unique_ptr<IceMessage> CreateStunMessageWithUsername(
+ StunMessageType type,
+ absl::string_view username) {
+ std::unique_ptr<IceMessage> msg = CreateStunMessage(type);
+ msg->AddAttribute(std::make_unique<StunByteStringAttribute>(
+ STUN_ATTR_USERNAME, std::string(username)));
+ return msg;
+ }
+ std::unique_ptr<TestPort> CreateTestPort(const rtc::SocketAddress& addr,
+ absl::string_view username,
+ absl::string_view password) {
+ auto port =
+ std::make_unique<TestPort>(&main_, "test", &socket_factory_,
+ MakeNetwork(addr), 0, 0, username, password);
+ port->SignalRoleConflict.connect(this, &PortTest::OnRoleConflict);
+ return port;
+ }
+ std::unique_ptr<TestPort> CreateTestPort(const rtc::SocketAddress& addr,
+ absl::string_view username,
+ absl::string_view password,
+ cricket::IceRole role,
+ int tiebreaker) {
+ auto port = CreateTestPort(addr, username, password);
+ port->SetIceRole(role);
+ port->SetIceTiebreaker(tiebreaker);
+ return port;
+ }
+ // Overload to create a test port given an rtc::Network directly.
+ std::unique_ptr<TestPort> CreateTestPort(const rtc::Network* network,
+ absl::string_view username,
+ absl::string_view password) {
+ auto port = std::make_unique<TestPort>(&main_, "test", &socket_factory_,
+ network, 0, 0, username, password);
+ port->SignalRoleConflict.connect(this, &PortTest::OnRoleConflict);
+ return port;
+ }
+
+ void OnRoleConflict(PortInterface* port) { role_conflict_ = true; }
+ bool role_conflict() const { return role_conflict_; }
+
+ void ConnectToSignalDestroyed(PortInterface* port) {
+ port->SubscribePortDestroyed(
+ [this](PortInterface* port) { OnDestroyed(port); });
+ }
+
+ void OnDestroyed(PortInterface* port) { ++ports_destroyed_; }
+ int ports_destroyed() const { return ports_destroyed_; }
+
+ rtc::BasicPacketSocketFactory* nat_socket_factory1() {
+ return &nat_socket_factory1_;
+ }
+
+ rtc::VirtualSocketServer* vss() { return ss_.get(); }
+
+ private:
+ // When a "create port" helper method is called with an IP, we create a
+ // Network with that IP and add it to this list. Using a list instead of a
+ // vector so that when it grows, pointers aren't invalidated.
+ std::list<rtc::Network> networks_;
+ std::unique_ptr<rtc::VirtualSocketServer> ss_;
+ rtc::AutoSocketServerThread main_;
+ rtc::BasicPacketSocketFactory socket_factory_;
+ std::unique_ptr<rtc::NATServer> nat_server1_;
+ std::unique_ptr<rtc::NATServer> nat_server2_;
+ rtc::NATSocketFactory nat_factory1_;
+ rtc::NATSocketFactory nat_factory2_;
+ rtc::BasicPacketSocketFactory nat_socket_factory1_;
+ rtc::BasicPacketSocketFactory nat_socket_factory2_;
+ std::unique_ptr<TestStunServer> stun_server_;
+ TestTurnServer turn_server_;
+ std::string username_;
+ std::string password_;
+ bool role_conflict_;
+ int ports_destroyed_;
+ webrtc::test::ScopedKeyValueConfig field_trials_;
+};
+
+void PortTest::TestConnectivity(absl::string_view name1,
+ std::unique_ptr<Port> port1,
+ absl::string_view name2,
+ std::unique_ptr<Port> port2,
+ bool accept,
+ bool same_addr1,
+ bool same_addr2,
+ bool possible) {
+ rtc::ScopedFakeClock clock;
+ RTC_LOG(LS_INFO) << "Test: " << name1 << " to " << name2 << ": ";
+ port1->set_component(cricket::ICE_CANDIDATE_COMPONENT_DEFAULT);
+ port2->set_component(cricket::ICE_CANDIDATE_COMPONENT_DEFAULT);
+
+ // Set up channels and ensure both ports will be deleted.
+ TestChannel ch1(std::move(port1));
+ TestChannel ch2(std::move(port2));
+ EXPECT_EQ(0, ch1.complete_count());
+ EXPECT_EQ(0, ch2.complete_count());
+
+ // Acquire addresses.
+ ch1.Start();
+ ch2.Start();
+ ASSERT_EQ_SIMULATED_WAIT(1, ch1.complete_count(), kDefaultTimeout, clock);
+ ASSERT_EQ_SIMULATED_WAIT(1, ch2.complete_count(), kDefaultTimeout, clock);
+
+ // Send a ping from src to dst. This may or may not make it.
+ ch1.CreateConnection(GetCandidate(ch2.port()));
+ ASSERT_TRUE(ch1.conn() != NULL);
+ EXPECT_TRUE_SIMULATED_WAIT(ch1.conn()->connected(), kDefaultTimeout,
+ clock); // for TCP connect
+ ch1.Ping();
+ SIMULATED_WAIT(!ch2.remote_address().IsNil(), kShortTimeout, clock);
+
+ if (accept) {
+ // We are able to send a ping from src to dst. This is the case when
+ // sending to UDP ports and cone NATs.
+ EXPECT_TRUE(ch1.remote_address().IsNil());
+ EXPECT_EQ(ch2.remote_fragment(), ch1.port()->username_fragment());
+
+ // Ensure the ping came from the same address used for src.
+ // This is the case unless the source NAT was symmetric.
+ if (same_addr1)
+ EXPECT_EQ(ch2.remote_address(), GetAddress(ch1.port()));
+ EXPECT_TRUE(same_addr2);
+
+ // Send a ping from dst to src.
+ ch2.AcceptConnection(GetCandidate(ch1.port()));
+ ASSERT_TRUE(ch2.conn() != NULL);
+ ch2.Ping();
+ EXPECT_EQ_SIMULATED_WAIT(Connection::STATE_WRITABLE,
+ ch2.conn()->write_state(), kDefaultTimeout, clock);
+ } else {
+ // We can't send a ping from src to dst, so flip it around. This will happen
+ // when the destination NAT is addr/port restricted or symmetric.
+ EXPECT_TRUE(ch1.remote_address().IsNil());
+ EXPECT_TRUE(ch2.remote_address().IsNil());
+
+ // Send a ping from dst to src. Again, this may or may not make it.
+ ch2.CreateConnection(GetCandidate(ch1.port()));
+ ASSERT_TRUE(ch2.conn() != NULL);
+ ch2.Ping();
+ SIMULATED_WAIT(ch2.conn()->write_state() == Connection::STATE_WRITABLE,
+ kShortTimeout, clock);
+
+ if (same_addr1 && same_addr2) {
+ // The new ping got back to the source.
+ EXPECT_TRUE(ch1.conn()->receiving());
+ EXPECT_EQ(Connection::STATE_WRITABLE, ch2.conn()->write_state());
+
+ // First connection may not be writable if the first ping did not get
+ // through. So we will have to do another.
+ if (ch1.conn()->write_state() == Connection::STATE_WRITE_INIT) {
+ ch1.Ping();
+ EXPECT_EQ_SIMULATED_WAIT(Connection::STATE_WRITABLE,
+ ch1.conn()->write_state(), kDefaultTimeout,
+ clock);
+ }
+ } else if (!same_addr1 && possible) {
+ // The new ping went to the candidate address, but that address was bad.
+ // This will happen when the source NAT is symmetric.
+ EXPECT_TRUE(ch1.remote_address().IsNil());
+ EXPECT_TRUE(ch2.remote_address().IsNil());
+
+ // However, since we have now sent a ping to the source IP, we should be
+ // able to get a ping from it. This gives us the real source address.
+ ch1.Ping();
+ EXPECT_TRUE_SIMULATED_WAIT(!ch2.remote_address().IsNil(), kDefaultTimeout,
+ clock);
+ EXPECT_FALSE(ch2.conn()->receiving());
+ EXPECT_TRUE(ch1.remote_address().IsNil());
+
+ // Pick up the actual address and establish the connection.
+ ch2.AcceptConnection(GetCandidate(ch1.port()));
+ ASSERT_TRUE(ch2.conn() != NULL);
+ ch2.Ping();
+ EXPECT_EQ_SIMULATED_WAIT(Connection::STATE_WRITABLE,
+ ch2.conn()->write_state(), kDefaultTimeout,
+ clock);
+ } else if (!same_addr2 && possible) {
+ // The new ping came in, but from an unexpected address. This will happen
+ // when the destination NAT is symmetric.
+ EXPECT_FALSE(ch1.remote_address().IsNil());
+ EXPECT_FALSE(ch1.conn()->receiving());
+
+ // Update our address and complete the connection.
+ ch1.AcceptConnection(GetCandidate(ch2.port()));
+ ch1.Ping();
+ EXPECT_EQ_SIMULATED_WAIT(Connection::STATE_WRITABLE,
+ ch1.conn()->write_state(), kDefaultTimeout,
+ clock);
+ } else { // (!possible)
+ // There should be s no way for the pings to reach each other. Check it.
+ EXPECT_TRUE(ch1.remote_address().IsNil());
+ EXPECT_TRUE(ch2.remote_address().IsNil());
+ ch1.Ping();
+ SIMULATED_WAIT(!ch2.remote_address().IsNil(), kShortTimeout, clock);
+ EXPECT_TRUE(ch1.remote_address().IsNil());
+ EXPECT_TRUE(ch2.remote_address().IsNil());
+ }
+ }
+
+ // Everything should be good, unless we know the situation is impossible.
+ ASSERT_TRUE(ch1.conn() != NULL);
+ ASSERT_TRUE(ch2.conn() != NULL);
+ if (possible) {
+ EXPECT_TRUE(ch1.conn()->receiving());
+ EXPECT_EQ(Connection::STATE_WRITABLE, ch1.conn()->write_state());
+ EXPECT_TRUE(ch2.conn()->receiving());
+ EXPECT_EQ(Connection::STATE_WRITABLE, ch2.conn()->write_state());
+ } else {
+ EXPECT_FALSE(ch1.conn()->receiving());
+ EXPECT_NE(Connection::STATE_WRITABLE, ch1.conn()->write_state());
+ EXPECT_FALSE(ch2.conn()->receiving());
+ EXPECT_NE(Connection::STATE_WRITABLE, ch2.conn()->write_state());
+ }
+
+ // Tear down and ensure that goes smoothly.
+ ch1.Stop();
+ ch2.Stop();
+ EXPECT_TRUE_SIMULATED_WAIT(ch1.conn() == NULL, kDefaultTimeout, clock);
+ EXPECT_TRUE_SIMULATED_WAIT(ch2.conn() == NULL, kDefaultTimeout, clock);
+}
+
+class FakePacketSocketFactory : public rtc::PacketSocketFactory {
+ public:
+ FakePacketSocketFactory()
+ : next_udp_socket_(NULL), next_server_tcp_socket_(NULL) {}
+ ~FakePacketSocketFactory() override {}
+
+ AsyncPacketSocket* CreateUdpSocket(const SocketAddress& address,
+ uint16_t min_port,
+ uint16_t max_port) override {
+ EXPECT_TRUE(next_udp_socket_ != NULL);
+ AsyncPacketSocket* result = next_udp_socket_;
+ next_udp_socket_ = NULL;
+ return result;
+ }
+
+ AsyncListenSocket* CreateServerTcpSocket(const SocketAddress& local_address,
+ uint16_t min_port,
+ uint16_t max_port,
+ int opts) override {
+ EXPECT_TRUE(next_server_tcp_socket_ != NULL);
+ AsyncListenSocket* result = next_server_tcp_socket_;
+ next_server_tcp_socket_ = NULL;
+ return result;
+ }
+
+ AsyncPacketSocket* CreateClientTcpSocket(
+ const SocketAddress& local_address,
+ const SocketAddress& remote_address,
+ const rtc::ProxyInfo& proxy_info,
+ const std::string& user_agent,
+ const rtc::PacketSocketTcpOptions& opts) override {
+ EXPECT_TRUE(next_client_tcp_socket_.has_value());
+ AsyncPacketSocket* result = *next_client_tcp_socket_;
+ next_client_tcp_socket_ = nullptr;
+ return result;
+ }
+
+ void set_next_udp_socket(AsyncPacketSocket* next_udp_socket) {
+ next_udp_socket_ = next_udp_socket;
+ }
+ void set_next_server_tcp_socket(AsyncListenSocket* next_server_tcp_socket) {
+ next_server_tcp_socket_ = next_server_tcp_socket;
+ }
+ void set_next_client_tcp_socket(AsyncPacketSocket* next_client_tcp_socket) {
+ next_client_tcp_socket_ = next_client_tcp_socket;
+ }
+ std::unique_ptr<webrtc::AsyncDnsResolverInterface> CreateAsyncDnsResolver()
+ override {
+ return nullptr;
+ }
+
+ private:
+ AsyncPacketSocket* next_udp_socket_;
+ AsyncListenSocket* next_server_tcp_socket_;
+ absl::optional<AsyncPacketSocket*> next_client_tcp_socket_;
+};
+
+class FakeAsyncPacketSocket : public AsyncPacketSocket {
+ public:
+ // Returns current local address. Address may be set to NULL if the
+ // socket is not bound yet (GetState() returns STATE_BINDING).
+ virtual SocketAddress GetLocalAddress() const { return local_address_; }
+
+ // Returns remote address. Returns zeroes if this is not a client TCP socket.
+ virtual SocketAddress GetRemoteAddress() const { return remote_address_; }
+
+ // Send a packet.
+ virtual int Send(const void* pv,
+ size_t cb,
+ const rtc::PacketOptions& options) {
+ if (error_ == 0) {
+ return static_cast<int>(cb);
+ } else {
+ return -1;
+ }
+ }
+ virtual int SendTo(const void* pv,
+ size_t cb,
+ const SocketAddress& addr,
+ const rtc::PacketOptions& options) {
+ if (error_ == 0) {
+ return static_cast<int>(cb);
+ } else {
+ return -1;
+ }
+ }
+ virtual int Close() { return 0; }
+
+ virtual State GetState() const { return state_; }
+ virtual int GetOption(Socket::Option opt, int* value) { return 0; }
+ virtual int SetOption(Socket::Option opt, int value) { return 0; }
+ virtual int GetError() const { return 0; }
+ virtual void SetError(int error) { error_ = error; }
+
+ void set_state(State state) { state_ = state; }
+
+ SocketAddress local_address_;
+ SocketAddress remote_address_;
+
+ private:
+ int error_ = 0;
+ State state_;
+};
+
+class FakeAsyncListenSocket : public AsyncListenSocket {
+ public:
+ // Returns current local address. Address may be set to NULL if the
+ // socket is not bound yet (GetState() returns STATE_BINDING).
+ virtual SocketAddress GetLocalAddress() const { return local_address_; }
+ void Bind(const SocketAddress& address) {
+ local_address_ = address;
+ state_ = State::kBound;
+ }
+ virtual int GetOption(Socket::Option opt, int* value) { return 0; }
+ virtual int SetOption(Socket::Option opt, int value) { return 0; }
+ virtual State GetState() const { return state_; }
+
+ private:
+ SocketAddress local_address_;
+ State state_ = State::kClosed;
+};
+
+// Local -> XXXX
+TEST_F(PortTest, TestLocalToLocal) {
+ TestLocalToLocal();
+}
+
+TEST_F(PortTest, TestLocalToConeNat) {
+ TestLocalToStun(NAT_OPEN_CONE);
+}
+
+TEST_F(PortTest, TestLocalToARNat) {
+ TestLocalToStun(NAT_ADDR_RESTRICTED);
+}
+
+TEST_F(PortTest, TestLocalToPRNat) {
+ TestLocalToStun(NAT_PORT_RESTRICTED);
+}
+
+TEST_F(PortTest, TestLocalToSymNat) {
+ TestLocalToStun(NAT_SYMMETRIC);
+}
+
+// Flaky: https://code.google.com/p/webrtc/issues/detail?id=3316.
+TEST_F(PortTest, DISABLED_TestLocalToTurn) {
+ TestLocalToRelay(PROTO_UDP);
+}
+
+// Cone NAT -> XXXX
+TEST_F(PortTest, TestConeNatToLocal) {
+ TestStunToLocal(NAT_OPEN_CONE);
+}
+
+TEST_F(PortTest, TestConeNatToConeNat) {
+ TestStunToStun(NAT_OPEN_CONE, NAT_OPEN_CONE);
+}
+
+TEST_F(PortTest, TestConeNatToARNat) {
+ TestStunToStun(NAT_OPEN_CONE, NAT_ADDR_RESTRICTED);
+}
+
+TEST_F(PortTest, TestConeNatToPRNat) {
+ TestStunToStun(NAT_OPEN_CONE, NAT_PORT_RESTRICTED);
+}
+
+TEST_F(PortTest, TestConeNatToSymNat) {
+ TestStunToStun(NAT_OPEN_CONE, NAT_SYMMETRIC);
+}
+
+TEST_F(PortTest, TestConeNatToTurn) {
+ TestStunToRelay(NAT_OPEN_CONE, PROTO_UDP);
+}
+
+// Address-restricted NAT -> XXXX
+TEST_F(PortTest, TestARNatToLocal) {
+ TestStunToLocal(NAT_ADDR_RESTRICTED);
+}
+
+TEST_F(PortTest, TestARNatToConeNat) {
+ TestStunToStun(NAT_ADDR_RESTRICTED, NAT_OPEN_CONE);
+}
+
+TEST_F(PortTest, TestARNatToARNat) {
+ TestStunToStun(NAT_ADDR_RESTRICTED, NAT_ADDR_RESTRICTED);
+}
+
+TEST_F(PortTest, TestARNatToPRNat) {
+ TestStunToStun(NAT_ADDR_RESTRICTED, NAT_PORT_RESTRICTED);
+}
+
+TEST_F(PortTest, TestARNatToSymNat) {
+ TestStunToStun(NAT_ADDR_RESTRICTED, NAT_SYMMETRIC);
+}
+
+TEST_F(PortTest, TestARNatToTurn) {
+ TestStunToRelay(NAT_ADDR_RESTRICTED, PROTO_UDP);
+}
+
+// Port-restricted NAT -> XXXX
+TEST_F(PortTest, TestPRNatToLocal) {
+ TestStunToLocal(NAT_PORT_RESTRICTED);
+}
+
+TEST_F(PortTest, TestPRNatToConeNat) {
+ TestStunToStun(NAT_PORT_RESTRICTED, NAT_OPEN_CONE);
+}
+
+TEST_F(PortTest, TestPRNatToARNat) {
+ TestStunToStun(NAT_PORT_RESTRICTED, NAT_ADDR_RESTRICTED);
+}
+
+TEST_F(PortTest, TestPRNatToPRNat) {
+ TestStunToStun(NAT_PORT_RESTRICTED, NAT_PORT_RESTRICTED);
+}
+
+TEST_F(PortTest, TestPRNatToSymNat) {
+ // Will "fail"
+ TestStunToStun(NAT_PORT_RESTRICTED, NAT_SYMMETRIC);
+}
+
+TEST_F(PortTest, TestPRNatToTurn) {
+ TestStunToRelay(NAT_PORT_RESTRICTED, PROTO_UDP);
+}
+
+// Symmetric NAT -> XXXX
+TEST_F(PortTest, TestSymNatToLocal) {
+ TestStunToLocal(NAT_SYMMETRIC);
+}
+
+TEST_F(PortTest, TestSymNatToConeNat) {
+ TestStunToStun(NAT_SYMMETRIC, NAT_OPEN_CONE);
+}
+
+TEST_F(PortTest, TestSymNatToARNat) {
+ TestStunToStun(NAT_SYMMETRIC, NAT_ADDR_RESTRICTED);
+}
+
+TEST_F(PortTest, TestSymNatToPRNat) {
+ // Will "fail"
+ TestStunToStun(NAT_SYMMETRIC, NAT_PORT_RESTRICTED);
+}
+
+TEST_F(PortTest, TestSymNatToSymNat) {
+ // Will "fail"
+ TestStunToStun(NAT_SYMMETRIC, NAT_SYMMETRIC);
+}
+
+TEST_F(PortTest, TestSymNatToTurn) {
+ TestStunToRelay(NAT_SYMMETRIC, PROTO_UDP);
+}
+
+// Outbound TCP -> XXXX
+TEST_F(PortTest, TestTcpToTcp) {
+ TestTcpToTcp();
+}
+
+TEST_F(PortTest, TestTcpReconnectOnSendPacket) {
+ TestTcpReconnect(false /* ping */, true /* send */);
+}
+
+TEST_F(PortTest, TestTcpReconnectOnPing) {
+ TestTcpReconnect(true /* ping */, false /* send */);
+}
+
+TEST_F(PortTest, TestTcpReconnectTimeout) {
+ TestTcpReconnect(false /* ping */, false /* send */);
+}
+
+// Test when TcpConnection never connects, the OnClose() will be called to
+// destroy the connection.
+TEST_F(PortTest, TestTcpNeverConnect) {
+ auto port1 = CreateTcpPort(kLocalAddr1);
+ port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ port1->set_component(cricket::ICE_CANDIDATE_COMPONENT_DEFAULT);
+
+ // Set up a channel and ensure the port will be deleted.
+ TestChannel ch1(std::move(port1));
+ EXPECT_EQ(0, ch1.complete_count());
+
+ ch1.Start();
+ ASSERT_EQ_WAIT(1, ch1.complete_count(), kDefaultTimeout);
+
+ std::unique_ptr<rtc::Socket> server(
+ vss()->CreateSocket(kLocalAddr2.family(), SOCK_STREAM));
+ // Bind but not listen.
+ EXPECT_EQ(0, server->Bind(kLocalAddr2));
+
+ Candidate c = GetCandidate(ch1.port());
+ c.set_address(server->GetLocalAddress());
+
+ ch1.CreateConnection(c);
+ EXPECT_TRUE(ch1.conn());
+ EXPECT_TRUE_WAIT(!ch1.conn(), kDefaultTimeout); // for TCP connect
+}
+
+/* TODO(?): Enable these once testrelayserver can accept external TCP.
+TEST_F(PortTest, TestTcpToTcpRelay) {
+ TestTcpToRelay(PROTO_TCP);
+}
+
+TEST_F(PortTest, TestTcpToSslTcpRelay) {
+ TestTcpToRelay(PROTO_SSLTCP);
+}
+*/
+
+// Outbound SSLTCP -> XXXX
+/* TODO(?): Enable these once testrelayserver can accept external SSL.
+TEST_F(PortTest, TestSslTcpToTcpRelay) {
+ TestSslTcpToRelay(PROTO_TCP);
+}
+
+TEST_F(PortTest, TestSslTcpToSslTcpRelay) {
+ TestSslTcpToRelay(PROTO_SSLTCP);
+}
+*/
+
+// Test that a connection will be dead and deleted if
+// i) it has never received anything for MIN_CONNECTION_LIFETIME milliseconds
+// since it was created, or
+// ii) it has not received anything for DEAD_CONNECTION_RECEIVE_TIMEOUT
+// milliseconds since last receiving.
+TEST_F(PortTest, TestConnectionDead) {
+ TestChannel ch1(CreateUdpPort(kLocalAddr1));
+ TestChannel ch2(CreateUdpPort(kLocalAddr2));
+ // Acquire address.
+ ch1.Start();
+ ch2.Start();
+ ASSERT_EQ_WAIT(1, ch1.complete_count(), kDefaultTimeout);
+ ASSERT_EQ_WAIT(1, ch2.complete_count(), kDefaultTimeout);
+
+ // Test case that the connection has never received anything.
+ int64_t before_created = rtc::TimeMillis();
+ ch1.CreateConnection(GetCandidate(ch2.port()));
+ int64_t after_created = rtc::TimeMillis();
+ Connection* conn = ch1.conn();
+ ASSERT_NE(conn, nullptr);
+ // It is not dead if it is after MIN_CONNECTION_LIFETIME but not pruned.
+ conn->UpdateState(after_created + MIN_CONNECTION_LIFETIME + 1);
+ rtc::Thread::Current()->ProcessMessages(0);
+ EXPECT_TRUE(ch1.conn() != nullptr);
+ // It is not dead if it is before MIN_CONNECTION_LIFETIME and pruned.
+ conn->UpdateState(before_created + MIN_CONNECTION_LIFETIME - 1);
+ conn->Prune();
+ rtc::Thread::Current()->ProcessMessages(0);
+ EXPECT_TRUE(ch1.conn() != nullptr);
+ // It will be dead after MIN_CONNECTION_LIFETIME and pruned.
+ conn->UpdateState(after_created + MIN_CONNECTION_LIFETIME + 1);
+ EXPECT_TRUE_WAIT(ch1.conn() == nullptr, kDefaultTimeout);
+
+ // Test case that the connection has received something.
+ // Create a connection again and receive a ping.
+ ch1.CreateConnection(GetCandidate(ch2.port()));
+ conn = ch1.conn();
+ ASSERT_NE(conn, nullptr);
+ int64_t before_last_receiving = rtc::TimeMillis();
+ conn->ReceivedPing();
+ int64_t after_last_receiving = rtc::TimeMillis();
+ // The connection will be dead after DEAD_CONNECTION_RECEIVE_TIMEOUT
+ conn->UpdateState(before_last_receiving + DEAD_CONNECTION_RECEIVE_TIMEOUT -
+ 1);
+ rtc::Thread::Current()->ProcessMessages(100);
+ EXPECT_TRUE(ch1.conn() != nullptr);
+ conn->UpdateState(after_last_receiving + DEAD_CONNECTION_RECEIVE_TIMEOUT + 1);
+ EXPECT_TRUE_WAIT(ch1.conn() == nullptr, kDefaultTimeout);
+}
+
+TEST_F(PortTest, TestConnectionDeadWithDeadConnectionTimeout) {
+ TestChannel ch1(CreateUdpPort(kLocalAddr1));
+ TestChannel ch2(CreateUdpPort(kLocalAddr2));
+ // Acquire address.
+ ch1.Start();
+ ch2.Start();
+ ASSERT_EQ_WAIT(1, ch1.complete_count(), kDefaultTimeout);
+ ASSERT_EQ_WAIT(1, ch2.complete_count(), kDefaultTimeout);
+
+ // Note: set field trials manually since they are parsed by
+ // P2PTransportChannel but P2PTransportChannel is not used in this test.
+ IceFieldTrials field_trials;
+ field_trials.dead_connection_timeout_ms = 90000;
+
+ // Create a connection again and receive a ping.
+ ch1.CreateConnection(GetCandidate(ch2.port()));
+ auto conn = ch1.conn();
+ conn->SetIceFieldTrials(&field_trials);
+
+ ASSERT_NE(conn, nullptr);
+ int64_t before_last_receiving = rtc::TimeMillis();
+ conn->ReceivedPing();
+ int64_t after_last_receiving = rtc::TimeMillis();
+ // The connection will be dead after 90s
+ conn->UpdateState(before_last_receiving + 90000 - 1);
+ rtc::Thread::Current()->ProcessMessages(100);
+ EXPECT_TRUE(ch1.conn() != nullptr);
+ conn->UpdateState(after_last_receiving + 90000 + 1);
+ EXPECT_TRUE_WAIT(ch1.conn() == nullptr, kDefaultTimeout);
+}
+
+TEST_F(PortTest, TestConnectionDeadOutstandingPing) {
+ auto port1 = CreateUdpPort(kLocalAddr1);
+ port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ port1->SetIceTiebreaker(kTiebreaker1);
+ auto port2 = CreateUdpPort(kLocalAddr2);
+ port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ port2->SetIceTiebreaker(kTiebreaker2);
+
+ TestChannel ch1(std::move(port1));
+ TestChannel ch2(std::move(port2));
+ // Acquire address.
+ ch1.Start();
+ ch2.Start();
+ ASSERT_EQ_WAIT(1, ch1.complete_count(), kDefaultTimeout);
+ ASSERT_EQ_WAIT(1, ch2.complete_count(), kDefaultTimeout);
+
+ // Note: set field trials manually since they are parsed by
+ // P2PTransportChannel but P2PTransportChannel is not used in this test.
+ IceFieldTrials field_trials;
+ field_trials.dead_connection_timeout_ms = 360000;
+
+ // Create a connection again and receive a ping and then send
+ // a ping and keep it outstanding.
+ ch1.CreateConnection(GetCandidate(ch2.port()));
+ auto conn = ch1.conn();
+ conn->SetIceFieldTrials(&field_trials);
+
+ ASSERT_NE(conn, nullptr);
+ conn->ReceivedPing();
+ int64_t send_ping_timestamp = rtc::TimeMillis();
+ conn->Ping(send_ping_timestamp);
+
+ // The connection will be dead 30s after the ping was sent.
+ conn->UpdateState(send_ping_timestamp + DEAD_CONNECTION_RECEIVE_TIMEOUT - 1);
+ rtc::Thread::Current()->ProcessMessages(100);
+ EXPECT_TRUE(ch1.conn() != nullptr);
+ conn->UpdateState(send_ping_timestamp + DEAD_CONNECTION_RECEIVE_TIMEOUT + 1);
+ EXPECT_TRUE_WAIT(ch1.conn() == nullptr, kDefaultTimeout);
+}
+
+// This test case verifies standard ICE features in STUN messages. Currently it
+// verifies Message Integrity attribute in STUN messages and username in STUN
+// binding request will have colon (":") between remote and local username.
+TEST_F(PortTest, TestLocalToLocalStandard) {
+ auto port1 = CreateUdpPort(kLocalAddr1);
+ port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ port1->SetIceTiebreaker(kTiebreaker1);
+ auto port2 = CreateUdpPort(kLocalAddr2);
+ port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ port2->SetIceTiebreaker(kTiebreaker2);
+ // Same parameters as TestLocalToLocal above.
+ TestConnectivity("udp", std::move(port1), "udp", std::move(port2), true, true,
+ true, true);
+}
+
+// This test is trying to validate a successful and failure scenario in a
+// loopback test when protocol is RFC5245. For success IceTiebreaker, username
+// should remain equal to the request generated by the port and role of port
+// must be in controlling.
+TEST_F(PortTest, TestLoopbackCall) {
+ auto lport = CreateTestPort(kLocalAddr1, "lfrag", "lpass");
+ lport->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ lport->SetIceTiebreaker(kTiebreaker1);
+ lport->PrepareAddress();
+ ASSERT_FALSE(lport->Candidates().empty());
+ Connection* conn =
+ lport->CreateConnection(lport->Candidates()[0], Port::ORIGIN_MESSAGE);
+ conn->Ping(0);
+
+ ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, kDefaultTimeout);
+ IceMessage* msg = lport->last_stun_msg();
+ EXPECT_EQ(STUN_BINDING_REQUEST, msg->type());
+ conn->OnReadPacket(lport->last_stun_buf()->data<char>(),
+ lport->last_stun_buf()->size(), /* packet_time_us */ -1);
+ ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, kDefaultTimeout);
+ msg = lport->last_stun_msg();
+ EXPECT_EQ(STUN_BINDING_RESPONSE, msg->type());
+
+ // If the tiebreaker value is different from port, we expect a error
+ // response.
+ lport->Reset();
+ lport->AddCandidateAddress(kLocalAddr2);
+ // Creating a different connection as `conn` is receiving.
+ Connection* conn1 =
+ lport->CreateConnection(lport->Candidates()[1], Port::ORIGIN_MESSAGE);
+ conn1->Ping(0);
+
+ ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, kDefaultTimeout);
+ msg = lport->last_stun_msg();
+ EXPECT_EQ(STUN_BINDING_REQUEST, msg->type());
+ std::unique_ptr<IceMessage> modified_req(
+ CreateStunMessage(STUN_BINDING_REQUEST));
+ const StunByteStringAttribute* username_attr =
+ msg->GetByteString(STUN_ATTR_USERNAME);
+ modified_req->AddAttribute(std::make_unique<StunByteStringAttribute>(
+ STUN_ATTR_USERNAME, username_attr->string_view()));
+ // To make sure we receive error response, adding tiebreaker less than
+ // what's present in request.
+ modified_req->AddAttribute(std::make_unique<StunUInt64Attribute>(
+ STUN_ATTR_ICE_CONTROLLING, kTiebreaker1 - 1));
+ modified_req->AddMessageIntegrity("lpass");
+ modified_req->AddFingerprint();
+
+ lport->Reset();
+ auto buf = std::make_unique<ByteBufferWriter>();
+ WriteStunMessage(*modified_req, buf.get());
+ conn1->OnReadPacket(buf->Data(), buf->Length(), /* packet_time_us */ -1);
+ ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, kDefaultTimeout);
+ msg = lport->last_stun_msg();
+ EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, msg->type());
+}
+
+// This test verifies role conflict signal is received when there is
+// conflict in the role. In this case both ports are in controlling and
+// `rport` has higher tiebreaker value than `lport`. Since `lport` has lower
+// value of tiebreaker, when it receives ping request from `rport` it will
+// send role conflict signal.
+TEST_F(PortTest, TestIceRoleConflict) {
+ auto lport = CreateTestPort(kLocalAddr1, "lfrag", "lpass");
+ lport->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ lport->SetIceTiebreaker(kTiebreaker1);
+ auto rport = CreateTestPort(kLocalAddr2, "rfrag", "rpass");
+ rport->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ rport->SetIceTiebreaker(kTiebreaker2);
+
+ lport->PrepareAddress();
+ rport->PrepareAddress();
+ ASSERT_FALSE(lport->Candidates().empty());
+ ASSERT_FALSE(rport->Candidates().empty());
+ Connection* lconn =
+ lport->CreateConnection(rport->Candidates()[0], Port::ORIGIN_MESSAGE);
+ Connection* rconn =
+ rport->CreateConnection(lport->Candidates()[0], Port::ORIGIN_MESSAGE);
+ rconn->Ping(0);
+
+ ASSERT_TRUE_WAIT(rport->last_stun_msg() != NULL, kDefaultTimeout);
+ IceMessage* msg = rport->last_stun_msg();
+ EXPECT_EQ(STUN_BINDING_REQUEST, msg->type());
+ // Send rport binding request to lport.
+ lconn->OnReadPacket(rport->last_stun_buf()->data<char>(),
+ rport->last_stun_buf()->size(), /* packet_time_us */ -1);
+
+ ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, kDefaultTimeout);
+ EXPECT_EQ(STUN_BINDING_RESPONSE, lport->last_stun_msg()->type());
+ EXPECT_TRUE(role_conflict());
+}
+
+TEST_F(PortTest, TestTcpNoDelay) {
+ rtc::ScopedFakeClock clock;
+ auto port1 = CreateTcpPort(kLocalAddr1);
+ port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ int option_value = -1;
+ int success = port1->GetOption(rtc::Socket::OPT_NODELAY, &option_value);
+ ASSERT_EQ(0, success); // GetOption() should complete successfully w/ 0
+ EXPECT_EQ(1, option_value);
+
+ auto port2 = CreateTcpPort(kLocalAddr2);
+ port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
+
+ // Set up a connection, and verify that option is set on connected sockets at
+ // both ends.
+ TestChannel ch1(std::move(port1));
+ TestChannel ch2(std::move(port2));
+ // Acquire addresses.
+ ch1.Start();
+ ch2.Start();
+ ASSERT_EQ_SIMULATED_WAIT(1, ch1.complete_count(), kDefaultTimeout, clock);
+ ASSERT_EQ_SIMULATED_WAIT(1, ch2.complete_count(), kDefaultTimeout, clock);
+ // Connect and send a ping from src to dst.
+ ch1.CreateConnection(GetCandidate(ch2.port()));
+ ASSERT_TRUE(ch1.conn() != NULL);
+ EXPECT_TRUE_SIMULATED_WAIT(ch1.conn()->connected(), kDefaultTimeout,
+ clock); // for TCP connect
+ ch1.Ping();
+ SIMULATED_WAIT(!ch2.remote_address().IsNil(), kShortTimeout, clock);
+
+ // Accept the connection.
+ ch2.AcceptConnection(GetCandidate(ch1.port()));
+ ASSERT_TRUE(ch2.conn() != NULL);
+
+ option_value = -1;
+ success = static_cast<TCPConnection*>(ch1.conn())
+ ->socket()
+ ->GetOption(rtc::Socket::OPT_NODELAY, &option_value);
+ ASSERT_EQ(0, success);
+ EXPECT_EQ(1, option_value);
+
+ option_value = -1;
+ success = static_cast<TCPConnection*>(ch2.conn())
+ ->socket()
+ ->GetOption(rtc::Socket::OPT_NODELAY, &option_value);
+ ASSERT_EQ(0, success);
+ EXPECT_EQ(1, option_value);
+}
+
+TEST_F(PortTest, TestDelayedBindingUdp) {
+ FakeAsyncPacketSocket* socket = new FakeAsyncPacketSocket();
+ FakePacketSocketFactory socket_factory;
+
+ socket_factory.set_next_udp_socket(socket);
+ auto port = CreateUdpPort(kLocalAddr1, &socket_factory);
+
+ socket->set_state(AsyncPacketSocket::STATE_BINDING);
+ port->PrepareAddress();
+
+ EXPECT_EQ(0U, port->Candidates().size());
+ socket->SignalAddressReady(socket, kLocalAddr2);
+
+ EXPECT_EQ(1U, port->Candidates().size());
+}
+
+TEST_F(PortTest, TestDisableInterfaceOfTcpPort) {
+ FakeAsyncListenSocket* lsocket = new FakeAsyncListenSocket();
+ FakeAsyncListenSocket* rsocket = new FakeAsyncListenSocket();
+ FakePacketSocketFactory socket_factory;
+
+ socket_factory.set_next_server_tcp_socket(lsocket);
+ auto lport = CreateTcpPort(kLocalAddr1, &socket_factory);
+
+ socket_factory.set_next_server_tcp_socket(rsocket);
+ auto rport = CreateTcpPort(kLocalAddr2, &socket_factory);
+
+ lsocket->Bind(kLocalAddr1);
+ rsocket->Bind(kLocalAddr2);
+
+ lport->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ lport->SetIceTiebreaker(kTiebreaker1);
+ rport->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ rport->SetIceTiebreaker(kTiebreaker2);
+
+ lport->PrepareAddress();
+ rport->PrepareAddress();
+ ASSERT_FALSE(rport->Candidates().empty());
+
+ // A client socket.
+ FakeAsyncPacketSocket* socket = new FakeAsyncPacketSocket();
+ socket->local_address_ = kLocalAddr1;
+ socket->remote_address_ = kLocalAddr2;
+ socket_factory.set_next_client_tcp_socket(socket);
+ Connection* lconn =
+ lport->CreateConnection(rport->Candidates()[0], Port::ORIGIN_MESSAGE);
+ ASSERT_NE(lconn, nullptr);
+ socket->SignalConnect(socket);
+ lconn->Ping(0);
+
+ // Now disconnect the client socket...
+ socket->NotifyClosedForTest(1);
+
+ // And prevent new sockets from being created.
+ socket_factory.set_next_client_tcp_socket(nullptr);
+
+ // Test that Ping() does not cause SEGV.
+ lconn->Ping(0);
+}
+
+void PortTest::TestCrossFamilyPorts(int type) {
+ FakePacketSocketFactory factory;
+ std::unique_ptr<Port> ports[4];
+ SocketAddress addresses[4] = {
+ SocketAddress("192.168.1.3", 0), SocketAddress("192.168.1.4", 0),
+ SocketAddress("2001:db8::1", 0), SocketAddress("2001:db8::2", 0)};
+ for (int i = 0; i < 4; i++) {
+ if (type == SOCK_DGRAM) {
+ FakeAsyncPacketSocket* socket = new FakeAsyncPacketSocket();
+ factory.set_next_udp_socket(socket);
+ ports[i] = CreateUdpPort(addresses[i], &factory);
+ socket->set_state(AsyncPacketSocket::STATE_BINDING);
+ socket->SignalAddressReady(socket, addresses[i]);
+ } else if (type == SOCK_STREAM) {
+ FakeAsyncListenSocket* socket = new FakeAsyncListenSocket();
+ factory.set_next_server_tcp_socket(socket);
+ ports[i] = CreateTcpPort(addresses[i], &factory);
+ socket->Bind(addresses[i]);
+ }
+ ports[i]->PrepareAddress();
+ }
+
+ // IPv4 Port, connects to IPv6 candidate and then to IPv4 candidate.
+ if (type == SOCK_STREAM) {
+ FakeAsyncPacketSocket* clientsocket = new FakeAsyncPacketSocket();
+ factory.set_next_client_tcp_socket(clientsocket);
+ }
+ Connection* c = ports[0]->CreateConnection(GetCandidate(ports[2].get()),
+ Port::ORIGIN_MESSAGE);
+ EXPECT_TRUE(NULL == c);
+ EXPECT_EQ(0U, ports[0]->connections().size());
+ c = ports[0]->CreateConnection(GetCandidate(ports[1].get()),
+ Port::ORIGIN_MESSAGE);
+ EXPECT_FALSE(NULL == c);
+ EXPECT_EQ(1U, ports[0]->connections().size());
+
+ // IPv6 Port, connects to IPv4 candidate and to IPv6 candidate.
+ if (type == SOCK_STREAM) {
+ FakeAsyncPacketSocket* clientsocket = new FakeAsyncPacketSocket();
+ factory.set_next_client_tcp_socket(clientsocket);
+ }
+ c = ports[2]->CreateConnection(GetCandidate(ports[0].get()),
+ Port::ORIGIN_MESSAGE);
+ EXPECT_TRUE(NULL == c);
+ EXPECT_EQ(0U, ports[2]->connections().size());
+ c = ports[2]->CreateConnection(GetCandidate(ports[3].get()),
+ Port::ORIGIN_MESSAGE);
+ EXPECT_FALSE(NULL == c);
+ EXPECT_EQ(1U, ports[2]->connections().size());
+}
+
+TEST_F(PortTest, TestSkipCrossFamilyTcp) {
+ TestCrossFamilyPorts(SOCK_STREAM);
+}
+
+TEST_F(PortTest, TestSkipCrossFamilyUdp) {
+ TestCrossFamilyPorts(SOCK_DGRAM);
+}
+
+void PortTest::ExpectPortsCanConnect(bool can_connect, Port* p1, Port* p2) {
+ Connection* c = p1->CreateConnection(GetCandidate(p2), Port::ORIGIN_MESSAGE);
+ if (can_connect) {
+ EXPECT_FALSE(NULL == c);
+ EXPECT_EQ(1U, p1->connections().size());
+ } else {
+ EXPECT_TRUE(NULL == c);
+ EXPECT_EQ(0U, p1->connections().size());
+ }
+}
+
+TEST_F(PortTest, TestUdpSingleAddressV6CrossTypePorts) {
+ FakePacketSocketFactory factory;
+ std::unique_ptr<Port> ports[4];
+ SocketAddress addresses[4] = {
+ SocketAddress("2001:db8::1", 0), SocketAddress("fe80::1", 0),
+ SocketAddress("fe80::2", 0), SocketAddress("::1", 0)};
+ for (int i = 0; i < 4; i++) {
+ FakeAsyncPacketSocket* socket = new FakeAsyncPacketSocket();
+ factory.set_next_udp_socket(socket);
+ ports[i] = CreateUdpPort(addresses[i], &factory);
+ socket->set_state(AsyncPacketSocket::STATE_BINDING);
+ socket->SignalAddressReady(socket, addresses[i]);
+ ports[i]->PrepareAddress();
+ }
+
+ Port* standard = ports[0].get();
+ Port* link_local1 = ports[1].get();
+ Port* link_local2 = ports[2].get();
+ Port* localhost = ports[3].get();
+
+ ExpectPortsCanConnect(false, link_local1, standard);
+ ExpectPortsCanConnect(false, standard, link_local1);
+ ExpectPortsCanConnect(false, link_local1, localhost);
+ ExpectPortsCanConnect(false, localhost, link_local1);
+
+ ExpectPortsCanConnect(true, link_local1, link_local2);
+ ExpectPortsCanConnect(true, localhost, standard);
+ ExpectPortsCanConnect(true, standard, localhost);
+}
+
+TEST_F(PortTest, TestUdpMultipleAddressesV6CrossTypePorts) {
+ webrtc::test::ScopedKeyValueConfig field_trials(
+ "WebRTC-IPv6NetworkResolutionFixes/"
+ "Enabled,PreferGlobalIPv6Address:true/");
+ FakePacketSocketFactory factory;
+ std::unique_ptr<Port> ports[5];
+ SocketAddress addresses[5] = {
+ SocketAddress("2001:db8::1", 0), SocketAddress("2001:db8::2", 0),
+ SocketAddress("fe80::1", 0), SocketAddress("fe80::2", 0),
+ SocketAddress("::1", 0)};
+ for (int i = 0; i < 5; i++) {
+ FakeAsyncPacketSocket* socket = new FakeAsyncPacketSocket();
+ factory.set_next_udp_socket(socket);
+ ports[i] = CreateUdpPortMultipleAddrs(addresses[i], kLinkLocalIPv6Addr,
+ &factory, field_trials);
+ ports[i]->SetIceTiebreaker(kTiebreakerDefault);
+ socket->set_state(AsyncPacketSocket::STATE_BINDING);
+ socket->SignalAddressReady(socket, addresses[i]);
+ ports[i]->PrepareAddress();
+ }
+
+ Port* standard1 = ports[0].get();
+ Port* standard2 = ports[1].get();
+ Port* link_local1 = ports[2].get();
+ Port* link_local2 = ports[3].get();
+ Port* localhost = ports[4].get();
+
+ ExpectPortsCanConnect(false, link_local1, standard1);
+ ExpectPortsCanConnect(false, standard1, link_local1);
+ ExpectPortsCanConnect(false, link_local1, localhost);
+ ExpectPortsCanConnect(false, localhost, link_local1);
+
+ ExpectPortsCanConnect(true, link_local1, link_local2);
+ ExpectPortsCanConnect(true, localhost, standard1);
+ ExpectPortsCanConnect(true, standard1, localhost);
+ ExpectPortsCanConnect(true, standard2, standard1);
+}
+
+// This test verifies DSCP value set through SetOption interface can be
+// get through DefaultDscpValue.
+TEST_F(PortTest, TestDefaultDscpValue) {
+ int dscp;
+ auto udpport = CreateUdpPort(kLocalAddr1);
+ EXPECT_EQ(0, udpport->SetOption(rtc::Socket::OPT_DSCP, rtc::DSCP_CS6));
+ EXPECT_EQ(0, udpport->GetOption(rtc::Socket::OPT_DSCP, &dscp));
+ auto tcpport = CreateTcpPort(kLocalAddr1);
+ EXPECT_EQ(0, tcpport->SetOption(rtc::Socket::OPT_DSCP, rtc::DSCP_AF31));
+ EXPECT_EQ(0, tcpport->GetOption(rtc::Socket::OPT_DSCP, &dscp));
+ EXPECT_EQ(rtc::DSCP_AF31, dscp);
+ auto stunport = CreateStunPort(kLocalAddr1, nat_socket_factory1());
+ EXPECT_EQ(0, stunport->SetOption(rtc::Socket::OPT_DSCP, rtc::DSCP_AF41));
+ EXPECT_EQ(0, stunport->GetOption(rtc::Socket::OPT_DSCP, &dscp));
+ EXPECT_EQ(rtc::DSCP_AF41, dscp);
+ auto turnport1 =
+ CreateTurnPort(kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP);
+ // Socket is created in PrepareAddress.
+ turnport1->PrepareAddress();
+ EXPECT_EQ(0, turnport1->SetOption(rtc::Socket::OPT_DSCP, rtc::DSCP_CS7));
+ EXPECT_EQ(0, turnport1->GetOption(rtc::Socket::OPT_DSCP, &dscp));
+ EXPECT_EQ(rtc::DSCP_CS7, dscp);
+ // This will verify correct value returned without the socket.
+ auto turnport2 =
+ CreateTurnPort(kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP);
+ EXPECT_EQ(0, turnport2->SetOption(rtc::Socket::OPT_DSCP, rtc::DSCP_CS6));
+ EXPECT_EQ(0, turnport2->GetOption(rtc::Socket::OPT_DSCP, &dscp));
+ EXPECT_EQ(rtc::DSCP_CS6, dscp);
+}
+
+// Test sending STUN messages.
+TEST_F(PortTest, TestSendStunMessage) {
+ auto lport = CreateTestPort(kLocalAddr1, "lfrag", "lpass");
+ auto rport = CreateTestPort(kLocalAddr2, "rfrag", "rpass");
+ lport->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ lport->SetIceTiebreaker(kTiebreaker1);
+ rport->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ rport->SetIceTiebreaker(kTiebreaker2);
+
+ // Send a fake ping from lport to rport.
+ lport->PrepareAddress();
+ rport->PrepareAddress();
+ ASSERT_FALSE(rport->Candidates().empty());
+ Connection* lconn =
+ lport->CreateConnection(rport->Candidates()[0], Port::ORIGIN_MESSAGE);
+ Connection* rconn =
+ rport->CreateConnection(lport->Candidates()[0], Port::ORIGIN_MESSAGE);
+ lconn->Ping(0);
+
+ // Check that it's a proper BINDING-REQUEST.
+ ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, kDefaultTimeout);
+ IceMessage* msg = lport->last_stun_msg();
+ EXPECT_EQ(STUN_BINDING_REQUEST, msg->type());
+ EXPECT_FALSE(msg->IsLegacy());
+ const StunByteStringAttribute* username_attr =
+ msg->GetByteString(STUN_ATTR_USERNAME);
+ ASSERT_TRUE(username_attr != NULL);
+ const StunUInt32Attribute* priority_attr = msg->GetUInt32(STUN_ATTR_PRIORITY);
+ ASSERT_TRUE(priority_attr != NULL);
+ EXPECT_EQ(kDefaultPrflxPriority, priority_attr->value());
+ EXPECT_EQ("rfrag:lfrag", username_attr->string_view());
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL);
+ EXPECT_EQ(StunMessage::IntegrityStatus::kIntegrityOk,
+ msg->ValidateMessageIntegrity("rpass"));
+ const StunUInt64Attribute* ice_controlling_attr =
+ msg->GetUInt64(STUN_ATTR_ICE_CONTROLLING);
+ ASSERT_TRUE(ice_controlling_attr != NULL);
+ EXPECT_EQ(lport->IceTiebreaker(), ice_controlling_attr->value());
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_ICE_CONTROLLED) == NULL);
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USE_CANDIDATE) != NULL);
+ EXPECT_TRUE(msg->GetUInt32(STUN_ATTR_FINGERPRINT) != NULL);
+ EXPECT_TRUE(StunMessage::ValidateFingerprint(
+ lport->last_stun_buf()->data<char>(), lport->last_stun_buf()->size()));
+
+ // Request should not include ping count.
+ ASSERT_TRUE(msg->GetUInt32(STUN_ATTR_RETRANSMIT_COUNT) == NULL);
+
+ // Save a copy of the BINDING-REQUEST for use below.
+ std::unique_ptr<IceMessage> request = CopyStunMessage(*msg);
+
+ // Receive the BINDING-REQUEST and respond with BINDING-RESPONSE.
+ rconn->OnReadPacket(lport->last_stun_buf()->data<char>(),
+ lport->last_stun_buf()->size(), /* packet_time_us */ -1);
+ msg = rport->last_stun_msg();
+ ASSERT_TRUE(msg != NULL);
+ EXPECT_EQ(STUN_BINDING_RESPONSE, msg->type());
+ // Received a BINDING-RESPONSE.
+ lconn->OnReadPacket(rport->last_stun_buf()->data<char>(),
+ rport->last_stun_buf()->size(), /* packet_time_us */ -1);
+ // Verify the STUN Stats.
+ EXPECT_EQ(1U, lconn->stats().sent_ping_requests_total);
+ EXPECT_EQ(1U, lconn->stats().sent_ping_requests_before_first_response);
+ EXPECT_EQ(1U, lconn->stats().recv_ping_responses);
+ EXPECT_EQ(1U, rconn->stats().recv_ping_requests);
+ EXPECT_EQ(1U, rconn->stats().sent_ping_responses);
+
+ EXPECT_FALSE(msg->IsLegacy());
+ const StunAddressAttribute* addr_attr =
+ msg->GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+ ASSERT_TRUE(addr_attr != NULL);
+ EXPECT_EQ(lport->Candidates()[0].address(), addr_attr->GetAddress());
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL);
+ EXPECT_EQ(StunMessage::IntegrityStatus::kIntegrityOk,
+ msg->ValidateMessageIntegrity("rpass"));
+ EXPECT_TRUE(msg->GetUInt32(STUN_ATTR_FINGERPRINT) != NULL);
+ EXPECT_TRUE(StunMessage::ValidateFingerprint(
+ lport->last_stun_buf()->data<char>(), lport->last_stun_buf()->size()));
+ // No USERNAME or PRIORITY in ICE responses.
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USERNAME) == NULL);
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_PRIORITY) == NULL);
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MAPPED_ADDRESS) == NULL);
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_ICE_CONTROLLING) == NULL);
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_ICE_CONTROLLED) == NULL);
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USE_CANDIDATE) == NULL);
+
+ // Response should not include ping count.
+ ASSERT_TRUE(msg->GetUInt32(STUN_ATTR_RETRANSMIT_COUNT) == NULL);
+
+ // Respond with a BINDING-ERROR-RESPONSE. This wouldn't happen in real life,
+ // but we can do it here.
+ rport->SendBindingErrorResponse(
+ request.get(), lport->Candidates()[0].address(), STUN_ERROR_SERVER_ERROR,
+ STUN_ERROR_REASON_SERVER_ERROR);
+ msg = rport->last_stun_msg();
+ ASSERT_TRUE(msg != NULL);
+ EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, msg->type());
+ EXPECT_FALSE(msg->IsLegacy());
+ const StunErrorCodeAttribute* error_attr = msg->GetErrorCode();
+ ASSERT_TRUE(error_attr != NULL);
+ EXPECT_EQ(STUN_ERROR_SERVER_ERROR, error_attr->code());
+ EXPECT_EQ(std::string(STUN_ERROR_REASON_SERVER_ERROR), error_attr->reason());
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL);
+ EXPECT_EQ(StunMessage::IntegrityStatus::kIntegrityOk,
+ msg->ValidateMessageIntegrity("rpass"));
+ EXPECT_TRUE(msg->GetUInt32(STUN_ATTR_FINGERPRINT) != NULL);
+ EXPECT_TRUE(StunMessage::ValidateFingerprint(
+ lport->last_stun_buf()->data<char>(), lport->last_stun_buf()->size()));
+ // No USERNAME with ICE.
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USERNAME) == NULL);
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_PRIORITY) == NULL);
+
+ // Testing STUN binding requests from rport --> lport, having ICE_CONTROLLED
+ // and (incremented) RETRANSMIT_COUNT attributes.
+ rport->Reset();
+ rport->set_send_retransmit_count_attribute(true);
+ rconn->Ping(0);
+ rconn->Ping(0);
+ rconn->Ping(0);
+ ASSERT_TRUE_WAIT(rport->last_stun_msg() != NULL, kDefaultTimeout);
+ msg = rport->last_stun_msg();
+ EXPECT_EQ(STUN_BINDING_REQUEST, msg->type());
+ const StunUInt64Attribute* ice_controlled_attr =
+ msg->GetUInt64(STUN_ATTR_ICE_CONTROLLED);
+ ASSERT_TRUE(ice_controlled_attr != NULL);
+ EXPECT_EQ(rport->IceTiebreaker(), ice_controlled_attr->value());
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USE_CANDIDATE) == NULL);
+
+ // Request should include ping count.
+ const StunUInt32Attribute* retransmit_attr =
+ msg->GetUInt32(STUN_ATTR_RETRANSMIT_COUNT);
+ ASSERT_TRUE(retransmit_attr != NULL);
+ EXPECT_EQ(2U, retransmit_attr->value());
+
+ // Respond with a BINDING-RESPONSE.
+ request = CopyStunMessage(*msg);
+ lconn->OnReadPacket(rport->last_stun_buf()->data<char>(),
+ rport->last_stun_buf()->size(), /* packet_time_us */ -1);
+ msg = lport->last_stun_msg();
+ // Receive the BINDING-RESPONSE.
+ rconn->OnReadPacket(lport->last_stun_buf()->data<char>(),
+ lport->last_stun_buf()->size(), /* packet_time_us */ -1);
+
+ // Verify the Stun ping stats.
+ EXPECT_EQ(3U, rconn->stats().sent_ping_requests_total);
+ EXPECT_EQ(3U, rconn->stats().sent_ping_requests_before_first_response);
+ EXPECT_EQ(1U, rconn->stats().recv_ping_responses);
+ EXPECT_EQ(1U, lconn->stats().sent_ping_responses);
+ EXPECT_EQ(1U, lconn->stats().recv_ping_requests);
+ // Ping after receiver the first response
+ rconn->Ping(0);
+ rconn->Ping(0);
+ EXPECT_EQ(5U, rconn->stats().sent_ping_requests_total);
+ EXPECT_EQ(3U, rconn->stats().sent_ping_requests_before_first_response);
+
+ // Response should include same ping count.
+ retransmit_attr = msg->GetUInt32(STUN_ATTR_RETRANSMIT_COUNT);
+ ASSERT_TRUE(retransmit_attr != NULL);
+ EXPECT_EQ(2U, retransmit_attr->value());
+}
+
+TEST_F(PortTest, TestNomination) {
+ auto lport = CreateTestPort(kLocalAddr1, "lfrag", "lpass");
+ auto rport = CreateTestPort(kLocalAddr2, "rfrag", "rpass");
+ lport->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ lport->SetIceTiebreaker(kTiebreaker1);
+ rport->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ rport->SetIceTiebreaker(kTiebreaker2);
+
+ lport->PrepareAddress();
+ rport->PrepareAddress();
+ ASSERT_FALSE(lport->Candidates().empty());
+ ASSERT_FALSE(rport->Candidates().empty());
+ Connection* lconn =
+ lport->CreateConnection(rport->Candidates()[0], Port::ORIGIN_MESSAGE);
+ Connection* rconn =
+ rport->CreateConnection(lport->Candidates()[0], Port::ORIGIN_MESSAGE);
+
+ // `lconn` is controlling, `rconn` is controlled.
+ uint32_t nomination = 1234;
+ lconn->set_nomination(nomination);
+
+ EXPECT_FALSE(lconn->nominated());
+ EXPECT_FALSE(rconn->nominated());
+ EXPECT_EQ(lconn->nominated(), lconn->stats().nominated);
+ EXPECT_EQ(rconn->nominated(), rconn->stats().nominated);
+
+ // Send ping (including the nomination value) from `lconn` to `rconn`. This
+ // should set the remote nomination of `rconn`.
+ lconn->Ping(0);
+ ASSERT_TRUE_WAIT(lport->last_stun_msg(), kDefaultTimeout);
+ ASSERT_TRUE(lport->last_stun_buf());
+ rconn->OnReadPacket(lport->last_stun_buf()->data<char>(),
+ lport->last_stun_buf()->size(), /* packet_time_us */ -1);
+ EXPECT_EQ(nomination, rconn->remote_nomination());
+ EXPECT_FALSE(lconn->nominated());
+ EXPECT_TRUE(rconn->nominated());
+ EXPECT_EQ(lconn->nominated(), lconn->stats().nominated);
+ EXPECT_EQ(rconn->nominated(), rconn->stats().nominated);
+
+ // This should result in an acknowledgment sent back from `rconn` to `lconn`,
+ // updating the acknowledged nomination of `lconn`.
+ ASSERT_TRUE_WAIT(rport->last_stun_msg(), kDefaultTimeout);
+ ASSERT_TRUE(rport->last_stun_buf());
+ lconn->OnReadPacket(rport->last_stun_buf()->data<char>(),
+ rport->last_stun_buf()->size(), /* packet_time_us */ -1);
+ EXPECT_EQ(nomination, lconn->acked_nomination());
+ EXPECT_TRUE(lconn->nominated());
+ EXPECT_TRUE(rconn->nominated());
+ EXPECT_EQ(lconn->nominated(), lconn->stats().nominated);
+ EXPECT_EQ(rconn->nominated(), rconn->stats().nominated);
+}
+
+TEST_F(PortTest, TestRoundTripTime) {
+ rtc::ScopedFakeClock clock;
+
+ auto lport = CreateTestPort(kLocalAddr1, "lfrag", "lpass");
+ auto rport = CreateTestPort(kLocalAddr2, "rfrag", "rpass");
+ lport->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ lport->SetIceTiebreaker(kTiebreaker1);
+ rport->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ rport->SetIceTiebreaker(kTiebreaker2);
+
+ lport->PrepareAddress();
+ rport->PrepareAddress();
+ ASSERT_FALSE(lport->Candidates().empty());
+ ASSERT_FALSE(rport->Candidates().empty());
+ Connection* lconn =
+ lport->CreateConnection(rport->Candidates()[0], Port::ORIGIN_MESSAGE);
+ Connection* rconn =
+ rport->CreateConnection(lport->Candidates()[0], Port::ORIGIN_MESSAGE);
+
+ EXPECT_EQ(0u, lconn->stats().total_round_trip_time_ms);
+ EXPECT_FALSE(lconn->stats().current_round_trip_time_ms);
+
+ SendPingAndReceiveResponse(lconn, lport.get(), rconn, rport.get(), &clock,
+ 10);
+ EXPECT_EQ(10u, lconn->stats().total_round_trip_time_ms);
+ ASSERT_TRUE(lconn->stats().current_round_trip_time_ms);
+ EXPECT_EQ(10u, *lconn->stats().current_round_trip_time_ms);
+
+ SendPingAndReceiveResponse(lconn, lport.get(), rconn, rport.get(), &clock,
+ 20);
+ EXPECT_EQ(30u, lconn->stats().total_round_trip_time_ms);
+ ASSERT_TRUE(lconn->stats().current_round_trip_time_ms);
+ EXPECT_EQ(20u, *lconn->stats().current_round_trip_time_ms);
+
+ SendPingAndReceiveResponse(lconn, lport.get(), rconn, rport.get(), &clock,
+ 30);
+ EXPECT_EQ(60u, lconn->stats().total_round_trip_time_ms);
+ ASSERT_TRUE(lconn->stats().current_round_trip_time_ms);
+ EXPECT_EQ(30u, *lconn->stats().current_round_trip_time_ms);
+}
+
+TEST_F(PortTest, TestUseCandidateAttribute) {
+ auto lport = CreateTestPort(kLocalAddr1, "lfrag", "lpass");
+ auto rport = CreateTestPort(kLocalAddr2, "rfrag", "rpass");
+ lport->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ lport->SetIceTiebreaker(kTiebreaker1);
+ rport->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ rport->SetIceTiebreaker(kTiebreaker2);
+
+ // Send a fake ping from lport to rport.
+ lport->PrepareAddress();
+ rport->PrepareAddress();
+ ASSERT_FALSE(rport->Candidates().empty());
+ Connection* lconn =
+ lport->CreateConnection(rport->Candidates()[0], Port::ORIGIN_MESSAGE);
+ lconn->Ping(0);
+ ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, kDefaultTimeout);
+ IceMessage* msg = lport->last_stun_msg();
+ const StunUInt64Attribute* ice_controlling_attr =
+ msg->GetUInt64(STUN_ATTR_ICE_CONTROLLING);
+ ASSERT_TRUE(ice_controlling_attr != NULL);
+ const StunByteStringAttribute* use_candidate_attr =
+ msg->GetByteString(STUN_ATTR_USE_CANDIDATE);
+ ASSERT_TRUE(use_candidate_attr != NULL);
+}
+
+// Tests that when the network type changes, the network cost of the port will
+// change, the network cost of the local candidates will change. Also tests that
+// the remote network costs are updated with the stun binding requests.
+TEST_F(PortTest, TestNetworkCostChange) {
+ rtc::Network* test_network = MakeNetwork(kLocalAddr1);
+ auto lport = CreateTestPort(test_network, "lfrag", "lpass");
+ auto rport = CreateTestPort(test_network, "rfrag", "rpass");
+ lport->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ lport->SetIceTiebreaker(kTiebreaker1);
+ rport->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ rport->SetIceTiebreaker(kTiebreaker2);
+ lport->PrepareAddress();
+ rport->PrepareAddress();
+
+ // Default local port cost is rtc::kNetworkCostUnknown.
+ EXPECT_EQ(rtc::kNetworkCostUnknown, lport->network_cost());
+ ASSERT_TRUE(!lport->Candidates().empty());
+ for (const cricket::Candidate& candidate : lport->Candidates()) {
+ EXPECT_EQ(rtc::kNetworkCostUnknown, candidate.network_cost());
+ }
+
+ // Change the network type to wifi.
+ test_network->set_type(rtc::ADAPTER_TYPE_WIFI);
+ EXPECT_EQ(rtc::kNetworkCostLow, lport->network_cost());
+ for (const cricket::Candidate& candidate : lport->Candidates()) {
+ EXPECT_EQ(rtc::kNetworkCostLow, candidate.network_cost());
+ }
+
+ // Add a connection and then change the network type.
+ Connection* lconn =
+ lport->CreateConnection(rport->Candidates()[0], Port::ORIGIN_MESSAGE);
+ // Change the network type to cellular.
+ test_network->set_type(rtc::ADAPTER_TYPE_CELLULAR);
+ EXPECT_EQ(rtc::kNetworkCostHigh, lport->network_cost());
+ for (const cricket::Candidate& candidate : lport->Candidates()) {
+ EXPECT_EQ(rtc::kNetworkCostHigh, candidate.network_cost());
+ }
+
+ test_network->set_type(rtc::ADAPTER_TYPE_WIFI);
+ Connection* rconn =
+ rport->CreateConnection(lport->Candidates()[0], Port::ORIGIN_MESSAGE);
+ test_network->set_type(rtc::ADAPTER_TYPE_CELLULAR);
+ lconn->Ping(0);
+ // The rconn's remote candidate cost is rtc::kNetworkCostLow, but the ping
+ // contains an attribute of network cost of rtc::kNetworkCostHigh. Once the
+ // message is handled in rconn, The rconn's remote candidate will have cost
+ // rtc::kNetworkCostHigh;
+ EXPECT_EQ(rtc::kNetworkCostLow, rconn->remote_candidate().network_cost());
+ ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, kDefaultTimeout);
+ IceMessage* msg = lport->last_stun_msg();
+ EXPECT_EQ(STUN_BINDING_REQUEST, msg->type());
+ // Pass the binding request to rport.
+ rconn->OnReadPacket(lport->last_stun_buf()->data<char>(),
+ lport->last_stun_buf()->size(), /* packet_time_us */ -1);
+ // Wait until rport sends the response and then check the remote network cost.
+ ASSERT_TRUE_WAIT(rport->last_stun_msg() != NULL, kDefaultTimeout);
+ EXPECT_EQ(rtc::kNetworkCostHigh, rconn->remote_candidate().network_cost());
+}
+
+TEST_F(PortTest, TestNetworkInfoAttribute) {
+ rtc::Network* test_network = MakeNetwork(kLocalAddr1);
+ auto lport = CreateTestPort(test_network, "lfrag", "lpass");
+ auto rport = CreateTestPort(test_network, "rfrag", "rpass");
+ lport->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ lport->SetIceTiebreaker(kTiebreaker1);
+ rport->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ rport->SetIceTiebreaker(kTiebreaker2);
+
+ uint16_t lnetwork_id = 9;
+ test_network->set_id(lnetwork_id);
+ // Send a fake ping from lport to rport.
+ lport->PrepareAddress();
+ rport->PrepareAddress();
+ Connection* lconn =
+ lport->CreateConnection(rport->Candidates()[0], Port::ORIGIN_MESSAGE);
+ lconn->Ping(0);
+ ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, kDefaultTimeout);
+ IceMessage* msg = lport->last_stun_msg();
+ const StunUInt32Attribute* network_info_attr =
+ msg->GetUInt32(STUN_ATTR_GOOG_NETWORK_INFO);
+ ASSERT_TRUE(network_info_attr != NULL);
+ uint32_t network_info = network_info_attr->value();
+ EXPECT_EQ(lnetwork_id, network_info >> 16);
+ // Default network has unknown type and cost kNetworkCostUnknown.
+ EXPECT_EQ(rtc::kNetworkCostUnknown, network_info & 0xFFFF);
+
+ // Set the network type to be cellular so its cost will be kNetworkCostHigh.
+ // Send a fake ping from rport to lport.
+ test_network->set_type(rtc::ADAPTER_TYPE_CELLULAR);
+ uint16_t rnetwork_id = 8;
+ test_network->set_id(rnetwork_id);
+ Connection* rconn =
+ rport->CreateConnection(lport->Candidates()[0], Port::ORIGIN_MESSAGE);
+ rconn->Ping(0);
+ ASSERT_TRUE_WAIT(rport->last_stun_msg() != NULL, kDefaultTimeout);
+ msg = rport->last_stun_msg();
+ network_info_attr = msg->GetUInt32(STUN_ATTR_GOOG_NETWORK_INFO);
+ ASSERT_TRUE(network_info_attr != NULL);
+ network_info = network_info_attr->value();
+ EXPECT_EQ(rnetwork_id, network_info >> 16);
+ EXPECT_EQ(rtc::kNetworkCostHigh, network_info & 0xFFFF);
+}
+
+// Test handling STUN messages.
+TEST_F(PortTest, TestHandleStunMessage) {
+ // Our port will act as the "remote" port.
+ auto port = CreateTestPort(kLocalAddr2, "rfrag", "rpass");
+
+ std::unique_ptr<IceMessage> in_msg, out_msg;
+ auto buf = std::make_unique<ByteBufferWriter>();
+ rtc::SocketAddress addr(kLocalAddr1);
+ std::string username;
+
+ // BINDING-REQUEST from local to remote with valid ICE username,
+ // MESSAGE-INTEGRITY, and FINGERPRINT.
+ in_msg = CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "rfrag:lfrag");
+ in_msg->AddMessageIntegrity("rpass");
+ in_msg->AddFingerprint();
+ WriteStunMessage(*in_msg, buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
+ &username));
+ EXPECT_TRUE(out_msg.get() != NULL);
+ EXPECT_EQ("lfrag", username);
+
+ // BINDING-RESPONSE without username, with MESSAGE-INTEGRITY and FINGERPRINT.
+ in_msg = CreateStunMessage(STUN_BINDING_RESPONSE);
+ in_msg->AddAttribute(std::make_unique<StunXorAddressAttribute>(
+ STUN_ATTR_XOR_MAPPED_ADDRESS, kLocalAddr2));
+ in_msg->AddMessageIntegrity("rpass");
+ in_msg->AddFingerprint();
+ WriteStunMessage(*in_msg, buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
+ &username));
+ EXPECT_TRUE(out_msg.get() != NULL);
+ EXPECT_EQ("", username);
+
+ // BINDING-ERROR-RESPONSE without username, with error, M-I, and FINGERPRINT.
+ in_msg = CreateStunMessage(STUN_BINDING_ERROR_RESPONSE);
+ in_msg->AddAttribute(std::make_unique<StunErrorCodeAttribute>(
+ STUN_ATTR_ERROR_CODE, STUN_ERROR_SERVER_ERROR,
+ STUN_ERROR_REASON_SERVER_ERROR));
+ in_msg->AddFingerprint();
+ WriteStunMessage(*in_msg, buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
+ &username));
+ EXPECT_TRUE(out_msg.get() != NULL);
+ EXPECT_EQ("", username);
+ ASSERT_TRUE(out_msg->GetErrorCode() != NULL);
+ EXPECT_EQ(STUN_ERROR_SERVER_ERROR, out_msg->GetErrorCode()->code());
+ EXPECT_EQ(std::string(STUN_ERROR_REASON_SERVER_ERROR),
+ out_msg->GetErrorCode()->reason());
+}
+
+// Tests handling of ICE binding requests with missing or incorrect usernames.
+TEST_F(PortTest, TestHandleStunMessageBadUsername) {
+ auto port = CreateTestPort(kLocalAddr2, "rfrag", "rpass");
+
+ std::unique_ptr<IceMessage> in_msg, out_msg;
+ auto buf = std::make_unique<ByteBufferWriter>();
+ rtc::SocketAddress addr(kLocalAddr1);
+ std::string username;
+
+ // BINDING-REQUEST with no username.
+ in_msg = CreateStunMessage(STUN_BINDING_REQUEST);
+ in_msg->AddMessageIntegrity("rpass");
+ in_msg->AddFingerprint();
+ WriteStunMessage(*in_msg, buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
+ &username));
+ EXPECT_TRUE(out_msg.get() == NULL);
+ EXPECT_EQ("", username);
+ EXPECT_EQ(STUN_ERROR_BAD_REQUEST, port->last_stun_error_code());
+
+ // BINDING-REQUEST with empty username.
+ in_msg = CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "");
+ in_msg->AddMessageIntegrity("rpass");
+ in_msg->AddFingerprint();
+ WriteStunMessage(*in_msg, buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
+ &username));
+ EXPECT_TRUE(out_msg.get() == NULL);
+ EXPECT_EQ("", username);
+ EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code());
+
+ // BINDING-REQUEST with too-short username.
+ in_msg = CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "rfra");
+ in_msg->AddMessageIntegrity("rpass");
+ in_msg->AddFingerprint();
+ WriteStunMessage(*in_msg, buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
+ &username));
+ EXPECT_TRUE(out_msg.get() == NULL);
+ EXPECT_EQ("", username);
+ EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code());
+
+ // BINDING-REQUEST with reversed username.
+ in_msg = CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "lfrag:rfrag");
+ in_msg->AddMessageIntegrity("rpass");
+ in_msg->AddFingerprint();
+ WriteStunMessage(*in_msg, buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
+ &username));
+ EXPECT_TRUE(out_msg.get() == NULL);
+ EXPECT_EQ("", username);
+ EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code());
+
+ // BINDING-REQUEST with garbage username.
+ in_msg = CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "abcd:efgh");
+ in_msg->AddMessageIntegrity("rpass");
+ in_msg->AddFingerprint();
+ WriteStunMessage(*in_msg, buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
+ &username));
+ EXPECT_TRUE(out_msg.get() == NULL);
+ EXPECT_EQ("", username);
+ EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code());
+}
+
+// Test handling STUN messages with missing or malformed M-I.
+TEST_F(PortTest, TestHandleStunMessageBadMessageIntegrity) {
+ // Our port will act as the "remote" port.
+ auto port = CreateTestPort(kLocalAddr2, "rfrag", "rpass");
+
+ std::unique_ptr<IceMessage> in_msg, out_msg;
+ auto buf = std::make_unique<ByteBufferWriter>();
+ rtc::SocketAddress addr(kLocalAddr1);
+ std::string username;
+
+ // BINDING-REQUEST from local to remote with valid ICE username and
+ // FINGERPRINT, but no MESSAGE-INTEGRITY.
+ in_msg = CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "rfrag:lfrag");
+ in_msg->AddFingerprint();
+ WriteStunMessage(*in_msg, buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
+ &username));
+ EXPECT_TRUE(out_msg.get() == NULL);
+ EXPECT_EQ("", username);
+ EXPECT_EQ(STUN_ERROR_BAD_REQUEST, port->last_stun_error_code());
+
+ // BINDING-REQUEST from local to remote with valid ICE username and
+ // FINGERPRINT, but invalid MESSAGE-INTEGRITY.
+ in_msg = CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "rfrag:lfrag");
+ in_msg->AddMessageIntegrity("invalid");
+ in_msg->AddFingerprint();
+ WriteStunMessage(*in_msg, buf.get());
+ EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
+ &username));
+ EXPECT_TRUE(out_msg.get() == NULL);
+ EXPECT_EQ("", username);
+ EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code());
+
+ // TODO(?): BINDING-RESPONSES and BINDING-ERROR-RESPONSES are checked
+ // by the Connection, not the Port, since they require the remote username.
+ // Change this test to pass in data via Connection::OnReadPacket instead.
+}
+
+// Test handling STUN messages with missing or malformed FINGERPRINT.
+TEST_F(PortTest, TestHandleStunMessageBadFingerprint) {
+ // Our port will act as the "remote" port.
+ auto port = CreateTestPort(kLocalAddr2, "rfrag", "rpass");
+
+ std::unique_ptr<IceMessage> in_msg, out_msg;
+ auto buf = std::make_unique<ByteBufferWriter>();
+ rtc::SocketAddress addr(kLocalAddr1);
+ std::string username;
+
+ // BINDING-REQUEST from local to remote with valid ICE username and
+ // MESSAGE-INTEGRITY, but no FINGERPRINT; GetStunMessage should fail.
+ in_msg = CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "rfrag:lfrag");
+ in_msg->AddMessageIntegrity("rpass");
+ WriteStunMessage(*in_msg, buf.get());
+ EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
+ &username));
+ EXPECT_EQ(0, port->last_stun_error_code());
+
+ // Now, add a fingerprint, but munge the message so it's not valid.
+ in_msg->AddFingerprint();
+ in_msg->SetTransactionIdForTesting("TESTTESTBADD");
+ WriteStunMessage(*in_msg, buf.get());
+ EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
+ &username));
+ EXPECT_EQ(0, port->last_stun_error_code());
+
+ // Valid BINDING-RESPONSE, except no FINGERPRINT.
+ in_msg = CreateStunMessage(STUN_BINDING_RESPONSE);
+ in_msg->AddAttribute(std::make_unique<StunXorAddressAttribute>(
+ STUN_ATTR_XOR_MAPPED_ADDRESS, kLocalAddr2));
+ in_msg->AddMessageIntegrity("rpass");
+ WriteStunMessage(*in_msg, buf.get());
+ EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
+ &username));
+ EXPECT_EQ(0, port->last_stun_error_code());
+
+ // Now, add a fingerprint, but munge the message so it's not valid.
+ in_msg->AddFingerprint();
+ in_msg->SetTransactionIdForTesting("TESTTESTBADD");
+ WriteStunMessage(*in_msg, buf.get());
+ EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
+ &username));
+ EXPECT_EQ(0, port->last_stun_error_code());
+
+ // Valid BINDING-ERROR-RESPONSE, except no FINGERPRINT.
+ in_msg = CreateStunMessage(STUN_BINDING_ERROR_RESPONSE);
+ in_msg->AddAttribute(std::make_unique<StunErrorCodeAttribute>(
+ STUN_ATTR_ERROR_CODE, STUN_ERROR_SERVER_ERROR,
+ STUN_ERROR_REASON_SERVER_ERROR));
+ in_msg->AddMessageIntegrity("rpass");
+ WriteStunMessage(*in_msg, buf.get());
+ EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
+ &username));
+ EXPECT_EQ(0, port->last_stun_error_code());
+
+ // Now, add a fingerprint, but munge the message so it's not valid.
+ in_msg->AddFingerprint();
+ in_msg->SetTransactionIdForTesting("TESTTESTBADD");
+ WriteStunMessage(*in_msg, buf.get());
+ EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
+ &username));
+ EXPECT_EQ(0, port->last_stun_error_code());
+}
+
+// Test handling a STUN message with unknown attributes in the
+// "comprehension-required" range. Should respond with an error with the
+// unknown attributes' IDs.
+TEST_F(PortTest,
+ TestHandleStunRequestWithUnknownComprehensionRequiredAttribute) {
+ // Our port will act as the "remote" port.
+ std::unique_ptr<TestPort> port(CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
+
+ std::unique_ptr<IceMessage> in_msg, out_msg;
+ auto buf = std::make_unique<ByteBufferWriter>();
+ rtc::SocketAddress addr(kLocalAddr1);
+ std::string username;
+
+ // Build ordinary message with valid ufrag/pass.
+ in_msg = CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "rfrag:lfrag");
+ in_msg->AddMessageIntegrity("rpass");
+ // Add a couple attributes with ID in comprehension-required range.
+ in_msg->AddAttribute(StunAttribute::CreateUInt32(0x7777));
+ in_msg->AddAttribute(StunAttribute::CreateUInt32(0x4567));
+ // ... And one outside the range.
+ in_msg->AddAttribute(StunAttribute::CreateUInt32(0xdead));
+ in_msg->AddFingerprint();
+ WriteStunMessage(*in_msg, buf.get());
+ ASSERT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
+ &username));
+ IceMessage* error_response = port->last_stun_msg();
+ ASSERT_NE(nullptr, error_response);
+
+ // Verify that the "unknown attribute" error response has the right error
+ // code, and includes an attribute that lists out the unrecognized attribute
+ // types.
+ EXPECT_EQ(STUN_ERROR_UNKNOWN_ATTRIBUTE, error_response->GetErrorCodeValue());
+ const StunUInt16ListAttribute* unknown_attributes =
+ error_response->GetUnknownAttributes();
+ ASSERT_NE(nullptr, unknown_attributes);
+ ASSERT_EQ(2u, unknown_attributes->Size());
+ EXPECT_EQ(0x7777, unknown_attributes->GetType(0));
+ EXPECT_EQ(0x4567, unknown_attributes->GetType(1));
+}
+
+// Similar to the above, but with a response instead of a request. In this
+// case the response should just be ignored and transaction treated is failed.
+TEST_F(PortTest,
+ TestHandleStunResponseWithUnknownComprehensionRequiredAttribute) {
+ // Generic setup.
+ auto lport = CreateTestPort(kLocalAddr1, "lfrag", "lpass",
+ cricket::ICEROLE_CONTROLLING, kTiebreakerDefault);
+ auto rport = CreateTestPort(kLocalAddr2, "rfrag", "rpass",
+ cricket::ICEROLE_CONTROLLED, kTiebreakerDefault);
+ lport->PrepareAddress();
+ rport->PrepareAddress();
+ ASSERT_FALSE(lport->Candidates().empty());
+ ASSERT_FALSE(rport->Candidates().empty());
+ Connection* lconn =
+ lport->CreateConnection(rport->Candidates()[0], Port::ORIGIN_MESSAGE);
+ Connection* rconn =
+ rport->CreateConnection(lport->Candidates()[0], Port::ORIGIN_MESSAGE);
+
+ // Send request.
+ lconn->Ping(0);
+ ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, kDefaultTimeout);
+ rconn->OnReadPacket(lport->last_stun_buf()->data<char>(),
+ lport->last_stun_buf()->size(), /* packet_time_us */ -1);
+
+ // Intercept request and add comprehension required attribute.
+ ASSERT_TRUE_WAIT(rport->last_stun_msg() != NULL, kDefaultTimeout);
+ auto modified_response = rport->last_stun_msg()->Clone();
+ modified_response->AddAttribute(StunAttribute::CreateUInt32(0x7777));
+ modified_response->RemoveAttribute(STUN_ATTR_FINGERPRINT);
+ modified_response->AddFingerprint();
+ ByteBufferWriter buf;
+ WriteStunMessage(*modified_response, &buf);
+ lconn->OnReadPacket(buf.Data(), buf.Length(), /* packet_time_us */ -1);
+ // Response should have been ignored, leaving us unwritable still.
+ EXPECT_FALSE(lconn->writable());
+}
+
+// Similar to the above, but with an indication. As with a response, it should
+// just be ignored.
+TEST_F(PortTest,
+ TestHandleStunIndicationWithUnknownComprehensionRequiredAttribute) {
+ // Generic set up.
+ auto lport = CreateTestPort(kLocalAddr2, "lfrag", "lpass",
+ cricket::ICEROLE_CONTROLLING, kTiebreakerDefault);
+ auto rport = CreateTestPort(kLocalAddr2, "rfrag", "rpass",
+ cricket::ICEROLE_CONTROLLED, kTiebreakerDefault);
+ lport->PrepareAddress();
+ rport->PrepareAddress();
+ ASSERT_FALSE(lport->Candidates().empty());
+ ASSERT_FALSE(rport->Candidates().empty());
+ Connection* lconn =
+ lport->CreateConnection(rport->Candidates()[0], Port::ORIGIN_MESSAGE);
+
+ // Generate indication with comprehension required attribute and verify it
+ // doesn't update last_ping_received.
+ auto in_msg = CreateStunMessage(STUN_BINDING_INDICATION);
+ in_msg->AddAttribute(StunAttribute::CreateUInt32(0x7777));
+ in_msg->AddFingerprint();
+ ByteBufferWriter buf;
+ WriteStunMessage(*in_msg, &buf);
+ lconn->OnReadPacket(buf.Data(), buf.Length(), /* packet_time_us */ -1);
+ EXPECT_EQ(0u, lconn->last_ping_received());
+}
+
+// Test handling of STUN binding indication messages . STUN binding
+// indications are allowed only to the connection which is in read mode.
+TEST_F(PortTest, TestHandleStunBindingIndication) {
+ auto lport = CreateTestPort(kLocalAddr2, "lfrag", "lpass",
+ cricket::ICEROLE_CONTROLLING, kTiebreaker1);
+
+ // Verifying encoding and decoding STUN indication message.
+ std::unique_ptr<IceMessage> in_msg, out_msg;
+ std::unique_ptr<ByteBufferWriter> buf(new ByteBufferWriter());
+ rtc::SocketAddress addr(kLocalAddr1);
+ std::string username;
+
+ in_msg = CreateStunMessage(STUN_BINDING_INDICATION);
+ in_msg->AddFingerprint();
+ WriteStunMessage(*in_msg, buf.get());
+ EXPECT_TRUE(lport->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
+ &username));
+ EXPECT_TRUE(out_msg.get() != NULL);
+ EXPECT_EQ(out_msg->type(), STUN_BINDING_INDICATION);
+ EXPECT_EQ("", username);
+
+ // Verify connection can handle STUN indication and updates
+ // last_ping_received.
+ auto rport = CreateTestPort(kLocalAddr2, "rfrag", "rpass");
+ rport->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ rport->SetIceTiebreaker(kTiebreaker2);
+
+ lport->PrepareAddress();
+ rport->PrepareAddress();
+ ASSERT_FALSE(lport->Candidates().empty());
+ ASSERT_FALSE(rport->Candidates().empty());
+
+ Connection* lconn =
+ lport->CreateConnection(rport->Candidates()[0], Port::ORIGIN_MESSAGE);
+ Connection* rconn =
+ rport->CreateConnection(lport->Candidates()[0], Port::ORIGIN_MESSAGE);
+ rconn->Ping(0);
+
+ ASSERT_TRUE_WAIT(rport->last_stun_msg() != NULL, kDefaultTimeout);
+ IceMessage* msg = rport->last_stun_msg();
+ EXPECT_EQ(STUN_BINDING_REQUEST, msg->type());
+ // Send rport binding request to lport.
+ lconn->OnReadPacket(rport->last_stun_buf()->data<char>(),
+ rport->last_stun_buf()->size(), /* packet_time_us */ -1);
+ ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, kDefaultTimeout);
+ EXPECT_EQ(STUN_BINDING_RESPONSE, lport->last_stun_msg()->type());
+ int64_t last_ping_received1 = lconn->last_ping_received();
+
+ // Adding a delay of 100ms.
+ rtc::Thread::Current()->ProcessMessages(100);
+ // Pinging lconn using stun indication message.
+ lconn->OnReadPacket(buf->Data(), buf->Length(), /* packet_time_us */ -1);
+ int64_t last_ping_received2 = lconn->last_ping_received();
+ EXPECT_GT(last_ping_received2, last_ping_received1);
+}
+
+TEST_F(PortTest, TestComputeCandidatePriority) {
+ auto port = CreateTestPort(kLocalAddr1, "name", "pass");
+ port->SetIceTiebreaker(kTiebreakerDefault);
+ port->set_type_preference(90);
+ port->set_component(177);
+ port->AddCandidateAddress(SocketAddress("192.168.1.4", 1234));
+ port->AddCandidateAddress(SocketAddress("2001:db8::1234", 1234));
+ port->AddCandidateAddress(SocketAddress("fc12:3456::1234", 1234));
+ port->AddCandidateAddress(SocketAddress("::ffff:192.168.1.4", 1234));
+ port->AddCandidateAddress(SocketAddress("::192.168.1.4", 1234));
+ port->AddCandidateAddress(SocketAddress("2002::1234:5678", 1234));
+ port->AddCandidateAddress(SocketAddress("2001::1234:5678", 1234));
+ port->AddCandidateAddress(SocketAddress("fecf::1234:5678", 1234));
+ port->AddCandidateAddress(SocketAddress("3ffe::1234:5678", 1234));
+ // These should all be:
+ // (90 << 24) | ([rfc3484 pref value] << 8) | (256 - 177)
+ uint32_t expected_priority_v4 = 1509957199U;
+ uint32_t expected_priority_v6 = 1509959759U;
+ uint32_t expected_priority_ula = 1509962319U;
+ uint32_t expected_priority_v4mapped = expected_priority_v4;
+ uint32_t expected_priority_v4compat = 1509949775U;
+ uint32_t expected_priority_6to4 = 1509954639U;
+ uint32_t expected_priority_teredo = 1509952079U;
+ uint32_t expected_priority_sitelocal = 1509949775U;
+ uint32_t expected_priority_6bone = 1509949775U;
+ ASSERT_EQ(expected_priority_v4, port->Candidates()[0].priority());
+ ASSERT_EQ(expected_priority_v6, port->Candidates()[1].priority());
+ ASSERT_EQ(expected_priority_ula, port->Candidates()[2].priority());
+ ASSERT_EQ(expected_priority_v4mapped, port->Candidates()[3].priority());
+ ASSERT_EQ(expected_priority_v4compat, port->Candidates()[4].priority());
+ ASSERT_EQ(expected_priority_6to4, port->Candidates()[5].priority());
+ ASSERT_EQ(expected_priority_teredo, port->Candidates()[6].priority());
+ ASSERT_EQ(expected_priority_sitelocal, port->Candidates()[7].priority());
+ ASSERT_EQ(expected_priority_6bone, port->Candidates()[8].priority());
+}
+
+// In the case of shared socket, one port may be shared by local and stun.
+// Test that candidates with different types will have different foundation.
+TEST_F(PortTest, TestFoundation) {
+ auto testport = CreateTestPort(kLocalAddr1, "name", "pass");
+ testport->SetIceTiebreaker(kTiebreakerDefault);
+ testport->AddCandidateAddress(kLocalAddr1, kLocalAddr1, LOCAL_PORT_TYPE,
+ cricket::ICE_TYPE_PREFERENCE_HOST, false);
+ testport->AddCandidateAddress(kLocalAddr2, kLocalAddr1, STUN_PORT_TYPE,
+ cricket::ICE_TYPE_PREFERENCE_SRFLX, true);
+ EXPECT_NE(testport->Candidates()[0].foundation(),
+ testport->Candidates()[1].foundation());
+}
+
+// This test verifies the foundation of different types of ICE candidates.
+TEST_F(PortTest, TestCandidateFoundation) {
+ std::unique_ptr<rtc::NATServer> nat_server(
+ CreateNatServer(kNatAddr1, NAT_OPEN_CONE));
+ auto udpport1 = CreateUdpPort(kLocalAddr1);
+ udpport1->PrepareAddress();
+ auto udpport2 = CreateUdpPort(kLocalAddr1);
+ udpport2->PrepareAddress();
+ EXPECT_EQ(udpport1->Candidates()[0].foundation(),
+ udpport2->Candidates()[0].foundation());
+ auto tcpport1 = CreateTcpPort(kLocalAddr1);
+ tcpport1->PrepareAddress();
+ auto tcpport2 = CreateTcpPort(kLocalAddr1);
+ tcpport2->PrepareAddress();
+ EXPECT_EQ(tcpport1->Candidates()[0].foundation(),
+ tcpport2->Candidates()[0].foundation());
+ auto stunport = CreateStunPort(kLocalAddr1, nat_socket_factory1());
+ stunport->PrepareAddress();
+ ASSERT_EQ_WAIT(1U, stunport->Candidates().size(), kDefaultTimeout);
+ EXPECT_NE(tcpport1->Candidates()[0].foundation(),
+ stunport->Candidates()[0].foundation());
+ EXPECT_NE(tcpport2->Candidates()[0].foundation(),
+ stunport->Candidates()[0].foundation());
+ EXPECT_NE(udpport1->Candidates()[0].foundation(),
+ stunport->Candidates()[0].foundation());
+ EXPECT_NE(udpport2->Candidates()[0].foundation(),
+ stunport->Candidates()[0].foundation());
+ // Verifying TURN candidate foundation.
+ auto turnport1 =
+ CreateTurnPort(kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP);
+ turnport1->PrepareAddress();
+ ASSERT_EQ_WAIT(1U, turnport1->Candidates().size(), kDefaultTimeout);
+ EXPECT_NE(udpport1->Candidates()[0].foundation(),
+ turnport1->Candidates()[0].foundation());
+ EXPECT_NE(udpport2->Candidates()[0].foundation(),
+ turnport1->Candidates()[0].foundation());
+ EXPECT_NE(stunport->Candidates()[0].foundation(),
+ turnport1->Candidates()[0].foundation());
+ auto turnport2 =
+ CreateTurnPort(kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP);
+ turnport2->PrepareAddress();
+ ASSERT_EQ_WAIT(1U, turnport2->Candidates().size(), kDefaultTimeout);
+ EXPECT_EQ(turnport1->Candidates()[0].foundation(),
+ turnport2->Candidates()[0].foundation());
+
+ // Running a second turn server, to get different base IP address.
+ SocketAddress kTurnUdpIntAddr2("99.99.98.4", STUN_SERVER_PORT);
+ SocketAddress kTurnUdpExtAddr2("99.99.98.5", 0);
+ TestTurnServer turn_server2(rtc::Thread::Current(), vss(), kTurnUdpIntAddr2,
+ kTurnUdpExtAddr2);
+ auto turnport3 = CreateTurnPort(kLocalAddr1, nat_socket_factory1(), PROTO_UDP,
+ PROTO_UDP, kTurnUdpIntAddr2);
+ turnport3->PrepareAddress();
+ ASSERT_EQ_WAIT(1U, turnport3->Candidates().size(), kDefaultTimeout);
+ EXPECT_NE(turnport3->Candidates()[0].foundation(),
+ turnport2->Candidates()[0].foundation());
+
+ // Start a TCP turn server, and check that two turn candidates have
+ // different foundations if their relay protocols are different.
+ TestTurnServer turn_server3(rtc::Thread::Current(), vss(), kTurnTcpIntAddr,
+ kTurnUdpExtAddr, PROTO_TCP);
+ auto turnport4 =
+ CreateTurnPort(kLocalAddr1, nat_socket_factory1(), PROTO_TCP, PROTO_UDP);
+ turnport4->PrepareAddress();
+ ASSERT_EQ_WAIT(1U, turnport4->Candidates().size(), kDefaultTimeout);
+ EXPECT_NE(turnport2->Candidates()[0].foundation(),
+ turnport4->Candidates()[0].foundation());
+}
+
+// This test verifies the related addresses of different types of
+// ICE candidates.
+TEST_F(PortTest, TestCandidateRelatedAddress) {
+ auto nat_server = CreateNatServer(kNatAddr1, NAT_OPEN_CONE);
+ auto udpport = CreateUdpPort(kLocalAddr1);
+ udpport->PrepareAddress();
+ // For UDPPort, related address will be empty.
+ EXPECT_TRUE(udpport->Candidates()[0].related_address().IsNil());
+ // Testing related address for stun candidates.
+ // For stun candidate related address must be equal to the base
+ // socket address.
+ auto stunport = CreateStunPort(kLocalAddr1, nat_socket_factory1());
+ stunport->PrepareAddress();
+ ASSERT_EQ_WAIT(1U, stunport->Candidates().size(), kDefaultTimeout);
+ // Check STUN candidate address.
+ EXPECT_EQ(stunport->Candidates()[0].address().ipaddr(), kNatAddr1.ipaddr());
+ // Check STUN candidate related address.
+ EXPECT_EQ(stunport->Candidates()[0].related_address(),
+ stunport->GetLocalAddress());
+ // Verifying the related address for TURN candidate.
+ // For TURN related address must be equal to the mapped address.
+ auto turnport =
+ CreateTurnPort(kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP);
+ turnport->PrepareAddress();
+ ASSERT_EQ_WAIT(1U, turnport->Candidates().size(), kDefaultTimeout);
+ EXPECT_EQ(kTurnUdpExtAddr.ipaddr(),
+ turnport->Candidates()[0].address().ipaddr());
+ EXPECT_EQ(kNatAddr1.ipaddr(),
+ turnport->Candidates()[0].related_address().ipaddr());
+}
+
+// Test priority value overflow handling when preference is set to 3.
+TEST_F(PortTest, TestCandidatePriority) {
+ cricket::Candidate cand1;
+ cand1.set_priority(3);
+ cricket::Candidate cand2;
+ cand2.set_priority(1);
+ EXPECT_TRUE(cand1.priority() > cand2.priority());
+}
+
+// Test the Connection priority is calculated correctly.
+TEST_F(PortTest, TestConnectionPriority) {
+ auto lport = CreateTestPort(kLocalAddr1, "lfrag", "lpass");
+ lport->SetIceTiebreaker(kTiebreakerDefault);
+ lport->set_type_preference(cricket::ICE_TYPE_PREFERENCE_HOST);
+
+ auto rport = CreateTestPort(kLocalAddr2, "rfrag", "rpass");
+ rport->SetIceTiebreaker(kTiebreakerDefault);
+ rport->set_type_preference(cricket::ICE_TYPE_PREFERENCE_RELAY_UDP);
+ lport->set_component(123);
+ lport->AddCandidateAddress(SocketAddress("192.168.1.4", 1234));
+ rport->set_component(23);
+ rport->AddCandidateAddress(SocketAddress("10.1.1.100", 1234));
+
+ EXPECT_EQ(0x7E001E85U, lport->Candidates()[0].priority());
+ EXPECT_EQ(0x2001EE9U, rport->Candidates()[0].priority());
+
+ // RFC 5245
+ // pair priority = 2^32*MIN(G,D) + 2*MAX(G,D) + (G>D?1:0)
+ lport->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ rport->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ Connection* lconn =
+ lport->CreateConnection(rport->Candidates()[0], Port::ORIGIN_MESSAGE);
+#if defined(WEBRTC_WIN)
+ EXPECT_EQ(0x2001EE9FC003D0BU, lconn->priority());
+#else
+ EXPECT_EQ(0x2001EE9FC003D0BLLU, lconn->priority());
+#endif
+
+ lport->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ rport->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ Connection* rconn =
+ rport->CreateConnection(lport->Candidates()[0], Port::ORIGIN_MESSAGE);
+#if defined(WEBRTC_WIN)
+ EXPECT_EQ(0x2001EE9FC003D0AU, rconn->priority());
+#else
+ EXPECT_EQ(0x2001EE9FC003D0ALLU, rconn->priority());
+#endif
+}
+
+// Note that UpdateState takes into account the estimated RTT, and the
+// correctness of using `kMaxExpectedSimulatedRtt` as an upper bound of RTT in
+// the following tests depends on the link rate and the delay distriubtion
+// configured in VirtualSocketServer::AddPacketToNetwork. The tests below use
+// the default setup where the RTT is deterministically one, which generates an
+// estimate given by `MINIMUM_RTT` = 100.
+TEST_F(PortTest, TestWritableState) {
+ rtc::ScopedFakeClock clock;
+ auto port1 = CreateUdpPort(kLocalAddr1);
+ port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ auto port2 = CreateUdpPort(kLocalAddr2);
+ port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
+
+ // Set up channels.
+ TestChannel ch1(std::move(port1));
+ TestChannel ch2(std::move(port2));
+
+ // Acquire addresses.
+ ch1.Start();
+ ch2.Start();
+ ASSERT_EQ_SIMULATED_WAIT(1, ch1.complete_count(), kDefaultTimeout, clock);
+ ASSERT_EQ_SIMULATED_WAIT(1, ch2.complete_count(), kDefaultTimeout, clock);
+
+ // Send a ping from src to dst.
+ ch1.CreateConnection(GetCandidate(ch2.port()));
+ ASSERT_TRUE(ch1.conn() != NULL);
+ EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state());
+ // for TCP connect
+ EXPECT_TRUE_SIMULATED_WAIT(ch1.conn()->connected(), kDefaultTimeout, clock);
+ ch1.Ping();
+ SIMULATED_WAIT(!ch2.remote_address().IsNil(), kShortTimeout, clock);
+
+ // Data should be sendable before the connection is accepted.
+ char data[] = "abcd";
+ int data_size = arraysize(data);
+ rtc::PacketOptions options;
+ EXPECT_EQ(data_size, ch1.conn()->Send(data, data_size, options));
+
+ // Accept the connection to return the binding response, transition to
+ // writable, and allow data to be sent.
+ ch2.AcceptConnection(GetCandidate(ch1.port()));
+ EXPECT_EQ_SIMULATED_WAIT(Connection::STATE_WRITABLE,
+ ch1.conn()->write_state(), kDefaultTimeout, clock);
+ EXPECT_EQ(data_size, ch1.conn()->Send(data, data_size, options));
+
+ // Ask the connection to update state as if enough time has passed to lose
+ // full writability and 5 pings went unresponded to. We'll accomplish the
+ // latter by sending pings but not pumping messages.
+ for (uint32_t i = 1; i <= CONNECTION_WRITE_CONNECT_FAILURES; ++i) {
+ ch1.Ping(i);
+ }
+ int unreliable_timeout_delay =
+ CONNECTION_WRITE_CONNECT_TIMEOUT + kMaxExpectedSimulatedRtt;
+ ch1.conn()->UpdateState(unreliable_timeout_delay);
+ EXPECT_EQ(Connection::STATE_WRITE_UNRELIABLE, ch1.conn()->write_state());
+
+ // Data should be able to be sent in this state.
+ EXPECT_EQ(data_size, ch1.conn()->Send(data, data_size, options));
+
+ // And now allow the other side to process the pings and send binding
+ // responses.
+ EXPECT_EQ_SIMULATED_WAIT(Connection::STATE_WRITABLE,
+ ch1.conn()->write_state(), kDefaultTimeout, clock);
+ // Wait long enough for a full timeout (past however long we've already
+ // waited).
+ for (uint32_t i = 1; i <= CONNECTION_WRITE_CONNECT_FAILURES; ++i) {
+ ch1.Ping(unreliable_timeout_delay + i);
+ }
+ ch1.conn()->UpdateState(unreliable_timeout_delay + CONNECTION_WRITE_TIMEOUT +
+ kMaxExpectedSimulatedRtt);
+ EXPECT_EQ(Connection::STATE_WRITE_TIMEOUT, ch1.conn()->write_state());
+
+ // Even if the connection has timed out, the Connection shouldn't block
+ // the sending of data.
+ EXPECT_EQ(data_size, ch1.conn()->Send(data, data_size, options));
+
+ ch1.Stop();
+ ch2.Stop();
+}
+
+// Test writability states using the configured threshold value to replace
+// the default value given by `CONNECTION_WRITE_CONNECT_TIMEOUT` and
+// `CONNECTION_WRITE_CONNECT_FAILURES`.
+TEST_F(PortTest, TestWritableStateWithConfiguredThreshold) {
+ rtc::ScopedFakeClock clock;
+ auto port1 = CreateUdpPort(kLocalAddr1);
+ port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ auto port2 = CreateUdpPort(kLocalAddr2);
+ port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
+
+ // Set up channels.
+ TestChannel ch1(std::move(port1));
+ TestChannel ch2(std::move(port2));
+
+ // Acquire addresses.
+ ch1.Start();
+ ch2.Start();
+ ASSERT_EQ_SIMULATED_WAIT(1, ch1.complete_count(), kDefaultTimeout, clock);
+ ASSERT_EQ_SIMULATED_WAIT(1, ch2.complete_count(), kDefaultTimeout, clock);
+
+ // Send a ping from src to dst.
+ ch1.CreateConnection(GetCandidate(ch2.port()));
+ ASSERT_TRUE(ch1.conn() != NULL);
+ ch1.Ping();
+ SIMULATED_WAIT(!ch2.remote_address().IsNil(), kShortTimeout, clock);
+
+ // Accept the connection to return the binding response, transition to
+ // writable, and allow data to be sent.
+ ch2.AcceptConnection(GetCandidate(ch1.port()));
+ EXPECT_EQ_SIMULATED_WAIT(Connection::STATE_WRITABLE,
+ ch1.conn()->write_state(), kDefaultTimeout, clock);
+
+ ch1.conn()->set_unwritable_timeout(1000);
+ ch1.conn()->set_unwritable_min_checks(3);
+ // Send two checks.
+ ch1.Ping(1);
+ ch1.Ping(2);
+ // We have not reached the timeout nor have we sent the minimum number of
+ // checks to change the state to Unreliable.
+ ch1.conn()->UpdateState(999);
+ EXPECT_EQ(Connection::STATE_WRITABLE, ch1.conn()->write_state());
+ // We have not sent the minimum number of checks without responses.
+ ch1.conn()->UpdateState(1000 + kMaxExpectedSimulatedRtt);
+ EXPECT_EQ(Connection::STATE_WRITABLE, ch1.conn()->write_state());
+ // Last ping after which the candidate pair should become Unreliable after
+ // timeout.
+ ch1.Ping(3);
+ // We have not reached the timeout.
+ ch1.conn()->UpdateState(999);
+ EXPECT_EQ(Connection::STATE_WRITABLE, ch1.conn()->write_state());
+ // We should be in the state Unreliable now.
+ ch1.conn()->UpdateState(1000 + kMaxExpectedSimulatedRtt);
+ EXPECT_EQ(Connection::STATE_WRITE_UNRELIABLE, ch1.conn()->write_state());
+
+ ch1.Stop();
+ ch2.Stop();
+}
+
+TEST_F(PortTest, TestTimeoutForNeverWritable) {
+ auto port1 = CreateUdpPort(kLocalAddr1);
+ port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ auto port2 = CreateUdpPort(kLocalAddr2);
+ port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
+
+ // Set up channels.
+ TestChannel ch1(std::move(port1));
+ TestChannel ch2(std::move(port2));
+
+ // Acquire addresses.
+ ch1.Start();
+ ch2.Start();
+
+ ch1.CreateConnection(GetCandidate(ch2.port()));
+ ASSERT_TRUE(ch1.conn() != NULL);
+ EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state());
+
+ // Attempt to go directly to write timeout.
+ for (uint32_t i = 1; i <= CONNECTION_WRITE_CONNECT_FAILURES; ++i) {
+ ch1.Ping(i);
+ }
+ ch1.conn()->UpdateState(CONNECTION_WRITE_TIMEOUT + kMaxExpectedSimulatedRtt);
+ EXPECT_EQ(Connection::STATE_WRITE_TIMEOUT, ch1.conn()->write_state());
+}
+
+// This test verifies the connection setup between ICEMODE_FULL
+// and ICEMODE_LITE.
+// In this test `ch1` behaves like FULL mode client and we have created
+// port which responds to the ping message just like LITE client.
+TEST_F(PortTest, TestIceLiteConnectivity) {
+ auto ice_full_port =
+ CreateTestPort(kLocalAddr1, "lfrag", "lpass",
+ cricket::ICEROLE_CONTROLLING, kTiebreaker1);
+ auto* ice_full_port_ptr = ice_full_port.get();
+
+ auto ice_lite_port = CreateTestPort(
+ kLocalAddr2, "rfrag", "rpass", cricket::ICEROLE_CONTROLLED, kTiebreaker2);
+ // Setup TestChannel. This behaves like FULL mode client.
+ TestChannel ch1(std::move(ice_full_port));
+ ch1.SetIceMode(ICEMODE_FULL);
+
+ // Start gathering candidates.
+ ch1.Start();
+ ice_lite_port->PrepareAddress();
+
+ ASSERT_EQ_WAIT(1, ch1.complete_count(), kDefaultTimeout);
+ ASSERT_FALSE(ice_lite_port->Candidates().empty());
+
+ ch1.CreateConnection(GetCandidate(ice_lite_port.get()));
+ ASSERT_TRUE(ch1.conn() != NULL);
+ EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state());
+
+ // Send ping from full mode client.
+ // This ping must not have USE_CANDIDATE_ATTR.
+ ch1.Ping();
+
+ // Verify stun ping is without USE_CANDIDATE_ATTR. Getting message directly
+ // from port.
+ ASSERT_TRUE_WAIT(ice_full_port_ptr->last_stun_msg() != NULL, kDefaultTimeout);
+ IceMessage* msg = ice_full_port_ptr->last_stun_msg();
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USE_CANDIDATE) == NULL);
+
+ // Respond with a BINDING-RESPONSE from litemode client.
+ // NOTE: Ideally we should't create connection at this stage from lite
+ // port, as it should be done only after receiving ping with USE_CANDIDATE.
+ // But we need a connection to send a response message.
+ auto* con = ice_lite_port->CreateConnection(
+ ice_full_port_ptr->Candidates()[0], cricket::Port::ORIGIN_MESSAGE);
+ std::unique_ptr<IceMessage> request = CopyStunMessage(*msg);
+ con->SendStunBindingResponse(request.get());
+
+ // Feeding the respone message from litemode to the full mode connection.
+ ch1.conn()->OnReadPacket(ice_lite_port->last_stun_buf()->data<char>(),
+ ice_lite_port->last_stun_buf()->size(),
+ /* packet_time_us */ -1);
+ // Verifying full mode connection becomes writable from the response.
+ EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch1.conn()->write_state(),
+ kDefaultTimeout);
+ EXPECT_TRUE_WAIT(ch1.nominated(), kDefaultTimeout);
+
+ // Clear existing stun messsages. Otherwise we will process old stun
+ // message right after we send ping.
+ ice_full_port_ptr->Reset();
+ // Send ping. This must have USE_CANDIDATE_ATTR.
+ ch1.Ping();
+ ASSERT_TRUE_WAIT(ice_full_port_ptr->last_stun_msg() != NULL, kDefaultTimeout);
+ msg = ice_full_port_ptr->last_stun_msg();
+ EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USE_CANDIDATE) != NULL);
+ ch1.Stop();
+}
+
+namespace {
+
+// Utility function for testing goog ping.
+absl::optional<int> GetSupportedGoogPingVersion(const StunMessage* msg) {
+ auto goog_misc = msg->GetUInt16List(STUN_ATTR_GOOG_MISC_INFO);
+ if (goog_misc == nullptr) {
+ return absl::nullopt;
+ }
+
+ if (msg->type() == STUN_BINDING_REQUEST) {
+ if (goog_misc->Size() <
+ static_cast<int>(cricket::IceGoogMiscInfoBindingRequestAttributeIndex::
+ SUPPORT_GOOG_PING_VERSION)) {
+ return absl::nullopt;
+ }
+
+ return goog_misc->GetType(
+ static_cast<int>(cricket::IceGoogMiscInfoBindingRequestAttributeIndex::
+ SUPPORT_GOOG_PING_VERSION));
+ }
+
+ if (msg->type() == STUN_BINDING_RESPONSE) {
+ if (goog_misc->Size() <
+ static_cast<int>(cricket::IceGoogMiscInfoBindingResponseAttributeIndex::
+ SUPPORT_GOOG_PING_VERSION)) {
+ return absl::nullopt;
+ }
+
+ return goog_misc->GetType(
+ static_cast<int>(cricket::IceGoogMiscInfoBindingResponseAttributeIndex::
+ SUPPORT_GOOG_PING_VERSION));
+ }
+ return absl::nullopt;
+}
+
+} // namespace
+
+class GoogPingTest
+ : public PortTest,
+ public ::testing::WithParamInterface<std::pair<bool, bool>> {};
+
+// This test verifies the announce/enable on/off behavior
+TEST_P(GoogPingTest, TestGoogPingAnnounceEnable) {
+ IceFieldTrials trials;
+ trials.announce_goog_ping = GetParam().first;
+ trials.enable_goog_ping = GetParam().second;
+ RTC_LOG(LS_INFO) << "Testing combination: "
+ " announce: "
+ << trials.announce_goog_ping
+ << " enable:" << trials.enable_goog_ping;
+
+ auto port1_unique =
+ CreateTestPort(kLocalAddr1, "lfrag", "lpass",
+ cricket::ICEROLE_CONTROLLING, kTiebreaker1);
+ auto* port1 = port1_unique.get();
+ auto port2 = CreateTestPort(kLocalAddr2, "rfrag", "rpass",
+ cricket::ICEROLE_CONTROLLED, kTiebreaker2);
+
+ TestChannel ch1(std::move(port1_unique));
+ // Block usage of STUN_ATTR_USE_CANDIDATE so that
+ // ch1.conn() will sent GOOG_PING_REQUEST directly.
+ // This only makes test a bit shorter...
+ ch1.SetIceMode(ICEMODE_LITE);
+ // Start gathering candidates.
+ ch1.Start();
+ port2->PrepareAddress();
+
+ ASSERT_EQ_WAIT(1, ch1.complete_count(), kDefaultTimeout);
+ ASSERT_FALSE(port2->Candidates().empty());
+
+ ch1.CreateConnection(GetCandidate(port2.get()));
+ ASSERT_TRUE(ch1.conn() != NULL);
+ EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state());
+ ch1.conn()->SetIceFieldTrials(&trials);
+
+ // Send ping.
+ ch1.Ping();
+
+ ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
+ const IceMessage* request1 = port1->last_stun_msg();
+
+ ASSERT_EQ(trials.enable_goog_ping,
+ GetSupportedGoogPingVersion(request1) &&
+ GetSupportedGoogPingVersion(request1) >= kGoogPingVersion);
+
+ auto* con = port2->CreateConnection(port1->Candidates()[0],
+ cricket::Port::ORIGIN_MESSAGE);
+ con->SetIceFieldTrials(&trials);
+
+ con->SendStunBindingResponse(request1);
+
+ // Then check the response matches the settings.
+ const auto* response = port2->last_stun_msg();
+ EXPECT_EQ(response->type(), STUN_BINDING_RESPONSE);
+ EXPECT_EQ(trials.enable_goog_ping && trials.announce_goog_ping,
+ GetSupportedGoogPingVersion(response) &&
+ GetSupportedGoogPingVersion(response) >= kGoogPingVersion);
+
+ // Feeding the respone message back.
+ ch1.conn()->OnReadPacket(port2->last_stun_buf()->data<char>(),
+ port2->last_stun_buf()->size(),
+ /* packet_time_us */ -1);
+
+ port1->Reset();
+ port2->Reset();
+
+ ch1.Ping();
+ ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
+ const IceMessage* request2 = port1->last_stun_msg();
+
+ // It should be a GOOG_PING if both of these are TRUE
+ if (trials.announce_goog_ping && trials.enable_goog_ping) {
+ ASSERT_EQ(request2->type(), GOOG_PING_REQUEST);
+ con->SendGoogPingResponse(request2);
+ } else {
+ ASSERT_EQ(request2->type(), STUN_BINDING_REQUEST);
+ // If we sent a BINDING with enable, and we got a reply that
+ // didn't contain announce, the next ping should not contain
+ // the enable again.
+ ASSERT_FALSE(GetSupportedGoogPingVersion(request2).has_value());
+ con->SendStunBindingResponse(request2);
+ }
+
+ const auto* response2 = port2->last_stun_msg();
+ ASSERT_TRUE(response2 != nullptr);
+
+ // It should be a GOOG_PING_RESPONSE if both of these are TRUE
+ if (trials.announce_goog_ping && trials.enable_goog_ping) {
+ ASSERT_EQ(response2->type(), GOOG_PING_RESPONSE);
+ } else {
+ ASSERT_EQ(response2->type(), STUN_BINDING_RESPONSE);
+ }
+
+ ch1.Stop();
+}
+
+// This test if a someone send a STUN_BINDING with unsupported version
+// (kGoogPingVersion == 0)
+TEST_F(PortTest, TestGoogPingUnsupportedVersionInStunBinding) {
+ IceFieldTrials trials;
+ trials.announce_goog_ping = true;
+ trials.enable_goog_ping = true;
+
+ auto port1_unique =
+ CreateTestPort(kLocalAddr1, "lfrag", "lpass",
+ cricket::ICEROLE_CONTROLLING, kTiebreaker1);
+ auto* port1 = port1_unique.get();
+ auto port2 = CreateTestPort(kLocalAddr2, "rfrag", "rpass",
+ cricket::ICEROLE_CONTROLLED, kTiebreaker2);
+
+ TestChannel ch1(std::move(port1_unique));
+ // Block usage of STUN_ATTR_USE_CANDIDATE so that
+ // ch1.conn() will sent GOOG_PING_REQUEST directly.
+ // This only makes test a bit shorter...
+ ch1.SetIceMode(ICEMODE_LITE);
+ // Start gathering candidates.
+ ch1.Start();
+ port2->PrepareAddress();
+
+ ASSERT_EQ_WAIT(1, ch1.complete_count(), kDefaultTimeout);
+ ASSERT_FALSE(port2->Candidates().empty());
+
+ ch1.CreateConnection(GetCandidate(port2.get()));
+ ASSERT_TRUE(ch1.conn() != NULL);
+ EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state());
+ ch1.conn()->SetIceFieldTrials(&trials);
+
+ // Send ping.
+ ch1.Ping();
+
+ ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
+ const IceMessage* request1 = port1->last_stun_msg();
+
+ ASSERT_TRUE(GetSupportedGoogPingVersion(request1) &&
+ GetSupportedGoogPingVersion(request1) >= kGoogPingVersion);
+
+ // Modify the STUN message request1 to send GetSupportedGoogPingVersion == 0
+ auto modified_request1 = request1->Clone();
+ ASSERT_TRUE(modified_request1->RemoveAttribute(STUN_ATTR_GOOG_MISC_INFO) !=
+ nullptr);
+ ASSERT_TRUE(modified_request1->RemoveAttribute(STUN_ATTR_MESSAGE_INTEGRITY) !=
+ nullptr);
+ {
+ auto list =
+ StunAttribute::CreateUInt16ListAttribute(STUN_ATTR_GOOG_MISC_INFO);
+ list->AddTypeAtIndex(
+ static_cast<uint16_t>(
+ cricket::IceGoogMiscInfoBindingRequestAttributeIndex::
+ SUPPORT_GOOG_PING_VERSION),
+ /* version */ 0);
+ modified_request1->AddAttribute(std::move(list));
+ modified_request1->AddMessageIntegrity("rpass");
+ }
+ auto* con = port2->CreateConnection(port1->Candidates()[0],
+ cricket::Port::ORIGIN_MESSAGE);
+ con->SetIceFieldTrials(&trials);
+
+ con->SendStunBindingResponse(modified_request1.get());
+
+ // Then check the response matches the settings.
+ const auto* response = port2->last_stun_msg();
+ EXPECT_EQ(response->type(), STUN_BINDING_RESPONSE);
+ EXPECT_FALSE(GetSupportedGoogPingVersion(response));
+
+ ch1.Stop();
+}
+
+// This test if a someone send a STUN_BINDING_RESPONSE with unsupported version
+// (kGoogPingVersion == 0)
+TEST_F(PortTest, TestGoogPingUnsupportedVersionInStunBindingResponse) {
+ IceFieldTrials trials;
+ trials.announce_goog_ping = true;
+ trials.enable_goog_ping = true;
+
+ auto port1_unique =
+ CreateTestPort(kLocalAddr1, "lfrag", "lpass",
+ cricket::ICEROLE_CONTROLLING, kTiebreaker1);
+ auto* port1 = port1_unique.get();
+ auto port2 = CreateTestPort(kLocalAddr2, "rfrag", "rpass",
+ cricket::ICEROLE_CONTROLLED, kTiebreaker2);
+
+ TestChannel ch1(std::move(port1_unique));
+ // Block usage of STUN_ATTR_USE_CANDIDATE so that
+ // ch1.conn() will sent GOOG_PING_REQUEST directly.
+ // This only makes test a bit shorter...
+ ch1.SetIceMode(ICEMODE_LITE);
+ // Start gathering candidates.
+ ch1.Start();
+ port2->PrepareAddress();
+
+ ASSERT_EQ_WAIT(1, ch1.complete_count(), kDefaultTimeout);
+ ASSERT_FALSE(port2->Candidates().empty());
+
+ ch1.CreateConnection(GetCandidate(port2.get()));
+ ASSERT_TRUE(ch1.conn() != NULL);
+ EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state());
+ ch1.conn()->SetIceFieldTrials(&trials);
+
+ // Send ping.
+ ch1.Ping();
+
+ ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
+ const IceMessage* request1 = port1->last_stun_msg();
+
+ ASSERT_TRUE(GetSupportedGoogPingVersion(request1) &&
+ GetSupportedGoogPingVersion(request1) >= kGoogPingVersion);
+
+ auto* con = port2->CreateConnection(port1->Candidates()[0],
+ cricket::Port::ORIGIN_MESSAGE);
+ con->SetIceFieldTrials(&trials);
+
+ con->SendStunBindingResponse(request1);
+
+ // Then check the response matches the settings.
+ const auto* response = port2->last_stun_msg();
+ EXPECT_EQ(response->type(), STUN_BINDING_RESPONSE);
+ EXPECT_TRUE(GetSupportedGoogPingVersion(response));
+
+ // Modify the STUN message response to contain GetSupportedGoogPingVersion ==
+ // 0
+ auto modified_response = response->Clone();
+ ASSERT_TRUE(modified_response->RemoveAttribute(STUN_ATTR_GOOG_MISC_INFO) !=
+ nullptr);
+ ASSERT_TRUE(modified_response->RemoveAttribute(STUN_ATTR_MESSAGE_INTEGRITY) !=
+ nullptr);
+ ASSERT_TRUE(modified_response->RemoveAttribute(STUN_ATTR_FINGERPRINT) !=
+ nullptr);
+ {
+ auto list =
+ StunAttribute::CreateUInt16ListAttribute(STUN_ATTR_GOOG_MISC_INFO);
+ list->AddTypeAtIndex(
+ static_cast<uint16_t>(
+ cricket::IceGoogMiscInfoBindingResponseAttributeIndex::
+ SUPPORT_GOOG_PING_VERSION),
+ /* version */ 0);
+ modified_response->AddAttribute(std::move(list));
+ modified_response->AddMessageIntegrity("rpass");
+ modified_response->AddFingerprint();
+ }
+
+ rtc::ByteBufferWriter buf;
+ modified_response->Write(&buf);
+
+ // Feeding the modified respone message back.
+ ch1.conn()->OnReadPacket(buf.Data(), buf.Length(), /* packet_time_us */ -1);
+
+ port1->Reset();
+ port2->Reset();
+
+ ch1.Ping();
+ ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
+
+ // This should now be a STUN_BINDING...without a kGoogPingVersion
+ const IceMessage* request2 = port1->last_stun_msg();
+ EXPECT_EQ(request2->type(), STUN_BINDING_REQUEST);
+ EXPECT_FALSE(GetSupportedGoogPingVersion(request2));
+
+ ch1.Stop();
+}
+
+INSTANTIATE_TEST_SUITE_P(GoogPingTest,
+ GoogPingTest,
+ // test all combinations of <announce, enable> pairs.
+ ::testing::Values(std::make_pair(false, false),
+ std::make_pair(true, false),
+ std::make_pair(false, true),
+ std::make_pair(true, true)));
+
+// This test checks that a change in attributes falls back to STUN_BINDING
+TEST_F(PortTest, TestChangeInAttributeMakesGoogPingFallsbackToStunBinding) {
+ IceFieldTrials trials;
+ trials.announce_goog_ping = true;
+ trials.enable_goog_ping = true;
+
+ auto port1_unique =
+ CreateTestPort(kLocalAddr1, "lfrag", "lpass",
+ cricket::ICEROLE_CONTROLLING, kTiebreaker1);
+ auto* port1 = port1_unique.get();
+ auto port2 = CreateTestPort(kLocalAddr2, "rfrag", "rpass",
+ cricket::ICEROLE_CONTROLLED, kTiebreaker2);
+
+ TestChannel ch1(std::move(port1_unique));
+ // Block usage of STUN_ATTR_USE_CANDIDATE so that
+ // ch1.conn() will sent GOOG_PING_REQUEST directly.
+ // This only makes test a bit shorter...
+ ch1.SetIceMode(ICEMODE_LITE);
+ // Start gathering candidates.
+ ch1.Start();
+ port2->PrepareAddress();
+
+ ASSERT_EQ_WAIT(1, ch1.complete_count(), kDefaultTimeout);
+ ASSERT_FALSE(port2->Candidates().empty());
+
+ ch1.CreateConnection(GetCandidate(port2.get()));
+ ASSERT_TRUE(ch1.conn() != nullptr);
+ EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state());
+ ch1.conn()->SetIceFieldTrials(&trials);
+
+ // Send ping.
+ ch1.Ping();
+
+ ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
+ const IceMessage* msg = port1->last_stun_msg();
+ auto* con = port2->CreateConnection(port1->Candidates()[0],
+ cricket::Port::ORIGIN_MESSAGE);
+ con->SetIceFieldTrials(&trials);
+
+ // Feed the message into the connection.
+ con->SendStunBindingResponse(msg);
+
+ // The check reply wrt to settings.
+ const auto* response = port2->last_stun_msg();
+ ASSERT_EQ(response->type(), STUN_BINDING_RESPONSE);
+ ASSERT_TRUE(GetSupportedGoogPingVersion(response) >= kGoogPingVersion);
+
+ // Feeding the respone message back.
+ ch1.conn()->OnReadPacket(port2->last_stun_buf()->data<char>(),
+ port2->last_stun_buf()->size(),
+ /* packet_time_us */ -1);
+
+ port1->Reset();
+ port2->Reset();
+
+ ch1.Ping();
+ ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
+ const IceMessage* msg2 = port1->last_stun_msg();
+
+ // It should be a GOOG_PING if both of these are TRUE
+ ASSERT_EQ(msg2->type(), GOOG_PING_REQUEST);
+ con->SendGoogPingResponse(msg2);
+
+ const auto* response2 = port2->last_stun_msg();
+ ASSERT_TRUE(response2 != nullptr);
+
+ // It should be a GOOG_PING_RESPONSE.
+ ASSERT_EQ(response2->type(), GOOG_PING_RESPONSE);
+
+ // And now the third ping.
+ port1->Reset();
+ port2->Reset();
+
+ // Modify the message to be sent.
+ ch1.conn()->set_use_candidate_attr(!ch1.conn()->use_candidate_attr());
+
+ ch1.Ping();
+ ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
+ const IceMessage* msg3 = port1->last_stun_msg();
+
+ // It should be a STUN_BINDING_REQUEST
+ ASSERT_EQ(msg3->type(), STUN_BINDING_REQUEST);
+
+ ch1.Stop();
+}
+
+// This test that an error response fall back to STUN_BINDING.
+TEST_F(PortTest, TestErrorResponseMakesGoogPingFallBackToStunBinding) {
+ IceFieldTrials trials;
+ trials.announce_goog_ping = true;
+ trials.enable_goog_ping = true;
+
+ auto port1_unique =
+ CreateTestPort(kLocalAddr1, "lfrag", "lpass",
+ cricket::ICEROLE_CONTROLLING, kTiebreaker1);
+ auto* port1 = port1_unique.get();
+ auto port2 = CreateTestPort(kLocalAddr2, "rfrag", "rpass",
+ cricket::ICEROLE_CONTROLLED, kTiebreaker2);
+
+ TestChannel ch1(std::move(port1_unique));
+ // Block usage of STUN_ATTR_USE_CANDIDATE so that
+ // ch1.conn() will sent GOOG_PING_REQUEST directly.
+ // This only makes test a bit shorter...
+ ch1.SetIceMode(ICEMODE_LITE);
+ // Start gathering candidates.
+ ch1.Start();
+ port2->PrepareAddress();
+
+ ASSERT_EQ_WAIT(1, ch1.complete_count(), kDefaultTimeout);
+ ASSERT_FALSE(port2->Candidates().empty());
+
+ ch1.CreateConnection(GetCandidate(port2.get()));
+ ASSERT_TRUE(ch1.conn() != NULL);
+ EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state());
+ ch1.conn()->SetIceFieldTrials(&trials);
+
+ // Send ping.
+ ch1.Ping();
+
+ ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
+ const IceMessage* msg = port1->last_stun_msg();
+ auto* con = port2->CreateConnection(port1->Candidates()[0],
+ cricket::Port::ORIGIN_MESSAGE);
+ con->SetIceFieldTrials(&trials);
+
+ // Feed the message into the connection.
+ con->SendStunBindingResponse(msg);
+
+ // The check reply wrt to settings.
+ const auto* response = port2->last_stun_msg();
+ ASSERT_EQ(response->type(), STUN_BINDING_RESPONSE);
+ ASSERT_TRUE(GetSupportedGoogPingVersion(response) >= kGoogPingVersion);
+
+ // Feeding the respone message back.
+ ch1.conn()->OnReadPacket(port2->last_stun_buf()->data<char>(),
+ port2->last_stun_buf()->size(),
+ /* packet_time_us */ -1);
+
+ port1->Reset();
+ port2->Reset();
+
+ ch1.Ping();
+ ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
+ const IceMessage* msg2 = port1->last_stun_msg();
+
+ // It should be a GOOG_PING.
+ ASSERT_EQ(msg2->type(), GOOG_PING_REQUEST);
+ con->SendGoogPingResponse(msg2);
+
+ const auto* response2 = port2->last_stun_msg();
+ ASSERT_TRUE(response2 != nullptr);
+
+ // It should be a GOOG_PING_RESPONSE.
+ ASSERT_EQ(response2->type(), GOOG_PING_RESPONSE);
+
+ // But rather than the RESPONSE...feedback an error.
+ StunMessage error_response(GOOG_PING_ERROR_RESPONSE);
+ error_response.SetTransactionIdForTesting(response2->transaction_id());
+ error_response.AddMessageIntegrity32("rpass");
+ rtc::ByteBufferWriter buf;
+ error_response.Write(&buf);
+
+ ch1.conn()->OnReadPacket(buf.Data(), buf.Length(),
+ /* packet_time_us */ -1);
+
+ // And now the third ping...this should be a binding.
+ port1->Reset();
+ port2->Reset();
+
+ ch1.Ping();
+ ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
+ const IceMessage* msg3 = port1->last_stun_msg();
+
+ // It should be a STUN_BINDING_REQUEST
+ ASSERT_EQ(msg3->type(), STUN_BINDING_REQUEST);
+
+ ch1.Stop();
+}
+
+// This test case verifies that both the controlling port and the controlled
+// port will time out after connectivity is lost, if they are not marked as
+// "keep alive until pruned."
+TEST_F(PortTest, TestPortTimeoutIfNotKeptAlive) {
+ rtc::ScopedFakeClock clock;
+ int timeout_delay = 100;
+ auto port1 = CreateUdpPort(kLocalAddr1);
+ ConnectToSignalDestroyed(port1.get());
+ port1->set_timeout_delay(timeout_delay); // milliseconds
+ port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ port1->SetIceTiebreaker(kTiebreaker1);
+
+ auto port2 = CreateUdpPort(kLocalAddr2);
+ ConnectToSignalDestroyed(port2.get());
+ port2->set_timeout_delay(timeout_delay); // milliseconds
+ port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ port2->SetIceTiebreaker(kTiebreaker2);
+
+ // Set up channels and ensure both ports will be deleted.
+ TestChannel ch1(std::move(port1));
+ TestChannel ch2(std::move(port2));
+
+ // Simulate a connection that succeeds, and then is destroyed.
+ StartConnectAndStopChannels(&ch1, &ch2);
+ // After the connection is destroyed, the port will be destroyed because
+ // none of them is marked as "keep alive until pruned.
+ EXPECT_EQ_SIMULATED_WAIT(2, ports_destroyed(), 110, clock);
+}
+
+// Test that if after all connection are destroyed, new connections are created
+// and destroyed again, ports won't be destroyed until a timeout period passes
+// after the last set of connections are all destroyed.
+TEST_F(PortTest, TestPortTimeoutAfterNewConnectionCreatedAndDestroyed) {
+ rtc::ScopedFakeClock clock;
+ int timeout_delay = 100;
+ auto port1 = CreateUdpPort(kLocalAddr1);
+ ConnectToSignalDestroyed(port1.get());
+ port1->set_timeout_delay(timeout_delay); // milliseconds
+ port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ port1->SetIceTiebreaker(kTiebreaker1);
+
+ auto port2 = CreateUdpPort(kLocalAddr2);
+ ConnectToSignalDestroyed(port2.get());
+ port2->set_timeout_delay(timeout_delay); // milliseconds
+
+ port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ port2->SetIceTiebreaker(kTiebreaker2);
+
+ // Set up channels and ensure both ports will be deleted.
+ TestChannel ch1(std::move(port1));
+ TestChannel ch2(std::move(port2));
+
+ // Simulate a connection that succeeds, and then is destroyed.
+ StartConnectAndStopChannels(&ch1, &ch2);
+ SIMULATED_WAIT(ports_destroyed() > 0, 80, clock);
+ EXPECT_EQ(0, ports_destroyed());
+
+ // Start the second set of connection and destroy them.
+ ch1.CreateConnection(GetCandidate(ch2.port()));
+ ch2.CreateConnection(GetCandidate(ch1.port()));
+ ch1.Stop();
+ ch2.Stop();
+
+ SIMULATED_WAIT(ports_destroyed() > 0, 80, clock);
+ EXPECT_EQ(0, ports_destroyed());
+
+ // The ports on both sides should be destroyed after timeout.
+ EXPECT_TRUE_SIMULATED_WAIT(ports_destroyed() == 2, 30, clock);
+}
+
+// This test case verifies that neither the controlling port nor the controlled
+// port will time out after connectivity is lost if they are marked as "keep
+// alive until pruned". They will time out after they are pruned.
+TEST_F(PortTest, TestPortNotTimeoutUntilPruned) {
+ rtc::ScopedFakeClock clock;
+ int timeout_delay = 100;
+ auto port1 = CreateUdpPort(kLocalAddr1);
+ ConnectToSignalDestroyed(port1.get());
+ port1->set_timeout_delay(timeout_delay); // milliseconds
+ port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ port1->SetIceTiebreaker(kTiebreaker1);
+
+ auto port2 = CreateUdpPort(kLocalAddr2);
+ ConnectToSignalDestroyed(port2.get());
+ port2->set_timeout_delay(timeout_delay); // milliseconds
+ port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ port2->SetIceTiebreaker(kTiebreaker2);
+ // The connection must not be destroyed before a connection is attempted.
+ EXPECT_EQ(0, ports_destroyed());
+
+ port1->set_component(cricket::ICE_CANDIDATE_COMPONENT_DEFAULT);
+ port2->set_component(cricket::ICE_CANDIDATE_COMPONENT_DEFAULT);
+
+ // Set up channels and keep the port alive.
+ TestChannel ch1(std::move(port1));
+ TestChannel ch2(std::move(port2));
+ // Simulate a connection that succeeds, and then is destroyed. But ports
+ // are kept alive. Ports won't be destroyed.
+ StartConnectAndStopChannels(&ch1, &ch2);
+ ch1.port()->KeepAliveUntilPruned();
+ ch2.port()->KeepAliveUntilPruned();
+ SIMULATED_WAIT(ports_destroyed() > 0, 150, clock);
+ EXPECT_EQ(0, ports_destroyed());
+
+ // If they are pruned now, they will be destroyed right away.
+ ch1.port()->Prune();
+ ch2.port()->Prune();
+ // The ports on both sides should be destroyed after timeout.
+ EXPECT_TRUE_SIMULATED_WAIT(ports_destroyed() == 2, 1, clock);
+}
+
+TEST_F(PortTest, TestSupportsProtocol) {
+ auto udp_port = CreateUdpPort(kLocalAddr1);
+ EXPECT_TRUE(udp_port->SupportsProtocol(UDP_PROTOCOL_NAME));
+ EXPECT_FALSE(udp_port->SupportsProtocol(TCP_PROTOCOL_NAME));
+
+ auto stun_port = CreateStunPort(kLocalAddr1, nat_socket_factory1());
+ EXPECT_TRUE(stun_port->SupportsProtocol(UDP_PROTOCOL_NAME));
+ EXPECT_FALSE(stun_port->SupportsProtocol(TCP_PROTOCOL_NAME));
+
+ auto tcp_port = CreateTcpPort(kLocalAddr1);
+ EXPECT_TRUE(tcp_port->SupportsProtocol(TCP_PROTOCOL_NAME));
+ EXPECT_TRUE(tcp_port->SupportsProtocol(SSLTCP_PROTOCOL_NAME));
+ EXPECT_FALSE(tcp_port->SupportsProtocol(UDP_PROTOCOL_NAME));
+
+ auto turn_port =
+ CreateTurnPort(kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP);
+ EXPECT_TRUE(turn_port->SupportsProtocol(UDP_PROTOCOL_NAME));
+ EXPECT_FALSE(turn_port->SupportsProtocol(TCP_PROTOCOL_NAME));
+}
+
+// Test that SetIceParameters updates the component, ufrag and password
+// on both the port itself and its candidates.
+TEST_F(PortTest, TestSetIceParameters) {
+ auto port = CreateTestPort(kLocalAddr1, "ufrag1", "password1");
+ port->SetIceTiebreaker(kTiebreakerDefault);
+ port->PrepareAddress();
+ EXPECT_EQ(1UL, port->Candidates().size());
+ port->SetIceParameters(1, "ufrag2", "password2");
+ EXPECT_EQ(1, port->component());
+ EXPECT_EQ("ufrag2", port->username_fragment());
+ EXPECT_EQ("password2", port->password());
+ const Candidate& candidate = port->Candidates()[0];
+ EXPECT_EQ(1, candidate.component());
+ EXPECT_EQ("ufrag2", candidate.username());
+ EXPECT_EQ("password2", candidate.password());
+}
+
+TEST_F(PortTest, TestAddConnectionWithSameAddress) {
+ auto port = CreateTestPort(kLocalAddr1, "ufrag1", "password1");
+ port->SetIceTiebreaker(kTiebreakerDefault);
+ port->PrepareAddress();
+ EXPECT_EQ(1u, port->Candidates().size());
+ rtc::SocketAddress address("1.1.1.1", 5000);
+ cricket::Candidate candidate(1, "udp", address, 0, "", "", "relay", 0, "");
+ cricket::Connection* conn1 =
+ port->CreateConnection(candidate, Port::ORIGIN_MESSAGE);
+ cricket::Connection* conn_in_use = port->GetConnection(address);
+ EXPECT_EQ(conn1, conn_in_use);
+ EXPECT_EQ(0u, conn_in_use->remote_candidate().generation());
+
+ // Creating with a candidate with the same address again will get us a
+ // different connection with the new candidate.
+ candidate.set_generation(2);
+ cricket::Connection* conn2 =
+ port->CreateConnection(candidate, Port::ORIGIN_MESSAGE);
+ EXPECT_NE(conn1, conn2);
+ conn_in_use = port->GetConnection(address);
+ EXPECT_EQ(conn2, conn_in_use);
+ EXPECT_EQ(2u, conn_in_use->remote_candidate().generation());
+
+ // Make sure the new connection was not deleted.
+ rtc::Thread::Current()->ProcessMessages(300);
+ EXPECT_TRUE(port->GetConnection(address) != nullptr);
+}
+
+// TODO(webrtc:11463) : Move Connection tests into separate unit test
+// splitting out shared test code as needed.
+
+class ConnectionTest : public PortTest {
+ public:
+ ConnectionTest() {
+ lport_ = CreateTestPort(kLocalAddr1, "lfrag", "lpass");
+ rport_ = CreateTestPort(kLocalAddr2, "rfrag", "rpass");
+ lport_->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ lport_->SetIceTiebreaker(kTiebreaker1);
+ rport_->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ rport_->SetIceTiebreaker(kTiebreaker2);
+
+ lport_->PrepareAddress();
+ rport_->PrepareAddress();
+ }
+
+ rtc::ScopedFakeClock clock_;
+ int num_state_changes_ = 0;
+
+ Connection* CreateConnection(IceRole role) {
+ Connection* conn;
+ if (role == cricket::ICEROLE_CONTROLLING) {
+ conn = lport_->CreateConnection(rport_->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ } else {
+ conn = rport_->CreateConnection(lport_->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ }
+ conn->SignalStateChange.connect(this,
+ &ConnectionTest::OnConnectionStateChange);
+ return conn;
+ }
+
+ void SendPingAndCaptureReply(Connection* lconn,
+ Connection* rconn,
+ int64_t ms,
+ rtc::BufferT<uint8_t>* reply) {
+ TestPort* lport =
+ lconn->PortForTest() == lport_.get() ? lport_.get() : rport_.get();
+ TestPort* rport =
+ rconn->PortForTest() == rport_.get() ? rport_.get() : lport_.get();
+ lconn->Ping(rtc::TimeMillis());
+ ASSERT_TRUE_WAIT(lport->last_stun_msg(), kDefaultTimeout);
+ ASSERT_TRUE(lport->last_stun_buf());
+ rconn->OnReadPacket(lport->last_stun_buf()->data<char>(),
+ lport->last_stun_buf()->size(),
+ /* packet_time_us */ -1);
+ clock_.AdvanceTime(webrtc::TimeDelta::Millis(ms));
+ ASSERT_TRUE_WAIT(rport->last_stun_msg(), kDefaultTimeout);
+ ASSERT_TRUE(rport->last_stun_buf());
+ *reply = std::move(*rport->last_stun_buf());
+ }
+
+ void SendPingAndReceiveResponse(Connection* lconn,
+ Connection* rconn,
+ int64_t ms) {
+ rtc::BufferT<uint8_t> reply;
+ SendPingAndCaptureReply(lconn, rconn, ms, &reply);
+ lconn->OnReadPacket(reply.data<char>(), reply.size(),
+ /* packet_time_us */ -1);
+ }
+
+ void OnConnectionStateChange(Connection* connection) { num_state_changes_++; }
+
+ private:
+ std::unique_ptr<TestPort> lport_;
+ std::unique_ptr<TestPort> rport_;
+};
+
+TEST_F(ConnectionTest, ConnectionForgetLearnedState) {
+ Connection* lconn = CreateConnection(ICEROLE_CONTROLLING);
+ Connection* rconn = CreateConnection(ICEROLE_CONTROLLED);
+
+ EXPECT_FALSE(lconn->writable());
+ EXPECT_FALSE(lconn->receiving());
+ EXPECT_TRUE(std::isnan(lconn->GetRttEstimate().GetAverage()));
+ EXPECT_EQ(lconn->GetRttEstimate().GetVariance(),
+ std::numeric_limits<double>::infinity());
+
+ SendPingAndReceiveResponse(lconn, rconn, 10);
+
+ EXPECT_TRUE(lconn->writable());
+ EXPECT_TRUE(lconn->receiving());
+ EXPECT_EQ(lconn->GetRttEstimate().GetAverage(), 10);
+ EXPECT_EQ(lconn->GetRttEstimate().GetVariance(),
+ std::numeric_limits<double>::infinity());
+
+ SendPingAndReceiveResponse(lconn, rconn, 11);
+
+ EXPECT_TRUE(lconn->writable());
+ EXPECT_TRUE(lconn->receiving());
+ EXPECT_NEAR(lconn->GetRttEstimate().GetAverage(), 10, 0.5);
+ EXPECT_LT(lconn->GetRttEstimate().GetVariance(),
+ std::numeric_limits<double>::infinity());
+
+ lconn->ForgetLearnedState();
+
+ EXPECT_FALSE(lconn->writable());
+ EXPECT_FALSE(lconn->receiving());
+ EXPECT_TRUE(std::isnan(lconn->GetRttEstimate().GetAverage()));
+ EXPECT_EQ(lconn->GetRttEstimate().GetVariance(),
+ std::numeric_limits<double>::infinity());
+}
+
+TEST_F(ConnectionTest, ConnectionForgetLearnedStateDiscardsPendingPings) {
+ Connection* lconn = CreateConnection(ICEROLE_CONTROLLING);
+ Connection* rconn = CreateConnection(ICEROLE_CONTROLLED);
+
+ SendPingAndReceiveResponse(lconn, rconn, 10);
+
+ EXPECT_TRUE(lconn->writable());
+ EXPECT_TRUE(lconn->receiving());
+
+ rtc::BufferT<uint8_t> reply;
+ SendPingAndCaptureReply(lconn, rconn, 10, &reply);
+
+ lconn->ForgetLearnedState();
+
+ EXPECT_FALSE(lconn->writable());
+ EXPECT_FALSE(lconn->receiving());
+
+ lconn->OnReadPacket(reply.data<char>(), reply.size(),
+ /* packet_time_us */ -1);
+
+ // That reply was discarded due to the ForgetLearnedState() while it was
+ // outstanding.
+ EXPECT_FALSE(lconn->writable());
+ EXPECT_FALSE(lconn->receiving());
+
+ // But sending a new ping and getting a reply works.
+ SendPingAndReceiveResponse(lconn, rconn, 11);
+ EXPECT_TRUE(lconn->writable());
+ EXPECT_TRUE(lconn->receiving());
+}
+
+TEST_F(ConnectionTest, ConnectionForgetLearnedStateDoesNotTriggerStateChange) {
+ Connection* lconn = CreateConnection(ICEROLE_CONTROLLING);
+ Connection* rconn = CreateConnection(ICEROLE_CONTROLLED);
+
+ EXPECT_EQ(num_state_changes_, 0);
+ SendPingAndReceiveResponse(lconn, rconn, 10);
+
+ EXPECT_TRUE(lconn->writable());
+ EXPECT_TRUE(lconn->receiving());
+ EXPECT_EQ(num_state_changes_, 2);
+
+ lconn->ForgetLearnedState();
+
+ EXPECT_FALSE(lconn->writable());
+ EXPECT_FALSE(lconn->receiving());
+ EXPECT_EQ(num_state_changes_, 2);
+}
+
+} // namespace cricket
diff --git a/third_party/libwebrtc/p2p/base/pseudo_tcp.cc b/third_party/libwebrtc/p2p/base/pseudo_tcp.cc
new file mode 100644
index 0000000000..eff86e849e
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/pseudo_tcp.cc
@@ -0,0 +1,1432 @@
+/*
+ * 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/pseudo_tcp.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <algorithm>
+#include <cstdint>
+#include <memory>
+#include <set>
+
+#include "rtc_base/byte_buffer.h"
+#include "rtc_base/byte_order.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/numerics/safe_minmax.h"
+#include "rtc_base/socket.h"
+#include "rtc_base/time_utils.h"
+
+// The following logging is for detailed (packet-level) analysis only.
+#define _DBG_NONE 0
+#define _DBG_NORMAL 1
+#define _DBG_VERBOSE 2
+#define _DEBUGMSG _DBG_NONE
+
+namespace cricket {
+
+//////////////////////////////////////////////////////////////////////
+// Network Constants
+//////////////////////////////////////////////////////////////////////
+
+// Standard MTUs
+const uint16_t PACKET_MAXIMUMS[] = {
+ 65535, // Theoretical maximum, Hyperchannel
+ 32000, // Nothing
+ 17914, // 16Mb IBM Token Ring
+ 8166, // IEEE 802.4
+ // 4464, // IEEE 802.5 (4Mb max)
+ 4352, // FDDI
+ // 2048, // Wideband Network
+ 2002, // IEEE 802.5 (4Mb recommended)
+ // 1536, // Expermental Ethernet Networks
+ // 1500, // Ethernet, Point-to-Point (default)
+ 1492, // IEEE 802.3
+ 1006, // SLIP, ARPANET
+ // 576, // X.25 Networks
+ // 544, // DEC IP Portal
+ // 512, // NETBIOS
+ 508, // IEEE 802/Source-Rt Bridge, ARCNET
+ 296, // Point-to-Point (low delay)
+ // 68, // Official minimum
+ 0, // End of list marker
+};
+
+const uint32_t MAX_PACKET = 65535;
+// Note: we removed lowest level because packet overhead was larger!
+const uint32_t MIN_PACKET = 296;
+
+const uint32_t IP_HEADER_SIZE = 20; // (+ up to 40 bytes of options?)
+const uint32_t UDP_HEADER_SIZE = 8;
+// TODO(?): Make JINGLE_HEADER_SIZE transparent to this code?
+const uint32_t JINGLE_HEADER_SIZE = 64; // when relay framing is in use
+
+// Default size for receive and send buffer.
+const uint32_t DEFAULT_RCV_BUF_SIZE = 60 * 1024;
+const uint32_t DEFAULT_SND_BUF_SIZE = 90 * 1024;
+
+//////////////////////////////////////////////////////////////////////
+// Global Constants and Functions
+//////////////////////////////////////////////////////////////////////
+//
+// 0 1 2 3
+// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// 0 | Conversation Number |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// 4 | Sequence Number |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// 8 | Acknowledgment Number |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | | |U|A|P|R|S|F| |
+// 12 | Control | |R|C|S|S|Y|I| Window |
+// | | |G|K|H|T|N|N| |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// 16 | Timestamp sending |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// 20 | Timestamp receiving |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// 24 | data |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//
+//////////////////////////////////////////////////////////////////////
+
+#define PSEUDO_KEEPALIVE 0
+
+const uint32_t HEADER_SIZE = 24;
+const uint32_t PACKET_OVERHEAD =
+ HEADER_SIZE + UDP_HEADER_SIZE + IP_HEADER_SIZE + JINGLE_HEADER_SIZE;
+
+const uint32_t MIN_RTO =
+ 250; // 250 ms (RFC1122, Sec 4.2.3.1 "fractions of a second")
+const uint32_t DEF_RTO = 3000; // 3 seconds (RFC1122, Sec 4.2.3.1)
+const uint32_t MAX_RTO = 60000; // 60 seconds
+const uint32_t DEF_ACK_DELAY = 100; // 100 milliseconds
+
+const uint8_t FLAG_CTL = 0x02;
+const uint8_t FLAG_RST = 0x04;
+
+const uint8_t CTL_CONNECT = 0;
+
+// TCP options.
+const uint8_t TCP_OPT_EOL = 0; // End of list.
+const uint8_t TCP_OPT_NOOP = 1; // No-op.
+const uint8_t TCP_OPT_MSS = 2; // Maximum segment size.
+const uint8_t TCP_OPT_WND_SCALE = 3; // Window scale factor.
+
+const long DEFAULT_TIMEOUT =
+ 4000; // If there are no pending clocks, wake up every 4 seconds
+const long CLOSED_TIMEOUT =
+ 60 * 1000; // If the connection is closed, once per minute
+
+#if PSEUDO_KEEPALIVE
+// !?! Rethink these times
+const uint32_t IDLE_PING =
+ 20 *
+ 1000; // 20 seconds (note: WinXP SP2 firewall udp timeout is 90 seconds)
+const uint32_t IDLE_TIMEOUT = 90 * 1000; // 90 seconds;
+#endif // PSEUDO_KEEPALIVE
+
+//////////////////////////////////////////////////////////////////////
+// Helper Functions
+//////////////////////////////////////////////////////////////////////
+
+inline void long_to_bytes(uint32_t val, void* buf) {
+ *static_cast<uint32_t*>(buf) = rtc::HostToNetwork32(val);
+}
+
+inline void short_to_bytes(uint16_t val, void* buf) {
+ *static_cast<uint16_t*>(buf) = rtc::HostToNetwork16(val);
+}
+
+inline uint32_t bytes_to_long(const void* buf) {
+ return rtc::NetworkToHost32(*static_cast<const uint32_t*>(buf));
+}
+
+inline uint16_t bytes_to_short(const void* buf) {
+ return rtc::NetworkToHost16(*static_cast<const uint16_t*>(buf));
+}
+
+//////////////////////////////////////////////////////////////////////
+// Debugging Statistics
+//////////////////////////////////////////////////////////////////////
+
+#if 0 // Not used yet
+
+enum Stat {
+ S_SENT_PACKET, // All packet sends
+ S_RESENT_PACKET, // All packet sends that are retransmits
+ S_RECV_PACKET, // All packet receives
+ S_RECV_NEW, // All packet receives that are too new
+ S_RECV_OLD, // All packet receives that are too old
+ S_NUM_STATS
+};
+
+const char* const STAT_NAMES[S_NUM_STATS] = {
+ "snt",
+ "snt-r",
+ "rcv"
+ "rcv-n",
+ "rcv-o"
+};
+
+int g_stats[S_NUM_STATS];
+inline void Incr(Stat s) { ++g_stats[s]; }
+void ReportStats() {
+ char buffer[256];
+ size_t len = 0;
+ for (int i = 0; i < S_NUM_STATS; ++i) {
+ len += snprintf(buffer, arraysize(buffer), "%s%s:%d",
+ (i == 0) ? "" : ",", STAT_NAMES[i], g_stats[i]);
+ g_stats[i] = 0;
+ }
+ RTC_LOG(LS_INFO) << "Stats[" << buffer << "]";
+}
+
+#endif
+
+//////////////////////////////////////////////////////////////////////
+// PseudoTcp
+//////////////////////////////////////////////////////////////////////
+
+uint32_t PseudoTcp::Now() {
+#if 0 // Use this to synchronize timers with logging timestamps (easier debug)
+ return static_cast<uint32_t>(rtc::TimeSince(StartTime()));
+#else
+ return rtc::Time32();
+#endif
+}
+
+PseudoTcp::PseudoTcp(IPseudoTcpNotify* notify, uint32_t conv)
+ : m_notify(notify),
+ m_shutdown(SD_NONE),
+ m_error(0),
+ m_rbuf_len(DEFAULT_RCV_BUF_SIZE),
+ m_rbuf(m_rbuf_len),
+ m_sbuf_len(DEFAULT_SND_BUF_SIZE),
+ m_sbuf(m_sbuf_len) {
+ // Sanity check on buffer sizes (needed for OnTcpWriteable notification logic)
+ RTC_DCHECK(m_rbuf_len + MIN_PACKET < m_sbuf_len);
+
+ uint32_t now = Now();
+
+ m_state = TCP_LISTEN;
+ m_conv = conv;
+ m_rcv_wnd = m_rbuf_len;
+ m_rwnd_scale = m_swnd_scale = 0;
+ m_snd_nxt = 0;
+ m_snd_wnd = 1;
+ m_snd_una = m_rcv_nxt = 0;
+ m_bReadEnable = true;
+ m_bWriteEnable = false;
+ m_t_ack = 0;
+
+ m_msslevel = 0;
+ m_largest = 0;
+ RTC_DCHECK(MIN_PACKET > PACKET_OVERHEAD);
+ m_mss = MIN_PACKET - PACKET_OVERHEAD;
+ m_mtu_advise = MAX_PACKET;
+
+ m_rto_base = 0;
+
+ m_cwnd = 2 * m_mss;
+ m_ssthresh = m_rbuf_len;
+ m_lastrecv = m_lastsend = m_lasttraffic = now;
+ m_bOutgoing = false;
+
+ m_dup_acks = 0;
+ m_recover = 0;
+
+ m_ts_recent = m_ts_lastack = 0;
+
+ m_rx_rto = DEF_RTO;
+ m_rx_srtt = m_rx_rttvar = 0;
+
+ m_use_nagling = true;
+ m_ack_delay = DEF_ACK_DELAY;
+ m_support_wnd_scale = true;
+}
+
+PseudoTcp::~PseudoTcp() {}
+
+int PseudoTcp::Connect() {
+ if (m_state != TCP_LISTEN) {
+ m_error = EINVAL;
+ return -1;
+ }
+
+ m_state = TCP_SYN_SENT;
+ RTC_LOG(LS_INFO) << "State: TCP_SYN_SENT";
+
+ queueConnectMessage();
+ attemptSend();
+
+ return 0;
+}
+
+void PseudoTcp::NotifyMTU(uint16_t mtu) {
+ m_mtu_advise = mtu;
+ if (m_state == TCP_ESTABLISHED) {
+ adjustMTU();
+ }
+}
+
+void PseudoTcp::NotifyClock(uint32_t now) {
+ if (m_state == TCP_CLOSED)
+ return;
+
+ // Check if it's time to retransmit a segment
+ if (m_rto_base && (rtc::TimeDiff32(m_rto_base + m_rx_rto, now) <= 0)) {
+ if (m_slist.empty()) {
+ RTC_DCHECK_NOTREACHED();
+ } else {
+// Note: (m_slist.front().xmit == 0)) {
+// retransmit segments
+#if _DEBUGMSG >= _DBG_NORMAL
+ RTC_LOG(LS_INFO) << "timeout retransmit (rto: " << m_rx_rto
+ << ") (rto_base: " << m_rto_base << ") (now: " << now
+ << ") (dup_acks: " << static_cast<unsigned>(m_dup_acks)
+ << ")";
+#endif // _DEBUGMSG
+ if (!transmit(m_slist.begin(), now)) {
+ closedown(ECONNABORTED);
+ return;
+ }
+
+ uint32_t nInFlight = m_snd_nxt - m_snd_una;
+ m_ssthresh = std::max(nInFlight / 2, 2 * m_mss);
+ // RTC_LOG(LS_INFO) << "m_ssthresh: " << m_ssthresh << " nInFlight: " <<
+ // nInFlight << " m_mss: " << m_mss;
+ m_cwnd = m_mss;
+
+ // Back off retransmit timer. Note: the limit is lower when connecting.
+ uint32_t rto_limit = (m_state < TCP_ESTABLISHED) ? DEF_RTO : MAX_RTO;
+ m_rx_rto = std::min(rto_limit, m_rx_rto * 2);
+ m_rto_base = now;
+ }
+ }
+
+ // Check if it's time to probe closed windows
+ if ((m_snd_wnd == 0) && (rtc::TimeDiff32(m_lastsend + m_rx_rto, now) <= 0)) {
+ if (rtc::TimeDiff32(now, m_lastrecv) >= 15000) {
+ closedown(ECONNABORTED);
+ return;
+ }
+
+ // probe the window
+ packet(m_snd_nxt - 1, 0, 0, 0);
+ m_lastsend = now;
+
+ // back off retransmit timer
+ m_rx_rto = std::min(MAX_RTO, m_rx_rto * 2);
+ }
+
+ // Check if it's time to send delayed acks
+ if (m_t_ack && (rtc::TimeDiff32(m_t_ack + m_ack_delay, now) <= 0)) {
+ packet(m_snd_nxt, 0, 0, 0);
+ }
+
+#if PSEUDO_KEEPALIVE
+ // Check for idle timeout
+ if ((m_state == TCP_ESTABLISHED) &&
+ (TimeDiff32(m_lastrecv + IDLE_TIMEOUT, now) <= 0)) {
+ closedown(ECONNABORTED);
+ return;
+ }
+
+ // Check for ping timeout (to keep udp mapping open)
+ if ((m_state == TCP_ESTABLISHED) &&
+ (TimeDiff32(m_lasttraffic + (m_bOutgoing ? IDLE_PING * 3 / 2 : IDLE_PING),
+ now) <= 0)) {
+ packet(m_snd_nxt, 0, 0, 0);
+ }
+#endif // PSEUDO_KEEPALIVE
+}
+
+bool PseudoTcp::NotifyPacket(const char* buffer, size_t len) {
+ if (len > MAX_PACKET) {
+ RTC_LOG_F(LS_WARNING) << "packet too large";
+ return false;
+ }
+ return parse(reinterpret_cast<const uint8_t*>(buffer), uint32_t(len));
+}
+
+bool PseudoTcp::GetNextClock(uint32_t now, long& timeout) {
+ return clock_check(now, timeout);
+}
+
+void PseudoTcp::GetOption(Option opt, int* value) {
+ if (opt == OPT_NODELAY) {
+ *value = m_use_nagling ? 0 : 1;
+ } else if (opt == OPT_ACKDELAY) {
+ *value = m_ack_delay;
+ } else if (opt == OPT_SNDBUF) {
+ *value = m_sbuf_len;
+ } else if (opt == OPT_RCVBUF) {
+ *value = m_rbuf_len;
+ } else {
+ RTC_DCHECK_NOTREACHED();
+ }
+}
+void PseudoTcp::SetOption(Option opt, int value) {
+ if (opt == OPT_NODELAY) {
+ m_use_nagling = value == 0;
+ } else if (opt == OPT_ACKDELAY) {
+ m_ack_delay = value;
+ } else if (opt == OPT_SNDBUF) {
+ RTC_DCHECK(m_state == TCP_LISTEN);
+ resizeSendBuffer(value);
+ } else if (opt == OPT_RCVBUF) {
+ RTC_DCHECK(m_state == TCP_LISTEN);
+ resizeReceiveBuffer(value);
+ } else {
+ RTC_DCHECK_NOTREACHED();
+ }
+}
+
+uint32_t PseudoTcp::GetCongestionWindow() const {
+ return m_cwnd;
+}
+
+uint32_t PseudoTcp::GetBytesInFlight() const {
+ return m_snd_nxt - m_snd_una;
+}
+
+uint32_t PseudoTcp::GetBytesBufferedNotSent() const {
+ return static_cast<uint32_t>(m_snd_una + m_sbuf.GetBuffered() - m_snd_nxt);
+}
+
+uint32_t PseudoTcp::GetRoundTripTimeEstimateMs() const {
+ return m_rx_srtt;
+}
+
+//
+// IPStream Implementation
+//
+
+int PseudoTcp::Recv(char* buffer, size_t len) {
+ if (m_state != TCP_ESTABLISHED) {
+ m_error = ENOTCONN;
+ return SOCKET_ERROR;
+ }
+
+ size_t read = 0;
+ if (!m_rbuf.Read(buffer, len, &read)) {
+ m_bReadEnable = true;
+ m_error = EWOULDBLOCK;
+ return SOCKET_ERROR;
+ }
+
+ size_t available_space = 0;
+ m_rbuf.GetWriteRemaining(&available_space);
+
+ if (uint32_t(available_space) - m_rcv_wnd >=
+ std::min<uint32_t>(m_rbuf_len / 2, m_mss)) {
+ // TODO(jbeda): !?! Not sure about this was closed business
+ bool bWasClosed = (m_rcv_wnd == 0);
+ m_rcv_wnd = static_cast<uint32_t>(available_space);
+
+ if (bWasClosed) {
+ attemptSend(sfImmediateAck);
+ }
+ }
+
+ return static_cast<int>(read);
+}
+
+int PseudoTcp::Send(const char* buffer, size_t len) {
+ if (m_state != TCP_ESTABLISHED) {
+ m_error = ENOTCONN;
+ return SOCKET_ERROR;
+ }
+
+ size_t available_space = 0;
+ m_sbuf.GetWriteRemaining(&available_space);
+
+ if (!available_space) {
+ m_bWriteEnable = true;
+ m_error = EWOULDBLOCK;
+ return SOCKET_ERROR;
+ }
+
+ int written = queue(buffer, uint32_t(len), false);
+ attemptSend();
+ return written;
+}
+
+void PseudoTcp::Close(bool force) {
+ RTC_LOG_F(LS_VERBOSE) << "(" << (force ? "true" : "false") << ")";
+ m_shutdown = force ? SD_FORCEFUL : SD_GRACEFUL;
+}
+
+int PseudoTcp::GetError() {
+ return m_error;
+}
+
+//
+// Internal Implementation
+//
+
+uint32_t PseudoTcp::queue(const char* data, uint32_t len, bool bCtrl) {
+ size_t available_space = 0;
+ m_sbuf.GetWriteRemaining(&available_space);
+
+ if (len > static_cast<uint32_t>(available_space)) {
+ RTC_DCHECK(!bCtrl);
+ len = static_cast<uint32_t>(available_space);
+ }
+
+ // We can concatenate data if the last segment is the same type
+ // (control v. regular data), and has not been transmitted yet
+ if (!m_slist.empty() && (m_slist.back().bCtrl == bCtrl) &&
+ (m_slist.back().xmit == 0)) {
+ m_slist.back().len += len;
+ } else {
+ SSegment sseg(static_cast<uint32_t>(m_snd_una + m_sbuf.GetBuffered()), len,
+ bCtrl);
+ m_slist.push_back(sseg);
+ }
+
+ size_t written = 0;
+ m_sbuf.Write(data, len, &written);
+ return static_cast<uint32_t>(written);
+}
+
+IPseudoTcpNotify::WriteResult PseudoTcp::packet(uint32_t seq,
+ uint8_t flags,
+ uint32_t offset,
+ uint32_t len) {
+ RTC_DCHECK(HEADER_SIZE + len <= MAX_PACKET);
+
+ uint32_t now = Now();
+
+ std::unique_ptr<uint8_t[]> buffer(new uint8_t[MAX_PACKET]);
+ long_to_bytes(m_conv, buffer.get());
+ long_to_bytes(seq, buffer.get() + 4);
+ long_to_bytes(m_rcv_nxt, buffer.get() + 8);
+ buffer[12] = 0;
+ buffer[13] = flags;
+ short_to_bytes(static_cast<uint16_t>(m_rcv_wnd >> m_rwnd_scale),
+ buffer.get() + 14);
+
+ // Timestamp computations
+ long_to_bytes(now, buffer.get() + 16);
+ long_to_bytes(m_ts_recent, buffer.get() + 20);
+ m_ts_lastack = m_rcv_nxt;
+
+ if (len) {
+ size_t bytes_read = 0;
+ bool result =
+ m_sbuf.ReadOffset(buffer.get() + HEADER_SIZE, len, offset, &bytes_read);
+ RTC_DCHECK(result);
+ RTC_DCHECK(static_cast<uint32_t>(bytes_read) == len);
+ }
+
+#if _DEBUGMSG >= _DBG_VERBOSE
+ RTC_LOG(LS_INFO) << "<-- <CONV=" << m_conv
+ << "><FLG=" << static_cast<unsigned>(flags)
+ << "><SEQ=" << seq << ":" << seq + len
+ << "><ACK=" << m_rcv_nxt << "><WND=" << m_rcv_wnd
+ << "><TS=" << (now % 10000)
+ << "><TSR=" << (m_ts_recent % 10000) << "><LEN=" << len
+ << ">";
+#endif // _DEBUGMSG
+
+ IPseudoTcpNotify::WriteResult wres = m_notify->TcpWritePacket(
+ this, reinterpret_cast<char*>(buffer.get()), len + HEADER_SIZE);
+ // Note: When len is 0, this is an ACK packet. We don't read the return value
+ // for those, and thus we won't retry. So go ahead and treat the packet as a
+ // success (basically simulate as if it were dropped), which will prevent our
+ // timers from being messed up.
+ if ((wres != IPseudoTcpNotify::WR_SUCCESS) && (0 != len))
+ return wres;
+
+ m_t_ack = 0;
+ if (len > 0) {
+ m_lastsend = now;
+ }
+ m_lasttraffic = now;
+ m_bOutgoing = true;
+
+ return IPseudoTcpNotify::WR_SUCCESS;
+}
+
+bool PseudoTcp::parse(const uint8_t* buffer, uint32_t size) {
+ if (size < HEADER_SIZE)
+ return false;
+
+ Segment seg;
+ seg.conv = bytes_to_long(buffer);
+ seg.seq = bytes_to_long(buffer + 4);
+ seg.ack = bytes_to_long(buffer + 8);
+ seg.flags = buffer[13];
+ seg.wnd = bytes_to_short(buffer + 14);
+
+ seg.tsval = bytes_to_long(buffer + 16);
+ seg.tsecr = bytes_to_long(buffer + 20);
+
+ seg.data = reinterpret_cast<const char*>(buffer) + HEADER_SIZE;
+ seg.len = size - HEADER_SIZE;
+
+#if _DEBUGMSG >= _DBG_VERBOSE
+ RTC_LOG(LS_INFO) << "--> <CONV=" << seg.conv
+ << "><FLG=" << static_cast<unsigned>(seg.flags)
+ << "><SEQ=" << seg.seq << ":" << seg.seq + seg.len
+ << "><ACK=" << seg.ack << "><WND=" << seg.wnd
+ << "><TS=" << (seg.tsval % 10000)
+ << "><TSR=" << (seg.tsecr % 10000) << "><LEN=" << seg.len
+ << ">";
+#endif // _DEBUGMSG
+
+ return process(seg);
+}
+
+bool PseudoTcp::clock_check(uint32_t now, long& nTimeout) {
+ if (m_shutdown == SD_FORCEFUL)
+ return false;
+
+ if ((m_shutdown == SD_GRACEFUL) &&
+ ((m_state != TCP_ESTABLISHED) ||
+ ((m_sbuf.GetBuffered() == 0) && (m_t_ack == 0)))) {
+ return false;
+ }
+
+ if (m_state == TCP_CLOSED) {
+ nTimeout = CLOSED_TIMEOUT;
+ return true;
+ }
+
+ nTimeout = DEFAULT_TIMEOUT;
+
+ if (m_t_ack) {
+ nTimeout = std::min<int32_t>(nTimeout,
+ rtc::TimeDiff32(m_t_ack + m_ack_delay, now));
+ }
+ if (m_rto_base) {
+ nTimeout = std::min<int32_t>(nTimeout,
+ rtc::TimeDiff32(m_rto_base + m_rx_rto, now));
+ }
+ if (m_snd_wnd == 0) {
+ nTimeout = std::min<int32_t>(nTimeout,
+ rtc::TimeDiff32(m_lastsend + m_rx_rto, now));
+ }
+#if PSEUDO_KEEPALIVE
+ if (m_state == TCP_ESTABLISHED) {
+ nTimeout = std::min<int32_t>(
+ nTimeout,
+ rtc::TimeDiff32(
+ m_lasttraffic + (m_bOutgoing ? IDLE_PING * 3 / 2 : IDLE_PING),
+ now));
+ }
+#endif // PSEUDO_KEEPALIVE
+ return true;
+}
+
+bool PseudoTcp::process(Segment& seg) {
+ // If this is the wrong conversation, send a reset!?! (with the correct
+ // conversation?)
+ if (seg.conv != m_conv) {
+ // if ((seg.flags & FLAG_RST) == 0) {
+ // packet(tcb, seg.ack, 0, FLAG_RST, 0, 0);
+ //}
+ RTC_LOG_F(LS_ERROR) << "wrong conversation";
+ return false;
+ }
+
+ uint32_t now = Now();
+ m_lasttraffic = m_lastrecv = now;
+ m_bOutgoing = false;
+
+ if (m_state == TCP_CLOSED) {
+ // !?! send reset?
+ RTC_LOG_F(LS_ERROR) << "closed";
+ return false;
+ }
+
+ // Check if this is a reset segment
+ if (seg.flags & FLAG_RST) {
+ closedown(ECONNRESET);
+ return false;
+ }
+
+ // Check for control data
+ bool bConnect = false;
+ if (seg.flags & FLAG_CTL) {
+ if (seg.len == 0) {
+ RTC_LOG_F(LS_ERROR) << "Missing control code";
+ return false;
+ } else if (seg.data[0] == CTL_CONNECT) {
+ bConnect = true;
+
+ // TCP options are in the remainder of the payload after CTL_CONNECT.
+ parseOptions(&seg.data[1], seg.len - 1);
+
+ if (m_state == TCP_LISTEN) {
+ m_state = TCP_SYN_RECEIVED;
+ RTC_LOG(LS_INFO) << "State: TCP_SYN_RECEIVED";
+ // m_notify->associate(addr);
+ queueConnectMessage();
+ } else if (m_state == TCP_SYN_SENT) {
+ m_state = TCP_ESTABLISHED;
+ RTC_LOG(LS_INFO) << "State: TCP_ESTABLISHED";
+ adjustMTU();
+ if (m_notify) {
+ m_notify->OnTcpOpen(this);
+ }
+ // notify(evOpen);
+ }
+ } else {
+ RTC_LOG_F(LS_WARNING) << "Unknown control code: " << seg.data[0];
+ return false;
+ }
+ }
+
+ // Update timestamp
+ if ((seg.seq <= m_ts_lastack) && (m_ts_lastack < seg.seq + seg.len)) {
+ m_ts_recent = seg.tsval;
+ }
+
+ // Check if this is a valuable ack
+ if ((seg.ack > m_snd_una) && (seg.ack <= m_snd_nxt)) {
+ // Calculate round-trip time
+ if (seg.tsecr) {
+ int32_t rtt = rtc::TimeDiff32(now, seg.tsecr);
+ if (rtt >= 0) {
+ if (m_rx_srtt == 0) {
+ m_rx_srtt = rtt;
+ m_rx_rttvar = rtt / 2;
+ } else {
+ uint32_t unsigned_rtt = static_cast<uint32_t>(rtt);
+ uint32_t abs_err = unsigned_rtt > m_rx_srtt
+ ? unsigned_rtt - m_rx_srtt
+ : m_rx_srtt - unsigned_rtt;
+ m_rx_rttvar = (3 * m_rx_rttvar + abs_err) / 4;
+ m_rx_srtt = (7 * m_rx_srtt + rtt) / 8;
+ }
+ m_rx_rto = rtc::SafeClamp(m_rx_srtt + rtc::SafeMax(1, 4 * m_rx_rttvar),
+ MIN_RTO, MAX_RTO);
+#if _DEBUGMSG >= _DBG_VERBOSE
+ RTC_LOG(LS_INFO) << "rtt: " << rtt << " srtt: " << m_rx_srtt
+ << " rto: " << m_rx_rto;
+#endif // _DEBUGMSG
+ } else {
+ RTC_LOG(LS_WARNING) << "rtt < 0";
+ }
+ }
+
+ m_snd_wnd = static_cast<uint32_t>(seg.wnd) << m_swnd_scale;
+
+ uint32_t nAcked = seg.ack - m_snd_una;
+ m_snd_una = seg.ack;
+
+ m_rto_base = (m_snd_una == m_snd_nxt) ? 0 : now;
+
+ m_sbuf.ConsumeReadData(nAcked);
+
+ for (uint32_t nFree = nAcked; nFree > 0;) {
+ RTC_DCHECK(!m_slist.empty());
+ if (nFree < m_slist.front().len) {
+ m_slist.front().len -= nFree;
+ nFree = 0;
+ } else {
+ if (m_slist.front().len > m_largest) {
+ m_largest = m_slist.front().len;
+ }
+ nFree -= m_slist.front().len;
+ m_slist.pop_front();
+ }
+ }
+
+ if (m_dup_acks >= 3) {
+ if (m_snd_una >= m_recover) { // NewReno
+ uint32_t nInFlight = m_snd_nxt - m_snd_una;
+ m_cwnd = std::min(m_ssthresh, nInFlight + m_mss); // (Fast Retransmit)
+#if _DEBUGMSG >= _DBG_NORMAL
+ RTC_LOG(LS_INFO) << "exit recovery";
+#endif // _DEBUGMSG
+ m_dup_acks = 0;
+ } else {
+#if _DEBUGMSG >= _DBG_NORMAL
+ RTC_LOG(LS_INFO) << "recovery retransmit";
+#endif // _DEBUGMSG
+ if (!transmit(m_slist.begin(), now)) {
+ closedown(ECONNABORTED);
+ return false;
+ }
+ m_cwnd += m_mss - std::min(nAcked, m_cwnd);
+ }
+ } else {
+ m_dup_acks = 0;
+ // Slow start, congestion avoidance
+ if (m_cwnd < m_ssthresh) {
+ m_cwnd += m_mss;
+ } else {
+ m_cwnd += std::max<uint32_t>(1, m_mss * m_mss / m_cwnd);
+ }
+ }
+ } else if (seg.ack == m_snd_una) {
+ // !?! Note, tcp says don't do this... but otherwise how does a closed
+ // window become open?
+ m_snd_wnd = static_cast<uint32_t>(seg.wnd) << m_swnd_scale;
+
+ // Check duplicate acks
+ if (seg.len > 0) {
+ // it's a dup ack, but with a data payload, so don't modify m_dup_acks
+ } else if (m_snd_una != m_snd_nxt) {
+ m_dup_acks += 1;
+ if (m_dup_acks == 3) { // (Fast Retransmit)
+#if _DEBUGMSG >= _DBG_NORMAL
+ RTC_LOG(LS_INFO) << "enter recovery";
+ RTC_LOG(LS_INFO) << "recovery retransmit";
+#endif // _DEBUGMSG
+ if (!transmit(m_slist.begin(), now)) {
+ closedown(ECONNABORTED);
+ return false;
+ }
+ m_recover = m_snd_nxt;
+ uint32_t nInFlight = m_snd_nxt - m_snd_una;
+ m_ssthresh = std::max(nInFlight / 2, 2 * m_mss);
+ // RTC_LOG(LS_INFO) << "m_ssthresh: " << m_ssthresh << " nInFlight: "
+ // << nInFlight << " m_mss: " << m_mss;
+ m_cwnd = m_ssthresh + 3 * m_mss;
+ } else if (m_dup_acks > 3) {
+ m_cwnd += m_mss;
+ }
+ } else {
+ m_dup_acks = 0;
+ }
+ }
+
+ // !?! A bit hacky
+ if ((m_state == TCP_SYN_RECEIVED) && !bConnect) {
+ m_state = TCP_ESTABLISHED;
+ RTC_LOG(LS_INFO) << "State: TCP_ESTABLISHED";
+ adjustMTU();
+ if (m_notify) {
+ m_notify->OnTcpOpen(this);
+ }
+ // notify(evOpen);
+ }
+
+ // If we make room in the send queue, notify the user
+ // The goal it to make sure we always have at least enough data to fill the
+ // window. We'd like to notify the app when we are halfway to that point.
+ const uint32_t kIdealRefillSize = (m_sbuf_len + m_rbuf_len) / 2;
+ if (m_bWriteEnable &&
+ static_cast<uint32_t>(m_sbuf.GetBuffered()) < kIdealRefillSize) {
+ m_bWriteEnable = false;
+ if (m_notify) {
+ m_notify->OnTcpWriteable(this);
+ }
+ // notify(evWrite);
+ }
+
+ // Conditions were acks must be sent:
+ // 1) Segment is too old (they missed an ACK) (immediately)
+ // 2) Segment is too new (we missed a segment) (immediately)
+ // 3) Segment has data (so we need to ACK!) (delayed)
+ // ... so the only time we don't need to ACK, is an empty segment that points
+ // to rcv_nxt!
+
+ SendFlags sflags = sfNone;
+ if (seg.seq != m_rcv_nxt) {
+ sflags = sfImmediateAck; // (Fast Recovery)
+ } else if (seg.len != 0) {
+ if (m_ack_delay == 0) {
+ sflags = sfImmediateAck;
+ } else {
+ sflags = sfDelayedAck;
+ }
+ }
+#if _DEBUGMSG >= _DBG_NORMAL
+ if (sflags == sfImmediateAck) {
+ if (seg.seq > m_rcv_nxt) {
+ RTC_LOG_F(LS_INFO) << "too new";
+ } else if (seg.seq + seg.len <= m_rcv_nxt) {
+ RTC_LOG_F(LS_INFO) << "too old";
+ }
+ }
+#endif // _DEBUGMSG
+
+ // Adjust the incoming segment to fit our receive buffer
+ if (seg.seq < m_rcv_nxt) {
+ uint32_t nAdjust = m_rcv_nxt - seg.seq;
+ if (nAdjust < seg.len) {
+ seg.seq += nAdjust;
+ seg.data += nAdjust;
+ seg.len -= nAdjust;
+ } else {
+ seg.len = 0;
+ }
+ }
+
+ size_t available_space = 0;
+ m_rbuf.GetWriteRemaining(&available_space);
+
+ if ((seg.seq + seg.len - m_rcv_nxt) >
+ static_cast<uint32_t>(available_space)) {
+ uint32_t nAdjust =
+ seg.seq + seg.len - m_rcv_nxt - static_cast<uint32_t>(available_space);
+ if (nAdjust < seg.len) {
+ seg.len -= nAdjust;
+ } else {
+ seg.len = 0;
+ }
+ }
+
+ bool bIgnoreData = (seg.flags & FLAG_CTL) || (m_shutdown != SD_NONE);
+ bool bNewData = false;
+
+ if (seg.len > 0) {
+ bool bRecover = false;
+ if (bIgnoreData) {
+ if (seg.seq == m_rcv_nxt) {
+ m_rcv_nxt += seg.len;
+ // If we received a data segment out of order relative to a control
+ // segment, then we wrote it into the receive buffer at an offset (see
+ // "WriteOffset") below. So we need to advance the position in the
+ // buffer to avoid corrupting data. See bugs.webrtc.org/9208
+ //
+ // We advance the position in the buffer by N bytes by acting like we
+ // wrote N bytes and then immediately read them. We can only do this if
+ // there's not already data ready to read, but this should always be
+ // true in the problematic scenario, since control frames are always
+ // sent first in the stream.
+ if (m_rbuf.GetBuffered() == 0) {
+ m_rbuf.ConsumeWriteBuffer(seg.len);
+ m_rbuf.ConsumeReadData(seg.len);
+ // After shifting the position in the buffer, we may have
+ // out-of-order packets ready to be recovered.
+ bRecover = true;
+ }
+ }
+ } else {
+ uint32_t nOffset = seg.seq - m_rcv_nxt;
+
+ if (!m_rbuf.WriteOffset(seg.data, seg.len, nOffset, NULL)) {
+ // Ignore incoming packets outside of the receive window.
+ return false;
+ }
+
+ if (seg.seq == m_rcv_nxt) {
+ m_rbuf.ConsumeWriteBuffer(seg.len);
+ m_rcv_nxt += seg.len;
+ m_rcv_wnd -= seg.len;
+ bNewData = true;
+ // May be able to recover packets previously received out-of-order
+ // now.
+ bRecover = true;
+ } else {
+#if _DEBUGMSG >= _DBG_NORMAL
+ RTC_LOG(LS_INFO) << "Saving " << seg.len << " bytes (" << seg.seq
+ << " -> " << seg.seq + seg.len << ")";
+#endif // _DEBUGMSG
+ RSegment rseg;
+ rseg.seq = seg.seq;
+ rseg.len = seg.len;
+ RList::iterator it = m_rlist.begin();
+ while ((it != m_rlist.end()) && (it->seq < rseg.seq)) {
+ ++it;
+ }
+ m_rlist.insert(it, rseg);
+ }
+ }
+ if (bRecover) {
+ RList::iterator it = m_rlist.begin();
+ while ((it != m_rlist.end()) && (it->seq <= m_rcv_nxt)) {
+ if (it->seq + it->len > m_rcv_nxt) {
+ sflags = sfImmediateAck; // (Fast Recovery)
+ uint32_t nAdjust = (it->seq + it->len) - m_rcv_nxt;
+#if _DEBUGMSG >= _DBG_NORMAL
+ RTC_LOG(LS_INFO) << "Recovered " << nAdjust << " bytes (" << m_rcv_nxt
+ << " -> " << m_rcv_nxt + nAdjust << ")";
+#endif // _DEBUGMSG
+ m_rbuf.ConsumeWriteBuffer(nAdjust);
+ m_rcv_nxt += nAdjust;
+ m_rcv_wnd -= nAdjust;
+ bNewData = true;
+ }
+ it = m_rlist.erase(it);
+ }
+ }
+ }
+
+ attemptSend(sflags);
+
+ // If we have new data, notify the user
+ if (bNewData && m_bReadEnable) {
+ m_bReadEnable = false;
+ if (m_notify) {
+ m_notify->OnTcpReadable(this);
+ }
+ // notify(evRead);
+ }
+
+ return true;
+}
+
+bool PseudoTcp::transmit(const SList::iterator& seg, uint32_t now) {
+ if (seg->xmit >= ((m_state == TCP_ESTABLISHED) ? 15 : 30)) {
+ RTC_LOG_F(LS_VERBOSE) << "too many retransmits";
+ return false;
+ }
+
+ uint32_t nTransmit = std::min(seg->len, m_mss);
+
+ while (true) {
+ uint32_t seq = seg->seq;
+ uint8_t flags = (seg->bCtrl ? FLAG_CTL : 0);
+ IPseudoTcpNotify::WriteResult wres =
+ packet(seq, flags, seg->seq - m_snd_una, nTransmit);
+
+ if (wres == IPseudoTcpNotify::WR_SUCCESS)
+ break;
+
+ if (wres == IPseudoTcpNotify::WR_FAIL) {
+ RTC_LOG_F(LS_VERBOSE) << "packet failed";
+ return false;
+ }
+
+ RTC_DCHECK(wres == IPseudoTcpNotify::WR_TOO_LARGE);
+
+ while (true) {
+ if (PACKET_MAXIMUMS[m_msslevel + 1] == 0) {
+ RTC_LOG_F(LS_VERBOSE) << "MTU too small";
+ return false;
+ }
+ // !?! We need to break up all outstanding and pending packets and then
+ // retransmit!?!
+
+ m_mss = PACKET_MAXIMUMS[++m_msslevel] - PACKET_OVERHEAD;
+ m_cwnd = 2 * m_mss; // I added this... haven't researched actual formula
+ if (m_mss < nTransmit) {
+ nTransmit = m_mss;
+ break;
+ }
+ }
+#if _DEBUGMSG >= _DBG_NORMAL
+ RTC_LOG(LS_INFO) << "Adjusting mss to " << m_mss << " bytes";
+#endif // _DEBUGMSG
+ }
+
+ if (nTransmit < seg->len) {
+ RTC_LOG_F(LS_VERBOSE) << "mss reduced to " << m_mss;
+
+ SSegment subseg(seg->seq + nTransmit, seg->len - nTransmit, seg->bCtrl);
+ // subseg.tstamp = seg->tstamp;
+ subseg.xmit = seg->xmit;
+ seg->len = nTransmit;
+
+ SList::iterator next = seg;
+ m_slist.insert(++next, subseg);
+ }
+
+ if (seg->xmit == 0) {
+ m_snd_nxt += seg->len;
+ }
+ seg->xmit += 1;
+ // seg->tstamp = now;
+ if (m_rto_base == 0) {
+ m_rto_base = now;
+ }
+
+ return true;
+}
+
+void PseudoTcp::attemptSend(SendFlags sflags) {
+ uint32_t now = Now();
+
+ if (rtc::TimeDiff32(now, m_lastsend) > static_cast<long>(m_rx_rto)) {
+ m_cwnd = m_mss;
+ }
+
+#if _DEBUGMSG
+ bool bFirst = true;
+#endif // _DEBUGMSG
+
+ while (true) {
+ uint32_t cwnd = m_cwnd;
+ if ((m_dup_acks == 1) || (m_dup_acks == 2)) { // Limited Transmit
+ cwnd += m_dup_acks * m_mss;
+ }
+ uint32_t nWindow = std::min(m_snd_wnd, cwnd);
+ uint32_t nInFlight = m_snd_nxt - m_snd_una;
+ uint32_t nUseable = (nInFlight < nWindow) ? (nWindow - nInFlight) : 0;
+
+ size_t snd_buffered = m_sbuf.GetBuffered();
+ uint32_t nAvailable =
+ std::min(static_cast<uint32_t>(snd_buffered) - nInFlight, m_mss);
+
+ if (nAvailable > nUseable) {
+ if (nUseable * 4 < nWindow) {
+ // RFC 813 - avoid SWS
+ nAvailable = 0;
+ } else {
+ nAvailable = nUseable;
+ }
+ }
+
+#if _DEBUGMSG >= _DBG_VERBOSE
+ if (bFirst) {
+ size_t available_space = 0;
+ m_sbuf.GetWriteRemaining(&available_space);
+
+ bFirst = false;
+ RTC_LOG(LS_INFO) << "[cwnd: " << m_cwnd << " nWindow: " << nWindow
+ << " nInFlight: " << nInFlight
+ << " nAvailable: " << nAvailable
+ << " nQueued: " << snd_buffered
+ << " nEmpty: " << available_space
+ << " ssthresh: " << m_ssthresh << "]";
+ }
+#endif // _DEBUGMSG
+
+ if (nAvailable == 0) {
+ if (sflags == sfNone)
+ return;
+
+ // If this is an immediate ack, or the second delayed ack
+ if ((sflags == sfImmediateAck) || m_t_ack) {
+ packet(m_snd_nxt, 0, 0, 0);
+ } else {
+ m_t_ack = Now();
+ }
+ return;
+ }
+
+ // Nagle's algorithm.
+ // If there is data already in-flight, and we haven't a full segment of
+ // data ready to send then hold off until we get more to send, or the
+ // in-flight data is acknowledged.
+ if (m_use_nagling && (m_snd_nxt > m_snd_una) && (nAvailable < m_mss)) {
+ return;
+ }
+
+ // Find the next segment to transmit
+ SList::iterator it = m_slist.begin();
+ while (it->xmit > 0) {
+ ++it;
+ RTC_DCHECK(it != m_slist.end());
+ }
+ SList::iterator seg = it;
+
+ // If the segment is too large, break it into two
+ if (seg->len > nAvailable) {
+ SSegment subseg(seg->seq + nAvailable, seg->len - nAvailable, seg->bCtrl);
+ seg->len = nAvailable;
+ m_slist.insert(++it, subseg);
+ }
+
+ if (!transmit(seg, now)) {
+ RTC_LOG_F(LS_VERBOSE) << "transmit failed";
+ // TODO(?): consider closing socket
+ return;
+ }
+
+ sflags = sfNone;
+ }
+}
+
+void PseudoTcp::closedown(uint32_t err) {
+ RTC_LOG(LS_INFO) << "State: TCP_CLOSED";
+ m_state = TCP_CLOSED;
+ if (m_notify) {
+ m_notify->OnTcpClosed(this, err);
+ }
+ // notify(evClose, err);
+}
+
+void PseudoTcp::adjustMTU() {
+ // Determine our current mss level, so that we can adjust appropriately later
+ for (m_msslevel = 0; PACKET_MAXIMUMS[m_msslevel + 1] > 0; ++m_msslevel) {
+ if (static_cast<uint16_t>(PACKET_MAXIMUMS[m_msslevel]) <= m_mtu_advise) {
+ break;
+ }
+ }
+ m_mss = m_mtu_advise - PACKET_OVERHEAD;
+// !?! Should we reset m_largest here?
+#if _DEBUGMSG >= _DBG_NORMAL
+ RTC_LOG(LS_INFO) << "Adjusting mss to " << m_mss << " bytes";
+#endif // _DEBUGMSG
+ // Enforce minimums on ssthresh and cwnd
+ m_ssthresh = std::max(m_ssthresh, 2 * m_mss);
+ m_cwnd = std::max(m_cwnd, m_mss);
+}
+
+bool PseudoTcp::isReceiveBufferFull() const {
+ size_t available_space = 0;
+ m_rbuf.GetWriteRemaining(&available_space);
+ return !available_space;
+}
+
+void PseudoTcp::disableWindowScale() {
+ m_support_wnd_scale = false;
+}
+
+void PseudoTcp::queueConnectMessage() {
+ rtc::ByteBufferWriter buf;
+
+ buf.WriteUInt8(CTL_CONNECT);
+ if (m_support_wnd_scale) {
+ buf.WriteUInt8(TCP_OPT_WND_SCALE);
+ buf.WriteUInt8(1);
+ buf.WriteUInt8(m_rwnd_scale);
+ }
+ m_snd_wnd = static_cast<uint32_t>(buf.Length());
+ queue(buf.Data(), static_cast<uint32_t>(buf.Length()), true);
+}
+
+void PseudoTcp::parseOptions(const char* data, uint32_t len) {
+ std::set<uint8_t> options_specified;
+
+ // See http://www.freesoft.org/CIE/Course/Section4/8.htm for
+ // parsing the options list.
+ rtc::ByteBufferReader buf(data, len);
+ while (buf.Length()) {
+ uint8_t kind = TCP_OPT_EOL;
+ buf.ReadUInt8(&kind);
+
+ if (kind == TCP_OPT_EOL) {
+ // End of option list.
+ break;
+ } else if (kind == TCP_OPT_NOOP) {
+ // No op.
+ continue;
+ }
+
+ // Length of this option.
+ RTC_DCHECK(len != 0);
+ uint8_t opt_len = 0;
+ buf.ReadUInt8(&opt_len);
+
+ // Content of this option.
+ if (opt_len <= buf.Length()) {
+ applyOption(kind, buf.Data(), opt_len);
+ buf.Consume(opt_len);
+ } else {
+ RTC_LOG(LS_ERROR) << "Invalid option length received.";
+ return;
+ }
+ options_specified.insert(kind);
+ }
+
+ if (options_specified.find(TCP_OPT_WND_SCALE) == options_specified.end()) {
+ RTC_LOG(LS_WARNING) << "Peer doesn't support window scaling";
+
+ if (m_rwnd_scale > 0) {
+ // Peer doesn't support TCP options and window scaling.
+ // Revert receive buffer size to default value.
+ resizeReceiveBuffer(DEFAULT_RCV_BUF_SIZE);
+ m_swnd_scale = 0;
+ }
+ }
+}
+
+void PseudoTcp::applyOption(char kind, const char* data, uint32_t len) {
+ if (kind == TCP_OPT_MSS) {
+ RTC_LOG(LS_WARNING) << "Peer specified MSS option which is not supported.";
+ // TODO(?): Implement.
+ } else if (kind == TCP_OPT_WND_SCALE) {
+ // Window scale factor.
+ // http://www.ietf.org/rfc/rfc1323.txt
+ if (len != 1) {
+ RTC_LOG_F(LS_WARNING) << "Invalid window scale option received.";
+ return;
+ }
+ applyWindowScaleOption(data[0]);
+ }
+}
+
+void PseudoTcp::applyWindowScaleOption(uint8_t scale_factor) {
+ m_swnd_scale = scale_factor;
+}
+
+void PseudoTcp::resizeSendBuffer(uint32_t new_size) {
+ m_sbuf_len = new_size;
+ m_sbuf.SetCapacity(new_size);
+}
+
+void PseudoTcp::resizeReceiveBuffer(uint32_t new_size) {
+ uint8_t scale_factor = 0;
+
+ // Determine the scale factor such that the scaled window size can fit
+ // in a 16-bit unsigned integer.
+ while (new_size > 0xFFFF) {
+ ++scale_factor;
+ new_size >>= 1;
+ }
+
+ // Determine the proper size of the buffer.
+ new_size <<= scale_factor;
+ bool result = m_rbuf.SetCapacity(new_size);
+
+ // Make sure the new buffer is large enough to contain data in the old
+ // buffer. This should always be true because this method is called either
+ // before connection is established or when peers are exchanging connect
+ // messages.
+ RTC_DCHECK(result);
+ m_rbuf_len = new_size;
+ m_rwnd_scale = scale_factor;
+ m_ssthresh = new_size;
+
+ size_t available_space = 0;
+ m_rbuf.GetWriteRemaining(&available_space);
+ m_rcv_wnd = static_cast<uint32_t>(available_space);
+}
+
+PseudoTcp::LockedFifoBuffer::LockedFifoBuffer(size_t size)
+ : buffer_(new char[size]),
+ buffer_length_(size),
+ data_length_(0),
+ read_position_(0) {}
+
+PseudoTcp::LockedFifoBuffer::~LockedFifoBuffer() {}
+
+size_t PseudoTcp::LockedFifoBuffer::GetBuffered() const {
+ webrtc::MutexLock lock(&mutex_);
+ return data_length_;
+}
+
+bool PseudoTcp::LockedFifoBuffer::SetCapacity(size_t size) {
+ webrtc::MutexLock lock(&mutex_);
+ if (data_length_ > size)
+ return false;
+
+ if (size != buffer_length_) {
+ char* buffer = new char[size];
+ const size_t copy = data_length_;
+ const size_t tail_copy = std::min(copy, buffer_length_ - read_position_);
+ memcpy(buffer, &buffer_[read_position_], tail_copy);
+ memcpy(buffer + tail_copy, &buffer_[0], copy - tail_copy);
+ buffer_.reset(buffer);
+ read_position_ = 0;
+ buffer_length_ = size;
+ }
+
+ return true;
+}
+
+bool PseudoTcp::LockedFifoBuffer::ReadOffset(void* buffer,
+ size_t bytes,
+ size_t offset,
+ size_t* bytes_read) {
+ webrtc::MutexLock lock(&mutex_);
+ return ReadOffsetLocked(buffer, bytes, offset, bytes_read);
+}
+
+bool PseudoTcp::LockedFifoBuffer::WriteOffset(const void* buffer,
+ size_t bytes,
+ size_t offset,
+ size_t* bytes_written) {
+ webrtc::MutexLock lock(&mutex_);
+ return WriteOffsetLocked(buffer, bytes, offset, bytes_written);
+}
+
+bool PseudoTcp::LockedFifoBuffer::Read(void* buffer,
+ size_t bytes,
+ size_t* bytes_read) {
+ webrtc::MutexLock lock(&mutex_);
+ size_t copy = 0;
+ if (!ReadOffsetLocked(buffer, bytes, 0, &copy))
+ return false;
+
+ // If read was successful then adjust the read position and number of
+ // bytes buffered.
+ read_position_ = (read_position_ + copy) % buffer_length_;
+ data_length_ -= copy;
+ if (bytes_read)
+ *bytes_read = copy;
+
+ return true;
+}
+
+bool PseudoTcp::LockedFifoBuffer::Write(const void* buffer,
+ size_t bytes,
+ size_t* bytes_written) {
+ webrtc::MutexLock lock(&mutex_);
+ size_t copy = 0;
+ if (!WriteOffsetLocked(buffer, bytes, 0, &copy))
+ return false;
+
+ // If write was successful then adjust the number of readable bytes.
+ data_length_ += copy;
+ if (bytes_written) {
+ *bytes_written = copy;
+ }
+
+ return true;
+}
+
+void PseudoTcp::LockedFifoBuffer::ConsumeReadData(size_t size) {
+ webrtc::MutexLock lock(&mutex_);
+ RTC_DCHECK(size <= data_length_);
+ read_position_ = (read_position_ + size) % buffer_length_;
+ data_length_ -= size;
+}
+
+void PseudoTcp::LockedFifoBuffer::ConsumeWriteBuffer(size_t size) {
+ webrtc::MutexLock lock(&mutex_);
+ RTC_DCHECK(size <= buffer_length_ - data_length_);
+ data_length_ += size;
+}
+
+bool PseudoTcp::LockedFifoBuffer::GetWriteRemaining(size_t* size) const {
+ webrtc::MutexLock lock(&mutex_);
+ *size = buffer_length_ - data_length_;
+ return true;
+}
+
+bool PseudoTcp::LockedFifoBuffer::ReadOffsetLocked(void* buffer,
+ size_t bytes,
+ size_t offset,
+ size_t* bytes_read) {
+ if (offset >= data_length_)
+ return false;
+
+ const size_t available = data_length_ - offset;
+ const size_t read_position = (read_position_ + offset) % buffer_length_;
+ const size_t copy = std::min(bytes, available);
+ const size_t tail_copy = std::min(copy, buffer_length_ - read_position);
+ char* const p = static_cast<char*>(buffer);
+ memcpy(p, &buffer_[read_position], tail_copy);
+ memcpy(p + tail_copy, &buffer_[0], copy - tail_copy);
+
+ if (bytes_read)
+ *bytes_read = copy;
+
+ return true;
+}
+
+bool PseudoTcp::LockedFifoBuffer::WriteOffsetLocked(const void* buffer,
+ size_t bytes,
+ size_t offset,
+ size_t* bytes_written) {
+ if (data_length_ + offset >= buffer_length_)
+ return false;
+
+ const size_t available = buffer_length_ - data_length_ - offset;
+ const size_t write_position =
+ (read_position_ + data_length_ + offset) % buffer_length_;
+ const size_t copy = std::min(bytes, available);
+ const size_t tail_copy = std::min(copy, buffer_length_ - write_position);
+ const char* const p = static_cast<const char*>(buffer);
+ memcpy(&buffer_[write_position], p, tail_copy);
+ memcpy(&buffer_[0], p + tail_copy, copy - tail_copy);
+
+ if (bytes_written)
+ *bytes_written = copy;
+
+ return true;
+}
+
+} // namespace cricket
diff --git a/third_party/libwebrtc/p2p/base/pseudo_tcp.h b/third_party/libwebrtc/p2p/base/pseudo_tcp.h
new file mode 100644
index 0000000000..65b54ba125
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/pseudo_tcp.h
@@ -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_
diff --git a/third_party/libwebrtc/p2p/base/pseudo_tcp_unittest.cc b/third_party/libwebrtc/p2p/base/pseudo_tcp_unittest.cc
new file mode 100644
index 0000000000..e56c6fa2c5
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/pseudo_tcp_unittest.cc
@@ -0,0 +1,880 @@
+/*
+ * 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/pseudo_tcp.h"
+
+#include <string.h>
+
+#include <algorithm>
+#include <cstddef>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "api/task_queue/pending_task_safety_flag.h"
+#include "api/task_queue/task_queue_base.h"
+#include "api/units/time_delta.h"
+#include "rtc_base/gunit.h"
+#include "rtc_base/helpers.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/memory_stream.h"
+#include "rtc_base/time_utils.h"
+#include "test/gtest.h"
+
+using ::cricket::PseudoTcp;
+using ::webrtc::ScopedTaskSafety;
+using ::webrtc::TaskQueueBase;
+using ::webrtc::TimeDelta;
+
+static const int kConnectTimeoutMs = 10000; // ~3 * default RTO of 3000ms
+static const int kTransferTimeoutMs = 15000;
+static const int kBlockSize = 4096;
+
+class PseudoTcpForTest : public cricket::PseudoTcp {
+ public:
+ PseudoTcpForTest(cricket::IPseudoTcpNotify* notify, uint32_t conv)
+ : PseudoTcp(notify, conv) {}
+
+ bool isReceiveBufferFull() const { return PseudoTcp::isReceiveBufferFull(); }
+
+ void disableWindowScale() { PseudoTcp::disableWindowScale(); }
+};
+
+class PseudoTcpTestBase : public ::testing::Test,
+ public cricket::IPseudoTcpNotify {
+ public:
+ PseudoTcpTestBase()
+ : local_(this, 1),
+ remote_(this, 1),
+ have_connected_(false),
+ have_disconnected_(false),
+ local_mtu_(65535),
+ remote_mtu_(65535),
+ delay_(0),
+ loss_(0) {
+ // Set use of the test RNG to get predictable loss patterns. Otherwise,
+ // this test would occasionally get really unlucky loss and time out.
+ rtc::SetRandomTestMode(true);
+ }
+ ~PseudoTcpTestBase() {
+ // Put it back for the next test.
+ rtc::SetRandomTestMode(false);
+ }
+ // If true, both endpoints will send the "connect" segment simultaneously,
+ // rather than `local_` sending it followed by a response from `remote_`.
+ // Note that this is what chromoting ends up doing.
+ void SetSimultaneousOpen(bool enabled) { simultaneous_open_ = enabled; }
+ void SetLocalMtu(int mtu) {
+ local_.NotifyMTU(mtu);
+ local_mtu_ = mtu;
+ }
+ void SetRemoteMtu(int mtu) {
+ remote_.NotifyMTU(mtu);
+ remote_mtu_ = mtu;
+ }
+ void SetDelay(int delay) { delay_ = delay; }
+ void SetLoss(int percent) { loss_ = percent; }
+ // Used to cause the initial "connect" segment to be lost, needed for a
+ // regression test.
+ void DropNextPacket() { drop_next_packet_ = true; }
+ void SetOptNagling(bool enable_nagles) {
+ local_.SetOption(PseudoTcp::OPT_NODELAY, !enable_nagles);
+ remote_.SetOption(PseudoTcp::OPT_NODELAY, !enable_nagles);
+ }
+ void SetOptAckDelay(int ack_delay) {
+ local_.SetOption(PseudoTcp::OPT_ACKDELAY, ack_delay);
+ remote_.SetOption(PseudoTcp::OPT_ACKDELAY, ack_delay);
+ }
+ void SetOptSndBuf(int size) {
+ local_.SetOption(PseudoTcp::OPT_SNDBUF, size);
+ remote_.SetOption(PseudoTcp::OPT_SNDBUF, size);
+ }
+ void SetRemoteOptRcvBuf(int size) {
+ remote_.SetOption(PseudoTcp::OPT_RCVBUF, size);
+ }
+ void SetLocalOptRcvBuf(int size) {
+ local_.SetOption(PseudoTcp::OPT_RCVBUF, size);
+ }
+ void DisableRemoteWindowScale() { remote_.disableWindowScale(); }
+ void DisableLocalWindowScale() { local_.disableWindowScale(); }
+
+ protected:
+ int Connect() {
+ int ret = local_.Connect();
+ if (ret == 0) {
+ UpdateLocalClock();
+ }
+ if (simultaneous_open_) {
+ ret = remote_.Connect();
+ if (ret == 0) {
+ UpdateRemoteClock();
+ }
+ }
+ return ret;
+ }
+ void Close() {
+ local_.Close(false);
+ UpdateLocalClock();
+ }
+
+ virtual void OnTcpOpen(PseudoTcp* tcp) {
+ // Consider ourselves connected when the local side gets OnTcpOpen.
+ // OnTcpWriteable isn't fired at open, so we trigger it now.
+ RTC_LOG(LS_VERBOSE) << "Opened";
+ if (tcp == &local_) {
+ have_connected_ = true;
+ OnTcpWriteable(tcp);
+ }
+ }
+ // Test derived from the base should override
+ // virtual void OnTcpReadable(PseudoTcp* tcp)
+ // and
+ // virtual void OnTcpWritable(PseudoTcp* tcp)
+ virtual void OnTcpClosed(PseudoTcp* tcp, uint32_t error) {
+ // Consider ourselves closed when the remote side gets OnTcpClosed.
+ // TODO(?): OnTcpClosed is only ever notified in case of error in
+ // the current implementation. Solicited close is not (yet) supported.
+ RTC_LOG(LS_VERBOSE) << "Closed";
+ EXPECT_EQ(0U, error);
+ if (tcp == &remote_) {
+ have_disconnected_ = true;
+ }
+ }
+ virtual WriteResult TcpWritePacket(PseudoTcp* tcp,
+ const char* buffer,
+ size_t len) {
+ // Drop a packet if the test called DropNextPacket.
+ if (drop_next_packet_) {
+ drop_next_packet_ = false;
+ RTC_LOG(LS_VERBOSE) << "Dropping packet due to DropNextPacket, size="
+ << len;
+ return WR_SUCCESS;
+ }
+ // Randomly drop the desired percentage of packets.
+ if (rtc::CreateRandomId() % 100 < static_cast<uint32_t>(loss_)) {
+ RTC_LOG(LS_VERBOSE) << "Randomly dropping packet, size=" << len;
+ return WR_SUCCESS;
+ }
+ // Also drop packets that are larger than the configured MTU.
+ if (len > static_cast<size_t>(std::min(local_mtu_, remote_mtu_))) {
+ RTC_LOG(LS_VERBOSE) << "Dropping packet that exceeds path MTU, size="
+ << len;
+ return WR_SUCCESS;
+ }
+ PseudoTcp* other;
+ ScopedTaskSafety* timer;
+ if (tcp == &local_) {
+ other = &remote_;
+ timer = &remote_timer_;
+ } else {
+ other = &local_;
+ timer = &local_timer_;
+ }
+ std::string packet(buffer, len);
+ ++packets_in_flight_;
+ TaskQueueBase::Current()->PostDelayedTask(
+ [other, timer, packet = std::move(packet), this] {
+ --packets_in_flight_;
+ other->NotifyPacket(packet.c_str(), packet.size());
+ UpdateClock(*other, *timer);
+ },
+ TimeDelta::Millis(delay_));
+ return WR_SUCCESS;
+ }
+
+ void UpdateLocalClock() { UpdateClock(local_, local_timer_); }
+ void UpdateRemoteClock() { UpdateClock(remote_, remote_timer_); }
+ static void UpdateClock(PseudoTcp& tcp, ScopedTaskSafety& timer) {
+ long interval = 0; // NOLINT
+ tcp.GetNextClock(PseudoTcp::Now(), interval);
+ interval = std::max<int>(interval, 0L); // sometimes interval is < 0
+ timer.reset();
+ TaskQueueBase::Current()->PostDelayedTask(
+ SafeTask(timer.flag(),
+ [&tcp, &timer] {
+ tcp.NotifyClock(PseudoTcp::Now());
+ UpdateClock(tcp, timer);
+ }),
+ TimeDelta::Millis(interval));
+ }
+
+ rtc::AutoThread main_thread_;
+ PseudoTcpForTest local_;
+ PseudoTcpForTest remote_;
+ ScopedTaskSafety local_timer_;
+ ScopedTaskSafety remote_timer_;
+ rtc::MemoryStream send_stream_;
+ rtc::MemoryStream recv_stream_;
+ bool have_connected_;
+ bool have_disconnected_;
+ int local_mtu_;
+ int remote_mtu_;
+ int delay_;
+ int loss_;
+ bool drop_next_packet_ = false;
+ bool simultaneous_open_ = false;
+ int packets_in_flight_ = 0;
+};
+
+class PseudoTcpTest : public PseudoTcpTestBase {
+ public:
+ void TestTransfer(int size) {
+ uint32_t start;
+ int32_t elapsed;
+ size_t received;
+ // Create some dummy data to send.
+ send_stream_.ReserveSize(size);
+ for (int i = 0; i < size; ++i) {
+ uint8_t ch = static_cast<uint8_t>(i);
+ size_t written;
+ int error;
+ send_stream_.Write(rtc::MakeArrayView(&ch, 1), written, error);
+ }
+ send_stream_.Rewind();
+ // Prepare the receive stream.
+ recv_stream_.ReserveSize(size);
+ // Connect and wait until connected.
+ start = rtc::Time32();
+ EXPECT_EQ(0, Connect());
+ EXPECT_TRUE_WAIT(have_connected_, kConnectTimeoutMs);
+ // Sending will start from OnTcpWriteable and complete when all data has
+ // been received.
+ EXPECT_TRUE_WAIT(have_disconnected_, kTransferTimeoutMs);
+ elapsed = rtc::Time32() - start;
+ recv_stream_.GetSize(&received);
+ // Ensure we closed down OK and we got the right data.
+ // TODO(?): Ensure the errors are cleared properly.
+ // EXPECT_EQ(0, local_.GetError());
+ // EXPECT_EQ(0, remote_.GetError());
+ EXPECT_EQ(static_cast<size_t>(size), received);
+ EXPECT_EQ(0,
+ memcmp(send_stream_.GetBuffer(), recv_stream_.GetBuffer(), size));
+ RTC_LOG(LS_INFO) << "Transferred " << received << " bytes in " << elapsed
+ << " ms (" << size * 8 / elapsed << " Kbps)";
+ }
+
+ private:
+ // IPseudoTcpNotify interface
+
+ virtual void OnTcpReadable(PseudoTcp* tcp) {
+ // Stream bytes to the recv stream as they arrive.
+ if (tcp == &remote_) {
+ ReadData();
+
+ // TODO(?): OnTcpClosed() is currently only notified on error -
+ // there is no on-the-wire equivalent of TCP FIN.
+ // So we fake the notification when all the data has been read.
+ size_t received, required;
+ recv_stream_.GetPosition(&received);
+ send_stream_.GetSize(&required);
+ if (received == required)
+ OnTcpClosed(&remote_, 0);
+ }
+ }
+ virtual void OnTcpWriteable(PseudoTcp* tcp) {
+ // Write bytes from the send stream when we can.
+ // Shut down when we've sent everything.
+ if (tcp == &local_) {
+ RTC_LOG(LS_VERBOSE) << "Flow Control Lifted";
+ bool done;
+ WriteData(&done);
+ if (done) {
+ Close();
+ }
+ }
+ }
+
+ void ReadData() {
+ char block[kBlockSize];
+ size_t position;
+ int rcvd;
+ do {
+ rcvd = remote_.Recv(block, sizeof(block));
+ if (rcvd != -1) {
+ size_t written;
+ int error;
+ recv_stream_.Write(
+ rtc::MakeArrayView(reinterpret_cast<uint8_t*>(block), rcvd),
+ written, error);
+ recv_stream_.GetPosition(&position);
+ RTC_LOG(LS_VERBOSE) << "Received: " << position;
+ }
+ } while (rcvd > 0);
+ }
+ void WriteData(bool* done) {
+ size_t position, tosend;
+ int sent;
+ char block[kBlockSize];
+ do {
+ send_stream_.GetPosition(&position);
+ int error;
+ if (send_stream_.Read(
+ rtc::MakeArrayView(reinterpret_cast<uint8_t*>(block), kBlockSize),
+ tosend, error) != rtc::SR_EOS) {
+ sent = local_.Send(block, tosend);
+ UpdateLocalClock();
+ if (sent != -1) {
+ send_stream_.SetPosition(position + sent);
+ RTC_LOG(LS_VERBOSE) << "Sent: " << position + sent;
+ } else {
+ send_stream_.SetPosition(position);
+ RTC_LOG(LS_VERBOSE) << "Flow Controlled";
+ }
+ } else {
+ sent = static_cast<int>(tosend = 0);
+ }
+ } while (sent > 0);
+ *done = (tosend == 0);
+ }
+
+ private:
+ rtc::MemoryStream send_stream_;
+ rtc::MemoryStream recv_stream_;
+};
+
+class PseudoTcpTestPingPong : public PseudoTcpTestBase {
+ public:
+ PseudoTcpTestPingPong()
+ : iterations_remaining_(0),
+ sender_(NULL),
+ receiver_(NULL),
+ bytes_per_send_(0) {}
+ void SetBytesPerSend(int bytes) { bytes_per_send_ = bytes; }
+ void TestPingPong(int size, int iterations) {
+ uint32_t start, elapsed;
+ iterations_remaining_ = iterations;
+ receiver_ = &remote_;
+ sender_ = &local_;
+ // Create some dummy data to send.
+ send_stream_.ReserveSize(size);
+ for (int i = 0; i < size; ++i) {
+ uint8_t ch = static_cast<uint8_t>(i);
+ size_t written;
+ int error;
+ send_stream_.Write(rtc::MakeArrayView(&ch, 1), written, error);
+ }
+ send_stream_.Rewind();
+ // Prepare the receive stream.
+ recv_stream_.ReserveSize(size);
+ // Connect and wait until connected.
+ start = rtc::Time32();
+ EXPECT_EQ(0, Connect());
+ EXPECT_TRUE_WAIT(have_connected_, kConnectTimeoutMs);
+ // Sending will start from OnTcpWriteable and stop when the required
+ // number of iterations have completed.
+ EXPECT_TRUE_WAIT(have_disconnected_, kTransferTimeoutMs);
+ elapsed = rtc::TimeSince(start);
+ RTC_LOG(LS_INFO) << "Performed " << iterations << " pings in " << elapsed
+ << " ms";
+ }
+
+ private:
+ // IPseudoTcpNotify interface
+
+ virtual void OnTcpReadable(PseudoTcp* tcp) {
+ if (tcp != receiver_) {
+ RTC_LOG_F(LS_ERROR) << "unexpected OnTcpReadable";
+ return;
+ }
+ // Stream bytes to the recv stream as they arrive.
+ ReadData();
+ // If we've received the desired amount of data, rewind things
+ // and send it back the other way!
+ size_t position, desired;
+ recv_stream_.GetPosition(&position);
+ send_stream_.GetSize(&desired);
+ if (position == desired) {
+ if (receiver_ == &local_ && --iterations_remaining_ == 0) {
+ Close();
+ // TODO(?): Fake OnTcpClosed() on the receiver for now.
+ OnTcpClosed(&remote_, 0);
+ return;
+ }
+ PseudoTcp* tmp = receiver_;
+ receiver_ = sender_;
+ sender_ = tmp;
+ recv_stream_.Rewind();
+ send_stream_.Rewind();
+ OnTcpWriteable(sender_);
+ }
+ }
+ virtual void OnTcpWriteable(PseudoTcp* tcp) {
+ if (tcp != sender_)
+ return;
+ // Write bytes from the send stream when we can.
+ // Shut down when we've sent everything.
+ RTC_LOG(LS_VERBOSE) << "Flow Control Lifted";
+ WriteData();
+ }
+
+ void ReadData() {
+ char block[kBlockSize];
+ size_t position;
+ int rcvd;
+ do {
+ rcvd = receiver_->Recv(block, sizeof(block));
+ if (rcvd != -1) {
+ size_t written;
+ int error;
+ recv_stream_.Write(
+ rtc::MakeArrayView(reinterpret_cast<const uint8_t*>(block), rcvd),
+ written, error);
+ recv_stream_.GetPosition(&position);
+ RTC_LOG(LS_VERBOSE) << "Received: " << position;
+ }
+ } while (rcvd > 0);
+ }
+ void WriteData() {
+ size_t position, tosend;
+ int sent;
+ char block[kBlockSize];
+ do {
+ send_stream_.GetPosition(&position);
+ tosend = bytes_per_send_ ? bytes_per_send_ : sizeof(block);
+ int error;
+ if (send_stream_.Read(
+ rtc::MakeArrayView(reinterpret_cast<uint8_t*>(block), tosend),
+ tosend, error) != rtc::SR_EOS) {
+ sent = sender_->Send(block, tosend);
+ UpdateLocalClock();
+ if (sent != -1) {
+ send_stream_.SetPosition(position + sent);
+ RTC_LOG(LS_VERBOSE) << "Sent: " << position + sent;
+ } else {
+ send_stream_.SetPosition(position);
+ RTC_LOG(LS_VERBOSE) << "Flow Controlled";
+ }
+ } else {
+ sent = static_cast<int>(tosend = 0);
+ }
+ } while (sent > 0);
+ }
+
+ private:
+ int iterations_remaining_;
+ PseudoTcp* sender_;
+ PseudoTcp* receiver_;
+ int bytes_per_send_;
+};
+
+// Fill the receiver window until it is full, drain it and then
+// fill it with the same amount. This is to test that receiver window
+// contracts and enlarges correctly.
+class PseudoTcpTestReceiveWindow : public PseudoTcpTestBase {
+ public:
+ // Not all the data are transfered, `size` just need to be big enough
+ // to fill up the receiver window twice.
+ void TestTransfer(int size) {
+ // Create some dummy data to send.
+ send_stream_.ReserveSize(size);
+ for (int i = 0; i < size; ++i) {
+ uint8_t ch = static_cast<uint8_t>(i);
+ size_t written;
+ int error;
+ send_stream_.Write(rtc::MakeArrayView(&ch, 1), written, error);
+ }
+ send_stream_.Rewind();
+
+ // Prepare the receive stream.
+ recv_stream_.ReserveSize(size);
+
+ // Connect and wait until connected.
+ EXPECT_EQ(0, Connect());
+ EXPECT_TRUE_WAIT(have_connected_, kConnectTimeoutMs);
+
+ TaskQueueBase::Current()->PostTask([this] { WriteData(); });
+ EXPECT_TRUE_WAIT(have_disconnected_, kTransferTimeoutMs);
+
+ ASSERT_EQ(2u, send_position_.size());
+ ASSERT_EQ(2u, recv_position_.size());
+
+ const size_t estimated_recv_window = EstimateReceiveWindowSize();
+
+ // The difference in consecutive send positions should equal the
+ // receive window size or match very closely. This verifies that receive
+ // window is open after receiver drained all the data.
+ const size_t send_position_diff = send_position_[1] - send_position_[0];
+ EXPECT_GE(1024u, estimated_recv_window - send_position_diff);
+
+ // Receiver drained the receive window twice.
+ EXPECT_EQ(2 * estimated_recv_window, recv_position_[1]);
+ }
+
+ uint32_t EstimateReceiveWindowSize() const {
+ return static_cast<uint32_t>(recv_position_[0]);
+ }
+
+ uint32_t EstimateSendWindowSize() const {
+ return static_cast<uint32_t>(send_position_[0] - recv_position_[0]);
+ }
+
+ private:
+ // IPseudoTcpNotify interface
+ virtual void OnTcpReadable(PseudoTcp* tcp) {}
+
+ virtual void OnTcpWriteable(PseudoTcp* tcp) {}
+
+ void ReadUntilIOPending() {
+ char block[kBlockSize];
+ size_t position;
+ int rcvd;
+
+ do {
+ rcvd = remote_.Recv(block, sizeof(block));
+ if (rcvd != -1) {
+ size_t written;
+ int error;
+ recv_stream_.Write(
+ rtc::MakeArrayView(reinterpret_cast<uint8_t*>(block), rcvd),
+ written, error);
+ recv_stream_.GetPosition(&position);
+ RTC_LOG(LS_VERBOSE) << "Received: " << position;
+ }
+ } while (rcvd > 0);
+
+ recv_stream_.GetPosition(&position);
+ recv_position_.push_back(position);
+
+ // Disconnect if we have done two transfers.
+ if (recv_position_.size() == 2u) {
+ Close();
+ OnTcpClosed(&remote_, 0);
+ } else {
+ WriteData();
+ }
+ }
+
+ void WriteData() {
+ size_t position, tosend;
+ int sent;
+ char block[kBlockSize];
+ do {
+ send_stream_.GetPosition(&position);
+ int error;
+ if (send_stream_.Read(
+ rtc::MakeArrayView(reinterpret_cast<uint8_t*>(block),
+ sizeof(block)),
+ tosend, error) != rtc::SR_EOS) {
+ sent = local_.Send(block, tosend);
+ UpdateLocalClock();
+ if (sent != -1) {
+ send_stream_.SetPosition(position + sent);
+ RTC_LOG(LS_VERBOSE) << "Sent: " << position + sent;
+ } else {
+ send_stream_.SetPosition(position);
+ RTC_LOG(LS_VERBOSE) << "Flow Controlled";
+ }
+ } else {
+ sent = static_cast<int>(tosend = 0);
+ }
+ } while (sent > 0);
+ // At this point, we've filled up the available space in the send queue.
+
+ if (packets_in_flight_ > 0) {
+ // If there are packet tasks, attempt to continue sending after giving
+ // those packets time to process, which should free up the send buffer.
+ rtc::Thread::Current()->PostDelayedTask([this] { WriteData(); },
+ TimeDelta::Millis(10));
+ } else {
+ if (!remote_.isReceiveBufferFull()) {
+ RTC_LOG(LS_ERROR) << "This shouldn't happen - the send buffer is full, "
+ "the receive buffer is not, and there are no "
+ "remaining messages to process.";
+ }
+ send_stream_.GetPosition(&position);
+ send_position_.push_back(position);
+
+ // Drain the receiver buffer.
+ ReadUntilIOPending();
+ }
+ }
+
+ private:
+ rtc::MemoryStream send_stream_;
+ rtc::MemoryStream recv_stream_;
+
+ std::vector<size_t> send_position_;
+ std::vector<size_t> recv_position_;
+};
+
+// Basic end-to-end data transfer tests
+
+// Test the normal case of sending data from one side to the other.
+TEST_F(PseudoTcpTest, TestSend) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ TestTransfer(1000000);
+}
+
+// Test sending data with a 50 ms RTT. Transmission should take longer due
+// to a slower ramp-up in send rate.
+TEST_F(PseudoTcpTest, TestSendWithDelay) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetDelay(50);
+ TestTransfer(1000000);
+}
+
+// Test sending data with packet loss. Transmission should take much longer due
+// to send back-off when loss occurs.
+TEST_F(PseudoTcpTest, TestSendWithLoss) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetLoss(10);
+ TestTransfer(100000); // less data so test runs faster
+}
+
+// Test sending data with a 50 ms RTT and 10% packet loss. Transmission should
+// take much longer due to send back-off and slower detection of loss.
+TEST_F(PseudoTcpTest, TestSendWithDelayAndLoss) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetDelay(50);
+ SetLoss(10);
+ TestTransfer(100000); // less data so test runs faster
+}
+
+// Test sending data with 10% packet loss and Nagling disabled. Transmission
+// should take about the same time as with Nagling enabled.
+TEST_F(PseudoTcpTest, TestSendWithLossAndOptNaglingOff) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetLoss(10);
+ SetOptNagling(false);
+ TestTransfer(100000); // less data so test runs faster
+}
+
+// Regression test for bugs.webrtc.org/9208.
+//
+// This bug resulted in corrupted data if a "connect" segment was received after
+// a data segment. This is only possible if:
+//
+// * The initial "connect" segment is lost, and retransmitted later.
+// * Both sides send "connect"s simultaneously, such that the local side thinks
+// a connection is established even before its "connect" has been
+// acknowledged.
+// * Nagle algorithm disabled, allowing a data segment to be sent before the
+// "connect" has been acknowledged.
+TEST_F(PseudoTcpTest,
+ TestSendWhenFirstPacketLostWithOptNaglingOffAndSimultaneousOpen) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ DropNextPacket();
+ SetOptNagling(false);
+ SetSimultaneousOpen(true);
+ TestTransfer(10000);
+}
+
+// Test sending data with 10% packet loss and Delayed ACK disabled.
+// Transmission should be slightly faster than with it enabled.
+TEST_F(PseudoTcpTest, TestSendWithLossAndOptAckDelayOff) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetLoss(10);
+ SetOptAckDelay(0);
+ TestTransfer(100000);
+}
+
+// Test sending data with 50ms delay and Nagling disabled.
+TEST_F(PseudoTcpTest, TestSendWithDelayAndOptNaglingOff) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetDelay(50);
+ SetOptNagling(false);
+ TestTransfer(100000); // less data so test runs faster
+}
+
+// Test sending data with 50ms delay and Delayed ACK disabled.
+TEST_F(PseudoTcpTest, TestSendWithDelayAndOptAckDelayOff) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetDelay(50);
+ SetOptAckDelay(0);
+ TestTransfer(100000); // less data so test runs faster
+}
+
+// Test a large receive buffer with a sender that doesn't support scaling.
+TEST_F(PseudoTcpTest, TestSendRemoteNoWindowScale) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetLocalOptRcvBuf(100000);
+ DisableRemoteWindowScale();
+ TestTransfer(1000000);
+}
+
+// Test a large sender-side receive buffer with a receiver that doesn't support
+// scaling.
+TEST_F(PseudoTcpTest, TestSendLocalNoWindowScale) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetRemoteOptRcvBuf(100000);
+ DisableLocalWindowScale();
+ TestTransfer(1000000);
+}
+
+// Test when both sides use window scaling.
+TEST_F(PseudoTcpTest, TestSendBothUseWindowScale) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetRemoteOptRcvBuf(100000);
+ SetLocalOptRcvBuf(100000);
+ TestTransfer(1000000);
+}
+
+// Test using a large window scale value.
+TEST_F(PseudoTcpTest, TestSendLargeInFlight) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetRemoteOptRcvBuf(100000);
+ SetLocalOptRcvBuf(100000);
+ SetOptSndBuf(150000);
+ TestTransfer(1000000);
+}
+
+TEST_F(PseudoTcpTest, TestSendBothUseLargeWindowScale) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetRemoteOptRcvBuf(1000000);
+ SetLocalOptRcvBuf(1000000);
+ TestTransfer(10000000);
+}
+
+// Test using a small receive buffer.
+TEST_F(PseudoTcpTest, TestSendSmallReceiveBuffer) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetRemoteOptRcvBuf(10000);
+ SetLocalOptRcvBuf(10000);
+ TestTransfer(1000000);
+}
+
+// Test using a very small receive buffer.
+TEST_F(PseudoTcpTest, TestSendVerySmallReceiveBuffer) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetRemoteOptRcvBuf(100);
+ SetLocalOptRcvBuf(100);
+ TestTransfer(100000);
+}
+
+// Ping-pong (request/response) tests
+
+// Test sending <= 1x MTU of data in each ping/pong. Should take <10ms.
+TEST_F(PseudoTcpTestPingPong, TestPingPong1xMtu) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ TestPingPong(100, 100);
+}
+
+// Test sending 2x-3x MTU of data in each ping/pong. Should take <10ms.
+TEST_F(PseudoTcpTestPingPong, TestPingPong3xMtu) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ TestPingPong(400, 100);
+}
+
+// Test sending 1x-2x MTU of data in each ping/pong.
+// Should take ~1s, due to interaction between Nagling and Delayed ACK.
+TEST_F(PseudoTcpTestPingPong, TestPingPong2xMtu) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ TestPingPong(2000, 5);
+}
+
+// Test sending 1x-2x MTU of data in each ping/pong with Delayed ACK off.
+// Should take <10ms.
+TEST_F(PseudoTcpTestPingPong, TestPingPong2xMtuWithAckDelayOff) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetOptAckDelay(0);
+ TestPingPong(2000, 100);
+}
+
+// Test sending 1x-2x MTU of data in each ping/pong with Nagling off.
+// Should take <10ms.
+TEST_F(PseudoTcpTestPingPong, TestPingPong2xMtuWithNaglingOff) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetOptNagling(false);
+ TestPingPong(2000, 5);
+}
+
+// Test sending a ping as pair of short (non-full) segments.
+// Should take ~1s, due to Delayed ACK interaction with Nagling.
+TEST_F(PseudoTcpTestPingPong, TestPingPongShortSegments) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetOptAckDelay(5000);
+ SetBytesPerSend(50); // i.e. two Send calls per payload
+ TestPingPong(100, 5);
+}
+
+// Test sending ping as a pair of short (non-full) segments, with Nagling off.
+// Should take <10ms.
+TEST_F(PseudoTcpTestPingPong, TestPingPongShortSegmentsWithNaglingOff) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetOptNagling(false);
+ SetBytesPerSend(50); // i.e. two Send calls per payload
+ TestPingPong(100, 5);
+}
+
+// Test sending <= 1x MTU of data ping/pong, in two segments, no Delayed ACK.
+// Should take ~1s.
+TEST_F(PseudoTcpTestPingPong, TestPingPongShortSegmentsWithAckDelayOff) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetBytesPerSend(50); // i.e. two Send calls per payload
+ SetOptAckDelay(0);
+ TestPingPong(100, 5);
+}
+
+// Test that receive window expands and contract correctly.
+TEST_F(PseudoTcpTestReceiveWindow, TestReceiveWindow) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetOptNagling(false);
+ SetOptAckDelay(0);
+ TestTransfer(1024 * 1000);
+}
+
+// Test setting send window size to a very small value.
+TEST_F(PseudoTcpTestReceiveWindow, TestSetVerySmallSendWindowSize) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetOptNagling(false);
+ SetOptAckDelay(0);
+ SetOptSndBuf(900);
+ TestTransfer(1024 * 1000);
+ EXPECT_EQ(900u, EstimateSendWindowSize());
+}
+
+// Test setting receive window size to a value other than default.
+TEST_F(PseudoTcpTestReceiveWindow, TestSetReceiveWindowSize) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetOptNagling(false);
+ SetOptAckDelay(0);
+ SetRemoteOptRcvBuf(100000);
+ SetLocalOptRcvBuf(100000);
+ TestTransfer(1024 * 1000);
+ EXPECT_EQ(100000u, EstimateReceiveWindowSize());
+}
+
+/* Test sending data with mismatched MTUs. We should detect this and reduce
+// our packet size accordingly.
+// TODO(?): This doesn't actually work right now. The current code
+// doesn't detect if the MTU is set too high on either side.
+TEST_F(PseudoTcpTest, TestSendWithMismatchedMtus) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1280);
+ TestTransfer(1000000);
+}
+*/
diff --git a/third_party/libwebrtc/p2p/base/regathering_controller.cc b/third_party/libwebrtc/p2p/base/regathering_controller.cc
new file mode 100644
index 0000000000..572c2a616f
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/regathering_controller.cc
@@ -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
diff --git a/third_party/libwebrtc/p2p/base/regathering_controller.h b/third_party/libwebrtc/p2p/base/regathering_controller.h
new file mode 100644
index 0000000000..a0dfb8053d
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/regathering_controller.h
@@ -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_
diff --git a/third_party/libwebrtc/p2p/base/regathering_controller_unittest.cc b/third_party/libwebrtc/p2p/base/regathering_controller_unittest.cc
new file mode 100644
index 0000000000..91b7270f77
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/regathering_controller_unittest.cc
@@ -0,0 +1,189 @@
+/*
+ * 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 <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "api/scoped_refptr.h"
+#include "p2p/base/fake_port_allocator.h"
+#include "p2p/base/mock_ice_transport.h"
+#include "p2p/base/p2p_constants.h"
+#include "p2p/base/port.h"
+#include "p2p/base/stun_server.h"
+#include "rtc_base/gunit.h"
+#include "rtc_base/socket_address.h"
+#include "rtc_base/thread.h"
+#include "rtc_base/virtual_socket_server.h"
+#include "test/scoped_key_value_config.h"
+
+namespace {
+
+const int kOnlyLocalPorts = cricket::PORTALLOCATOR_DISABLE_STUN |
+ cricket::PORTALLOCATOR_DISABLE_RELAY |
+ cricket::PORTALLOCATOR_DISABLE_TCP;
+// The address of the public STUN server.
+const rtc::SocketAddress kStunAddr("99.99.99.1", cricket::STUN_SERVER_PORT);
+// The addresses for the public TURN server.
+const rtc::SocketAddress kTurnUdpIntAddr("99.99.99.3",
+ cricket::STUN_SERVER_PORT);
+const cricket::RelayCredentials kRelayCredentials("test", "test");
+const char kIceUfrag[] = "UF00";
+const char kIcePwd[] = "TESTICEPWD00000000000000";
+constexpr uint64_t kTiebreakerDefault = 44444;
+
+} // namespace
+
+namespace webrtc {
+
+class RegatheringControllerTest : public ::testing::Test,
+ public sigslot::has_slots<> {
+ public:
+ RegatheringControllerTest()
+ : vss_(std::make_unique<rtc::VirtualSocketServer>()),
+ thread_(vss_.get()),
+ ice_transport_(std::make_unique<cricket::MockIceTransport>()),
+ packet_socket_factory_(
+ std::make_unique<rtc::BasicPacketSocketFactory>(vss_.get())),
+ allocator_(std::make_unique<cricket::FakePortAllocator>(
+ rtc::Thread::Current(),
+ packet_socket_factory_.get(),
+ &field_trials_)) {
+ allocator_->SetIceTiebreaker(kTiebreakerDefault);
+ BasicRegatheringController::Config regathering_config;
+ regathering_config.regather_on_failed_networks_interval = 0;
+ regathering_controller_.reset(new BasicRegatheringController(
+ regathering_config, ice_transport_.get(), rtc::Thread::Current()));
+ }
+
+ // Initializes the allocator and gathers candidates once by StartGettingPorts.
+ void InitializeAndGatherOnce() {
+ cricket::ServerAddresses stun_servers;
+ stun_servers.insert(kStunAddr);
+ cricket::RelayServerConfig turn_server;
+ turn_server.credentials = kRelayCredentials;
+ turn_server.ports.push_back(
+ cricket::ProtocolAddress(kTurnUdpIntAddr, cricket::PROTO_UDP));
+ std::vector<cricket::RelayServerConfig> turn_servers(1, turn_server);
+ allocator_->set_flags(kOnlyLocalPorts);
+ allocator_->SetConfiguration(stun_servers, turn_servers, 0 /* pool size */,
+ webrtc::NO_PRUNE);
+ allocator_session_ = allocator_->CreateSession(
+ "test", cricket::ICE_CANDIDATE_COMPONENT_RTP, kIceUfrag, kIcePwd);
+ // The gathering will take place on the current thread and the following
+ // call of StartGettingPorts is blocking. We will not ClearGettingPorts
+ // prematurely.
+ allocator_session_->StartGettingPorts();
+ allocator_session_->SignalIceRegathering.connect(
+ this, &RegatheringControllerTest::OnIceRegathering);
+ regathering_controller_->set_allocator_session(allocator_session_.get());
+ }
+
+ // The regathering controller is initialized with the allocator session
+ // cleared. Only after clearing the session, we would be able to regather. See
+ // the comments for BasicRegatheringController in regatheringcontroller.h.
+ void InitializeAndGatherOnceWithSessionCleared() {
+ InitializeAndGatherOnce();
+ allocator_session_->ClearGettingPorts();
+ }
+
+ void OnIceRegathering(cricket::PortAllocatorSession* allocator_session,
+ cricket::IceRegatheringReason reason) {
+ ++count_[reason];
+ }
+
+ int GetRegatheringReasonCount(cricket::IceRegatheringReason reason) {
+ return count_[reason];
+ }
+
+ BasicRegatheringController* regathering_controller() {
+ return regathering_controller_.get();
+ }
+
+ private:
+ webrtc::test::ScopedKeyValueConfig field_trials_;
+ std::unique_ptr<rtc::VirtualSocketServer> vss_;
+ rtc::AutoSocketServerThread thread_;
+ std::unique_ptr<cricket::IceTransportInternal> ice_transport_;
+ std::unique_ptr<BasicRegatheringController> regathering_controller_;
+ std::unique_ptr<rtc::PacketSocketFactory> packet_socket_factory_;
+ std::unique_ptr<cricket::PortAllocator> allocator_;
+ std::unique_ptr<cricket::PortAllocatorSession> allocator_session_;
+ std::map<cricket::IceRegatheringReason, int> count_;
+};
+
+// Tests that ICE regathering occurs only if the port allocator session is
+// cleared. A port allocation session is not cleared if the initial gathering is
+// still in progress or the continual gathering is not enabled.
+TEST_F(RegatheringControllerTest,
+ IceRegatheringDoesNotOccurIfSessionNotCleared) {
+ rtc::ScopedFakeClock clock;
+ InitializeAndGatherOnce(); // Session not cleared.
+
+ BasicRegatheringController::Config config;
+ config.regather_on_failed_networks_interval = 2000;
+ regathering_controller()->SetConfig(config);
+ regathering_controller()->Start();
+ SIMULATED_WAIT(false, 10000, clock);
+ // Expect no regathering in the last 10s.
+ EXPECT_EQ(0, GetRegatheringReasonCount(
+ cricket::IceRegatheringReason::NETWORK_FAILURE));
+}
+
+TEST_F(RegatheringControllerTest, IceRegatheringRepeatsAsScheduled) {
+ rtc::ScopedFakeClock clock;
+ InitializeAndGatherOnceWithSessionCleared();
+
+ BasicRegatheringController::Config config;
+ config.regather_on_failed_networks_interval = 2000;
+ regathering_controller()->SetConfig(config);
+ regathering_controller()->Start();
+ SIMULATED_WAIT(false, 2000 - 1, clock);
+ // Expect no regathering.
+ EXPECT_EQ(0, GetRegatheringReasonCount(
+ cricket::IceRegatheringReason::NETWORK_FAILURE));
+ SIMULATED_WAIT(false, 2, clock);
+ // Expect regathering on all networks and on failed networks to happen once
+ // respectively in that last 2s with 2s interval.
+ EXPECT_EQ(1, GetRegatheringReasonCount(
+ cricket::IceRegatheringReason::NETWORK_FAILURE));
+ SIMULATED_WAIT(false, 11000, clock);
+ // Expect regathering to happen for another 5 times in 11s with 2s interval.
+ EXPECT_EQ(6, GetRegatheringReasonCount(
+ cricket::IceRegatheringReason::NETWORK_FAILURE));
+}
+
+// Tests that the schedule of ICE regathering on failed networks can be canceled
+// and replaced by a new recurring schedule.
+TEST_F(RegatheringControllerTest,
+ ScheduleOfIceRegatheringOnFailedNetworksCanBeReplaced) {
+ rtc::ScopedFakeClock clock;
+ InitializeAndGatherOnceWithSessionCleared();
+
+ BasicRegatheringController::Config config;
+ config.regather_on_failed_networks_interval = 2000;
+ regathering_controller()->SetConfig(config);
+ regathering_controller()->Start();
+ config.regather_on_failed_networks_interval = 5000;
+ regathering_controller()->SetConfig(config);
+ SIMULATED_WAIT(false, 3000, clock);
+ // Expect no regathering from the previous schedule.
+ EXPECT_EQ(0, GetRegatheringReasonCount(
+ cricket::IceRegatheringReason::NETWORK_FAILURE));
+ SIMULATED_WAIT(false, 11000 - 3000, clock);
+ // Expect regathering to happen twice in the last 11s with 5s interval.
+ EXPECT_EQ(2, GetRegatheringReasonCount(
+ cricket::IceRegatheringReason::NETWORK_FAILURE));
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/p2p/base/stun_port.cc b/third_party/libwebrtc/p2p/base/stun_port.cc
new file mode 100644
index 0000000000..9fd39da8f3
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/stun_port.cc
@@ -0,0 +1,700 @@
+/*
+ * 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/async_resolver_interface.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/strings/string_builder.h"
+
+namespace cricket {
+
+namespace {
+
+bool ResolveStunHostnameForFamily(const webrtc::FieldTrialsView& field_trials) {
+ // Bug fix for STUN hostname resolution on IPv6.
+ // Field trial key reserved in bugs.webrtc.org/14334
+ static constexpr char field_trial_name[] =
+ "WebRTC-IPv6NetworkResolutionFixes";
+ if (!field_trials.IsEnabled(field_trial_name)) {
+ return false;
+ }
+
+ webrtc::FieldTrialParameter<bool> resolve_stun_hostname_for_family(
+ "ResolveStunHostnameForFamily", /*default_value=*/false);
+ webrtc::ParseFieldTrial({&resolve_stun_hostname_for_family},
+ field_trials.Lookup(field_trial_name));
+ return resolve_stun_hostname_for_family;
+}
+
+} // namespace
+
+// 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) {}
+
+ 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());
+ }
+ };
+ if (ResolveStunHostnameForFamily(field_trials)) {
+ resolver_ptr->Start(address, family, std::move(callback));
+ } else {
+ resolver_ptr->Start(address, 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,
+ 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,
+ LOCAL_PORT_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,
+ 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,
+ LOCAL_PORT_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_->SignalReadPacket.connect(this, &UDPPort::OnReadPacket);
+ }
+ 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].type() == LOCAL_PORT_TYPE ||
+ 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 char* data,
+ size_t size,
+ const rtc::SocketAddress& remote_addr,
+ int64_t packet_time_us) {
+ // All packets given to UDP port will be consumed.
+ OnReadPacket(socket, data, size, remote_addr, packet_time_us);
+ 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 char* data,
+ size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const int64_t& packet_time_us) {
+ RTC_DCHECK(socket == socket_);
+ RTC_DCHECK(!remote_addr.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(remote_addr) != server_addresses_.end()) {
+ request_manager_.CheckResponse(data, size);
+ return;
+ }
+
+ if (Connection* conn = GetConnection(remote_addr)) {
+ conn->OnReadPacket(data, size, packet_time_us);
+ } else {
+ Port::OnReadPacket(data, size, remote_addr, 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->type() == STUN_PORT_TYPE && 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,
+ factory,
+ network,
+ min_port,
+ max_port,
+ username,
+ password,
+ false,
+ field_trials) {
+ // UDPPort will set these to local udp, updating these to STUN.
+ set_type(STUN_PORT_TYPE);
+ set_server_addresses(servers);
+}
+
+void StunPort::PrepareAddress() {
+ SendStunBindingRequests();
+}
+
+} // namespace cricket
diff --git a/third_party/libwebrtc/p2p/base/stun_port.h b/third_party/libwebrtc/p2p/base/stun_port.h
new file mode 100644
index 0000000000..380dbecd2d
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/stun_port.h
@@ -0,0 +1,301 @@
+/*
+ * 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/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, 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, 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 char* data,
+ size_t size,
+ const rtc::SocketAddress& remote_addr,
+ int64_t packet_time_us) 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,
+ 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,
+ 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 char* data,
+ size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const int64_t& packet_time_us);
+
+ void OnSentPacket(rtc::AsyncPacketSocket* socket,
+ const rtc::SentPacket& sent_packet) 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_
diff --git a/third_party/libwebrtc/p2p/base/stun_port_unittest.cc b/third_party/libwebrtc/p2p/base/stun_port_unittest.cc
new file mode 100644
index 0000000000..3d56636a9b
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/stun_port_unittest.cc
@@ -0,0 +1,765 @@
+/*
+ * Copyright 2009 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 <memory>
+
+#include "api/test/mock_async_dns_resolver.h"
+#include "p2p/base/basic_packet_socket_factory.h"
+#include "p2p/base/mock_dns_resolving_packet_socket_factory.h"
+#include "p2p/base/test_stun_server.h"
+#include "rtc_base/gunit.h"
+#include "rtc_base/helpers.h"
+#include "rtc_base/socket_address.h"
+#include "rtc_base/ssl_adapter.h"
+#include "rtc_base/virtual_socket_server.h"
+#include "test/gmock.h"
+#include "test/scoped_key_value_config.h"
+
+namespace {
+
+using cricket::ServerAddresses;
+using rtc::SocketAddress;
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InvokeArgument;
+using ::testing::Return;
+using ::testing::ReturnPointee;
+using ::testing::SetArgPointee;
+
+static const SocketAddress kLocalAddr("127.0.0.1", 0);
+static const SocketAddress kIPv6LocalAddr("::1", 0);
+static const SocketAddress kStunAddr1("127.0.0.1", 5000);
+static const SocketAddress kStunAddr2("127.0.0.1", 4000);
+static const SocketAddress kStunAddr3("127.0.0.1", 3000);
+static const SocketAddress kIPv6StunAddr1("::1", 5000);
+static const SocketAddress kBadAddr("0.0.0.1", 5000);
+static const SocketAddress kValidHostnameAddr("valid-hostname", 5000);
+static const SocketAddress kBadHostnameAddr("not-a-real-hostname", 5000);
+// STUN timeout (with all retries) is cricket::STUN_TOTAL_TIMEOUT.
+// Add some margin of error for slow bots.
+static const int kTimeoutMs = cricket::STUN_TOTAL_TIMEOUT;
+// stun prio = 100 (srflx) << 24 | 30 (IPv4) << 8 | 256 - 1 (component)
+static const uint32_t kStunCandidatePriority =
+ (100 << 24) | (30 << 8) | (256 - 1);
+// stun prio = 100 (srflx) << 24 | 60 (loopback IPv6) << 8 | 256 - 1 (component)
+static const uint32_t kIPv6StunCandidatePriority =
+ (100 << 24) | (60 << 8) | (256 - 1);
+static const int kInfiniteLifetime = -1;
+static const int kHighCostPortKeepaliveLifetimeMs = 2 * 60 * 1000;
+
+constexpr uint64_t kTiebreakerDefault = 44444;
+
+class FakeMdnsResponder : public webrtc::MdnsResponderInterface {
+ public:
+ void CreateNameForAddress(const rtc::IPAddress& addr,
+ NameCreatedCallback callback) override {
+ callback(addr, std::string("unittest-mdns-host-name.local"));
+ }
+
+ void RemoveNameForAddress(const rtc::IPAddress& addr,
+ NameRemovedCallback callback) override {}
+};
+
+class FakeMdnsResponderProvider : public rtc::MdnsResponderProvider {
+ public:
+ FakeMdnsResponderProvider() : mdns_responder_(new FakeMdnsResponder()) {}
+
+ webrtc::MdnsResponderInterface* GetMdnsResponder() const override {
+ return mdns_responder_.get();
+ }
+
+ private:
+ std::unique_ptr<webrtc::MdnsResponderInterface> mdns_responder_;
+};
+
+// Base class for tests connecting a StunPort to a fake STUN server
+// (cricket::StunServer).
+class StunPortTestBase : public ::testing::Test, public sigslot::has_slots<> {
+ public:
+ StunPortTestBase()
+ : StunPortTestBase(
+ rtc::Network("unittest", "unittest", kLocalAddr.ipaddr(), 32),
+ kLocalAddr.ipaddr()) {}
+
+ StunPortTestBase(rtc::Network network, const rtc::IPAddress address)
+ : ss_(new rtc::VirtualSocketServer()),
+ thread_(ss_.get()),
+ network_(network),
+ socket_factory_(ss_.get()),
+ stun_server_1_(cricket::TestStunServer::Create(ss_.get(), kStunAddr1)),
+ stun_server_2_(cricket::TestStunServer::Create(ss_.get(), kStunAddr2)),
+ mdns_responder_provider_(new FakeMdnsResponderProvider()),
+ done_(false),
+ error_(false),
+ stun_keepalive_delay_(1),
+ stun_keepalive_lifetime_(-1) {
+ network_.AddIP(address);
+ }
+
+ virtual rtc::PacketSocketFactory* socket_factory() {
+ return &socket_factory_;
+ }
+
+ rtc::VirtualSocketServer* ss() const { return ss_.get(); }
+ cricket::UDPPort* port() const { return stun_port_.get(); }
+ rtc::AsyncPacketSocket* socket() const { return socket_.get(); }
+ bool done() const { return done_; }
+ bool error() const { return error_; }
+
+ bool HasPendingRequest(int msg_type) {
+ return stun_port_->request_manager().HasRequestForTest(msg_type);
+ }
+
+ void SetNetworkType(rtc::AdapterType adapter_type) {
+ network_.set_type(adapter_type);
+ }
+
+ void CreateStunPort(const rtc::SocketAddress& server_addr,
+ const webrtc::FieldTrialsView* field_trials = nullptr) {
+ ServerAddresses stun_servers;
+ stun_servers.insert(server_addr);
+ CreateStunPort(stun_servers, field_trials);
+ }
+
+ void CreateStunPort(const ServerAddresses& stun_servers,
+ const webrtc::FieldTrialsView* field_trials = nullptr) {
+ stun_port_ = cricket::StunPort::Create(
+ rtc::Thread::Current(), socket_factory(), &network_, 0, 0,
+ rtc::CreateRandomString(16), rtc::CreateRandomString(22), stun_servers,
+ absl::nullopt, field_trials);
+ stun_port_->SetIceTiebreaker(kTiebreakerDefault);
+ stun_port_->set_stun_keepalive_delay(stun_keepalive_delay_);
+ // If `stun_keepalive_lifetime_` is negative, let the stun port
+ // choose its lifetime from the network type.
+ if (stun_keepalive_lifetime_ >= 0) {
+ stun_port_->set_stun_keepalive_lifetime(stun_keepalive_lifetime_);
+ }
+ stun_port_->SignalPortComplete.connect(this,
+ &StunPortTestBase::OnPortComplete);
+ stun_port_->SignalPortError.connect(this, &StunPortTestBase::OnPortError);
+ stun_port_->SignalCandidateError.connect(
+ this, &StunPortTestBase::OnCandidateError);
+ }
+
+ void CreateSharedUdpPort(
+ const rtc::SocketAddress& server_addr,
+ rtc::AsyncPacketSocket* socket,
+ const webrtc::FieldTrialsView* field_trials = nullptr) {
+ if (socket) {
+ socket_.reset(socket);
+ } else {
+ socket_.reset(socket_factory()->CreateUdpSocket(
+ rtc::SocketAddress(kLocalAddr.ipaddr(), 0), 0, 0));
+ }
+ ASSERT_TRUE(socket_ != NULL);
+ socket_->SignalReadPacket.connect(this, &StunPortTestBase::OnReadPacket);
+ stun_port_ = cricket::UDPPort::Create(
+ rtc::Thread::Current(), socket_factory(), &network_, socket_.get(),
+ rtc::CreateRandomString(16), rtc::CreateRandomString(22), false,
+ absl::nullopt, field_trials);
+ ASSERT_TRUE(stun_port_ != NULL);
+ stun_port_->SetIceTiebreaker(kTiebreakerDefault);
+ ServerAddresses stun_servers;
+ stun_servers.insert(server_addr);
+ stun_port_->set_server_addresses(stun_servers);
+ stun_port_->SignalPortComplete.connect(this,
+ &StunPortTestBase::OnPortComplete);
+ stun_port_->SignalPortError.connect(this, &StunPortTestBase::OnPortError);
+ }
+
+ void PrepareAddress() { stun_port_->PrepareAddress(); }
+
+ void OnReadPacket(rtc::AsyncPacketSocket* socket,
+ const char* data,
+ size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const int64_t& /* packet_time_us */) {
+ stun_port_->HandleIncomingPacket(socket, data, size, remote_addr,
+ /* packet_time_us */ -1);
+ }
+
+ void SendData(const char* data, size_t len) {
+ stun_port_->HandleIncomingPacket(socket_.get(), data, len,
+ rtc::SocketAddress("22.22.22.22", 0),
+ /* packet_time_us */ -1);
+ }
+
+ void EnableMdnsObfuscation() {
+ network_.set_mdns_responder_provider(mdns_responder_provider_.get());
+ }
+
+ protected:
+ static void SetUpTestSuite() {
+ // Ensure the RNG is inited.
+ rtc::InitRandom(NULL, 0);
+ }
+
+ void OnPortComplete(cricket::Port* port) {
+ ASSERT_FALSE(done_);
+ done_ = true;
+ error_ = false;
+ }
+ void OnPortError(cricket::Port* port) {
+ done_ = true;
+ error_ = true;
+ }
+ void OnCandidateError(cricket::Port* port,
+ const cricket::IceCandidateErrorEvent& event) {
+ error_event_ = event;
+ }
+ void SetKeepaliveDelay(int delay) { stun_keepalive_delay_ = delay; }
+
+ void SetKeepaliveLifetime(int lifetime) {
+ stun_keepalive_lifetime_ = lifetime;
+ }
+
+ cricket::TestStunServer* stun_server_1() { return stun_server_1_.get(); }
+ cricket::TestStunServer* stun_server_2() { return stun_server_2_.get(); }
+
+ private:
+ std::unique_ptr<rtc::VirtualSocketServer> ss_;
+ rtc::AutoSocketServerThread thread_;
+ rtc::Network network_;
+ rtc::BasicPacketSocketFactory socket_factory_;
+ std::unique_ptr<cricket::UDPPort> stun_port_;
+ std::unique_ptr<cricket::TestStunServer> stun_server_1_;
+ std::unique_ptr<cricket::TestStunServer> stun_server_2_;
+ std::unique_ptr<rtc::AsyncPacketSocket> socket_;
+ std::unique_ptr<rtc::MdnsResponderProvider> mdns_responder_provider_;
+ bool done_;
+ bool error_;
+ int stun_keepalive_delay_;
+ int stun_keepalive_lifetime_;
+
+ protected:
+ cricket::IceCandidateErrorEvent error_event_;
+};
+
+class StunPortTestWithRealClock : public StunPortTestBase {};
+
+class FakeClockBase {
+ public:
+ rtc::ScopedFakeClock fake_clock;
+};
+
+class StunPortTest : public FakeClockBase, public StunPortTestBase {};
+
+// Test that we can create a STUN port.
+TEST_F(StunPortTest, TestCreateStunPort) {
+ CreateStunPort(kStunAddr1);
+ EXPECT_EQ("stun", port()->Type());
+ EXPECT_EQ(0U, port()->Candidates().size());
+}
+
+// Test that we can create a UDP port.
+TEST_F(StunPortTest, TestCreateUdpPort) {
+ CreateSharedUdpPort(kStunAddr1, nullptr);
+ EXPECT_EQ("local", port()->Type());
+ EXPECT_EQ(0U, port()->Candidates().size());
+}
+
+// Test that we can get an address from a STUN server.
+TEST_F(StunPortTest, TestPrepareAddress) {
+ CreateStunPort(kStunAddr1);
+ PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(done(), kTimeoutMs, fake_clock);
+ ASSERT_EQ(1U, port()->Candidates().size());
+ EXPECT_TRUE(kLocalAddr.EqualIPs(port()->Candidates()[0].address()));
+ std::string expected_server_url = "stun:127.0.0.1:5000";
+ EXPECT_EQ(port()->Candidates()[0].url(), expected_server_url);
+}
+
+// Test that we fail properly if we can't get an address.
+TEST_F(StunPortTest, TestPrepareAddressFail) {
+ CreateStunPort(kBadAddr);
+ PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(done(), kTimeoutMs, fake_clock);
+ EXPECT_TRUE(error());
+ EXPECT_EQ(0U, port()->Candidates().size());
+ EXPECT_EQ_SIMULATED_WAIT(error_event_.error_code,
+ cricket::SERVER_NOT_REACHABLE_ERROR, kTimeoutMs,
+ fake_clock);
+ ASSERT_NE(error_event_.error_text.find('.'), std::string::npos);
+ ASSERT_NE(error_event_.address.find(kLocalAddr.HostAsSensitiveURIString()),
+ std::string::npos);
+ std::string server_url = "stun:" + kBadAddr.ToString();
+ ASSERT_EQ(error_event_.url, server_url);
+}
+
+class StunPortWithMockDnsResolverTest : public StunPortTest {
+ public:
+ StunPortWithMockDnsResolverTest() : StunPortTest(), socket_factory_(ss()) {}
+
+ rtc::PacketSocketFactory* socket_factory() override {
+ return &socket_factory_;
+ }
+
+ void SetDnsResolverExpectations(
+ rtc::MockDnsResolvingPacketSocketFactory::Expectations expectations) {
+ socket_factory_.SetExpectations(expectations);
+ }
+
+ private:
+ rtc::MockDnsResolvingPacketSocketFactory socket_factory_;
+};
+
+// Test that we can get an address from a STUN server specified by a hostname.
+TEST_F(StunPortWithMockDnsResolverTest, TestPrepareAddressHostname) {
+ SetDnsResolverExpectations(
+ [](webrtc::MockAsyncDnsResolver* resolver,
+ webrtc::MockAsyncDnsResolverResult* resolver_result) {
+ EXPECT_CALL(*resolver, Start(kValidHostnameAddr, _))
+ .WillOnce(InvokeArgument<1>());
+ EXPECT_CALL(*resolver, result)
+ .WillRepeatedly(ReturnPointee(resolver_result));
+ EXPECT_CALL(*resolver_result, GetError).WillOnce(Return(0));
+ EXPECT_CALL(*resolver_result, GetResolvedAddress(AF_INET, _))
+ .WillOnce(DoAll(SetArgPointee<1>(SocketAddress("127.0.0.1", 5000)),
+ Return(true)));
+ });
+ CreateStunPort(kValidHostnameAddr);
+ PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(done(), kTimeoutMs, fake_clock);
+ ASSERT_EQ(1U, port()->Candidates().size());
+ EXPECT_TRUE(kLocalAddr.EqualIPs(port()->Candidates()[0].address()));
+ EXPECT_EQ(kStunCandidatePriority, port()->Candidates()[0].priority());
+}
+
+// Test that we handle hostname lookup failures properly.
+TEST_F(StunPortTestWithRealClock, TestPrepareAddressHostnameFail) {
+ CreateStunPort(kBadHostnameAddr);
+ PrepareAddress();
+ EXPECT_TRUE_WAIT(done(), kTimeoutMs);
+ EXPECT_TRUE(error());
+ EXPECT_EQ(0U, port()->Candidates().size());
+ EXPECT_EQ_WAIT(error_event_.error_code, cricket::SERVER_NOT_REACHABLE_ERROR,
+ kTimeoutMs);
+}
+
+// This test verifies keepalive response messages don't result in
+// additional candidate generation.
+TEST_F(StunPortTest, TestKeepAliveResponse) {
+ SetKeepaliveDelay(500); // 500ms of keepalive delay.
+ CreateStunPort(kStunAddr1);
+ PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(done(), kTimeoutMs, fake_clock);
+ ASSERT_EQ(1U, port()->Candidates().size());
+ EXPECT_TRUE(kLocalAddr.EqualIPs(port()->Candidates()[0].address()));
+ SIMULATED_WAIT(false, 1000, fake_clock);
+ EXPECT_EQ(1U, port()->Candidates().size());
+}
+
+// Test that a local candidate can be generated using a shared socket.
+TEST_F(StunPortTest, TestSharedSocketPrepareAddress) {
+ CreateSharedUdpPort(kStunAddr1, nullptr);
+ PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(done(), kTimeoutMs, fake_clock);
+ ASSERT_EQ(1U, port()->Candidates().size());
+ EXPECT_TRUE(kLocalAddr.EqualIPs(port()->Candidates()[0].address()));
+}
+
+// Test that we still get a local candidate with invalid stun server hostname.
+// Also verifing that UDPPort can receive packets when stun address can't be
+// resolved.
+TEST_F(StunPortTestWithRealClock,
+ TestSharedSocketPrepareAddressInvalidHostname) {
+ CreateSharedUdpPort(kBadHostnameAddr, nullptr);
+ PrepareAddress();
+ EXPECT_TRUE_WAIT(done(), kTimeoutMs);
+ ASSERT_EQ(1U, port()->Candidates().size());
+ EXPECT_TRUE(kLocalAddr.EqualIPs(port()->Candidates()[0].address()));
+
+ // Send data to port after it's ready. This is to make sure, UDP port can
+ // handle data with unresolved stun server address.
+ std::string data = "some random data, sending to cricket::Port.";
+ SendData(data.c_str(), data.length());
+ // No crash is success.
+}
+
+// Test that a stun candidate (srflx candidate) is discarded whose address is
+// equal to that of a local candidate if mDNS obfuscation is not enabled.
+TEST_F(StunPortTest, TestStunCandidateDiscardedWithMdnsObfuscationNotEnabled) {
+ CreateSharedUdpPort(kStunAddr1, nullptr);
+ PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(done(), kTimeoutMs, fake_clock);
+ ASSERT_EQ(1U, port()->Candidates().size());
+ EXPECT_TRUE(kLocalAddr.EqualIPs(port()->Candidates()[0].address()));
+ EXPECT_EQ(port()->Candidates()[0].type(), cricket::LOCAL_PORT_TYPE);
+}
+
+// Test that a stun candidate (srflx candidate) is generated whose address is
+// equal to that of a local candidate if mDNS obfuscation is enabled.
+TEST_F(StunPortTest, TestStunCandidateGeneratedWithMdnsObfuscationEnabled) {
+ EnableMdnsObfuscation();
+ CreateSharedUdpPort(kStunAddr1, nullptr);
+ PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(done(), kTimeoutMs, fake_clock);
+ ASSERT_EQ(2U, port()->Candidates().size());
+
+ // The addresses of the candidates are both equal to kLocalAddr.
+ EXPECT_TRUE(kLocalAddr.EqualIPs(port()->Candidates()[0].address()));
+ EXPECT_TRUE(kLocalAddr.EqualIPs(port()->Candidates()[1].address()));
+
+ // One of the generated candidates is a local candidate and the other is a
+ // stun candidate.
+ EXPECT_NE(port()->Candidates()[0].type(), port()->Candidates()[1].type());
+ if (port()->Candidates()[0].type() == cricket::LOCAL_PORT_TYPE) {
+ EXPECT_EQ(port()->Candidates()[1].type(), cricket::STUN_PORT_TYPE);
+ } else {
+ EXPECT_EQ(port()->Candidates()[0].type(), cricket::STUN_PORT_TYPE);
+ EXPECT_EQ(port()->Candidates()[1].type(), cricket::LOCAL_PORT_TYPE);
+ }
+}
+
+// Test that the same address is added only once if two STUN servers are in
+// use.
+TEST_F(StunPortTest, TestNoDuplicatedAddressWithTwoStunServers) {
+ ServerAddresses stun_servers;
+ stun_servers.insert(kStunAddr1);
+ stun_servers.insert(kStunAddr2);
+ CreateStunPort(stun_servers);
+ EXPECT_EQ("stun", port()->Type());
+ PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(done(), kTimeoutMs, fake_clock);
+ EXPECT_EQ(1U, port()->Candidates().size());
+ EXPECT_EQ(port()->Candidates()[0].relay_protocol(), "");
+}
+
+// Test that candidates can be allocated for multiple STUN servers, one of
+// which is not reachable.
+TEST_F(StunPortTest, TestMultipleStunServersWithBadServer) {
+ ServerAddresses stun_servers;
+ stun_servers.insert(kStunAddr1);
+ stun_servers.insert(kBadAddr);
+ CreateStunPort(stun_servers);
+ EXPECT_EQ("stun", port()->Type());
+ PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(done(), kTimeoutMs, fake_clock);
+ EXPECT_EQ(1U, port()->Candidates().size());
+ std::string server_url = "stun:" + kBadAddr.ToString();
+ ASSERT_EQ_SIMULATED_WAIT(error_event_.url, server_url, kTimeoutMs,
+ fake_clock);
+}
+
+// Test that two candidates are allocated if the two STUN servers return
+// different mapped addresses.
+TEST_F(StunPortTest, TestTwoCandidatesWithTwoStunServersAcrossNat) {
+ const SocketAddress kStunMappedAddr1("77.77.77.77", 0);
+ const SocketAddress kStunMappedAddr2("88.77.77.77", 0);
+ stun_server_1()->set_fake_stun_addr(kStunMappedAddr1);
+ stun_server_2()->set_fake_stun_addr(kStunMappedAddr2);
+
+ ServerAddresses stun_servers;
+ stun_servers.insert(kStunAddr1);
+ stun_servers.insert(kStunAddr2);
+ CreateStunPort(stun_servers);
+ EXPECT_EQ("stun", port()->Type());
+ PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(done(), kTimeoutMs, fake_clock);
+ EXPECT_EQ(2U, port()->Candidates().size());
+ EXPECT_EQ(port()->Candidates()[0].relay_protocol(), "");
+ EXPECT_EQ(port()->Candidates()[1].relay_protocol(), "");
+}
+
+// Test that the stun_keepalive_lifetime is set correctly based on the network
+// type on a STUN port. Also test that it will be updated if the network type
+// changes.
+TEST_F(StunPortTest, TestStunPortGetStunKeepaliveLifetime) {
+ // Lifetime for the default (unknown) network type is `kInfiniteLifetime`.
+ CreateStunPort(kStunAddr1);
+ EXPECT_EQ(kInfiniteLifetime, port()->stun_keepalive_lifetime());
+ // Lifetime for the cellular network is `kHighCostPortKeepaliveLifetimeMs`
+ SetNetworkType(rtc::ADAPTER_TYPE_CELLULAR);
+ EXPECT_EQ(kHighCostPortKeepaliveLifetimeMs,
+ port()->stun_keepalive_lifetime());
+
+ // Lifetime for the wifi network is `kInfiniteLifetime`.
+ SetNetworkType(rtc::ADAPTER_TYPE_WIFI);
+ CreateStunPort(kStunAddr2);
+ EXPECT_EQ(kInfiniteLifetime, port()->stun_keepalive_lifetime());
+}
+
+// Test that the stun_keepalive_lifetime is set correctly based on the network
+// type on a shared STUN port (UDPPort). Also test that it will be updated
+// if the network type changes.
+TEST_F(StunPortTest, TestUdpPortGetStunKeepaliveLifetime) {
+ // Lifetime for the default (unknown) network type is `kInfiniteLifetime`.
+ CreateSharedUdpPort(kStunAddr1, nullptr);
+ EXPECT_EQ(kInfiniteLifetime, port()->stun_keepalive_lifetime());
+ // Lifetime for the cellular network is `kHighCostPortKeepaliveLifetimeMs`.
+ SetNetworkType(rtc::ADAPTER_TYPE_CELLULAR);
+ EXPECT_EQ(kHighCostPortKeepaliveLifetimeMs,
+ port()->stun_keepalive_lifetime());
+
+ // Lifetime for the wifi network type is `kInfiniteLifetime`.
+ SetNetworkType(rtc::ADAPTER_TYPE_WIFI);
+ CreateSharedUdpPort(kStunAddr2, nullptr);
+ EXPECT_EQ(kInfiniteLifetime, port()->stun_keepalive_lifetime());
+}
+
+// Test that STUN binding requests will be stopped shortly if the keep-alive
+// lifetime is short.
+TEST_F(StunPortTest, TestStunBindingRequestShortLifetime) {
+ SetKeepaliveDelay(101);
+ SetKeepaliveLifetime(100);
+ CreateStunPort(kStunAddr1);
+ PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(done(), kTimeoutMs, fake_clock);
+ EXPECT_TRUE_SIMULATED_WAIT(!HasPendingRequest(cricket::STUN_BINDING_REQUEST),
+ 2000, fake_clock);
+}
+
+// Test that by default, the STUN binding requests will last for a long time.
+TEST_F(StunPortTest, TestStunBindingRequestLongLifetime) {
+ SetKeepaliveDelay(101);
+ CreateStunPort(kStunAddr1);
+ PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(done(), kTimeoutMs, fake_clock);
+ EXPECT_TRUE_SIMULATED_WAIT(HasPendingRequest(cricket::STUN_BINDING_REQUEST),
+ 1000, fake_clock);
+}
+
+class MockAsyncPacketSocket : public rtc::AsyncPacketSocket {
+ public:
+ ~MockAsyncPacketSocket() = default;
+
+ MOCK_METHOD(SocketAddress, GetLocalAddress, (), (const, override));
+ MOCK_METHOD(SocketAddress, GetRemoteAddress, (), (const, override));
+ MOCK_METHOD(int,
+ Send,
+ (const void* pv, size_t cb, const rtc::PacketOptions& options),
+ (override));
+
+ MOCK_METHOD(int,
+ SendTo,
+ (const void* pv,
+ size_t cb,
+ const SocketAddress& addr,
+ const rtc::PacketOptions& options),
+ (override));
+ MOCK_METHOD(int, Close, (), (override));
+ MOCK_METHOD(State, GetState, (), (const, override));
+ MOCK_METHOD(int,
+ GetOption,
+ (rtc::Socket::Option opt, int* value),
+ (override));
+ MOCK_METHOD(int, SetOption, (rtc::Socket::Option opt, int value), (override));
+ MOCK_METHOD(int, GetError, (), (const, override));
+ MOCK_METHOD(void, SetError, (int error), (override));
+};
+
+// Test that outbound packets inherit the dscp value assigned to the socket.
+TEST_F(StunPortTest, TestStunPacketsHaveDscpPacketOption) {
+ MockAsyncPacketSocket* socket = new MockAsyncPacketSocket();
+ CreateSharedUdpPort(kStunAddr1, socket);
+ EXPECT_CALL(*socket, GetLocalAddress()).WillRepeatedly(Return(kLocalAddr));
+ EXPECT_CALL(*socket, GetState())
+ .WillRepeatedly(Return(rtc::AsyncPacketSocket::STATE_BOUND));
+ EXPECT_CALL(*socket, SetOption(_, _)).WillRepeatedly(Return(0));
+
+ // If DSCP is not set on the socket, stun packets should have no value.
+ EXPECT_CALL(*socket,
+ SendTo(_, _, _,
+ ::testing::Field(&rtc::PacketOptions::dscp,
+ ::testing::Eq(rtc::DSCP_NO_CHANGE))))
+ .WillOnce(Return(100));
+ PrepareAddress();
+
+ // Once it is set transport wide, they should inherit that value.
+ port()->SetOption(rtc::Socket::OPT_DSCP, rtc::DSCP_AF41);
+ EXPECT_CALL(*socket, SendTo(_, _, _,
+ ::testing::Field(&rtc::PacketOptions::dscp,
+ ::testing::Eq(rtc::DSCP_AF41))))
+ .WillRepeatedly(Return(100));
+ EXPECT_TRUE_SIMULATED_WAIT(done(), kTimeoutMs, fake_clock);
+}
+
+class StunIPv6PortTestBase : public StunPortTestBase {
+ public:
+ StunIPv6PortTestBase()
+ : StunPortTestBase(rtc::Network("unittestipv6",
+ "unittestipv6",
+ kIPv6LocalAddr.ipaddr(),
+ 128),
+ kIPv6LocalAddr.ipaddr()) {
+ stun_server_ipv6_1_.reset(
+ cricket::TestStunServer::Create(ss(), kIPv6StunAddr1));
+ }
+
+ protected:
+ std::unique_ptr<cricket::TestStunServer> stun_server_ipv6_1_;
+};
+
+class StunIPv6PortTestWithRealClock : public StunIPv6PortTestBase {};
+
+class StunIPv6PortTest : public FakeClockBase, public StunIPv6PortTestBase {};
+
+// Test that we can get an address from a STUN server.
+TEST_F(StunIPv6PortTest, TestPrepareAddress) {
+ CreateStunPort(kIPv6StunAddr1);
+ PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(done(), kTimeoutMs, fake_clock);
+ ASSERT_EQ(1U, port()->Candidates().size());
+ EXPECT_TRUE(kIPv6LocalAddr.EqualIPs(port()->Candidates()[0].address()));
+ std::string expected_server_url = "stun:::1:5000";
+ EXPECT_EQ(port()->Candidates()[0].url(), expected_server_url);
+}
+
+// Test that we fail properly if we can't get an address.
+TEST_F(StunIPv6PortTest, TestPrepareAddressFail) {
+ CreateStunPort(kBadAddr);
+ PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(done(), kTimeoutMs, fake_clock);
+ EXPECT_TRUE(error());
+ EXPECT_EQ(0U, port()->Candidates().size());
+ EXPECT_EQ_SIMULATED_WAIT(error_event_.error_code,
+ cricket::SERVER_NOT_REACHABLE_ERROR, kTimeoutMs,
+ fake_clock);
+ ASSERT_NE(error_event_.error_text.find('.'), std::string::npos);
+ ASSERT_NE(
+ error_event_.address.find(kIPv6LocalAddr.HostAsSensitiveURIString()),
+ std::string::npos);
+ std::string server_url = "stun:" + kBadAddr.ToString();
+ ASSERT_EQ(error_event_.url, server_url);
+}
+
+// Test that we handle hostname lookup failures properly with a real clock.
+TEST_F(StunIPv6PortTestWithRealClock, TestPrepareAddressHostnameFail) {
+ CreateStunPort(kBadHostnameAddr);
+ PrepareAddress();
+ EXPECT_TRUE_WAIT(done(), kTimeoutMs);
+ EXPECT_TRUE(error());
+ EXPECT_EQ(0U, port()->Candidates().size());
+ EXPECT_EQ_WAIT(error_event_.error_code, cricket::SERVER_NOT_REACHABLE_ERROR,
+ kTimeoutMs);
+}
+
+class StunIPv6PortTestWithMockDnsResolver : public StunIPv6PortTest {
+ public:
+ StunIPv6PortTestWithMockDnsResolver()
+ : StunIPv6PortTest(), socket_factory_(ss()) {}
+
+ rtc::PacketSocketFactory* socket_factory() override {
+ return &socket_factory_;
+ }
+
+ void SetDnsResolverExpectations(
+ rtc::MockDnsResolvingPacketSocketFactory::Expectations expectations) {
+ socket_factory_.SetExpectations(expectations);
+ }
+
+ private:
+ rtc::MockDnsResolvingPacketSocketFactory socket_factory_;
+};
+
+// Test that we can get an address from a STUN server specified by a hostname.
+TEST_F(StunIPv6PortTestWithMockDnsResolver, TestPrepareAddressHostname) {
+ SetDnsResolverExpectations(
+ [](webrtc::MockAsyncDnsResolver* resolver,
+ webrtc::MockAsyncDnsResolverResult* resolver_result) {
+ // Expect to call Resolver::Start without family arg.
+ EXPECT_CALL(*resolver, Start(kValidHostnameAddr, _))
+ .WillOnce(InvokeArgument<1>());
+ EXPECT_CALL(*resolver, result)
+ .WillRepeatedly(ReturnPointee(resolver_result));
+ EXPECT_CALL(*resolver_result, GetError).WillOnce(Return(0));
+ EXPECT_CALL(*resolver_result, GetResolvedAddress(AF_INET6, _))
+ .WillOnce(DoAll(SetArgPointee<1>(SocketAddress("::1", 5000)),
+ Return(true)));
+ });
+ CreateStunPort(kValidHostnameAddr);
+ PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(done(), kTimeoutMs, fake_clock);
+ ASSERT_EQ(1U, port()->Candidates().size());
+ EXPECT_TRUE(kIPv6LocalAddr.EqualIPs(port()->Candidates()[0].address()));
+ EXPECT_EQ(kIPv6StunCandidatePriority, port()->Candidates()[0].priority());
+}
+
+TEST_F(StunIPv6PortTestWithMockDnsResolver,
+ TestPrepareAddressHostnameFamilyFieldTrialDisabled) {
+ webrtc::test::ScopedKeyValueConfig field_trials(
+ "WebRTC-IPv6NetworkResolutionFixes/Disabled/");
+ SetDnsResolverExpectations(
+ [](webrtc::MockAsyncDnsResolver* resolver,
+ webrtc::MockAsyncDnsResolverResult* resolver_result) {
+ // Expect to call Resolver::Start without family arg.
+ EXPECT_CALL(*resolver, Start(kValidHostnameAddr, _))
+ .WillOnce(InvokeArgument<1>());
+ EXPECT_CALL(*resolver, result)
+ .WillRepeatedly(ReturnPointee(resolver_result));
+ EXPECT_CALL(*resolver_result, GetError).WillOnce(Return(0));
+ EXPECT_CALL(*resolver_result, GetResolvedAddress(AF_INET6, _))
+ .WillOnce(DoAll(SetArgPointee<1>(SocketAddress("::1", 5000)),
+ Return(true)));
+ });
+ CreateStunPort(kValidHostnameAddr, &field_trials);
+ PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(done(), kTimeoutMs, fake_clock);
+ ASSERT_EQ(1U, port()->Candidates().size());
+ EXPECT_TRUE(kIPv6LocalAddr.EqualIPs(port()->Candidates()[0].address()));
+ EXPECT_EQ(kIPv6StunCandidatePriority, port()->Candidates()[0].priority());
+}
+
+TEST_F(StunIPv6PortTestWithMockDnsResolver,
+ TestPrepareAddressHostnameFamilyFieldTrialParamDisabled) {
+ webrtc::test::ScopedKeyValueConfig field_trials(
+ "WebRTC-IPv6NetworkResolutionFixes/"
+ "Enabled,ResolveStunHostnameForFamily:false/");
+ SetDnsResolverExpectations(
+ [](webrtc::MockAsyncDnsResolver* resolver,
+ webrtc::MockAsyncDnsResolverResult* resolver_result) {
+ // Expect to call Resolver::Start without family arg.
+ EXPECT_CALL(*resolver, Start(kValidHostnameAddr, _))
+ .WillOnce(InvokeArgument<1>());
+ EXPECT_CALL(*resolver, result)
+ .WillRepeatedly(ReturnPointee(resolver_result));
+ EXPECT_CALL(*resolver_result, GetError).WillOnce(Return(0));
+ EXPECT_CALL(*resolver_result, GetResolvedAddress(AF_INET6, _))
+ .WillOnce(DoAll(SetArgPointee<1>(SocketAddress("::1", 5000)),
+ Return(true)));
+ });
+ CreateStunPort(kValidHostnameAddr, &field_trials);
+ PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(done(), kTimeoutMs, fake_clock);
+ ASSERT_EQ(1U, port()->Candidates().size());
+ EXPECT_TRUE(kIPv6LocalAddr.EqualIPs(port()->Candidates()[0].address()));
+ EXPECT_EQ(kIPv6StunCandidatePriority, port()->Candidates()[0].priority());
+}
+
+TEST_F(StunIPv6PortTestWithMockDnsResolver,
+ TestPrepareAddressHostnameFamilyFieldTrialEnabled) {
+ webrtc::test::ScopedKeyValueConfig field_trials(
+ "WebRTC-IPv6NetworkResolutionFixes/"
+ "Enabled,ResolveStunHostnameForFamily:true/");
+ SetDnsResolverExpectations(
+ [](webrtc::MockAsyncDnsResolver* resolver,
+ webrtc::MockAsyncDnsResolverResult* resolver_result) {
+ // Expect to call Resolver::Start _with_ family arg.
+ EXPECT_CALL(*resolver,
+ Start(kValidHostnameAddr, /*family=*/AF_INET6, _))
+ .WillOnce(InvokeArgument<2>());
+ EXPECT_CALL(*resolver, result)
+ .WillRepeatedly(ReturnPointee(resolver_result));
+ EXPECT_CALL(*resolver_result, GetError).WillOnce(Return(0));
+ EXPECT_CALL(*resolver_result, GetResolvedAddress(AF_INET6, _))
+ .WillOnce(DoAll(SetArgPointee<1>(SocketAddress("::1", 5000)),
+ Return(true)));
+ });
+ CreateStunPort(kValidHostnameAddr, &field_trials);
+ PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(done(), kTimeoutMs, fake_clock);
+ ASSERT_EQ(1U, port()->Candidates().size());
+ EXPECT_TRUE(kIPv6LocalAddr.EqualIPs(port()->Candidates()[0].address()));
+ EXPECT_EQ(kIPv6StunCandidatePriority, port()->Candidates()[0].priority());
+}
+
+} // namespace
diff --git a/third_party/libwebrtc/p2p/base/stun_request.cc b/third_party/libwebrtc/p2p/base/stun_request.cc
new file mode 100644
index 0000000000..d15a3e65e2
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/stun_request.cc
@@ -0,0 +1,310 @@
+/*
+ * 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());
+ 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.
+ // 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 (skip_integrity_checking) {
+ // This indicates lazy test writing (not adding integrity attribute).
+ // Complain, but only in debug mode (while developing).
+ RTC_DLOG(LS_ERROR)
+ << "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();
+ }
+ }
+
+ bool success = true;
+
+ 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.";
+ success = false;
+ } else if (msg->type() == GetStunSuccessResponseType(request->type())) {
+ if (!msg->IntegrityOk() && !skip_integrity_checking) {
+ return false;
+ }
+ request->OnResponse(msg);
+ } else if (msg->type() == GetStunErrorResponseType(request->type())) {
+ request->OnErrorResponse(msg);
+ } else {
+ RTC_LOG(LS_ERROR) << "Received response with wrong type: " << msg->type()
+ << " (expecting "
+ << GetStunSuccessResponseType(request->type()) << ")";
+ return false;
+ }
+
+ requests_.erase(iter);
+ return success;
+}
+
+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(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
diff --git a/third_party/libwebrtc/p2p/base/stun_request.h b/third_party/libwebrtc/p2p/base/stun_request.h
new file mode 100644
index 0000000000..6e83be3830
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/stun_request.h
@@ -0,0 +1,162 @@
+/*
+ * 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;
+
+ 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()};
+};
+
+} // namespace cricket
+
+#endif // P2P_BASE_STUN_REQUEST_H_
diff --git a/third_party/libwebrtc/p2p/base/stun_request_unittest.cc b/third_party/libwebrtc/p2p/base/stun_request_unittest.cc
new file mode 100644
index 0000000000..6831d9ffa2
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/stun_request_unittest.cc
@@ -0,0 +1,219 @@
+/*
+ * 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 <utility>
+#include <vector>
+
+#include "rtc_base/fake_clock.h"
+#include "rtc_base/gunit.h"
+#include "rtc_base/helpers.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/time_utils.h"
+#include "test/gtest.h"
+
+namespace cricket {
+namespace {
+std::unique_ptr<StunMessage> CreateStunMessage(
+ StunMessageType type,
+ const StunMessage* req = nullptr) {
+ std::unique_ptr<StunMessage> msg = std::make_unique<StunMessage>(
+ type, req ? req->transaction_id() : StunMessage::GenerateTransactionId());
+ return msg;
+}
+
+int TotalDelay(int sends) {
+ std::vector<int> delays = {0, 250, 750, 1750, 3750,
+ 7750, 15750, 23750, 31750, 39750};
+ return delays[sends];
+}
+} // namespace
+
+class StunRequestTest : public ::testing::Test {
+ public:
+ StunRequestTest()
+ : manager_(rtc::Thread::Current(),
+ [this](const void* data, size_t size, StunRequest* request) {
+ OnSendPacket(data, size, request);
+ }),
+ request_count_(0),
+ response_(NULL),
+ success_(false),
+ failure_(false),
+ timeout_(false) {}
+
+ void OnSendPacket(const void* data, size_t size, StunRequest* req) {
+ request_count_++;
+ }
+
+ void OnResponse(StunMessage* res) {
+ response_ = res;
+ success_ = true;
+ }
+ void OnErrorResponse(StunMessage* res) {
+ response_ = res;
+ failure_ = true;
+ }
+ void OnTimeout() { timeout_ = true; }
+
+ protected:
+ rtc::AutoThread main_thread_;
+ StunRequestManager manager_;
+ int request_count_;
+ StunMessage* response_;
+ bool success_;
+ bool failure_;
+ bool timeout_;
+};
+
+// Forwards results to the test class.
+class StunRequestThunker : public StunRequest {
+ public:
+ StunRequestThunker(StunRequestManager& manager, StunRequestTest* test)
+ : StunRequest(manager, CreateStunMessage(STUN_BINDING_REQUEST)),
+ test_(test) {}
+
+ std::unique_ptr<StunMessage> CreateResponseMessage(StunMessageType type) {
+ return CreateStunMessage(type, msg());
+ }
+
+ private:
+ virtual void OnResponse(StunMessage* res) { test_->OnResponse(res); }
+ virtual void OnErrorResponse(StunMessage* res) {
+ test_->OnErrorResponse(res);
+ }
+ virtual void OnTimeout() { test_->OnTimeout(); }
+
+ StunRequestTest* test_;
+};
+
+// Test handling of a normal binding response.
+TEST_F(StunRequestTest, TestSuccess) {
+ auto* request = new StunRequestThunker(manager_, this);
+ std::unique_ptr<StunMessage> res =
+ request->CreateResponseMessage(STUN_BINDING_RESPONSE);
+ manager_.Send(request);
+ EXPECT_TRUE(manager_.CheckResponse(res.get()));
+
+ EXPECT_TRUE(response_ == res.get());
+ EXPECT_TRUE(success_);
+ EXPECT_FALSE(failure_);
+ EXPECT_FALSE(timeout_);
+}
+
+// Test handling of an error binding response.
+TEST_F(StunRequestTest, TestError) {
+ auto* request = new StunRequestThunker(manager_, this);
+ std::unique_ptr<StunMessage> res =
+ request->CreateResponseMessage(STUN_BINDING_ERROR_RESPONSE);
+ manager_.Send(request);
+ EXPECT_TRUE(manager_.CheckResponse(res.get()));
+
+ EXPECT_TRUE(response_ == res.get());
+ EXPECT_FALSE(success_);
+ EXPECT_TRUE(failure_);
+ EXPECT_FALSE(timeout_);
+}
+
+// Test handling of a binding response with the wrong transaction id.
+TEST_F(StunRequestTest, TestUnexpected) {
+ auto* request = new StunRequestThunker(manager_, this);
+ std::unique_ptr<StunMessage> res = CreateStunMessage(STUN_BINDING_RESPONSE);
+
+ manager_.Send(request);
+ EXPECT_FALSE(manager_.CheckResponse(res.get()));
+
+ EXPECT_TRUE(response_ == NULL);
+ EXPECT_FALSE(success_);
+ EXPECT_FALSE(failure_);
+ EXPECT_FALSE(timeout_);
+}
+
+// Test that requests are sent at the right times.
+TEST_F(StunRequestTest, TestBackoff) {
+ rtc::ScopedFakeClock fake_clock;
+ auto* request = new StunRequestThunker(manager_, this);
+ std::unique_ptr<StunMessage> res =
+ request->CreateResponseMessage(STUN_BINDING_RESPONSE);
+
+ int64_t start = rtc::TimeMillis();
+ manager_.Send(request);
+ for (int i = 0; i < 9; ++i) {
+ EXPECT_TRUE_SIMULATED_WAIT(request_count_ != i, STUN_TOTAL_TIMEOUT,
+ fake_clock);
+ int64_t elapsed = rtc::TimeMillis() - start;
+ RTC_DLOG(LS_INFO) << "STUN request #" << (i + 1) << " sent at " << elapsed
+ << " ms";
+ EXPECT_EQ(TotalDelay(i), elapsed);
+ }
+ EXPECT_TRUE(manager_.CheckResponse(res.get()));
+
+ EXPECT_TRUE(response_ == res.get());
+ EXPECT_TRUE(success_);
+ EXPECT_FALSE(failure_);
+ EXPECT_FALSE(timeout_);
+}
+
+// Test that we timeout properly if no response is received.
+TEST_F(StunRequestTest, TestTimeout) {
+ rtc::ScopedFakeClock fake_clock;
+ auto* request = new StunRequestThunker(manager_, this);
+ std::unique_ptr<StunMessage> res =
+ request->CreateResponseMessage(STUN_BINDING_RESPONSE);
+
+ manager_.Send(request);
+ SIMULATED_WAIT(false, cricket::STUN_TOTAL_TIMEOUT, fake_clock);
+
+ EXPECT_FALSE(manager_.CheckResponse(res.get()));
+ EXPECT_TRUE(response_ == NULL);
+ EXPECT_FALSE(success_);
+ EXPECT_FALSE(failure_);
+ EXPECT_TRUE(timeout_);
+}
+
+// Regression test for specific crash where we receive a response with the
+// same id as a request that doesn't have an underlying StunMessage yet.
+TEST_F(StunRequestTest, TestNoEmptyRequest) {
+ StunRequestThunker* request = new StunRequestThunker(manager_, this);
+
+ manager_.SendDelayed(request, 100);
+
+ StunMessage dummy_req(0, request->id());
+ std::unique_ptr<StunMessage> res =
+ CreateStunMessage(STUN_BINDING_RESPONSE, &dummy_req);
+
+ EXPECT_TRUE(manager_.CheckResponse(res.get()));
+
+ EXPECT_TRUE(response_ == res.get());
+ EXPECT_TRUE(success_);
+ EXPECT_FALSE(failure_);
+ EXPECT_FALSE(timeout_);
+}
+
+// If the response contains an attribute in the "comprehension required" range
+// which is not recognized, the transaction should be considered a failure and
+// the response should be ignored.
+TEST_F(StunRequestTest, TestUnrecognizedComprehensionRequiredAttribute) {
+ auto* request = new StunRequestThunker(manager_, this);
+ std::unique_ptr<StunMessage> res =
+ request->CreateResponseMessage(STUN_BINDING_ERROR_RESPONSE);
+
+ manager_.Send(request);
+ res->AddAttribute(StunAttribute::CreateUInt32(0x7777));
+ EXPECT_FALSE(manager_.CheckResponse(res.get()));
+
+ EXPECT_EQ(nullptr, response_);
+ EXPECT_FALSE(success_);
+ EXPECT_FALSE(failure_);
+ EXPECT_FALSE(timeout_);
+}
+
+} // namespace cricket
diff --git a/third_party/libwebrtc/p2p/base/stun_server.cc b/third_party/libwebrtc/p2p/base/stun_server.cc
new file mode 100644
index 0000000000..7827a0bb81
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/stun_server.cc
@@ -0,0 +1,104 @@
+/*
+ * 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 "rtc_base/byte_buffer.h"
+#include "rtc_base/logging.h"
+
+namespace cricket {
+
+StunServer::StunServer(rtc::AsyncUDPSocket* socket) : socket_(socket) {
+ socket_->SignalReadPacket.connect(this, &StunServer::OnPacket);
+}
+
+StunServer::~StunServer() {
+ socket_->SignalReadPacket.disconnect(this);
+}
+
+void StunServer::OnPacket(rtc::AsyncPacketSocket* socket,
+ const char* buf,
+ size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const int64_t& /* packet_time_us */) {
+ // Parse the STUN message; eat any messages that fail to parse.
+ rtc::ByteBufferReader bbuf(buf, size);
+ 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, remote_addr);
+ break;
+
+ default:
+ SendErrorResponse(msg, remote_addr, 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
diff --git a/third_party/libwebrtc/p2p/base/stun_server.h b/third_party/libwebrtc/p2p/base/stun_server.h
new file mode 100644
index 0000000000..505773b052
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/stun_server.h
@@ -0,0 +1,72 @@
+/*
+ * 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 <stdint.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"
+#include "rtc_base/third_party/sigslot/sigslot.h"
+
+namespace cricket {
+
+const int STUN_SERVER_PORT = 3478;
+
+class StunServer : public sigslot::has_slots<> {
+ 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.
+ ~StunServer() override;
+
+ protected:
+ // Slot for Socket.PacketRead:
+ void OnPacket(rtc::AsyncPacketSocket* socket,
+ const char* buf,
+ size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const int64_t& packet_time_us);
+
+ // 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:
+ std::unique_ptr<rtc::AsyncUDPSocket> socket_;
+};
+
+} // namespace cricket
+
+#endif // P2P_BASE_STUN_SERVER_H_
diff --git a/third_party/libwebrtc/p2p/base/stun_server_unittest.cc b/third_party/libwebrtc/p2p/base/stun_server_unittest.cc
new file mode 100644
index 0000000000..5d3f31fb98
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/stun_server_unittest.cc
@@ -0,0 +1,145 @@
+/*
+ * 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.h>
+
+#include <memory>
+#include <string>
+
+#include "absl/memory/memory.h"
+#include "rtc_base/byte_buffer.h"
+#include "rtc_base/ip_address.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/test_client.h"
+#include "rtc_base/thread.h"
+#include "rtc_base/virtual_socket_server.h"
+#include "test/gtest.h"
+
+namespace cricket {
+
+namespace {
+const rtc::SocketAddress server_addr("99.99.99.1", 3478);
+const rtc::SocketAddress client_addr("1.2.3.4", 1234);
+} // namespace
+
+class StunServerTest : public ::testing::Test {
+ public:
+ StunServerTest() : ss_(new rtc::VirtualSocketServer()), network_(ss_.get()) {
+ server_.reset(
+ new StunServer(rtc::AsyncUDPSocket::Create(ss_.get(), server_addr)));
+ client_.reset(new rtc::TestClient(
+ absl::WrapUnique(rtc::AsyncUDPSocket::Create(ss_.get(), client_addr))));
+
+ network_.Start();
+ }
+ ~StunServerTest() override { network_.Stop(); }
+
+ void Send(const StunMessage& msg) {
+ rtc::ByteBufferWriter buf;
+ msg.Write(&buf);
+ Send(buf.Data(), static_cast<int>(buf.Length()));
+ }
+ void Send(const char* buf, int len) {
+ client_->SendTo(buf, len, server_addr);
+ }
+ bool ReceiveFails() { return (client_->CheckNoPacket()); }
+ StunMessage* Receive() {
+ StunMessage* msg = NULL;
+ std::unique_ptr<rtc::TestClient::Packet> packet =
+ client_->NextPacket(rtc::TestClient::kTimeoutMs);
+ if (packet) {
+ rtc::ByteBufferReader buf(packet->buf, packet->size);
+ msg = new StunMessage();
+ msg->Read(&buf);
+ }
+ return msg;
+ }
+
+ private:
+ rtc::AutoThread main_thread;
+ std::unique_ptr<rtc::VirtualSocketServer> ss_;
+ rtc::Thread network_;
+ std::unique_ptr<StunServer> server_;
+ std::unique_ptr<rtc::TestClient> client_;
+};
+
+TEST_F(StunServerTest, TestGood) {
+ // kStunLegacyTransactionIdLength = 16 for legacy RFC 3489 request
+ std::string transaction_id = "0123456789abcdef";
+ StunMessage req(STUN_BINDING_REQUEST, transaction_id);
+ Send(req);
+
+ StunMessage* msg = Receive();
+ ASSERT_TRUE(msg != NULL);
+ EXPECT_EQ(STUN_BINDING_RESPONSE, msg->type());
+ EXPECT_EQ(req.transaction_id(), msg->transaction_id());
+
+ const StunAddressAttribute* mapped_addr =
+ msg->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+ EXPECT_TRUE(mapped_addr != NULL);
+ EXPECT_EQ(1, mapped_addr->family());
+ EXPECT_EQ(client_addr.port(), mapped_addr->port());
+
+ delete msg;
+}
+
+TEST_F(StunServerTest, TestGoodXorMappedAddr) {
+ // kStunTransactionIdLength = 12 for RFC 5389 request
+ // StunMessage::Write will automatically insert magic cookie (0x2112A442)
+ std::string transaction_id = "0123456789ab";
+ StunMessage req(STUN_BINDING_REQUEST, transaction_id);
+ Send(req);
+
+ StunMessage* msg = Receive();
+ ASSERT_TRUE(msg != NULL);
+ EXPECT_EQ(STUN_BINDING_RESPONSE, msg->type());
+ EXPECT_EQ(req.transaction_id(), msg->transaction_id());
+
+ const StunAddressAttribute* mapped_addr =
+ msg->GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+ EXPECT_TRUE(mapped_addr != NULL);
+ EXPECT_EQ(1, mapped_addr->family());
+ EXPECT_EQ(client_addr.port(), mapped_addr->port());
+
+ delete msg;
+}
+
+// Send legacy RFC 3489 request, should not get xor mapped addr
+TEST_F(StunServerTest, TestNoXorMappedAddr) {
+ // kStunLegacyTransactionIdLength = 16 for legacy RFC 3489 request
+ std::string transaction_id = "0123456789abcdef";
+ StunMessage req(STUN_BINDING_REQUEST, transaction_id);
+ Send(req);
+
+ StunMessage* msg = Receive();
+ ASSERT_TRUE(msg != NULL);
+ EXPECT_EQ(STUN_BINDING_RESPONSE, msg->type());
+ EXPECT_EQ(req.transaction_id(), msg->transaction_id());
+
+ const StunAddressAttribute* mapped_addr =
+ msg->GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+ EXPECT_TRUE(mapped_addr == NULL);
+
+ delete msg;
+}
+
+TEST_F(StunServerTest, TestBad) {
+ const char* bad =
+ "this is a completely nonsensical message whose only "
+ "purpose is to make the parser go 'ack'. it doesn't "
+ "look anything like a normal stun message";
+ Send(bad, static_cast<int>(strlen(bad)));
+
+ ASSERT_TRUE(ReceiveFails());
+}
+
+} // namespace cricket
diff --git a/third_party/libwebrtc/p2p/base/tcp_port.cc b/third_party/libwebrtc/p2p/base/tcp_port.cc
new file mode 100644
index 0000000000..fbda2999f9
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/tcp_port.cc
@@ -0,0 +1,620 @@
+/*
+ * 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/rate_tracker.h"
+#include "rtc_base/third_party/sigslot/sigslot.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.type() != PRFLX_PORT_TYPE) ||
+ (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->SignalReadPacket.disconnect(this);
+ 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->SignalReadPacket.connect(this, &TCPPort::OnReadPacket);
+ 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 char* data,
+ size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const int64_t& packet_time_us) {
+ Port::OnReadPacket(data, size, remote_addr, 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_EQ(port()->GetProtocol(), PROTO_TCP); // Needs to be TCPPort.
+ 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_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.
+ socket_->UnsubscribeClose(this);
+ port()->DestroyConnectionAsync(this);
+ }
+}
+
+void TCPConnection::MaybeReconnect() {
+ // 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 char* data,
+ size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const int64_t& packet_time_us) {
+ RTC_DCHECK_EQ(socket, socket_.get());
+ Connection::OnReadPacket(data, size, packet_time_us);
+}
+
+void TCPConnection::OnReadyToSend(rtc::AsyncPacketSocket* socket) {
+ RTC_DCHECK_EQ(socket, socket_.get());
+ Connection::OnReadyToSend();
+}
+
+void TCPConnection::CreateOutgoingTcpSocket() {
+ RTC_DCHECK(outgoing_);
+ int opts = (remote_candidate().protocol() == SSLTCP_PROTOCOL_NAME)
+ ? rtc::PacketSocketFactory::OPT_TLS_FAKE
+ : 0;
+
+ if (socket_) {
+ socket_->UnsubscribeClose(this);
+ }
+
+ 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->SignalReadPacket.connect(this, &TCPConnection::OnReadPacket);
+ socket->SignalReadyToSend.connect(this, &TCPConnection::OnReadyToSend);
+ socket->SubscribeClose(this, [this, safety = network_safety_.flag()](
+ rtc::AsyncPacketSocket* s, int err) {
+ if (safety->alive())
+ OnClose(s, err);
+ });
+}
+
+} // namespace cricket
diff --git a/third_party/libwebrtc/p2p/base/tcp_port.h b/third_party/libwebrtc/p2p/base/tcp_port.h
new file mode 100644
index 0000000000..ff69e6e48b
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/tcp_port.h
@@ -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"
+
+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 char* data,
+ size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const int64_t& packet_time_us);
+
+ void OnSentPacket(rtc::AsyncPacketSocket* socket,
+ const rtc::SentPacket& sent_packet) 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:
+ // Helper function to handle the case when Ping or Send fails with error
+ // related to socket close.
+ void MaybeReconnect();
+
+ void CreateOutgoingTcpSocket();
+
+ void ConnectSocketSignals(rtc::AsyncPacketSocket* socket);
+
+ void OnConnect(rtc::AsyncPacketSocket* socket);
+ void OnClose(rtc::AsyncPacketSocket* socket, int error);
+ void OnReadPacket(rtc::AsyncPacketSocket* socket,
+ const char* data,
+ size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const int64_t& packet_time_us);
+ void OnReadyToSend(rtc::AsyncPacketSocket* socket);
+
+ TCPPort* tcp_port() {
+ RTC_DCHECK_EQ(port()->GetProtocol(), PROTO_TCP);
+ return static_cast<TCPPort*>(port());
+ }
+
+ std::unique_ptr<rtc::AsyncPacketSocket> socket_;
+ int error_;
+ 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_;
+
+ friend class TCPPort;
+};
+
+} // namespace cricket
+
+#endif // P2P_BASE_TCP_PORT_H_
diff --git a/third_party/libwebrtc/p2p/base/tcp_port_unittest.cc b/third_party/libwebrtc/p2p/base/tcp_port_unittest.cc
new file mode 100644
index 0000000000..1bb59811b8
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/tcp_port_unittest.cc
@@ -0,0 +1,259 @@
+/*
+ * 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.
+ */
+
+#include "p2p/base/tcp_port.h"
+
+#include <list>
+#include <memory>
+#include <vector>
+
+#include "p2p/base/basic_packet_socket_factory.h"
+#include "p2p/base/p2p_constants.h"
+#include "p2p/base/transport_description.h"
+#include "rtc_base/gunit.h"
+#include "rtc_base/helpers.h"
+#include "rtc_base/ip_address.h"
+#include "rtc_base/third_party/sigslot/sigslot.h"
+#include "rtc_base/thread.h"
+#include "rtc_base/time_utils.h"
+#include "rtc_base/virtual_socket_server.h"
+#include "test/gtest.h"
+#include "test/scoped_key_value_config.h"
+
+using cricket::Connection;
+using cricket::ICE_PWD_LENGTH;
+using cricket::ICE_UFRAG_LENGTH;
+using cricket::Port;
+using cricket::TCPPort;
+using rtc::SocketAddress;
+
+static int kTimeout = 1000;
+static const SocketAddress kLocalAddr("11.11.11.11", 0);
+static const SocketAddress kLocalIPv6Addr("2401:fa00:4:1000:be30:5bff:fee5:c3",
+ 0);
+static const SocketAddress kAlternateLocalAddr("1.2.3.4", 0);
+static const SocketAddress kRemoteAddr("22.22.22.22", 0);
+static const SocketAddress kRemoteIPv6Addr("2401:fa00:4:1000:be30:5bff:fee5:c4",
+ 0);
+
+constexpr uint64_t kTiebreakerDefault = 44444;
+
+class ConnectionObserver : public sigslot::has_slots<> {
+ public:
+ explicit ConnectionObserver(Connection* conn) : conn_(conn) {
+ conn->SignalDestroyed.connect(this, &ConnectionObserver::OnDestroyed);
+ }
+
+ ~ConnectionObserver() {
+ if (!connection_destroyed_) {
+ RTC_DCHECK(conn_);
+ conn_->SignalDestroyed.disconnect(this);
+ }
+ }
+
+ bool connection_destroyed() { return connection_destroyed_; }
+
+ private:
+ void OnDestroyed(Connection*) { connection_destroyed_ = true; }
+
+ Connection* const conn_;
+ bool connection_destroyed_ = false;
+};
+
+class TCPPortTest : public ::testing::Test, public sigslot::has_slots<> {
+ public:
+ TCPPortTest()
+ : ss_(new rtc::VirtualSocketServer()),
+ main_(ss_.get()),
+ socket_factory_(ss_.get()),
+ username_(rtc::CreateRandomString(ICE_UFRAG_LENGTH)),
+ password_(rtc::CreateRandomString(ICE_PWD_LENGTH)) {}
+
+ rtc::Network* MakeNetwork(const SocketAddress& addr) {
+ networks_.emplace_back("unittest", "unittest", addr.ipaddr(), 32);
+ networks_.back().AddIP(addr.ipaddr());
+ return &networks_.back();
+ }
+
+ std::unique_ptr<TCPPort> CreateTCPPort(const SocketAddress& addr) {
+ auto port = std::unique_ptr<TCPPort>(
+ TCPPort::Create(&main_, &socket_factory_, MakeNetwork(addr), 0, 0,
+ username_, password_, true, &field_trials_));
+ port->SetIceTiebreaker(kTiebreakerDefault);
+ return port;
+ }
+
+ std::unique_ptr<TCPPort> CreateTCPPort(const rtc::Network* network) {
+ auto port = std::unique_ptr<TCPPort>(
+ TCPPort::Create(&main_, &socket_factory_, network, 0, 0, username_,
+ password_, true, &field_trials_));
+ port->SetIceTiebreaker(kTiebreakerDefault);
+ return port;
+ }
+
+ protected:
+ // When a "create port" helper method is called with an IP, we create a
+ // Network with that IP and add it to this list. Using a list instead of a
+ // vector so that when it grows, pointers aren't invalidated.
+ std::list<rtc::Network> networks_;
+ std::unique_ptr<rtc::VirtualSocketServer> ss_;
+ rtc::AutoSocketServerThread main_;
+ rtc::BasicPacketSocketFactory socket_factory_;
+ std::string username_;
+ std::string password_;
+ webrtc::test::ScopedKeyValueConfig field_trials_;
+};
+
+TEST_F(TCPPortTest, TestTCPPortWithLocalhostAddress) {
+ SocketAddress local_address("127.0.0.1", 0);
+ // After calling this, when TCPPort attempts to get a socket bound to
+ // kLocalAddr, it will end up using localhost instead.
+ ss_->SetAlternativeLocalAddress(kLocalAddr.ipaddr(), local_address.ipaddr());
+ auto local_port = CreateTCPPort(kLocalAddr);
+ auto remote_port = CreateTCPPort(kRemoteAddr);
+ local_port->PrepareAddress();
+ remote_port->PrepareAddress();
+ Connection* conn = local_port->CreateConnection(remote_port->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ EXPECT_TRUE_WAIT(conn->connected(), kTimeout);
+ // Verify that the socket actually used localhost, otherwise this test isn't
+ // doing what it meant to.
+ ASSERT_EQ(local_address.ipaddr(),
+ local_port->Candidates()[0].address().ipaddr());
+}
+
+// If the address the socket ends up bound to does not match any address of the
+// TCPPort's Network, then the socket should be discarded and no candidates
+// should be signaled. In the context of ICE, where one TCPPort is created for
+// each Network, when this happens it's likely that the unexpected address is
+// associated with some other Network, which another TCPPort is already
+// covering.
+TEST_F(TCPPortTest, TCPPortDiscardedIfBoundAddressDoesNotMatchNetwork) {
+ // Sockets bound to kLocalAddr will actually end up with kAlternateLocalAddr.
+ ss_->SetAlternativeLocalAddress(kLocalAddr.ipaddr(),
+ kAlternateLocalAddr.ipaddr());
+
+ // Create ports (local_port is the one whose IP will end up reassigned).
+ auto local_port = CreateTCPPort(kLocalAddr);
+ auto remote_port = CreateTCPPort(kRemoteAddr);
+ local_port->PrepareAddress();
+ remote_port->PrepareAddress();
+
+ // Tell port to create a connection; it should be destroyed when it's
+ // realized that it's using an unexpected address.
+ Connection* conn = local_port->CreateConnection(remote_port->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ ConnectionObserver observer(conn);
+ EXPECT_TRUE_WAIT(observer.connection_destroyed(), kTimeout);
+}
+
+// A caveat for the above logic: if the socket ends up bound to one of the IPs
+// associated with the Network, just not the "best" one, this is ok.
+TEST_F(TCPPortTest, TCPPortNotDiscardedIfNotBoundToBestIP) {
+ // Sockets bound to kLocalAddr will actually end up with kAlternateLocalAddr.
+ ss_->SetAlternativeLocalAddress(kLocalAddr.ipaddr(),
+ kAlternateLocalAddr.ipaddr());
+
+ // Set up a network with kLocalAddr1 as the "best" IP, and kAlternateLocalAddr
+ // as an alternate.
+ rtc::Network* network = MakeNetwork(kLocalAddr);
+ network->AddIP(kAlternateLocalAddr.ipaddr());
+ ASSERT_EQ(kLocalAddr.ipaddr(), network->GetBestIP());
+
+ // Create ports (using our special 2-IP Network for local_port).
+ auto local_port = CreateTCPPort(network);
+ auto remote_port = CreateTCPPort(kRemoteAddr);
+ local_port->PrepareAddress();
+ remote_port->PrepareAddress();
+
+ // Expect connection to succeed.
+ Connection* conn = local_port->CreateConnection(remote_port->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ EXPECT_TRUE_WAIT(conn->connected(), kTimeout);
+
+ // Verify that the socket actually used the alternate address, otherwise this
+ // test isn't doing what it meant to.
+ ASSERT_EQ(kAlternateLocalAddr.ipaddr(),
+ local_port->Candidates()[0].address().ipaddr());
+}
+
+// Regression test for crbug.com/webrtc/8972, caused by buggy comparison
+// between rtc::IPAddress and rtc::InterfaceAddress.
+TEST_F(TCPPortTest, TCPPortNotDiscardedIfBoundToTemporaryIP) {
+ networks_.emplace_back("unittest", "unittest", kLocalIPv6Addr.ipaddr(), 32);
+ networks_.back().AddIP(rtc::InterfaceAddress(
+ kLocalIPv6Addr.ipaddr(), rtc::IPV6_ADDRESS_FLAG_TEMPORARY));
+
+ auto local_port = CreateTCPPort(&networks_.back());
+ auto remote_port = CreateTCPPort(kRemoteIPv6Addr);
+ local_port->PrepareAddress();
+ remote_port->PrepareAddress();
+
+ // Connection should succeed if the port isn't discarded.
+ Connection* conn = local_port->CreateConnection(remote_port->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ ASSERT_NE(nullptr, conn);
+ EXPECT_TRUE_WAIT(conn->connected(), kTimeout);
+}
+
+class SentPacketCounter : public sigslot::has_slots<> {
+ public:
+ explicit SentPacketCounter(TCPPort* p) {
+ p->SignalSentPacket.connect(this, &SentPacketCounter::OnSentPacket);
+ }
+
+ int sent_packets() const { return sent_packets_; }
+
+ private:
+ void OnSentPacket(const rtc::SentPacket&) { ++sent_packets_; }
+
+ int sent_packets_ = 0;
+};
+
+// Test that SignalSentPacket is fired when a packet is successfully sent, for
+// both TCP client and server sockets.
+TEST_F(TCPPortTest, SignalSentPacket) {
+ std::unique_ptr<TCPPort> client(CreateTCPPort(kLocalAddr));
+ std::unique_ptr<TCPPort> server(CreateTCPPort(kRemoteAddr));
+ client->SetIceRole(cricket::ICEROLE_CONTROLLING);
+ server->SetIceRole(cricket::ICEROLE_CONTROLLED);
+ client->PrepareAddress();
+ server->PrepareAddress();
+
+ Connection* client_conn =
+ client->CreateConnection(server->Candidates()[0], Port::ORIGIN_MESSAGE);
+ ASSERT_NE(nullptr, client_conn);
+ ASSERT_TRUE_WAIT(client_conn->connected(), kTimeout);
+
+ // Need to get the port of the actual outgoing socket, not the server socket..
+ cricket::Candidate client_candidate = client->Candidates()[0];
+ client_candidate.set_address(static_cast<cricket::TCPConnection*>(client_conn)
+ ->socket()
+ ->GetLocalAddress());
+ Connection* server_conn =
+ server->CreateConnection(client_candidate, Port::ORIGIN_THIS_PORT);
+ ASSERT_NE(nullptr, server_conn);
+ ASSERT_TRUE_WAIT(server_conn->connected(), kTimeout);
+
+ client_conn->Ping(rtc::TimeMillis());
+ server_conn->Ping(rtc::TimeMillis());
+ ASSERT_TRUE_WAIT(client_conn->writable(), kTimeout);
+ ASSERT_TRUE_WAIT(server_conn->writable(), kTimeout);
+
+ SentPacketCounter client_counter(client.get());
+ SentPacketCounter server_counter(server.get());
+ static const char kData[] = "hello";
+ for (int i = 0; i < 10; ++i) {
+ client_conn->Send(&kData, sizeof(kData), rtc::PacketOptions());
+ server_conn->Send(&kData, sizeof(kData), rtc::PacketOptions());
+ }
+ EXPECT_EQ_WAIT(10, client_counter.sent_packets(), kTimeout);
+ EXPECT_EQ_WAIT(10, server_counter.sent_packets(), kTimeout);
+}
diff --git a/third_party/libwebrtc/p2p/base/test_stun_server.cc b/third_party/libwebrtc/p2p/base/test_stun_server.cc
new file mode 100644
index 0000000000..d4c3b2d851
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/test_stun_server.cc
@@ -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.
+ */
+
+#include "p2p/base/test_stun_server.h"
+
+#include "rtc_base/socket.h"
+#include "rtc_base/socket_server.h"
+
+namespace cricket {
+
+TestStunServer* TestStunServer::Create(rtc::SocketServer* ss,
+ const rtc::SocketAddress& addr) {
+ rtc::Socket* socket = ss->CreateSocket(addr.family(), SOCK_DGRAM);
+ rtc::AsyncUDPSocket* udp_socket = rtc::AsyncUDPSocket::Create(socket, addr);
+
+ return new TestStunServer(udp_socket);
+}
+
+void TestStunServer::OnBindingRequest(StunMessage* msg,
+ const rtc::SocketAddress& remote_addr) {
+ 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
diff --git a/third_party/libwebrtc/p2p/base/test_stun_server.h b/third_party/libwebrtc/p2p/base/test_stun_server.h
new file mode 100644
index 0000000000..11ac620bb8
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/test_stun_server.h
@@ -0,0 +1,45 @@
+/*
+ * 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 "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"
+
+namespace cricket {
+
+// A test STUN server. Useful for unit tests.
+class TestStunServer : StunServer {
+ public:
+ static TestStunServer* Create(rtc::SocketServer* ss,
+ const rtc::SocketAddress& addr);
+
+ // Set a fake STUN address to return to the client.
+ void set_fake_stun_addr(const rtc::SocketAddress& addr) {
+ fake_stun_addr_ = addr;
+ }
+
+ private:
+ explicit TestStunServer(rtc::AsyncUDPSocket* socket) : StunServer(socket) {}
+
+ void OnBindingRequest(StunMessage* msg,
+ const rtc::SocketAddress& remote_addr) override;
+
+ private:
+ rtc::SocketAddress fake_stun_addr_;
+};
+
+} // namespace cricket
+
+#endif // P2P_BASE_TEST_STUN_SERVER_H_
diff --git a/third_party/libwebrtc/p2p/base/test_turn_customizer.h b/third_party/libwebrtc/p2p/base/test_turn_customizer.h
new file mode 100644
index 0000000000..415b13fbf2
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/test_turn_customizer.h
@@ -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_
diff --git a/third_party/libwebrtc/p2p/base/test_turn_server.h b/third_party/libwebrtc/p2p/base/test_turn_server.h
new file mode 100644
index 0000000000..4070372db2
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/test_turn_server.h
@@ -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_
diff --git a/third_party/libwebrtc/p2p/base/transport_description.cc b/third_party/libwebrtc/p2p/base/transport_description.cc
new file mode 100644
index 0000000000..f3b1fbb6ea
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/transport_description.cc
@@ -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
diff --git a/third_party/libwebrtc/p2p/base/transport_description.h b/third_party/libwebrtc/p2p/base/transport_description.h
new file mode 100644
index 0000000000..7d28ad52e9
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/transport_description.h
@@ -0,0 +1,154 @@
+/*
+ * 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 {
+
+// SEC_ENABLED and SEC_REQUIRED should only be used if the session
+// was negotiated over TLS, to protect the inline crypto material
+// exchange.
+// SEC_DISABLED: No crypto in outgoing offer, ignore any supplied crypto.
+// SEC_ENABLED: Crypto in outgoing offer and answer (if supplied in offer).
+// SEC_REQUIRED: Crypto in outgoing offer and answer. Fail any offer with absent
+// or unsupported crypto.
+// TODO(deadbeef): Remove this or rename it to something more appropriate, like
+// SdesPolicy.
+enum SecurePolicy { SEC_DISABLED, SEC_ENABLED, SEC_REQUIRED };
+
+// 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_
diff --git a/third_party/libwebrtc/p2p/base/transport_description_factory.cc b/third_party/libwebrtc/p2p/base/transport_description_factory.cc
new file mode 100644
index 0000000000..7eb21da166
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/transport_description_factory.cc
@@ -0,0 +1,151 @@
+/*
+ * 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)
+ : secure_(SEC_DISABLED), 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 trying to establish a secure transport, add a fingerprint.
+ if (secure_ == SEC_ENABLED || secure_ == SEC_REQUIRED) {
+ // 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);
+ }
+
+ // Negotiate security params.
+ if (offer && offer->identity_fingerprint.get()) {
+ // The offer supports DTLS, so answer with DTLS, as long as we support it.
+ if (secure_ == SEC_ENABLED || secure_ == SEC_REQUIRED) {
+ 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();
+ }
+
+ if (!SetSecurityInfo(desc.get(), role)) {
+ return NULL;
+ }
+ }
+ } else if (require_transport_attributes && secure_ == SEC_REQUIRED) {
+ // 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;
+ }
+
+ 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
diff --git a/third_party/libwebrtc/p2p/base/transport_description_factory.h b/third_party/libwebrtc/p2p/base/transport_description_factory.h
new file mode 100644
index 0000000000..11352f88b4
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/transport_description_factory.h
@@ -0,0 +1,91 @@
+/*
+ * 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();
+
+ SecurePolicy secure() const { return secure_; }
+ // The certificate to use when setting up DTLS.
+ const rtc::scoped_refptr<rtc::RTCCertificate>& certificate() const {
+ return certificate_;
+ }
+
+ // Specifies the transport security policy to use.
+ void set_secure(SecurePolicy s) { secure_ = s; }
+ // Specifies the certificate to use (only used when secure != SEC_DISABLED).
+ 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_; }
+
+ private:
+ bool SetSecurityInfo(TransportDescription* description,
+ ConnectionRole role) const;
+
+ SecurePolicy secure_;
+ rtc::scoped_refptr<rtc::RTCCertificate> certificate_;
+ const webrtc::FieldTrialsView& field_trials_;
+};
+
+} // namespace cricket
+
+#endif // P2P_BASE_TRANSPORT_DESCRIPTION_FACTORY_H_
diff --git a/third_party/libwebrtc/p2p/base/transport_description_factory_unittest.cc b/third_party/libwebrtc/p2p/base/transport_description_factory_unittest.cc
new file mode 100644
index 0000000000..0da5b7c294
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/transport_description_factory_unittest.cc
@@ -0,0 +1,406 @@
+/*
+ * 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 <vector>
+
+#include "absl/strings/string_view.h"
+#include "p2p/base/p2p_constants.h"
+#include "p2p/base/transport_description.h"
+#include "rtc_base/copy_on_write_buffer.h"
+#include "rtc_base/fake_ssl_identity.h"
+#include "rtc_base/ssl_certificate.h"
+#include "rtc_base/ssl_fingerprint.h"
+#include "rtc_base/ssl_identity.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+#include "test/scoped_key_value_config.h"
+
+using cricket::TransportDescription;
+using cricket::TransportDescriptionFactory;
+using cricket::TransportOptions;
+using ::testing::Contains;
+using ::testing::Not;
+
+class TransportDescriptionFactoryTest : public ::testing::Test {
+ public:
+ TransportDescriptionFactoryTest()
+ : ice_credentials_({}),
+ f1_(field_trials_),
+ f2_(field_trials_),
+ cert1_(rtc::RTCCertificate::Create(std::unique_ptr<rtc::SSLIdentity>(
+ new rtc::FakeSSLIdentity("User1")))),
+ cert2_(rtc::RTCCertificate::Create(std::unique_ptr<rtc::SSLIdentity>(
+ new rtc::FakeSSLIdentity("User2")))) {}
+
+ void CheckDesc(const TransportDescription* desc,
+ absl::string_view opt,
+ absl::string_view ice_ufrag,
+ absl::string_view ice_pwd,
+ absl::string_view dtls_alg) {
+ ASSERT_TRUE(desc != NULL);
+ EXPECT_EQ(!opt.empty(), desc->HasOption(opt));
+ if (ice_ufrag.empty() && ice_pwd.empty()) {
+ EXPECT_EQ(static_cast<size_t>(cricket::ICE_UFRAG_LENGTH),
+ desc->ice_ufrag.size());
+ EXPECT_EQ(static_cast<size_t>(cricket::ICE_PWD_LENGTH),
+ desc->ice_pwd.size());
+ } else {
+ EXPECT_EQ(ice_ufrag, desc->ice_ufrag);
+ EXPECT_EQ(ice_pwd, desc->ice_pwd);
+ }
+ if (dtls_alg.empty()) {
+ EXPECT_TRUE(desc->identity_fingerprint.get() == NULL);
+ } else {
+ ASSERT_TRUE(desc->identity_fingerprint.get() != NULL);
+ EXPECT_EQ(desc->identity_fingerprint->algorithm, dtls_alg);
+ EXPECT_GT(desc->identity_fingerprint->digest.size(), 0U);
+ }
+ }
+
+ // This test ice restart by doing two offer answer exchanges. On the second
+ // exchange ice is restarted. The test verifies that the ufrag and password
+ // in the offer and answer is changed.
+ // If `dtls` is true, the test verifies that the finger print is not changed.
+ void TestIceRestart(bool dtls) {
+ SetDtls(dtls);
+ cricket::TransportOptions options;
+ // The initial offer / answer exchange.
+ std::unique_ptr<TransportDescription> offer =
+ f1_.CreateOffer(options, NULL, &ice_credentials_);
+ std::unique_ptr<TransportDescription> answer =
+ f2_.CreateAnswer(offer.get(), options, true, NULL, &ice_credentials_);
+
+ // Create an updated offer where we restart ice.
+ options.ice_restart = true;
+ std::unique_ptr<TransportDescription> restart_offer =
+ f1_.CreateOffer(options, offer.get(), &ice_credentials_);
+
+ VerifyUfragAndPasswordChanged(dtls, offer.get(), restart_offer.get());
+
+ // Create a new answer. The transport ufrag and password is changed since
+ // |options.ice_restart == true|
+ std::unique_ptr<TransportDescription> restart_answer = f2_.CreateAnswer(
+ restart_offer.get(), options, true, answer.get(), &ice_credentials_);
+ ASSERT_TRUE(restart_answer.get() != NULL);
+
+ VerifyUfragAndPasswordChanged(dtls, answer.get(), restart_answer.get());
+ }
+
+ void VerifyUfragAndPasswordChanged(bool dtls,
+ const TransportDescription* org_desc,
+ const TransportDescription* restart_desc) {
+ EXPECT_NE(org_desc->ice_pwd, restart_desc->ice_pwd);
+ EXPECT_NE(org_desc->ice_ufrag, restart_desc->ice_ufrag);
+ EXPECT_EQ(static_cast<size_t>(cricket::ICE_UFRAG_LENGTH),
+ restart_desc->ice_ufrag.size());
+ EXPECT_EQ(static_cast<size_t>(cricket::ICE_PWD_LENGTH),
+ restart_desc->ice_pwd.size());
+ // If DTLS is enabled, make sure the finger print is unchanged.
+ if (dtls) {
+ EXPECT_FALSE(
+ org_desc->identity_fingerprint->GetRfc4572Fingerprint().empty());
+ EXPECT_EQ(org_desc->identity_fingerprint->GetRfc4572Fingerprint(),
+ restart_desc->identity_fingerprint->GetRfc4572Fingerprint());
+ }
+ }
+
+ void TestIceRenomination(bool dtls) {
+ SetDtls(dtls);
+
+ cricket::TransportOptions options;
+ // The initial offer / answer exchange.
+ std::unique_ptr<TransportDescription> offer =
+ f1_.CreateOffer(options, nullptr, &ice_credentials_);
+ ASSERT_TRUE(offer);
+ EXPECT_THAT(offer->transport_options, Not(Contains("renomination")));
+
+ std::unique_ptr<TransportDescription> answer = f2_.CreateAnswer(
+ offer.get(), options, true, nullptr, &ice_credentials_);
+ ASSERT_TRUE(answer);
+ EXPECT_THAT(answer->transport_options, Not(Contains("renomination")));
+
+ options.enable_ice_renomination = true;
+ std::unique_ptr<TransportDescription> renomination_offer =
+ f1_.CreateOffer(options, offer.get(), &ice_credentials_);
+ ASSERT_TRUE(renomination_offer);
+ EXPECT_THAT(renomination_offer->transport_options,
+ Contains("renomination"));
+
+ std::unique_ptr<TransportDescription> renomination_answer =
+ f2_.CreateAnswer(renomination_offer.get(), options, true, answer.get(),
+ &ice_credentials_);
+ ASSERT_TRUE(renomination_answer);
+ EXPECT_THAT(renomination_answer->transport_options,
+ Contains("renomination"));
+ }
+
+ protected:
+ void SetDtls(bool dtls) {
+ if (dtls) {
+ f1_.set_secure(cricket::SEC_ENABLED);
+ f2_.set_secure(cricket::SEC_ENABLED);
+ f1_.set_certificate(cert1_);
+ f2_.set_certificate(cert2_);
+ } else {
+ f1_.set_secure(cricket::SEC_DISABLED);
+ f2_.set_secure(cricket::SEC_DISABLED);
+ }
+ }
+
+ webrtc::test::ScopedKeyValueConfig field_trials_;
+ cricket::IceCredentialsIterator ice_credentials_;
+ TransportDescriptionFactory f1_;
+ TransportDescriptionFactory f2_;
+
+ rtc::scoped_refptr<rtc::RTCCertificate> cert1_;
+ rtc::scoped_refptr<rtc::RTCCertificate> cert2_;
+};
+
+TEST_F(TransportDescriptionFactoryTest, TestOfferDefault) {
+ std::unique_ptr<TransportDescription> desc =
+ f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_);
+ CheckDesc(desc.get(), "", "", "", "");
+}
+
+TEST_F(TransportDescriptionFactoryTest, TestOfferDtls) {
+ f1_.set_secure(cricket::SEC_ENABLED);
+ f1_.set_certificate(cert1_);
+ std::string digest_alg;
+ ASSERT_TRUE(
+ cert1_->GetSSLCertificate().GetSignatureDigestAlgorithm(&digest_alg));
+ std::unique_ptr<TransportDescription> desc =
+ f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_);
+ CheckDesc(desc.get(), "", "", "", digest_alg);
+ // Ensure it also works with SEC_REQUIRED.
+ f1_.set_secure(cricket::SEC_REQUIRED);
+ desc = f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_);
+ CheckDesc(desc.get(), "", "", "", digest_alg);
+}
+
+// Test generating an offer with DTLS fails with no identity.
+TEST_F(TransportDescriptionFactoryTest, TestOfferDtlsWithNoIdentity) {
+ f1_.set_secure(cricket::SEC_ENABLED);
+ std::unique_ptr<TransportDescription> desc =
+ f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_);
+ ASSERT_TRUE(desc.get() == NULL);
+}
+
+// Test updating an offer with DTLS to pick ICE.
+// The ICE credentials should stay the same in the new offer.
+TEST_F(TransportDescriptionFactoryTest, TestOfferDtlsReofferDtls) {
+ f1_.set_secure(cricket::SEC_ENABLED);
+ f1_.set_certificate(cert1_);
+ std::string digest_alg;
+ ASSERT_TRUE(
+ cert1_->GetSSLCertificate().GetSignatureDigestAlgorithm(&digest_alg));
+ std::unique_ptr<TransportDescription> old_desc =
+ f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_);
+ ASSERT_TRUE(old_desc.get() != NULL);
+ std::unique_ptr<TransportDescription> desc =
+ f1_.CreateOffer(TransportOptions(), old_desc.get(), &ice_credentials_);
+ CheckDesc(desc.get(), "", old_desc->ice_ufrag, old_desc->ice_pwd, digest_alg);
+}
+
+TEST_F(TransportDescriptionFactoryTest, TestAnswerDefault) {
+ std::unique_ptr<TransportDescription> offer =
+ f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_);
+ ASSERT_TRUE(offer.get() != NULL);
+ std::unique_ptr<TransportDescription> desc = f2_.CreateAnswer(
+ offer.get(), TransportOptions(), true, NULL, &ice_credentials_);
+ CheckDesc(desc.get(), "", "", "", "");
+ desc = f2_.CreateAnswer(offer.get(), TransportOptions(), true, NULL,
+ &ice_credentials_);
+ CheckDesc(desc.get(), "", "", "", "");
+}
+
+// Test that we can update an answer properly; ICE credentials shouldn't change.
+TEST_F(TransportDescriptionFactoryTest, TestReanswer) {
+ std::unique_ptr<TransportDescription> offer =
+ f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_);
+ ASSERT_TRUE(offer.get() != NULL);
+ std::unique_ptr<TransportDescription> old_desc = f2_.CreateAnswer(
+ offer.get(), TransportOptions(), true, NULL, &ice_credentials_);
+ ASSERT_TRUE(old_desc.get() != NULL);
+ std::unique_ptr<TransportDescription> desc = f2_.CreateAnswer(
+ offer.get(), TransportOptions(), true, old_desc.get(), &ice_credentials_);
+ ASSERT_TRUE(desc.get() != NULL);
+ CheckDesc(desc.get(), "", old_desc->ice_ufrag, old_desc->ice_pwd, "");
+}
+
+// Test that we handle answering an offer with DTLS with no DTLS.
+TEST_F(TransportDescriptionFactoryTest, TestAnswerDtlsToNoDtls) {
+ f1_.set_secure(cricket::SEC_ENABLED);
+ f1_.set_certificate(cert1_);
+ std::unique_ptr<TransportDescription> offer =
+ f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_);
+ ASSERT_TRUE(offer.get() != NULL);
+ std::unique_ptr<TransportDescription> desc = f2_.CreateAnswer(
+ offer.get(), TransportOptions(), true, NULL, &ice_credentials_);
+ CheckDesc(desc.get(), "", "", "", "");
+}
+
+// Test that we handle answering an offer without DTLS if we have DTLS enabled,
+// but fail if we require DTLS.
+TEST_F(TransportDescriptionFactoryTest, TestAnswerNoDtlsToDtls) {
+ f2_.set_secure(cricket::SEC_ENABLED);
+ f2_.set_certificate(cert2_);
+ std::unique_ptr<TransportDescription> offer =
+ f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_);
+ ASSERT_TRUE(offer.get() != NULL);
+ std::unique_ptr<TransportDescription> desc = f2_.CreateAnswer(
+ offer.get(), TransportOptions(), true, NULL, &ice_credentials_);
+ CheckDesc(desc.get(), "", "", "", "");
+ f2_.set_secure(cricket::SEC_REQUIRED);
+ desc = f2_.CreateAnswer(offer.get(), TransportOptions(), true, NULL,
+ &ice_credentials_);
+ ASSERT_TRUE(desc.get() == NULL);
+}
+
+// Test that we handle answering an DTLS offer with DTLS, both if we have
+// DTLS enabled and required.
+TEST_F(TransportDescriptionFactoryTest, TestAnswerDtlsToDtls) {
+ f1_.set_secure(cricket::SEC_ENABLED);
+ f1_.set_certificate(cert1_);
+
+ f2_.set_secure(cricket::SEC_ENABLED);
+ f2_.set_certificate(cert2_);
+ // f2_ produces the answer that is being checked in this test, so the
+ // answer must contain fingerprint lines with cert2_'s digest algorithm.
+ std::string digest_alg2;
+ ASSERT_TRUE(
+ cert2_->GetSSLCertificate().GetSignatureDigestAlgorithm(&digest_alg2));
+
+ std::unique_ptr<TransportDescription> offer =
+ f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_);
+ ASSERT_TRUE(offer.get() != NULL);
+ std::unique_ptr<TransportDescription> desc = f2_.CreateAnswer(
+ offer.get(), TransportOptions(), true, NULL, &ice_credentials_);
+ CheckDesc(desc.get(), "", "", "", digest_alg2);
+ f2_.set_secure(cricket::SEC_REQUIRED);
+ desc = f2_.CreateAnswer(offer.get(), TransportOptions(), true, NULL,
+ &ice_credentials_);
+ CheckDesc(desc.get(), "", "", "", digest_alg2);
+}
+
+// Test that ice ufrag and password is changed in an updated offer and answer
+// if `TransportDescriptionOptions::ice_restart` is true.
+TEST_F(TransportDescriptionFactoryTest, TestIceRestart) {
+ TestIceRestart(false);
+}
+
+// Test that ice ufrag and password is changed in an updated offer and answer
+// if `TransportDescriptionOptions::ice_restart` is true and DTLS is enabled.
+TEST_F(TransportDescriptionFactoryTest, TestIceRestartWithDtls) {
+ TestIceRestart(true);
+}
+
+// Test that ice renomination is set in an updated offer and answer
+// if `TransportDescriptionOptions::enable_ice_renomination` is true.
+TEST_F(TransportDescriptionFactoryTest, TestIceRenomination) {
+ TestIceRenomination(false);
+}
+
+// Test that ice renomination is set in an updated offer and answer
+// if `TransportDescriptionOptions::enable_ice_renomination` is true and DTLS
+// is enabled.
+TEST_F(TransportDescriptionFactoryTest, TestIceRenominationWithDtls) {
+ TestIceRenomination(true);
+}
+
+// Test that offers and answers have ice-option:trickle.
+TEST_F(TransportDescriptionFactoryTest, AddsTrickleIceOption) {
+ cricket::TransportOptions options;
+ std::unique_ptr<TransportDescription> offer =
+ f1_.CreateOffer(options, nullptr, &ice_credentials_);
+ EXPECT_TRUE(offer->HasOption("trickle"));
+ std::unique_ptr<TransportDescription> answer =
+ f2_.CreateAnswer(offer.get(), options, true, nullptr, &ice_credentials_);
+ EXPECT_TRUE(answer->HasOption("trickle"));
+}
+
+// Test CreateOffer with IceCredentialsIterator.
+TEST_F(TransportDescriptionFactoryTest, CreateOfferIceCredentialsIterator) {
+ std::vector<cricket::IceParameters> credentials = {
+ cricket::IceParameters("kalle", "anka", false)};
+ cricket::IceCredentialsIterator credentialsIterator(credentials);
+ cricket::TransportOptions options;
+ std::unique_ptr<TransportDescription> offer =
+ f1_.CreateOffer(options, nullptr, &credentialsIterator);
+ EXPECT_EQ(offer->GetIceParameters().ufrag, credentials[0].ufrag);
+ EXPECT_EQ(offer->GetIceParameters().pwd, credentials[0].pwd);
+}
+
+// Test CreateAnswer with IceCredentialsIterator.
+TEST_F(TransportDescriptionFactoryTest, CreateAnswerIceCredentialsIterator) {
+ cricket::TransportOptions options;
+ std::unique_ptr<TransportDescription> offer =
+ f1_.CreateOffer(options, nullptr, &ice_credentials_);
+
+ std::vector<cricket::IceParameters> credentials = {
+ cricket::IceParameters("kalle", "anka", false)};
+ cricket::IceCredentialsIterator credentialsIterator(credentials);
+ std::unique_ptr<TransportDescription> answer = f1_.CreateAnswer(
+ offer.get(), options, false, nullptr, &credentialsIterator);
+ EXPECT_EQ(answer->GetIceParameters().ufrag, credentials[0].ufrag);
+ EXPECT_EQ(answer->GetIceParameters().pwd, credentials[0].pwd);
+}
+
+TEST_F(TransportDescriptionFactoryTest, CreateAnswerToDtlsActpassOffer) {
+ f1_.set_secure(cricket::SEC_ENABLED);
+ f1_.set_certificate(cert1_);
+
+ f2_.set_secure(cricket::SEC_ENABLED);
+ f2_.set_certificate(cert2_);
+ cricket::TransportOptions options;
+ std::unique_ptr<TransportDescription> offer =
+ f1_.CreateOffer(options, nullptr, &ice_credentials_);
+
+ std::unique_ptr<TransportDescription> answer =
+ f2_.CreateAnswer(offer.get(), options, false, nullptr, &ice_credentials_);
+ EXPECT_EQ(answer->connection_role, cricket::CONNECTIONROLE_ACTIVE);
+}
+
+TEST_F(TransportDescriptionFactoryTest, CreateAnswerToDtlsActiveOffer) {
+ f1_.set_secure(cricket::SEC_ENABLED);
+ f1_.set_certificate(cert1_);
+
+ f2_.set_secure(cricket::SEC_ENABLED);
+ f2_.set_certificate(cert2_);
+ cricket::TransportOptions options;
+ std::unique_ptr<TransportDescription> offer =
+ f1_.CreateOffer(options, nullptr, &ice_credentials_);
+ offer->connection_role = cricket::CONNECTIONROLE_ACTIVE;
+
+ std::unique_ptr<TransportDescription> answer =
+ f2_.CreateAnswer(offer.get(), options, false, nullptr, &ice_credentials_);
+ EXPECT_EQ(answer->connection_role, cricket::CONNECTIONROLE_PASSIVE);
+}
+
+TEST_F(TransportDescriptionFactoryTest, CreateAnswerToDtlsPassiveOffer) {
+ f1_.set_secure(cricket::SEC_ENABLED);
+ f1_.set_certificate(cert1_);
+
+ f2_.set_secure(cricket::SEC_ENABLED);
+ f2_.set_certificate(cert2_);
+ cricket::TransportOptions options;
+ std::unique_ptr<TransportDescription> offer =
+ f1_.CreateOffer(options, nullptr, &ice_credentials_);
+ offer->connection_role = cricket::CONNECTIONROLE_PASSIVE;
+
+ std::unique_ptr<TransportDescription> answer =
+ f2_.CreateAnswer(offer.get(), options, false, nullptr, &ice_credentials_);
+ EXPECT_EQ(answer->connection_role, cricket::CONNECTIONROLE_ACTIVE);
+}
diff --git a/third_party/libwebrtc/p2p/base/transport_description_unittest.cc b/third_party/libwebrtc/p2p/base/transport_description_unittest.cc
new file mode 100644
index 0000000000..41d7336ff6
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/transport_description_unittest.cc
@@ -0,0 +1,58 @@
+/*
+ * 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/transport_description.h"
+#include "test/gtest.h"
+
+using webrtc::RTCErrorType;
+
+namespace cricket {
+
+TEST(IceParameters, SuccessfulParse) {
+ auto result = IceParameters::Parse("ufrag", "22+characters+long+pwd");
+ ASSERT_TRUE(result.ok());
+ IceParameters parameters = result.MoveValue();
+ EXPECT_EQ("ufrag", parameters.ufrag);
+ EXPECT_EQ("22+characters+long+pwd", parameters.pwd);
+}
+
+TEST(IceParameters, FailedParseShortUfrag) {
+ auto result = IceParameters::Parse("3ch", "22+characters+long+pwd");
+ EXPECT_EQ(RTCErrorType::SYNTAX_ERROR, result.error().type());
+}
+
+TEST(IceParameters, FailedParseLongUfrag) {
+ std::string ufrag(257, '+');
+ auto result = IceParameters::Parse(ufrag, "22+characters+long+pwd");
+ EXPECT_EQ(RTCErrorType::SYNTAX_ERROR, result.error().type());
+}
+
+TEST(IceParameters, FailedParseShortPwd) {
+ auto result = IceParameters::Parse("ufrag", "21+character+long+pwd");
+ EXPECT_EQ(RTCErrorType::SYNTAX_ERROR, result.error().type());
+}
+
+TEST(IceParameters, FailedParseLongPwd) {
+ std::string pwd(257, '+');
+ auto result = IceParameters::Parse("ufrag", pwd);
+ EXPECT_EQ(RTCErrorType::SYNTAX_ERROR, result.error().type());
+}
+
+TEST(IceParameters, FailedParseBadUfragChar) {
+ auto result = IceParameters::Parse("ufrag\r\n", "22+characters+long+pwd");
+ EXPECT_EQ(RTCErrorType::SYNTAX_ERROR, result.error().type());
+}
+
+TEST(IceParameters, FailedParseBadPwdChar) {
+ auto result = IceParameters::Parse("ufrag", "22+characters+long+pwd\r\n");
+ EXPECT_EQ(RTCErrorType::SYNTAX_ERROR, result.error().type());
+}
+
+} // namespace cricket
diff --git a/third_party/libwebrtc/p2p/base/transport_info.h b/third_party/libwebrtc/p2p/base/transport_info.h
new file mode 100644
index 0000000000..1f60b64012
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/transport_info.h
@@ -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_
diff --git a/third_party/libwebrtc/p2p/base/turn_port.cc b/third_party/libwebrtc/p2p/base/turn_port.cc
new file mode 100644
index 0000000000..089910e072
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/turn_port.cc
@@ -0,0 +1,1900 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "p2p/base/turn_port.h"
+
+#include <functional>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "absl/algorithm/container.h"
+#include "absl/strings/match.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "api/task_queue/pending_task_safety_flag.h"
+#include "api/transport/stun.h"
+#include "p2p/base/connection.h"
+#include "p2p/base/p2p_constants.h"
+#include "rtc_base/async_packet_socket.h"
+#include "rtc_base/byte_order.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/experiments/field_trial_parser.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/net_helpers.h"
+#include "rtc_base/socket_address.h"
+#include "rtc_base/strings/string_builder.h"
+
+namespace cricket {
+
+namespace {
+
+bool ResolveTurnHostnameForFamily(const webrtc::FieldTrialsView& field_trials) {
+ // Bug fix for TURN hostname resolution on IPv6.
+ // Field trial key reserved in bugs.webrtc.org/14334
+ static constexpr char field_trial_name[] =
+ "WebRTC-IPv6NetworkResolutionFixes";
+ if (!field_trials.IsEnabled(field_trial_name)) {
+ return false;
+ }
+
+ webrtc::FieldTrialParameter<bool> resolve_turn_hostname_for_family(
+ "ResolveTurnHostnameForFamily", /*default_value=*/false);
+ webrtc::ParseFieldTrial({&resolve_turn_hostname_for_family},
+ field_trials.Lookup(field_trial_name));
+ return resolve_turn_hostname_for_family;
+}
+
+} // namespace
+
+using ::webrtc::SafeTask;
+using ::webrtc::TaskQueueBase;
+using ::webrtc::TimeDelta;
+
+// TODO(juberti): Move to stun.h when relay messages have been renamed.
+static const int TURN_ALLOCATE_REQUEST = STUN_ALLOCATE_REQUEST;
+
+// Attributes in comprehension-optional range,
+// ignored by TURN server that doesn't know about them.
+// https://tools.ietf.org/html/rfc5389#section-18.2
+const int STUN_ATTR_TURN_LOGGING_ID = 0xff05;
+
+// TODO(juberti): Extract to turnmessage.h
+static const int TURN_DEFAULT_PORT = 3478;
+static const int TURN_CHANNEL_NUMBER_START = 0x4000;
+
+static constexpr TimeDelta kTurnPermissionTimeout = TimeDelta::Minutes(5);
+
+static const size_t TURN_CHANNEL_HEADER_SIZE = 4U;
+
+// Retry at most twice (i.e. three different ALLOCATE requests) on
+// STUN_ERROR_ALLOCATION_MISMATCH error per rfc5766.
+static const size_t MAX_ALLOCATE_MISMATCH_RETRIES = 2;
+
+static const int TURN_SUCCESS_RESULT_CODE = 0;
+
+inline bool IsTurnChannelData(uint16_t msg_type) {
+ return ((msg_type & 0xC000) == 0x4000); // MSB are 0b01
+}
+
+static int GetRelayPreference(cricket::ProtocolType proto) {
+ switch (proto) {
+ case cricket::PROTO_TCP:
+ return ICE_TYPE_PREFERENCE_RELAY_TCP;
+ case cricket::PROTO_TLS:
+ return ICE_TYPE_PREFERENCE_RELAY_TLS;
+ default:
+ RTC_DCHECK(proto == PROTO_UDP);
+ return ICE_TYPE_PREFERENCE_RELAY_UDP;
+ }
+}
+
+class TurnAllocateRequest : public StunRequest {
+ public:
+ explicit TurnAllocateRequest(TurnPort* port);
+ void OnSent() override;
+ void OnResponse(StunMessage* response) override;
+ void OnErrorResponse(StunMessage* response) override;
+ void OnTimeout() override;
+
+ private:
+ // Handles authentication challenge from the server.
+ void OnAuthChallenge(StunMessage* response, int code);
+ void OnTryAlternate(StunMessage* response, int code);
+ void OnUnknownAttribute(StunMessage* response);
+
+ TurnPort* port_;
+};
+
+class TurnRefreshRequest : public StunRequest {
+ public:
+ explicit TurnRefreshRequest(TurnPort* port, int lifetime = -1);
+ void OnSent() override;
+ void OnResponse(StunMessage* response) override;
+ void OnErrorResponse(StunMessage* response) override;
+ void OnTimeout() override;
+
+ private:
+ TurnPort* port_;
+};
+
+class TurnCreatePermissionRequest : public StunRequest {
+ public:
+ TurnCreatePermissionRequest(TurnPort* port,
+ TurnEntry* entry,
+ const rtc::SocketAddress& ext_addr);
+ ~TurnCreatePermissionRequest() override;
+ void OnSent() override;
+ void OnResponse(StunMessage* response) override;
+ void OnErrorResponse(StunMessage* response) override;
+ void OnTimeout() override;
+
+ private:
+ TurnPort* port_;
+ TurnEntry* entry_;
+ rtc::SocketAddress ext_addr_;
+};
+
+class TurnChannelBindRequest : public StunRequest {
+ public:
+ TurnChannelBindRequest(TurnPort* port,
+ TurnEntry* entry,
+ int channel_id,
+ const rtc::SocketAddress& ext_addr);
+ ~TurnChannelBindRequest() override;
+ void OnSent() override;
+ void OnResponse(StunMessage* response) override;
+ void OnErrorResponse(StunMessage* response) override;
+ void OnTimeout() override;
+
+ private:
+ TurnPort* port_;
+ TurnEntry* entry_;
+ int channel_id_;
+ rtc::SocketAddress ext_addr_;
+};
+
+// Manages a "connection" to a remote destination. We will attempt to bring up
+// a channel for this remote destination to reduce the overhead of sending data.
+class TurnEntry : public sigslot::has_slots<> {
+ public:
+ enum BindState { STATE_UNBOUND, STATE_BINDING, STATE_BOUND };
+ TurnEntry(TurnPort* port, Connection* conn, int channel_id);
+ ~TurnEntry();
+
+ TurnPort* port() { return port_; }
+
+ int channel_id() const { return channel_id_; }
+ // For testing only.
+ void set_channel_id(int channel_id) { channel_id_ = channel_id; }
+
+ const rtc::SocketAddress& address() const { return ext_addr_; }
+ BindState state() const { return state_; }
+
+ // Adds a new connection object to the list of connections that are associated
+ // with this entry. If prior to this call there were no connections being
+ // tracked (i.e. count goes from 0 -> 1), the internal safety flag is reset
+ // which cancels any potential pending deletion tasks.
+ void TrackConnection(Connection* conn);
+
+ // Removes a connection from the list of tracked connections.
+ // * If `conn` was the last connection removed, the function returns a
+ // safety flag that's used to schedule the deletion of the entry after a
+ // timeout expires. If during this timeout `TrackConnection` is called, the
+ // flag will be reset and pending tasks associated with it, cancelled.
+ // * If `conn` was not the last connection, the return value will be nullptr.
+ rtc::scoped_refptr<webrtc::PendingTaskSafetyFlag> UntrackConnection(
+ Connection* conn);
+
+ // Helper methods to send permission and channel bind requests.
+ void SendCreatePermissionRequest(int delay);
+ void SendChannelBindRequest(int delay);
+ // Sends a packet to the given destination address.
+ // This will wrap the packet in STUN if necessary.
+ int Send(const void* data,
+ size_t size,
+ bool payload,
+ const rtc::PacketOptions& options);
+
+ void OnCreatePermissionSuccess();
+ void OnCreatePermissionError(StunMessage* response, int code);
+ void OnCreatePermissionTimeout();
+ void OnChannelBindSuccess();
+ void OnChannelBindError(StunMessage* response, int code);
+ void OnChannelBindTimeout();
+ // Signal sent when TurnEntry is destroyed.
+ webrtc::CallbackList<TurnEntry*> destroyed_callback_list_;
+
+ private:
+ TurnPort* port_;
+ int channel_id_;
+ rtc::SocketAddress ext_addr_;
+ BindState state_;
+ // List of associated connection instances to keep track of how many and
+ // which connections are associated with this entry. Once this is empty,
+ // the entry can be deleted.
+ std::vector<Connection*> connections_;
+ webrtc::ScopedTaskSafety task_safety_;
+};
+
+TurnPort::TurnPort(TaskQueueBase* thread,
+ rtc::PacketSocketFactory* factory,
+ const rtc::Network* network,
+ rtc::AsyncPacketSocket* socket,
+ absl::string_view username,
+ absl::string_view password,
+ const ProtocolAddress& server_address,
+ const RelayCredentials& credentials,
+ int server_priority,
+ const std::vector<std::string>& tls_alpn_protocols,
+ const std::vector<std::string>& tls_elliptic_curves,
+ webrtc::TurnCustomizer* customizer,
+ rtc::SSLCertificateVerifier* tls_cert_verifier,
+ const webrtc::FieldTrialsView* field_trials)
+ : Port(thread,
+ RELAY_PORT_TYPE,
+ factory,
+ network,
+ username,
+ password,
+ field_trials),
+ server_address_(server_address),
+ tls_alpn_protocols_(tls_alpn_protocols),
+ tls_elliptic_curves_(tls_elliptic_curves),
+ tls_cert_verifier_(tls_cert_verifier),
+ credentials_(credentials),
+ socket_(socket),
+ error_(0),
+ stun_dscp_value_(rtc::DSCP_NO_CHANGE),
+ request_manager_(
+ thread,
+ [this](const void* data, size_t size, StunRequest* request) {
+ OnSendStunPacket(data, size, request);
+ }),
+ next_channel_number_(TURN_CHANNEL_NUMBER_START),
+ state_(STATE_CONNECTING),
+ server_priority_(server_priority),
+ allocate_mismatch_retries_(0),
+ turn_customizer_(customizer) {}
+
+TurnPort::TurnPort(TaskQueueBase* thread,
+ rtc::PacketSocketFactory* factory,
+ const rtc::Network* network,
+ uint16_t min_port,
+ uint16_t max_port,
+ absl::string_view username,
+ absl::string_view password,
+ const ProtocolAddress& server_address,
+ const RelayCredentials& credentials,
+ int server_priority,
+ const std::vector<std::string>& tls_alpn_protocols,
+ const std::vector<std::string>& tls_elliptic_curves,
+ webrtc::TurnCustomizer* customizer,
+ rtc::SSLCertificateVerifier* tls_cert_verifier,
+ const webrtc::FieldTrialsView* field_trials)
+ : Port(thread,
+ RELAY_PORT_TYPE,
+ factory,
+ network,
+ min_port,
+ max_port,
+ username,
+ password,
+ field_trials),
+ server_address_(server_address),
+ tls_alpn_protocols_(tls_alpn_protocols),
+ tls_elliptic_curves_(tls_elliptic_curves),
+ tls_cert_verifier_(tls_cert_verifier),
+ credentials_(credentials),
+ socket_(nullptr),
+ error_(0),
+ stun_dscp_value_(rtc::DSCP_NO_CHANGE),
+ request_manager_(
+ thread,
+ [this](const void* data, size_t size, StunRequest* request) {
+ OnSendStunPacket(data, size, request);
+ }),
+ next_channel_number_(TURN_CHANNEL_NUMBER_START),
+ state_(STATE_CONNECTING),
+ server_priority_(server_priority),
+ allocate_mismatch_retries_(0),
+ turn_customizer_(customizer) {}
+
+TurnPort::~TurnPort() {
+ // TODO(juberti): Should this even be necessary?
+
+ // release the allocation by sending a refresh with
+ // lifetime 0.
+ if (ready()) {
+ Release();
+ }
+
+ entries_.clear();
+
+ if (socket_)
+ socket_->UnsubscribeClose(this);
+
+ if (!SharedSocket()) {
+ delete socket_;
+ }
+}
+
+rtc::SocketAddress TurnPort::GetLocalAddress() const {
+ return socket_ ? socket_->GetLocalAddress() : rtc::SocketAddress();
+}
+
+ProtocolType TurnPort::GetProtocol() const {
+ return server_address_.proto;
+}
+
+TlsCertPolicy TurnPort::GetTlsCertPolicy() const {
+ return tls_cert_policy_;
+}
+
+void TurnPort::SetTlsCertPolicy(TlsCertPolicy tls_cert_policy) {
+ tls_cert_policy_ = tls_cert_policy;
+}
+
+void TurnPort::SetTurnLoggingId(absl::string_view turn_logging_id) {
+ turn_logging_id_ = std::string(turn_logging_id);
+}
+
+std::vector<std::string> TurnPort::GetTlsAlpnProtocols() const {
+ return tls_alpn_protocols_;
+}
+
+std::vector<std::string> TurnPort::GetTlsEllipticCurves() const {
+ return tls_elliptic_curves_;
+}
+
+void TurnPort::PrepareAddress() {
+ if (credentials_.username.empty() || credentials_.password.empty()) {
+ RTC_LOG(LS_ERROR) << "Allocation can't be started without setting the"
+ " TURN server credentials for the user.";
+ OnAllocateError(STUN_ERROR_UNAUTHORIZED,
+ "Missing TURN server credentials.");
+ return;
+ }
+
+ if (!server_address_.address.port()) {
+ // We will set default TURN port, if no port is set in the address.
+ server_address_.address.SetPort(TURN_DEFAULT_PORT);
+ }
+
+ if (!AllowedTurnPort(server_address_.address.port(), &field_trials())) {
+ // This can only happen after a 300 ALTERNATE SERVER, since the port can't
+ // be created with a disallowed port number.
+ RTC_LOG(LS_ERROR) << "Attempt to start allocation with disallowed port# "
+ << server_address_.address.port();
+ OnAllocateError(STUN_ERROR_SERVER_ERROR,
+ "Attempt to start allocation to a disallowed port");
+ return;
+ }
+ if (server_address_.address.IsUnresolvedIP()) {
+ ResolveTurnAddress(server_address_.address);
+ } else {
+ // If protocol family of server address doesn't match with local, return.
+ if (!IsCompatibleAddress(server_address_.address)) {
+ RTC_LOG(LS_ERROR) << "IP address family does not match. server: "
+ << server_address_.address.family()
+ << " local: " << Network()->GetBestIP().family();
+ OnAllocateError(STUN_ERROR_GLOBAL_FAILURE,
+ "IP address family does not match.");
+ return;
+ }
+
+ // Insert the current address to prevent redirection pingpong.
+ attempted_server_addresses_.insert(server_address_.address);
+
+ RTC_LOG(LS_INFO)
+ << ToString() << ": Trying to connect to TURN server via "
+ << ProtoToString(server_address_.proto) << " @ "
+ << server_address_.address.ToSensitiveNameAndAddressString();
+ if (!CreateTurnClientSocket()) {
+ RTC_LOG(LS_ERROR) << "Failed to create TURN client socket";
+ OnAllocateError(SERVER_NOT_REACHABLE_ERROR,
+ "Failed to create TURN client socket.");
+ return;
+ }
+ if (server_address_.proto == PROTO_UDP) {
+ // If its UDP, send AllocateRequest now.
+ // For TCP and TLS AllcateRequest will be sent by OnSocketConnect.
+ SendRequest(new TurnAllocateRequest(this), 0);
+ }
+ }
+}
+
+bool TurnPort::CreateTurnClientSocket() {
+ RTC_DCHECK(!socket_ || SharedSocket());
+
+ if (server_address_.proto == PROTO_UDP && !SharedSocket()) {
+ socket_ = socket_factory()->CreateUdpSocket(
+ rtc::SocketAddress(Network()->GetBestIP(), 0), min_port(), max_port());
+ } else if (server_address_.proto == PROTO_TCP ||
+ server_address_.proto == PROTO_TLS) {
+ RTC_DCHECK(!SharedSocket());
+ int opts = rtc::PacketSocketFactory::OPT_STUN;
+
+ // Apply server address TLS and insecure bits to options.
+ if (server_address_.proto == PROTO_TLS) {
+ if (tls_cert_policy_ ==
+ TlsCertPolicy::TLS_CERT_POLICY_INSECURE_NO_CHECK) {
+ opts |= rtc::PacketSocketFactory::OPT_TLS_INSECURE;
+ } else {
+ opts |= rtc::PacketSocketFactory::OPT_TLS;
+ }
+ }
+
+ rtc::PacketSocketTcpOptions tcp_options;
+ tcp_options.opts = opts;
+ tcp_options.tls_alpn_protocols = tls_alpn_protocols_;
+ tcp_options.tls_elliptic_curves = tls_elliptic_curves_;
+ tcp_options.tls_cert_verifier = tls_cert_verifier_;
+ socket_ = socket_factory()->CreateClientTcpSocket(
+ rtc::SocketAddress(Network()->GetBestIP(), 0), server_address_.address,
+ proxy(), user_agent(), tcp_options);
+ }
+
+ if (!socket_) {
+ error_ = SOCKET_ERROR;
+ return false;
+ }
+
+ // Apply options if any.
+ for (SocketOptionsMap::iterator iter = socket_options_.begin();
+ iter != socket_options_.end(); ++iter) {
+ socket_->SetOption(iter->first, iter->second);
+ }
+
+ if (!SharedSocket()) {
+ // If socket is shared, AllocationSequence will receive the packet.
+ socket_->SignalReadPacket.connect(this, &TurnPort::OnReadPacket);
+ }
+
+ socket_->SignalReadyToSend.connect(this, &TurnPort::OnReadyToSend);
+
+ socket_->SignalSentPacket.connect(this, &TurnPort::OnSentPacket);
+
+ // TCP port is ready to send stun requests after the socket is connected,
+ // while UDP port is ready to do so once the socket is created.
+ if (server_address_.proto == PROTO_TCP ||
+ server_address_.proto == PROTO_TLS) {
+ socket_->SignalConnect.connect(this, &TurnPort::OnSocketConnect);
+ socket_->SubscribeClose(this, [this](rtc::AsyncPacketSocket* s, int err) {
+ OnSocketClose(s, err);
+ });
+ } else {
+ state_ = STATE_CONNECTED;
+ }
+ return true;
+}
+
+void TurnPort::OnSocketConnect(rtc::AsyncPacketSocket* socket) {
+ // This slot should only be invoked if we're using a connection-oriented
+ // protocol.
+ RTC_DCHECK(server_address_.proto == PROTO_TCP ||
+ server_address_.proto == PROTO_TLS);
+
+ // Do not use this port if the socket bound to an address not associated with
+ // the desired network interface. This is seen in Chrome, where TCP sockets
+ // cannot be given a binding address, and the platform is expected to pick
+ // the correct local address.
+ //
+ // However, there are two situations in which we allow the bound address to
+ // not be one of the addresses of the requested interface:
+ // 1. The bound address is the loopback address. This happens when a proxy
+ // forces TCP to bind to only the localhost address (see issue 3927).
+ // 2. The bound address is the "any address". This happens when
+ // multiple_routes is disabled (see issue 4780).
+ //
+ // Note that, aside from minor differences in log statements, this logic is
+ // identical to that in TcpPort.
+ const rtc::SocketAddress& socket_address = socket->GetLocalAddress();
+ if (absl::c_none_of(Network()->GetIPs(),
+ [socket_address](const rtc::InterfaceAddress& addr) {
+ return socket_address.ipaddr() == addr;
+ })) {
+ if (socket->GetLocalAddress().IsLoopbackIP()) {
+ RTC_LOG(LS_WARNING) << "Socket is bound to the address:"
+ << socket_address.ToSensitiveNameAndAddressString()
+ << ", rather than an address associated with network:"
+ << Network()->ToString()
+ << ". Still allowing it since it's localhost.";
+ } else if (IPIsAny(Network()->GetBestIP())) {
+ RTC_LOG(LS_WARNING)
+ << "Socket is bound to the address:"
+ << socket_address.ToSensitiveNameAndAddressString()
+ << ", rather than an address associated with network:"
+ << Network()->ToString()
+ << ". Still allowing it since it's the 'any' address"
+ ", possibly caused by multiple_routes being disabled.";
+ } else {
+ RTC_LOG(LS_WARNING) << "Socket is bound to the address:"
+ << socket_address.ToSensitiveNameAndAddressString()
+ << ", rather than an address associated with network:"
+ << Network()->ToString() << ". Discarding TURN port.";
+ OnAllocateError(
+ STUN_ERROR_GLOBAL_FAILURE,
+ "Address not associated with the desired network interface.");
+ return;
+ }
+ }
+
+ state_ = STATE_CONNECTED; // It is ready to send stun requests.
+ if (server_address_.address.IsUnresolvedIP()) {
+ server_address_.address = socket_->GetRemoteAddress();
+ }
+
+ RTC_LOG(LS_INFO) << "TurnPort connected to "
+ << socket->GetRemoteAddress().ToSensitiveString()
+ << " using tcp.";
+ SendRequest(new TurnAllocateRequest(this), 0);
+}
+
+void TurnPort::OnSocketClose(rtc::AsyncPacketSocket* socket, int error) {
+ RTC_LOG(LS_WARNING) << ToString()
+ << ": Connection with server failed with error: "
+ << error;
+ RTC_DCHECK(socket == socket_);
+ Close();
+}
+
+void TurnPort::OnAllocateMismatch() {
+ if (allocate_mismatch_retries_ >= MAX_ALLOCATE_MISMATCH_RETRIES) {
+ RTC_LOG(LS_WARNING) << ToString() << ": Giving up on the port after "
+ << allocate_mismatch_retries_
+ << " retries for STUN_ERROR_ALLOCATION_MISMATCH";
+ OnAllocateError(STUN_ERROR_ALLOCATION_MISMATCH,
+ "Maximum retries reached for allocation mismatch.");
+ return;
+ }
+
+ RTC_LOG(LS_INFO) << ToString()
+ << ": Allocating a new socket after "
+ "STUN_ERROR_ALLOCATION_MISMATCH, retry: "
+ << allocate_mismatch_retries_ + 1;
+
+ socket_->UnsubscribeClose(this);
+
+ if (SharedSocket()) {
+ ResetSharedSocket();
+ } else {
+ delete socket_;
+ }
+ socket_ = nullptr;
+
+ ResetNonce();
+ PrepareAddress();
+ ++allocate_mismatch_retries_;
+}
+
+Connection* TurnPort::CreateConnection(const Candidate& remote_candidate,
+ CandidateOrigin origin) {
+ // TURN-UDP can only connect to UDP candidates.
+ if (!SupportsProtocol(remote_candidate.protocol())) {
+ return nullptr;
+ }
+
+ if (state_ == STATE_DISCONNECTED || state_ == STATE_RECEIVEONLY) {
+ return nullptr;
+ }
+
+ // If the remote endpoint signaled us an mDNS candidate, we do not form a pair
+ // with the relay candidate to avoid IP leakage in the CreatePermission
+ // request.
+ if (absl::EndsWith(remote_candidate.address().hostname(), LOCAL_TLD)) {
+ return nullptr;
+ }
+
+ // A TURN port will have two candidates, STUN and TURN. STUN may not
+ // present in all cases. If present stun candidate will be added first
+ // and TURN candidate later.
+ for (size_t index = 0; index < Candidates().size(); ++index) {
+ const Candidate& local_candidate = Candidates()[index];
+ if (local_candidate.type() == RELAY_PORT_TYPE &&
+ local_candidate.address().family() ==
+ remote_candidate.address().family()) {
+ ProxyConnection* conn =
+ new ProxyConnection(NewWeakPtr(), index, remote_candidate);
+ // Create an entry, if needed, so we can get our permissions set up
+ // correctly.
+ if (CreateOrRefreshEntry(conn, next_channel_number_)) {
+ next_channel_number_++;
+ }
+ AddOrReplaceConnection(conn);
+ return conn;
+ }
+ }
+ return nullptr;
+}
+
+bool TurnPort::FailAndPruneConnection(const rtc::SocketAddress& address) {
+ Connection* conn = GetConnection(address);
+ if (conn != nullptr) {
+ conn->FailAndPrune();
+ return true;
+ }
+ return false;
+}
+
+int TurnPort::SetOption(rtc::Socket::Option opt, int value) {
+ // Remember the last requested DSCP value, for STUN traffic.
+ if (opt == rtc::Socket::OPT_DSCP)
+ stun_dscp_value_ = static_cast<rtc::DiffServCodePoint>(value);
+
+ if (!socket_) {
+ // If socket is not created yet, these options will be applied during socket
+ // creation.
+ socket_options_[opt] = value;
+ return 0;
+ }
+ return socket_->SetOption(opt, value);
+}
+
+int TurnPort::GetOption(rtc::Socket::Option opt, int* value) {
+ if (!socket_) {
+ SocketOptionsMap::const_iterator it = socket_options_.find(opt);
+ if (it == socket_options_.end()) {
+ return -1;
+ }
+ *value = it->second;
+ return 0;
+ }
+
+ return socket_->GetOption(opt, value);
+}
+
+int TurnPort::GetError() {
+ return error_;
+}
+
+int TurnPort::SendTo(const void* data,
+ size_t size,
+ const rtc::SocketAddress& addr,
+ const rtc::PacketOptions& options,
+ bool payload) {
+ // Try to find an entry for this specific address; we should have one.
+ TurnEntry* entry = FindEntry(addr);
+ RTC_DCHECK(entry);
+
+ if (!ready()) {
+ error_ = ENOTCONN;
+ return SOCKET_ERROR;
+ }
+
+ // Send the actual contents to the server using the usual mechanism.
+ rtc::PacketOptions modified_options(options);
+ CopyPortInformationToPacketInfo(&modified_options.info_signaled_after_sent);
+ int sent = entry->Send(data, size, payload, modified_options);
+ if (sent <= 0) {
+ error_ = socket_->GetError();
+ return SOCKET_ERROR;
+ }
+
+ // The caller of the function is expecting the number of user data bytes,
+ // rather than the size of the packet.
+ return static_cast<int>(size);
+}
+
+bool TurnPort::CanHandleIncomingPacketsFrom(
+ const rtc::SocketAddress& addr) const {
+ return server_address_.address == addr;
+}
+
+void TurnPort::SendBindingErrorResponse(StunMessage* message,
+ const rtc::SocketAddress& addr,
+ int error_code,
+ absl::string_view reason) {
+ if (!GetConnection(addr))
+ return;
+
+ Port::SendBindingErrorResponse(message, addr, error_code, reason);
+}
+
+bool TurnPort::HandleIncomingPacket(rtc::AsyncPacketSocket* socket,
+ const char* data,
+ size_t size,
+ const rtc::SocketAddress& remote_addr,
+ int64_t packet_time_us) {
+ if (socket != socket_) {
+ // The packet was received on a shared socket after we've allocated a new
+ // socket for this TURN port.
+ return false;
+ }
+
+ // This is to guard against a STUN response from previous server after
+ // alternative server redirection. TODO(guoweis): add a unit test for this
+ // race condition.
+ if (remote_addr != server_address_.address) {
+ RTC_LOG(LS_WARNING)
+ << ToString() << ": Discarding TURN message from unknown address: "
+ << remote_addr.ToSensitiveNameAndAddressString() << " server_address_: "
+ << server_address_.address.ToSensitiveNameAndAddressString();
+ return false;
+ }
+
+ // The message must be at least the size of a channel header.
+ if (size < TURN_CHANNEL_HEADER_SIZE) {
+ RTC_LOG(LS_WARNING) << ToString()
+ << ": Received TURN message that was too short";
+ return false;
+ }
+
+ if (state_ == STATE_DISCONNECTED) {
+ RTC_LOG(LS_WARNING)
+ << ToString()
+ << ": Received TURN message while the TURN port is disconnected";
+ return false;
+ }
+
+ // Check the message type, to see if is a Channel Data message.
+ // The message will either be channel data, a TURN data indication, or
+ // a response to a previous request.
+ uint16_t msg_type = rtc::GetBE16(data);
+ if (IsTurnChannelData(msg_type)) {
+ HandleChannelData(msg_type, data, size, packet_time_us);
+ return true;
+ }
+
+ if (msg_type == TURN_DATA_INDICATION) {
+ HandleDataIndication(data, size, packet_time_us);
+ return true;
+ }
+
+ if (SharedSocket() && (msg_type == STUN_BINDING_RESPONSE ||
+ msg_type == STUN_BINDING_ERROR_RESPONSE)) {
+ RTC_LOG(LS_VERBOSE)
+ << ToString()
+ << ": Ignoring STUN binding response message on shared socket.";
+ return false;
+ }
+
+ request_manager_.CheckResponse(data, size);
+
+ return true;
+}
+
+void TurnPort::OnReadPacket(rtc::AsyncPacketSocket* socket,
+ const char* data,
+ size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const int64_t& packet_time_us) {
+ HandleIncomingPacket(socket, data, size, remote_addr, packet_time_us);
+}
+
+void TurnPort::OnSentPacket(rtc::AsyncPacketSocket* socket,
+ const rtc::SentPacket& sent_packet) {
+ PortInterface::SignalSentPacket(sent_packet);
+}
+
+void TurnPort::OnReadyToSend(rtc::AsyncPacketSocket* socket) {
+ if (ready()) {
+ Port::OnReadyToSend();
+ }
+}
+
+bool TurnPort::SupportsProtocol(absl::string_view protocol) const {
+ // Turn port only connects to UDP candidates.
+ return protocol == UDP_PROTOCOL_NAME;
+}
+
+// Update current server address port with the alternate server address port.
+bool TurnPort::SetAlternateServer(const rtc::SocketAddress& address) {
+ // Check if we have seen this address before and reject if we did.
+ AttemptedServerSet::iterator iter = attempted_server_addresses_.find(address);
+ if (iter != attempted_server_addresses_.end()) {
+ RTC_LOG(LS_WARNING) << ToString() << ": Redirection to ["
+ << address.ToSensitiveNameAndAddressString()
+ << "] ignored, allocation failed.";
+ return false;
+ }
+
+ // If protocol family of server address doesn't match with local, return.
+ if (!IsCompatibleAddress(address)) {
+ RTC_LOG(LS_WARNING) << "Server IP address family does not match with "
+ "local host address family type";
+ return false;
+ }
+
+ // Block redirects to a loopback address.
+ // See: https://bugs.chromium.org/p/chromium/issues/detail?id=649118
+ if (address.IsLoopbackIP()) {
+ RTC_LOG(LS_WARNING) << ToString()
+ << ": Blocking attempted redirect to loopback address.";
+ return false;
+ }
+
+ RTC_LOG(LS_INFO) << ToString() << ": Redirecting from TURN server ["
+ << server_address_.address.ToSensitiveNameAndAddressString()
+ << "] to TURN server ["
+ << address.ToSensitiveNameAndAddressString() << "]";
+ server_address_ = ProtocolAddress(address, server_address_.proto);
+
+ // Insert the current address to prevent redirection pingpong.
+ attempted_server_addresses_.insert(server_address_.address);
+ return true;
+}
+
+void TurnPort::ResolveTurnAddress(const rtc::SocketAddress& address) {
+ if (resolver_)
+ return;
+
+ RTC_LOG(LS_INFO) << ToString() << ": Starting TURN host lookup for "
+ << address.ToSensitiveString();
+ resolver_ = socket_factory()->CreateAsyncDnsResolver();
+ auto callback = [this] {
+ // If DNS resolve is failed when trying to connect to the server using TCP,
+ // one of the reason could be due to DNS queries blocked by firewall.
+ // In such cases we will try to connect to the server with hostname,
+ // assuming socket layer will resolve the hostname through a HTTP proxy (if
+ // any).
+ auto& result = resolver_->result();
+ if (result.GetError() != 0 && (server_address_.proto == PROTO_TCP ||
+ server_address_.proto == PROTO_TLS)) {
+ if (!CreateTurnClientSocket()) {
+ OnAllocateError(SERVER_NOT_REACHABLE_ERROR,
+ "TURN host lookup received error.");
+ }
+ return;
+ }
+
+ // Copy the original server address in `resolved_address`. For TLS based
+ // sockets we need hostname along with resolved address.
+ rtc::SocketAddress resolved_address = server_address_.address;
+ if (result.GetError() != 0 ||
+ !result.GetResolvedAddress(Network()->GetBestIP().family(),
+ &resolved_address)) {
+ RTC_LOG(LS_WARNING) << ToString() << ": TURN host lookup received error "
+ << result.GetError();
+ error_ = result.GetError();
+ OnAllocateError(SERVER_NOT_REACHABLE_ERROR,
+ "TURN host lookup received error.");
+ return;
+ }
+ server_address_.address = resolved_address;
+ PrepareAddress();
+ };
+ // TODO(bugs.webrtc.org/14733): remove duplicate resolution with STUN port.
+ if (ResolveTurnHostnameForFamily(field_trials())) {
+ resolver_->Start(address, Network()->family(), std::move(callback));
+ } else {
+ resolver_->Start(address, std::move(callback));
+ }
+}
+
+void TurnPort::OnSendStunPacket(const void* data,
+ size_t size,
+ StunRequest* request) {
+ RTC_DCHECK(connected());
+ rtc::PacketOptions options(StunDscpValue());
+ options.info_signaled_after_sent.packet_type = rtc::PacketType::kTurnMessage;
+ CopyPortInformationToPacketInfo(&options.info_signaled_after_sent);
+ if (Send(data, size, options) < 0) {
+ RTC_LOG(LS_ERROR) << ToString() << ": Failed to send TURN message, error: "
+ << socket_->GetError();
+ }
+}
+
+void TurnPort::OnStunAddress(const rtc::SocketAddress& address) {
+ // STUN Port will discover STUN candidate, as it's supplied with first TURN
+ // server address.
+ // Why not using this address? - P2PTransportChannel will start creating
+ // connections after first candidate, which means it could start creating the
+ // connections before TURN candidate added. For that to handle, we need to
+ // supply STUN candidate from this port to UDPPort, and TurnPort should have
+ // handle to UDPPort to pass back the address.
+}
+
+void TurnPort::OnAllocateSuccess(const rtc::SocketAddress& address,
+ const rtc::SocketAddress& stun_address) {
+ state_ = STATE_READY;
+
+ rtc::SocketAddress related_address = stun_address;
+
+ // For relayed candidate, Base is the candidate itself.
+ AddAddress(address, // Candidate address.
+ address, // Base address.
+ related_address, // Related address.
+ UDP_PROTOCOL_NAME,
+ ProtoToString(server_address_.proto), // The first hop protocol.
+ "", // TCP candidate type, empty for turn candidates.
+ RELAY_PORT_TYPE, GetRelayPreference(server_address_.proto),
+ server_priority_, ReconstructedServerUrl(), true);
+}
+
+void TurnPort::OnAllocateError(int error_code, absl::string_view reason) {
+ // We will send SignalPortError asynchronously as this can be sent during
+ // port initialization. This way it will not be blocking other port
+ // creation.
+ thread()->PostTask(
+ SafeTask(task_safety_.flag(), [this] { SignalPortError(this); }));
+ std::string address = GetLocalAddress().HostAsSensitiveURIString();
+ int port = GetLocalAddress().port();
+ if (server_address_.proto == PROTO_TCP &&
+ server_address_.address.IsPrivateIP()) {
+ address.clear();
+ port = 0;
+ }
+ SignalCandidateError(
+ this, IceCandidateErrorEvent(address, port, ReconstructedServerUrl(),
+ error_code, reason));
+}
+
+void TurnPort::OnRefreshError() {
+ // Need to clear the requests asynchronously because otherwise, the refresh
+ // request may be deleted twice: once at the end of the message processing
+ // and the other in HandleRefreshError().
+ thread()->PostTask(
+ SafeTask(task_safety_.flag(), [this] { HandleRefreshError(); }));
+}
+
+void TurnPort::HandleRefreshError() {
+ request_manager_.Clear();
+ state_ = STATE_RECEIVEONLY;
+ // Fail and prune all connections; stop sending data.
+ for (auto kv : connections()) {
+ kv.second->FailAndPrune();
+ }
+}
+
+void TurnPort::Release() {
+ // Remove any pending refresh requests.
+ request_manager_.Clear();
+
+ // Send refresh with lifetime 0.
+ TurnRefreshRequest* req = new TurnRefreshRequest(this, 0);
+ SendRequest(req, 0);
+
+ state_ = STATE_RECEIVEONLY;
+}
+
+void TurnPort::Close() {
+ if (!ready()) {
+ OnAllocateError(SERVER_NOT_REACHABLE_ERROR, "");
+ }
+ request_manager_.Clear();
+ // Stop the port from creating new connections.
+ state_ = STATE_DISCONNECTED;
+ // Delete all existing connections; stop sending data.
+ DestroyAllConnections();
+ if (callbacks_for_test_) {
+ callbacks_for_test_->OnTurnPortClosed();
+ }
+}
+
+rtc::DiffServCodePoint TurnPort::StunDscpValue() const {
+ return stun_dscp_value_;
+}
+
+// static
+bool TurnPort::AllowedTurnPort(int port,
+ const webrtc::FieldTrialsView* field_trials) {
+ // Port 53, 80 and 443 are used for existing deployments.
+ // Ports above 1024 are assumed to be OK to use.
+ if (port == 53 || port == 80 || port == 443 || port >= 1024) {
+ return true;
+ }
+ // Allow any port if relevant field trial is set. This allows disabling the
+ // check.
+ if (field_trials && field_trials->IsEnabled("WebRTC-Turn-AllowSystemPorts")) {
+ return true;
+ }
+ return false;
+}
+
+void TurnPort::TryAlternateServer() {
+ if (server_address().proto == PROTO_UDP) {
+ // Send another allocate request to alternate server, with the received
+ // realm and nonce values.
+ SendRequest(new TurnAllocateRequest(this), 0);
+ } else {
+ // Since it's TCP, we have to delete the connected socket and reconnect
+ // with the alternate server. PrepareAddress will send stun binding once
+ // the new socket is connected.
+ RTC_DCHECK(server_address().proto == PROTO_TCP ||
+ server_address().proto == PROTO_TLS);
+ RTC_DCHECK(!SharedSocket());
+ delete socket_;
+ socket_ = nullptr;
+ PrepareAddress();
+ }
+}
+
+void TurnPort::OnAllocateRequestTimeout() {
+ OnAllocateError(SERVER_NOT_REACHABLE_ERROR,
+ "TURN allocate request timed out.");
+}
+
+void TurnPort::HandleDataIndication(const char* data,
+ size_t size,
+ int64_t packet_time_us) {
+ // Read in the message, and process according to RFC5766, Section 10.4.
+ rtc::ByteBufferReader buf(data, size);
+ TurnMessage msg;
+ if (!msg.Read(&buf)) {
+ RTC_LOG(LS_WARNING) << ToString()
+ << ": Received invalid TURN data indication";
+ return;
+ }
+
+ // Check mandatory attributes.
+ const StunAddressAttribute* addr_attr =
+ msg.GetAddress(STUN_ATTR_XOR_PEER_ADDRESS);
+ if (!addr_attr) {
+ RTC_LOG(LS_WARNING) << ToString()
+ << ": Missing STUN_ATTR_XOR_PEER_ADDRESS attribute "
+ "in data indication.";
+ return;
+ }
+
+ const StunByteStringAttribute* data_attr = msg.GetByteString(STUN_ATTR_DATA);
+ if (!data_attr) {
+ RTC_LOG(LS_WARNING) << ToString()
+ << ": Missing STUN_ATTR_DATA attribute in "
+ "data indication.";
+ return;
+ }
+
+ // Log a warning if the data didn't come from an address that we think we have
+ // a permission for.
+ rtc::SocketAddress ext_addr(addr_attr->GetAddress());
+ if (!HasPermission(ext_addr.ipaddr())) {
+ RTC_LOG(LS_WARNING) << ToString()
+ << ": Received TURN data indication with unknown "
+ "peer address, addr: "
+ << ext_addr.ToSensitiveString();
+ }
+
+ DispatchPacket(data_attr->bytes(), data_attr->length(), ext_addr, PROTO_UDP,
+ packet_time_us);
+}
+
+void TurnPort::HandleChannelData(int channel_id,
+ const char* data,
+ size_t size,
+ int64_t packet_time_us) {
+ // Read the message, and process according to RFC5766, Section 11.6.
+ // 0 1 2 3
+ // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | Channel Number | Length |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | |
+ // / Application Data /
+ // / /
+ // | |
+ // | +-------------------------------+
+ // | |
+ // +-------------------------------+
+
+ // Extract header fields from the message.
+ uint16_t len = rtc::GetBE16(data + 2);
+ if (len > size - TURN_CHANNEL_HEADER_SIZE) {
+ RTC_LOG(LS_WARNING) << ToString()
+ << ": Received TURN channel data message with "
+ "incorrect length, len: "
+ << len;
+ return;
+ }
+ // Allowing messages larger than `len`, as ChannelData can be padded.
+
+ TurnEntry* entry = FindEntry(channel_id);
+ if (!entry) {
+ RTC_LOG(LS_WARNING) << ToString()
+ << ": Received TURN channel data message for invalid "
+ "channel, channel_id: "
+ << channel_id;
+ return;
+ }
+
+ DispatchPacket(data + TURN_CHANNEL_HEADER_SIZE, len, entry->address(),
+ PROTO_UDP, packet_time_us);
+}
+
+void TurnPort::DispatchPacket(const char* data,
+ size_t size,
+ const rtc::SocketAddress& remote_addr,
+ ProtocolType proto,
+ int64_t packet_time_us) {
+ if (Connection* conn = GetConnection(remote_addr)) {
+ conn->OnReadPacket(data, size, packet_time_us);
+ } else {
+ Port::OnReadPacket(data, size, remote_addr, proto);
+ }
+}
+
+bool TurnPort::ScheduleRefresh(uint32_t lifetime) {
+ // Lifetime is in seconds, delay is in milliseconds.
+ int delay = 1 * 60 * 1000;
+
+ // Cutoff lifetime bigger than 1h.
+ constexpr uint32_t max_lifetime = 60 * 60;
+
+ if (lifetime < 2 * 60) {
+ // The RFC does not mention a lower limit on lifetime.
+ // So if server sends a value less than 2 minutes, we schedule a refresh
+ // for half lifetime.
+ RTC_LOG(LS_WARNING) << ToString()
+ << ": Received response with short lifetime: "
+ << lifetime << " seconds.";
+ delay = (lifetime * 1000) / 2;
+ } else if (lifetime > max_lifetime) {
+ // Make 1 hour largest delay, and then we schedule a refresh for one minute
+ // less than max lifetime.
+ RTC_LOG(LS_WARNING) << ToString()
+ << ": Received response with long lifetime: "
+ << lifetime << " seconds.";
+ delay = (max_lifetime - 60) * 1000;
+ } else {
+ // Normal case,
+ // we schedule a refresh for one minute less than requested lifetime.
+ delay = (lifetime - 60) * 1000;
+ }
+
+ SendRequest(new TurnRefreshRequest(this), delay);
+ RTC_LOG(LS_INFO) << ToString() << ": Scheduled refresh in " << delay << "ms.";
+ return true;
+}
+
+void TurnPort::SendRequest(StunRequest* req, int delay) {
+ request_manager_.SendDelayed(req, delay);
+}
+
+void TurnPort::AddRequestAuthInfo(StunMessage* msg) {
+ // If we've gotten the necessary data from the server, add it to our request.
+ RTC_DCHECK(!hash_.empty());
+ msg->AddAttribute(std::make_unique<StunByteStringAttribute>(
+ STUN_ATTR_USERNAME, credentials_.username));
+ msg->AddAttribute(
+ std::make_unique<StunByteStringAttribute>(STUN_ATTR_REALM, realm_));
+ msg->AddAttribute(
+ std::make_unique<StunByteStringAttribute>(STUN_ATTR_NONCE, nonce_));
+ const bool success = msg->AddMessageIntegrity(hash());
+ RTC_DCHECK(success);
+}
+
+int TurnPort::Send(const void* data,
+ size_t len,
+ const rtc::PacketOptions& options) {
+ return socket_->SendTo(data, len, server_address_.address, options);
+}
+
+void TurnPort::UpdateHash() {
+ const bool success = ComputeStunCredentialHash(credentials_.username, realm_,
+ credentials_.password, &hash_);
+ RTC_DCHECK(success);
+}
+
+bool TurnPort::UpdateNonce(StunMessage* response) {
+ // When stale nonce error received, we should update
+ // hash and store realm and nonce.
+ // Check the mandatory attributes.
+ const StunByteStringAttribute* realm_attr =
+ response->GetByteString(STUN_ATTR_REALM);
+ if (!realm_attr) {
+ RTC_LOG(LS_ERROR) << "Missing STUN_ATTR_REALM attribute in "
+ "stale nonce error response.";
+ return false;
+ }
+ set_realm(realm_attr->string_view());
+
+ const StunByteStringAttribute* nonce_attr =
+ response->GetByteString(STUN_ATTR_NONCE);
+ if (!nonce_attr) {
+ RTC_LOG(LS_ERROR) << "Missing STUN_ATTR_NONCE attribute in "
+ "stale nonce error response.";
+ return false;
+ }
+ set_nonce(nonce_attr->string_view());
+ return true;
+}
+
+void TurnPort::ResetNonce() {
+ hash_.clear();
+ nonce_.clear();
+ realm_.clear();
+}
+
+bool TurnPort::HasPermission(const rtc::IPAddress& ipaddr) const {
+ return absl::c_any_of(entries_, [&ipaddr](const auto& e) {
+ return e->address().ipaddr() == ipaddr;
+ });
+}
+
+TurnEntry* TurnPort::FindEntry(const rtc::SocketAddress& addr) const {
+ auto it = absl::c_find_if(
+ entries_, [&addr](const auto& e) { return e->address() == addr; });
+ return (it != entries_.end()) ? it->get() : nullptr;
+}
+
+TurnEntry* TurnPort::FindEntry(int channel_id) const {
+ auto it = absl::c_find_if(entries_, [&channel_id](const auto& e) {
+ return e->channel_id() == channel_id;
+ });
+ return (it != entries_.end()) ? it->get() : nullptr;
+}
+
+bool TurnPort::CreateOrRefreshEntry(Connection* conn, int channel_number) {
+ const Candidate& remote_candidate = conn->remote_candidate();
+ TurnEntry* entry = FindEntry(remote_candidate.address());
+ if (entry == nullptr) {
+ entries_.push_back(std::make_unique<TurnEntry>(this, conn, channel_number));
+ return true;
+ }
+
+ // Associate this connection object with an existing entry. If the entry
+ // has been scheduled for deletion, this will cancel that task.
+ entry->TrackConnection(conn);
+
+ return false;
+}
+
+void TurnPort::HandleConnectionDestroyed(Connection* conn) {
+ // Schedule an event to destroy TurnEntry for the connection, which is
+ // being destroyed.
+ const rtc::SocketAddress& remote_address = conn->remote_candidate().address();
+ // We should always have an entry for this connection.
+ TurnEntry* entry = FindEntry(remote_address);
+ rtc::scoped_refptr<webrtc::PendingTaskSafetyFlag> flag =
+ entry->UntrackConnection(conn);
+ if (flag) {
+ // An assumption here is that the lifetime flag for the entry, is within
+ // the lifetime scope of `task_safety_` and therefore use of `this` is safe.
+ // If an entry gets reused (associated with a new connection) while this
+ // task is pending, the entry will reset the safety flag, thus cancel this
+ // task.
+ thread()->PostDelayedTask(SafeTask(flag,
+ [this, entry] {
+ entries_.erase(absl::c_find_if(
+ entries_, [entry](const auto& e) {
+ return e.get() == entry;
+ }));
+ }),
+ kTurnPermissionTimeout);
+ }
+}
+
+void TurnPort::SetCallbacksForTest(CallbacksForTest* callbacks) {
+ RTC_DCHECK(!callbacks_for_test_);
+ callbacks_for_test_ = callbacks;
+}
+
+bool TurnPort::SetEntryChannelId(const rtc::SocketAddress& address,
+ int channel_id) {
+ TurnEntry* entry = FindEntry(address);
+ if (!entry) {
+ return false;
+ }
+ entry->set_channel_id(channel_id);
+ return true;
+}
+
+std::string TurnPort::ReconstructedServerUrl() {
+ // draft-petithuguenin-behave-turn-uris-01
+ // turnURI = scheme ":" turn-host [ ":" turn-port ]
+ // [ "?transport=" transport ]
+ // scheme = "turn" / "turns"
+ // transport = "udp" / "tcp" / transport-ext
+ // transport-ext = 1*unreserved
+ // turn-host = IP-literal / IPv4address / reg-name
+ // turn-port = *DIGIT
+ std::string scheme = "turn";
+ std::string transport = "tcp";
+ switch (server_address_.proto) {
+ case PROTO_SSLTCP:
+ case PROTO_TLS:
+ scheme = "turns";
+ break;
+ case PROTO_UDP:
+ transport = "udp";
+ break;
+ case PROTO_TCP:
+ break;
+ }
+ rtc::StringBuilder url;
+ url << scheme << ":" << server_address_.address.hostname() << ":"
+ << server_address_.address.port() << "?transport=" << transport;
+ return url.Release();
+}
+
+void TurnPort::TurnCustomizerMaybeModifyOutgoingStunMessage(
+ StunMessage* message) {
+ if (turn_customizer_ == nullptr) {
+ return;
+ }
+
+ turn_customizer_->MaybeModifyOutgoingStunMessage(this, message);
+}
+
+bool TurnPort::TurnCustomizerAllowChannelData(const void* data,
+ size_t size,
+ bool payload) {
+ if (turn_customizer_ == nullptr) {
+ return true;
+ }
+
+ return turn_customizer_->AllowChannelData(this, data, size, payload);
+}
+
+void TurnPort::MaybeAddTurnLoggingId(StunMessage* msg) {
+ if (!turn_logging_id_.empty()) {
+ msg->AddAttribute(std::make_unique<StunByteStringAttribute>(
+ STUN_ATTR_TURN_LOGGING_ID, turn_logging_id_));
+ }
+}
+
+TurnAllocateRequest::TurnAllocateRequest(TurnPort* port)
+ : StunRequest(port->request_manager(),
+ std::make_unique<TurnMessage>(TURN_ALLOCATE_REQUEST)),
+ port_(port) {
+ StunMessage* message = mutable_msg();
+ // Create the request as indicated in RFC 5766, Section 6.1.
+ RTC_DCHECK_EQ(message->type(), TURN_ALLOCATE_REQUEST);
+ auto transport_attr =
+ StunAttribute::CreateUInt32(STUN_ATTR_REQUESTED_TRANSPORT);
+ transport_attr->SetValue(IPPROTO_UDP << 24);
+ message->AddAttribute(std::move(transport_attr));
+ if (!port_->hash().empty()) {
+ port_->AddRequestAuthInfo(message);
+ }
+ port_->MaybeAddTurnLoggingId(message);
+ port_->TurnCustomizerMaybeModifyOutgoingStunMessage(message);
+}
+
+void TurnAllocateRequest::OnSent() {
+ RTC_LOG(LS_INFO) << port_->ToString() << ": TURN allocate request sent, id="
+ << rtc::hex_encode(id());
+ StunRequest::OnSent();
+}
+
+void TurnAllocateRequest::OnResponse(StunMessage* response) {
+ RTC_LOG(LS_INFO) << port_->ToString()
+ << ": TURN allocate requested successfully, id="
+ << rtc::hex_encode(id())
+ << ", code=0" // Makes logging easier to parse.
+ ", rtt="
+ << Elapsed();
+
+ // Check mandatory attributes as indicated in RFC5766, Section 6.3.
+ const StunAddressAttribute* mapped_attr =
+ response->GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+ if (!mapped_attr) {
+ RTC_LOG(LS_WARNING) << port_->ToString()
+ << ": Missing STUN_ATTR_XOR_MAPPED_ADDRESS "
+ "attribute in allocate success response";
+ return;
+ }
+ // Using XOR-Mapped-Address for stun.
+ port_->OnStunAddress(mapped_attr->GetAddress());
+
+ const StunAddressAttribute* relayed_attr =
+ response->GetAddress(STUN_ATTR_XOR_RELAYED_ADDRESS);
+ if (!relayed_attr) {
+ RTC_LOG(LS_WARNING) << port_->ToString()
+ << ": Missing STUN_ATTR_XOR_RELAYED_ADDRESS "
+ "attribute in allocate success response";
+ return;
+ }
+
+ const StunUInt32Attribute* lifetime_attr =
+ response->GetUInt32(STUN_ATTR_TURN_LIFETIME);
+ if (!lifetime_attr) {
+ RTC_LOG(LS_WARNING) << port_->ToString()
+ << ": Missing STUN_ATTR_TURN_LIFETIME attribute in "
+ "allocate success response";
+ return;
+ }
+ // Notify the port the allocate succeeded, and schedule a refresh request.
+ port_->OnAllocateSuccess(relayed_attr->GetAddress(),
+ mapped_attr->GetAddress());
+ port_->ScheduleRefresh(lifetime_attr->value());
+}
+
+void TurnAllocateRequest::OnErrorResponse(StunMessage* response) {
+ // Process error response according to RFC5766, Section 6.4.
+ int error_code = response->GetErrorCodeValue();
+
+ RTC_LOG(LS_INFO) << port_->ToString()
+ << ": Received TURN allocate error response, id="
+ << rtc::hex_encode(id()) << ", code=" << error_code
+ << ", rtt=" << Elapsed();
+
+ switch (error_code) {
+ case STUN_ERROR_UNAUTHORIZED: // Unauthrorized.
+ OnAuthChallenge(response, error_code);
+ break;
+ case STUN_ERROR_TRY_ALTERNATE:
+ OnTryAlternate(response, error_code);
+ break;
+ case STUN_ERROR_ALLOCATION_MISMATCH: {
+ // We must handle this error async because trying to delete the socket in
+ // OnErrorResponse will cause a deadlock on the socket.
+ TurnPort* port = port_;
+ port->thread()->PostTask(SafeTask(
+ port->task_safety_.flag(), [port] { port->OnAllocateMismatch(); }));
+ } break;
+ default:
+ RTC_LOG(LS_WARNING) << port_->ToString()
+ << ": Received TURN allocate error response, id="
+ << rtc::hex_encode(id()) << ", code=" << error_code
+ << ", rtt=" << Elapsed();
+ const StunErrorCodeAttribute* attr = response->GetErrorCode();
+ port_->OnAllocateError(error_code, attr ? attr->reason() : "");
+ }
+}
+
+void TurnAllocateRequest::OnTimeout() {
+ RTC_LOG(LS_WARNING) << port_->ToString() << ": TURN allocate request "
+ << rtc::hex_encode(id()) << " timeout";
+ port_->OnAllocateRequestTimeout();
+}
+
+void TurnAllocateRequest::OnAuthChallenge(StunMessage* response, int code) {
+ // If we failed to authenticate even after we sent our credentials, fail hard.
+ if (code == STUN_ERROR_UNAUTHORIZED && !port_->hash().empty()) {
+ RTC_LOG(LS_WARNING) << port_->ToString()
+ << ": Failed to authenticate with the server "
+ "after challenge.";
+ const StunErrorCodeAttribute* attr = response->GetErrorCode();
+ port_->OnAllocateError(STUN_ERROR_UNAUTHORIZED, attr ? attr->reason() : "");
+ return;
+ }
+
+ // Check the mandatory attributes.
+ const StunByteStringAttribute* realm_attr =
+ response->GetByteString(STUN_ATTR_REALM);
+ if (!realm_attr) {
+ RTC_LOG(LS_WARNING) << port_->ToString()
+ << ": Missing STUN_ATTR_REALM attribute in "
+ "allocate unauthorized response.";
+ return;
+ }
+ port_->set_realm(realm_attr->string_view());
+
+ const StunByteStringAttribute* nonce_attr =
+ response->GetByteString(STUN_ATTR_NONCE);
+ if (!nonce_attr) {
+ RTC_LOG(LS_WARNING) << port_->ToString()
+ << ": Missing STUN_ATTR_NONCE attribute in "
+ "allocate unauthorized response.";
+ return;
+ }
+ port_->set_nonce(nonce_attr->string_view());
+
+ // Send another allocate request, with the received realm and nonce values.
+ port_->SendRequest(new TurnAllocateRequest(port_), 0);
+}
+
+void TurnAllocateRequest::OnTryAlternate(StunMessage* response, int code) {
+ // According to RFC 5389 section 11, there are use cases where
+ // authentication of response is not possible, we're not validating
+ // message integrity.
+ const StunErrorCodeAttribute* error_code_attr = response->GetErrorCode();
+ // Get the alternate server address attribute value.
+ const StunAddressAttribute* alternate_server_attr =
+ response->GetAddress(STUN_ATTR_ALTERNATE_SERVER);
+ if (!alternate_server_attr) {
+ RTC_LOG(LS_WARNING) << port_->ToString()
+ << ": Missing STUN_ATTR_ALTERNATE_SERVER "
+ "attribute in try alternate error response";
+ port_->OnAllocateError(STUN_ERROR_TRY_ALTERNATE,
+ error_code_attr ? error_code_attr->reason() : "");
+ return;
+ }
+ if (!port_->SetAlternateServer(alternate_server_attr->GetAddress())) {
+ port_->OnAllocateError(STUN_ERROR_TRY_ALTERNATE,
+ error_code_attr ? error_code_attr->reason() : "");
+ return;
+ }
+
+ // Check the attributes.
+ const StunByteStringAttribute* realm_attr =
+ response->GetByteString(STUN_ATTR_REALM);
+ if (realm_attr) {
+ RTC_LOG(LS_INFO) << port_->ToString()
+ << ": Applying STUN_ATTR_REALM attribute in "
+ "try alternate error response.";
+ port_->set_realm(realm_attr->string_view());
+ }
+
+ const StunByteStringAttribute* nonce_attr =
+ response->GetByteString(STUN_ATTR_NONCE);
+ if (nonce_attr) {
+ RTC_LOG(LS_INFO) << port_->ToString()
+ << ": Applying STUN_ATTR_NONCE attribute in "
+ "try alternate error response.";
+ port_->set_nonce(nonce_attr->string_view());
+ }
+
+ // For TCP, we can't close the original Tcp socket during handling a 300 as
+ // we're still inside that socket's event handler. Doing so will cause
+ // deadlock.
+ TurnPort* port = port_;
+ port->thread()->PostTask(SafeTask(port->task_safety_.flag(),
+ [port] { port->TryAlternateServer(); }));
+}
+
+TurnRefreshRequest::TurnRefreshRequest(TurnPort* port, int lifetime /*= -1*/)
+ : StunRequest(port->request_manager(),
+ std::make_unique<TurnMessage>(TURN_REFRESH_REQUEST)),
+ port_(port) {
+ StunMessage* message = mutable_msg();
+ // Create the request as indicated in RFC 5766, Section 7.1.
+ // No attributes need to be included.
+ RTC_DCHECK_EQ(message->type(), TURN_REFRESH_REQUEST);
+ if (lifetime > -1) {
+ message->AddAttribute(
+ std::make_unique<StunUInt32Attribute>(STUN_ATTR_LIFETIME, lifetime));
+ }
+
+ port_->AddRequestAuthInfo(message);
+ port_->TurnCustomizerMaybeModifyOutgoingStunMessage(message);
+}
+
+void TurnRefreshRequest::OnSent() {
+ RTC_LOG(LS_INFO) << port_->ToString() << ": TURN refresh request sent, id="
+ << rtc::hex_encode(id());
+ StunRequest::OnSent();
+}
+
+void TurnRefreshRequest::OnResponse(StunMessage* response) {
+ RTC_LOG(LS_INFO) << port_->ToString()
+ << ": TURN refresh requested successfully, id="
+ << rtc::hex_encode(id())
+ << ", code=0" // Makes logging easier to parse.
+ ", rtt="
+ << Elapsed();
+
+ // Check mandatory attributes as indicated in RFC5766, Section 7.3.
+ const StunUInt32Attribute* lifetime_attr =
+ response->GetUInt32(STUN_ATTR_TURN_LIFETIME);
+ if (!lifetime_attr) {
+ RTC_LOG(LS_WARNING) << port_->ToString()
+ << ": Missing STUN_ATTR_TURN_LIFETIME attribute in "
+ "refresh success response.";
+ return;
+ }
+
+ if (lifetime_attr->value() > 0) {
+ // Schedule a refresh based on the returned lifetime value.
+ port_->ScheduleRefresh(lifetime_attr->value());
+ } else {
+ // If we scheduled a refresh with lifetime 0, we're releasing this
+ // allocation; see TurnPort::Release.
+ TurnPort* port = port_;
+ port->thread()->PostTask(
+ SafeTask(port->task_safety_.flag(), [port] { port->Close(); }));
+ }
+
+ if (port_->callbacks_for_test_) {
+ port_->callbacks_for_test_->OnTurnRefreshResult(TURN_SUCCESS_RESULT_CODE);
+ }
+}
+
+void TurnRefreshRequest::OnErrorResponse(StunMessage* response) {
+ int error_code = response->GetErrorCodeValue();
+
+ if (error_code == STUN_ERROR_STALE_NONCE) {
+ if (port_->UpdateNonce(response)) {
+ // Send RefreshRequest immediately.
+ port_->SendRequest(new TurnRefreshRequest(port_), 0);
+ }
+ } else {
+ RTC_LOG(LS_WARNING) << port_->ToString()
+ << ": Received TURN refresh error response, id="
+ << rtc::hex_encode(id()) << ", code=" << error_code
+ << ", rtt=" << Elapsed();
+ port_->OnRefreshError();
+ if (port_->callbacks_for_test_) {
+ port_->callbacks_for_test_->OnTurnRefreshResult(error_code);
+ }
+ }
+}
+
+void TurnRefreshRequest::OnTimeout() {
+ RTC_LOG(LS_WARNING) << port_->ToString() << ": TURN refresh timeout "
+ << rtc::hex_encode(id());
+ port_->OnRefreshError();
+}
+
+TurnCreatePermissionRequest::TurnCreatePermissionRequest(
+ TurnPort* port,
+ TurnEntry* entry,
+ const rtc::SocketAddress& ext_addr)
+ : StunRequest(
+ port->request_manager(),
+ std::make_unique<TurnMessage>(TURN_CREATE_PERMISSION_REQUEST)),
+ port_(port),
+ entry_(entry),
+ ext_addr_(ext_addr) {
+ RTC_DCHECK(entry_);
+ entry_->destroyed_callback_list_.AddReceiver(this, [this](TurnEntry* entry) {
+ RTC_DCHECK(entry_ == entry);
+ entry_ = nullptr;
+ });
+ StunMessage* message = mutable_msg();
+ // Create the request as indicated in RFC5766, Section 9.1.
+ RTC_DCHECK_EQ(message->type(), TURN_CREATE_PERMISSION_REQUEST);
+ message->AddAttribute(std::make_unique<StunXorAddressAttribute>(
+ STUN_ATTR_XOR_PEER_ADDRESS, ext_addr_));
+ port_->AddRequestAuthInfo(message);
+ port_->TurnCustomizerMaybeModifyOutgoingStunMessage(message);
+}
+
+TurnCreatePermissionRequest::~TurnCreatePermissionRequest() {
+ if (entry_) {
+ entry_->destroyed_callback_list_.RemoveReceivers(this);
+ }
+}
+
+void TurnCreatePermissionRequest::OnSent() {
+ RTC_LOG(LS_INFO) << port_->ToString()
+ << ": TURN create permission request sent, id="
+ << rtc::hex_encode(id());
+ StunRequest::OnSent();
+}
+
+void TurnCreatePermissionRequest::OnResponse(StunMessage* response) {
+ RTC_LOG(LS_INFO) << port_->ToString()
+ << ": TURN permission requested successfully, id="
+ << rtc::hex_encode(id())
+ << ", code=0" // Makes logging easier to parse.
+ ", rtt="
+ << Elapsed();
+
+ if (entry_) {
+ entry_->OnCreatePermissionSuccess();
+ }
+}
+
+void TurnCreatePermissionRequest::OnErrorResponse(StunMessage* response) {
+ int error_code = response->GetErrorCodeValue();
+ RTC_LOG(LS_WARNING) << port_->ToString()
+ << ": Received TURN create permission error response, id="
+ << rtc::hex_encode(id()) << ", code=" << error_code
+ << ", rtt=" << Elapsed();
+ if (entry_) {
+ entry_->OnCreatePermissionError(response, error_code);
+ }
+}
+
+void TurnCreatePermissionRequest::OnTimeout() {
+ RTC_LOG(LS_WARNING) << port_->ToString()
+ << ": TURN create permission timeout "
+ << rtc::hex_encode(id());
+ if (entry_) {
+ entry_->OnCreatePermissionTimeout();
+ }
+}
+
+TurnChannelBindRequest::TurnChannelBindRequest(
+ TurnPort* port,
+ TurnEntry* entry,
+ int channel_id,
+ const rtc::SocketAddress& ext_addr)
+ : StunRequest(port->request_manager(),
+ std::make_unique<TurnMessage>(TURN_CHANNEL_BIND_REQUEST)),
+ port_(port),
+ entry_(entry),
+ channel_id_(channel_id),
+ ext_addr_(ext_addr) {
+ RTC_DCHECK(entry_);
+ entry_->destroyed_callback_list_.AddReceiver(this, [this](TurnEntry* entry) {
+ RTC_DCHECK(entry_ == entry);
+ entry_ = nullptr;
+ });
+ StunMessage* message = mutable_msg();
+ // Create the request as indicated in RFC5766, Section 11.1.
+ RTC_DCHECK_EQ(message->type(), TURN_CHANNEL_BIND_REQUEST);
+ message->AddAttribute(std::make_unique<StunUInt32Attribute>(
+ STUN_ATTR_CHANNEL_NUMBER, channel_id_ << 16));
+ message->AddAttribute(std::make_unique<StunXorAddressAttribute>(
+ STUN_ATTR_XOR_PEER_ADDRESS, ext_addr_));
+ port_->AddRequestAuthInfo(message);
+ port_->TurnCustomizerMaybeModifyOutgoingStunMessage(message);
+}
+
+TurnChannelBindRequest::~TurnChannelBindRequest() {
+ if (entry_) {
+ entry_->destroyed_callback_list_.RemoveReceivers(this);
+ }
+}
+
+void TurnChannelBindRequest::OnSent() {
+ RTC_LOG(LS_INFO) << port_->ToString()
+ << ": TURN channel bind request sent, id="
+ << rtc::hex_encode(id());
+ StunRequest::OnSent();
+}
+
+void TurnChannelBindRequest::OnResponse(StunMessage* response) {
+ RTC_LOG(LS_INFO) << port_->ToString()
+ << ": TURN channel bind requested successfully, id="
+ << rtc::hex_encode(id())
+ << ", code=0" // Makes logging easier to parse.
+ ", rtt="
+ << Elapsed();
+
+ if (entry_) {
+ entry_->OnChannelBindSuccess();
+ // Refresh the channel binding just under the permission timeout
+ // threshold. The channel binding has a longer lifetime, but
+ // this is the easiest way to keep both the channel and the
+ // permission from expiring.
+ TimeDelta delay = kTurnPermissionTimeout - TimeDelta::Minutes(1);
+ entry_->SendChannelBindRequest(delay.ms());
+ RTC_LOG(LS_INFO) << port_->ToString() << ": Scheduled channel bind in "
+ << delay.ms() << "ms.";
+ }
+}
+
+void TurnChannelBindRequest::OnErrorResponse(StunMessage* response) {
+ int error_code = response->GetErrorCodeValue();
+ RTC_LOG(LS_WARNING) << port_->ToString()
+ << ": Received TURN channel bind error response, id="
+ << rtc::hex_encode(id()) << ", code=" << error_code
+ << ", rtt=" << Elapsed();
+ if (entry_) {
+ entry_->OnChannelBindError(response, error_code);
+ }
+}
+
+void TurnChannelBindRequest::OnTimeout() {
+ RTC_LOG(LS_WARNING) << port_->ToString() << ": TURN channel bind timeout "
+ << rtc::hex_encode(id());
+ if (entry_) {
+ entry_->OnChannelBindTimeout();
+ }
+}
+
+TurnEntry::TurnEntry(TurnPort* port, Connection* conn, int channel_id)
+ : port_(port),
+ channel_id_(channel_id),
+ ext_addr_(conn->remote_candidate().address()),
+ state_(STATE_UNBOUND),
+ connections_({conn}) {
+ // Creating permission for `ext_addr_`.
+ SendCreatePermissionRequest(0);
+}
+
+TurnEntry::~TurnEntry() {
+ destroyed_callback_list_.Send(this);
+}
+
+void TurnEntry::TrackConnection(Connection* conn) {
+ RTC_DCHECK(absl::c_find(connections_, conn) == connections_.end());
+ if (connections_.empty()) {
+ task_safety_.reset();
+ }
+ connections_.push_back(conn);
+}
+
+rtc::scoped_refptr<webrtc::PendingTaskSafetyFlag> TurnEntry::UntrackConnection(
+ Connection* conn) {
+ connections_.erase(absl::c_find(connections_, conn));
+ return connections_.empty() ? task_safety_.flag() : nullptr;
+}
+
+void TurnEntry::SendCreatePermissionRequest(int delay) {
+ port_->SendRequest(new TurnCreatePermissionRequest(port_, this, ext_addr_),
+ delay);
+}
+
+void TurnEntry::SendChannelBindRequest(int delay) {
+ port_->SendRequest(
+ new TurnChannelBindRequest(port_, this, channel_id_, ext_addr_), delay);
+}
+
+int TurnEntry::Send(const void* data,
+ size_t size,
+ bool payload,
+ const rtc::PacketOptions& options) {
+ rtc::ByteBufferWriter buf;
+ if (state_ != STATE_BOUND ||
+ !port_->TurnCustomizerAllowChannelData(data, size, payload)) {
+ // If we haven't bound the channel yet, we have to use a Send Indication.
+ // The turn_customizer_ can also make us use Send Indication.
+ TurnMessage msg(TURN_SEND_INDICATION);
+ msg.AddAttribute(std::make_unique<StunXorAddressAttribute>(
+ STUN_ATTR_XOR_PEER_ADDRESS, ext_addr_));
+ msg.AddAttribute(
+ std::make_unique<StunByteStringAttribute>(STUN_ATTR_DATA, data, size));
+
+ port_->TurnCustomizerMaybeModifyOutgoingStunMessage(&msg);
+
+ const bool success = msg.Write(&buf);
+ RTC_DCHECK(success);
+
+ // If we're sending real data, request a channel bind that we can use later.
+ if (state_ == STATE_UNBOUND && payload) {
+ SendChannelBindRequest(0);
+ state_ = STATE_BINDING;
+ }
+ } else {
+ // If the channel is bound, we can send the data as a Channel Message.
+ buf.WriteUInt16(channel_id_);
+ buf.WriteUInt16(static_cast<uint16_t>(size));
+ buf.WriteBytes(reinterpret_cast<const char*>(data), size);
+ }
+ rtc::PacketOptions modified_options(options);
+ modified_options.info_signaled_after_sent.turn_overhead_bytes =
+ buf.Length() - size;
+ return port_->Send(buf.Data(), buf.Length(), modified_options);
+}
+
+void TurnEntry::OnCreatePermissionSuccess() {
+ RTC_LOG(LS_INFO) << port_->ToString() << ": Create permission for "
+ << ext_addr_.ToSensitiveString() << " succeeded";
+ if (port_->callbacks_for_test_) {
+ port_->callbacks_for_test_->OnTurnCreatePermissionResult(
+ TURN_SUCCESS_RESULT_CODE);
+ }
+
+ // If `state_` is STATE_BOUND, the permission will be refreshed
+ // by ChannelBindRequest.
+ if (state_ != STATE_BOUND) {
+ // Refresh the permission request about 1 minute before the permission
+ // times out.
+ TimeDelta delay = kTurnPermissionTimeout - TimeDelta::Minutes(1);
+ SendCreatePermissionRequest(delay.ms());
+ RTC_LOG(LS_INFO) << port_->ToString()
+ << ": Scheduled create-permission-request in "
+ << delay.ms() << "ms.";
+ }
+}
+
+void TurnEntry::OnCreatePermissionError(StunMessage* response, int code) {
+ if (code == STUN_ERROR_STALE_NONCE) {
+ if (port_->UpdateNonce(response)) {
+ SendCreatePermissionRequest(0);
+ }
+ } else {
+ bool found = port_->FailAndPruneConnection(ext_addr_);
+ if (found) {
+ RTC_LOG(LS_ERROR) << "Received TURN CreatePermission error response, "
+ "code="
+ << code << "; pruned connection.";
+ }
+ }
+ if (port_->callbacks_for_test_) {
+ port_->callbacks_for_test_->OnTurnCreatePermissionResult(code);
+ }
+}
+
+void TurnEntry::OnCreatePermissionTimeout() {
+ port_->FailAndPruneConnection(ext_addr_);
+}
+
+void TurnEntry::OnChannelBindSuccess() {
+ RTC_LOG(LS_INFO) << port_->ToString() << ": Successful channel bind for "
+ << ext_addr_.ToSensitiveString();
+ RTC_DCHECK(state_ == STATE_BINDING || state_ == STATE_BOUND);
+ state_ = STATE_BOUND;
+}
+
+void TurnEntry::OnChannelBindError(StunMessage* response, int code) {
+ // If the channel bind fails due to errors other than STATE_NONCE,
+ // we will fail and prune the connection and rely on ICE restart to
+ // re-establish a new connection if needed.
+ if (code == STUN_ERROR_STALE_NONCE) {
+ if (port_->UpdateNonce(response)) {
+ // Send channel bind request with fresh nonce.
+ SendChannelBindRequest(0);
+ }
+ } else {
+ state_ = STATE_UNBOUND;
+ port_->FailAndPruneConnection(ext_addr_);
+ }
+}
+void TurnEntry::OnChannelBindTimeout() {
+ state_ = STATE_UNBOUND;
+ port_->FailAndPruneConnection(ext_addr_);
+}
+} // namespace cricket
diff --git a/third_party/libwebrtc/p2p/base/turn_port.h b/third_party/libwebrtc/p2p/base/turn_port.h
new file mode 100644
index 0000000000..ac660d6599
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/turn_port.h
@@ -0,0 +1,375 @@
+/*
+ * 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/client/basic_port_allocator.h"
+#include "rtc_base/async_packet_socket.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 char* data,
+ size_t size,
+ const rtc::SocketAddress& remote_addr,
+ int64_t packet_time_us) 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 char* data,
+ size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const int64_t& packet_time_us);
+
+ void OnSentPacket(rtc::AsyncPacketSocket* socket,
+ const rtc::SentPacket& sent_packet) 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(rtc::AsyncResolverInterface* resolver);
+
+ 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);
+
+ // Reconstruct the URL of the server which the candidate is gathered from.
+ std::string ReconstructedServerUrl();
+
+ void MaybeAddTurnLoggingId(StunMessage* message);
+
+ void TurnCustomizerMaybeModifyOutgoingStunMessage(StunMessage* message);
+ bool TurnCustomizerAllowChannelData(const void* data,
+ size_t size,
+ bool payload);
+
+ ProtocolAddress server_address_;
+ 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_
diff --git a/third_party/libwebrtc/p2p/base/turn_port_unittest.cc b/third_party/libwebrtc/p2p/base/turn_port_unittest.cc
new file mode 100644
index 0000000000..d63dd4a75c
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/turn_port_unittest.cc
@@ -0,0 +1,2026 @@
+/*
+ * 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.
+ */
+#if defined(WEBRTC_POSIX)
+#include <dirent.h>
+
+#include "absl/strings/string_view.h"
+#endif
+
+#include <list>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "absl/types/optional.h"
+#include "api/units/time_delta.h"
+#include "p2p/base/basic_packet_socket_factory.h"
+#include "p2p/base/connection.h"
+#include "p2p/base/mock_dns_resolving_packet_socket_factory.h"
+#include "p2p/base/p2p_constants.h"
+#include "p2p/base/port_allocator.h"
+#include "p2p/base/stun_port.h"
+#include "p2p/base/test_turn_customizer.h"
+#include "p2p/base/test_turn_server.h"
+#include "p2p/base/transport_description.h"
+#include "p2p/base/turn_port.h"
+#include "p2p/base/turn_server.h"
+#include "rtc_base/buffer.h"
+#include "rtc_base/byte_buffer.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/fake_clock.h"
+#include "rtc_base/gunit.h"
+#include "rtc_base/net_helper.h"
+#include "rtc_base/socket.h"
+#include "rtc_base/socket_address.h"
+#include "rtc_base/thread.h"
+#include "rtc_base/time_utils.h"
+#include "rtc_base/virtual_socket_server.h"
+#include "test/gtest.h"
+#include "test/scoped_key_value_config.h"
+
+namespace {
+using rtc::SocketAddress;
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InvokeArgument;
+using ::testing::Return;
+using ::testing::ReturnPointee;
+using ::testing::SetArgPointee;
+
+static const SocketAddress kLocalAddr1("11.11.11.11", 0);
+static const SocketAddress kLocalAddr2("22.22.22.22", 0);
+static const SocketAddress kLocalIPv6Addr("2401:fa00:4:1000:be30:5bff:fee5:c3",
+ 0);
+static const SocketAddress kLocalIPv6Addr2("2401:fa00:4:2000:be30:5bff:fee5:d4",
+ 0);
+static const SocketAddress kTurnUdpIntAddr("99.99.99.3",
+ cricket::TURN_SERVER_PORT);
+static const SocketAddress kTurnTcpIntAddr("99.99.99.4",
+ cricket::TURN_SERVER_PORT);
+static const SocketAddress kTurnUdpExtAddr("99.99.99.5", 0);
+static const SocketAddress kTurnAlternateIntAddr("99.99.99.6",
+ cricket::TURN_SERVER_PORT);
+// Port for redirecting to a TCP Web server. Should not work.
+static const SocketAddress kTurnDangerousAddr("99.99.99.7", 81);
+// Port 53 (the DNS port); should work.
+static const SocketAddress kTurnPort53Addr("99.99.99.7", 53);
+// Port 80 (the HTTP port); should work.
+static const SocketAddress kTurnPort80Addr("99.99.99.7", 80);
+// Port 443 (the HTTPS port); should work.
+static const SocketAddress kTurnPort443Addr("99.99.99.7", 443);
+// The default TURN server port.
+static const SocketAddress kTurnIntAddr("99.99.99.7",
+ cricket::TURN_SERVER_PORT);
+static const SocketAddress kTurnIPv6IntAddr(
+ "2400:4030:2:2c00:be30:abcd:efab:cdef",
+ cricket::TURN_SERVER_PORT);
+static const SocketAddress kTurnUdpIPv6IntAddr(
+ "2400:4030:1:2c00:be30:abcd:efab:cdef",
+ cricket::TURN_SERVER_PORT);
+static const SocketAddress kTurnInvalidAddr("www.google.invalid.", 3478);
+static const SocketAddress kTurnValidAddr("www.google.valid.", 3478);
+
+static const char kCandidateFoundation[] = "foundation";
+static const char kIceUfrag1[] = "TESTICEUFRAG0001";
+static const char kIceUfrag2[] = "TESTICEUFRAG0002";
+static const char kIcePwd1[] = "TESTICEPWD00000000000001";
+static const char kIcePwd2[] = "TESTICEPWD00000000000002";
+static const char kTurnUsername[] = "test";
+static const char kTurnPassword[] = "test";
+// This test configures the virtual socket server to simulate delay so that we
+// can verify operations take no more than the expected number of round trips.
+static constexpr unsigned int kSimulatedRtt = 50;
+// Connection destruction may happen asynchronously, but it should only
+// take one simulated clock tick.
+static constexpr unsigned int kConnectionDestructionDelay = 1;
+// This used to be 1 second, but that's not always enough for getaddrinfo().
+// See: https://bugs.chromium.org/p/webrtc/issues/detail?id=5191
+static constexpr unsigned int kResolverTimeout = 10000;
+
+constexpr uint64_t kTiebreakerDefault = 44444;
+
+static const cricket::ProtocolAddress kTurnUdpProtoAddr(kTurnUdpIntAddr,
+ cricket::PROTO_UDP);
+static const cricket::ProtocolAddress kTurnTcpProtoAddr(kTurnTcpIntAddr,
+ cricket::PROTO_TCP);
+static const cricket::ProtocolAddress kTurnTlsProtoAddr(kTurnTcpIntAddr,
+ cricket::PROTO_TLS);
+static const cricket::ProtocolAddress kTurnUdpIPv6ProtoAddr(kTurnUdpIPv6IntAddr,
+ cricket::PROTO_UDP);
+static const cricket::ProtocolAddress kTurnDangerousProtoAddr(
+ kTurnDangerousAddr,
+ cricket::PROTO_TCP);
+static const cricket::ProtocolAddress kTurnPort53ProtoAddr(kTurnPort53Addr,
+ cricket::PROTO_TCP);
+static const cricket::ProtocolAddress kTurnPort80ProtoAddr(kTurnPort80Addr,
+ cricket::PROTO_TCP);
+static const cricket::ProtocolAddress kTurnPort443ProtoAddr(kTurnPort443Addr,
+ cricket::PROTO_TCP);
+static const cricket::ProtocolAddress kTurnPortInvalidHostnameProtoAddr(
+ kTurnInvalidAddr,
+ cricket::PROTO_UDP);
+static const cricket::ProtocolAddress kTurnPortValidHostnameProtoAddr(
+ kTurnValidAddr,
+ cricket::PROTO_UDP);
+
+#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID)
+static int GetFDCount() {
+ struct dirent* dp;
+ int fd_count = 0;
+ DIR* dir = opendir("/proc/self/fd/");
+ while ((dp = readdir(dir)) != NULL) {
+ if (dp->d_name[0] == '.')
+ continue;
+ ++fd_count;
+ }
+ closedir(dir);
+ return fd_count;
+}
+#endif
+
+} // unnamed namespace
+
+namespace cricket {
+
+class TurnPortTestVirtualSocketServer : public rtc::VirtualSocketServer {
+ public:
+ TurnPortTestVirtualSocketServer() {
+ // This configures the virtual socket server to always add a simulated
+ // delay of exactly half of kSimulatedRtt.
+ set_delay_mean(kSimulatedRtt / 2);
+ UpdateDelayDistribution();
+ }
+
+ using rtc::VirtualSocketServer::LookupBinding;
+};
+
+class TestConnectionWrapper : public sigslot::has_slots<> {
+ public:
+ explicit TestConnectionWrapper(Connection* conn) : connection_(conn) {
+ conn->SignalDestroyed.connect(
+ this, &TestConnectionWrapper::OnConnectionDestroyed);
+ }
+
+ ~TestConnectionWrapper() {
+ if (connection_) {
+ connection_->SignalDestroyed.disconnect(this);
+ }
+ }
+
+ Connection* connection() { return connection_; }
+
+ private:
+ void OnConnectionDestroyed(Connection* conn) {
+ ASSERT_TRUE(conn == connection_);
+ connection_ = nullptr;
+ }
+
+ Connection* connection_;
+};
+
+// Note: This test uses a fake clock with a simulated network round trip
+// (between local port and TURN server) of kSimulatedRtt.
+class TurnPortTest : public ::testing::Test,
+ public TurnPort::CallbacksForTest,
+ public sigslot::has_slots<> {
+ public:
+ TurnPortTest()
+ : ss_(new TurnPortTestVirtualSocketServer()),
+ main_(ss_.get()),
+ turn_server_(&main_, ss_.get(), kTurnUdpIntAddr, kTurnUdpExtAddr),
+ socket_factory_(ss_.get()) {
+ // Some code uses "last received time == 0" to represent "nothing received
+ // so far", so we need to start the fake clock at a nonzero time...
+ // TODO(deadbeef): Fix this.
+ fake_clock_.AdvanceTime(webrtc::TimeDelta::Seconds(1));
+ }
+
+ void OnTurnPortComplete(Port* port) { turn_ready_ = true; }
+ void OnTurnPortError(Port* port) { turn_error_ = true; }
+ void OnCandidateError(Port* port,
+ const cricket::IceCandidateErrorEvent& event) {
+ error_event_ = event;
+ }
+ void OnTurnUnknownAddress(PortInterface* port,
+ const SocketAddress& addr,
+ ProtocolType proto,
+ IceMessage* msg,
+ const std::string& rf,
+ bool /*port_muxed*/) {
+ turn_unknown_address_ = true;
+ }
+ void OnTurnReadPacket(Connection* conn,
+ const char* data,
+ size_t size,
+ int64_t packet_time_us) {
+ turn_packets_.push_back(rtc::Buffer(data, size));
+ }
+ void OnUdpPortComplete(Port* port) { udp_ready_ = true; }
+ void OnUdpReadPacket(Connection* conn,
+ const char* data,
+ size_t size,
+ int64_t packet_time_us) {
+ udp_packets_.push_back(rtc::Buffer(data, size));
+ }
+ void OnSocketReadPacket(rtc::AsyncPacketSocket* socket,
+ const char* data,
+ size_t size,
+ const rtc::SocketAddress& remote_addr,
+ const int64_t& packet_time_us) {
+ turn_port_->HandleIncomingPacket(socket, data, size, remote_addr,
+ packet_time_us);
+ }
+ void OnTurnPortDestroyed(PortInterface* port) { turn_port_destroyed_ = true; }
+
+ // TurnPort::TestCallbacks
+ void OnTurnCreatePermissionResult(int code) override {
+ turn_create_permission_success_ = (code == 0);
+ }
+ void OnTurnRefreshResult(int code) override {
+ turn_refresh_success_ = (code == 0);
+ }
+ void OnTurnPortClosed() override { turn_port_closed_ = true; }
+
+ rtc::Socket* CreateServerSocket(const SocketAddress addr) {
+ rtc::Socket* socket = ss_->CreateSocket(AF_INET, SOCK_STREAM);
+ EXPECT_GE(socket->Bind(addr), 0);
+ EXPECT_GE(socket->Listen(5), 0);
+ return socket;
+ }
+
+ rtc::Network* MakeNetwork(const SocketAddress& addr) {
+ networks_.emplace_back("unittest", "unittest", addr.ipaddr(), 32);
+ networks_.back().AddIP(addr.ipaddr());
+ return &networks_.back();
+ }
+
+ bool CreateTurnPort(absl::string_view username,
+ absl::string_view password,
+ const ProtocolAddress& server_address) {
+ return CreateTurnPortWithAllParams(MakeNetwork(kLocalAddr1), username,
+ password, server_address);
+ }
+ bool CreateTurnPort(const rtc::SocketAddress& local_address,
+ absl::string_view username,
+ absl::string_view password,
+ const ProtocolAddress& server_address) {
+ return CreateTurnPortWithAllParams(MakeNetwork(local_address), username,
+ password, server_address);
+ }
+
+ bool CreateTurnPortWithNetwork(const rtc::Network* network,
+ absl::string_view username,
+ absl::string_view password,
+ const ProtocolAddress& server_address) {
+ return CreateTurnPortWithAllParams(network, username, password,
+ server_address);
+ }
+
+ // Version of CreateTurnPort that takes all possible parameters; all other
+ // helper methods call this, such that "SetIceRole" and "ConnectSignals" (and
+ // possibly other things in the future) only happen in one place.
+ bool CreateTurnPortWithAllParams(const rtc::Network* network,
+ absl::string_view username,
+ absl::string_view password,
+ const ProtocolAddress& server_address) {
+ RelayServerConfig config;
+ config.credentials = RelayCredentials(username, password);
+ CreateRelayPortArgs args;
+ args.network_thread = &main_;
+ args.socket_factory = socket_factory();
+ args.network = network;
+ args.username = kIceUfrag1;
+ args.password = kIcePwd1;
+ args.server_address = &server_address;
+ args.config = &config;
+ args.turn_customizer = turn_customizer_.get();
+ args.field_trials = &field_trials_;
+
+ turn_port_ = TurnPort::Create(args, 0, 0);
+ if (!turn_port_) {
+ return false;
+ }
+ // This TURN port will be the controlling.
+ turn_port_->SetIceRole(ICEROLE_CONTROLLING);
+ turn_port_->SetIceTiebreaker(kTiebreakerDefault);
+ ConnectSignals();
+
+ if (server_address.proto == cricket::PROTO_TLS) {
+ // The test TURN server has a self-signed certificate so will not pass
+ // the normal client validation. Instruct the client to ignore certificate
+ // errors for testing only.
+ turn_port_->SetTlsCertPolicy(
+ TlsCertPolicy::TLS_CERT_POLICY_INSECURE_NO_CHECK);
+ }
+ return true;
+ }
+
+ void CreateSharedTurnPort(absl::string_view username,
+ absl::string_view password,
+ const ProtocolAddress& server_address) {
+ RTC_CHECK(server_address.proto == PROTO_UDP);
+
+ if (!socket_) {
+ socket_.reset(socket_factory()->CreateUdpSocket(
+ rtc::SocketAddress(kLocalAddr1.ipaddr(), 0), 0, 0));
+ ASSERT_TRUE(socket_ != NULL);
+ socket_->SignalReadPacket.connect(this,
+ &TurnPortTest::OnSocketReadPacket);
+ }
+
+ RelayServerConfig config;
+ config.credentials = RelayCredentials(username, password);
+ CreateRelayPortArgs args;
+ args.network_thread = &main_;
+ args.socket_factory = socket_factory();
+ args.network = MakeNetwork(kLocalAddr1);
+ args.username = kIceUfrag1;
+ args.password = kIcePwd1;
+ args.server_address = &server_address;
+ args.config = &config;
+ args.turn_customizer = turn_customizer_.get();
+ args.field_trials = &field_trials_;
+ turn_port_ = TurnPort::Create(args, socket_.get());
+ // This TURN port will be the controlling.
+ turn_port_->SetIceRole(ICEROLE_CONTROLLING);
+ turn_port_->SetIceTiebreaker(kTiebreakerDefault);
+ ConnectSignals();
+ }
+
+ void ConnectSignals() {
+ turn_port_->SignalPortComplete.connect(this,
+ &TurnPortTest::OnTurnPortComplete);
+ turn_port_->SignalPortError.connect(this, &TurnPortTest::OnTurnPortError);
+ turn_port_->SignalCandidateError.connect(this,
+ &TurnPortTest::OnCandidateError);
+ turn_port_->SignalUnknownAddress.connect(
+ this, &TurnPortTest::OnTurnUnknownAddress);
+ turn_port_->SubscribePortDestroyed(
+ [this](PortInterface* port) { OnTurnPortDestroyed(port); });
+ turn_port_->SetCallbacksForTest(this);
+ }
+
+ void CreateUdpPort() { CreateUdpPort(kLocalAddr2); }
+
+ void CreateUdpPort(const SocketAddress& address) {
+ udp_port_ = UDPPort::Create(&main_, socket_factory(), MakeNetwork(address),
+ 0, 0, kIceUfrag2, kIcePwd2, false,
+ absl::nullopt, &field_trials_);
+ // UDP port will be controlled.
+ udp_port_->SetIceRole(ICEROLE_CONTROLLED);
+ udp_port_->SetIceTiebreaker(kTiebreakerDefault);
+ udp_port_->SignalPortComplete.connect(this,
+ &TurnPortTest::OnUdpPortComplete);
+ }
+
+ void PrepareTurnAndUdpPorts(ProtocolType protocol_type) {
+ // turn_port_ should have been created.
+ ASSERT_TRUE(turn_port_ != nullptr);
+ turn_port_->PrepareAddress();
+ ASSERT_TRUE_SIMULATED_WAIT(
+ turn_ready_, TimeToGetTurnCandidate(protocol_type), fake_clock_);
+
+ CreateUdpPort();
+ udp_port_->PrepareAddress();
+ ASSERT_TRUE_SIMULATED_WAIT(udp_ready_, kSimulatedRtt, fake_clock_);
+ }
+
+ // Returns the fake clock time to establish a connection over the given
+ // protocol.
+ int TimeToConnect(ProtocolType protocol_type) {
+ switch (protocol_type) {
+ case PROTO_TCP:
+ // The virtual socket server will delay by a fixed half a round trip
+ // for a TCP connection.
+ return kSimulatedRtt / 2;
+ case PROTO_TLS:
+ // TLS operates over TCP and additionally has a round of HELLO for
+ // negotiating ciphers and a round for exchanging certificates.
+ return 2 * kSimulatedRtt + TimeToConnect(PROTO_TCP);
+ case PROTO_UDP:
+ default:
+ // UDP requires no round trips to set up the connection.
+ return 0;
+ }
+ }
+
+ // Returns the total fake clock time to establish a connection with a TURN
+ // server over the given protocol and to allocate a TURN candidate.
+ int TimeToGetTurnCandidate(ProtocolType protocol_type) {
+ // For a simple allocation, the first Allocate message will return with an
+ // error asking for credentials and will succeed after the second Allocate
+ // message.
+ return 2 * kSimulatedRtt + TimeToConnect(protocol_type);
+ }
+
+ // Total fake clock time to do the following:
+ // 1. Connect to primary TURN server
+ // 2. Send Allocate and receive a redirect from the primary TURN server
+ // 3. Connect to alternate TURN server
+ // 4. Send Allocate and receive a request for credentials
+ // 5. Send Allocate with credentials and receive allocation
+ int TimeToGetAlternateTurnCandidate(ProtocolType protocol_type) {
+ return 3 * kSimulatedRtt + 2 * TimeToConnect(protocol_type);
+ }
+
+ bool CheckConnectionFailedAndPruned(Connection* conn) {
+ return conn && !conn->active() &&
+ conn->state() == IceCandidatePairState::FAILED;
+ }
+
+ // Checks that `turn_port_` has a nonempty set of connections and they are all
+ // failed and pruned.
+ bool CheckAllConnectionsFailedAndPruned() {
+ auto& connections = turn_port_->connections();
+ if (connections.empty()) {
+ return false;
+ }
+ for (const auto& kv : connections) {
+ if (!CheckConnectionFailedAndPruned(kv.second)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ void TestTurnAllocateSucceeds(unsigned int timeout) {
+ ASSERT_TRUE(turn_port_);
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(turn_ready_, timeout, fake_clock_);
+ ASSERT_EQ(1U, turn_port_->Candidates().size());
+ EXPECT_EQ(kTurnUdpExtAddr.ipaddr(),
+ turn_port_->Candidates()[0].address().ipaddr());
+ EXPECT_NE(0, turn_port_->Candidates()[0].address().port());
+ }
+
+ void TestReconstructedServerUrl(ProtocolType protocol_type,
+ absl::string_view expected_url) {
+ ASSERT_TRUE(turn_port_);
+ turn_port_->PrepareAddress();
+ ASSERT_TRUE_SIMULATED_WAIT(
+ turn_ready_, TimeToGetTurnCandidate(protocol_type), fake_clock_);
+ ASSERT_EQ(1U, turn_port_->Candidates().size());
+ EXPECT_EQ(turn_port_->Candidates()[0].url(), expected_url);
+ }
+
+ void TestTurnAlternateServer(ProtocolType protocol_type) {
+ std::vector<rtc::SocketAddress> redirect_addresses;
+ redirect_addresses.push_back(kTurnAlternateIntAddr);
+
+ TestTurnRedirector redirector(redirect_addresses);
+
+ turn_server_.AddInternalSocket(kTurnIntAddr, protocol_type);
+ turn_server_.AddInternalSocket(kTurnAlternateIntAddr, protocol_type);
+ turn_server_.set_redirect_hook(&redirector);
+ CreateTurnPort(kTurnUsername, kTurnPassword,
+ ProtocolAddress(kTurnIntAddr, protocol_type));
+
+ // Retrieve the address before we run the state machine.
+ const SocketAddress old_addr = turn_port_->server_address().address;
+
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(turn_ready_,
+ TimeToGetAlternateTurnCandidate(protocol_type),
+ fake_clock_);
+ // Retrieve the address again, the turn port's address should be
+ // changed.
+ const SocketAddress new_addr = turn_port_->server_address().address;
+ EXPECT_NE(old_addr, new_addr);
+ ASSERT_EQ(1U, turn_port_->Candidates().size());
+ EXPECT_EQ(kTurnUdpExtAddr.ipaddr(),
+ turn_port_->Candidates()[0].address().ipaddr());
+ EXPECT_NE(0, turn_port_->Candidates()[0].address().port());
+ }
+
+ void TestTurnAlternateServerV4toV6(ProtocolType protocol_type) {
+ std::vector<rtc::SocketAddress> redirect_addresses;
+ redirect_addresses.push_back(kTurnIPv6IntAddr);
+
+ TestTurnRedirector redirector(redirect_addresses);
+ turn_server_.AddInternalSocket(kTurnIntAddr, protocol_type);
+ turn_server_.set_redirect_hook(&redirector);
+ CreateTurnPort(kTurnUsername, kTurnPassword,
+ ProtocolAddress(kTurnIntAddr, protocol_type));
+ turn_port_->PrepareAddress();
+ // Need time to connect to TURN server, send Allocate request and receive
+ // redirect notice.
+ EXPECT_TRUE_SIMULATED_WAIT(
+ turn_error_, kSimulatedRtt + TimeToConnect(protocol_type), fake_clock_);
+ }
+
+ void TestTurnAlternateServerPingPong(ProtocolType protocol_type) {
+ std::vector<rtc::SocketAddress> redirect_addresses;
+ redirect_addresses.push_back(kTurnAlternateIntAddr);
+ redirect_addresses.push_back(kTurnIntAddr);
+
+ TestTurnRedirector redirector(redirect_addresses);
+
+ turn_server_.AddInternalSocket(kTurnIntAddr, protocol_type);
+ turn_server_.AddInternalSocket(kTurnAlternateIntAddr, protocol_type);
+ turn_server_.set_redirect_hook(&redirector);
+ CreateTurnPort(kTurnUsername, kTurnPassword,
+ ProtocolAddress(kTurnIntAddr, protocol_type));
+
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(turn_error_,
+ TimeToGetAlternateTurnCandidate(protocol_type),
+ fake_clock_);
+ ASSERT_EQ(0U, turn_port_->Candidates().size());
+ rtc::SocketAddress address;
+ // Verify that we have exhausted all alternate servers instead of
+ // failure caused by other errors.
+ EXPECT_FALSE(redirector.ShouldRedirect(address, &address));
+ }
+
+ void TestTurnAlternateServerDetectRepetition(ProtocolType protocol_type) {
+ std::vector<rtc::SocketAddress> redirect_addresses;
+ redirect_addresses.push_back(kTurnAlternateIntAddr);
+ redirect_addresses.push_back(kTurnAlternateIntAddr);
+
+ TestTurnRedirector redirector(redirect_addresses);
+
+ turn_server_.AddInternalSocket(kTurnIntAddr, protocol_type);
+ turn_server_.AddInternalSocket(kTurnAlternateIntAddr, protocol_type);
+ turn_server_.set_redirect_hook(&redirector);
+ CreateTurnPort(kTurnUsername, kTurnPassword,
+ ProtocolAddress(kTurnIntAddr, protocol_type));
+
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(turn_error_,
+ TimeToGetAlternateTurnCandidate(protocol_type),
+ fake_clock_);
+ ASSERT_EQ(0U, turn_port_->Candidates().size());
+ }
+
+ // A certain security exploit works by redirecting to a loopback address,
+ // which doesn't ever actually make sense. So redirects to loopback should
+ // be treated as errors.
+ // See: https://bugs.chromium.org/p/chromium/issues/detail?id=649118
+ void TestTurnAlternateServerLoopback(ProtocolType protocol_type, bool ipv6) {
+ const SocketAddress& local_address = ipv6 ? kLocalIPv6Addr : kLocalAddr1;
+ const SocketAddress& server_address =
+ ipv6 ? kTurnIPv6IntAddr : kTurnIntAddr;
+
+ std::vector<rtc::SocketAddress> redirect_addresses;
+ // Pick an unusual address in the 127.0.0.0/8 range to make sure more than
+ // 127.0.0.1 is covered.
+ SocketAddress loopback_address(ipv6 ? "::1" : "127.1.2.3",
+ TURN_SERVER_PORT);
+ redirect_addresses.push_back(loopback_address);
+
+ // Make a socket and bind it to the local port, to make extra sure no
+ // packet is sent to this address.
+ std::unique_ptr<rtc::Socket> loopback_socket(ss_->CreateSocket(
+ AF_INET, protocol_type == PROTO_UDP ? SOCK_DGRAM : SOCK_STREAM));
+ ASSERT_NE(nullptr, loopback_socket.get());
+ ASSERT_EQ(0, loopback_socket->Bind(loopback_address));
+ if (protocol_type == PROTO_TCP) {
+ ASSERT_EQ(0, loopback_socket->Listen(1));
+ }
+
+ TestTurnRedirector redirector(redirect_addresses);
+
+ turn_server_.AddInternalSocket(server_address, protocol_type);
+ turn_server_.set_redirect_hook(&redirector);
+ CreateTurnPort(local_address, kTurnUsername, kTurnPassword,
+ ProtocolAddress(server_address, protocol_type));
+
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(
+ turn_error_, TimeToGetTurnCandidate(protocol_type), fake_clock_);
+
+ // Wait for some extra time, and make sure no packets were received on the
+ // loopback port we created (or in the case of TCP, no connection attempt
+ // occurred).
+ SIMULATED_WAIT(false, kSimulatedRtt, fake_clock_);
+ if (protocol_type == PROTO_UDP) {
+ char buf[1];
+ EXPECT_EQ(-1, loopback_socket->Recv(&buf, 1, nullptr));
+ } else {
+ std::unique_ptr<rtc::Socket> accepted_socket(
+ loopback_socket->Accept(nullptr));
+ EXPECT_EQ(nullptr, accepted_socket.get());
+ }
+ }
+
+ void TestTurnConnection(ProtocolType protocol_type) {
+ // Create ports and prepare addresses.
+ PrepareTurnAndUdpPorts(protocol_type);
+
+ // Send ping from UDP to TURN.
+ ASSERT_GE(turn_port_->Candidates().size(), 1U);
+ Connection* conn1 = udp_port_->CreateConnection(turn_port_->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ ASSERT_TRUE(conn1 != NULL);
+ conn1->Ping(0);
+ SIMULATED_WAIT(!turn_unknown_address_, kSimulatedRtt * 2, fake_clock_);
+ EXPECT_FALSE(turn_unknown_address_);
+ EXPECT_FALSE(conn1->receiving());
+ EXPECT_EQ(Connection::STATE_WRITE_INIT, conn1->write_state());
+
+ // Send ping from TURN to UDP.
+ Connection* conn2 = turn_port_->CreateConnection(udp_port_->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ ASSERT_TRUE(conn2 != NULL);
+ ASSERT_TRUE_SIMULATED_WAIT(turn_create_permission_success_, kSimulatedRtt,
+ fake_clock_);
+ conn2->Ping(0);
+
+ // Two hops from TURN port to UDP port through TURN server, thus two RTTs.
+ EXPECT_EQ_SIMULATED_WAIT(Connection::STATE_WRITABLE, conn2->write_state(),
+ kSimulatedRtt * 2, fake_clock_);
+ EXPECT_TRUE(conn1->receiving());
+ EXPECT_TRUE(conn2->receiving());
+ EXPECT_EQ(Connection::STATE_WRITE_INIT, conn1->write_state());
+
+ // Send another ping from UDP to TURN.
+ conn1->Ping(0);
+ EXPECT_EQ_SIMULATED_WAIT(Connection::STATE_WRITABLE, conn1->write_state(),
+ kSimulatedRtt * 2, fake_clock_);
+ EXPECT_TRUE(conn2->receiving());
+ }
+
+ void TestDestroyTurnConnection() {
+ PrepareTurnAndUdpPorts(PROTO_UDP);
+
+ // Create connections on both ends.
+ Connection* conn1 = udp_port_->CreateConnection(turn_port_->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ Connection* conn2 = turn_port_->CreateConnection(udp_port_->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+
+ // Increased to 10 minutes, to ensure that the TurnEntry times out before
+ // the TurnPort.
+ turn_port_->set_timeout_delay(10 * 60 * 1000);
+
+ ASSERT_TRUE(conn2 != NULL);
+ ASSERT_TRUE_SIMULATED_WAIT(turn_create_permission_success_, kSimulatedRtt,
+ fake_clock_);
+ // Make sure turn connection can receive.
+ conn1->Ping(0);
+ EXPECT_EQ_SIMULATED_WAIT(Connection::STATE_WRITABLE, conn1->write_state(),
+ kSimulatedRtt * 2, fake_clock_);
+ EXPECT_FALSE(turn_unknown_address_);
+
+ // Destroy the connection on the TURN port. The TurnEntry still exists, so
+ // the TURN port should still process a ping from an unknown address.
+ turn_port_->DestroyConnection(conn2);
+
+ conn1->Ping(0);
+ EXPECT_TRUE_SIMULATED_WAIT(turn_unknown_address_, kSimulatedRtt,
+ fake_clock_);
+
+ // Wait for TurnEntry to expire. Timeout is 5 minutes.
+ // Expect that it still processes an incoming ping and signals the
+ // unknown address.
+ turn_unknown_address_ = false;
+ fake_clock_.AdvanceTime(webrtc::TimeDelta::Seconds(5 * 60));
+
+ // TODO(chromium:1395625): When `TurnPort` doesn't find connection objects
+ // for incoming packets, it forwards calls to the parent class, `Port`. This
+ // happens inside `TurnPort::DispatchPacket`. The `Port` implementation may
+ // need to send a binding error back over a connection which, unless the
+ // `TurnPort` implementation handles it, could result in a null deref.
+ // This special check tests if dispatching messages via `TurnPort` for which
+ // there's no connection, results in a no-op rather than crashing.
+ // See `TurnPort::SendBindingErrorResponse` for the check.
+ // This should probably be done in a neater way both from a testing pov and
+ // how incoming messages are handled in the `Port` class, when an assumption
+ // is made about connection objects existing and when those assumptions
+ // may not hold.
+ std::string pwd = conn1->remote_password_for_test();
+ conn1->set_remote_password_for_test("bad");
+ auto msg = conn1->BuildPingRequestForTest();
+
+ rtc::ByteBufferWriter buf;
+ msg->Write(&buf);
+ conn1->Send(buf.Data(), buf.Length(), options);
+
+ // Now restore the password before continuing.
+ conn1->set_remote_password_for_test(pwd);
+
+ conn1->Ping(0);
+ EXPECT_TRUE_SIMULATED_WAIT(turn_unknown_address_, kSimulatedRtt,
+ fake_clock_);
+
+ // If the connection is created again, it will start to receive pings.
+ conn2 = turn_port_->CreateConnection(udp_port_->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ conn1->Ping(0);
+ EXPECT_TRUE_SIMULATED_WAIT(conn2->receiving(), kSimulatedRtt, fake_clock_);
+ }
+
+ void TestTurnSendData(ProtocolType protocol_type) {
+ PrepareTurnAndUdpPorts(protocol_type);
+
+ // Create connections and send pings.
+ Connection* conn1 = turn_port_->CreateConnection(udp_port_->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ Connection* conn2 = udp_port_->CreateConnection(turn_port_->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ ASSERT_TRUE(conn1 != NULL);
+ ASSERT_TRUE(conn2 != NULL);
+ conn1->SignalReadPacket.connect(static_cast<TurnPortTest*>(this),
+ &TurnPortTest::OnTurnReadPacket);
+ conn2->SignalReadPacket.connect(static_cast<TurnPortTest*>(this),
+ &TurnPortTest::OnUdpReadPacket);
+ conn1->Ping(0);
+ EXPECT_EQ_SIMULATED_WAIT(Connection::STATE_WRITABLE, conn1->write_state(),
+ kSimulatedRtt * 2, fake_clock_);
+ conn2->Ping(0);
+ EXPECT_EQ_SIMULATED_WAIT(Connection::STATE_WRITABLE, conn2->write_state(),
+ kSimulatedRtt * 2, fake_clock_);
+
+ // Send some data.
+ size_t num_packets = 256;
+ for (size_t i = 0; i < num_packets; ++i) {
+ unsigned char buf[256] = {0};
+ for (size_t j = 0; j < i + 1; ++j) {
+ buf[j] = 0xFF - static_cast<unsigned char>(j);
+ }
+ conn1->Send(buf, i + 1, options);
+ conn2->Send(buf, i + 1, options);
+ SIMULATED_WAIT(false, kSimulatedRtt, fake_clock_);
+ }
+
+ // Check the data.
+ ASSERT_EQ(num_packets, turn_packets_.size());
+ ASSERT_EQ(num_packets, udp_packets_.size());
+ for (size_t i = 0; i < num_packets; ++i) {
+ EXPECT_EQ(i + 1, turn_packets_[i].size());
+ EXPECT_EQ(i + 1, udp_packets_[i].size());
+ EXPECT_EQ(turn_packets_[i], udp_packets_[i]);
+ }
+ }
+
+ // Test that a TURN allocation is released when the port is closed.
+ void TestTurnReleaseAllocation(ProtocolType protocol_type) {
+ PrepareTurnAndUdpPorts(protocol_type);
+ turn_port_.reset();
+ EXPECT_EQ_SIMULATED_WAIT(0U, turn_server_.server()->allocations().size(),
+ kSimulatedRtt, fake_clock_);
+ }
+
+ // Test that the TURN allocation is released by sending a refresh request
+ // with lifetime 0 when Release is called.
+ void TestTurnGracefulReleaseAllocation(ProtocolType protocol_type) {
+ PrepareTurnAndUdpPorts(protocol_type);
+
+ // Create connections and send pings.
+ Connection* conn1 = turn_port_->CreateConnection(udp_port_->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ Connection* conn2 = udp_port_->CreateConnection(turn_port_->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ ASSERT_TRUE(conn1 != NULL);
+ ASSERT_TRUE(conn2 != NULL);
+ conn1->SignalReadPacket.connect(static_cast<TurnPortTest*>(this),
+ &TurnPortTest::OnTurnReadPacket);
+ conn2->SignalReadPacket.connect(static_cast<TurnPortTest*>(this),
+ &TurnPortTest::OnUdpReadPacket);
+ conn1->Ping(0);
+ EXPECT_EQ_SIMULATED_WAIT(Connection::STATE_WRITABLE, conn1->write_state(),
+ kSimulatedRtt * 2, fake_clock_);
+ conn2->Ping(0);
+ EXPECT_EQ_SIMULATED_WAIT(Connection::STATE_WRITABLE, conn2->write_state(),
+ kSimulatedRtt * 2, fake_clock_);
+
+ // Send some data from Udp to TurnPort.
+ unsigned char buf[256] = {0};
+ conn2->Send(buf, sizeof(buf), options);
+
+ // Now release the TurnPort allocation.
+ // This will send a REFRESH with lifetime 0 to server.
+ turn_port_->Release();
+
+ // Wait for the TurnPort to signal closed.
+ ASSERT_TRUE_SIMULATED_WAIT(turn_port_closed_, kSimulatedRtt, fake_clock_);
+
+ // But the data should have arrived first.
+ ASSERT_EQ(1ul, turn_packets_.size());
+ EXPECT_EQ(sizeof(buf), turn_packets_[0].size());
+
+ // The allocation is released at server.
+ EXPECT_EQ(0U, turn_server_.server()->allocations().size());
+ }
+
+ protected:
+ virtual rtc::PacketSocketFactory* socket_factory() {
+ return &socket_factory_;
+ }
+
+ webrtc::test::ScopedKeyValueConfig field_trials_;
+ rtc::ScopedFakeClock fake_clock_;
+ // When a "create port" helper method is called with an IP, we create a
+ // Network with that IP and add it to this list. Using a list instead of a
+ // vector so that when it grows, pointers aren't invalidated.
+ std::list<rtc::Network> networks_;
+ std::unique_ptr<TurnPortTestVirtualSocketServer> ss_;
+ rtc::AutoSocketServerThread main_;
+ std::unique_ptr<rtc::AsyncPacketSocket> socket_;
+ TestTurnServer turn_server_;
+ std::unique_ptr<TurnPort> turn_port_;
+ std::unique_ptr<UDPPort> udp_port_;
+ bool turn_ready_ = false;
+ bool turn_error_ = false;
+ bool turn_unknown_address_ = false;
+ bool turn_create_permission_success_ = false;
+ bool turn_port_closed_ = false;
+ bool turn_port_destroyed_ = false;
+ bool udp_ready_ = false;
+ bool test_finish_ = false;
+ bool turn_refresh_success_ = false;
+ std::vector<rtc::Buffer> turn_packets_;
+ std::vector<rtc::Buffer> udp_packets_;
+ rtc::PacketOptions options;
+ std::unique_ptr<webrtc::TurnCustomizer> turn_customizer_;
+ cricket::IceCandidateErrorEvent error_event_;
+
+ private:
+ rtc::BasicPacketSocketFactory socket_factory_;
+};
+
+TEST_F(TurnPortTest, TestTurnPortType) {
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ EXPECT_EQ(cricket::RELAY_PORT_TYPE, turn_port_->Type());
+}
+
+// Tests that the URL of the servers can be correctly reconstructed when
+// gathering the candidates.
+TEST_F(TurnPortTest, TestReconstructedServerUrlForUdpIPv4) {
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ TestReconstructedServerUrl(PROTO_UDP, "turn:99.99.99.3:3478?transport=udp");
+}
+
+TEST_F(TurnPortTest, TestReconstructedServerUrlForUdpIPv6) {
+ turn_server_.AddInternalSocket(kTurnUdpIPv6IntAddr, PROTO_UDP);
+ CreateTurnPort(kLocalIPv6Addr, kTurnUsername, kTurnPassword,
+ kTurnUdpIPv6ProtoAddr);
+ TestReconstructedServerUrl(
+ PROTO_UDP,
+ "turn:2400:4030:1:2c00:be30:abcd:efab:cdef:3478?transport=udp");
+}
+
+TEST_F(TurnPortTest, TestReconstructedServerUrlForTcp) {
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TCP);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr);
+ TestReconstructedServerUrl(PROTO_TCP, "turn:99.99.99.4:3478?transport=tcp");
+}
+
+TEST_F(TurnPortTest, TestReconstructedServerUrlForTls) {
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TLS);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTlsProtoAddr);
+ TestReconstructedServerUrl(PROTO_TLS, "turns:99.99.99.4:3478?transport=tcp");
+}
+
+TEST_F(TurnPortTest, TestReconstructedServerUrlForHostname) {
+ CreateTurnPort(kTurnUsername, kTurnPassword,
+ kTurnPortInvalidHostnameProtoAddr);
+ // This test follows the pattern from TestTurnTcpOnAddressResolveFailure.
+ // As VSS doesn't provide DNS resolution, name resolve will fail,
+ // the error will be set and contain the url.
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_error_, kResolverTimeout);
+ std::string server_url =
+ "turn:" + kTurnInvalidAddr.ToString() + "?transport=udp";
+ ASSERT_EQ(error_event_.url, server_url);
+}
+
+// Do a normal TURN allocation.
+TEST_F(TurnPortTest, TestTurnAllocate) {
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ EXPECT_EQ(0, turn_port_->SetOption(rtc::Socket::OPT_SNDBUF, 10 * 1024));
+ TestTurnAllocateSucceeds(kSimulatedRtt * 2);
+}
+
+class TurnLoggingIdValidator : public StunMessageObserver {
+ public:
+ explicit TurnLoggingIdValidator(const char* expect_val)
+ : expect_val_(expect_val) {}
+ ~TurnLoggingIdValidator() {}
+ void ReceivedMessage(const TurnMessage* msg) override {
+ if (msg->type() == cricket::STUN_ALLOCATE_REQUEST) {
+ const StunByteStringAttribute* attr =
+ msg->GetByteString(cricket::STUN_ATTR_TURN_LOGGING_ID);
+ if (expect_val_) {
+ ASSERT_NE(nullptr, attr);
+ ASSERT_EQ(expect_val_, attr->string_view());
+ } else {
+ EXPECT_EQ(nullptr, attr);
+ }
+ }
+ }
+ void ReceivedChannelData(const char* data, size_t size) override {}
+
+ private:
+ const char* expect_val_;
+};
+
+TEST_F(TurnPortTest, TestTurnAllocateWithLoggingId) {
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ turn_port_->SetTurnLoggingId("KESO");
+ turn_server_.server()->SetStunMessageObserver(
+ std::make_unique<TurnLoggingIdValidator>("KESO"));
+ TestTurnAllocateSucceeds(kSimulatedRtt * 2);
+}
+
+TEST_F(TurnPortTest, TestTurnAllocateWithoutLoggingId) {
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ turn_server_.server()->SetStunMessageObserver(
+ std::make_unique<TurnLoggingIdValidator>(nullptr));
+ TestTurnAllocateSucceeds(kSimulatedRtt * 2);
+}
+
+// Test bad credentials.
+TEST_F(TurnPortTest, TestTurnBadCredentials) {
+ CreateTurnPort(kTurnUsername, "bad", kTurnUdpProtoAddr);
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(turn_error_, kSimulatedRtt * 3, fake_clock_);
+ ASSERT_EQ(0U, turn_port_->Candidates().size());
+ EXPECT_EQ_SIMULATED_WAIT(error_event_.error_code, STUN_ERROR_UNAUTHORIZED,
+ kSimulatedRtt * 3, fake_clock_);
+ EXPECT_EQ(error_event_.error_text, "Unauthorized");
+}
+
+// Testing a normal UDP allocation using TCP connection.
+TEST_F(TurnPortTest, TestTurnTcpAllocate) {
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TCP);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr);
+ EXPECT_EQ(0, turn_port_->SetOption(rtc::Socket::OPT_SNDBUF, 10 * 1024));
+ TestTurnAllocateSucceeds(kSimulatedRtt * 3);
+}
+
+// Test case for WebRTC issue 3927 where a proxy binds to the local host address
+// instead the address that TurnPort originally bound to. The candidate pair
+// impacted by this behavior should still be used.
+TEST_F(TurnPortTest, TestTurnTcpAllocationWhenProxyChangesAddressToLocalHost) {
+ SocketAddress local_address("127.0.0.1", 0);
+ // After calling this, when TurnPort attempts to get a socket bound to
+ // kLocalAddr, it will end up using localhost instead.
+ ss_->SetAlternativeLocalAddress(kLocalAddr1.ipaddr(), local_address.ipaddr());
+
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TCP);
+ CreateTurnPort(kLocalAddr1, kTurnUsername, kTurnPassword, kTurnTcpProtoAddr);
+ EXPECT_EQ(0, turn_port_->SetOption(rtc::Socket::OPT_SNDBUF, 10 * 1024));
+ TestTurnAllocateSucceeds(kSimulatedRtt * 3);
+
+ // Verify that the socket actually used localhost, otherwise this test isn't
+ // doing what it meant to.
+ ASSERT_EQ(local_address.ipaddr(),
+ turn_port_->Candidates()[0].related_address().ipaddr());
+}
+
+// If the address the socket ends up bound to does not match any address of the
+// TurnPort's Network, then the socket should be discarded and no candidates
+// should be signaled. In the context of ICE, where one TurnPort is created for
+// each Network, when this happens it's likely that the unexpected address is
+// associated with some other Network, which another TurnPort is already
+// covering.
+TEST_F(TurnPortTest,
+ TurnTcpAllocationDiscardedIfBoundAddressDoesNotMatchNetwork) {
+ // Sockets bound to kLocalAddr1 will actually end up with kLocalAddr2.
+ ss_->SetAlternativeLocalAddress(kLocalAddr1.ipaddr(), kLocalAddr2.ipaddr());
+
+ // Set up TURN server to use TCP (this logic only exists for TCP).
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TCP);
+
+ // Create TURN port and tell it to start allocation.
+ CreateTurnPort(kLocalAddr1, kTurnUsername, kTurnPassword, kTurnTcpProtoAddr);
+ turn_port_->PrepareAddress();
+
+ // Shouldn't take more than 1 RTT to realize the bound address isn't the one
+ // expected.
+ EXPECT_TRUE_SIMULATED_WAIT(turn_error_, kSimulatedRtt, fake_clock_);
+ EXPECT_EQ_SIMULATED_WAIT(error_event_.error_code, STUN_ERROR_GLOBAL_FAILURE,
+ kSimulatedRtt, fake_clock_);
+ ASSERT_NE(error_event_.error_text.find('.'), std::string::npos);
+ ASSERT_NE(error_event_.address.find(kLocalAddr2.HostAsSensitiveURIString()),
+ std::string::npos);
+ ASSERT_NE(error_event_.port, 0);
+ std::string server_url =
+ "turn:" + kTurnTcpIntAddr.ToString() + "?transport=tcp";
+ ASSERT_EQ(error_event_.url, server_url);
+}
+
+// A caveat for the above logic: if the socket ends up bound to one of the IPs
+// associated with the Network, just not the "best" one, this is ok.
+TEST_F(TurnPortTest, TurnTcpAllocationNotDiscardedIfNotBoundToBestIP) {
+ // Sockets bound to kLocalAddr1 will actually end up with kLocalAddr2.
+ ss_->SetAlternativeLocalAddress(kLocalAddr1.ipaddr(), kLocalAddr2.ipaddr());
+
+ // Set up a network with kLocalAddr1 as the "best" IP, and kLocalAddr2 as an
+ // alternate.
+ rtc::Network* network = MakeNetwork(kLocalAddr1);
+ network->AddIP(kLocalAddr2.ipaddr());
+ ASSERT_EQ(kLocalAddr1.ipaddr(), network->GetBestIP());
+
+ // Set up TURN server to use TCP (this logic only exists for TCP).
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TCP);
+
+ // Create TURN port using our special Network, and tell it to start
+ // allocation.
+ CreateTurnPortWithNetwork(network, kTurnUsername, kTurnPassword,
+ kTurnTcpProtoAddr);
+ turn_port_->PrepareAddress();
+
+ // Candidate should be gathered as normally.
+ EXPECT_TRUE_SIMULATED_WAIT(turn_ready_, kSimulatedRtt * 3, fake_clock_);
+ ASSERT_EQ(1U, turn_port_->Candidates().size());
+
+ // Verify that the socket actually used the alternate address, otherwise this
+ // test isn't doing what it meant to.
+ ASSERT_EQ(kLocalAddr2.ipaddr(),
+ turn_port_->Candidates()[0].related_address().ipaddr());
+}
+
+// Regression test for crbug.com/webrtc/8972, caused by buggy comparison
+// between rtc::IPAddress and rtc::InterfaceAddress.
+TEST_F(TurnPortTest, TCPPortNotDiscardedIfBoundToTemporaryIP) {
+ networks_.emplace_back("unittest", "unittest", kLocalIPv6Addr.ipaddr(), 32);
+ networks_.back().AddIP(rtc::InterfaceAddress(
+ kLocalIPv6Addr.ipaddr(), rtc::IPV6_ADDRESS_FLAG_TEMPORARY));
+
+ // Set up TURN server to use TCP (this logic only exists for TCP).
+ turn_server_.AddInternalSocket(kTurnIPv6IntAddr, PROTO_TCP);
+
+ // Create TURN port using our special Network, and tell it to start
+ // allocation.
+ CreateTurnPortWithNetwork(
+ &networks_.back(), kTurnUsername, kTurnPassword,
+ cricket::ProtocolAddress(kTurnIPv6IntAddr, PROTO_TCP));
+ turn_port_->PrepareAddress();
+
+ // Candidate should be gathered as normally.
+ EXPECT_TRUE_SIMULATED_WAIT(turn_ready_, kSimulatedRtt * 3, fake_clock_);
+ ASSERT_EQ(1U, turn_port_->Candidates().size());
+}
+
+// Testing turn port will attempt to create TCP socket on address resolution
+// failure.
+TEST_F(TurnPortTest, TestTurnTcpOnAddressResolveFailure) {
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TCP);
+ CreateTurnPort(kTurnUsername, kTurnPassword,
+ ProtocolAddress(kTurnInvalidAddr, PROTO_TCP));
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_error_, kResolverTimeout);
+ // As VSS doesn't provide DNS resolution, name resolve will fail. TurnPort
+ // will proceed in creating a TCP socket which will fail as there is no
+ // server on the above domain and error will be set to SOCKET_ERROR.
+ EXPECT_EQ(SOCKET_ERROR, turn_port_->error());
+ EXPECT_EQ_SIMULATED_WAIT(error_event_.error_code, SERVER_NOT_REACHABLE_ERROR,
+ kSimulatedRtt, fake_clock_);
+ std::string server_url =
+ "turn:" + kTurnInvalidAddr.ToString() + "?transport=tcp";
+ ASSERT_EQ(error_event_.url, server_url);
+}
+
+// Testing turn port will attempt to create TLS socket on address resolution
+// failure.
+TEST_F(TurnPortTest, TestTurnTlsOnAddressResolveFailure) {
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TLS);
+ CreateTurnPort(kTurnUsername, kTurnPassword,
+ ProtocolAddress(kTurnInvalidAddr, PROTO_TLS));
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_error_, kResolverTimeout);
+ EXPECT_EQ(SOCKET_ERROR, turn_port_->error());
+}
+
+// In case of UDP on address resolve failure, TurnPort will not create socket
+// and return allocate failure.
+TEST_F(TurnPortTest, TestTurnUdpOnAddressResolveFailure) {
+ CreateTurnPort(kTurnUsername, kTurnPassword,
+ ProtocolAddress(kTurnInvalidAddr, PROTO_UDP));
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_error_, kResolverTimeout);
+ // Error from turn port will not be socket error.
+ EXPECT_NE(SOCKET_ERROR, turn_port_->error());
+}
+
+// Try to do a TURN allocation with an invalid password.
+TEST_F(TurnPortTest, TestTurnAllocateBadPassword) {
+ CreateTurnPort(kTurnUsername, "bad", kTurnUdpProtoAddr);
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(turn_error_, kSimulatedRtt * 2, fake_clock_);
+ ASSERT_EQ(0U, turn_port_->Candidates().size());
+}
+
+// Tests that TURN port nonce will be reset when receiving an ALLOCATE MISMATCH
+// error.
+TEST_F(TurnPortTest, TestTurnAllocateNonceResetAfterAllocateMismatch) {
+ // Do a normal allocation first.
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(turn_ready_, kSimulatedRtt * 2, fake_clock_);
+ rtc::SocketAddress first_addr(turn_port_->socket()->GetLocalAddress());
+ // Destroy the turnport while keeping the drop probability to 1 to
+ // suppress the release of the allocation at the server.
+ ss_->set_drop_probability(1.0);
+ turn_port_.reset();
+ SIMULATED_WAIT(false, kSimulatedRtt, fake_clock_);
+ ss_->set_drop_probability(0.0);
+
+ // Force the socket server to assign the same port.
+ ss_->SetNextPortForTesting(first_addr.port());
+ turn_ready_ = false;
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+
+ // It is expected that the turn port will first get a nonce from the server
+ // using timestamp `ts_before` but then get an allocate mismatch error and
+ // receive an even newer nonce based on the system clock. `ts_before` is
+ // chosen so that the two NONCEs generated by the server will be different.
+ int64_t ts_before = rtc::TimeMillis() - 1;
+ std::string first_nonce =
+ turn_server_.server()->SetTimestampForNextNonce(ts_before);
+ turn_port_->PrepareAddress();
+
+ // Four round trips; first we'll get "stale nonce", then
+ // "allocate mismatch", then "stale nonce" again, then finally it will
+ // succeed.
+ EXPECT_TRUE_SIMULATED_WAIT(turn_ready_, kSimulatedRtt * 4, fake_clock_);
+ EXPECT_NE(first_nonce, turn_port_->nonce());
+}
+
+// Tests that a new local address is created after
+// STUN_ERROR_ALLOCATION_MISMATCH.
+TEST_F(TurnPortTest, TestTurnAllocateMismatch) {
+ // Do a normal allocation first.
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(turn_ready_, kSimulatedRtt * 2, fake_clock_);
+ rtc::SocketAddress first_addr(turn_port_->socket()->GetLocalAddress());
+
+ // Clear connected_ flag on turnport to suppress the release of
+ // the allocation.
+ turn_port_->OnSocketClose(turn_port_->socket(), 0);
+
+ // Forces the socket server to assign the same port.
+ ss_->SetNextPortForTesting(first_addr.port());
+
+ turn_ready_ = false;
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ turn_port_->PrepareAddress();
+
+ // Verifies that the new port has the same address.
+ EXPECT_EQ(first_addr, turn_port_->socket()->GetLocalAddress());
+
+ // Four round trips; first we'll get "stale nonce", then
+ // "allocate mismatch", then "stale nonce" again, then finally it will
+ // succeed.
+ EXPECT_TRUE_SIMULATED_WAIT(turn_ready_, kSimulatedRtt * 4, fake_clock_);
+
+ // Verifies that the new port has a different address now.
+ EXPECT_NE(first_addr, turn_port_->socket()->GetLocalAddress());
+
+ // Verify that all packets received from the shared socket are ignored.
+ std::string test_packet = "Test packet";
+ EXPECT_FALSE(turn_port_->HandleIncomingPacket(
+ socket_.get(), test_packet.data(), test_packet.size(),
+ rtc::SocketAddress(kTurnUdpExtAddr.ipaddr(), 0), rtc::TimeMicros()));
+}
+
+// Tests that a shared-socket-TurnPort creates its own socket after
+// STUN_ERROR_ALLOCATION_MISMATCH.
+TEST_F(TurnPortTest, TestSharedSocketAllocateMismatch) {
+ // Do a normal allocation first.
+ CreateSharedTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(turn_ready_, kSimulatedRtt * 2, fake_clock_);
+ rtc::SocketAddress first_addr(turn_port_->socket()->GetLocalAddress());
+
+ // Clear connected_ flag on turnport to suppress the release of
+ // the allocation.
+ turn_port_->OnSocketClose(turn_port_->socket(), 0);
+
+ turn_ready_ = false;
+ CreateSharedTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+
+ // Verifies that the new port has the same address.
+ EXPECT_EQ(first_addr, turn_port_->socket()->GetLocalAddress());
+ EXPECT_TRUE(turn_port_->SharedSocket());
+
+ turn_port_->PrepareAddress();
+ // Extra 2 round trips due to allocate mismatch.
+ EXPECT_TRUE_SIMULATED_WAIT(turn_ready_, kSimulatedRtt * 4, fake_clock_);
+
+ // Verifies that the new port has a different address now.
+ EXPECT_NE(first_addr, turn_port_->socket()->GetLocalAddress());
+ EXPECT_FALSE(turn_port_->SharedSocket());
+}
+
+TEST_F(TurnPortTest, TestTurnTcpAllocateMismatch) {
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TCP);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr);
+
+ // Do a normal allocation first.
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(turn_ready_, kSimulatedRtt * 3, fake_clock_);
+ rtc::SocketAddress first_addr(turn_port_->socket()->GetLocalAddress());
+
+ // Clear connected_ flag on turnport to suppress the release of
+ // the allocation.
+ turn_port_->OnSocketClose(turn_port_->socket(), 0);
+
+ // Forces the socket server to assign the same port.
+ ss_->SetNextPortForTesting(first_addr.port());
+
+ turn_ready_ = false;
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr);
+ turn_port_->PrepareAddress();
+
+ // Verifies that the new port has the same address.
+ EXPECT_EQ(first_addr, turn_port_->socket()->GetLocalAddress());
+
+ // Extra 2 round trips due to allocate mismatch.
+ EXPECT_TRUE_SIMULATED_WAIT(turn_ready_, kSimulatedRtt * 5, fake_clock_);
+
+ // Verifies that the new port has a different address now.
+ EXPECT_NE(first_addr, turn_port_->socket()->GetLocalAddress());
+}
+
+TEST_F(TurnPortTest, TestRefreshRequestGetsErrorResponse) {
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ PrepareTurnAndUdpPorts(PROTO_UDP);
+ turn_port_->CreateConnection(udp_port_->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ // Set bad credentials.
+ RelayCredentials bad_credentials("bad_user", "bad_pwd");
+ turn_port_->set_credentials(bad_credentials);
+ turn_refresh_success_ = false;
+ // This sends out the first RefreshRequest with correct credentials.
+ // When this succeeds, it will schedule a new RefreshRequest with the bad
+ // credential.
+ turn_port_->request_manager().FlushForTest(TURN_REFRESH_REQUEST);
+ EXPECT_TRUE_SIMULATED_WAIT(turn_refresh_success_, kSimulatedRtt, fake_clock_);
+ // Flush it again, it will receive a bad response.
+ turn_port_->request_manager().FlushForTest(TURN_REFRESH_REQUEST);
+ EXPECT_TRUE_SIMULATED_WAIT(!turn_refresh_success_, kSimulatedRtt,
+ fake_clock_);
+ EXPECT_FALSE(turn_port_->connected());
+ EXPECT_TRUE(CheckAllConnectionsFailedAndPruned());
+ EXPECT_FALSE(turn_port_->HasRequests());
+}
+
+// Test that TurnPort will not handle any incoming packets once it has been
+// closed.
+TEST_F(TurnPortTest, TestStopProcessingPacketsAfterClosed) {
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ PrepareTurnAndUdpPorts(PROTO_UDP);
+ Connection* conn1 = turn_port_->CreateConnection(udp_port_->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ Connection* conn2 = udp_port_->CreateConnection(turn_port_->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ ASSERT_TRUE(conn1 != NULL);
+ ASSERT_TRUE(conn2 != NULL);
+ // Make sure conn2 is writable.
+ conn2->Ping(0);
+ EXPECT_EQ_SIMULATED_WAIT(Connection::STATE_WRITABLE, conn2->write_state(),
+ kSimulatedRtt * 2, fake_clock_);
+
+ turn_port_->CloseForTest();
+ SIMULATED_WAIT(false, kSimulatedRtt, fake_clock_);
+ turn_unknown_address_ = false;
+ conn2->Ping(0);
+ SIMULATED_WAIT(false, kSimulatedRtt, fake_clock_);
+ // Since the turn port does not handle packets any more, it should not
+ // SignalUnknownAddress.
+ EXPECT_FALSE(turn_unknown_address_);
+}
+
+// Test that CreateConnection will return null if port becomes disconnected.
+TEST_F(TurnPortTest, TestCreateConnectionWhenSocketClosed) {
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TCP);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr);
+ PrepareTurnAndUdpPorts(PROTO_TCP);
+ // Create a connection.
+ Connection* conn1 = turn_port_->CreateConnection(udp_port_->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ ASSERT_TRUE(conn1 != NULL);
+
+ // Close the socket and create a connection again.
+ turn_port_->OnSocketClose(turn_port_->socket(), 1);
+ conn1 = turn_port_->CreateConnection(udp_port_->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ ASSERT_TRUE(conn1 == NULL);
+}
+
+// Tests that when a TCP socket is closed, the respective TURN connection will
+// be destroyed.
+TEST_F(TurnPortTest, TestSocketCloseWillDestroyConnection) {
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TCP);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr);
+ PrepareTurnAndUdpPorts(PROTO_TCP);
+ Connection* conn = turn_port_->CreateConnection(udp_port_->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ EXPECT_NE(nullptr, conn);
+ EXPECT_TRUE(!turn_port_->connections().empty());
+ turn_port_->socket()->NotifyClosedForTest(1);
+ EXPECT_TRUE_SIMULATED_WAIT(turn_port_->connections().empty(),
+ kConnectionDestructionDelay, fake_clock_);
+}
+
+// Test try-alternate-server feature.
+TEST_F(TurnPortTest, TestTurnAlternateServerUDP) {
+ TestTurnAlternateServer(PROTO_UDP);
+}
+
+TEST_F(TurnPortTest, TestTurnAlternateServerTCP) {
+ TestTurnAlternateServer(PROTO_TCP);
+}
+
+TEST_F(TurnPortTest, TestTurnAlternateServerTLS) {
+ TestTurnAlternateServer(PROTO_TLS);
+}
+
+// Test that we fail when we redirect to an address different from
+// current IP family.
+TEST_F(TurnPortTest, TestTurnAlternateServerV4toV6UDP) {
+ TestTurnAlternateServerV4toV6(PROTO_UDP);
+}
+
+TEST_F(TurnPortTest, TestTurnAlternateServerV4toV6TCP) {
+ TestTurnAlternateServerV4toV6(PROTO_TCP);
+}
+
+TEST_F(TurnPortTest, TestTurnAlternateServerV4toV6TLS) {
+ TestTurnAlternateServerV4toV6(PROTO_TLS);
+}
+
+// Test try-alternate-server catches the case of pingpong.
+TEST_F(TurnPortTest, TestTurnAlternateServerPingPongUDP) {
+ TestTurnAlternateServerPingPong(PROTO_UDP);
+}
+
+TEST_F(TurnPortTest, TestTurnAlternateServerPingPongTCP) {
+ TestTurnAlternateServerPingPong(PROTO_TCP);
+}
+
+TEST_F(TurnPortTest, TestTurnAlternateServerPingPongTLS) {
+ TestTurnAlternateServerPingPong(PROTO_TLS);
+}
+
+// Test try-alternate-server catch the case of repeated server.
+TEST_F(TurnPortTest, TestTurnAlternateServerDetectRepetitionUDP) {
+ TestTurnAlternateServerDetectRepetition(PROTO_UDP);
+}
+
+TEST_F(TurnPortTest, TestTurnAlternateServerDetectRepetitionTCP) {
+ TestTurnAlternateServerDetectRepetition(PROTO_TCP);
+}
+
+TEST_F(TurnPortTest, TestTurnAlternateServerDetectRepetitionTLS) {
+ TestTurnAlternateServerDetectRepetition(PROTO_TCP);
+}
+
+// Test catching the case of a redirect to loopback.
+TEST_F(TurnPortTest, TestTurnAlternateServerLoopbackUdpIpv4) {
+ TestTurnAlternateServerLoopback(PROTO_UDP, false);
+}
+
+TEST_F(TurnPortTest, TestTurnAlternateServerLoopbackUdpIpv6) {
+ TestTurnAlternateServerLoopback(PROTO_UDP, true);
+}
+
+TEST_F(TurnPortTest, TestTurnAlternateServerLoopbackTcpIpv4) {
+ TestTurnAlternateServerLoopback(PROTO_TCP, false);
+}
+
+TEST_F(TurnPortTest, TestTurnAlternateServerLoopbackTcpIpv6) {
+ TestTurnAlternateServerLoopback(PROTO_TCP, true);
+}
+
+TEST_F(TurnPortTest, TestTurnAlternateServerLoopbackTlsIpv4) {
+ TestTurnAlternateServerLoopback(PROTO_TLS, false);
+}
+
+TEST_F(TurnPortTest, TestTurnAlternateServerLoopbackTlsIpv6) {
+ TestTurnAlternateServerLoopback(PROTO_TLS, true);
+}
+
+// Do a TURN allocation and try to send a packet to it from the outside.
+// The packet should be dropped. Then, try to send a packet from TURN to the
+// outside. It should reach its destination. Finally, try again from the
+// outside. It should now work as well.
+TEST_F(TurnPortTest, TestTurnConnection) {
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ TestTurnConnection(PROTO_UDP);
+}
+
+// Similar to above, except that this test will use the shared socket.
+TEST_F(TurnPortTest, TestTurnConnectionUsingSharedSocket) {
+ CreateSharedTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ TestTurnConnection(PROTO_UDP);
+}
+
+// Test that we can establish a TCP connection with TURN server.
+TEST_F(TurnPortTest, TestTurnTcpConnection) {
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TCP);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr);
+ TestTurnConnection(PROTO_TCP);
+}
+
+// Test that we can establish a TLS connection with TURN server.
+TEST_F(TurnPortTest, TestTurnTlsConnection) {
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TLS);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTlsProtoAddr);
+ TestTurnConnection(PROTO_TLS);
+}
+
+// Test that if a connection on a TURN port is destroyed, the TURN port can
+// still receive ping on that connection as if it is from an unknown address.
+// If the connection is created again, it will be used to receive ping.
+TEST_F(TurnPortTest, TestDestroyTurnConnection) {
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ TestDestroyTurnConnection();
+}
+
+// Similar to above, except that this test will use the shared socket.
+TEST_F(TurnPortTest, TestDestroyTurnConnectionUsingSharedSocket) {
+ CreateSharedTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ TestDestroyTurnConnection();
+}
+
+// Run TurnConnectionTest with one-time-use nonce feature.
+// Here server will send a 438 STALE_NONCE error message for
+// every TURN transaction.
+TEST_F(TurnPortTest, TestTurnConnectionUsingOTUNonce) {
+ turn_server_.set_enable_otu_nonce(true);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ TestTurnConnection(PROTO_UDP);
+}
+
+// Test that CreatePermissionRequest will be scheduled after the success
+// of the first create permission request and the request will get an
+// ErrorResponse if the ufrag and pwd are incorrect.
+TEST_F(TurnPortTest, TestRefreshCreatePermissionRequest) {
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ PrepareTurnAndUdpPorts(PROTO_UDP);
+
+ Connection* conn = turn_port_->CreateConnection(udp_port_->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ ASSERT_TRUE(conn != NULL);
+ EXPECT_TRUE_SIMULATED_WAIT(turn_create_permission_success_, kSimulatedRtt,
+ fake_clock_);
+ turn_create_permission_success_ = false;
+ // A create-permission-request should be pending.
+ // After the next create-permission-response is received, it will schedule
+ // another request with bad_ufrag and bad_pwd.
+ RelayCredentials bad_credentials("bad_user", "bad_pwd");
+ turn_port_->set_credentials(bad_credentials);
+ turn_port_->request_manager().FlushForTest(kAllRequestsForTest);
+ EXPECT_TRUE_SIMULATED_WAIT(turn_create_permission_success_, kSimulatedRtt,
+ fake_clock_);
+ // Flush the requests again; the create-permission-request will fail.
+ turn_port_->request_manager().FlushForTest(kAllRequestsForTest);
+ EXPECT_TRUE_SIMULATED_WAIT(!turn_create_permission_success_, kSimulatedRtt,
+ fake_clock_);
+ EXPECT_TRUE(CheckConnectionFailedAndPruned(conn));
+}
+
+TEST_F(TurnPortTest, TestChannelBindGetErrorResponse) {
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ PrepareTurnAndUdpPorts(PROTO_UDP);
+ Connection* conn1 = turn_port_->CreateConnection(udp_port_->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ ASSERT_TRUE(conn1 != nullptr);
+ Connection* conn2 = udp_port_->CreateConnection(turn_port_->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+
+ ASSERT_TRUE(conn2 != nullptr);
+ conn1->Ping(0);
+ EXPECT_TRUE_SIMULATED_WAIT(conn1->writable(), kSimulatedRtt * 2, fake_clock_);
+ // TODO(deadbeef): SetEntryChannelId should not be a public method.
+ // Instead we should set an option on the fake TURN server to force it to
+ // send a channel bind errors.
+ ASSERT_TRUE(
+ turn_port_->SetEntryChannelId(udp_port_->Candidates()[0].address(), -1));
+
+ std::string data = "ABC";
+ conn1->Send(data.data(), data.length(), options);
+
+ EXPECT_TRUE_SIMULATED_WAIT(CheckConnectionFailedAndPruned(conn1),
+ kSimulatedRtt, fake_clock_);
+ // Verify that packets are allowed to be sent after a bind request error.
+ // They'll just use a send indication instead.
+ conn2->SignalReadPacket.connect(static_cast<TurnPortTest*>(this),
+ &TurnPortTest::OnUdpReadPacket);
+ conn1->Send(data.data(), data.length(), options);
+ EXPECT_TRUE_SIMULATED_WAIT(!udp_packets_.empty(), kSimulatedRtt, fake_clock_);
+}
+
+// Do a TURN allocation, establish a UDP connection, and send some data.
+TEST_F(TurnPortTest, TestTurnSendDataTurnUdpToUdp) {
+ // Create ports and prepare addresses.
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ TestTurnSendData(PROTO_UDP);
+ EXPECT_EQ(UDP_PROTOCOL_NAME, turn_port_->Candidates()[0].relay_protocol());
+}
+
+// Do a TURN allocation, establish a TCP connection, and send some data.
+TEST_F(TurnPortTest, TestTurnSendDataTurnTcpToUdp) {
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TCP);
+ // Create ports and prepare addresses.
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr);
+ TestTurnSendData(PROTO_TCP);
+ EXPECT_EQ(TCP_PROTOCOL_NAME, turn_port_->Candidates()[0].relay_protocol());
+}
+
+// Do a TURN allocation, establish a TLS connection, and send some data.
+TEST_F(TurnPortTest, TestTurnSendDataTurnTlsToUdp) {
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TLS);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTlsProtoAddr);
+ TestTurnSendData(PROTO_TLS);
+ EXPECT_EQ(TLS_PROTOCOL_NAME, turn_port_->Candidates()[0].relay_protocol());
+}
+
+// Test TURN fails to make a connection from IPv6 address to a server which has
+// IPv4 address.
+TEST_F(TurnPortTest, TestTurnLocalIPv6AddressServerIPv4) {
+ turn_server_.AddInternalSocket(kTurnUdpIPv6IntAddr, PROTO_UDP);
+ CreateTurnPort(kLocalIPv6Addr, kTurnUsername, kTurnPassword,
+ kTurnUdpProtoAddr);
+ turn_port_->PrepareAddress();
+ ASSERT_TRUE_SIMULATED_WAIT(turn_error_, kSimulatedRtt, fake_clock_);
+ EXPECT_TRUE(turn_port_->Candidates().empty());
+}
+
+// Test TURN make a connection from IPv6 address to a server which has
+// IPv6 intenal address. But in this test external address is a IPv4 address,
+// hence allocated address will be a IPv4 address.
+TEST_F(TurnPortTest, TestTurnLocalIPv6AddressServerIPv6ExtenalIPv4) {
+ turn_server_.AddInternalSocket(kTurnUdpIPv6IntAddr, PROTO_UDP);
+ CreateTurnPort(kLocalIPv6Addr, kTurnUsername, kTurnPassword,
+ kTurnUdpIPv6ProtoAddr);
+ TestTurnAllocateSucceeds(kSimulatedRtt * 2);
+}
+
+// Tests that the local and remote candidate address families should match when
+// a connection is created. Specifically, if a TURN port has an IPv6 address,
+// its local candidate will still be an IPv4 address and it can only create
+// connections with IPv4 remote candidates.
+TEST_F(TurnPortTest, TestCandidateAddressFamilyMatch) {
+ turn_server_.AddInternalSocket(kTurnUdpIPv6IntAddr, PROTO_UDP);
+
+ CreateTurnPort(kLocalIPv6Addr, kTurnUsername, kTurnPassword,
+ kTurnUdpIPv6ProtoAddr);
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(turn_ready_, kSimulatedRtt * 2, fake_clock_);
+ ASSERT_EQ(1U, turn_port_->Candidates().size());
+
+ // Create an IPv4 candidate. It will match the TURN candidate.
+ Candidate remote_candidate(ICE_CANDIDATE_COMPONENT_RTP, "udp", kLocalAddr2, 0,
+ "", "", "local", 0, kCandidateFoundation);
+ remote_candidate.set_address(kLocalAddr2);
+ Connection* conn =
+ turn_port_->CreateConnection(remote_candidate, Port::ORIGIN_MESSAGE);
+ EXPECT_NE(nullptr, conn);
+
+ // Set the candidate address family to IPv6. It won't match the TURN
+ // candidate.
+ remote_candidate.set_address(kLocalIPv6Addr2);
+ conn = turn_port_->CreateConnection(remote_candidate, Port::ORIGIN_MESSAGE);
+ EXPECT_EQ(nullptr, conn);
+}
+
+// Test that a CreatePermission failure will result in the connection being
+// pruned and failed.
+TEST_F(TurnPortTest, TestConnectionFailedAndPrunedOnCreatePermissionFailure) {
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TCP);
+ turn_server_.server()->set_reject_private_addresses(true);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr);
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(turn_ready_, kSimulatedRtt * 3, fake_clock_);
+
+ CreateUdpPort(SocketAddress("10.0.0.10", 0));
+ udp_port_->PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(udp_ready_, kSimulatedRtt, fake_clock_);
+ // Create a connection.
+ TestConnectionWrapper conn(turn_port_->CreateConnection(
+ udp_port_->Candidates()[0], Port::ORIGIN_MESSAGE));
+ EXPECT_TRUE(conn.connection() != nullptr);
+
+ // Asynchronously, CreatePermission request should be sent and fail, which
+ // will make the connection pruned and failed.
+ EXPECT_TRUE_SIMULATED_WAIT(CheckConnectionFailedAndPruned(conn.connection()),
+ kSimulatedRtt, fake_clock_);
+ EXPECT_TRUE_SIMULATED_WAIT(!turn_create_permission_success_, kSimulatedRtt,
+ fake_clock_);
+ // Check that the connection is not deleted asynchronously.
+ SIMULATED_WAIT(conn.connection() == nullptr, kConnectionDestructionDelay,
+ fake_clock_);
+ EXPECT_NE(nullptr, conn.connection());
+}
+
+// Test that a TURN allocation is released when the port is closed.
+TEST_F(TurnPortTest, TestTurnReleaseAllocation) {
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ TestTurnReleaseAllocation(PROTO_UDP);
+}
+
+// Test that a TURN TCP allocation is released when the port is closed.
+TEST_F(TurnPortTest, TestTurnTCPReleaseAllocation) {
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TCP);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr);
+ TestTurnReleaseAllocation(PROTO_TCP);
+}
+
+TEST_F(TurnPortTest, TestTurnTLSReleaseAllocation) {
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TLS);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTlsProtoAddr);
+ TestTurnReleaseAllocation(PROTO_TLS);
+}
+
+TEST_F(TurnPortTest, TestTurnUDPGracefulReleaseAllocation) {
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_UDP);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ TestTurnGracefulReleaseAllocation(PROTO_UDP);
+}
+
+TEST_F(TurnPortTest, TestTurnTCPGracefulReleaseAllocation) {
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TCP);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr);
+ TestTurnGracefulReleaseAllocation(PROTO_TCP);
+}
+
+TEST_F(TurnPortTest, TestTurnTLSGracefulReleaseAllocation) {
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TLS);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTlsProtoAddr);
+ TestTurnGracefulReleaseAllocation(PROTO_TLS);
+}
+
+// Test that nothing bad happens if we try to create a connection to the same
+// remote address twice. Previously there was a bug that caused this to hit a
+// DCHECK.
+TEST_F(TurnPortTest, CanCreateTwoConnectionsToSameAddress) {
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ PrepareTurnAndUdpPorts(PROTO_UDP);
+ Connection* conn1 = turn_port_->CreateConnection(udp_port_->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ Connection* conn2 = turn_port_->CreateConnection(udp_port_->Candidates()[0],
+ Port::ORIGIN_MESSAGE);
+ EXPECT_NE(conn1, conn2);
+}
+
+// This test verifies any FD's are not leaked after TurnPort is destroyed.
+// https://code.google.com/p/webrtc/issues/detail?id=2651
+#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID)
+
+TEST_F(TurnPortTest, TestResolverShutdown) {
+ turn_server_.AddInternalSocket(kTurnUdpIPv6IntAddr, PROTO_UDP);
+ int last_fd_count = GetFDCount();
+ // Need to supply unresolved address to kick off resolver.
+ CreateTurnPort(kLocalIPv6Addr, kTurnUsername, kTurnPassword,
+ ProtocolAddress(kTurnInvalidAddr, PROTO_UDP));
+ turn_port_->PrepareAddress();
+ ASSERT_TRUE_WAIT(turn_error_, kResolverTimeout);
+ EXPECT_TRUE(turn_port_->Candidates().empty());
+ turn_port_.reset();
+ rtc::Thread::Current()->PostTask([this] { test_finish_ = true; });
+ // Waiting for above message to be processed.
+ ASSERT_TRUE_SIMULATED_WAIT(test_finish_, 1, fake_clock_);
+ EXPECT_EQ(last_fd_count, GetFDCount());
+}
+#endif
+
+class MessageObserver : public StunMessageObserver {
+ public:
+ MessageObserver(unsigned int* message_counter,
+ unsigned int* channel_data_counter,
+ unsigned int* attr_counter)
+ : message_counter_(message_counter),
+ channel_data_counter_(channel_data_counter),
+ attr_counter_(attr_counter) {}
+ virtual ~MessageObserver() {}
+ void ReceivedMessage(const TurnMessage* msg) override {
+ if (message_counter_ != nullptr) {
+ (*message_counter_)++;
+ }
+ // Implementation defined attributes are returned as ByteString
+ const StunByteStringAttribute* attr =
+ msg->GetByteString(TestTurnCustomizer::STUN_ATTR_COUNTER);
+ if (attr != nullptr && attr_counter_ != nullptr) {
+ rtc::ByteBufferReader buf(attr->bytes(), attr->length());
+ unsigned int val = ~0u;
+ buf.ReadUInt32(&val);
+ (*attr_counter_)++;
+ }
+ }
+
+ void ReceivedChannelData(const char* data, size_t size) override {
+ if (channel_data_counter_ != nullptr) {
+ (*channel_data_counter_)++;
+ }
+ }
+
+ // Number of TurnMessages observed.
+ unsigned int* message_counter_ = nullptr;
+
+ // Number of channel data observed.
+ unsigned int* channel_data_counter_ = nullptr;
+
+ // Number of TurnMessages that had STUN_ATTR_COUNTER.
+ unsigned int* attr_counter_ = nullptr;
+};
+
+// Do a TURN allocation, establish a TLS connection, and send some data.
+// Add customizer and check that it get called.
+TEST_F(TurnPortTest, TestTurnCustomizerCount) {
+ unsigned int observer_message_counter = 0;
+ unsigned int observer_channel_data_counter = 0;
+ unsigned int observer_attr_counter = 0;
+ TestTurnCustomizer* customizer = new TestTurnCustomizer();
+ std::unique_ptr<MessageObserver> validator(new MessageObserver(
+ &observer_message_counter, &observer_channel_data_counter,
+ &observer_attr_counter));
+
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TLS);
+ turn_customizer_.reset(customizer);
+ turn_server_.server()->SetStunMessageObserver(std::move(validator));
+
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTlsProtoAddr);
+ TestTurnSendData(PROTO_TLS);
+ EXPECT_EQ(TLS_PROTOCOL_NAME, turn_port_->Candidates()[0].relay_protocol());
+
+ // There should have been at least turn_packets_.size() calls to `customizer`.
+ EXPECT_GE(customizer->modify_cnt_ + customizer->allow_channel_data_cnt_,
+ turn_packets_.size());
+
+ // Some channel data should be received.
+ EXPECT_GE(observer_channel_data_counter, 0u);
+
+ // Need to release TURN port before the customizer.
+ turn_port_.reset(nullptr);
+}
+
+// Do a TURN allocation, establish a TLS connection, and send some data.
+// Add customizer and check that it can can prevent usage of channel data.
+TEST_F(TurnPortTest, TestTurnCustomizerDisallowChannelData) {
+ unsigned int observer_message_counter = 0;
+ unsigned int observer_channel_data_counter = 0;
+ unsigned int observer_attr_counter = 0;
+ TestTurnCustomizer* customizer = new TestTurnCustomizer();
+ std::unique_ptr<MessageObserver> validator(new MessageObserver(
+ &observer_message_counter, &observer_channel_data_counter,
+ &observer_attr_counter));
+ customizer->allow_channel_data_ = false;
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TLS);
+ turn_customizer_.reset(customizer);
+ turn_server_.server()->SetStunMessageObserver(std::move(validator));
+
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTlsProtoAddr);
+ TestTurnSendData(PROTO_TLS);
+ EXPECT_EQ(TLS_PROTOCOL_NAME, turn_port_->Candidates()[0].relay_protocol());
+
+ // There should have been at least turn_packets_.size() calls to `customizer`.
+ EXPECT_GE(customizer->modify_cnt_, turn_packets_.size());
+
+ // No channel data should be received.
+ EXPECT_EQ(observer_channel_data_counter, 0u);
+
+ // Need to release TURN port before the customizer.
+ turn_port_.reset(nullptr);
+}
+
+// Do a TURN allocation, establish a TLS connection, and send some data.
+// Add customizer and check that it can add attribute to messages.
+TEST_F(TurnPortTest, TestTurnCustomizerAddAttribute) {
+ unsigned int observer_message_counter = 0;
+ unsigned int observer_channel_data_counter = 0;
+ unsigned int observer_attr_counter = 0;
+ TestTurnCustomizer* customizer = new TestTurnCustomizer();
+ std::unique_ptr<MessageObserver> validator(new MessageObserver(
+ &observer_message_counter, &observer_channel_data_counter,
+ &observer_attr_counter));
+ customizer->allow_channel_data_ = false;
+ customizer->add_counter_ = true;
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TLS);
+ turn_customizer_.reset(customizer);
+ turn_server_.server()->SetStunMessageObserver(std::move(validator));
+
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTlsProtoAddr);
+ TestTurnSendData(PROTO_TLS);
+ EXPECT_EQ(TLS_PROTOCOL_NAME, turn_port_->Candidates()[0].relay_protocol());
+
+ // There should have been at least turn_packets_.size() calls to `customizer`.
+ EXPECT_GE(customizer->modify_cnt_, turn_packets_.size());
+
+ // Everything will be sent as messages since channel data is disallowed.
+ EXPECT_GE(customizer->modify_cnt_, observer_message_counter);
+
+ // All messages should have attribute.
+ EXPECT_EQ(observer_message_counter, observer_attr_counter);
+
+ // At least allow_channel_data_cnt_ messages should have been sent.
+ EXPECT_GE(customizer->modify_cnt_, customizer->allow_channel_data_cnt_);
+ EXPECT_GE(customizer->allow_channel_data_cnt_, 0u);
+
+ // No channel data should be received.
+ EXPECT_EQ(observer_channel_data_counter, 0u);
+
+ // Need to release TURN port before the customizer.
+ turn_port_.reset(nullptr);
+}
+
+TEST_F(TurnPortTest, TestOverlongUsername) {
+ std::string overlong_username(513, 'x');
+ RelayCredentials credentials(overlong_username, kTurnPassword);
+ EXPECT_FALSE(
+ CreateTurnPort(overlong_username, kTurnPassword, kTurnTlsProtoAddr));
+}
+
+TEST_F(TurnPortTest, TestTurnDangerousServer) {
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnDangerousProtoAddr);
+ ASSERT_FALSE(turn_port_);
+}
+
+TEST_F(TurnPortTest, TestTurnDangerousServerPermits53) {
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnPort53ProtoAddr);
+ ASSERT_TRUE(turn_port_);
+}
+
+TEST_F(TurnPortTest, TestTurnDangerousServerPermits80) {
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnPort80ProtoAddr);
+ ASSERT_TRUE(turn_port_);
+}
+
+TEST_F(TurnPortTest, TestTurnDangerousServerPermits443) {
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnPort443ProtoAddr);
+ ASSERT_TRUE(turn_port_);
+}
+
+TEST_F(TurnPortTest, TestTurnDangerousAlternateServer) {
+ const ProtocolType protocol_type = PROTO_TCP;
+ std::vector<rtc::SocketAddress> redirect_addresses;
+ redirect_addresses.push_back(kTurnDangerousAddr);
+
+ TestTurnRedirector redirector(redirect_addresses);
+
+ turn_server_.AddInternalSocket(kTurnIntAddr, protocol_type);
+ turn_server_.AddInternalSocket(kTurnDangerousAddr, protocol_type);
+ turn_server_.set_redirect_hook(&redirector);
+ CreateTurnPort(kTurnUsername, kTurnPassword,
+ ProtocolAddress(kTurnIntAddr, protocol_type));
+
+ // Retrieve the address before we run the state machine.
+ const SocketAddress old_addr = turn_port_->server_address().address;
+
+ turn_port_->PrepareAddress();
+ // This should result in an error event.
+ EXPECT_TRUE_SIMULATED_WAIT(error_event_.error_code != 0,
+ TimeToGetAlternateTurnCandidate(protocol_type),
+ fake_clock_);
+ // but should NOT result in the port turning ready, and no candidates
+ // should be gathered.
+ EXPECT_FALSE(turn_ready_);
+ ASSERT_EQ(0U, turn_port_->Candidates().size());
+}
+
+TEST_F(TurnPortTest, TestTurnDangerousServerAllowedWithFieldTrial) {
+ webrtc::test::ScopedKeyValueConfig override_field_trials(
+ field_trials_, "WebRTC-Turn-AllowSystemPorts/Enabled/");
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnDangerousProtoAddr);
+ ASSERT_TRUE(turn_port_);
+}
+
+class TurnPortWithMockDnsResolverTest : public TurnPortTest {
+ public:
+ TurnPortWithMockDnsResolverTest()
+ : TurnPortTest(), socket_factory_(ss_.get()) {}
+
+ rtc::PacketSocketFactory* socket_factory() override {
+ return &socket_factory_;
+ }
+
+ void SetDnsResolverExpectations(
+ rtc::MockDnsResolvingPacketSocketFactory::Expectations expectations) {
+ socket_factory_.SetExpectations(expectations);
+ }
+
+ private:
+ rtc::MockDnsResolvingPacketSocketFactory socket_factory_;
+};
+
+// Test an allocation from a TURN server specified by a hostname.
+TEST_F(TurnPortWithMockDnsResolverTest, TestHostnameResolved) {
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnPortValidHostnameProtoAddr);
+ SetDnsResolverExpectations(
+ [](webrtc::MockAsyncDnsResolver* resolver,
+ webrtc::MockAsyncDnsResolverResult* resolver_result) {
+ EXPECT_CALL(*resolver, Start(kTurnValidAddr, _))
+ .WillOnce(InvokeArgument<1>());
+ EXPECT_CALL(*resolver, result)
+ .WillRepeatedly(ReturnPointee(resolver_result));
+ EXPECT_CALL(*resolver_result, GetError).WillRepeatedly(Return(0));
+ EXPECT_CALL(*resolver_result, GetResolvedAddress(AF_INET, _))
+ .WillOnce(DoAll(SetArgPointee<1>(kTurnUdpIntAddr), Return(true)));
+ });
+ TestTurnAllocateSucceeds(kSimulatedRtt * 2);
+}
+
+// Test an allocation from a TURN server specified by a hostname on an IPv6
+// network.
+TEST_F(TurnPortWithMockDnsResolverTest, TestHostnameResolvedIPv6Network) {
+ turn_server_.AddInternalSocket(kTurnUdpIPv6IntAddr, PROTO_UDP);
+ CreateTurnPort(kLocalIPv6Addr, kTurnUsername, kTurnPassword,
+ kTurnPortValidHostnameProtoAddr);
+ SetDnsResolverExpectations(
+ [](webrtc::MockAsyncDnsResolver* resolver,
+ webrtc::MockAsyncDnsResolverResult* resolver_result) {
+ EXPECT_CALL(*resolver, Start(kTurnValidAddr, _))
+ .WillOnce(InvokeArgument<1>());
+ EXPECT_CALL(*resolver, result)
+ .WillRepeatedly(ReturnPointee(resolver_result));
+ EXPECT_CALL(*resolver_result, GetError).WillRepeatedly(Return(0));
+ EXPECT_CALL(*resolver_result, GetResolvedAddress(AF_INET6, _))
+ .WillOnce(
+ DoAll(SetArgPointee<1>(kTurnUdpIPv6IntAddr), Return(true)));
+ });
+ TestTurnAllocateSucceeds(kSimulatedRtt * 2);
+}
+
+// Test an allocation from a TURN server specified by a hostname on an IPv6
+// network, without network family-specific resolution.
+TEST_F(TurnPortWithMockDnsResolverTest,
+ TestHostnameResolvedIPv6NetworkFamilyFieldTrialDisabled) {
+ webrtc::test::ScopedKeyValueConfig override_field_trials(
+ field_trials_, "WebRTC-IPv6NetworkResolutionFixes/Disabled/");
+ turn_server_.AddInternalSocket(kTurnUdpIPv6IntAddr, PROTO_UDP);
+ CreateTurnPort(kLocalIPv6Addr, kTurnUsername, kTurnPassword,
+ kTurnPortValidHostnameProtoAddr);
+ SetDnsResolverExpectations(
+ [](webrtc::MockAsyncDnsResolver* resolver,
+ webrtc::MockAsyncDnsResolverResult* resolver_result) {
+ // Expect to call Resolver::Start without family arg.
+ EXPECT_CALL(*resolver, Start(kTurnValidAddr, _))
+ .WillOnce(InvokeArgument<1>());
+ EXPECT_CALL(*resolver, result)
+ .WillRepeatedly(ReturnPointee(resolver_result));
+ EXPECT_CALL(*resolver_result, GetError).WillRepeatedly(Return(0));
+ EXPECT_CALL(*resolver_result, GetResolvedAddress(AF_INET6, _))
+ .WillOnce(
+ DoAll(SetArgPointee<1>(kTurnUdpIPv6IntAddr), Return(true)));
+ });
+ TestTurnAllocateSucceeds(kSimulatedRtt * 2);
+}
+
+// Test an allocation from a TURN server specified by a hostname on an IPv6
+// network, without network family-specific resolution.
+TEST_F(TurnPortWithMockDnsResolverTest,
+ TestHostnameResolvedIPv6NetworkFamilyFieldTrialParamDisabled) {
+ webrtc::test::ScopedKeyValueConfig override_field_trials(
+ field_trials_,
+ "WebRTC-IPv6NetworkResolutionFixes/"
+ "Enabled,ResolveTurnHostnameForFamily:false/");
+ turn_server_.AddInternalSocket(kTurnUdpIPv6IntAddr, PROTO_UDP);
+ CreateTurnPort(kLocalIPv6Addr, kTurnUsername, kTurnPassword,
+ kTurnPortValidHostnameProtoAddr);
+ SetDnsResolverExpectations(
+ [](webrtc::MockAsyncDnsResolver* resolver,
+ webrtc::MockAsyncDnsResolverResult* resolver_result) {
+ // Expect to call Resolver::Start without family arg.
+ EXPECT_CALL(*resolver, Start(kTurnValidAddr, _))
+ .WillOnce(InvokeArgument<1>());
+ EXPECT_CALL(*resolver, result)
+ .WillRepeatedly(ReturnPointee(resolver_result));
+ EXPECT_CALL(*resolver_result, GetError).WillRepeatedly(Return(0));
+ EXPECT_CALL(*resolver_result, GetResolvedAddress(AF_INET6, _))
+ .WillOnce(
+ DoAll(SetArgPointee<1>(kTurnUdpIPv6IntAddr), Return(true)));
+ });
+ TestTurnAllocateSucceeds(kSimulatedRtt * 2);
+}
+
+// Test an allocation from a TURN server specified by a hostname on an IPv6
+// network, with network family-specific resolution.
+TEST_F(TurnPortWithMockDnsResolverTest,
+ TestHostnameResolvedIPv6NetworkFieldTrialEnabled) {
+ webrtc::test::ScopedKeyValueConfig override_field_trials(
+ field_trials_,
+ "WebRTC-IPv6NetworkResolutionFixes/"
+ "Enabled,ResolveTurnHostnameForFamily:true/");
+ turn_server_.AddInternalSocket(kTurnUdpIPv6IntAddr, PROTO_UDP);
+ CreateTurnPort(kLocalIPv6Addr, kTurnUsername, kTurnPassword,
+ kTurnPortValidHostnameProtoAddr);
+ SetDnsResolverExpectations(
+ [](webrtc::MockAsyncDnsResolver* resolver,
+ webrtc::MockAsyncDnsResolverResult* resolver_result) {
+ // Expect to call Resolver::Start _with_ family arg.
+ EXPECT_CALL(*resolver, Start(kTurnValidAddr, /*family=*/AF_INET6, _))
+ .WillOnce(InvokeArgument<2>());
+ EXPECT_CALL(*resolver, result)
+ .WillRepeatedly(ReturnPointee(resolver_result));
+ EXPECT_CALL(*resolver_result, GetError).WillRepeatedly(Return(0));
+ EXPECT_CALL(*resolver_result, GetResolvedAddress(AF_INET6, _))
+ .WillOnce(
+ DoAll(SetArgPointee<1>(kTurnUdpIPv6IntAddr), Return(true)));
+ });
+ TestTurnAllocateSucceeds(kSimulatedRtt * 2);
+}
+
+} // namespace cricket
diff --git a/third_party/libwebrtc/p2p/base/turn_server.cc b/third_party/libwebrtc/p2p/base/turn_server.cc
new file mode 100644
index 0000000000..e11b52aecd
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/turn_server.cc
@@ -0,0 +1,881 @@
+/*
+ * 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->SignalReadPacket.connect(this, &TurnServer::OnInternalPacket);
+}
+
+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->SubscribeClose(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 char* data,
+ size_t size,
+ const rtc::SocketAddress& addr,
+ const int64_t& /* packet_time_us */) {
+ RTC_DCHECK_RUN_ON(thread_);
+ // Fail if the packet is too small to even contain a channel header.
+ if (size < TURN_CHANNEL_HEADER_SIZE) {
+ return;
+ }
+ InternalSocketMap::iterator iter = server_sockets_.find(socket);
+ RTC_DCHECK(iter != server_sockets_.end());
+ TurnServerConnection conn(addr, iter->second, socket);
+ uint16_t msg_type = rtc::GetBE16(data);
+ if (!IsTurnChannelData(msg_type)) {
+ // This is a STUN message.
+ HandleStunMessage(&conn, data, size);
+ } else {
+ // This is a channel message; let the allocation handle it.
+ TurnServerAllocation* allocation = FindAllocation(&conn);
+ if (allocation) {
+ allocation->HandleChannelData(data, size);
+ }
+ if (stun_message_observer_ != nullptr) {
+ stun_message_observer_->ReceivedChannelData(data, size);
+ }
+ }
+}
+
+void TurnServer::HandleStunMessage(TurnServerConnection* conn,
+ const char* data,
+ size_t size) {
+ TurnMessage msg;
+ rtc::ByteBufferReader buf(data, size);
+ 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, data, size, 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,
+ const char* data,
+ size_t size,
+ 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->UnsubscribeClose(this);
+ socket->SignalReadPacket.disconnect(this);
+ 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_->SignalReadPacket.connect(
+ this, &TurnServerAllocation::OnExternalPacket);
+}
+
+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(data_attr->bytes(), 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(const char* data, size_t size) {
+ // Extract the channel number from the data.
+ uint16_t channel_id = rtc::GetBE16(data);
+ auto channel = FindChannel(channel_id);
+ if (channel != channels_.end()) {
+ // Send the data to the peer address.
+ SendExternal(data + TURN_CHANNEL_HEADER_SIZE,
+ 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 char* data,
+ size_t size,
+ const rtc::SocketAddress& addr,
+ const int64_t& /* packet_time_us */) {
+ RTC_DCHECK(external_socket_.get() == socket);
+ auto channel = FindChannel(addr);
+ 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>(size));
+ buf.WriteBytes(data, size);
+ server_->Send(&conn_, buf);
+ } else if (!server_->enable_permission_checks_ ||
+ HasPermission(addr.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, addr));
+ msg.AddAttribute(
+ std::make_unique<StunByteStringAttribute>(STUN_ATTR_DATA, data, size));
+ server_->SendStun(&conn_, &msg);
+ } else {
+ RTC_LOG(LS_WARNING)
+ << ToString() << ": Received external packet without permission, peer="
+ << addr.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
diff --git a/third_party/libwebrtc/p2p/base/turn_server.h b/third_party/libwebrtc/p2p/base/turn_server.h
new file mode 100644
index 0000000000..e951d089af
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/turn_server.h
@@ -0,0 +1,373 @@
+/*
+ * 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/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 sigslot::has_slots<> {
+ public:
+ TurnServerAllocation(TurnServer* server_,
+ webrtc::TaskQueueBase* thread,
+ const TurnServerConnection& conn,
+ rtc::AsyncPacketSocket* server_socket,
+ absl::string_view key);
+ ~TurnServerAllocation() override;
+
+ 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(const char* data, size_t size);
+
+ 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 char* data,
+ size_t size,
+ const rtc::SocketAddress& addr,
+ const int64_t& packet_time_us);
+
+ 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(const char* data, size_t size) = 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), and the On* methods, which
+ // are called via sigslot.
+ std::string GenerateNonce(int64_t now) const RTC_RUN_ON(thread_);
+ void OnInternalPacket(rtc::AsyncPacketSocket* socket,
+ const char* data,
+ size_t size,
+ const rtc::SocketAddress& address,
+ const int64_t& packet_time_us);
+
+ 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,
+ const char* data,
+ size_t size) 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,
+ const char* data,
+ size_t size,
+ 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_
diff --git a/third_party/libwebrtc/p2p/base/turn_server_unittest.cc b/third_party/libwebrtc/p2p/base/turn_server_unittest.cc
new file mode 100644
index 0000000000..e534f6598c
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/turn_server_unittest.cc
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+#include "p2p/base/turn_server.h"
+
+#include "p2p/base/basic_packet_socket_factory.h"
+#include "rtc_base/virtual_socket_server.h"
+#include "test/gtest.h"
+
+// NOTE: This is a work in progress. Currently this file only has tests for
+// TurnServerConnection, a primitive class used by TurnServer.
+
+namespace cricket {
+
+class TurnServerConnectionTest : public ::testing::Test {
+ public:
+ TurnServerConnectionTest() : thread_(&vss_), socket_factory_(&vss_) {}
+
+ void ExpectEqual(const TurnServerConnection& a,
+ const TurnServerConnection& b) {
+ EXPECT_TRUE(a == b);
+ EXPECT_FALSE(a < b);
+ EXPECT_FALSE(b < a);
+ }
+
+ void ExpectNotEqual(const TurnServerConnection& a,
+ const TurnServerConnection& b) {
+ EXPECT_FALSE(a == b);
+ // We don't care which is less than the other, as long as only one is less
+ // than the other.
+ EXPECT_TRUE((a < b) != (b < a));
+ }
+
+ protected:
+ rtc::VirtualSocketServer vss_;
+ rtc::AutoSocketServerThread thread_;
+ rtc::BasicPacketSocketFactory socket_factory_;
+};
+
+TEST_F(TurnServerConnectionTest, ComparisonOperators) {
+ std::unique_ptr<rtc::AsyncPacketSocket> socket1(
+ socket_factory_.CreateUdpSocket(rtc::SocketAddress("1.1.1.1", 1), 0, 0));
+ std::unique_ptr<rtc::AsyncPacketSocket> socket2(
+ socket_factory_.CreateUdpSocket(rtc::SocketAddress("2.2.2.2", 2), 0, 0));
+ TurnServerConnection connection1(socket2->GetLocalAddress(), PROTO_UDP,
+ socket1.get());
+ TurnServerConnection connection2(socket2->GetLocalAddress(), PROTO_UDP,
+ socket1.get());
+ TurnServerConnection connection3(socket1->GetLocalAddress(), PROTO_UDP,
+ socket2.get());
+ TurnServerConnection connection4(socket2->GetLocalAddress(), PROTO_TCP,
+ socket1.get());
+ ExpectEqual(connection1, connection2);
+ ExpectNotEqual(connection1, connection3);
+ ExpectNotEqual(connection1, connection4);
+}
+
+} // namespace cricket
diff --git a/third_party/libwebrtc/p2p/base/udp_port.h b/third_party/libwebrtc/p2p/base/udp_port.h
new file mode 100644
index 0000000000..2fd68680cf
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/udp_port.h
@@ -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_
diff --git a/third_party/libwebrtc/p2p/base/wrapping_active_ice_controller.cc b/third_party/libwebrtc/p2p/base/wrapping_active_ice_controller.cc
new file mode 100644
index 0000000000..c6659217fc
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/wrapping_active_ice_controller.cc
@@ -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
diff --git a/third_party/libwebrtc/p2p/base/wrapping_active_ice_controller.h b/third_party/libwebrtc/p2p/base/wrapping_active_ice_controller.h
new file mode 100644
index 0000000000..449c0f0ee1
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/wrapping_active_ice_controller.h
@@ -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_
diff --git a/third_party/libwebrtc/p2p/base/wrapping_active_ice_controller_unittest.cc b/third_party/libwebrtc/p2p/base/wrapping_active_ice_controller_unittest.cc
new file mode 100644
index 0000000000..b4811bd297
--- /dev/null
+++ b/third_party/libwebrtc/p2p/base/wrapping_active_ice_controller_unittest.cc
@@ -0,0 +1,315 @@
+/*
+ * Copyright 2009 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 "p2p/base/connection.h"
+#include "p2p/base/mock_ice_agent.h"
+#include "p2p/base/mock_ice_controller.h"
+#include "rtc_base/fake_clock.h"
+#include "rtc_base/gunit.h"
+#include "rtc_base/thread.h"
+
+namespace {
+
+using ::cricket::Connection;
+using ::cricket::IceConfig;
+using ::cricket::IceControllerFactoryArgs;
+using ::cricket::IceControllerInterface;
+using ::cricket::IceMode;
+using ::cricket::IceRecheckEvent;
+using ::cricket::IceSwitchReason;
+using ::cricket::MockIceAgent;
+using ::cricket::MockIceController;
+using ::cricket::MockIceControllerFactory;
+using ::cricket::NominationMode;
+using ::cricket::WrappingActiveIceController;
+
+using ::testing::_;
+using ::testing::ElementsAreArray;
+using ::testing::IsEmpty;
+using ::testing::NiceMock;
+using ::testing::Ref;
+using ::testing::Return;
+using ::testing::Sequence;
+
+using ::rtc::AutoThread;
+using ::rtc::Event;
+using ::rtc::ScopedFakeClock;
+using ::webrtc::TimeDelta;
+
+using NiceMockIceController = NiceMock<MockIceController>;
+
+static const Connection* kConnection =
+ reinterpret_cast<const Connection*>(0xabcd);
+static const Connection* kConnectionTwo =
+ reinterpret_cast<const Connection*>(0xbcde);
+static const Connection* kConnectionThree =
+ reinterpret_cast<const Connection*>(0xcdef);
+
+static const std::vector<const Connection*> kEmptyConnsList =
+ std::vector<const Connection*>();
+
+static const TimeDelta kTick = TimeDelta::Millis(1);
+
+TEST(WrappingActiveIceControllerTest, CreateLegacyIceControllerFromFactory) {
+ AutoThread main;
+ MockIceAgent agent;
+ IceControllerFactoryArgs args;
+ MockIceControllerFactory legacy_controller_factory;
+ EXPECT_CALL(legacy_controller_factory, RecordIceControllerCreated()).Times(1);
+ WrappingActiveIceController controller(&agent, &legacy_controller_factory,
+ args);
+}
+
+TEST(WrappingActiveIceControllerTest, PassthroughIceControllerInterface) {
+ AutoThread main;
+ MockIceAgent agent;
+ std::unique_ptr<MockIceController> will_move =
+ std::make_unique<MockIceController>(IceControllerFactoryArgs{});
+ MockIceController* wrapped = will_move.get();
+ WrappingActiveIceController controller(&agent, std::move(will_move));
+
+ IceConfig config{};
+ EXPECT_CALL(*wrapped, SetIceConfig(Ref(config)));
+ controller.SetIceConfig(config);
+
+ EXPECT_CALL(*wrapped,
+ GetUseCandidateAttr(kConnection, NominationMode::AGGRESSIVE,
+ IceMode::ICEMODE_LITE))
+ .WillOnce(Return(true));
+ EXPECT_TRUE(controller.GetUseCandidateAttribute(
+ kConnection, NominationMode::AGGRESSIVE, IceMode::ICEMODE_LITE));
+
+ EXPECT_CALL(*wrapped, AddConnection(kConnection));
+ controller.OnConnectionAdded(kConnection);
+
+ EXPECT_CALL(*wrapped, OnConnectionDestroyed(kConnection));
+ controller.OnConnectionDestroyed(kConnection);
+
+ EXPECT_CALL(*wrapped, SetSelectedConnection(kConnection));
+ controller.OnConnectionSwitched(kConnection);
+
+ EXPECT_CALL(*wrapped, MarkConnectionPinged(kConnection));
+ controller.OnConnectionPinged(kConnection);
+
+ EXPECT_CALL(*wrapped, FindNextPingableConnection())
+ .WillOnce(Return(kConnection));
+ EXPECT_EQ(controller.FindNextPingableConnection(), kConnection);
+}
+
+TEST(WrappingActiveIceControllerTest, HandlesImmediateSwitchRequest) {
+ AutoThread main;
+ ScopedFakeClock clock;
+ NiceMock<MockIceAgent> agent;
+ std::unique_ptr<NiceMockIceController> will_move =
+ std::make_unique<NiceMockIceController>(IceControllerFactoryArgs{});
+ NiceMockIceController* wrapped = will_move.get();
+ WrappingActiveIceController controller(&agent, std::move(will_move));
+
+ IceSwitchReason reason = IceSwitchReason::NOMINATION_ON_CONTROLLED_SIDE;
+ std::vector<const Connection*> conns_to_forget{kConnectionTwo};
+ int recheck_delay_ms = 10;
+ IceControllerInterface::SwitchResult switch_result{
+ kConnection,
+ IceRecheckEvent(IceSwitchReason::ICE_CONTROLLER_RECHECK,
+ recheck_delay_ms),
+ conns_to_forget};
+
+ // ICE controller should switch to given connection immediately.
+ Sequence check_then_switch;
+ EXPECT_CALL(*wrapped, ShouldSwitchConnection(reason, kConnection))
+ .InSequence(check_then_switch)
+ .WillOnce(Return(switch_result));
+ EXPECT_CALL(agent, SwitchSelectedConnection(kConnection, reason))
+ .InSequence(check_then_switch);
+ EXPECT_CALL(agent, ForgetLearnedStateForConnections(
+ ElementsAreArray(conns_to_forget)));
+
+ EXPECT_TRUE(controller.OnImmediateSwitchRequest(reason, kConnection));
+
+ // No rechecks before recheck delay.
+ clock.AdvanceTime(TimeDelta::Millis(recheck_delay_ms - 1));
+
+ // ICE controller should recheck for best connection after the recheck delay.
+ Sequence recheck_sort;
+ EXPECT_CALL(agent, UpdateConnectionStates()).InSequence(recheck_sort);
+ EXPECT_CALL(*wrapped,
+ SortAndSwitchConnection(IceSwitchReason::ICE_CONTROLLER_RECHECK))
+ .InSequence(recheck_sort)
+ .WillOnce(Return(IceControllerInterface::SwitchResult{}));
+ EXPECT_CALL(agent, ForgetLearnedStateForConnections(IsEmpty()));
+
+ clock.AdvanceTime(kTick);
+}
+
+TEST(WrappingActiveIceControllerTest, HandlesImmediateSortAndSwitchRequest) {
+ AutoThread main;
+ ScopedFakeClock clock;
+ NiceMock<MockIceAgent> agent;
+ std::unique_ptr<NiceMockIceController> will_move =
+ std::make_unique<NiceMockIceController>(IceControllerFactoryArgs{});
+ NiceMockIceController* wrapped = will_move.get();
+ WrappingActiveIceController controller(&agent, std::move(will_move));
+
+ IceSwitchReason reason = IceSwitchReason::NEW_CONNECTION_FROM_LOCAL_CANDIDATE;
+ std::vector<const Connection*> conns_to_forget{kConnectionTwo};
+ std::vector<const Connection*> conns_to_prune{kConnectionThree};
+ int recheck_delay_ms = 10;
+ IceControllerInterface::SwitchResult switch_result{
+ kConnection,
+ IceRecheckEvent(IceSwitchReason::ICE_CONTROLLER_RECHECK,
+ recheck_delay_ms),
+ conns_to_forget};
+
+ Sequence sort_and_switch;
+ EXPECT_CALL(agent, UpdateConnectionStates()).InSequence(sort_and_switch);
+ EXPECT_CALL(*wrapped, SortAndSwitchConnection(reason))
+ .InSequence(sort_and_switch)
+ .WillOnce(Return(switch_result));
+ EXPECT_CALL(agent, SwitchSelectedConnection(kConnection, reason))
+ .InSequence(sort_and_switch);
+ EXPECT_CALL(*wrapped, PruneConnections())
+ .InSequence(sort_and_switch)
+ .WillOnce(Return(conns_to_prune));
+ EXPECT_CALL(agent, PruneConnections(ElementsAreArray(conns_to_prune)))
+ .InSequence(sort_and_switch);
+
+ controller.OnImmediateSortAndSwitchRequest(reason);
+
+ // No rechecks before recheck delay.
+ clock.AdvanceTime(TimeDelta::Millis(recheck_delay_ms - 1));
+
+ // ICE controller should recheck for best connection after the recheck delay.
+ Sequence recheck_sort;
+ EXPECT_CALL(agent, UpdateConnectionStates()).InSequence(recheck_sort);
+ EXPECT_CALL(*wrapped,
+ SortAndSwitchConnection(IceSwitchReason::ICE_CONTROLLER_RECHECK))
+ .InSequence(recheck_sort)
+ .WillOnce(Return(IceControllerInterface::SwitchResult{}));
+ EXPECT_CALL(*wrapped, PruneConnections())
+ .InSequence(recheck_sort)
+ .WillOnce(Return(kEmptyConnsList));
+ EXPECT_CALL(agent, PruneConnections(IsEmpty())).InSequence(recheck_sort);
+
+ clock.AdvanceTime(kTick);
+}
+
+TEST(WrappingActiveIceControllerTest, HandlesSortAndSwitchRequest) {
+ AutoThread main;
+ ScopedFakeClock clock;
+
+ // Block the main task queue until ready.
+ Event init;
+ TimeDelta init_delay = TimeDelta::Millis(10);
+ main.PostTask([&init, &init_delay] { init.Wait(init_delay); });
+
+ NiceMock<MockIceAgent> agent;
+ std::unique_ptr<NiceMockIceController> will_move =
+ std::make_unique<NiceMockIceController>(IceControllerFactoryArgs{});
+ NiceMockIceController* wrapped = will_move.get();
+ WrappingActiveIceController controller(&agent, std::move(will_move));
+
+ IceSwitchReason reason = IceSwitchReason::NETWORK_PREFERENCE_CHANGE;
+
+ // No action should occur immediately
+ EXPECT_CALL(agent, UpdateConnectionStates()).Times(0);
+ EXPECT_CALL(*wrapped, SortAndSwitchConnection(_)).Times(0);
+ EXPECT_CALL(agent, SwitchSelectedConnection(_, _)).Times(0);
+
+ controller.OnSortAndSwitchRequest(reason);
+
+ std::vector<const Connection*> conns_to_forget{kConnectionTwo};
+ int recheck_delay_ms = 10;
+ IceControllerInterface::SwitchResult switch_result{
+ kConnection,
+ IceRecheckEvent(IceSwitchReason::ICE_CONTROLLER_RECHECK,
+ recheck_delay_ms),
+ conns_to_forget};
+
+ // Sort and switch should take place as the subsequent task.
+ Sequence sort_and_switch;
+ EXPECT_CALL(agent, UpdateConnectionStates()).InSequence(sort_and_switch);
+ EXPECT_CALL(*wrapped, SortAndSwitchConnection(reason))
+ .InSequence(sort_and_switch)
+ .WillOnce(Return(switch_result));
+ EXPECT_CALL(agent, SwitchSelectedConnection(kConnection, reason))
+ .InSequence(sort_and_switch);
+
+ // Unblock the init task.
+ clock.AdvanceTime(init_delay);
+}
+
+TEST(WrappingActiveIceControllerTest, StartPingingAfterSortAndSwitch) {
+ AutoThread main;
+ ScopedFakeClock clock;
+
+ // Block the main task queue until ready.
+ Event init;
+ TimeDelta init_delay = TimeDelta::Millis(10);
+ main.PostTask([&init, &init_delay] { init.Wait(init_delay); });
+
+ NiceMock<MockIceAgent> agent;
+ std::unique_ptr<NiceMockIceController> will_move =
+ std::make_unique<NiceMockIceController>(IceControllerFactoryArgs{});
+ NiceMockIceController* wrapped = will_move.get();
+ WrappingActiveIceController controller(&agent, std::move(will_move));
+
+ // Pinging does not start automatically, unless triggered through a sort.
+ EXPECT_CALL(*wrapped, HasPingableConnection()).Times(0);
+ EXPECT_CALL(*wrapped, SelectConnectionToPing(_)).Times(0);
+ EXPECT_CALL(agent, OnStartedPinging()).Times(0);
+
+ controller.OnSortAndSwitchRequest(IceSwitchReason::DATA_RECEIVED);
+
+ // Pinging does not start if no pingable connection.
+ EXPECT_CALL(*wrapped, HasPingableConnection()).WillOnce(Return(false));
+ EXPECT_CALL(*wrapped, SelectConnectionToPing(_)).Times(0);
+ EXPECT_CALL(agent, OnStartedPinging()).Times(0);
+
+ // Unblock the init task.
+ clock.AdvanceTime(init_delay);
+
+ int recheck_delay_ms = 10;
+ IceControllerInterface::PingResult ping_result(kConnection, recheck_delay_ms);
+
+ // Pinging starts when there is a pingable connection.
+ Sequence start_pinging;
+ EXPECT_CALL(*wrapped, HasPingableConnection())
+ .InSequence(start_pinging)
+ .WillOnce(Return(true));
+ EXPECT_CALL(agent, OnStartedPinging()).InSequence(start_pinging);
+ EXPECT_CALL(agent, GetLastPingSentMs())
+ .InSequence(start_pinging)
+ .WillOnce(Return(123));
+ EXPECT_CALL(*wrapped, SelectConnectionToPing(123))
+ .InSequence(start_pinging)
+ .WillOnce(Return(ping_result));
+ EXPECT_CALL(agent, SendPingRequest(kConnection)).InSequence(start_pinging);
+
+ controller.OnSortAndSwitchRequest(IceSwitchReason::DATA_RECEIVED);
+ clock.AdvanceTime(kTick);
+
+ // ICE controller should recheck and ping after the recheck delay.
+ // No ping should be sent if no connection selected to ping.
+ EXPECT_CALL(agent, GetLastPingSentMs()).WillOnce(Return(456));
+ EXPECT_CALL(*wrapped, SelectConnectionToPing(456))
+ .WillOnce(Return(IceControllerInterface::PingResult(
+ /* connection= */ nullptr, recheck_delay_ms)));
+ EXPECT_CALL(agent, SendPingRequest(kConnection)).Times(0);
+
+ clock.AdvanceTime(TimeDelta::Millis(recheck_delay_ms));
+}
+
+} // namespace