/work/testing/support/doubles/network_universe_test.cc
Line | Count | Source |
1 | | #include "network_universe.hh" |
2 | | |
3 | | #include <gtest/gtest.h> |
4 | | |
5 | | #include <cstddef> |
6 | | |
7 | | #include "fake_sockets.hh" |
8 | | |
9 | | namespace tox::test { |
10 | | namespace { |
11 | | |
12 | | class NetworkUniverseTest : public ::testing::Test { |
13 | | public: |
14 | | ~NetworkUniverseTest() override; |
15 | | |
16 | | protected: |
17 | | NetworkUniverse universe; |
18 | | FakeUdpSocket s1{universe}; |
19 | | FakeUdpSocket s2{universe}; |
20 | | }; |
21 | | |
22 | 9 | NetworkUniverseTest::~NetworkUniverseTest() = default; |
23 | | |
24 | | TEST_F(NetworkUniverseTest, LatencySimulation) |
25 | 1 | { |
26 | 1 | universe.set_latency(100); |
27 | | |
28 | 1 | IP_Port s2_addr; |
29 | 1 | ip_init(&s2_addr.ip, false); |
30 | 1 | s2_addr.ip.ip.v4.uint32 = net_htonl(0x7F000001); |
31 | 1 | s2_addr.port = net_htons(9004); |
32 | 1 | s2.bind(&s2_addr); |
33 | | |
34 | 1 | uint8_t data[] = "Ping"; |
35 | 1 | s1.sendto(data, 4, &s2_addr); |
36 | | |
37 | | // Time 0: packet sent but delivery time is 100 |
38 | 1 | universe.process_events(0); |
39 | 1 | IP_Port from; |
40 | 1 | uint8_t buf[10]; |
41 | 1 | ASSERT_EQ(s2.recvfrom(buf, 10, &from), -1); |
42 | | |
43 | | // Time 50: still not delivered |
44 | 1 | universe.process_events(50); |
45 | 1 | ASSERT_EQ(s2.recvfrom(buf, 10, &from), -1); |
46 | | |
47 | | // Time 100: delivered |
48 | 1 | universe.process_events(100); |
49 | 1 | ASSERT_EQ(s2.recvfrom(buf, 10, &from), 4); |
50 | 1 | } |
51 | | |
52 | | TEST_F(NetworkUniverseTest, RoutesBasedOnIpAndPort) |
53 | 1 | { |
54 | 1 | IP ip1{}, ip2{}; |
55 | 1 | ip_init(&ip1, false); |
56 | 1 | ip1.ip.v4.uint32 = net_htonl(0x01010101); |
57 | 1 | ip_init(&ip2, false); |
58 | 1 | ip2.ip.v4.uint32 = net_htonl(0x02020202); |
59 | | |
60 | 1 | uint16_t port = 33445; |
61 | | |
62 | 1 | FakeUdpSocket sock1{universe}; |
63 | 1 | FakeUdpSocket sock2{universe}; |
64 | | |
65 | 1 | sock1.set_ip(ip1); |
66 | 1 | sock2.set_ip(ip2); |
67 | | |
68 | 1 | IP_Port addr1{ip1, net_htons(port)}; |
69 | 1 | IP_Port addr2{ip2, net_htons(port)}; |
70 | | |
71 | 1 | ASSERT_EQ(sock1.bind(&addr1), 0); |
72 | 1 | ASSERT_EQ(sock2.bind(&addr2), 0); |
73 | | |
74 | 1 | const char *msg1 = "To IP 1"; |
75 | 1 | const char *msg2 = "To IP 2"; |
76 | | |
77 | 1 | FakeUdpSocket sender{universe}; |
78 | 1 | sender.sendto(reinterpret_cast<const uint8_t *>(msg1), strlen(msg1), &addr1); |
79 | 1 | sender.sendto(reinterpret_cast<const uint8_t *>(msg2), strlen(msg2), &addr2); |
80 | | |
81 | 1 | universe.process_events(0); |
82 | | |
83 | 1 | uint8_t buf[100]; |
84 | 1 | IP_Port from; |
85 | | |
86 | 1 | int len1 = sock1.recvfrom(buf, sizeof(buf), &from); |
87 | 1 | ASSERT_GT(len1, 0); |
88 | 1 | EXPECT_EQ(std::string(reinterpret_cast<char *>(buf), static_cast<std::size_t>(len1)), msg1); |
89 | | |
90 | 1 | int len2 = sock2.recvfrom(buf, sizeof(buf), &from); |
91 | 1 | ASSERT_GT(len2, 0); |
92 | 1 | EXPECT_EQ(std::string(reinterpret_cast<char *>(buf), static_cast<std::size_t>(len2)), msg2); |
93 | 1 | } |
94 | | |
95 | | TEST_F(NetworkUniverseTest, FindFreePortIsIpSpecific) |
96 | 1 | { |
97 | 1 | IP ip1{}, ip2{}; |
98 | 1 | ip_init(&ip1, false); |
99 | 1 | ip1.ip.v4.uint32 = net_htonl(0x01010101); |
100 | 1 | ip_init(&ip2, false); |
101 | 1 | ip2.ip.v4.uint32 = net_htonl(0x02020202); |
102 | | |
103 | 1 | FakeUdpSocket sock1{universe}; |
104 | 1 | sock1.set_ip(ip1); |
105 | | |
106 | 1 | uint16_t port = 33445; |
107 | 1 | IP_Port addr1{ip1, net_htons(port)}; |
108 | 1 | ASSERT_EQ(sock1.bind(&addr1), 0); |
109 | | |
110 | | // Port 33445 should be busy for ip1, but free for ip2 |
111 | 1 | EXPECT_EQ(universe.find_free_port(ip1, port), port + 1); |
112 | 1 | EXPECT_EQ(universe.find_free_port(ip2, port), port); |
113 | 1 | } |
114 | | |
115 | | TEST_F(NetworkUniverseTest, IpPortKeyEqualityRobustness) |
116 | 1 | { |
117 | 1 | IP ip1{}, ip2{}; |
118 | 1 | ip_init(&ip1, false); |
119 | 1 | ip1.ip.v4.uint32 = net_htonl(0x7F000001); |
120 | 1 | ip_init(&ip2, false); |
121 | 1 | ip2.ip.v4.uint32 = net_htonl(0x7F000001); |
122 | | |
123 | 1 | uint16_t port = 12345; |
124 | | |
125 | | // Force different garbage in the union padding for IPv4 |
126 | | // The union is 16 bytes. IP4 is 4 bytes. Trailing 12 bytes are unused. |
127 | 1 | memset(ip1.ip.v6.uint8 + 4, 0x11, 12); |
128 | 1 | memset(ip2.ip.v6.uint8 + 4, 0x22, 12); |
129 | | |
130 | 1 | NetworkUniverse::IP_Port_Key key1{ip1, port}; |
131 | 1 | NetworkUniverse::IP_Port_Key key2{ip2, port}; |
132 | | |
133 | | // They should be considered equal (neither is less than the other) |
134 | 1 | EXPECT_FALSE(key1 < key2); |
135 | 1 | EXPECT_FALSE(key2 < key1); |
136 | | |
137 | | // Now try with different IPv4 but same garbage |
138 | 1 | ip2.ip.v4.uint32 = net_htonl(0x7F000002); |
139 | 1 | memset(ip2.ip.v6.uint8 + 4, 0x11, 12); // same garbage as ip1 |
140 | 1 | NetworkUniverse::IP_Port_Key key3{ip2, port}; |
141 | | |
142 | 1 | EXPECT_TRUE(key1 < key3 || key3 < key1); |
143 | 1 | } |
144 | | |
145 | | TEST_F(NetworkUniverseTest, IPv4v6Distinction) |
146 | 1 | { |
147 | 1 | IP ip1{}, ip2{}; |
148 | 1 | ip_init(&ip1, false); // IPv4 |
149 | 1 | ip1.ip.v4.uint32 = net_htonl(0x01020304); |
150 | 1 | ip_init(&ip2, true); // IPv6 |
151 | | // Set IPv6 bytes to match IPv4 bytes at the beginning |
152 | 1 | memset(ip2.ip.v6.uint8, 0, 16); |
153 | 1 | ip2.ip.v6.uint32[0] = net_htonl(0x01020304); |
154 | | |
155 | 1 | uint16_t port = 12345; |
156 | | |
157 | 1 | NetworkUniverse::IP_Port_Key key1{ip1, port}; |
158 | 1 | NetworkUniverse::IP_Port_Key key2{ip2, port}; |
159 | | |
160 | | // Different families must be different even if underlying bytes happen to match |
161 | 1 | EXPECT_TRUE(key1 < key2 || key2 < key1); |
162 | 1 | } |
163 | | |
164 | | TEST_F(NetworkUniverseTest, ManyNodes) |
165 | 1 | { |
166 | 1 | const int num_nodes = 5000; |
167 | 1 | struct NodeInfo { |
168 | 1 | std::unique_ptr<FakeUdpSocket> sock; |
169 | 1 | IP_Port addr; |
170 | 1 | }; |
171 | 1 | std::vector<NodeInfo> nodes; |
172 | 1 | nodes.reserve(num_nodes); |
173 | | |
174 | 5.00k | for (int i = 0; i < num_nodes; ++i) { |
175 | 5.00k | auto sock = std::make_unique<FakeUdpSocket>(universe); |
176 | 5.00k | IP ip{}; |
177 | 5.00k | ip_init(&ip, false); |
178 | 5.00k | ip.ip.v4.uint32 = net_htonl(0x0A000000 + i); // 10.0.x.y |
179 | 5.00k | sock->set_ip(ip); |
180 | | |
181 | 5.00k | IP_Port addr{ip, net_htons(33445)}; |
182 | 5.00k | ASSERT_EQ(sock->bind(&addr), 0); |
183 | 5.00k | nodes.push_back({std::move(sock), addr}); |
184 | 5.00k | } |
185 | | |
186 | 1 | const int num_messages = 100; |
187 | | // Send messages from first num_messages to last num_messages nodes |
188 | 101 | for (int i = 0; i < num_messages; ++i) { |
189 | 100 | const char *msg = "Stress test"; |
190 | 100 | nodes[i].sock->sendto(reinterpret_cast<const uint8_t *>(msg), strlen(msg), |
191 | 100 | &nodes[num_nodes - 1 - i].addr); |
192 | 100 | } |
193 | | |
194 | 1 | universe.process_events(0); |
195 | | |
196 | 101 | for (int i = 0; i < num_messages; ++i) { |
197 | 100 | uint8_t buf[100]; |
198 | 100 | IP_Port from; |
199 | 100 | int len = nodes[num_nodes - 1 - i].sock->recvfrom(buf, sizeof(buf), &from); |
200 | 100 | ASSERT_GT(len, 0); |
201 | 100 | EXPECT_EQ(std::string(reinterpret_cast<char *>(buf), static_cast<std::size_t>(len)), |
202 | 100 | "Stress test"); |
203 | 100 | EXPECT_TRUE(ip_equal(&from.ip, &nodes[i].addr.ip)); |
204 | 100 | } |
205 | 1 | } |
206 | | |
207 | | TEST_F(NetworkUniverseTest, IpPadding) |
208 | 1 | { |
209 | 1 | IP ip1{}; |
210 | 1 | ip_init(&ip1, false); |
211 | 1 | ip1.ip.v4.uint32 = net_htonl(0x7F000001); |
212 | | |
213 | 1 | FakeUdpSocket sock{universe}; |
214 | 1 | sock.set_ip(ip1); |
215 | 1 | IP_Port bind_addr{ip1, net_htons(12345)}; |
216 | 1 | ASSERT_EQ(sock.bind(&bind_addr), 0); |
217 | | |
218 | | // Create an address with garbage in the padding |
219 | 1 | IP_Port target_addr; |
220 | 1 | memset(&target_addr, 0xAA, sizeof(target_addr)); |
221 | 1 | ip_init(&target_addr.ip, false); |
222 | 1 | target_addr.ip.ip.v4.uint32 = net_htonl(0x7F000001); |
223 | 1 | target_addr.port = net_htons(12345); |
224 | | |
225 | 1 | FakeUdpSocket sender{universe}; |
226 | 1 | const char *msg = "Padding test"; |
227 | 1 | sender.sendto(reinterpret_cast<const uint8_t *>(msg), strlen(msg), &target_addr); |
228 | | |
229 | 1 | universe.process_events(0); |
230 | | |
231 | 1 | uint8_t buf[100]; |
232 | 1 | IP_Port from; |
233 | 1 | int len = sock.recvfrom(buf, sizeof(buf), &from); |
234 | | |
235 | | // If this fails, it means NetworkUniverse is not robust against padding garbage |
236 | 1 | ASSERT_GT(len, 0); |
237 | 1 | EXPECT_EQ(std::string(reinterpret_cast<char *>(buf), static_cast<std::size_t>(len)), |
238 | 1 | "Padding test"); |
239 | 1 | } |
240 | | |
241 | | TEST_F(NetworkUniverseTest, TcpRoutingSpecificity) |
242 | 1 | { |
243 | 1 | IP ip1{}; |
244 | 1 | ip_init(&ip1, false); |
245 | 1 | ip1.ip.v4.uint32 = net_htonl(0x0A000001); // 10.0.0.1 |
246 | | |
247 | 1 | uint16_t port = 12345; |
248 | 1 | IP_Port local_addr{ip1, net_htons(port)}; |
249 | | |
250 | 1 | FakeTcpSocket listen_sock(universe); |
251 | 1 | listen_sock.set_ip(ip1); |
252 | 1 | listen_sock.bind(&local_addr); |
253 | 1 | listen_sock.listen(5); |
254 | | |
255 | 1 | IP remote_ip{}; |
256 | 1 | ip_init(&remote_ip, false); |
257 | 1 | remote_ip.ip.v4.uint32 = net_htonl(0x0A000002); // 10.0.0.2 |
258 | 1 | IP_Port remote_addr{remote_ip, net_htons(33445)}; |
259 | | |
260 | 1 | auto established_sock = FakeTcpSocket::create_connected(universe, remote_addr, port); |
261 | 1 | established_sock->set_ip(ip1); |
262 | 1 | universe.bind_tcp(ip1, port, established_sock.get()); |
263 | | |
264 | | // Send a data packet from remote to local |
265 | 1 | Packet p{}; |
266 | 1 | p.from = remote_addr; |
267 | 1 | p.to = local_addr; |
268 | 1 | p.is_tcp = true; |
269 | 1 | p.tcp_flags = 0x10; // ACK (Data) |
270 | 1 | const char *data = "Specific"; |
271 | 1 | p.data.assign(data, data + strlen(data)); |
272 | | |
273 | 1 | universe.send_packet(p); |
274 | 1 | universe.process_events(0); |
275 | | |
276 | | // established_sock should have received it |
277 | 1 | EXPECT_EQ(established_sock->recv_buffer_size(), strlen(data)); |
278 | | |
279 | | // listen_sock should NOT have received it (it doesn't have a buffer, but it shouldn't have |
280 | | // been called) We can't easily check listen_sock wasn't called without mocks or checking |
281 | | // logs, but we can check that it didn't create a new pending connection. |
282 | 1 | EXPECT_FALSE(listen_sock.is_readable()); |
283 | 1 | } |
284 | | |
285 | | TEST_F(NetworkUniverseTest, PacketOrdering) |
286 | 1 | { |
287 | 1 | IP ip1{}, ip2{}; |
288 | 1 | ip_init(&ip1, false); |
289 | 1 | ip1.ip.v4.uint32 = net_htonl(0x0A000001); |
290 | 1 | ip_init(&ip2, false); |
291 | 1 | ip2.ip.v4.uint32 = net_htonl(0x0A000002); |
292 | | |
293 | 1 | uint16_t port = 33445; |
294 | 1 | IP_Port addr1{ip1, net_htons(port)}; |
295 | 1 | IP_Port addr2{ip2, net_htons(port)}; |
296 | | |
297 | 1 | FakeUdpSocket sock1{universe}; |
298 | 1 | sock1.set_ip(ip1); |
299 | 1 | sock1.bind(&addr1); |
300 | | |
301 | 1 | FakeUdpSocket sock2{universe}; |
302 | 1 | sock2.set_ip(ip2); |
303 | 1 | sock2.bind(&addr2); |
304 | | |
305 | | // Send 10 packets with the same delivery time (global latency = 0) |
306 | 11 | for (int i = 0; i < 10; ++i) { |
307 | 10 | uint8_t data = static_cast<uint8_t>(i); |
308 | 10 | sock1.sendto(&data, 1, &addr2); |
309 | 10 | } |
310 | | |
311 | 1 | universe.process_events(0); |
312 | | |
313 | | // They should be received in the exact order they were sent |
314 | 11 | for (int i = 0; i < 10; ++i) { |
315 | 10 | uint8_t buf[1]; |
316 | 10 | IP_Port from; |
317 | 10 | ASSERT_EQ(sock2.recvfrom(buf, 1, &from), 1); |
318 | 20 | EXPECT_EQ(buf[0], i) << "Packet " << i << " was delivered out of order"; |
319 | 10 | } |
320 | 1 | } |
321 | | |
322 | | } // namespace |
323 | | } // namespace tox::test |