Files
tomato/testing/support/doubles/fake_network_tcp_test.cc
Green Sky 9b36dd9d99 Squashed 'external/toxcore/c-toxcore/' changes from c9cdae001..9ed2fa80d
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
2026-02-01 14:26:52 +01:00

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));
}
}
}