From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- .../base/active_ice_controller_factory_interface.h | 39 + .../p2p/base/active_ice_controller_interface.h | 84 + .../libwebrtc/p2p/base/async_stun_tcp_socket.cc | 156 + .../libwebrtc/p2p/base/async_stun_tcp_socket.h | 51 + .../p2p/base/async_stun_tcp_socket_unittest.cc | 288 + .../p2p/base/basic_async_resolver_factory.cc | 53 + .../p2p/base/basic_async_resolver_factory.h | 62 + .../base/basic_async_resolver_factory_unittest.cc | 111 + .../libwebrtc/p2p/base/basic_ice_controller.cc | 855 +++ .../libwebrtc/p2p/base/basic_ice_controller.h | 165 + .../p2p/base/basic_packet_socket_factory.cc | 210 + .../p2p/base/basic_packet_socket_factory.h | 69 + .../libwebrtc/p2p/base/candidate_pair_interface.h | 28 + third_party/libwebrtc/p2p/base/connection.cc | 1720 +++++ third_party/libwebrtc/p2p/base/connection.h | 498 ++ third_party/libwebrtc/p2p/base/connection_info.cc | 44 + third_party/libwebrtc/p2p/base/connection_info.h | 87 + .../p2p/base/default_ice_transport_factory.cc | 54 + .../p2p/base/default_ice_transport_factory.h | 58 + third_party/libwebrtc/p2p/base/dtls_transport.cc | 870 +++ third_party/libwebrtc/p2p/base/dtls_transport.h | 264 + .../libwebrtc/p2p/base/dtls_transport_factory.h | 40 + .../libwebrtc/p2p/base/dtls_transport_internal.cc | 19 + .../libwebrtc/p2p/base/dtls_transport_internal.h | 157 + .../libwebrtc/p2p/base/dtls_transport_unittest.cc | 748 +++ .../libwebrtc/p2p/base/fake_dtls_transport.h | 318 + .../libwebrtc/p2p/base/fake_ice_transport.h | 446 ++ .../libwebrtc/p2p/base/fake_packet_transport.h | 143 + .../libwebrtc/p2p/base/fake_port_allocator.h | 279 + .../libwebrtc/p2p/base/ice_agent_interface.h | 80 + .../p2p/base/ice_controller_factory_interface.h | 40 + .../libwebrtc/p2p/base/ice_controller_interface.cc | 27 + .../libwebrtc/p2p/base/ice_controller_interface.h | 138 + .../libwebrtc/p2p/base/ice_credentials_iterator.cc | 38 + .../libwebrtc/p2p/base/ice_credentials_iterator.h | 37 + .../p2p/base/ice_credentials_iterator_unittest.cc | 48 + .../libwebrtc/p2p/base/ice_switch_reason.cc | 44 + third_party/libwebrtc/p2p/base/ice_switch_reason.h | 38 + .../libwebrtc/p2p/base/ice_transport_internal.cc | 140 + .../libwebrtc/p2p/base/ice_transport_internal.h | 347 + .../p2p/base/mock_active_ice_controller.h | 89 + .../libwebrtc/p2p/base/mock_async_resolver.h | 57 + .../mock_dns_resolving_packet_socket_factory.h | 53 + third_party/libwebrtc/p2p/base/mock_ice_agent.h | 50 + .../libwebrtc/p2p/base/mock_ice_controller.h | 90 + .../libwebrtc/p2p/base/mock_ice_transport.h | 90 + third_party/libwebrtc/p2p/base/p2p_constants.cc | 75 + third_party/libwebrtc/p2p/base/p2p_constants.h | 114 + .../libwebrtc/p2p/base/p2p_transport_channel.cc | 2636 ++++++++ .../libwebrtc/p2p/base/p2p_transport_channel.h | 590 ++ .../base/p2p_transport_channel_ice_field_trials.h | 77 + .../p2p/base/p2p_transport_channel_unittest.cc | 6630 ++++++++++++++++++++ .../p2p/base/packet_transport_internal.cc | 27 + .../libwebrtc/p2p/base/packet_transport_internal.h | 108 + third_party/libwebrtc/p2p/base/port.cc | 969 +++ third_party/libwebrtc/p2p/base/port.h | 540 ++ third_party/libwebrtc/p2p/base/port_allocator.cc | 349 ++ third_party/libwebrtc/p2p/base/port_allocator.h | 683 ++ .../libwebrtc/p2p/base/port_allocator_unittest.cc | 371 ++ third_party/libwebrtc/p2p/base/port_interface.cc | 23 + third_party/libwebrtc/p2p/base/port_interface.h | 146 + third_party/libwebrtc/p2p/base/port_unittest.cc | 3844 ++++++++++++ third_party/libwebrtc/p2p/base/pseudo_tcp.cc | 1432 +++++ third_party/libwebrtc/p2p/base/pseudo_tcp.h | 295 + .../libwebrtc/p2p/base/pseudo_tcp_unittest.cc | 880 +++ .../libwebrtc/p2p/base/regathering_controller.cc | 80 + .../libwebrtc/p2p/base/regathering_controller.h | 97 + .../p2p/base/regathering_controller_unittest.cc | 189 + third_party/libwebrtc/p2p/base/stun_port.cc | 700 +++ third_party/libwebrtc/p2p/base/stun_port.h | 301 + .../libwebrtc/p2p/base/stun_port_unittest.cc | 765 +++ third_party/libwebrtc/p2p/base/stun_request.cc | 310 + third_party/libwebrtc/p2p/base/stun_request.h | 162 + .../libwebrtc/p2p/base/stun_request_unittest.cc | 219 + third_party/libwebrtc/p2p/base/stun_server.cc | 104 + third_party/libwebrtc/p2p/base/stun_server.h | 72 + .../libwebrtc/p2p/base/stun_server_unittest.cc | 145 + third_party/libwebrtc/p2p/base/tcp_port.cc | 620 ++ third_party/libwebrtc/p2p/base/tcp_port.h | 203 + .../libwebrtc/p2p/base/tcp_port_unittest.cc | 259 + third_party/libwebrtc/p2p/base/test_stun_server.cc | 37 + third_party/libwebrtc/p2p/base/test_stun_server.h | 45 + .../libwebrtc/p2p/base/test_turn_customizer.h | 59 + third_party/libwebrtc/p2p/base/test_turn_server.h | 161 + .../libwebrtc/p2p/base/transport_description.cc | 196 + .../libwebrtc/p2p/base/transport_description.h | 154 + .../p2p/base/transport_description_factory.cc | 151 + .../p2p/base/transport_description_factory.h | 91 + .../base/transport_description_factory_unittest.cc | 406 ++ .../p2p/base/transport_description_unittest.cc | 58 + third_party/libwebrtc/p2p/base/transport_info.h | 42 + third_party/libwebrtc/p2p/base/turn_port.cc | 1900 ++++++ third_party/libwebrtc/p2p/base/turn_port.h | 375 ++ .../libwebrtc/p2p/base/turn_port_unittest.cc | 2026 ++++++ third_party/libwebrtc/p2p/base/turn_server.cc | 881 +++ third_party/libwebrtc/p2p/base/turn_server.h | 373 ++ .../libwebrtc/p2p/base/turn_server_unittest.cc | 65 + third_party/libwebrtc/p2p/base/udp_port.h | 17 + .../p2p/base/wrapping_active_ice_controller.cc | 253 + .../p2p/base/wrapping_active_ice_controller.h | 97 + .../wrapping_active_ice_controller_unittest.cc | 315 + 101 files changed, 41039 insertions(+) create mode 100644 third_party/libwebrtc/p2p/base/active_ice_controller_factory_interface.h create mode 100644 third_party/libwebrtc/p2p/base/active_ice_controller_interface.h create mode 100644 third_party/libwebrtc/p2p/base/async_stun_tcp_socket.cc create mode 100644 third_party/libwebrtc/p2p/base/async_stun_tcp_socket.h create mode 100644 third_party/libwebrtc/p2p/base/async_stun_tcp_socket_unittest.cc create mode 100644 third_party/libwebrtc/p2p/base/basic_async_resolver_factory.cc create mode 100644 third_party/libwebrtc/p2p/base/basic_async_resolver_factory.h create mode 100644 third_party/libwebrtc/p2p/base/basic_async_resolver_factory_unittest.cc create mode 100644 third_party/libwebrtc/p2p/base/basic_ice_controller.cc create mode 100644 third_party/libwebrtc/p2p/base/basic_ice_controller.h create mode 100644 third_party/libwebrtc/p2p/base/basic_packet_socket_factory.cc create mode 100644 third_party/libwebrtc/p2p/base/basic_packet_socket_factory.h create mode 100644 third_party/libwebrtc/p2p/base/candidate_pair_interface.h create mode 100644 third_party/libwebrtc/p2p/base/connection.cc create mode 100644 third_party/libwebrtc/p2p/base/connection.h create mode 100644 third_party/libwebrtc/p2p/base/connection_info.cc create mode 100644 third_party/libwebrtc/p2p/base/connection_info.h create mode 100644 third_party/libwebrtc/p2p/base/default_ice_transport_factory.cc create mode 100644 third_party/libwebrtc/p2p/base/default_ice_transport_factory.h create mode 100644 third_party/libwebrtc/p2p/base/dtls_transport.cc create mode 100644 third_party/libwebrtc/p2p/base/dtls_transport.h create mode 100644 third_party/libwebrtc/p2p/base/dtls_transport_factory.h create mode 100644 third_party/libwebrtc/p2p/base/dtls_transport_internal.cc create mode 100644 third_party/libwebrtc/p2p/base/dtls_transport_internal.h create mode 100644 third_party/libwebrtc/p2p/base/dtls_transport_unittest.cc create mode 100644 third_party/libwebrtc/p2p/base/fake_dtls_transport.h create mode 100644 third_party/libwebrtc/p2p/base/fake_ice_transport.h create mode 100644 third_party/libwebrtc/p2p/base/fake_packet_transport.h create mode 100644 third_party/libwebrtc/p2p/base/fake_port_allocator.h create mode 100644 third_party/libwebrtc/p2p/base/ice_agent_interface.h create mode 100644 third_party/libwebrtc/p2p/base/ice_controller_factory_interface.h create mode 100644 third_party/libwebrtc/p2p/base/ice_controller_interface.cc create mode 100644 third_party/libwebrtc/p2p/base/ice_controller_interface.h create mode 100644 third_party/libwebrtc/p2p/base/ice_credentials_iterator.cc create mode 100644 third_party/libwebrtc/p2p/base/ice_credentials_iterator.h create mode 100644 third_party/libwebrtc/p2p/base/ice_credentials_iterator_unittest.cc create mode 100644 third_party/libwebrtc/p2p/base/ice_switch_reason.cc create mode 100644 third_party/libwebrtc/p2p/base/ice_switch_reason.h create mode 100644 third_party/libwebrtc/p2p/base/ice_transport_internal.cc create mode 100644 third_party/libwebrtc/p2p/base/ice_transport_internal.h create mode 100644 third_party/libwebrtc/p2p/base/mock_active_ice_controller.h create mode 100644 third_party/libwebrtc/p2p/base/mock_async_resolver.h create mode 100644 third_party/libwebrtc/p2p/base/mock_dns_resolving_packet_socket_factory.h create mode 100644 third_party/libwebrtc/p2p/base/mock_ice_agent.h create mode 100644 third_party/libwebrtc/p2p/base/mock_ice_controller.h create mode 100644 third_party/libwebrtc/p2p/base/mock_ice_transport.h create mode 100644 third_party/libwebrtc/p2p/base/p2p_constants.cc create mode 100644 third_party/libwebrtc/p2p/base/p2p_constants.h create mode 100644 third_party/libwebrtc/p2p/base/p2p_transport_channel.cc create mode 100644 third_party/libwebrtc/p2p/base/p2p_transport_channel.h create mode 100644 third_party/libwebrtc/p2p/base/p2p_transport_channel_ice_field_trials.h create mode 100644 third_party/libwebrtc/p2p/base/p2p_transport_channel_unittest.cc create mode 100644 third_party/libwebrtc/p2p/base/packet_transport_internal.cc create mode 100644 third_party/libwebrtc/p2p/base/packet_transport_internal.h create mode 100644 third_party/libwebrtc/p2p/base/port.cc create mode 100644 third_party/libwebrtc/p2p/base/port.h create mode 100644 third_party/libwebrtc/p2p/base/port_allocator.cc create mode 100644 third_party/libwebrtc/p2p/base/port_allocator.h create mode 100644 third_party/libwebrtc/p2p/base/port_allocator_unittest.cc create mode 100644 third_party/libwebrtc/p2p/base/port_interface.cc create mode 100644 third_party/libwebrtc/p2p/base/port_interface.h create mode 100644 third_party/libwebrtc/p2p/base/port_unittest.cc create mode 100644 third_party/libwebrtc/p2p/base/pseudo_tcp.cc create mode 100644 third_party/libwebrtc/p2p/base/pseudo_tcp.h create mode 100644 third_party/libwebrtc/p2p/base/pseudo_tcp_unittest.cc create mode 100644 third_party/libwebrtc/p2p/base/regathering_controller.cc create mode 100644 third_party/libwebrtc/p2p/base/regathering_controller.h create mode 100644 third_party/libwebrtc/p2p/base/regathering_controller_unittest.cc create mode 100644 third_party/libwebrtc/p2p/base/stun_port.cc create mode 100644 third_party/libwebrtc/p2p/base/stun_port.h create mode 100644 third_party/libwebrtc/p2p/base/stun_port_unittest.cc create mode 100644 third_party/libwebrtc/p2p/base/stun_request.cc create mode 100644 third_party/libwebrtc/p2p/base/stun_request.h create mode 100644 third_party/libwebrtc/p2p/base/stun_request_unittest.cc create mode 100644 third_party/libwebrtc/p2p/base/stun_server.cc create mode 100644 third_party/libwebrtc/p2p/base/stun_server.h create mode 100644 third_party/libwebrtc/p2p/base/stun_server_unittest.cc create mode 100644 third_party/libwebrtc/p2p/base/tcp_port.cc create mode 100644 third_party/libwebrtc/p2p/base/tcp_port.h create mode 100644 third_party/libwebrtc/p2p/base/tcp_port_unittest.cc create mode 100644 third_party/libwebrtc/p2p/base/test_stun_server.cc create mode 100644 third_party/libwebrtc/p2p/base/test_stun_server.h create mode 100644 third_party/libwebrtc/p2p/base/test_turn_customizer.h create mode 100644 third_party/libwebrtc/p2p/base/test_turn_server.h create mode 100644 third_party/libwebrtc/p2p/base/transport_description.cc create mode 100644 third_party/libwebrtc/p2p/base/transport_description.h create mode 100644 third_party/libwebrtc/p2p/base/transport_description_factory.cc create mode 100644 third_party/libwebrtc/p2p/base/transport_description_factory.h create mode 100644 third_party/libwebrtc/p2p/base/transport_description_factory_unittest.cc create mode 100644 third_party/libwebrtc/p2p/base/transport_description_unittest.cc create mode 100644 third_party/libwebrtc/p2p/base/transport_info.h create mode 100644 third_party/libwebrtc/p2p/base/turn_port.cc create mode 100644 third_party/libwebrtc/p2p/base/turn_port.h create mode 100644 third_party/libwebrtc/p2p/base/turn_port_unittest.cc create mode 100644 third_party/libwebrtc/p2p/base/turn_server.cc create mode 100644 third_party/libwebrtc/p2p/base/turn_server.h create mode 100644 third_party/libwebrtc/p2p/base/turn_server_unittest.cc create mode 100644 third_party/libwebrtc/p2p/base/udp_port.h create mode 100644 third_party/libwebrtc/p2p/base/wrapping_active_ice_controller.cc create mode 100644 third_party/libwebrtc/p2p/base/wrapping_active_ice_controller.h create mode 100644 third_party/libwebrtc/p2p/base/wrapping_active_ice_controller_unittest.cc (limited to 'third_party/libwebrtc/p2p/base') 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 + +#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 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 +#include +#include + +#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(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(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(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 + +#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 +#include + +#include +#include +#include +#include + +#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 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 server = + absl::WrapUnique(vss_->CreateSocket(kServerAddr.family(), SOCK_STREAM)); + server->Bind(kServerAddr); + listen_socket_ = + std::make_unique(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(data), len, options); + vss_->ProcessMessagesUntilIdle(); + return (ret == static_cast(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 vss_; + rtc::AutoSocketServerThread thread_; + std::unique_ptr send_socket_; + std::unique_ptr listen_socket_; + std::unique_ptr recv_socket_; + std::list 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 +#include + +#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 +WrappingAsyncDnsResolverFactory::Create() { + return std::make_unique(wrapped_factory_->Create()); +} + +std::unique_ptr +WrappingAsyncDnsResolverFactory::CreateAndResolve( + const rtc::SocketAddress& addr, + std::function callback) { + std::unique_ptr resolver = Create(); + resolver->Start(addr, std::move(callback)); + return resolver; +} + +std::unique_ptr +WrappingAsyncDnsResolverFactory::CreateAndResolve( + const rtc::SocketAddress& addr, + int family, + std::function callback) { + std::unique_ptr 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 +#include +#include + +#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 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 CreateAndResolve( + const rtc::SocketAddress& addr, + std::function callback) override; + + std::unique_ptr CreateAndResolve( + const rtc::SocketAddress& addr, + int family, + std::function callback) override; + + std::unique_ptr Create() override; + + private: + const std::unique_ptr 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()); + + std::unique_ptr 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 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 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()); + + // 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 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 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 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 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 +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 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 +BasicIceController::GetBestWritableConnectionPerNetwork() const { + std::vector 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 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 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 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 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 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 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 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 +#include +#include +#include +#include + +#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 connections() const override { + return rtc::ArrayView( + const_cast(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 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 GetBestConnectionByNetwork() + const; + std::vector GetBestWritableConnectionPerNetwork() const; + + bool ReadyToSend(const Connection* connection) const; + bool PresumedWritable(const Connection* conn) const; + + int CompareCandidatePairNetworks( + const Connection* a, + const Connection* b, + absl::optional 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 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 receiving_unchanged_threshold, + bool* missed_receiving_unchanged_threshold) const; + + SwitchResult HandleInitialSelectDampening(IceSwitchReason reason, + const Connection* new_connection); + + std::function ice_transport_state_func_; + std::function ice_role_func_; + std::function 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 connections_; + std::set pinged_connections_; + std::set 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 + +#include + +#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 +BasicPacketSocketFactory::CreateAsyncDnsResolver() { + return std::make_unique( + 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 + +#include +#include + +#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 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 + +#include +#include +#include +#include + +#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& 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& 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( + IceGoogMiscInfoBindingRequestAttributeIndex::SUPPORT_GOOG_PING_VERSION); + +constexpr int kSupportGoogPingVersionResponseIndex = static_cast( + 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 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 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, + 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& 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& 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& 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 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 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(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(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(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( + 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( + 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(requests_, this, BuildPingRequest()); + + if (ShouldSendGoogPing(req->msg())) { + auto message = std::make_unique(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 Connection::BuildPingRequest() { + auto message = std::make_unique(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( + STUN_ATTR_USERNAME, + port()->CreateStunUsername(remote_candidate_.username()))); + message->AddAttribute(std::make_unique( + 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( + 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( + 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(STUN_ATTR_USE_CANDIDATE)); + } + if (nomination_ && nomination_ != acked_nomination()) { + message->AddAttribute(std::make_unique( + STUN_ATTR_NOMINATION, nomination_)); + } + } + + message->AddAttribute(std::make_unique( + STUN_ATTR_PRIORITY, prflx_priority())); + + if (port()->send_retransmit_count_attribute()) { + message->AddAttribute(std::make_unique( + 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& 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& 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& 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(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(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(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 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& max_outstanding_pings) const { + RTC_DCHECK_RUN_ON(network_thread_); + if (!max_outstanding_pings.has_value()) { + return false; + } + if (static_cast(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, + 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 +#include +#include + +#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& value_ms); + int unwritable_min_checks() const; + void set_unwritable_min_checks(const absl::optional& value); + int inactive_timeout() const; + void set_inactive_timeout(const absl::optional& value); + + // Gets the `ConnectionInfo` stats, where `best_connection` has not been + // populated (default value false). + ConnectionInfo stats(); + + sigslot::signal1 SignalStateChange; + + // Sent when the connection has decided that it is no longer of value. It + // will delete itself immediately after this call. + sigslot::signal1 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 SignalReadPacket; + + sigslot::signal1 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 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& nomination = absl::nullopt); + std::unique_ptr BuildPingRequest() RTC_RUN_ON(network_thread_); + + int64_t last_ping_response_received() const; + const absl::optional& 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& 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 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& 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 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, 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_; + 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 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 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 last_ping_id_received_ + RTC_GUARDED_BY(network_thread_); + + absl::optional unwritable_timeout_ RTC_GUARDED_BY(network_thread_); + absl::optional unwritable_min_checks_ RTC_GUARDED_BY(network_thread_); + absl::optional 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 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 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 remote_support_goog_ping_ + RTC_GUARDED_BY(network_thread_); + std::unique_ptr 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, + 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 + +#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 current_round_trip_time_ms; + + // https://w3c.github.io/webrtc-stats/#dom-rtcicecandidatepairstats-lastpacketreceivedtimestamp + absl::optional last_data_received; + absl::optional last_data_sent; +}; + +// Information about all the candidate pairs of a channel. +typedef std::vector 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 + +#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 Create( + const cricket::IceControllerFactoryArgs& args) override { + return std::make_unique(args); + } +}; + +} // namespace + +namespace webrtc { + +DefaultIceTransport::DefaultIceTransport( + std::unique_ptr internal) + : internal_(std::move(internal)) {} + +DefaultIceTransport::~DefaultIceTransport() { + RTC_DCHECK_RUN_ON(&thread_checker_); +} + +rtc::scoped_refptr +DefaultIceTransportFactory::CreateIceTransport( + const std::string& transport_name, + int component, + IceTransportInit init) { + BasicIceControllerFactory factory; + init.set_ice_controller_factory(&factory); + return rtc::make_ref_counted( + 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 +#include + +#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 internal); + ~DefaultIceTransport(); + + cricket::IceTransportInternal* internal() override { + RTC_DCHECK_RUN_ON(&thread_checker_); + return internal_.get(); + } + + private: + const SequenceChecker thread_checker_{}; + std::unique_ptr 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 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 +#include +#include + +#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(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(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(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 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 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(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& 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 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 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(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 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(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(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(data), + size), + written, error) == rtc::SR_SUCCESS) + ? static_cast(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 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(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 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(), + 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(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(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(state)); + } + RTC_LOG(LS_VERBOSE) << ToString() << ": set_dtls_state from:" + << static_cast(dtls_state_) << " to " + << static_cast(state); + dtls_state_ = state; + SendDtlsState(this, state); +} + +void DtlsTransport::OnDtlsHandshakeError(rtc::SSLHandshakeError error) { + SendDtlsHandshakeError(error); +} + +void DtlsTransport::ConfigureHandshakeTimeout() { + RTC_DCHECK(dtls_); + absl::optional 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 +#include +#include + +#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 buffer, + size_t& read, + int& error) override; + rtc::StreamResult Write(rtc::ArrayView 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& certificate) override; + rtc::scoped_refptr 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 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 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 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 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 dtls_; // The DTLS stream + StreamInterfaceChannel* + downward_; // Wrapper for ice_transport_, owned by dtls_. + const std::vector srtp_ciphers_; // SRTP ciphers to use with DTLS. + bool dtls_active_ = false; + rtc::scoped_refptr local_certificate_; + absl::optional 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 +#include + +#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 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 +#include + +#include +#include +#include + +#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 GetLocalCertificate() + const = 0; + + virtual bool SetLocalCertificate( + const rtc::scoped_refptr& certificate) = 0; + + // Gets a copy of the remote side's SSL certificate chain. + virtual std::unique_ptr 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 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 + void SubscribeDtlsTransportState(F&& callback) { + dtls_transport_state_callback_list_.AddReceiver(std::forward(callback)); + } + + template + void SubscribeDtlsTransportState(const void* id, F&& callback) { + dtls_transport_state_callback_list_.AddReceiver(id, + std::forward(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 + void SubscribeDtlsHandshakeError(F&& callback) { + dtls_handshake_error_callback_list_.AddReceiver(std::forward(callback)); + } + + void SendDtlsHandshakeError(rtc::SSLHandshakeError error) { + dtls_handshake_error_callback_list_.Send(error); + } + + protected: + DtlsTransportInternal(); + + private: + webrtc::CallbackList + dtls_handshake_error_callback_list_; + webrtc::CallbackList + 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 +#include +#include +#include + +#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& cert, + bool modify_digest = false) { + std::unique_ptr 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(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& 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( + 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(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 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(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(rv)); + ++sent; + } while (sent < count); + } + + int SendInvalidSrtpPacket(size_t size) { + std::unique_ptr 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(data[0]) != 0x80)) { + return false; + } + uint32_t packet_num = rtc::GetBE32(data + kPacketNumOffset); + for (size_t i = kPacketHeaderLen; i < size; ++i) { + if (static_cast(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(data[i]) == (packet_num & 0xff)) { + ++num_matches; + } + } + return (num_matches < ((static_cast(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 certificate_; + std::unique_ptr fake_ice_transport_; + std::unique_ptr dtls_transport_; + size_t packet_size_ = 0u; + std::set 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> { +}; + +// 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 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 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, 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& 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{ + CALLER_RECEIVES_FINGERPRINT, CALLER_WRITABLE, + CALLER_RECEIVES_CLIENTHELLO, HANDSHAKE_FINISHES}, + std::vector{ + CALLER_WRITABLE, CALLER_RECEIVES_FINGERPRINT, + CALLER_RECEIVES_CLIENTHELLO, HANDSHAKE_FINISHES}, + std::vector{ + CALLER_WRITABLE, CALLER_RECEIVES_CLIENTHELLO, + CALLER_RECEIVES_FINGERPRINT, HANDSHAKE_FINISHES}, + std::vector{ + CALLER_WRITABLE, CALLER_RECEIVES_CLIENTHELLO, + HANDSHAKE_FINISHES, CALLER_RECEIVES_FINGERPRINT}, + std::vector{ + CALLER_RECEIVES_FINGERPRINT, CALLER_RECEIVES_CLIENTHELLO, + CALLER_WRITABLE, HANDSHAKE_FINISHES}, + std::vector{ + CALLER_RECEIVES_CLIENTHELLO, CALLER_RECEIVES_FINGERPRINT, + CALLER_WRITABLE, HANDSHAKE_FINISHES}, + std::vector{ + CALLER_RECEIVES_CLIENTHELLO, CALLER_WRITABLE, + CALLER_RECEIVES_FINGERPRINT, HANDSHAKE_FINISHES}, + std::vector{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 +#include +#include +#include + +#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 ice) + : owned_ice_transport_(std::move(ice)), + transport_name_(owned_ice_transport_->transport_name()), + component_(owned_ice_transport_->component()), + dtls_fingerprint_("", rtc::ArrayView()) { + 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(name, component)) { + } + FakeDtlsTransport(const std::string& name, + int component, + rtc::Thread* network_thread) + : FakeDtlsTransport(std::make_unique(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(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 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& 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 cipher_suite) { + ssl_cipher_suite_ = cipher_suite; + } + rtc::scoped_refptr GetLocalCertificate() const override { + return local_cert_; + } + std::unique_ptr GetRemoteSSLCertChain() const override { + if (!remote_cert_) { + return nullptr; + } + return std::make_unique(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 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 network_route) { + SignalNetworkRouteChanged(network_route); + } + + FakeIceTransport* ice_transport_; + std::unique_ptr owned_ice_transport_; + std::string transport_name_; + int component_; + FakeDtlsTransport* dest_ = nullptr; + rtc::scoped_refptr 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 dtls_role_; + int crypto_suite_ = rtc::kSrtpAes128CmSha1_80; + absl::optional 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 +#include +#include +#include + +#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 GetRttEstimate() override { return absl::nullopt; } + + const Connection* selected_connection() const override { return nullptr; } + absl::optional 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(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 network_route() const override { + RTC_DCHECK_RUN_ON(network_thread_); + return network_route_; + } + void SetNetworkRoute(absl::optional 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(), 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 transport_state_ + RTC_GUARDED_BY(network_thread_); + absl::optional 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 network_route_ + RTC_GUARDED_BY(network_thread_); + std::map 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 internal) + : internal_(std::move(internal)) {} + + cricket::IceTransportInternal* internal() override { return internal_.get(); } + + private: + std::unique_ptr 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 +#include + +#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(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 network_route() const override { + return network_route_; + } + void SetNetworkRoute(absl::optional 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(), packet.size(), + TimeMicros(), 0); + } + } + + CopyOnWriteBuffer last_sent_packet_; + std::string transport_name_; + FakePacketTransport* dest_ = nullptr; + bool writable_ = false; + bool receiving_ = false; + + std::map options_; + int error_ = 0; + + absl::optional 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 +#include +#include +#include + +#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 ReadyPorts() const override { + return ready_ports_; + } + std::vector 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& 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& 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 port_; + int port_config_count_; + std::vector candidates_; + std::vector ready_ports_; + bool allocation_done_ = false; + bool is_cleared = false; + ServerAddresses stun_servers_; + std::vector 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 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 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 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 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 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 +#include + +#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 ice_transport_state_func; + std::function ice_role_func; + std::function 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 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 + +#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 +#include +#include + +#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 connection; + + // An optional recheck event for when a Switch() should be attempted again. + absl::optional recheck_event; + + // A vector with connection to run ForgetLearnedState on. + std::vector 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(conn) + : absl::nullopt), + recheck_delay_ms(_recheck_delay_ms) {} + + // Connection that we should (optionally) ping. + const absl::optional 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 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 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& 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 + +#include "p2p/base/transport_description.h" + +namespace cricket { + +class IceCredentialsIterator { + public: + explicit IceCredentialsIterator(const std::vector&); + 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 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 + +#include "test/gtest.h" + +using cricket::IceCredentialsIterator; +using cricket::IceParameters; + +TEST(IceCredentialsIteratorTest, GetEmpty) { + std::vector empty; + IceCredentialsIterator iterator(empty); + // Verify that we can get credentials even if input is empty. + IceParameters credentials1 = iterator.GetIceCredentials(); +} + +TEST(IceCredentialsIteratorTest, GetOne) { + std::vector 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 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 + +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 + +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 + +#include +#include + +#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 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 receiving_timeout; + // Time interval in milliseconds to ping a backup connection when the ICE + // channel is strongly connected. + absl::optional 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 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 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 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 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 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 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 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 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 ice_inactive_timeout; + + // The interval in milliseconds at which STUN candidates will resend STUN + // binding requests to keep NAT bindings open. + absl::optional stun_keepalive_interval; + + absl::optional 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 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 GetSelectedCandidatePair() + const = 0; + + sigslot::signal1 SignalGatheringState; + + // Handles sending and receiving of candidates. + sigslot::signal2 + SignalCandidateGathered; + + sigslot::signal2 + SignalCandidateError; + + sigslot::signal2 + 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 SignalRouteChange; + + sigslot::signal1 + SignalCandidatePairChanged; + + // Invoked when there is conflict in the ICE role between local and remote + // agents. + sigslot::signal1 SignalRoleConflict; + + // Emitted whenever the transport state changed. + // TODO(bugs.webrtc.org/9308): Remove once all uses have migrated to the new + // IceTransportState. + sigslot::signal1 SignalStateChanged; + + // Emitted whenever the new standards-compliant transport state changed. + sigslot::signal1 SignalIceTransportStateChanged; + + // Invoked when the transport is being destroyed. + sigslot::signal1 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 + +#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 Create( + const cricket::ActiveIceControllerFactoryArgs& args) { + RecordActiveIceControllerCreated(); + return std::make_unique(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 +#include + +#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; + + explicit MockDnsResolvingPacketSocketFactory(SocketFactory* socket_factory) + : BasicPacketSocketFactory(socket_factory) {} + + std::unique_ptr CreateAsyncDnsResolver() + override { + std::unique_ptr resolver = + std::make_unique(); + 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 + +#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), + (override)); + MOCK_METHOD(void, SendPingRequest, (const Connection*), (override)); + MOCK_METHOD(void, + SwitchSelectedConnection, + (const Connection*, IceSwitchReason), + (override)); + MOCK_METHOD(bool, + PruneConnections, + (rtc::ArrayView), + (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 +#include + +#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, + 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, + PruneConnections, + (), + (override)); +}; + +class MockIceControllerFactory : public cricket::IceControllerFactoryInterface { + public: + ~MockIceControllerFactory() override = default; + + std::unique_ptr Create( + const cricket::IceControllerFactoryArgs& args) override { + RecordIceControllerCreated(); + return std::make_unique(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 +#include +#include + +#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 GetRttEstimate() override { return absl::nullopt; } + const Connection* selected_connection() const override { return nullptr; } + absl::optional 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 +#include + +#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 +#include + +#include +#include +#include +#include +#include + +#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(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(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::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( + 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 + 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( + 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( + 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 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 session) { + RTC_DCHECK_RUN_ON(network_thread_); + + session->set_generation(static_cast(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 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 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 +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 active_connections; + for (Connection* connection : connections()) { + if (connection->active()) { + active_connections.push_back(connection); + } + } + if (active_connections.empty()) { + return IceTransportState::STATE_FAILED; + } + + std::set 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(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(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(state), + static_cast(IceRestartState::MAX_VALUE)); + } + + for (const auto& session : allocator_sessions_) { + if (session->IsStopped()) { + continue; + } + session->StopGettingPorts(); + } + + // Time for a new allocator. + std::unique_ptr 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::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& 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(network_info >> 16); + network_cost = static_cast(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&& 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 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::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(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 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(it->second); +} + +rtc::ArrayView 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 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 connections_to_prune = + ice_adapter_->LegacyPruneConnections(); + PruneConnections(connections_to_prune); +} + +bool P2PTransportChannel::PruneConnections( + rtc::ArrayView 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(state_) << " to " + << static_cast(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 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& 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& 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 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( + /* 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(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 +P2PTransportChannel::IceControllerAdapter::LegacyConnections() const { + RTC_DCHECK_RUN_ON(transport_->network_thread_); + if (active_ice_controller_) { + return rtc::ArrayView(transport_->connections_.data(), + transport_->connections_.size()); + } + + rtc::ArrayView res = legacy_ice_controller_->connections(); + return rtc::ArrayView(const_cast(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 +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 +#include + +#include +#include +#include +#include +#include +#include +#include + +#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 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 GetRttEstimate() override; + const Connection* selected_connection() const override; + absl::optional 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 connections) override; + bool PruneConnections( + rtc::ArrayView 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& ports() { + RTC_DCHECK_RUN_ON(network_thread_); + return ports_; + } + const std::vector& 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 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 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& 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 + 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 session); + void AddConnection(Connection* connection); + + void OnPortReady(PortAllocatorSession* session, PortInterface* port); + void OnPortsPruned(PortAllocatorSession* session, + const std::vector& ports); + void OnCandidatesReady(PortAllocatorSession* session, + const std::vector& candidates); + void OnCandidateError(PortAllocatorSession* session, + const IceCandidateErrorEvent& event); + void OnCandidatesRemoved(PortAllocatorSession* session, + const std::vector& 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(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(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 + 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> 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 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 pruned_ports_ RTC_GUARDED_BY(network_thread_); + + Connection* selected_connection_ RTC_GUARDED_BY(network_thread_) = nullptr; + std::vector connections_ RTC_GUARDED_BY(network_thread_); + + std::vector 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 OptionMap; + OptionMap options_ RTC_GUARDED_BY(network_thread_); + IceParameters ice_parameters_ RTC_GUARDED_BY(network_thread_); + std::vector 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 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 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 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 LegacyPruneConnections(); + + private: + P2PTransportChannel* transport_; + std::unique_ptr legacy_ice_controller_; + std::unique_ptr active_ice_controller_; + }; + std::unique_ptr ice_adapter_ + RTC_GUARDED_BY(network_thread_); + + struct CandidateAndResolver final { + CandidateAndResolver( + const Candidate& candidate, + std::unique_ptr&& resolver); + ~CandidateAndResolver(); + // Moveable, but not copyable. + CandidateAndResolver(CandidateAndResolver&&) = default; + CandidateAndResolver& operator=(CandidateAndResolver&&) = default; + + Candidate candidate_; + std::unique_ptr resolver_; + }; + std::vector 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 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 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 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 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 +#include +#include +#include +#include + +#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 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 turn_servers(1, turn_server); + + std::unique_ptr allocator = + std::make_unique( + network_manager, + std::make_unique(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(); + 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 mock_async_dns_resolver_; + webrtc::MockAsyncDnsResolverResult mock_async_dns_resolver_result_; + std::function 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 ch_packets_; + std::unique_ptr 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 allocator_; + webrtc::AsyncDnsResolverFactoryInterface* async_dns_resolver_factory_; + ChannelData cd1_; + ChannelData cd2_; + IceRole role_; + uint64_t tiebreaker_; + bool role_conflict_; + bool save_candidates_; + std::vector saved_candidates_; + bool ready_to_send_ = false; + std::map 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 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 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(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 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& 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 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& 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& 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 vss_; + std::unique_ptr nss_; + std::unique_ptr ss_; + rtc::AutoSocketServerThread main_; + rtc::scoped_refptr safety_ = + PendingTaskSafetyFlag::Create(); + std::unique_ptr 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(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 { + 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(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(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(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(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(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(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(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(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(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 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 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(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(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 { + 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(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(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 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 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 view = channel->connections(); + std::vector 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(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, + public sigslot::has_slots<> { + public: + P2PTransportChannelPingTest() + : field_trials_(GetParam()), + vss_(std::make_unique()), + packet_socket_factory_( + std::make_unique(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(ch->ports()[0]); + } + + Port* GetPrunedPort(P2PTransportChannel* ch) { + if (ch->pruned_ports().empty()) { + return nullptr; + } + return static_cast(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 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& piggyback_ping_id) { + IceMessage msg(STUN_BINDING_REQUEST); + msg.AddAttribute(std::make_unique( + STUN_ATTR_USERNAME, + conn->local_candidate().username() + ":" + std::string(remote_ufrag))); + msg.AddAttribute( + std::make_unique(STUN_ATTR_PRIORITY, priority)); + if (nomination != 0) { + msg.AddAttribute(std::make_unique( + STUN_ATTR_NOMINATION, nomination)); + } + if (piggyback_ping_id) { + msg.AddAttribute(std::make_unique( + 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 vss_; + std::unique_ptr 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 last_candidate_change_event_; + IceTransportState channel_state_ = IceTransportState::STATE_INIT; + absl::optional 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( + STUN_ATTR_USERNAME, kIceUfrag[1])); + uint32_t prflx_priority = ICE_TYPE_PREFERENCE_PRFLX << 24; + request.AddAttribute(std::make_unique(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( + STUN_ATTR_USERNAME, kIceUfrag[1])); + uint32_t prflx_priority = ICE_TYPE_PREFERENCE_PRFLX << 24; + request.AddAttribute(std::make_unique(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(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( + STUN_ATTR_USERNAME, kIceUfrag[1])); + uint32_t prflx_priority = ICE_TYPE_PREFERENCE_PRFLX << 24; + request.AddAttribute(std::make_unique(STUN_ATTR_PRIORITY, + prflx_priority)); + TestUDPPort* port = static_cast(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(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( + STUN_ATTR_USERNAME, kIceUfrag[1])); + uint32_t prflx_priority = ICE_TYPE_PREFERENCE_PRFLX << 24; + request.AddAttribute(std::make_unique(STUN_ATTR_PRIORITY, + prflx_priority)); + request.AddAttribute( + std::make_unique(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 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 allocator_; + rtc::FakeNetworkManager network_manager_; + TestTurnServer turn_server_; + std::unique_ptr 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 {}; + +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 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(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(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(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(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(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 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(); + 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 { +}; + +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 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 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 Create( + const cricket::IceControllerFactoryArgs& args) override { + auto controller = std::make_unique(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 mock_async_resolver = + std::make_unique(); + // 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 mock_async_resolver = + std::make_unique(); + // 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 mock_async_resolver = + std::make_unique(); + 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> { + 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 PacketTransportInternal::network_route() const { + return absl::optional(); +} + +} // 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 +#include + +#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 network_route() const; + + // Emitted when the writable state, represented by `writable()`, changes. + sigslot::signal1 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 SignalReadyToSend; + + // Emitted when receiving state changes to true. + sigslot::signal1 SignalReceivingState; + + // Signalled each time a packet is received on this channel. + sigslot::signal5 + SignalReadPacket; + + // Signalled each time a packet is sent on this channel. + sigslot::signal2 + SignalSentPacket; + + // Signalled when the current network route has changed. + sigslot::signal1> SignalNetworkRouteChanged; + + // Signalled when the transport is closed. + sigslot::signal1 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 + +#include +#include +#include +#include + +#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 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(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& 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 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 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* 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 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 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& 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 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 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 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(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 +#include +#include +#include +#include +#include + +#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 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& stun_stats() const { return stun_stats_; } + + private: + Candidate candidate_; + // STUN port stats if this candidate is a STUN candidate. + absl::optional stun_stats_; +}; + +typedef std::vector CandidateStatsList; + +const char* ProtoToString(ProtocolType proto); +absl::optional 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 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 SignalCandidateReady; + // Provides all of the above information in one handy object. + const std::vector& Candidates() const override; + // Fired when candidate discovery failed using certain server. + sigslot::signal2 SignalCandidateError; + + // SignalPortComplete is sent when port completes the task of candidates + // allocation. + sigslot::signal1 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 SignalPortError; + + void SubscribePortDestroyed( + std::function 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 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& 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* 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 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* 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 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 weak_factory_; + webrtc::AlwaysValidPointer + field_trials_; + + bool MaybeObfuscateAddress(Candidate* c, + absl::string_view type, + bool is_final) RTC_RUN_ON(thread_); + + friend class Connection; + webrtc::CallbackList 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 +#include +#include + +#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& turn_servers, + int candidate_pool_size, + bool prune_turn_ports, + webrtc::TurnCustomizer* turn_customizer, + const absl::optional& 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& turn_servers, + int candidate_pool_size, + webrtc::PortPrunePolicy turn_port_prune_policy, + webrtc::TurnCustomizer* turn_customizer, + const absl::optional& 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(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(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(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 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( + CreateSessionInternal(content_name, component, ice_ufrag, ice_pwd)); + session->SetCandidateFilter(candidate_filter()); + session->set_ice_tiebreaker(tiebreaker_); + return session; +} + +std::unique_ptr 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 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>::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 PortAllocator::GetPooledIceCredentials() { + CheckRunOnValidThreadAndInitialized(); + std::vector 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 +#include +#include +#include + +#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 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 tls_alpn_protocols; + std::vector 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& 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 ReadyPorts() const = 0; + virtual std::vector 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 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&> + SignalPortsPruned; + sigslot::signal2&> + SignalCandidatesReady; + sigslot::signal2 + SignalCandidateError; + // Candidates should be signaled to be removed when the port that generated + // the candidates is removed. + sigslot::signal2&> + SignalCandidatesRemoved; + sigslot::signal1 SignalCandidatesAllocationDone; + + sigslot::signal2 + 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& turn_servers, + int candidate_pool_size, + bool prune_turn_ports, + webrtc::TurnCustomizer* turn_customizer = nullptr, + const absl::optional& + stun_candidate_keepalive_interval = absl::nullopt); + bool SetConfiguration(const ServerAddresses& stun_servers, + const std::vector& turn_servers, + int candidate_pool_size, + webrtc::PortPrunePolicy turn_port_prune_policy, + webrtc::TurnCustomizer* turn_customizer = nullptr, + const absl::optional& + 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& turn_servers() const { + CheckRunOnValidThreadIfInitialized(); + return turn_servers_; + } + + int candidate_pool_size() const { + CheckRunOnValidThreadIfInitialized(); + return candidate_pool_size_; + } + + const absl::optional& 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 that shall be categorized as VPN. + // Implemented by BasicPortAllocator. + virtual void SetVpnList(const std::vector& vpn_list) {} + + std::unique_ptr 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 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 GetPooledIceCredentials(); + + // Fired when `candidate_filter_` changes. + sigslot::signal2 + 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>& 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 turn_servers_; + int candidate_pool_size_ = 0; // Last value passed into SetConfiguration. + std::vector> 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 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>::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 + +#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()), + main_(vss_.get()), + packet_socket_factory_( + std::make_unique(vss_.get())), + allocator_(std::make_unique( + 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(), + candidate_pool_size, webrtc::NO_PRUNE)); + } + + void SetConfigurationWithPoolSizeExpectFailure(int candidate_pool_size) { + EXPECT_FALSE(allocator_->SetConfiguration( + cricket::ServerAddresses(), std::vector(), + candidate_pool_size, webrtc::NO_PRUNE)); + } + + std::unique_ptr CreateSession( + absl::string_view content_name, + int component, + absl::string_view ice_ufrag, + absl::string_view ice_pwd) { + return std::unique_ptr( + static_cast( + allocator_ + ->CreateSession(content_name, component, ice_ufrag, ice_pwd) + .release())); + } + + const cricket::FakePortAllocatorSession* GetPooledSession() const { + return static_cast( + allocator_->GetPooledSession()); + } + + std::unique_ptr TakePooledSession() { + return std::unique_ptr( + static_cast( + 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 vss_; + rtc::AutoSocketServerThread main_; + std::unique_ptr packet_socket_factory_; + std::unique_ptr 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 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 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 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 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 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 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 session( + static_cast( + 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 + +#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 +#include +#include + +#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& 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 + 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 callback) = 0; + + // Signaled when Port discovers ice role conflict with the peer. + sigslot::signal1 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 + SignalReadPacket; + + // Emitted each time a packet is sent on this port. + sigslot::signal1 SignalSentPacket; + + virtual std::string ToString() const = 0; + + virtual void GetStunStats(absl::optional* 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 + +#include +#include +#include +#include +#include +#include +#include + +#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 CopyStunMessage(const IceMessage& src) { + auto dst = std::make_unique(); + 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* 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(); + auto buf = std::make_unique>( + static_cast(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(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> last_stun_buf_; + std::unique_ptr 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(), + 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(), + 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 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_; + + int complete_count_ = 0; + Connection* conn_ = nullptr; + SocketAddress remote_address_; + std::unique_ptr 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 CreateUdpPort(const SocketAddress& addr) { + return CreateUdpPort(addr, &socket_factory_); + } + std::unique_ptr 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 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 CreateTcpPort(const SocketAddress& addr) { + return CreateTcpPort(addr, &socket_factory_); + } + std::unique_ptr 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 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 CreateRelayPort(const SocketAddress& addr, + ProtocolType int_proto, + ProtocolType ext_proto) { + return CreateTurnPort(addr, &socket_factory_, int_proto, ext_proto); + } + std::unique_ptr 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 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 CreateNatServer(const SocketAddress& addr, + rtc::NATType type) { + return std::make_unique(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 port1, + absl::string_view name2, + std::unique_ptr 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(ch1->conn()); + TCPConnection* tcp_conn2 = static_cast(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(ch1.conn()) + ->set_reconnection_timeout(kTcpReconnectTimeout); + static_cast(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(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 CreateStunMessage(StunMessageType type) { + auto msg = std::make_unique(type, "TESTTESTTEST"); + return msg; + } + std::unique_ptr CreateStunMessageWithUsername( + StunMessageType type, + absl::string_view username) { + std::unique_ptr msg = CreateStunMessage(type); + msg->AddAttribute(std::make_unique( + STUN_ATTR_USERNAME, std::string(username))); + return msg; + } + std::unique_ptr CreateTestPort(const rtc::SocketAddress& addr, + absl::string_view username, + absl::string_view password) { + auto port = + std::make_unique(&main_, "test", &socket_factory_, + MakeNetwork(addr), 0, 0, username, password); + port->SignalRoleConflict.connect(this, &PortTest::OnRoleConflict); + return port; + } + std::unique_ptr 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 CreateTestPort(const rtc::Network* network, + absl::string_view username, + absl::string_view password) { + auto port = std::make_unique(&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 networks_; + std::unique_ptr ss_; + rtc::AutoSocketServerThread main_; + rtc::BasicPacketSocketFactory socket_factory_; + std::unique_ptr nat_server1_; + std::unique_ptr nat_server2_; + rtc::NATSocketFactory nat_factory1_; + rtc::NATSocketFactory nat_factory2_; + rtc::BasicPacketSocketFactory nat_socket_factory1_; + rtc::BasicPacketSocketFactory nat_socket_factory2_; + std::unique_ptr 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 port1, + absl::string_view name2, + std::unique_ptr 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 CreateAsyncDnsResolver() + override { + return nullptr; + } + + private: + AsyncPacketSocket* next_udp_socket_; + AsyncListenSocket* next_server_tcp_socket_; + absl::optional 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(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(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 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(), + 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 modified_req( + CreateStunMessage(STUN_BINDING_REQUEST)); + const StunByteStringAttribute* username_attr = + msg->GetByteString(STUN_ATTR_USERNAME); + modified_req->AddAttribute(std::make_unique( + 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( + STUN_ATTR_ICE_CONTROLLING, kTiebreaker1 - 1)); + modified_req->AddMessageIntegrity("lpass"); + modified_req->AddFingerprint(); + + lport->Reset(); + auto buf = std::make_unique(); + 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(), + 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(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(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 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 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 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(), 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 request = CopyStunMessage(*msg); + + // Receive the BINDING-REQUEST and respond with BINDING-RESPONSE. + rconn->OnReadPacket(lport->last_stun_buf()->data(), + 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(), + 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(), 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(), 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(), + 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(), + 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(), + 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(), + 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(), + 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 in_msg, out_msg; + auto buf = std::make_unique(); + 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( + 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( + 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 in_msg, out_msg; + auto buf = std::make_unique(); + 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 in_msg, out_msg; + auto buf = std::make_unique(); + 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 in_msg, out_msg; + auto buf = std::make_unique(); + 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( + 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( + 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 port(CreateTestPort(kLocalAddr2, "rfrag", "rpass")); + + std::unique_ptr in_msg, out_msg; + auto buf = std::make_unique(); + 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(), + 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 in_msg, out_msg; + std::unique_ptr 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(), + 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 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 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(), + 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 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(cricket::IceGoogMiscInfoBindingRequestAttributeIndex:: + SUPPORT_GOOG_PING_VERSION)) { + return absl::nullopt; + } + + return goog_misc->GetType( + static_cast(cricket::IceGoogMiscInfoBindingRequestAttributeIndex:: + SUPPORT_GOOG_PING_VERSION)); + } + + if (msg->type() == STUN_BINDING_RESPONSE) { + if (goog_misc->Size() < + static_cast(cricket::IceGoogMiscInfoBindingResponseAttributeIndex:: + SUPPORT_GOOG_PING_VERSION)) { + return absl::nullopt; + } + + return goog_misc->GetType( + static_cast(cricket::IceGoogMiscInfoBindingResponseAttributeIndex:: + SUPPORT_GOOG_PING_VERSION)); + } + return absl::nullopt; +} + +} // namespace + +class GoogPingTest + : public PortTest, + public ::testing::WithParamInterface> {}; + +// 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(), + 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( + 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( + 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 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(), + 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(), + 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* 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(), + 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 reply; + SendPingAndCaptureReply(lconn, rconn, ms, &reply); + lconn->OnReadPacket(reply.data(), reply.size(), + /* packet_time_us */ -1); + } + + void OnConnectionStateChange(Connection* connection) { num_state_changes_++; } + + private: + std::unique_ptr lport_; + std::unique_ptr 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::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::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::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::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 reply; + SendPingAndCaptureReply(lconn, rconn, 10, &reply); + + lconn->ForgetLearnedState(); + + EXPECT_FALSE(lconn->writable()); + EXPECT_FALSE(lconn->receiving()); + + lconn->OnReadPacket(reply.data(), 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 +#include +#include + +#include +#include +#include +#include + +#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(buf) = rtc::HostToNetwork32(val); +} + +inline void short_to_bytes(uint16_t val, void* buf) { + *static_cast(buf) = rtc::HostToNetwork16(val); +} + +inline uint32_t bytes_to_long(const void* buf) { + return rtc::NetworkToHost32(*static_cast(buf)); +} + +inline uint16_t bytes_to_short(const void* buf) { + return rtc::NetworkToHost16(*static_cast(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(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(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(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(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(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(available_space); + + if (bWasClosed) { + attemptSend(sfImmediateAck); + } + } + + return static_cast(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(available_space)) { + RTC_DCHECK(!bCtrl); + len = static_cast(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(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(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 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(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(bytes_read) == len); + } + +#if _DEBUGMSG >= _DBG_VERBOSE + RTC_LOG(LS_INFO) << "<-- (flags) + << ">"; +#endif // _DEBUGMSG + + IPseudoTcpNotify::WriteResult wres = m_notify->TcpWritePacket( + this, reinterpret_cast(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(buffer) + HEADER_SIZE; + seg.len = size - HEADER_SIZE; + +#if _DEBUGMSG >= _DBG_VERBOSE + RTC_LOG(LS_INFO) << "--> (seg.flags) + << ">"; +#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(nTimeout, + rtc::TimeDiff32(m_t_ack + m_ack_delay, now)); + } + if (m_rto_base) { + nTimeout = std::min(nTimeout, + rtc::TimeDiff32(m_rto_base + m_rx_rto, now)); + } + if (m_snd_wnd == 0) { + nTimeout = std::min(nTimeout, + rtc::TimeDiff32(m_lastsend + m_rx_rto, now)); + } +#if PSEUDO_KEEPALIVE + if (m_state == TCP_ESTABLISHED) { + nTimeout = std::min( + 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(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(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(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(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(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(available_space)) { + uint32_t nAdjust = + seg.seq + seg.len - m_rcv_nxt - static_cast(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(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(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(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(buf.Length()); + queue(buf.Data(), static_cast(buf.Length()), true); +} + +void PseudoTcp::parseOptions(const char* data, uint32_t len) { + std::set 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(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, ©)) + 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, ©)) + 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(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(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 +#include + +#include +#include + +#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 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 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 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 + +#include +#include +#include +#include +#include + +#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(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(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(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(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), 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(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(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(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(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(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(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(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(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(recv_position_[0]); + } + + uint32_t EstimateSendWindowSize() const { + return static_cast(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(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(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(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 send_position_; + std::vector 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 + +#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) {} + // 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 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 +#include +#include +#include + +#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()), + thread_(vss_.get()), + ice_transport_(std::make_unique()), + packet_socket_factory_( + std::make_unique(vss_.get())), + allocator_(std::make_unique( + 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 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 vss_; + rtc::AutoSocketServerThread thread_; + std::unique_ptr ice_transport_; + std::unique_ptr regathering_controller_; + std::unique_ptr packet_socket_factory_; + std::unique_ptr allocator_; + std::unique_ptr allocator_session_; + std::map 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 +#include + +#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 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(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 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> + 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(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* stats) { + *stats = stats_; +} + +void UDPPort::set_stun_keepalive_delay(const absl::optional& 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(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& existing_candidates = Candidates(); + std::vector::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::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 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 +#include +#include +#include + +#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 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 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 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 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* stats) override; + + void set_stun_keepalive_delay(const absl::optional& 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 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> + 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 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 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 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 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 + +#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 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 ss_; + rtc::AutoSocketServerThread thread_; + rtc::Network network_; + rtc::BasicPacketSocketFactory socket_factory_; + std::unique_ptr stun_port_; + std::unique_ptr stun_server_1_; + std::unique_ptr stun_server_2_; + std::unique_ptr socket_; + std::unique_ptr 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 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 +#include +#include +#include + +#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 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 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 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(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 +#include + +#include +#include +#include +#include + +#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 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> RequestMap; + + webrtc::TaskQueueBase* const thread_; + RequestMap requests_ RTC_GUARDED_BY(thread_); + const std::function 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 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 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 +#include + +#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 CreateStunMessage( + StunMessageType type, + const StunMessage* req = nullptr) { + std::unique_ptr msg = std::make_unique( + type, req ? req->transaction_id() : StunMessage::GenerateTransactionId()); + return msg; +} + +int TotalDelay(int sends) { + std::vector 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 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 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 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 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 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 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 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 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 +#include + +#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 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 +#include + +#include + +#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 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 + +#include +#include + +#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(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 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 ss_; + rtc::Thread network_; + std::unique_ptr server_; + std::unique_ptr 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(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 + +#include +#include + +#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::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(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(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::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 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 +#include +#include + +#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 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 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 socket_options_; + + int error_; + std::list incoming_; + + friend class TCPConnection; +}; + +class TCPConnection : public Connection, public sigslot::has_slots<> { + public: + // Connection is outgoing unless socket is specified + TCPConnection(rtc::WeakPtr 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(port()); + } + + std::unique_ptr 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 +#include +#include + +#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 CreateTCPPort(const SocketAddress& addr) { + auto port = std::unique_ptr( + TCPPort::Create(&main_, &socket_factory_, MakeNetwork(addr), 0, 0, + username_, password_, true, &field_trials_)); + port->SetIceTiebreaker(kTiebreakerDefault); + return port; + } + + std::unique_ptr CreateTCPPort(const rtc::Network* network) { + auto port = std::unique_ptr( + 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 networks_; + std::unique_ptr 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 client(CreateTCPPort(kLocalAddr)); + std::unique_ptr 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(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 + +#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( + 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 +#include +#include +#include + +#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& 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& alternate_server_addresses_; + std::vector::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 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::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 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_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& 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 +#include +#include + +#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 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 StringToConnectionRole( + absl::string_view role_str); +bool ConnectionRoleToString(const ConnectionRole& role, std::string* role_str); + +struct TransportDescription { + TransportDescription(); + TransportDescription(const std::vector& 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 transport_options; + std::string ice_ufrag; + std::string ice_pwd; + IceMode ice_mode; + ConnectionRole connection_role; + + std::unique_ptr 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 + +#include +#include + +#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 TransportDescriptionFactory::CreateOffer( + const TransportOptions& options, + const TransportDescription* current_description, + IceCredentialsIterator* ice_credentials) const { + auto desc = std::make_unique(); + + // 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 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(); + // 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 +#include + +#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& 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 certificate) { + certificate_ = std::move(certificate); + } + + // Creates a transport description suitable for use in an offer. + std::unique_ptr 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 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 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 + +#include +#include +#include + +#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( + new rtc::FakeSSLIdentity("User1")))), + cert2_(rtc::RTCCertificate::Create(std::unique_ptr( + 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(cricket::ICE_UFRAG_LENGTH), + desc->ice_ufrag.size()); + EXPECT_EQ(static_cast(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 offer = + f1_.CreateOffer(options, NULL, &ice_credentials_); + std::unique_ptr 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 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 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(cricket::ICE_UFRAG_LENGTH), + restart_desc->ice_ufrag.size()); + EXPECT_EQ(static_cast(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 offer = + f1_.CreateOffer(options, nullptr, &ice_credentials_); + ASSERT_TRUE(offer); + EXPECT_THAT(offer->transport_options, Not(Contains("renomination"))); + + std::unique_ptr 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 renomination_offer = + f1_.CreateOffer(options, offer.get(), &ice_credentials_); + ASSERT_TRUE(renomination_offer); + EXPECT_THAT(renomination_offer->transport_options, + Contains("renomination")); + + std::unique_ptr 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 cert1_; + rtc::scoped_refptr cert2_; +}; + +TEST_F(TransportDescriptionFactoryTest, TestOfferDefault) { + std::unique_ptr 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 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 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 old_desc = + f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_); + ASSERT_TRUE(old_desc.get() != NULL); + std::unique_ptr 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 offer = + f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_); + ASSERT_TRUE(offer.get() != NULL); + std::unique_ptr 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 offer = + f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_); + ASSERT_TRUE(offer.get() != NULL); + std::unique_ptr old_desc = f2_.CreateAnswer( + offer.get(), TransportOptions(), true, NULL, &ice_credentials_); + ASSERT_TRUE(old_desc.get() != NULL); + std::unique_ptr 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 offer = + f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_); + ASSERT_TRUE(offer.get() != NULL); + std::unique_ptr 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 offer = + f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_); + ASSERT_TRUE(offer.get() != NULL); + std::unique_ptr 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 offer = + f1_.CreateOffer(TransportOptions(), NULL, &ice_credentials_); + ASSERT_TRUE(offer.get() != NULL); + std::unique_ptr 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 offer = + f1_.CreateOffer(options, nullptr, &ice_credentials_); + EXPECT_TRUE(offer->HasOption("trickle")); + std::unique_ptr 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 credentials = { + cricket::IceParameters("kalle", "anka", false)}; + cricket::IceCredentialsIterator credentialsIterator(credentials); + cricket::TransportOptions options; + std::unique_ptr 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 offer = + f1_.CreateOffer(options, nullptr, &ice_credentials_); + + std::vector credentials = { + cricket::IceParameters("kalle", "anka", false)}; + cricket::IceCredentialsIterator credentialsIterator(credentials); + std::unique_ptr 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 offer = + f1_.CreateOffer(options, nullptr, &ice_credentials_); + + std::unique_ptr 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 offer = + f1_.CreateOffer(options, nullptr, &ice_credentials_); + offer->connection_role = cricket::CONNECTIONROLE_ACTIVE; + + std::unique_ptr 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 offer = + f1_.CreateOffer(options, nullptr, &ice_credentials_); + offer->connection_role = cricket::CONNECTIONROLE_PASSIVE; + + std::unique_ptr 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 +#include + +#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 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 +#include +#include +#include + +#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 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 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 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 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& tls_alpn_protocols, + const std::vector& 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& tls_alpn_protocols, + const std::vector& 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 TurnPort::GetTlsAlpnProtocols() const { + return tls_alpn_protocols_; +} + +std::vector 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(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(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( + STUN_ATTR_USERNAME, credentials_.username)); + msg->AddAttribute( + std::make_unique(STUN_ATTR_REALM, realm_)); + msg->AddAttribute( + std::make_unique(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(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 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( + STUN_ATTR_TURN_LOGGING_ID, turn_logging_id_)); + } +} + +TurnAllocateRequest::TurnAllocateRequest(TurnPort* port) + : StunRequest(port->request_manager(), + std::make_unique(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(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(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(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( + 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(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( + STUN_ATTR_CHANNEL_NUMBER, channel_id_ << 16)); + message->AddAttribute(std::make_unique( + 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 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( + STUN_ATTR_XOR_PEER_ADDRESS, ext_addr_)); + msg.AddAttribute( + std::make_unique(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(size)); + buf.WriteBytes(reinterpret_cast(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 + +#include +#include +#include +#include +#include + +#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 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 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 GetTlsAlpnProtocols() const; + virtual std::vector 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& tls_alpn_protocols, + const std::vector& 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& tls_alpn_protocols, + const std::vector& 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 SocketOptionsMap; + typedef std::set 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 tls_alpn_protocols_; + std::vector tls_elliptic_curves_; + rtc::SSLCertificateVerifier* tls_cert_verifier_; + RelayCredentials credentials_; + AttemptedServerSet attempted_server_addresses_; + + rtc::AsyncPacketSocket* socket_; + SocketOptionsMap socket_options_; + std::unique_ptr 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> 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 + +#include "absl/strings/string_view.h" +#endif + +#include +#include +#include +#include + +#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 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 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 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 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 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 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 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(this), + &TurnPortTest::OnTurnReadPacket); + conn2->SignalReadPacket.connect(static_cast(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(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(this), + &TurnPortTest::OnTurnReadPacket); + conn2->SignalReadPacket.connect(static_cast(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 networks_; + std::unique_ptr ss_; + rtc::AutoSocketServerThread main_; + std::unique_ptr socket_; + TestTurnServer turn_server_; + std::unique_ptr turn_port_; + std::unique_ptr 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 turn_packets_; + std::vector udp_packets_; + rtc::PacketOptions options; + std::unique_ptr 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("KESO")); + TestTurnAllocateSucceeds(kSimulatedRtt * 2); +} + +TEST_F(TurnPortTest, TestTurnAllocateWithoutLoggingId) { + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + turn_server_.server()->SetStunMessageObserver( + std::make_unique(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(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 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 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 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 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 +#include +#include // for std::tie +#include + +#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( + 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 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( + 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(&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(&then); + size_t len = rtc::hex_decode(rtc::ArrayView(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( + STUN_ATTR_NONCE, GenerateNonce(timestamp))); + resp.AddAttribute( + std::make_unique(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(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( + 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 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( + STUN_ATTR_XOR_MAPPED_ADDRESS, conn_.src()); + auto relayed_addr_attr = std::make_unique( + STUN_ATTR_XOR_RELAYED_ADDRESS, external_socket_->GetLocalAddress()); + auto lifetime_attr = std::make_unique( + 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( + 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(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( + STUN_ATTR_XOR_PEER_ADDRESS, addr)); + msg.AddAttribute( + std::make_unique(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(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 +#include +#include +#include +#include +#include +#include + +#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; + using ChannelList = std::list; + + 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 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> + 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 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 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 InternalSocketMap; + struct ServerSocketInfo { + ProtocolType proto; + // If non-null, used to wrap accepted sockets. + std::unique_ptr ssl_adapter_factory; + }; + typedef std::map 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 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 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 socket1( + socket_factory_.CreateUdpSocket(rtc::SocketAddress("1.1.1.1", 1), 0, 0)); + std::unique_ptr 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 +#include +#include + +#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 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(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 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 + +#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 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 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 +#include +#include + +#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; + +static const Connection* kConnection = + reinterpret_cast(0xabcd); +static const Connection* kConnectionTwo = + reinterpret_cast(0xbcde); +static const Connection* kConnectionThree = + reinterpret_cast(0xcdef); + +static const std::vector kEmptyConnsList = + std::vector(); + +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 will_move = + std::make_unique(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 agent; + std::unique_ptr will_move = + std::make_unique(IceControllerFactoryArgs{}); + NiceMockIceController* wrapped = will_move.get(); + WrappingActiveIceController controller(&agent, std::move(will_move)); + + IceSwitchReason reason = IceSwitchReason::NOMINATION_ON_CONTROLLED_SIDE; + std::vector 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 agent; + std::unique_ptr will_move = + std::make_unique(IceControllerFactoryArgs{}); + NiceMockIceController* wrapped = will_move.get(); + WrappingActiveIceController controller(&agent, std::move(will_move)); + + IceSwitchReason reason = IceSwitchReason::NEW_CONNECTION_FROM_LOCAL_CANDIDATE; + std::vector conns_to_forget{kConnectionTwo}; + std::vector 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 agent; + std::unique_ptr will_move = + std::make_unique(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 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 agent; + std::unique_ptr will_move = + std::make_unique(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 -- cgit v1.2.3