Add IPv6 unit tests for STUN port.
Also refactored a bit to support IPv6 networks and socket factories with
a mocked DNS resolver.
Bug: webrtc:14319, webrtc:14131
Change-Id: I32ac5beb9a72201bf83aac26aed6a670ed2d4955
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/270741
Reviewed-by: Jonas Oreland <jonaso@webrtc.org>
Commit-Queue: Sameer Vijaykar <samvi@google.com>
Cr-Commit-Position: refs/heads/main@{#37737}
diff --git a/p2p/base/stun_port_unittest.cc b/p2p/base/stun_port_unittest.cc
index d9c6500..a5e13f9 100644
--- a/p2p/base/stun_port_unittest.cc
+++ b/p2p/base/stun_port_unittest.cc
@@ -12,6 +12,7 @@
#include <memory>
+#include "api/test/mock_async_dns_resolver.h"
#include "p2p/base/basic_packet_socket_factory.h"
#include "p2p/base/test_stun_server.h"
#include "rtc_base/gunit.h"
@@ -22,33 +23,83 @@
#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;
+
+using DnsResolverExpectations =
+ std::function<void(webrtc::MockAsyncDnsResolver*,
+ webrtc::MockAsyncDnsResolverResult*)>;
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 kStunHostnameAddr("localhost", 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 << 24 | 30 (IPV4) << 8 | 256 - 0
-static const uint32_t kStunCandidatePriority = 1677729535;
+// 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;
-// Tests connecting a StunPort to a fake STUN server (cricket::StunServer)
+// A PacketSocketFactory implementation that uses a mock DnsResolver and allows
+// setting expectations on the resolver and results.
+class MockDnsResolverPacketSocketFactory
+ : public rtc::BasicPacketSocketFactory {
+ public:
+ explicit MockDnsResolverPacketSocketFactory(
+ rtc::SocketFactory* socket_factory)
+ : rtc::BasicPacketSocketFactory(socket_factory) {}
+
+ std::unique_ptr<webrtc::AsyncDnsResolverInterface> CreateAsyncDnsResolver()
+ override {
+ std::unique_ptr<webrtc::MockAsyncDnsResolver> resolver =
+ std::make_unique<webrtc::MockAsyncDnsResolver>();
+ if (expectations_) {
+ expectations_(resolver.get(), &resolver_result_);
+ }
+ return resolver;
+ }
+
+ void SetExpectations(DnsResolverExpectations expectations) {
+ expectations_ = expectations;
+ }
+
+ private:
+ webrtc::MockAsyncDnsResolverResult resolver_result_;
+ DnsResolverExpectations expectations_;
+};
+
+// 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_("unittest", "unittest", kLocalAddr.ipaddr(), 32),
+ network_(network),
socket_factory_(ss_.get()),
stun_server_1_(cricket::TestStunServer::Create(ss_.get(), kStunAddr1)),
stun_server_2_(cricket::TestStunServer::Create(ss_.get(), kStunAddr2)),
@@ -56,9 +107,14 @@
error_(false),
stun_keepalive_delay_(1),
stun_keepalive_lifetime_(-1) {
- network_.AddIP(kLocalAddr.ipaddr());
+ 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_; }
@@ -80,7 +136,7 @@
void CreateStunPort(const ServerAddresses& stun_servers) {
stun_port_ = cricket::StunPort::Create(
- rtc::Thread::Current(), &socket_factory_, &network_, 0, 0,
+ rtc::Thread::Current(), socket_factory(), &network_, 0, 0,
rtc::CreateRandomString(16), rtc::CreateRandomString(22), stun_servers,
absl::nullopt, &field_trials_);
stun_port_->set_stun_keepalive_delay(stun_keepalive_delay_);
@@ -101,13 +157,13 @@
if (socket) {
socket_.reset(socket);
} else {
- socket_.reset(socket_factory_.CreateUdpSocket(
+ 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::Thread::Current(), socket_factory(), &network_, socket_.get(),
rtc::CreateRandomString(16), rtc::CreateRandomString(22), false,
absl::nullopt, &field_trials_);
ASSERT_TRUE(stun_port_ != NULL);
@@ -215,8 +271,6 @@
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);
-
- // TODO(deadbeef): Add IPv6 tests here.
}
// Test that we fail properly if we can't get an address.
@@ -236,15 +290,43 @@
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(DnsResolverExpectations expectations) {
+ socket_factory_.SetExpectations(expectations);
+ }
+
+ private:
+ MockDnsResolverPacketSocketFactory socket_factory_;
+};
+
// Test that we can get an address from a STUN server specified by a hostname.
// Crashes on Linux, see webrtc:7416
-#if defined(WEBRTC_LINUX) || defined(WEBRTC_WIN)
+#if defined(WEBRTC_LINUX) || defined(WEBRTC_WIN) || defined(WEBRTC_MAC)
#define MAYBE_TestPrepareAddressHostname DISABLED_TestPrepareAddressHostname
#else
#define MAYBE_TestPrepareAddressHostname TestPrepareAddressHostname
#endif
-TEST_F(StunPortTest, MAYBE_TestPrepareAddressHostname) {
- CreateStunPort(kStunHostnameAddr);
+TEST_F(StunPortWithMockDnsResolverTest, MAYBE_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());
@@ -464,3 +546,105 @@
.WillRepeatedly(Return(100));
EXPECT_TRUE_SIMULATED_WAIT(done(), kTimeoutMs, fake_clock);
}
+
+class StunIPv6PortTestBase : public StunPortTestBase {
+ public:
+ StunIPv6PortTestBase()
+ : StunPortTestBase(rtc::Network("unittestipv6",
+ "unittestipv6",
+ kIPv6LocalAddr.ipaddr(),
+ 128),
+ kIPv6LocalAddr.ipaddr()) {
+ stun_server_ipv6_1_.reset(
+ cricket::TestStunServer::Create(ss(), kIPv6StunAddr1));
+ }
+
+ protected:
+ std::unique_ptr<cricket::TestStunServer> stun_server_ipv6_1_;
+};
+
+class StunIPv6PortTestWithRealClock : public StunIPv6PortTestBase {};
+
+class StunIPv6PortTest : public FakeClockBase, public StunIPv6PortTestBase {};
+
+// Test that we can get an address from a STUN server.
+TEST_F(StunIPv6PortTest, TestPrepareAddress) {
+ CreateStunPort(kIPv6StunAddr1);
+ PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(done(), kTimeoutMs, fake_clock);
+ ASSERT_EQ(1U, port()->Candidates().size());
+ EXPECT_TRUE(kIPv6LocalAddr.EqualIPs(port()->Candidates()[0].address()));
+ std::string expected_server_url = "stun:::1:5000";
+ EXPECT_EQ(port()->Candidates()[0].url(), expected_server_url);
+}
+
+// Test that we fail properly if we can't get an address.
+TEST_F(StunIPv6PortTest, TestPrepareAddressFail) {
+ CreateStunPort(kBadAddr);
+ PrepareAddress();
+ EXPECT_TRUE_SIMULATED_WAIT(done(), kTimeoutMs, fake_clock);
+ EXPECT_TRUE(error());
+ EXPECT_EQ(0U, port()->Candidates().size());
+ EXPECT_EQ_SIMULATED_WAIT(error_event_.error_code,
+ cricket::SERVER_NOT_REACHABLE_ERROR, kTimeoutMs,
+ fake_clock);
+ ASSERT_NE(error_event_.error_text.find('.'), std::string::npos);
+ ASSERT_NE(
+ error_event_.address.find(kIPv6LocalAddr.HostAsSensitiveURIString()),
+ std::string::npos);
+ std::string server_url = "stun:" + kBadAddr.ToString();
+ ASSERT_EQ(error_event_.url, server_url);
+}
+
+// Test that we handle hostname lookup failures properly with a real clock.
+TEST_F(StunIPv6PortTestWithRealClock, TestPrepareAddressHostnameFail) {
+ CreateStunPort(kBadHostnameAddr);
+ PrepareAddress();
+ EXPECT_TRUE_WAIT(done(), kTimeoutMs);
+ EXPECT_TRUE(error());
+ EXPECT_EQ(0U, port()->Candidates().size());
+ EXPECT_EQ_WAIT(error_event_.error_code, cricket::SERVER_NOT_REACHABLE_ERROR,
+ kTimeoutMs);
+}
+
+class StunIPv6PortTestWithMockDnsResolver : public StunIPv6PortTest {
+ public:
+ StunIPv6PortTestWithMockDnsResolver()
+ : StunIPv6PortTest(), socket_factory_(ss()) {}
+
+ rtc::PacketSocketFactory* socket_factory() override {
+ return &socket_factory_;
+ }
+
+ void SetDnsResolverExpectations(DnsResolverExpectations expectations) {
+ socket_factory_.SetExpectations(expectations);
+ }
+
+ private:
+ MockDnsResolverPacketSocketFactory socket_factory_;
+};
+
+// Test that we can get an address from a STUN server specified by a hostname.
+// Crashes on Linux, see webrtc:7416
+TEST_F(StunIPv6PortTestWithMockDnsResolver, MAYBE_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_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());
+}
+
+} // namespace