/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include "mozilla/Atomics.h" #include "runnable_utils.h" #include "pk11pub.h" extern "C" { #include "nr_api.h" #include "nr_socket.h" #include "transport_addr.h" #include "nr_socket_multi_tcp.h" } #include "stunserver.h" #include "nricectx.h" #define GTEST_HAS_RTTI 0 #include "gtest/gtest.h" #include "gtest_utils.h" using namespace mozilla; namespace { class MultiTcpSocketTest : public MtransportTest { public: MultiTcpSocketTest() : MtransportTest(), socks(3, nullptr), readable(false), ice_ctx_() {} void SetUp() { MtransportTest::SetUp(); NrIceCtx::InitializeGlobals(NrIceCtx::GlobalConfig()); ice_ctx_ = NrIceCtx::Create("stun"); test_utils_->SyncDispatchToSTS( WrapRunnableNM(&TestStunTcpServer::GetInstance, AF_INET)); test_utils_->SyncDispatchToSTS( WrapRunnableNM(&TestStunTcpServer::GetInstance, AF_INET6)); } void TearDown() { test_utils_->SyncDispatchToSTS( WrapRunnable(this, &MultiTcpSocketTest::Shutdown_s)); MtransportTest::TearDown(); } DISALLOW_COPY_ASSIGN(MultiTcpSocketTest); static void SockReadable(NR_SOCKET s, int how, void* arg) { MultiTcpSocketTest* obj = static_cast(arg); obj->SetReadable(true); } void Shutdown_s() { ice_ctx_ = nullptr; for (auto& sock : socks) { nr_socket_destroy(&sock); } } static uint16_t GetRandomPort() { uint16_t result; if (PK11_GenerateRandom((unsigned char*)&result, 2) != SECSuccess) { MOZ_ASSERT(false); return 0; } return result; } static uint16_t EnsureEphemeral(uint16_t port) { // IANA ephemeral port range (49152 to 65535) return port | 49152; } void Create_s(nr_socket_tcp_type tcp_type, std::string stun_server_addr, uint16_t stun_server_port, nr_socket** sock) { nr_transport_addr local; // Get start of port range for test static unsigned short port_s = GetRandomPort(); int r; if (!stun_server_addr.empty()) { std::vector stun_servers; UniquePtr server(NrIceStunServer::Create( stun_server_addr, stun_server_port, kNrIceTransportTcp)); stun_servers.push_back(*server); ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->SetStunServers(stun_servers))); } r = 1; for (int tries = 10; tries && r; --tries) { r = nr_str_port_to_transport_addr( (char*)"127.0.0.1", EnsureEphemeral(port_s++), IPPROTO_TCP, &local); ASSERT_EQ(0, r); r = nr_socket_multi_tcp_create(ice_ctx_->ctx(), nullptr, &local, tcp_type, 1, 2048, sock); } ASSERT_EQ(0, r); printf("Creating socket on %s\n", local.as_string); r = nr_socket_multi_tcp_set_readable_cb( *sock, &MultiTcpSocketTest::SockReadable, this); ASSERT_EQ(0, r); } nr_socket* Create(nr_socket_tcp_type tcp_type, std::string stun_server_addr = "", uint16_t stun_server_port = 0) { nr_socket* sock = nullptr; test_utils_->SyncDispatchToSTS( WrapRunnable(this, &MultiTcpSocketTest::Create_s, tcp_type, stun_server_addr, stun_server_port, &sock)); return sock; } void Listen_s(nr_socket* sock) { nr_transport_addr addr; int r = nr_socket_getaddr(sock, &addr); ASSERT_EQ(0, r); printf("Listening on %s\n", addr.as_string); r = nr_socket_listen(sock, 5); ASSERT_EQ(0, r); } void Listen(nr_socket* sock) { test_utils_->SyncDispatchToSTS( WrapRunnable(this, &MultiTcpSocketTest::Listen_s, sock)); } void Destroy_s(nr_socket* sock) { int r = nr_socket_destroy(&sock); ASSERT_EQ(0, r); } void Destroy(nr_socket* sock) { test_utils_->SyncDispatchToSTS( WrapRunnable(this, &MultiTcpSocketTest::Destroy_s, sock)); } void Connect_s(nr_socket* from, nr_socket* to) { nr_transport_addr addr_to; nr_transport_addr addr_from; int r = nr_socket_getaddr(to, &addr_to); ASSERT_EQ(0, r); r = nr_socket_getaddr(from, &addr_from); ASSERT_EQ(0, r); printf("Connecting from %s to %s\n", addr_from.as_string, addr_to.as_string); r = nr_socket_connect(from, &addr_to); ASSERT_EQ(0, r); } void Connect(nr_socket* from, nr_socket* to) { test_utils_->SyncDispatchToSTS( WrapRunnable(this, &MultiTcpSocketTest::Connect_s, from, to)); } void ConnectSo_s(nr_socket* so1, nr_socket* so2) { nr_transport_addr addr_so1; nr_transport_addr addr_so2; int r = nr_socket_getaddr(so1, &addr_so1); ASSERT_EQ(0, r); r = nr_socket_getaddr(so2, &addr_so2); ASSERT_EQ(0, r); printf("Connecting SO %s <-> %s\n", addr_so1.as_string, addr_so2.as_string); r = nr_socket_connect(so1, &addr_so2); ASSERT_EQ(0, r); r = nr_socket_connect(so2, &addr_so1); ASSERT_EQ(0, r); } void ConnectSo(nr_socket* from, nr_socket* to) { test_utils_->SyncDispatchToSTS( WrapRunnable(this, &MultiTcpSocketTest::ConnectSo_s, from, to)); } void SendDataToAddress_s(nr_socket* from, nr_transport_addr* to, const char* data, size_t len) { nr_transport_addr addr_from; int r = nr_socket_getaddr(from, &addr_from); ASSERT_EQ(0, r); printf("Sending %lu bytes %s -> %s\n", (unsigned long)len, addr_from.as_string, to->as_string); r = nr_socket_sendto(from, data, len, 0, to); ASSERT_EQ(0, r); } void SendData(nr_socket* from, nr_transport_addr* to, const char* data, size_t len) { test_utils_->SyncDispatchToSTS(WrapRunnable( this, &MultiTcpSocketTest::SendDataToAddress_s, from, to, data, len)); } void SendDataToSocket_s(nr_socket* from, nr_socket* to, const char* data, size_t len) { nr_transport_addr addr_to; int r = nr_socket_getaddr(to, &addr_to); ASSERT_EQ(0, r); SendDataToAddress_s(from, &addr_to, data, len); } void SendData(nr_socket* from, nr_socket* to, const char* data, size_t len) { test_utils_->SyncDispatchToSTS(WrapRunnable( this, &MultiTcpSocketTest::SendDataToSocket_s, from, to, data, len)); } void RecvDataFromAddress_s(nr_transport_addr* expected_from, nr_socket* sent_to, const char* expected_data, size_t expected_len) { SetReadable(false); size_t buflen = expected_len ? expected_len + 1 : 100; char received_data[buflen]; nr_transport_addr addr_to; nr_transport_addr retaddr; size_t retlen; int r = nr_socket_getaddr(sent_to, &addr_to); ASSERT_EQ(0, r); printf("Receiving %lu bytes %s <- %s\n", (unsigned long)expected_len, addr_to.as_string, expected_from->as_string); r = nr_socket_recvfrom(sent_to, received_data, buflen, &retlen, 0, &retaddr); ASSERT_EQ(0, r); r = nr_transport_addr_cmp(&retaddr, expected_from, NR_TRANSPORT_ADDR_CMP_MODE_ALL); ASSERT_EQ(0, r); // expected_len == 0 means we just expected some data if (expected_len == 0) { ASSERT_GT(retlen, 0U); } else { ASSERT_EQ(expected_len, retlen); r = memcmp(expected_data, received_data, retlen); ASSERT_EQ(0, r); } } void RecvData(nr_transport_addr* expected_from, nr_socket* sent_to, const char* expected_data = nullptr, size_t expected_len = 0) { ASSERT_TRUE_WAIT(IsReadable(), 1000); test_utils_->SyncDispatchToSTS( WrapRunnable(this, &MultiTcpSocketTest::RecvDataFromAddress_s, expected_from, sent_to, expected_data, expected_len)); } void RecvDataFromSocket_s(nr_socket* expected_from, nr_socket* sent_to, const char* expected_data, size_t expected_len) { nr_transport_addr addr_from; int r = nr_socket_getaddr(expected_from, &addr_from); ASSERT_EQ(0, r); RecvDataFromAddress_s(&addr_from, sent_to, expected_data, expected_len); } void RecvData(nr_socket* expected_from, nr_socket* sent_to, const char* expected_data, size_t expected_len) { ASSERT_TRUE_WAIT(IsReadable(), 1000); test_utils_->SyncDispatchToSTS( WrapRunnable(this, &MultiTcpSocketTest::RecvDataFromSocket_s, expected_from, sent_to, expected_data, expected_len)); } void RecvDataFailed_s(nr_socket* sent_to, size_t expected_len, int expected_err) { SetReadable(false); char received_data[expected_len + 1]; nr_transport_addr addr_to; nr_transport_addr retaddr; size_t retlen; int r = nr_socket_getaddr(sent_to, &addr_to); ASSERT_EQ(0, r); r = nr_socket_recvfrom(sent_to, received_data, expected_len + 1, &retlen, 0, &retaddr); ASSERT_EQ(expected_err, r) << "Expecting receive failure " << expected_err << " on " << addr_to.as_string; } void RecvDataFailed(nr_socket* sent_to, size_t expected_len, int expected_err) { ASSERT_TRUE_WAIT(IsReadable(), 1000); test_utils_->SyncDispatchToSTS( WrapRunnable(this, &MultiTcpSocketTest::RecvDataFailed_s, sent_to, expected_len, expected_err)); } void TransferData(nr_socket* from, nr_socket* to, const char* data, size_t len) { SendData(from, to, data, len); RecvData(from, to, data, len); } protected: bool IsReadable() const { return readable; } void SetReadable(bool r) { readable = r; } std::vector socks; Atomic readable; RefPtr ice_ctx_; }; } // namespace TEST_F(MultiTcpSocketTest, TestListen) { socks[0] = Create(TCP_TYPE_PASSIVE); Listen(socks[0]); } TEST_F(MultiTcpSocketTest, TestConnect) { socks[0] = Create(TCP_TYPE_PASSIVE); socks[1] = Create(TCP_TYPE_ACTIVE); socks[2] = Create(TCP_TYPE_ACTIVE); Listen(socks[0]); Connect(socks[1], socks[0]); Connect(socks[2], socks[0]); } TEST_F(MultiTcpSocketTest, TestTransmit) { const char data[] = "TestTransmit"; socks[0] = Create(TCP_TYPE_ACTIVE); socks[1] = Create(TCP_TYPE_PASSIVE); Listen(socks[1]); Connect(socks[0], socks[1]); TransferData(socks[0], socks[1], data, sizeof(data)); TransferData(socks[1], socks[0], data, sizeof(data)); } TEST_F(MultiTcpSocketTest, TestClosePassive) { const char data[] = "TestClosePassive"; socks[0] = Create(TCP_TYPE_ACTIVE); socks[1] = Create(TCP_TYPE_PASSIVE); Listen(socks[1]); Connect(socks[0], socks[1]); TransferData(socks[0], socks[1], data, sizeof(data)); TransferData(socks[1], socks[0], data, sizeof(data)); /* We have to destroy as only that calls PR_Close() */ std::cerr << "Destructing socket" << std::endl; Destroy(socks[1]); RecvDataFailed(socks[0], sizeof(data), R_EOD); socks[1] = nullptr; } TEST_F(MultiTcpSocketTest, TestCloseActive) { const char data[] = "TestCloseActive"; socks[0] = Create(TCP_TYPE_ACTIVE); socks[1] = Create(TCP_TYPE_PASSIVE); Listen(socks[1]); Connect(socks[0], socks[1]); TransferData(socks[0], socks[1], data, sizeof(data)); TransferData(socks[1], socks[0], data, sizeof(data)); /* We have to destroy as only that calls PR_Close() */ std::cerr << "Destructing socket" << std::endl; Destroy(socks[0]); RecvDataFailed(socks[1], sizeof(data), R_EOD); socks[0] = nullptr; } TEST_F(MultiTcpSocketTest, TestTwoSendsBeforeReceives) { const char data1[] = "TestTwoSendsBeforeReceives"; const char data2[] = "2nd data"; socks[0] = Create(TCP_TYPE_ACTIVE); socks[1] = Create(TCP_TYPE_PASSIVE); Listen(socks[1]); Connect(socks[0], socks[1]); SendData(socks[0], socks[1], data1, sizeof(data1)); SendData(socks[0], socks[1], data2, sizeof(data2)); RecvData(socks[0], socks[1], data1, sizeof(data1)); /* ICE TCP framing turns TCP effectively into datagram mode */ RecvData(socks[0], socks[1], data2, sizeof(data2)); } TEST_F(MultiTcpSocketTest, TestTwoActiveBidirectionalTransmit) { const char data1[] = "TestTwoActiveBidirectionalTransmit"; const char data2[] = "ReplyToTheFirstSocket"; const char data3[] = "TestMessageFromTheSecondSocket"; const char data4[] = "ThisIsAReplyToTheSecondSocket"; socks[0] = Create(TCP_TYPE_PASSIVE); socks[1] = Create(TCP_TYPE_ACTIVE); socks[2] = Create(TCP_TYPE_ACTIVE); Listen(socks[0]); Connect(socks[1], socks[0]); Connect(socks[2], socks[0]); TransferData(socks[1], socks[0], data1, sizeof(data1)); TransferData(socks[0], socks[1], data2, sizeof(data2)); TransferData(socks[2], socks[0], data3, sizeof(data3)); TransferData(socks[0], socks[2], data4, sizeof(data4)); } TEST_F(MultiTcpSocketTest, TestTwoPassiveBidirectionalTransmit) { const char data1[] = "TestTwoPassiveBidirectionalTransmit"; const char data2[] = "FirstReply"; const char data3[] = "TestTwoPassiveBidirectionalTransmitToTheSecondSock"; const char data4[] = "SecondReply"; socks[0] = Create(TCP_TYPE_PASSIVE); socks[1] = Create(TCP_TYPE_PASSIVE); socks[2] = Create(TCP_TYPE_ACTIVE); Listen(socks[0]); Listen(socks[1]); Connect(socks[2], socks[0]); Connect(socks[2], socks[1]); TransferData(socks[2], socks[0], data1, sizeof(data1)); TransferData(socks[0], socks[2], data2, sizeof(data2)); TransferData(socks[2], socks[1], data3, sizeof(data3)); TransferData(socks[1], socks[2], data4, sizeof(data4)); } TEST_F(MultiTcpSocketTest, TestActivePassiveWithStunServerMockup) { /* Fake STUN message able to pass the nr_is_stun_msg check used in nr_socket_buffered_stun */ const char stunMessage[] = {'\x00', '\x01', '\x00', '\x04', '\x21', '\x12', '\xa4', '\x42', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x0c', '\x00', '\x00', '\x00', '\x00', '\x00', '\x1c', '\xed', '\xca', '\xfe'}; const char data[] = "TestActivePassiveWithStunServerMockup"; nr_transport_addr stun_srv_addr; std::string stun_addr; uint16_t stun_port; stun_addr = TestStunTcpServer::GetInstance(AF_INET)->addr(); stun_port = TestStunTcpServer::GetInstance(AF_INET)->port(); int r = nr_str_port_to_transport_addr(stun_addr.c_str(), stun_port, IPPROTO_TCP, &stun_srv_addr); ASSERT_EQ(0, r); socks[0] = Create(TCP_TYPE_PASSIVE, stun_addr, stun_port); Listen(socks[0]); socks[1] = Create(TCP_TYPE_ACTIVE, stun_addr, stun_port); /* Send a fake STUN request and expect a STUN error response */ SendData(socks[0], &stun_srv_addr, stunMessage, sizeof(stunMessage)); RecvData(&stun_srv_addr, socks[0]); Connect(socks[1], socks[0]); TransferData(socks[1], socks[0], data, sizeof(data)); TransferData(socks[0], socks[1], data, sizeof(data)); } TEST_F(MultiTcpSocketTest, TestConnectTwoSo) { socks[0] = Create(TCP_TYPE_SO); socks[1] = Create(TCP_TYPE_SO); ConnectSo(socks[0], socks[1]); } // test works on localhost only with delay applied: // tc qdisc add dev lo root netem delay 5ms TEST_F(MultiTcpSocketTest, DISABLED_TestTwoSoBidirectionalTransmit) { const char data[] = "TestTwoSoBidirectionalTransmit"; socks[0] = Create(TCP_TYPE_SO); socks[1] = Create(TCP_TYPE_SO); ConnectSo(socks[0], socks[1]); TransferData(socks[0], socks[1], data, sizeof(data)); TransferData(socks[1], socks[0], data, sizeof(data)); } TEST_F(MultiTcpSocketTest, TestBigData) { char buf1[2048]; char buf2[1024]; for (unsigned i = 0; i < sizeof(buf1); ++i) { buf1[i] = i & 0xff; } for (unsigned i = 0; i < sizeof(buf2); ++i) { buf2[i] = (i + 0x80) & 0xff; } socks[0] = Create(TCP_TYPE_ACTIVE); socks[1] = Create(TCP_TYPE_PASSIVE); Listen(socks[1]); Connect(socks[0], socks[1]); TransferData(socks[0], socks[1], buf1, sizeof(buf1)); TransferData(socks[0], socks[1], buf2, sizeof(buf2)); // opposite dir SendData(socks[1], socks[0], buf2, sizeof(buf2)); SendData(socks[1], socks[0], buf1, sizeof(buf1)); RecvData(socks[1], socks[0], buf2, sizeof(buf2)); RecvData(socks[1], socks[0], buf1, sizeof(buf1)); }