/work/toxcore/LAN_discovery.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* SPDX-License-Identifier: GPL-3.0-or-later |
2 | | * Copyright © 2016-2025 The TokTok team. |
3 | | * Copyright © 2013 Tox project. |
4 | | */ |
5 | | |
6 | | /** |
7 | | * LAN discovery implementation. |
8 | | */ |
9 | | #include "LAN_discovery.h" |
10 | | |
11 | | #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) |
12 | | // The mingw32/64 Windows library warns about including winsock2.h after |
13 | | // windows.h even though with the above it's a valid thing to do. So, to make |
14 | | // mingw32 headers happy, we include winsock2.h first. |
15 | | #include <winsock2.h> |
16 | | |
17 | | #include <windows.h> |
18 | | #include <ws2tcpip.h> |
19 | | |
20 | | #include <iphlpapi.h> |
21 | | #endif /* WIN32 */ |
22 | | |
23 | | #if defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__) |
24 | | #include <netinet/in.h> |
25 | | #include <sys/ioctl.h> |
26 | | #include <sys/socket.h> |
27 | | #include <sys/types.h> |
28 | | #include <unistd.h> |
29 | | #endif /* Linux/BSD */ |
30 | | |
31 | | #ifdef __linux__ |
32 | | #include <linux/if.h> |
33 | | #endif /* Linux */ |
34 | | |
35 | | #if defined(__FreeBSD__) || defined(__DragonFly__) |
36 | | #include <net/if.h> |
37 | | #endif /* BSD */ |
38 | | |
39 | | #include "attributes.h" |
40 | | #include "ccompat.h" |
41 | | #include "crypto_core.h" |
42 | | #include "mem.h" |
43 | | #include "network.h" |
44 | | |
45 | 59 | #define MAX_INTERFACES 16 |
46 | | |
47 | | struct Broadcast_Info { |
48 | | const Memory *mem; |
49 | | |
50 | | uint32_t count; |
51 | | IP ips[MAX_INTERFACES]; |
52 | | }; |
53 | | |
54 | | #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) |
55 | | |
56 | | static Broadcast_Info *fetch_broadcast_info(const Memory *_Nonnull mem, const Network *_Nonnull ns) |
57 | | { |
58 | | Broadcast_Info *broadcast = (Broadcast_Info *)mem_alloc(mem, sizeof(Broadcast_Info)); |
59 | | |
60 | | if (broadcast == nullptr) { |
61 | | return nullptr; |
62 | | } |
63 | | |
64 | | broadcast->mem = mem; |
65 | | |
66 | | IP_ADAPTER_INFO *adapter_info = (IP_ADAPTER_INFO *)mem_balloc(mem, sizeof(IP_ADAPTER_INFO)); |
67 | | |
68 | | if (adapter_info == nullptr) { |
69 | | mem_delete(mem, broadcast); |
70 | | return nullptr; |
71 | | } |
72 | | |
73 | | unsigned long out_buf_len = sizeof(IP_ADAPTER_INFO); |
74 | | |
75 | | if (GetAdaptersInfo(adapter_info, &out_buf_len) == ERROR_BUFFER_OVERFLOW) { |
76 | | mem_delete(mem, adapter_info); |
77 | | IP_ADAPTER_INFO *new_adapter_info = (IP_ADAPTER_INFO *)mem_balloc(mem, out_buf_len); |
78 | | |
79 | | if (new_adapter_info == nullptr) { |
80 | | mem_delete(mem, broadcast); |
81 | | return nullptr; |
82 | | } |
83 | | |
84 | | adapter_info = new_adapter_info; |
85 | | } |
86 | | |
87 | | const int ret = GetAdaptersInfo(adapter_info, &out_buf_len); |
88 | | |
89 | | if (ret == NO_ERROR) { |
90 | | IP_ADAPTER_INFO *adapter = adapter_info; |
91 | | |
92 | | while (adapter != nullptr) { |
93 | | IP gateway = {0}; |
94 | | IP subnet_mask = {0}; |
95 | | |
96 | | if (addr_parse_ip(adapter->IpAddressList.IpMask.String, &subnet_mask) |
97 | | && addr_parse_ip(adapter->GatewayList.IpAddress.String, &gateway)) { |
98 | | if (net_family_is_ipv4(gateway.family) && net_family_is_ipv4(subnet_mask.family)) { |
99 | | IP *ip = &broadcast->ips[broadcast->count]; |
100 | | ip->family = net_family_ipv4(); |
101 | | const uint32_t gateway_ip = net_ntohl(gateway.ip.v4.uint32); |
102 | | const uint32_t subnet_ip = net_ntohl(subnet_mask.ip.v4.uint32); |
103 | | const uint32_t broadcast_ip = gateway_ip + ~subnet_ip - 1; |
104 | | ip->ip.v4.uint32 = net_htonl(broadcast_ip); |
105 | | ++broadcast->count; |
106 | | |
107 | | if (broadcast->count >= MAX_INTERFACES) { |
108 | | break; |
109 | | } |
110 | | } |
111 | | } |
112 | | |
113 | | adapter = adapter->Next; |
114 | | } |
115 | | } |
116 | | |
117 | | if (adapter_info != nullptr) { |
118 | | mem_delete(mem, adapter_info); |
119 | | } |
120 | | |
121 | | return broadcast; |
122 | | } |
123 | | |
124 | | #elif !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) && (defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__)) |
125 | | |
126 | | static bool ip4_is_local(const IP4 *_Nonnull ip4); |
127 | | |
128 | | static Broadcast_Info *fetch_broadcast_info(const Memory *_Nonnull mem, const Network *_Nonnull ns) |
129 | 35 | { |
130 | 35 | Broadcast_Info *broadcast = (Broadcast_Info *)mem_alloc(mem, sizeof(Broadcast_Info)); |
131 | | |
132 | 35 | if (broadcast == nullptr) { |
133 | 2 | return nullptr; |
134 | 2 | } |
135 | | |
136 | 33 | broadcast->mem = mem; |
137 | | |
138 | | /* Not sure how many platforms this will run on, |
139 | | * so it's wrapped in `__linux__` for now. |
140 | | * Definitely won't work like this on Windows... |
141 | | */ |
142 | 33 | const Socket sock = net_socket(ns, net_family_ipv4(), TOX_SOCK_STREAM, 0); |
143 | | |
144 | 33 | if (!sock_valid(sock)) { |
145 | 2 | mem_delete(mem, broadcast); |
146 | 2 | return nullptr; |
147 | 2 | } |
148 | | |
149 | | /* Configure ifconf for the ioctl call. */ |
150 | 31 | struct ifreq i_faces[MAX_INTERFACES] = {{{0}}}; |
151 | | |
152 | 31 | struct ifconf ifc; |
153 | 31 | ifc.ifc_buf = (char *)i_faces; |
154 | 31 | ifc.ifc_len = sizeof(i_faces); |
155 | | |
156 | 31 | if (ioctl(net_socket_to_native(sock), SIOCGIFCONF, &ifc) < 0) { |
157 | 1 | kill_sock(ns, sock); |
158 | 1 | mem_delete(mem, broadcast); |
159 | 1 | return nullptr; |
160 | 1 | } |
161 | | |
162 | | /* `ifc.ifc_len` is set by the `ioctl()` to the actual length used. |
163 | | * On usage of the complete array the call should be repeated with |
164 | | * a larger array, not done (640kB and 16 interfaces shall be |
165 | | * enough, for everybody!) |
166 | | */ |
167 | 30 | const int n = ifc.ifc_len / sizeof(struct ifreq); |
168 | | |
169 | 90 | for (int i = 0; i < n; ++i) { |
170 | | /* there are interfaces with are incapable of broadcast |
171 | | * on Linux, `lo` has no broadcast address, but this function returns `>=0` */ |
172 | 60 | if (ioctl(net_socket_to_native(sock), SIOCGIFBRDADDR, &i_faces[i]) < 0) { |
173 | 1 | continue; |
174 | 1 | } |
175 | | |
176 | | /* moot check: only AF_INET returned (backwards compat.) */ |
177 | 59 | if (i_faces[i].ifr_broadaddr.sa_family != AF_INET) { |
178 | 0 | continue; |
179 | 0 | } |
180 | | |
181 | 59 | const struct sockaddr_in *broadaddr4 = (const struct sockaddr_in *)(void *)&i_faces[i].ifr_broadaddr; |
182 | | |
183 | 59 | if (broadcast->count >= MAX_INTERFACES) { |
184 | 0 | break; |
185 | 0 | } |
186 | | |
187 | 59 | IP *ip = &broadcast->ips[broadcast->count]; |
188 | 59 | ip->family = net_family_ipv4(); |
189 | 59 | ip->ip.v4.uint32 = broadaddr4->sin_addr.s_addr; |
190 | | |
191 | | // if no broadcast address |
192 | 59 | if (ip->ip.v4.uint32 == 0) { |
193 | 7 | if (ioctl(net_socket_to_native(sock), SIOCGIFADDR, &i_faces[i]) < 0) { |
194 | 0 | continue; |
195 | 0 | } |
196 | | |
197 | 7 | const struct sockaddr_in *addr4 = (const struct sockaddr_in *)(void *)&i_faces[i].ifr_addr; |
198 | | |
199 | | |
200 | 7 | IP4 ip4_staging; |
201 | 7 | ip4_staging.uint32 = addr4->sin_addr.s_addr; |
202 | | |
203 | 7 | if (ip4_is_local(&ip4_staging)) { |
204 | | // this is 127.x.x.x |
205 | 7 | ip->ip.v4.uint32 = ip4_staging.uint32; |
206 | 7 | } else { |
207 | | // give up. |
208 | 0 | continue; |
209 | 0 | } |
210 | 7 | } |
211 | | |
212 | 59 | ++broadcast->count; |
213 | 59 | } |
214 | | |
215 | 30 | kill_sock(ns, sock); |
216 | | |
217 | 30 | return broadcast; |
218 | 31 | } |
219 | | |
220 | | #else // TODO(irungentoo): Other platforms? |
221 | | |
222 | | static Broadcast_Info *fetch_broadcast_info(const Memory *_Nonnull mem, const Network *_Nonnull ns) |
223 | 1.37k | { |
224 | 1.37k | Broadcast_Info *broadcast = (Broadcast_Info *)mem_alloc(mem, sizeof(Broadcast_Info)); |
225 | | |
226 | 1.37k | if (broadcast == nullptr) { |
227 | 0 | return nullptr; |
228 | 0 | } |
229 | | |
230 | 1.37k | broadcast->mem = mem; |
231 | | |
232 | 1.37k | return broadcast; |
233 | 1.37k | } |
234 | | |
235 | | #endif /* platforms */ |
236 | | |
237 | | /** @brief Send packet to all IPv4 broadcast addresses |
238 | | * |
239 | | * @retval true if sent to at least one broadcast target. |
240 | | * @retval false on failure to find any valid broadcast target. |
241 | | */ |
242 | | static bool send_broadcasts(const Networking_Core *_Nonnull net, const Broadcast_Info *_Nonnull broadcast, uint16_t port, const uint8_t *_Nonnull data, uint16_t length) |
243 | 198 | { |
244 | 198 | if (broadcast->count == 0) { |
245 | 0 | return false; |
246 | 0 | } |
247 | | |
248 | 594 | for (uint32_t i = 0; i < broadcast->count; ++i) { |
249 | 396 | IP_Port ip_port; |
250 | 396 | ip_port.ip = broadcast->ips[i]; |
251 | 396 | ip_port.port = port; |
252 | 396 | sendpacket(net, &ip_port, data, length); |
253 | 396 | } |
254 | | |
255 | 198 | return true; |
256 | 198 | } |
257 | | |
258 | | /** Return the broadcast ip. */ |
259 | | static IP broadcast_ip(Family family_socket, Family family_broadcast) |
260 | 198 | { |
261 | 198 | IP ip; |
262 | 198 | ip_reset(&ip); |
263 | | |
264 | 198 | if (net_family_is_ipv6(family_socket)) { |
265 | 0 | if (net_family_is_ipv6(family_broadcast)) { |
266 | 0 | ip.family = net_family_ipv6(); |
267 | | /* `FF02::1` is - according to RFC 4291 - multicast all-nodes link-local */ |
268 | | /* `FE80::*:` MUST be exact, for that we would need to look over all |
269 | | * interfaces and check in which status they are */ |
270 | 0 | ip.ip.v6.uint8[0] = 0xFF; |
271 | 0 | ip.ip.v6.uint8[1] = 0x02; |
272 | 0 | ip.ip.v6.uint8[15] = 0x01; |
273 | 0 | } else if (net_family_is_ipv4(family_broadcast)) { |
274 | 0 | ip.family = net_family_ipv6(); |
275 | 0 | ip.ip.v6 = get_ip6_broadcast(); |
276 | 0 | } |
277 | 198 | } else if (net_family_is_ipv4(family_socket) && net_family_is_ipv4(family_broadcast)) { |
278 | 198 | ip.family = net_family_ipv4(); |
279 | 198 | ip.ip.v4 = get_ip4_broadcast(); |
280 | 198 | } |
281 | | |
282 | 198 | return ip; |
283 | 198 | } |
284 | | |
285 | | static bool ip4_is_local(const IP4 *_Nonnull ip4) |
286 | 4.69M | { |
287 | | /* Loopback. */ |
288 | 4.69M | return ip4->uint8[0] == 127; |
289 | 4.69M | } |
290 | | |
291 | | /** |
292 | | * Is IP a local ip or not. |
293 | | */ |
294 | | bool ip_is_local(const IP *ip) |
295 | 4.70M | { |
296 | 4.70M | if (net_family_is_ipv4(ip->family)) { |
297 | 4.69M | return ip4_is_local(&ip->ip.v4); |
298 | 4.69M | } |
299 | | |
300 | | /* embedded IPv4-in-IPv6 */ |
301 | 11.7k | if (ipv6_ipv4_in_v6(&ip->ip.v6)) { |
302 | 0 | IP4 ip4; |
303 | 0 | ip4.uint32 = ip->ip.v6.uint32[3]; |
304 | 0 | return ip4_is_local(&ip4); |
305 | 0 | } |
306 | | |
307 | | /* localhost in IPv6 (::1) */ |
308 | 11.7k | return ip->ip.v6.uint64[0] == 0 && ip->ip.v6.uint32[2] == 0 && ip->ip.v6.uint32[3] == net_htonl(1); |
309 | 11.7k | } |
310 | | |
311 | | static bool ip4_is_lan(const IP4 *_Nonnull ip4) |
312 | 116 | { |
313 | | /* 10.0.0.0 to 10.255.255.255 range. */ |
314 | 116 | if (ip4->uint8[0] == 10) { |
315 | 0 | return true; |
316 | 0 | } |
317 | | |
318 | | /* 172.16.0.0 to 172.31.255.255 range. */ |
319 | 116 | if (ip4->uint8[0] == 172 && ip4->uint8[1] >= 16 && ip4->uint8[1] <= 31) { |
320 | 108 | return true; |
321 | 108 | } |
322 | | |
323 | | /* 192.168.0.0 to 192.168.255.255 range. */ |
324 | 8 | if (ip4->uint8[0] == 192 && ip4->uint8[1] == 168) { |
325 | 0 | return true; |
326 | 0 | } |
327 | | |
328 | | /* 169.254.1.0 to 169.254.254.255 range. */ |
329 | 8 | if (ip4->uint8[0] == 169 && ip4->uint8[1] == 254 && ip4->uint8[2] != 0 |
330 | 8 | && ip4->uint8[2] != 255) { |
331 | 0 | return true; |
332 | 0 | } |
333 | | |
334 | | /* RFC 6598: 100.64.0.0 to 100.127.255.255 (100.64.0.0/10) |
335 | | * (shared address space to stack another layer of NAT) */ |
336 | 8 | return (ip4->uint8[0] == 100) && ((ip4->uint8[1] & 0xC0) == 0x40); |
337 | 8 | } |
338 | | |
339 | | bool ip_is_lan(const IP *ip) |
340 | 4.70M | { |
341 | 4.70M | if (ip_is_local(ip)) { |
342 | 4.69M | return true; |
343 | 4.69M | } |
344 | | |
345 | 11.7k | if (net_family_is_ipv4(ip->family)) { |
346 | 116 | return ip4_is_lan(&ip->ip.v4); |
347 | 116 | } |
348 | | |
349 | 11.6k | if (net_family_is_ipv6(ip->family)) { |
350 | | /* autogenerated for each interface: `FE80::*` (up to `FEBF::*`) |
351 | | * `FF02::1` is - according to RFC 4291 - multicast all-nodes link-local */ |
352 | 0 | if (((ip->ip.v6.uint8[0] == 0xFF) && (ip->ip.v6.uint8[1] < 3) && (ip->ip.v6.uint8[15] == 1)) || |
353 | 0 | ((ip->ip.v6.uint8[0] == 0xFE) && ((ip->ip.v6.uint8[1] & 0xC0) == 0x80))) { |
354 | 0 | return true; |
355 | 0 | } |
356 | | |
357 | | /* embedded IPv4-in-IPv6 */ |
358 | 0 | if (ipv6_ipv4_in_v6(&ip->ip.v6)) { |
359 | 0 | IP4 ip4; |
360 | 0 | ip4.uint32 = ip->ip.v6.uint32[3]; |
361 | 0 | return ip4_is_lan(&ip4); |
362 | 0 | } |
363 | 0 | } |
364 | | |
365 | 11.6k | return false; |
366 | 11.6k | } |
367 | | |
368 | | bool lan_discovery_send(const Networking_Core *net, const Broadcast_Info *broadcast, const uint8_t *dht_pk, |
369 | | uint16_t port) |
370 | 198 | { |
371 | 198 | if (broadcast == nullptr) { |
372 | 0 | return false; |
373 | 0 | } |
374 | | |
375 | 198 | uint8_t data[CRYPTO_PUBLIC_KEY_SIZE + 1]; |
376 | 198 | data[0] = NET_PACKET_LAN_DISCOVERY; |
377 | 198 | pk_copy(data + 1, dht_pk); |
378 | | |
379 | 198 | send_broadcasts(net, broadcast, port, data, 1 + CRYPTO_PUBLIC_KEY_SIZE); |
380 | | |
381 | 198 | bool res = false; |
382 | 198 | IP_Port ip_port; |
383 | 198 | ip_port.port = port; |
384 | | |
385 | | /* IPv6 multicast */ |
386 | 198 | if (net_family_is_ipv6(net_family(net))) { |
387 | 0 | ip_port.ip = broadcast_ip(net_family_ipv6(), net_family_ipv6()); |
388 | |
|
389 | 0 | if (ip_isset(&ip_port.ip) && sendpacket(net, &ip_port, data, 1 + CRYPTO_PUBLIC_KEY_SIZE) > 0) { |
390 | 0 | res = true; |
391 | 0 | } |
392 | 0 | } |
393 | | |
394 | | /* IPv4 broadcast (has to be IPv4-in-IPv6 mapping if socket is IPv6 */ |
395 | 198 | ip_port.ip = broadcast_ip(net_family(net), net_family_ipv4()); |
396 | | |
397 | 198 | if (ip_isset(&ip_port.ip) && sendpacket(net, &ip_port, data, 1 + CRYPTO_PUBLIC_KEY_SIZE) > 0) { |
398 | 198 | res = true; |
399 | 198 | } |
400 | | |
401 | 198 | return res; |
402 | 198 | } |
403 | | |
404 | | Broadcast_Info *lan_discovery_init(const Memory *mem, const Network *ns) |
405 | 1.41k | { |
406 | 1.41k | return fetch_broadcast_info(mem, ns); |
407 | 1.41k | } |
408 | | |
409 | | void lan_discovery_kill(Broadcast_Info *broadcast) |
410 | 1.90k | { |
411 | 1.90k | if (broadcast == nullptr) { |
412 | 503 | return; |
413 | 503 | } |
414 | | |
415 | 1.40k | mem_delete(broadcast->mem, broadcast); |
416 | 1.40k | } |