| /* |
| * Copyright 2025 The WebRTC Project Authors. All rights reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can 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 <cmath> |
| #include <cstdint> |
| #include <limits> |
| #include <list> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/memory/memory.h" |
| #include "absl/strings/string_view.h" |
| #include "api/environment/environment.h" |
| #include "api/environment/environment_factory.h" |
| #include "api/rtc_error.h" |
| #include "api/test/rtc_error_matchers.h" |
| #include "api/transport/stun.h" |
| #include "api/units/time_delta.h" |
| #include "api/units/timestamp.h" |
| #include "p2p/base/basic_packet_socket_factory.h" |
| #include "p2p/base/port.h" |
| #include "p2p/base/port_interface.h" |
| #include "p2p/base/transport_description.h" |
| #include "p2p/client/relay_port_factory_interface.h" |
| #include "p2p/test/test_port.h" |
| #include "rtc_base/buffer.h" |
| #include "rtc_base/network.h" |
| #include "rtc_base/network/received_packet.h" |
| #include "rtc_base/socket_address.h" |
| #include "rtc_base/virtual_socket_server.h" |
| #include "test/create_test_environment.h" |
| #include "test/gmock.h" |
| #include "test/gtest.h" |
| #include "test/time_controller/simulated_time_controller.h" |
| #include "test/wait_until.h" |
| |
| namespace webrtc { |
| namespace { |
| |
| using ::testing::IsTrue; |
| |
| constexpr int kDefaultTimeout = 3000; |
| const SocketAddress kLocalAddr1("192.168.1.2", 0); |
| const SocketAddress kLocalAddr2("192.168.1.3", 0); |
| |
| constexpr int kTiebreaker1 = 11111; |
| constexpr int kTiebreaker2 = 22222; |
| |
| class ConnectionTest : public ::testing::Test { |
| public: |
| ConnectionTest() |
| : ss_(new VirtualSocketServer()), |
| socket_factory_(ss_.get()) { |
| lport_ = CreateTestPort(kLocalAddr1, "lfrag", "lpass"); |
| rport_ = CreateTestPort(kLocalAddr2, "rfrag", "rpass"); |
| lport_->SetIceRole(ICEROLE_CONTROLLING); |
| lport_->SetIceTiebreaker(kTiebreaker1); |
| rport_->SetIceRole(ICEROLE_CONTROLLED); |
| rport_->SetIceTiebreaker(kTiebreaker2); |
| |
| lport_->PrepareAddress(); |
| rport_->PrepareAddress(); |
| } |
| |
| Connection* CreateConnection(IceRole role) { |
| Connection* conn; |
| if (role == ICEROLE_CONTROLLING) { |
| conn = lport_->CreateConnection(rport_->Candidates()[0], |
| Port::ORIGIN_MESSAGE); |
| } else { |
| conn = rport_->CreateConnection(lport_->Candidates()[0], |
| Port::ORIGIN_MESSAGE); |
| } |
| conn->SubscribeStateChange(this, [this](Connection* connection) { |
| OnConnectionStateChange(connection); |
| }); |
| return conn; |
| } |
| |
| void SendPingAndCaptureReply(Connection* lconn, |
| Connection* rconn, |
| int64_t ms, |
| BufferT<uint8_t>* reply) { |
| TestPort* lport = |
| lconn->PortForTest() == lport_.get() ? lport_.get() : rport_.get(); |
| TestPort* rport = |
| rconn->PortForTest() == rport_.get() ? rport_.get() : lport_.get(); |
| lconn->Ping(); |
| ASSERT_TRUE(WaitUntil([&] { return lport->last_stun_msg(); }, |
| {.timeout = TimeDelta::Millis(kDefaultTimeout), |
| .clock = &time_controller_})); |
| ASSERT_GT(lport->last_stun_buf().size(), 0u); |
| rconn->OnReadPacket(ReceivedIpPacket(lport->last_stun_buf(), |
| SocketAddress(), std::nullopt)); |
| |
| time_controller_.AdvanceTime(TimeDelta::Millis(ms)); |
| ASSERT_TRUE(WaitUntil([&] { return rport->last_stun_msg(); }, |
| {.timeout = TimeDelta::Millis(kDefaultTimeout), |
| .clock = &time_controller_})); |
| ASSERT_GT(rport->last_stun_buf().size(), 0u); |
| reply->SetData(rport->last_stun_buf()); |
| } |
| |
| void SendPingAndReceiveResponse(Connection* lconn, |
| Connection* rconn, |
| int64_t ms) { |
| BufferT<uint8_t> reply; |
| SendPingAndCaptureReply(lconn, rconn, ms, &reply); |
| |
| lconn->OnReadPacket(ReceivedIpPacket(reply, SocketAddress(), std::nullopt)); |
| } |
| |
| void OnConnectionStateChange(Connection* connection) { num_state_changes_++; } |
| |
| Network* MakeNetwork(const SocketAddress& addr) { |
| networks_.emplace_back("unittest", "unittest", addr.ipaddr(), 32); |
| networks_.back().AddIP(addr.ipaddr()); |
| return &networks_.back(); |
| } |
| |
| std::unique_ptr<TestPort> CreateTestPort( |
| const SocketAddress& addr, |
| absl::string_view username, |
| absl::string_view password, |
| const FieldTrialsView* field_trials = nullptr) { |
| Port::PortParametersRef args = { |
| .env = CreateEnvironment(field_trials), |
| .network_thread = time_controller_.GetMainThread(), |
| .socket_factory = &socket_factory_, |
| .network = MakeNetwork(addr), |
| .ice_username_fragment = username, |
| .ice_password = password}; |
| auto port = std::make_unique<TestPort>(args, 0, 0); |
| port->SubscribeRoleConflict([this]() { OnRoleConflict(); }); |
| return port; |
| } |
| |
| void OnRoleConflict() { role_conflict_ = true; } |
| const Environment& env() const { return env_; } |
| |
| GlobalSimulatedTimeController time_controller_{Timestamp::Zero()}; |
| const Environment env_ = CreateTestEnvironment(); |
| int num_state_changes_ = 0; |
| std::unique_ptr<VirtualSocketServer> ss_; |
| BasicPacketSocketFactory socket_factory_; |
| std::list<Network> networks_; |
| bool role_conflict_ = false; |
| |
| std::unique_ptr<TestPort> lport_; |
| std::unique_ptr<TestPort> rport_; |
| }; |
| |
| TEST_F(ConnectionTest, ConnectionForgetLearnedState) { |
| Connection* lconn = CreateConnection(ICEROLE_CONTROLLING); |
| Connection* rconn = CreateConnection(ICEROLE_CONTROLLED); |
| |
| EXPECT_FALSE(lconn->writable()); |
| EXPECT_FALSE(lconn->receiving()); |
| EXPECT_TRUE(std::isnan(lconn->GetRttEstimate().GetAverage())); |
| EXPECT_EQ(lconn->GetRttEstimate().GetVariance(), |
| std::numeric_limits<double>::infinity()); |
| |
| SendPingAndReceiveResponse(lconn, rconn, 10); |
| |
| EXPECT_TRUE(lconn->writable()); |
| EXPECT_TRUE(lconn->receiving()); |
| EXPECT_EQ(lconn->GetRttEstimate().GetAverage(), 10); |
| EXPECT_EQ(lconn->GetRttEstimate().GetVariance(), |
| std::numeric_limits<double>::infinity()); |
| |
| SendPingAndReceiveResponse(lconn, rconn, 11); |
| |
| EXPECT_TRUE(lconn->writable()); |
| EXPECT_TRUE(lconn->receiving()); |
| EXPECT_NEAR(lconn->GetRttEstimate().GetAverage(), 10, 0.5); |
| EXPECT_LT(lconn->GetRttEstimate().GetVariance(), |
| std::numeric_limits<double>::infinity()); |
| |
| lconn->ForgetLearnedState(); |
| |
| EXPECT_FALSE(lconn->writable()); |
| EXPECT_FALSE(lconn->receiving()); |
| EXPECT_TRUE(std::isnan(lconn->GetRttEstimate().GetAverage())); |
| EXPECT_EQ(lconn->GetRttEstimate().GetVariance(), |
| std::numeric_limits<double>::infinity()); |
| } |
| |
| TEST_F(ConnectionTest, ConnectionForgetLearnedStateDiscardsPendingPings) { |
| Connection* lconn = CreateConnection(ICEROLE_CONTROLLING); |
| Connection* rconn = CreateConnection(ICEROLE_CONTROLLED); |
| |
| SendPingAndReceiveResponse(lconn, rconn, 10); |
| |
| EXPECT_TRUE(lconn->writable()); |
| EXPECT_TRUE(lconn->receiving()); |
| |
| BufferT<uint8_t> reply; |
| SendPingAndCaptureReply(lconn, rconn, 10, &reply); |
| |
| lconn->ForgetLearnedState(); |
| |
| EXPECT_FALSE(lconn->writable()); |
| EXPECT_FALSE(lconn->receiving()); |
| |
| lconn->OnReadPacket(ReceivedIpPacket(reply, SocketAddress(), std::nullopt)); |
| |
| // 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); |
| } |
| |
| // Test normal happy case. |
| // Sending a delta and getting a delta ack in response. |
| TEST_F(ConnectionTest, SendReceiveGoogDelta) { |
| constexpr int64_t ms = 10; |
| Connection* lconn = CreateConnection(ICEROLE_CONTROLLING); |
| Connection* rconn = CreateConnection(ICEROLE_CONTROLLED); |
| |
| std::unique_ptr<StunByteStringAttribute> delta = |
| absl::WrapUnique(new StunByteStringAttribute(STUN_ATTR_GOOG_DELTA)); |
| delta->CopyBytes("DELTA"); |
| |
| std::unique_ptr<StunAttribute> delta_ack = |
| absl::WrapUnique(new StunUInt64Attribute(STUN_ATTR_GOOG_DELTA_ACK, 133)); |
| |
| bool received_goog_delta = false; |
| bool received_goog_delta_ack = false; |
| lconn->SetStunDictConsumer( |
| // DeltaReceived |
| [](const StunByteStringAttribute* delta) |
| -> std::unique_ptr<StunAttribute> { return nullptr; }, |
| // DeltaAckReceived |
| [&](RTCErrorOr<const StunUInt64Attribute*> error_or_ack) { |
| received_goog_delta_ack = true; |
| EXPECT_TRUE(error_or_ack.ok()); |
| EXPECT_EQ(error_or_ack.value()->value(), 133ull); |
| }); |
| |
| rconn->SetStunDictConsumer( |
| // DeltaReceived |
| [&](const StunByteStringAttribute* delta) |
| -> std::unique_ptr<StunAttribute> { |
| received_goog_delta = true; |
| EXPECT_EQ(delta->string_view(), "DELTA"); |
| return std::move(delta_ack); |
| }, |
| // DeltaAckReceived |
| [](RTCErrorOr<const StunUInt64Attribute*> error_or__ack) {}); |
| |
| lconn->Ping(env().clock().CurrentTime(), std::move(delta)); |
| ASSERT_THAT(WaitUntil([&] { return lport_->last_stun_msg(); }, IsTrue(), |
| {.timeout = TimeDelta::Millis(kDefaultTimeout), |
| .clock = &time_controller_}), |
| IsRtcOk()); |
| ASSERT_GT(lport_->last_stun_buf().size(), 0u); |
| rconn->OnReadPacket( |
| ReceivedIpPacket(lport_->last_stun_buf(), SocketAddress(), std::nullopt)); |
| EXPECT_TRUE(received_goog_delta); |
| |
| time_controller_.SkipForwardBy(TimeDelta::Millis(ms)); |
| ASSERT_TRUE(WaitUntil([&] { return rport_->last_stun_msg(); }, |
| {.timeout = TimeDelta::Millis(kDefaultTimeout), |
| .clock = &time_controller_})); |
| ASSERT_GT(rport_->last_stun_buf().size(), 0u); |
| lconn->OnReadPacket( |
| ReceivedIpPacket(rport_->last_stun_buf(), SocketAddress(), std::nullopt)); |
| EXPECT_TRUE(received_goog_delta_ack); |
| } |
| |
| // Test that sending a goog delta and not getting |
| // a delta ack in reply gives an error callback. |
| TEST_F(ConnectionTest, SendGoogDeltaNoReply) { |
| constexpr int64_t ms = 10; |
| Connection* lconn = CreateConnection(ICEROLE_CONTROLLING); |
| Connection* rconn = CreateConnection(ICEROLE_CONTROLLED); |
| |
| std::unique_ptr<StunByteStringAttribute> delta = |
| absl::WrapUnique(new StunByteStringAttribute(STUN_ATTR_GOOG_DELTA)); |
| delta->CopyBytes("DELTA"); |
| |
| bool received_goog_delta_ack_error = false; |
| lconn->SetStunDictConsumer( |
| // DeltaReceived |
| [](const StunByteStringAttribute* delta) |
| -> std::unique_ptr<StunAttribute> { return nullptr; }, |
| // DeltaAckReceived |
| [&](RTCErrorOr<const StunUInt64Attribute*> error_or_ack) { |
| received_goog_delta_ack_error = true; |
| EXPECT_FALSE(error_or_ack.ok()); |
| }); |
| |
| lconn->Ping(env().clock().CurrentTime(), std::move(delta)); |
| ASSERT_THAT(WaitUntil([&] { return lport_->last_stun_msg(); }, IsTrue(), |
| {.timeout = TimeDelta::Millis(kDefaultTimeout), |
| .clock = &time_controller_}), |
| IsRtcOk()); |
| ASSERT_GT(lport_->last_stun_buf().size(), 0u); |
| rconn->OnReadPacket( |
| ReceivedIpPacket(lport_->last_stun_buf(), SocketAddress(), std::nullopt)); |
| |
| time_controller_.SkipForwardBy(TimeDelta::Millis(ms)); |
| ASSERT_THAT(WaitUntil([&] { return rport_->last_stun_msg(); }, IsTrue(), |
| {.timeout = TimeDelta::Millis(kDefaultTimeout), |
| .clock = &time_controller_}), |
| IsRtcOk()); |
| ASSERT_GT(rport_->last_stun_buf().size(), 0u); |
| lconn->OnReadPacket( |
| ReceivedIpPacket(rport_->last_stun_buf(), SocketAddress(), std::nullopt)); |
| EXPECT_TRUE(received_goog_delta_ack_error); |
| } |
| |
| // Test that if StunBinding is sufficiently full, aka DELTA is too big, it is |
| // not sent. |
| TEST_F(ConnectionTest, TooBigDeltaIsNotSent) { |
| constexpr int64_t ms = 10; |
| Connection* lconn = CreateConnection(ICEROLE_CONTROLLING); |
| Connection* rconn = CreateConnection(ICEROLE_CONTROLLED); |
| |
| std::string a_long_string(1200, 'a'); |
| std::unique_ptr<StunByteStringAttribute> delta = |
| absl::WrapUnique(new StunByteStringAttribute(STUN_ATTR_GOOG_DELTA)); |
| delta->CopyBytes(a_long_string); |
| |
| std::unique_ptr<StunAttribute> delta_ack = |
| absl::WrapUnique(new StunUInt64Attribute(STUN_ATTR_GOOG_DELTA_ACK, 133)); |
| |
| bool received_goog_delta = false; |
| bool received_goog_delta_ack = false; |
| lconn->SetStunDictConsumer( |
| // DeltaReceived |
| [](const StunByteStringAttribute* delta) |
| -> std::unique_ptr<StunAttribute> { return nullptr; }, |
| // DeltaAckReceived |
| [&](RTCErrorOr<const StunUInt64Attribute*> error_or_ack) { |
| received_goog_delta_ack = true; |
| EXPECT_TRUE(error_or_ack.ok()); |
| EXPECT_EQ(error_or_ack.value()->value(), 133ull); |
| }); |
| |
| rconn->SetStunDictConsumer( |
| // DeltaReceived |
| [&](const StunByteStringAttribute* delta) |
| -> std::unique_ptr<StunAttribute> { |
| received_goog_delta = true; |
| EXPECT_EQ(delta->string_view(), "DELTA"); |
| return std::move(delta_ack); |
| }, |
| // DeltaAckReceived |
| [](RTCErrorOr<const StunUInt64Attribute*> error_or__ack) {}); |
| |
| lconn->Ping(env().clock().CurrentTime(), std::move(delta)); |
| ASSERT_THAT(WaitUntil([&] { return lport_->last_stun_msg(); }, IsTrue(), |
| {.timeout = TimeDelta::Millis(kDefaultTimeout), |
| .clock = &time_controller_}), |
| IsRtcOk()); |
| ASSERT_GT(lport_->last_stun_buf().size(), 0u); |
| rconn->OnReadPacket( |
| ReceivedIpPacket(lport_->last_stun_buf(), SocketAddress(), std::nullopt)); |
| EXPECT_FALSE(received_goog_delta); |
| |
| time_controller_.SkipForwardBy(TimeDelta::Millis(ms)); |
| ASSERT_TRUE(WaitUntil([&] { return rport_->last_stun_msg(); }, |
| {.timeout = TimeDelta::Millis(kDefaultTimeout), |
| .clock = &time_controller_})); |
| ASSERT_GT(rport_->last_stun_buf().size(), 0u); |
| lconn->OnReadPacket( |
| ReceivedIpPacket(rport_->last_stun_buf(), SocketAddress(), std::nullopt)); |
| |
| EXPECT_FALSE(received_goog_delta_ack); |
| } |
| |
| } // namespace |
| } // namespace webrtc |