#include #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(accepted1.get())->state(), FakeTcpSocket::ESTABLISHED); EXPECT_EQ( static_cast(accepted2.get())->state(), FakeTcpSocket::ESTABLISHED); // Verify data isolation const char *msg1 = "Message 1"; const char *msg2 = "Message 2"; client1.send(reinterpret_cast(msg1), strlen(msg1)); client2.send(reinterpret_cast(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(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(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(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(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(accepted.get())->state(), FakeTcpSocket::ESTABLISHED); // Data Transfer const char *msg = "Loopback"; client.send(reinterpret_cast(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(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)); } } }