9ed2fa80d fix(toxav): remove extra copy of video frame on encode de30cf3ad docs: Add new file kinds, that should be useful to all clients. d5b5e879d fix(DHT): Correct node skipping logic timed out nodes. 30e71fe97 refactor: Generate event dispatch functions and add tox_events_dispatch. 8fdbb0b50 style: Format parameter lists in event handlers. d00dee12b refactor: Add warning logs when losing chat invites. b144e8db1 feat: Add a way to look up a file number by ID. 849281ea0 feat: Add a way to fetch groups by chat ID. a2c177396 refactor: Harden event system and improve type safety. 8f5caa656 refactor: Add MessagePack string support to bin_pack. 34e8d5ad5 chore: Add GitHub CodeQL workflow and local Docker runner. f7b068010 refactor: Add nullability annotations to event headers. 788abe651 refactor(toxav): Use system allocator for mutexes. 2e4b423eb refactor: Use specific typedefs for public API arrays. 2baf34775 docs(toxav): update idle iteration interval see 679444751876fa3882a717772918ebdc8f083354 2f87ac67b feat: Add Event Loop abstraction (Ev). f8dfc38d8 test: Fix data race in ToxScenario virtual_clock. 38313921e test(TCP): Add regression test for TCP priority queue integrity. f94a50d9a refactor(toxav): Replace mutable_mutex with dynamically allocated mutex. ad054511e refactor: Internalize DHT structs and add debug helpers. 8b467cc96 fix: Prevent potential integer overflow in group chat handshake. 4962bdbb8 test: Improve TCP simulation and add tests 5f0227093 refactor: Allow nullable data in group chat handlers. e97b18ea9 chore: Improve Windows Docker support. b14943bbd refactor: Move Logger out of Messenger into Tox. dd3136250 cleanup: Apply nullability qualifiers to C++ codebase. 1849f70fc refactor: Extract low-level networking code to net and os_network. 8fec75421 refactor: Delete tox_random, align on rng and os_random. a03ae8051 refactor: Delete tox_memory, align on mem and os_memory. 4c88fed2c refactor: Use `std::` prefixes more consistently in C++ code. 72452f2ae test: Add some more tests for onion and shared key cache. d5a51b09a cleanup: Use tox_attributes.h in tox_private.h and install it. b6f5b9fc5 test: Add some benchmarks for various high level things. 8a8d02785 test(support): Introduce threaded Tox runner and simulation barrier d68d1d095 perf(toxav): optimize audio and video intermediate buffers by keeping them around REVERT: c9cdae001 fix(toxav): remove extra copy of video frame on encode git-subtree-dir: external/toxcore/c-toxcore git-subtree-split: 9ed2fa80d582c714d6bdde6a7648220a92cddff8
460 lines
15 KiB
C++
460 lines
15 KiB
C++
#include <gtest/gtest.h>
|
|
|
|
#include "fake_sockets.hh"
|
|
#include "network_universe.hh"
|
|
|
|
namespace tox::test {
|
|
namespace {
|
|
|
|
class FakeNetworkTcpTest : public ::testing::Test {
|
|
protected:
|
|
NetworkUniverse universe;
|
|
|
|
IP make_ip(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
|
|
{
|
|
IP ip;
|
|
ip_init(&ip, false);
|
|
ip.ip.v4.uint8[0] = a;
|
|
ip.ip.v4.uint8[1] = b;
|
|
ip.ip.v4.uint8[2] = c;
|
|
ip.ip.v4.uint8[3] = d;
|
|
return ip;
|
|
}
|
|
};
|
|
|
|
TEST_F(FakeNetworkTcpTest, MultipleConnectionsToSamePort)
|
|
{
|
|
universe.set_verbose(true);
|
|
IP server_ip = make_ip(10, 0, 0, 1);
|
|
uint16_t server_port = 12345;
|
|
|
|
FakeTcpSocket server(universe);
|
|
server.set_ip(server_ip);
|
|
IP_Port server_addr{server_ip, net_htons(server_port)};
|
|
ASSERT_EQ(server.bind(&server_addr), 0);
|
|
ASSERT_EQ(server.listen(5), 0);
|
|
|
|
// Client 1
|
|
IP client1_ip = make_ip(10, 0, 0, 2);
|
|
FakeTcpSocket client1(universe);
|
|
client1.set_ip(client1_ip);
|
|
client1.connect(&server_addr);
|
|
|
|
// Client 2 (same IP as client 1, different port)
|
|
FakeTcpSocket client2(universe);
|
|
client2.set_ip(client1_ip);
|
|
client2.connect(&server_addr);
|
|
|
|
// Handshake for both
|
|
// 1. SYNs
|
|
universe.process_events(0);
|
|
universe.process_events(0);
|
|
|
|
// 2. SYN-ACKs
|
|
universe.process_events(0);
|
|
universe.process_events(0);
|
|
|
|
// 3. ACKs
|
|
universe.process_events(0);
|
|
universe.process_events(0);
|
|
|
|
auto accepted1 = server.accept(nullptr);
|
|
auto accepted2 = server.accept(nullptr);
|
|
|
|
ASSERT_NE(accepted1, nullptr);
|
|
ASSERT_NE(accepted2, nullptr);
|
|
|
|
EXPECT_EQ(
|
|
static_cast<FakeTcpSocket *>(accepted1.get())->state(), FakeTcpSocket::ESTABLISHED);
|
|
EXPECT_EQ(
|
|
static_cast<FakeTcpSocket *>(accepted2.get())->state(), FakeTcpSocket::ESTABLISHED);
|
|
|
|
// Verify data isolation
|
|
const char *msg1 = "Message 1";
|
|
const char *msg2 = "Message 2";
|
|
|
|
client1.send(reinterpret_cast<const uint8_t *>(msg1), strlen(msg1));
|
|
client2.send(reinterpret_cast<const uint8_t *>(msg2), strlen(msg2));
|
|
|
|
universe.process_events(0);
|
|
universe.process_events(0);
|
|
|
|
uint8_t buf[100];
|
|
int len1 = accepted1->recv(buf, sizeof(buf));
|
|
EXPECT_EQ(len1, strlen(msg1));
|
|
int len2 = accepted2->recv(buf, sizeof(buf));
|
|
EXPECT_EQ(len2, strlen(msg2));
|
|
}
|
|
|
|
TEST_F(FakeNetworkTcpTest, DuplicateSynCreatesDuplicateConnections)
|
|
{
|
|
universe.set_verbose(true);
|
|
IP server_ip = make_ip(10, 0, 0, 1);
|
|
uint16_t server_port = 12345;
|
|
|
|
FakeTcpSocket server(universe);
|
|
server.set_ip(server_ip);
|
|
IP_Port server_addr{server_ip, net_htons(server_port)};
|
|
server.bind(&server_addr);
|
|
server.listen(5);
|
|
|
|
IP client_ip = make_ip(10, 0, 0, 2);
|
|
IP_Port client_addr{client_ip, net_htons(33445)};
|
|
|
|
Packet p{};
|
|
p.from = client_addr;
|
|
p.to = server_addr;
|
|
p.is_tcp = true;
|
|
p.tcp_flags = 0x02; // SYN
|
|
p.seq = 100;
|
|
|
|
universe.send_packet(p);
|
|
universe.send_packet(p); // Duplicate SYN
|
|
|
|
universe.process_events(0);
|
|
universe.process_events(0);
|
|
|
|
// Now send ACK from client
|
|
Packet ack{};
|
|
ack.from = client_addr;
|
|
ack.to = server_addr;
|
|
ack.is_tcp = true;
|
|
ack.tcp_flags = 0x10; // ACK
|
|
ack.ack = 101;
|
|
|
|
universe.send_packet(ack);
|
|
universe.process_events(0);
|
|
|
|
auto accepted1 = server.accept(nullptr);
|
|
auto accepted2 = server.accept(nullptr);
|
|
|
|
ASSERT_NE(accepted1, nullptr);
|
|
EXPECT_EQ(accepted2, nullptr); // This should pass now
|
|
}
|
|
|
|
TEST_F(FakeNetworkTcpTest, PeerCloseClearsConnection)
|
|
{
|
|
universe.set_verbose(true);
|
|
IP server_ip = make_ip(10, 0, 0, 1);
|
|
uint16_t server_port = 12345;
|
|
|
|
FakeTcpSocket server(universe);
|
|
server.set_ip(server_ip);
|
|
IP_Port server_addr{server_ip, net_htons(server_port)};
|
|
server.bind(&server_addr);
|
|
server.listen(5);
|
|
|
|
IP client_ip = make_ip(10, 0, 0, 2);
|
|
FakeTcpSocket client(universe);
|
|
client.set_ip(client_ip);
|
|
client.connect(&server_addr);
|
|
|
|
// Handshake
|
|
universe.process_events(0); // SYN
|
|
universe.process_events(0); // SYN-ACK
|
|
universe.process_events(0); // ACK
|
|
|
|
auto accepted = server.accept(nullptr);
|
|
ASSERT_NE(accepted, nullptr);
|
|
EXPECT_EQ(
|
|
static_cast<FakeTcpSocket *>(accepted.get())->state(), FakeTcpSocket::ESTABLISHED);
|
|
|
|
// Client closes
|
|
client.close();
|
|
universe.process_events(0); // Deliver RST/FIN
|
|
|
|
// Server should no longer be ESTABLISHED
|
|
EXPECT_EQ(static_cast<FakeTcpSocket *>(accepted.get())->state(), FakeTcpSocket::CLOSED);
|
|
|
|
// Now if client reconnects with same port
|
|
FakeTcpSocket client2(universe);
|
|
client2.set_ip(client_ip);
|
|
client2.connect(&server_addr);
|
|
universe.process_events(0); // Deliver SYN
|
|
|
|
// Node 2 port 20002 should have: 1 LISTEN, 0 ESTABLISHED (old one gone), 1 SYN_RECEIVED
|
|
// (new one) Total targets should be 2.
|
|
}
|
|
|
|
TEST_F(FakeNetworkTcpTest, DataNotProcessedByMultipleSockets)
|
|
{
|
|
universe.set_verbose(true);
|
|
IP server_ip = make_ip(10, 0, 0, 1);
|
|
uint16_t server_port = 12345;
|
|
|
|
FakeTcpSocket server(universe);
|
|
server.set_ip(server_ip);
|
|
IP_Port server_addr{server_ip, net_htons(server_port)};
|
|
server.bind(&server_addr);
|
|
server.listen(5);
|
|
|
|
IP client_ip = make_ip(10, 0, 0, 2);
|
|
IP_Port client_addr{client_ip, net_htons(33445)};
|
|
|
|
// Manually create two "established" sockets on the same port for the same peer
|
|
// This simulates a bug where duplicate connections were allowed.
|
|
auto sock1 = FakeTcpSocket::create_connected(universe, client_addr, server_port);
|
|
sock1->set_ip(server_ip);
|
|
auto sock2 = FakeTcpSocket::create_connected(universe, client_addr, server_port);
|
|
sock2->set_ip(server_ip);
|
|
|
|
universe.bind_tcp(server_ip, server_port, sock1.get());
|
|
universe.bind_tcp(server_ip, server_port, sock2.get());
|
|
|
|
// Send data from client to server
|
|
Packet p{};
|
|
p.from = client_addr;
|
|
p.to = server_addr;
|
|
p.is_tcp = true;
|
|
p.tcp_flags = 0x10; // ACK (Data)
|
|
const char *data = "Unique";
|
|
p.data.assign(data, data + strlen(data));
|
|
|
|
universe.send_packet(p);
|
|
universe.process_events(0);
|
|
|
|
// Only ONE of them should have received it, or at least they shouldn't BOTH have it
|
|
// in a way that suggests duplicate delivery.
|
|
EXPECT_TRUE((sock1->recv_buffer_size() == strlen(data))
|
|
^ (sock2->recv_buffer_size() == strlen(data)));
|
|
}
|
|
|
|
TEST_F(FakeNetworkTcpTest, ConnectionCollision)
|
|
{
|
|
universe.set_verbose(true);
|
|
IP server_ip = make_ip(10, 0, 0, 1);
|
|
uint16_t server_port = 12345;
|
|
|
|
FakeTcpSocket server(universe);
|
|
server.set_ip(server_ip);
|
|
IP_Port server_addr{server_ip, net_htons(server_port)};
|
|
server.bind(&server_addr);
|
|
server.listen(5);
|
|
|
|
IP client_ip = make_ip(10, 0, 0, 2);
|
|
|
|
FakeTcpSocket client1(universe);
|
|
client1.set_ip(client_ip);
|
|
// Bind to specific port to force collision later
|
|
IP_Port client_bind_addr{client_ip, net_htons(33445)};
|
|
client1.bind(&client_bind_addr);
|
|
client1.connect(&server_addr);
|
|
|
|
// Handshake 1
|
|
universe.process_events(0); // SYN
|
|
universe.process_events(0); // SYN-ACK
|
|
universe.process_events(0); // ACK
|
|
|
|
auto accepted1 = server.accept(nullptr);
|
|
ASSERT_NE(accepted1, nullptr);
|
|
EXPECT_EQ(
|
|
static_cast<FakeTcpSocket *>(accepted1.get())->state(), FakeTcpSocket::ESTABLISHED);
|
|
|
|
// Now client 1 "reconnects" (e.g. after a crash or timeout, but using same port)
|
|
FakeTcpSocket client2(universe);
|
|
client2.set_ip(client_ip);
|
|
client2.bind(&client_bind_addr); // Forced collision
|
|
client2.connect(&server_addr);
|
|
|
|
// Deliver new SYN
|
|
universe.process_events(0);
|
|
|
|
// server_addr port 12345 now has:
|
|
// 1. LISTEN socket
|
|
// 2. accepted1 (ESTABLISHED with 10.0.0.2:33445)
|
|
|
|
// In our simplified simulation, the ESTABLISHED socket now handles the SYN by returning
|
|
// true (ignoring it). So no new connection is created.
|
|
auto accepted2 = server.accept(nullptr);
|
|
EXPECT_EQ(accepted2, nullptr);
|
|
|
|
const char *msg1 = "Data 1";
|
|
client1.send(reinterpret_cast<const uint8_t *>(msg1), strlen(msg1));
|
|
universe.process_events(0);
|
|
|
|
// Data should still go to accepted1
|
|
EXPECT_EQ(accepted1->recv_buffer_size(), strlen(msg1));
|
|
}
|
|
|
|
TEST_F(FakeNetworkTcpTest, LoopbackConnection)
|
|
{
|
|
universe.set_verbose(true);
|
|
IP node_ip = make_ip(10, 0, 0, 1);
|
|
uint16_t port = 12345;
|
|
|
|
FakeTcpSocket server(universe);
|
|
server.set_ip(node_ip);
|
|
IP_Port listen_addr{node_ip, net_htons(port)};
|
|
server.bind(&listen_addr);
|
|
server.listen(5);
|
|
|
|
FakeTcpSocket client(universe);
|
|
client.set_ip(node_ip);
|
|
IP loopback_ip;
|
|
ip_init(&loopback_ip, false);
|
|
loopback_ip.ip.v4.uint32 = net_htonl(0x7F000001);
|
|
IP_Port server_loopback_addr{loopback_ip, net_htons(port)};
|
|
|
|
client.connect(&server_loopback_addr);
|
|
|
|
// SYN (Client -> 127.0.0.1:12345)
|
|
universe.process_events(0);
|
|
|
|
// SYN-ACK (Server -> Client)
|
|
universe.process_events(0);
|
|
|
|
// ACK (Client -> Server)
|
|
universe.process_events(0);
|
|
|
|
EXPECT_EQ(client.state(), FakeTcpSocket::ESTABLISHED);
|
|
auto accepted = server.accept(nullptr);
|
|
ASSERT_NE(accepted, nullptr);
|
|
EXPECT_EQ(
|
|
static_cast<FakeTcpSocket *>(accepted.get())->state(), FakeTcpSocket::ESTABLISHED);
|
|
|
|
// Data Transfer
|
|
const char *msg = "Loopback";
|
|
client.send(reinterpret_cast<const uint8_t *>(msg), strlen(msg));
|
|
universe.process_events(0);
|
|
|
|
uint8_t buf[100];
|
|
int len = accepted->recv(buf, sizeof(buf));
|
|
ASSERT_EQ(len, strlen(msg));
|
|
EXPECT_EQ(std::string(reinterpret_cast<char *>(buf), len), msg);
|
|
}
|
|
|
|
TEST_F(FakeNetworkTcpTest, SimultaneousConnect)
|
|
{
|
|
universe.set_verbose(true);
|
|
IP ipA = make_ip(10, 0, 0, 1);
|
|
IP ipB = make_ip(10, 0, 0, 2);
|
|
uint16_t portA = 10001;
|
|
uint16_t portB = 10002;
|
|
|
|
FakeTcpSocket sockA(universe);
|
|
sockA.set_ip(ipA);
|
|
IP_Port addrA{ipA, net_htons(portA)};
|
|
sockA.bind(&addrA);
|
|
sockA.listen(5);
|
|
|
|
FakeTcpSocket sockB(universe);
|
|
sockB.set_ip(ipB);
|
|
IP_Port addrB{ipB, net_htons(portB)};
|
|
sockB.bind(&addrB);
|
|
sockB.listen(5);
|
|
|
|
// A connects to B
|
|
sockA.connect(&addrB);
|
|
// B connects to A
|
|
sockB.connect(&addrA);
|
|
|
|
// This is "simultaneous open" in TCP but here they are also LISTENing.
|
|
// Toxcore uses this pattern sometimes.
|
|
|
|
universe.process_events(0); // SYN from A to B
|
|
universe.process_events(0); // SYN from B to A
|
|
|
|
universe.process_events(0); // SYN-ACK from B to A (for A's SYN)
|
|
universe.process_events(0); // SYN-ACK from A to B (for B's SYN)
|
|
|
|
universe.process_events(0); // ACK from A to B
|
|
universe.process_events(0); // ACK from B to A
|
|
|
|
EXPECT_EQ(sockA.state(), FakeTcpSocket::ESTABLISHED);
|
|
EXPECT_EQ(sockB.state(), FakeTcpSocket::ESTABLISHED);
|
|
}
|
|
|
|
TEST_F(FakeNetworkTcpTest, DataInHandshakeAck)
|
|
{
|
|
universe.set_verbose(true);
|
|
IP server_ip = make_ip(10, 0, 0, 1);
|
|
uint16_t server_port = 12345;
|
|
|
|
FakeTcpSocket server(universe);
|
|
server.set_ip(server_ip);
|
|
IP_Port server_addr{server_ip, net_htons(server_port)};
|
|
server.bind(&server_addr);
|
|
server.listen(5);
|
|
|
|
IP client_ip = make_ip(10, 0, 0, 2);
|
|
IP_Port client_addr{client_ip, net_htons(33445)};
|
|
|
|
// 1. SYN
|
|
Packet syn{};
|
|
syn.from = client_addr;
|
|
syn.to = server_addr;
|
|
syn.is_tcp = true;
|
|
syn.tcp_flags = 0x02;
|
|
universe.send_packet(syn);
|
|
universe.process_events(0);
|
|
|
|
// 2. SYN-ACK (Server -> Client)
|
|
universe.process_events(0);
|
|
|
|
// 3. ACK + Data (Client -> Server)
|
|
Packet ack{};
|
|
ack.from = client_addr;
|
|
ack.to = server_addr;
|
|
ack.is_tcp = true;
|
|
ack.tcp_flags = 0x10;
|
|
const char *data = "HandshakeData";
|
|
ack.data.assign(data, data + strlen(data));
|
|
universe.send_packet(ack);
|
|
universe.process_events(0);
|
|
|
|
auto accepted = server.accept(nullptr);
|
|
ASSERT_NE(accepted, nullptr);
|
|
EXPECT_EQ(accepted->recv_buffer_size(), strlen(data));
|
|
}
|
|
|
|
TEST_F(FakeNetworkTcpTest, LoopbackWithNodeIPMixed)
|
|
{
|
|
universe.set_verbose(true);
|
|
IP node_ip = make_ip(10, 0, 0, 1);
|
|
uint16_t port = 12345;
|
|
|
|
FakeTcpSocket server(universe);
|
|
server.set_ip(node_ip);
|
|
IP_Port listen_addr{node_ip, net_htons(port)};
|
|
server.bind(&listen_addr);
|
|
server.listen(5);
|
|
|
|
FakeTcpSocket client(universe);
|
|
client.set_ip(node_ip);
|
|
IP loopback_ip;
|
|
ip_init(&loopback_ip, false);
|
|
loopback_ip.ip.v4.uint32 = net_htonl(0x7F000001);
|
|
IP_Port server_loopback_addr{loopback_ip, net_htons(port)};
|
|
|
|
// Client connects to 127.0.0.1
|
|
client.connect(&server_loopback_addr);
|
|
|
|
universe.process_events(0); // SYN (Client -> 127.0.0.1)
|
|
universe.process_events(0); // SYN-ACK (Server -> Client)
|
|
universe.process_events(0); // ACK (Client -> Server)
|
|
|
|
EXPECT_EQ(client.state(), FakeTcpSocket::ESTABLISHED);
|
|
auto accepted = server.accept(nullptr);
|
|
ASSERT_NE(accepted, nullptr);
|
|
|
|
// Now manually simulate a packet coming from the server's EXTERNAL IP to the client.
|
|
// This happens because the server socket is bound to node_ip, so its packets might
|
|
// be delivered as coming from node_ip even if the client connected to 127.0.0.1.
|
|
Packet p{};
|
|
p.from = listen_addr; // node_ip:port
|
|
p.to.ip = node_ip;
|
|
p.to.port = net_htons(client.local_port());
|
|
p.is_tcp = true;
|
|
p.tcp_flags = 0x10; // ACK
|
|
const char *msg = "MixedIP";
|
|
p.data.assign(msg, msg + strlen(msg));
|
|
|
|
universe.send_packet(p);
|
|
universe.process_events(0);
|
|
|
|
EXPECT_EQ(client.recv_buffer_size(), strlen(msg));
|
|
}
|
|
|
|
}
|
|
}
|