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