/work/testing/support/doubles/network_universe.cc
Line | Count | Source (jump to first uncovered line) |
1 | | #include "network_universe.hh" |
2 | | |
3 | | #include <cstring> |
4 | | #include <iostream> |
5 | | |
6 | | #include "fake_sockets.hh" |
7 | | |
8 | | namespace tox::test { |
9 | | |
10 | | std::ostream &operator<<(std::ostream &os, TcpFlags flags) |
11 | 134 | { |
12 | 134 | bool first = true; |
13 | 134 | if (flags.value & 0x02) { |
14 | 86 | os << (first ? "" : "|") << "SYN"; |
15 | 86 | first = false; |
16 | 86 | } |
17 | 134 | if (flags.value & 0x10) { |
18 | 85 | os << (first ? "" : "|") << "ACK"; |
19 | 85 | first = false; |
20 | 85 | } |
21 | 134 | if (flags.value & 0x01) { |
22 | 0 | os << (first ? "" : "|") << "FIN"; |
23 | 0 | first = false; |
24 | 0 | } |
25 | 134 | if (flags.value & 0x04) { |
26 | 3 | os << (first ? "" : "|") << "RST"; |
27 | 3 | first = false; |
28 | 3 | } |
29 | 134 | if (first) |
30 | 0 | os << "NONE"; |
31 | 134 | return os << "(" << static_cast<int>(flags.value) << ")"; |
32 | 134 | } |
33 | | |
34 | | bool NetworkUniverse::IP_Port_Key::operator<(const IP_Port_Key &other) const |
35 | 684k | { |
36 | 684k | if (port != other.port) |
37 | 191k | return port < other.port; |
38 | 493k | if (ip.family.value != other.ip.family.value) |
39 | 16 | return ip.family.value < other.ip.family.value; |
40 | | |
41 | 493k | if (net_family_is_ipv4(ip.family)) { |
42 | 493k | return ip.ip.v4.uint32 < other.ip.ip.v4.uint32; |
43 | 493k | } |
44 | | |
45 | 0 | return std::memcmp(&ip.ip.v6, &other.ip.ip.v6, sizeof(ip.ip.v6)) < 0; |
46 | 493k | } |
47 | | |
48 | 2.84k | NetworkUniverse::NetworkUniverse() { } |
49 | 2.84k | NetworkUniverse::~NetworkUniverse() { } |
50 | | |
51 | | bool NetworkUniverse::bind_udp(IP ip, uint16_t port, FakeUdpSocket *_Nonnull socket) |
52 | 10.2k | { |
53 | 10.2k | std::lock_guard<std::recursive_mutex> lock(mutex_); |
54 | 10.2k | IP_Port_Key key{ip, port}; |
55 | 10.2k | if (udp_bindings_.count(key)) |
56 | 1.46k | return false; |
57 | 8.82k | udp_bindings_[key] = socket; |
58 | 8.82k | return true; |
59 | 10.2k | } |
60 | | |
61 | | void NetworkUniverse::unbind_udp(IP ip, uint16_t port) |
62 | 8.82k | { |
63 | 8.82k | std::lock_guard<std::recursive_mutex> lock(mutex_); |
64 | 8.82k | udp_bindings_.erase({ip, port}); |
65 | 8.82k | } |
66 | | |
67 | | bool NetworkUniverse::bind_tcp(IP ip, uint16_t port, FakeTcpSocket *_Nonnull socket) |
68 | 72 | { |
69 | 72 | std::lock_guard<std::recursive_mutex> lock(mutex_); |
70 | 72 | tcp_bindings_.insert({{ip, port}, socket}); |
71 | 72 | return true; |
72 | 72 | } |
73 | | |
74 | | void NetworkUniverse::unbind_tcp(IP ip, uint16_t port, FakeTcpSocket *_Nonnull socket) |
75 | 72 | { |
76 | 72 | std::lock_guard<std::recursive_mutex> lock(mutex_); |
77 | 72 | auto range = tcp_bindings_.equal_range({ip, port}); |
78 | 92 | for (auto it = range.first; it != range.second; ++it) { |
79 | 92 | if (it->second == socket) { |
80 | 72 | tcp_bindings_.erase(it); |
81 | 72 | break; |
82 | 72 | } |
83 | 92 | } |
84 | 72 | } |
85 | | |
86 | | void NetworkUniverse::send_packet(Packet p) |
87 | 41.1k | { |
88 | 41.1k | std::lock_guard<std::recursive_mutex> lock(mutex_); |
89 | | |
90 | | // Apply filters |
91 | 41.1k | for (const auto &filter : filters_) { |
92 | 24 | if (!filter(p)) |
93 | 9 | return; |
94 | 24 | } |
95 | | |
96 | | // Notify observers |
97 | 41.1k | for (const auto &observer : observers_) { |
98 | 11.1k | observer(p); |
99 | 11.1k | } |
100 | | |
101 | 41.1k | p.delivery_time += global_latency_ms_; |
102 | | |
103 | 41.1k | p.sequence_number = next_packet_id_++; |
104 | | |
105 | 41.1k | if (verbose_) { |
106 | 45 | Ip_Ntoa from_str, to_str; |
107 | 45 | net_ip_ntoa(&p.from.ip, &from_str); |
108 | 45 | net_ip_ntoa(&p.to.ip, &to_str); |
109 | 45 | std::cerr << "[NetworkUniverse] Enqueued packet #" << p.sequence_number << " from " |
110 | 45 | << from_str.buf << ":" << net_ntohs(p.from.port) << " to " << to_str.buf << ":" |
111 | 45 | << net_ntohs(p.to.port); |
112 | 45 | if (p.is_tcp) { |
113 | 45 | std::cerr << " (TCP Flags=" << TcpFlags{p.tcp_flags} << " Seq=" << p.seq |
114 | 45 | << " Ack=" << p.ack << ")"; |
115 | 45 | } |
116 | 45 | std::cerr << " with size " << p.data.size() << std::endl; |
117 | 45 | } |
118 | 41.1k | event_queue_.push(std::move(p)); |
119 | 41.1k | } |
120 | | |
121 | | static bool is_ipv4_mapped(const IP &ip) |
122 | 69 | { |
123 | 69 | if (!net_family_is_ipv6(ip.family)) |
124 | 0 | return false; |
125 | 69 | const uint8_t *b = ip.ip.v6.uint8; |
126 | 429 | for (int i = 0; i < 10; ++i) |
127 | 393 | if (b[i] != 0) |
128 | 33 | return false; |
129 | 36 | if (b[10] != 0xFF || b[11] != 0xFF) |
130 | 0 | return false; |
131 | 36 | return true; |
132 | 36 | } |
133 | | |
134 | | static IP extract_ipv4(const IP &ip) |
135 | 36 | { |
136 | 36 | IP ip4; |
137 | 36 | ip_init(&ip4, false); |
138 | 36 | const uint8_t *b = ip.ip.v6.uint8; |
139 | 36 | std::memcpy(ip4.ip.v4.uint8, b + 12, 4); |
140 | 36 | return ip4; |
141 | 36 | } |
142 | | |
143 | | bool is_loopback(const IP &ip) |
144 | 40.3k | { |
145 | 40.3k | if (net_family_is_ipv4(ip.family)) { |
146 | 40.2k | return ip.ip.v4.uint32 == net_htonl(0x7F000001); |
147 | 40.2k | } |
148 | 69 | if (net_family_is_ipv6(ip.family)) { |
149 | 69 | const uint8_t *b = ip.ip.v6.uint8; |
150 | 429 | for (int i = 0; i < 15; ++i) { |
151 | 429 | if (b[i] != 0) { |
152 | 69 | return false; |
153 | 69 | } |
154 | 429 | } |
155 | 0 | return b[15] == 1; |
156 | 69 | } |
157 | 0 | return false; |
158 | 69 | } |
159 | | |
160 | | void NetworkUniverse::process_events(uint64_t current_time_ms) |
161 | 3.19k | { |
162 | 43.5k | while (true) { |
163 | 43.5k | Packet p; |
164 | 43.5k | std::vector<FakeTcpSocket *> tcp_targets; |
165 | 43.5k | FakeUdpSocket *udp_target = nullptr; |
166 | 43.5k | bool has_packet = false; |
167 | | |
168 | 43.5k | { |
169 | 43.5k | std::lock_guard<std::recursive_mutex> lock(mutex_); |
170 | 43.5k | if (!event_queue_.empty()) { |
171 | 40.3k | const Packet &top = event_queue_.top(); |
172 | 40.3k | if (verbose_) { |
173 | 45 | std::cerr << "[NetworkUniverse] Peek packet: time=" << top.delivery_time |
174 | 45 | << " current=" << current_time_ms << " tcp=" << top.is_tcp |
175 | 45 | << std::endl; |
176 | 45 | } |
177 | 40.3k | } |
178 | | |
179 | 43.5k | if (!event_queue_.empty() && event_queue_.top().delivery_time <= current_time_ms) { |
180 | 40.3k | p = event_queue_.top(); |
181 | 40.3k | event_queue_.pop(); |
182 | 40.3k | has_packet = true; |
183 | | |
184 | 40.3k | if (verbose_) { |
185 | 45 | Ip_Ntoa from_str, to_str; |
186 | 45 | net_ip_ntoa(&p.from.ip, &from_str); |
187 | 45 | net_ip_ntoa(&p.to.ip, &to_str); |
188 | 45 | std::cerr << "[NetworkUniverse] Processing packet #" << p.sequence_number |
189 | 45 | << " from " << from_str.buf << ":" << net_ntohs(p.from.port) << " to " |
190 | 45 | << to_str.buf << ":" << net_ntohs(p.to.port) |
191 | 45 | << " (TCP=" << (p.is_tcp ? "true" : "false"); |
192 | 45 | if (p.is_tcp) { |
193 | 45 | std::cerr << " Flags=" << TcpFlags{p.tcp_flags} << " Seq=" << p.seq |
194 | 45 | << " Ack=" << p.ack; |
195 | 45 | } |
196 | 45 | std::cerr << " Size=" << p.data.size() << ")" << std::endl; |
197 | 45 | } |
198 | | |
199 | 40.3k | IP target_ip = p.to.ip; |
200 | | |
201 | 40.3k | if (p.is_tcp) { |
202 | 83 | if (is_loopback(target_ip) |
203 | 83 | && tcp_bindings_.count({target_ip, net_ntohs(p.to.port)}) == 0) { |
204 | 5 | if (verbose_) { |
205 | 5 | std::cerr << "[NetworkUniverse] Loopback packet to " |
206 | 5 | << static_cast<int>(target_ip.ip.v4.uint8[3]) |
207 | 5 | << " redirected to " |
208 | 5 | << static_cast<int>(p.from.ip.ip.v4.uint8[3]) << std::endl; |
209 | 5 | } |
210 | 5 | target_ip = p.from.ip; |
211 | 5 | } |
212 | | |
213 | 83 | auto range = tcp_bindings_.equal_range({target_ip, net_ntohs(p.to.port)}); |
214 | 83 | FakeTcpSocket *listen_match = nullptr; |
215 | | |
216 | 209 | for (auto it = range.first; it != range.second; ++it) { |
217 | 126 | FakeTcpSocket *s = it->second; |
218 | 126 | if (s->state() == FakeTcpSocket::LISTEN) { |
219 | 52 | listen_match = s; |
220 | 74 | } else { |
221 | 74 | const IP_Port &remote = s->remote_addr(); |
222 | 74 | if (net_ntohs(p.from.port) == net_ntohs(remote.port)) { |
223 | 64 | if (ip_equal(&p.from.ip, &remote.ip) |
224 | 64 | || (is_loopback(p.from.ip) && ip_equal(&remote.ip, &target_ip)) |
225 | 64 | || (is_loopback(remote.ip) |
226 | 64 | && ip_equal(&p.from.ip, &target_ip))) { |
227 | 64 | tcp_targets.push_back(s); |
228 | 64 | } |
229 | 64 | } |
230 | 74 | } |
231 | 126 | } |
232 | | |
233 | 83 | if (listen_match && (p.tcp_flags & 0x02)) { |
234 | 20 | tcp_targets.push_back(listen_match); |
235 | 20 | } |
236 | | |
237 | 83 | if (verbose_) { |
238 | 45 | std::cerr << "[NetworkUniverse] Routing TCP to " |
239 | 45 | << static_cast<int>(target_ip.ip.v4.uint8[0]) << "." |
240 | 45 | << static_cast<int>(target_ip.ip.v4.uint8[3]) << ":" |
241 | 45 | << net_ntohs(p.to.port) |
242 | 45 | << ". Targets found: " << tcp_targets.size() << std::endl; |
243 | 45 | } |
244 | 83 | if (tcp_targets.empty()) { |
245 | 2 | if (verbose_) { |
246 | 2 | std::cerr << "[NetworkUniverse] WARNING: No TCP targets for " |
247 | 2 | << static_cast<int>(target_ip.ip.v4.uint8[0]) << "." |
248 | 2 | << static_cast<int>(target_ip.ip.v4.uint8[3]) << ":" |
249 | 2 | << net_ntohs(p.to.port) << std::endl; |
250 | 2 | } |
251 | 2 | } |
252 | 40.2k | } else { |
253 | 40.2k | if (is_loopback(target_ip) |
254 | 40.2k | && udp_bindings_.count({target_ip, net_ntohs(p.to.port)}) == 0) { |
255 | 0 | target_ip = p.from.ip; |
256 | 0 | } |
257 | | |
258 | 40.2k | if (udp_bindings_.count({target_ip, net_ntohs(p.to.port)})) { |
259 | 40.2k | udp_target = udp_bindings_[{target_ip, net_ntohs(p.to.port)}]; |
260 | 40.2k | } else if (is_ipv4_mapped(target_ip)) { |
261 | 36 | IP ip4 = extract_ipv4(target_ip); |
262 | 36 | if (udp_bindings_.count({ip4, net_ntohs(p.to.port)})) { |
263 | 0 | udp_target = udp_bindings_[{ip4, net_ntohs(p.to.port)}]; |
264 | 0 | } |
265 | 36 | } |
266 | 40.2k | } |
267 | 40.3k | } |
268 | 43.5k | } |
269 | | |
270 | 43.5k | if (!has_packet) { |
271 | 3.19k | break; |
272 | 3.19k | } |
273 | | |
274 | 40.3k | if (p.is_tcp) { |
275 | 83 | for (auto *it : tcp_targets) { |
276 | 82 | if (it->handle_packet(p)) { |
277 | 80 | break; |
278 | 80 | } |
279 | 82 | } |
280 | 40.2k | } else { |
281 | 40.2k | if (udp_target) { |
282 | 40.2k | udp_target->push_packet(std::move(p.data), p.from); |
283 | 40.2k | } |
284 | 40.2k | } |
285 | 40.3k | } |
286 | 3.19k | } |
287 | | |
288 | 1 | void NetworkUniverse::set_latency(uint64_t ms) { global_latency_ms_ = ms; } |
289 | | |
290 | 11 | void NetworkUniverse::set_verbose(bool verbose) { verbose_ = verbose; } |
291 | | |
292 | 162k | bool NetworkUniverse::is_verbose() const { return verbose_; } |
293 | | |
294 | | void NetworkUniverse::add_filter(PacketFilter filter) |
295 | 2 | { |
296 | 2 | std::lock_guard<std::recursive_mutex> lock(mutex_); |
297 | 2 | filters_.push_back(std::move(filter)); |
298 | 2 | } |
299 | | |
300 | | void NetworkUniverse::add_observer(PacketSink sink) |
301 | 1 | { |
302 | 1 | std::lock_guard<std::recursive_mutex> lock(mutex_); |
303 | 1 | observers_.push_back(std::move(sink)); |
304 | 1 | } |
305 | | |
306 | | uint16_t NetworkUniverse::find_free_port(IP ip, uint16_t start) |
307 | 23 | { |
308 | 23 | std::lock_guard<std::recursive_mutex> lock(mutex_); |
309 | 28 | for (uint16_t port = start; port < 65535; ++port) { |
310 | 28 | if (!udp_bindings_.count({ip, port}) && !tcp_bindings_.count({ip, port})) |
311 | 23 | return port; |
312 | 28 | } |
313 | 0 | return 0; |
314 | 23 | } |
315 | | |
316 | | } // namespace tox::test |