ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
ip.c
Go to the documentation of this file.
1
7#include <ascii-chat/util/ip.h>
8#include <ascii-chat/util/parsing.h>
9#include <ascii-chat/common.h>
10#include <ascii-chat/common/buffer_sizes.h>
11#include <ascii-chat/platform/network.h>
12#include <ascii-chat/log/logging.h>
13#include <ascii-chat/util/pcre2.h>
14#include <string.h>
15#include <stdio.h>
16#include <stdlib.h>
17#ifdef _WIN32
18#include <winsock2.h>
19#include <ws2tcpip.h>
20#else
21#include <arpa/inet.h>
22#include <netinet/in.h>
23#endif
24#include <pcre2.h>
25
26// ============================================================================
27// PCRE2 IPv4 Address Validator
28// ============================================================================
29
39// Regex pattern validates IPv4 format:
40// - Each octet: 0-255 without leading zeros
41// - Exactly 4 octets separated by dots
42static const char *IPV4_PATTERN = "^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\\.){3}"
43 "(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])$";
44
45static pcre2_singleton_t *g_ipv4_regex = NULL;
46
50static pcre2_code *ipv4_regex_get(void) {
51 if (g_ipv4_regex == NULL) {
52 g_ipv4_regex = asciichat_pcre2_singleton_compile(IPV4_PATTERN, 0);
53 }
54 return asciichat_pcre2_singleton_get_code(g_ipv4_regex);
55}
56
57// Helper function to validate IPv4 address format
58int is_valid_ipv4(const char *ip) {
59 if (!ip) {
60 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid IP format: NULL");
61 return 0; // Invalid
62 }
63
64 size_t ip_len = strlen(ip);
65 if (ip_len == 0) {
66 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid IP format: empty string");
67 return 0; // Invalid
68 }
69
70 if (ip_len > 256) {
71 char ip_buffer[BUFFER_SIZE_SMALL];
72 SAFE_STRNCPY(ip_buffer, ip, sizeof(ip_buffer));
73 SET_ERRNO(ERROR_INVALID_PARAM, "Suspiciously long ip: %s", ip_buffer);
74 return 0; // Invalid
75 }
76
77 // Get compiled IPv4 regex
78 pcre2_code *regex = ipv4_regex_get();
79 if (!regex) {
80 log_error("IPv4 validator not initialized");
81 return 0;
82 }
83
84 // Create match data for regex matching
85 pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(regex, NULL);
86 if (!match_data) {
87 log_error("Failed to allocate match data for IPv4 regex");
88 return 0;
89 }
90
91 // Validate format using PCRE2 regex
92 int match_result = pcre2_match(regex, (PCRE2_SPTR8)ip, ip_len, 0, 0, match_data, NULL);
93
94 pcre2_match_data_free(match_data);
95
96 if (match_result < 0) {
97 return 0; // Format validation failed
98 }
99
100 return 1; // Valid IPv4 format
101}
102
103// Helper function to validate IPv6 address format
104// Uses POSIX inet_pton() instead of manual parsing - more robust and battle-tested
105int is_valid_ipv6(const char *ip) {
106 if (!ip) {
107 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid IP format: NULL");
108 return 0; // Invalid
109 }
110
111 size_t ip_len = strlen(ip);
112 if (ip_len == 0) {
113 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid IP format: empty string");
114 return 0; // Invalid
115 }
116
117 if (ip_len > 256) {
118 char ip_buffer[BUFFER_SIZE_SMALL];
119 SAFE_STRNCPY(ip_buffer, ip, sizeof(ip_buffer));
120 SET_ERRNO(ERROR_INVALID_PARAM, "Suspiciously long ip: %s", ip_buffer);
121 return 0; // Invalid
122 }
123
124 // Remove brackets if present (IPv6 addresses can be bracketed)
125 char normalized[INET6_ADDRSTRLEN];
126 const char *ip_to_validate = ip;
127
128 if (ip[0] == '[') {
129 // Has brackets, need to remove them
130 if (parse_ipv6_address(ip, normalized, sizeof(normalized)) != 0) {
131 return 0; // Failed to parse (malformed brackets)
132 }
133 ip_to_validate = normalized;
134 }
135
136 // Use POSIX inet_pton() for robust IPv6 validation
137 // It handles all RFC 5952 compliant formats:
138 // - Full format: 2001:0db8:85a3:0000:0000:8a2e:0370:7334
139 // - Compressed: 2001:db8:85a3::8a2e:370:7334
140 // - Loopback: ::1
141 // - All zeros: ::
142 // - IPv4-mapped: ::ffff:192.0.2.1
143 struct in6_addr addr;
144 int result = inet_pton(AF_INET6, ip_to_validate, &addr);
145
146 if (result == 1) {
147 return 1; // Valid IPv6
148 } else if (result == 0) {
149 return 0; // Not a valid IPv6 address
150 } else {
151 // result < 0 means system error (e.g., unsupported address family)
152 SET_ERRNO(ERROR_INVALID_PARAM, "Failed to validate IPv6 address");
153 return 0;
154 }
155}
156
157// Parse IPv6 address, removing brackets if present
158int parse_ipv6_address(const char *input, char *output, size_t output_size) {
159 if (!input || !output || output_size == 0)
160 return -1;
161
162 size_t input_len = strlen(input);
163 if (input_len == 0)
164 return -1; // Empty string
165
166 const char *start = input;
167 const char *end = input + input_len;
168
169 // Remove brackets if present - but brackets must be matched
170 if (*start == '[') {
171 if (input_len < 2 || *(end - 1) != ']')
172 return -1; // Malformed brackets
173 start++;
174 end--;
175
176 // Check for double brackets [[...]]
177 if (start < end && *start == '[')
178 return -1; // Double opening bracket
179 } else if (*(end - 1) == ']') {
180 // Closing bracket without opening bracket
181 return -1;
182 }
183
184 size_t len = (size_t)(end - start);
185 if (len == 0)
186 return -1; // Empty content after removing brackets
187 if (len >= output_size)
188 return -1; // Buffer too small
189
190 memcpy(output, start, len);
191 output[len] = '\0';
192 return 0;
193}
194
195// Format IP address from socket address structure
196asciichat_error_t format_ip_address(int family, const struct sockaddr *addr, char *output, size_t output_size) {
197 if (!addr || !output || output_size == 0) {
198 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid arguments to format_ip_address");
199 }
200
201 const void *ip_ptr = NULL;
202
203 if (family == AF_INET) {
204 const struct sockaddr_in *addr_in = (const struct sockaddr_in *)addr;
205 ip_ptr = &addr_in->sin_addr;
206 } else if (family == AF_INET6) {
207 const struct sockaddr_in6 *addr_in6 = (const struct sockaddr_in6 *)addr;
208 ip_ptr = &addr_in6->sin6_addr;
209 } else {
210 return SET_ERRNO(ERROR_INVALID_PARAM, "Unsupported address family: %d", family);
211 }
212
213 if (inet_ntop(family, ip_ptr, output, (socklen_t)output_size) == NULL) {
214 return SET_ERRNO(ERROR_NETWORK, "Failed to format IP address");
215 }
216
217 return ASCIICHAT_OK;
218}
219
220// Format IP address with port number
221asciichat_error_t format_ip_with_port(const char *ip, uint16_t port, char *output, size_t output_size) {
222 if (!ip || !output || output_size == 0) {
223 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid IP format: %s", ip);
224 }
225
226 if (strlen(ip) == 0) {
227 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid IP format: %s", ip);
228 }
229
230 if (strlen(ip) > 256) {
231 char ip_buffer[BUFFER_SIZE_SMALL];
232 SAFE_STRNCPY(ip_buffer, ip, sizeof(ip_buffer));
233 return SET_ERRNO(ERROR_INVALID_PARAM, "Suspiciously long ip: %s", ip_buffer);
234 }
235
236 // Check if it's IPv6 (contains ':')
237 if (strchr(ip, ':') != NULL) {
238 // IPv6 - use bracket notation
239 size_t written = SAFE_SNPRINTF(output, output_size, "[%s]:%u", ip, port);
240 if (written >= output_size) {
241 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid ip=%s or port=%u", ip, port);
242 }
243 } else {
244 // IPv4 - no brackets
245 size_t written = SAFE_SNPRINTF(output, output_size, "%s:%u", ip, port);
246 if (written >= output_size) {
247 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid ip=%s or port=%u", ip, port);
248 }
249 }
250
251 return ASCIICHAT_OK;
252}
253
254// Helper function to validate hostname format
255// Returns 1 if valid hostname, 0 otherwise
256static int is_valid_hostname(const char *hostname) {
257 if (!hostname || strlen(hostname) == 0 || strlen(hostname) > 253) {
258 return 0;
259 }
260
261 // Hostname must not start or end with hyphen or dot
262 size_t len = strlen(hostname);
263 if (hostname[0] == '-' || hostname[0] == '.' || hostname[len - 1] == '-' || hostname[len - 1] == '.') {
264 return 0;
265 }
266
267 // Check each character: alphanumeric, hyphen, or dot
268 int label_len = 0;
269 for (size_t i = 0; i < len; i++) {
270 char c = hostname[i];
271 if (c == '.') {
272 // Label cannot be empty or exceed 63 characters
273 if (label_len == 0 || label_len > 63) {
274 return 0;
275 }
276 label_len = 0;
277 } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-') {
278 label_len++;
279 if (label_len > 63) {
280 return 0;
281 }
282 } else {
283 return 0; // Invalid character
284 }
285 }
286
287 // Check last label
288 if (label_len == 0 || label_len > 63) {
289 return 0;
290 }
291
292 return 1;
293}
294
295// Parse IP address and port from string
296int parse_ip_with_port(const char *input, char *ip_output, size_t ip_output_size, uint16_t *port_output) {
297 if (!input || !ip_output || !port_output || ip_output_size == 0) {
298 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid IP format: %s", input);
299 return -1;
300 }
301
302 if (strlen(input) == 0) {
303 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid IP format: %s", input);
304 return -1;
305 }
306
307 if (strlen(input) > 256) {
308 char input_buffer[BUFFER_SIZE_SMALL];
309 SAFE_STRNCPY(input_buffer, input, sizeof(input_buffer));
310 SET_ERRNO(ERROR_INVALID_PARAM, "Suspiciously long ip: %s", input_buffer);
311 return -1;
312 }
313
314 // Check for IPv6 bracket notation: [2001:db8::1]:8080
315 if (input[0] == '[') {
316 // Find closing bracket
317 const char *bracket_end = strchr(input, ']');
318 if (!bracket_end)
319 return -1; // Malformed - opening bracket but no closing bracket
320
321 // Extract IP address (without brackets)
322 size_t ip_len = (size_t)(bracket_end - input - 1);
323 if (ip_len >= ip_output_size)
324 return -1; // IP too long
325
326 memcpy(ip_output, input + 1, ip_len);
327 ip_output[ip_len] = '\0';
328
329 // Check for port after closing bracket
330 if (bracket_end[1] == ':') {
331 // Parse port using safe integer parsing
332 if (parse_port(bracket_end + 2, port_output) != ASCIICHAT_OK) {
333 return -1; // Invalid port
334 }
335 } else {
336 return -1; // Expected ':' after closing bracket
337 }
338 } else {
339 // IPv4 or hostname: "192.0.2.1:8080" or "example.com:8080"
340 // Find last ':' for port separation
341 const char *colon = strrchr(input, ':');
342 if (!colon)
343 return -1; // No port separator
344
345 // Extract IP/hostname
346 size_t ip_len = (size_t)(colon - input);
347 if (ip_len >= ip_output_size)
348 return -1; // IP too long
349
350 memcpy(ip_output, input, ip_len);
351 ip_output[ip_len] = '\0';
352
353 // Reject IPv6 without brackets (check if extracted IP contains ':')
354 // This catches cases like "::1:8080" which should be "[::1]:8080"
355 if (strchr(ip_output, ':') != NULL)
356 return -1; // IPv6 address without brackets - invalid format
357
358 // Parse port using safe integer parsing
359 if (parse_port(colon + 1, port_output) != ASCIICHAT_OK) {
360 return -1; // Invalid port
361 }
362 }
363
364 return 0;
365}
366
367// Parse address with optional port from string
368// Handles IPv4, IPv6 (with or without brackets), and hostnames
369// If no port is specified, uses default_port
370int parse_address_with_optional_port(const char *input, char *address_output, size_t address_output_size,
371 uint16_t *port_output, uint16_t default_port) {
372 if (!input || !address_output || !port_output || address_output_size == 0) {
373 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid arguments to parse_address_with_optional_port");
374 return -1;
375 }
376
377 size_t input_len = strlen(input);
378 if (input_len == 0) {
379 SET_ERRNO(ERROR_INVALID_PARAM, "Empty address string");
380 return -1;
381 }
382
383 if (input_len > 256) {
384 SET_ERRNO(ERROR_INVALID_PARAM, "Address string too long");
385 return -1;
386 }
387
388 // Case 1: Bracketed IPv6 address with or without port
389 // Examples: "[::1]" or "[::1]:8080" or "[2001:db8::1]:443"
390 if (input[0] == '[') {
391 const char *bracket_end = strchr(input, ']');
392 if (!bracket_end) {
393 SET_ERRNO(ERROR_INVALID_PARAM, "Malformed bracketed address (missing ']'): %s", input);
394 return -1;
395 }
396
397 // Extract address without brackets
398 size_t addr_len = (size_t)(bracket_end - input - 1);
399 if (addr_len == 0) {
400 SET_ERRNO(ERROR_INVALID_PARAM, "Empty bracketed address: %s", input);
401 return -1;
402 }
403 if (addr_len >= address_output_size) {
404 SET_ERRNO(ERROR_INVALID_PARAM, "Address too long for buffer");
405 return -1;
406 }
407
408 memcpy(address_output, input + 1, addr_len);
409 address_output[addr_len] = '\0';
410
411 // Validate it's a valid IPv6 address
412 if (!is_valid_ipv6(address_output)) {
413 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid IPv6 address: %s", address_output);
414 return -1;
415 }
416
417 // Check for port after closing bracket
418 if (bracket_end[1] == '\0') {
419 // No port specified, use default
420 *port_output = default_port;
421 } else if (bracket_end[1] == ':') {
422 // Port specified
423 if (parse_port(bracket_end + 2, port_output) != ASCIICHAT_OK) {
424 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid port in: %s", input);
425 return -1;
426 }
427 } else {
428 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid format after ']' (expected ':' or end): %s", input);
429 return -1;
430 }
431 return 0;
432 }
433
434 // Case 2: Check if it's a valid IPv6 address without brackets (no port allowed in this case)
435 // Examples: "::1" or "2001:db8::1" or "fe80::1"
436 // Note: IPv6 addresses contain colons, so we can't use ':' as port separator without brackets
437 if (is_valid_ipv6(input)) {
438 if (strlen(input) >= address_output_size) {
439 SET_ERRNO(ERROR_INVALID_PARAM, "Address too long for buffer");
440 return -1;
441 }
442 SAFE_STRNCPY(address_output, input, address_output_size);
443 *port_output = default_port;
444 return 0;
445 }
446
447 // Case 3: IPv4 or hostname, possibly with port
448 // Examples: "192.168.1.1" or "192.168.1.1:8080" or "localhost" or "example.com:443"
449 const char *last_colon = strrchr(input, ':');
450
451 if (last_colon == NULL) {
452 // No colon - just address/hostname, no port
453 if (strlen(input) >= address_output_size) {
454 SET_ERRNO(ERROR_INVALID_PARAM, "Address too long for buffer");
455 return -1;
456 }
457
458 // Validate it's a valid IPv4 or hostname
459 if (!is_valid_ipv4(input) && !is_valid_hostname(input)) {
460 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid address format: %s", input);
461 return -1;
462 }
463
464 SAFE_STRNCPY(address_output, input, address_output_size);
465 *port_output = default_port;
466 return 0;
467 }
468
469 // Has colon - extract address and port
470 size_t addr_len = (size_t)(last_colon - input);
471 if (addr_len == 0) {
472 SET_ERRNO(ERROR_INVALID_PARAM, "Empty address before port: %s", input);
473 return -1;
474 }
475 if (addr_len >= address_output_size) {
476 SET_ERRNO(ERROR_INVALID_PARAM, "Address too long for buffer");
477 return -1;
478 }
479
480 memcpy(address_output, input, addr_len);
481 address_output[addr_len] = '\0';
482
483 // Check if extracted address contains ':' - that would be IPv6 without brackets (invalid with port)
484 if (strchr(address_output, ':') != NULL) {
485 SET_ERRNO(ERROR_INVALID_PARAM, "IPv6 addresses must use brackets when specifying port: [%s]:%s instead of %s",
486 address_output, last_colon + 1, input);
487 return -1;
488 }
489
490 // Validate address part is valid IPv4 or hostname
491 if (!is_valid_ipv4(address_output) && !is_valid_hostname(address_output)) {
492 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid address format: %s", address_output);
493 return -1;
494 }
495
496 // Parse port
497 if (parse_port(last_colon + 1, port_output) != ASCIICHAT_OK) {
498 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid port in: %s", input);
499 return -1;
500 }
501
502 return 0;
503}
504
505// ============================================================================
506// IP Version Detection & Unified Validation
507// ============================================================================
508
509// Get IP address version
510int get_ip_version(const char *ip) {
511 if (!ip || strlen(ip) == 0) {
512 return 0;
513 }
514
515 // Quick heuristic: if it contains a colon, it's likely IPv6
516 // (unless it's a port separator, but we're just checking the IP part)
517 if (strchr(ip, ':') != NULL) {
518 // Could be IPv6 or bracketed IPv6
519 if (is_valid_ipv6(ip)) {
520 return 6;
521 }
522 return 0; // Has colon but not valid IPv6
523 }
524
525 // No colon, check if it's IPv4
526 if (is_valid_ipv4(ip)) {
527 return 4;
528 }
529
530 return 0; // Not valid IPv4 or IPv6
531}
532
533// Check if string is a valid IP address (IPv4 or IPv6)
534int is_valid_ip(const char *ip) {
535 if (!ip) {
536 return 0;
537 }
538
539 // Try IPv4 first (faster check)
540 if (is_valid_ipv4(ip)) {
541 return 1;
542 }
543
544 // Try IPv6
545 if (is_valid_ipv6(ip)) {
546 return 1;
547 }
548
549 return 0;
550}
551
552// ============================================================================
553// IP Address Comparison & Equality
554// ============================================================================
555
556// Compare two IP addresses for equality
557int ip_equals(const char *ip1, const char *ip2) {
558 if (!ip1 || !ip2) {
559 return 0;
560 }
561
562 // Get versions
563 int v1 = get_ip_version(ip1);
564 int v2 = get_ip_version(ip2);
565
566 // If different versions or either invalid, not equal
567 if (v1 != v2 || v1 == 0) {
568 return 0;
569 }
570
571 if (v1 == 4) {
572 // IPv4: direct string comparison after validation
573 struct in_addr addr1, addr2;
574 if (inet_pton(AF_INET, ip1, &addr1) != 1 || inet_pton(AF_INET, ip2, &addr2) != 1) {
575 return 0;
576 }
577 return addr1.s_addr == addr2.s_addr;
578 } else {
579 // IPv6: normalize then compare
580 char norm1[INET6_ADDRSTRLEN], norm2[INET6_ADDRSTRLEN];
581
582 // Remove brackets if present
583 if (parse_ipv6_address(ip1, norm1, sizeof(norm1)) != 0) {
584 return 0;
585 }
586 if (parse_ipv6_address(ip2, norm2, sizeof(norm2)) != 0) {
587 return 0;
588 }
589
590 // Parse to binary and compare
591 struct in6_addr addr1, addr2;
592 if (inet_pton(AF_INET6, norm1, &addr1) != 1 || inet_pton(AF_INET6, norm2, &addr2) != 1) {
593 return 0;
594 }
595
596 return memcmp(&addr1, &addr2, sizeof(addr1)) == 0;
597 }
598}
599
600// Compare two IP addresses for sorting
601int ip_compare(const char *ip1, const char *ip2) {
602 if (!ip1 || !ip2) {
603 return -2; // Error
604 }
605
606 int v1 = get_ip_version(ip1);
607 int v2 = get_ip_version(ip2);
608
609 // Invalid addresses sort last
610 if (v1 == 0 && v2 == 0) {
611 return 0; // Both invalid, consider equal
612 }
613 if (v1 == 0) {
614 return 1; // ip1 invalid, sorts after ip2
615 }
616 if (v2 == 0) {
617 return -1; // ip2 invalid, sorts after ip1
618 }
619
620 // IPv4 sorts before IPv6
621 if (v1 != v2) {
622 return (v1 < v2) ? -1 : 1;
623 }
624
625 if (v1 == 4) {
626 // IPv4: compare numerically
627 struct in_addr addr1, addr2;
628 if (inet_pton(AF_INET, ip1, &addr1) != 1 || inet_pton(AF_INET, ip2, &addr2) != 1) {
629 return -2; // Error
630 }
631
632 uint32_t val1 = ntohl(addr1.s_addr);
633 uint32_t val2 = ntohl(addr2.s_addr);
634
635 if (val1 < val2)
636 return -1;
637 if (val1 > val2)
638 return 1;
639 return 0;
640 } else {
641 // IPv6: compare byte by byte
642 char norm1[INET6_ADDRSTRLEN], norm2[INET6_ADDRSTRLEN];
643
644 if (parse_ipv6_address(ip1, norm1, sizeof(norm1)) != 0) {
645 return -2;
646 }
647 if (parse_ipv6_address(ip2, norm2, sizeof(norm2)) != 0) {
648 return -2;
649 }
650
651 struct in6_addr addr1, addr2;
652 if (inet_pton(AF_INET6, norm1, &addr1) != 1 || inet_pton(AF_INET6, norm2, &addr2) != 1) {
653 return -2;
654 }
655
656 return memcmp(&addr1, &addr2, sizeof(addr1));
657 }
658}
659
660// ============================================================================
661// CIDR/Subnet Utilities
662// ============================================================================
663
664// Parse CIDR notation into IP and prefix length
665int parse_cidr(const char *cidr, char *ip_out, size_t ip_out_size, int *prefix_out) {
666 if (!cidr || !ip_out || !prefix_out || ip_out_size == 0) {
667 return -1;
668 }
669
670 // Find the '/' separator
671 const char *slash = strchr(cidr, '/');
672 if (!slash) {
673 return -1; // No slash found
674 }
675
676 // Extract IP part
677 size_t ip_len = (size_t)(slash - cidr);
678 if (ip_len == 0 || ip_len >= ip_out_size) {
679 return -1;
680 }
681
682 char ip_temp[256];
683 if (ip_len >= sizeof(ip_temp)) {
684 return -1;
685 }
686
687 memcpy(ip_temp, cidr, ip_len);
688 ip_temp[ip_len] = '\0';
689
690 // Remove brackets if IPv6
691 if (ip_temp[0] == '[') {
692 if (parse_ipv6_address(ip_temp, ip_out, ip_out_size) != 0) {
693 return -1;
694 }
695 } else {
696 if (ip_len >= ip_out_size) {
697 return -1;
698 }
699 memcpy(ip_out, ip_temp, ip_len + 1);
700 }
701
702 // Validate IP
703 int version = get_ip_version(ip_out);
704 if (version == 0) {
705 return -1; // Invalid IP
706 }
707
708 // Parse prefix length
709 const char *prefix_str = slash + 1;
710 if (strlen(prefix_str) == 0) {
711 return -1;
712 }
713
714 char *endptr;
715 long prefix = strtol(prefix_str, &endptr, 10);
716 if (*endptr != '\0' || prefix < 0) {
717 return -1; // Invalid prefix
718 }
719
720 // Validate prefix range based on IP version
721 if (version == 4 && (prefix > 32)) {
722 return -1;
723 }
724 if (version == 6 && (prefix > 128)) {
725 return -1;
726 }
727
728 *prefix_out = (int)prefix;
729 return 0;
730}
731
732// Helper: Apply netmask to IPv4 address
733static uint32_t apply_ipv4_mask(uint32_t ip, int prefix_len) {
734 if (prefix_len == 0) {
735 return 0;
736 }
737 if (prefix_len >= 32) {
738 return ip;
739 }
740 uint32_t mask = ~((1U << (32 - prefix_len)) - 1);
741 return ip & mask;
742}
743
744// Helper: Apply netmask to IPv6 address
745static void apply_ipv6_mask(struct in6_addr *addr, int prefix_len) {
746 if (prefix_len < 0 || prefix_len > 128) {
747 return;
748 }
749
750 int bytes = prefix_len / 8;
751 int bits = prefix_len % 8;
752
753 // Zero out bytes after the prefix
754 for (int i = bytes + (bits > 0 ? 1 : 0); i < 16; i++) {
755 addr->s6_addr[i] = 0;
756 }
757
758 // Mask the partial byte
759 if (bits > 0 && bytes < 16) {
760 uint8_t mask = (uint8_t)(0xFF << (8 - bits));
761 addr->s6_addr[bytes] &= mask;
762 }
763}
764
765// Check if IP is within CIDR range (parsed form)
766int ip_in_cidr_parsed(const char *ip, const char *network, int prefix_len) {
767 if (!ip || !network) {
768 return 0;
769 }
770
771 int ip_version = get_ip_version(ip);
772 int net_version = get_ip_version(network);
773
774 // Must be same version
775 if (ip_version != net_version || ip_version == 0) {
776 return 0;
777 }
778
779 if (ip_version == 4) {
780 // IPv4 comparison
781 struct in_addr ip_addr, net_addr;
782 if (inet_pton(AF_INET, ip, &ip_addr) != 1 || inet_pton(AF_INET, network, &net_addr) != 1) {
783 return 0;
784 }
785
786 uint32_t ip_val = ntohl(ip_addr.s_addr);
787 uint32_t net_val = ntohl(net_addr.s_addr);
788
789 uint32_t ip_masked = apply_ipv4_mask(ip_val, prefix_len);
790 uint32_t net_masked = apply_ipv4_mask(net_val, prefix_len);
791
792 return ip_masked == net_masked;
793 } else {
794 // IPv6 comparison
795 char ip_norm[INET6_ADDRSTRLEN], net_norm[INET6_ADDRSTRLEN];
796
797 if (parse_ipv6_address(ip, ip_norm, sizeof(ip_norm)) != 0) {
798 return 0;
799 }
800 if (parse_ipv6_address(network, net_norm, sizeof(net_norm)) != 0) {
801 return 0;
802 }
803
804 struct in6_addr ip_addr, net_addr;
805 if (inet_pton(AF_INET6, ip_norm, &ip_addr) != 1 || inet_pton(AF_INET6, net_norm, &net_addr) != 1) {
806 return 0;
807 }
808
809 // Apply mask to both addresses
810 struct in6_addr ip_masked = ip_addr;
811 struct in6_addr net_masked = net_addr;
812 apply_ipv6_mask(&ip_masked, prefix_len);
813 apply_ipv6_mask(&net_masked, prefix_len);
814
815 return memcmp(&ip_masked, &net_masked, sizeof(ip_masked)) == 0;
816 }
817}
818
819// Check if IP address is within CIDR range
820int ip_in_cidr(const char *ip, const char *cidr) {
821 if (!ip || !cidr) {
822 return 0;
823 }
824
825 char network[64];
826 int prefix;
827
828 if (parse_cidr(cidr, network, sizeof(network), &prefix) != 0) {
829 return 0; // Invalid CIDR
830 }
831
832 return ip_in_cidr_parsed(ip, network, prefix);
833}
834
835// ============================================================================
836// IPv4-Mapped IPv6 Utilities
837// ============================================================================
838
839// Convert IPv4 address to IPv6-mapped format
840asciichat_error_t ipv4_to_ipv6_mapped(const char *ipv4, char *ipv6_out, size_t out_size) {
841 if (!ipv4 || !ipv6_out || out_size == 0) {
842 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid arguments to ipv4_to_ipv6_mapped");
843 }
844
845 // Validate IPv4
846 if (!is_valid_ipv4(ipv4)) {
847 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid IPv4 address: %s", ipv4);
848 }
849
850 // Format as ::ffff:x.x.x.x
851 size_t written = SAFE_SNPRINTF(ipv6_out, out_size, "::ffff:%s", ipv4);
852 if (written >= out_size) {
853 return SET_ERRNO(ERROR_INVALID_PARAM, "Output buffer too small");
854 }
855
856 return ASCIICHAT_OK;
857}
858
859// Check if IPv6 address is IPv4-mapped
860int is_ipv4_mapped_ipv6(const char *ipv6) {
861 if (!ipv6) {
862 return 0;
863 }
864
865 // Normalize (remove brackets)
866 char normalized[INET6_ADDRSTRLEN];
867 if (parse_ipv6_address(ipv6, normalized, sizeof(normalized)) != 0) {
868 return 0;
869 }
870
871 // Validate IPv6
872 if (!is_valid_ipv6(normalized)) {
873 return 0;
874 }
875
876 struct in6_addr addr;
877 if (inet_pton(AF_INET6, normalized, &addr) != 1) {
878 return 0;
879 }
880
881 // Check for ::ffff:0:0/96 prefix
882 // First 10 bytes should be 0, next 2 should be 0xff
883 for (int i = 0; i < 10; i++) {
884 if (addr.s6_addr[i] != 0) {
885 return 0;
886 }
887 }
888 if (addr.s6_addr[10] != 0xFF || addr.s6_addr[11] != 0xFF) {
889 return 0;
890 }
891
892 return 1;
893}
894
895// Extract IPv4 address from IPv6-mapped format
896asciichat_error_t extract_ipv4_from_mapped_ipv6(const char *ipv6, char *ipv4_out, size_t out_size) {
897 if (!ipv6 || !ipv4_out || out_size == 0) {
898 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid arguments to extract_ipv4_from_mapped_ipv6");
899 }
900
901 if (!is_ipv4_mapped_ipv6(ipv6)) {
902 return SET_ERRNO(ERROR_INVALID_PARAM, "Not an IPv4-mapped IPv6 address: %s", ipv6);
903 }
904
905 // Normalize
906 char normalized[INET6_ADDRSTRLEN];
907 if (parse_ipv6_address(ipv6, normalized, sizeof(normalized)) != 0) {
908 return SET_ERRNO(ERROR_INVALID_PARAM, "Failed to parse IPv6 address");
909 }
910
911 struct in6_addr addr;
912 if (inet_pton(AF_INET6, normalized, &addr) != 1) {
913 return SET_ERRNO(ERROR_INVALID_PARAM, "Failed to convert IPv6 address");
914 }
915
916 // Extract last 4 bytes and format as IPv4
917 struct in_addr ipv4_addr;
918 memcpy(&ipv4_addr.s_addr, &addr.s6_addr[12], 4);
919
920 if (inet_ntop(AF_INET, &ipv4_addr, ipv4_out, (socklen_t)out_size) == NULL) {
921 return SET_ERRNO(ERROR_NETWORK, "Failed to format IPv4 address");
922 }
923
924 return ASCIICHAT_OK;
925}
926
927// ============================================================================
928// IPv6 Canonicalization & Formatting
929// ============================================================================
930
931// Expand IPv6 address to full form
932asciichat_error_t expand_ipv6(const char *ipv6, char *expanded_out, size_t out_size) {
933 if (!ipv6 || !expanded_out || out_size == 0) {
934 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid arguments to expand_ipv6");
935 }
936
937 // Normalize (remove brackets)
938 char normalized[INET6_ADDRSTRLEN];
939 if (parse_ipv6_address(ipv6, normalized, sizeof(normalized)) != 0) {
940 return SET_ERRNO(ERROR_INVALID_PARAM, "Failed to parse IPv6 address");
941 }
942
943 // Validate
944 if (!is_valid_ipv6(normalized)) {
945 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid IPv6 address: %s", ipv6);
946 }
947
948 struct in6_addr addr;
949 if (inet_pton(AF_INET6, normalized, &addr) != 1) {
950 return SET_ERRNO(ERROR_INVALID_PARAM, "Failed to convert IPv6 address");
951 }
952
953 // Format as full expanded form: xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx
954 size_t written =
955 SAFE_SNPRINTF(expanded_out, out_size, "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x",
956 (addr.s6_addr[0] << 8) | addr.s6_addr[1], (addr.s6_addr[2] << 8) | addr.s6_addr[3],
957 (addr.s6_addr[4] << 8) | addr.s6_addr[5], (addr.s6_addr[6] << 8) | addr.s6_addr[7],
958 (addr.s6_addr[8] << 8) | addr.s6_addr[9], (addr.s6_addr[10] << 8) | addr.s6_addr[11],
959 (addr.s6_addr[12] << 8) | addr.s6_addr[13], (addr.s6_addr[14] << 8) | addr.s6_addr[15]);
960
961 if (written >= out_size) {
962 return SET_ERRNO(ERROR_INVALID_PARAM, "Output buffer too small");
963 }
964
965 return ASCIICHAT_OK;
966}
967
968// Canonicalize IPv6 address to standard form
969asciichat_error_t canonicalize_ipv6(const char *ipv6, char *canonical_out, size_t out_size) {
970 if (!ipv6 || !canonical_out || out_size == 0) {
971 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid arguments to canonicalize_ipv6");
972 }
973
974 // Normalize (remove brackets)
975 char normalized[INET6_ADDRSTRLEN];
976 if (parse_ipv6_address(ipv6, normalized, sizeof(normalized)) != 0) {
977 return SET_ERRNO(ERROR_INVALID_PARAM, "Failed to parse IPv6 address");
978 }
979
980 // Validate
981 if (!is_valid_ipv6(normalized)) {
982 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid IPv6 address: %s", ipv6);
983 }
984
985 struct in6_addr addr;
986 if (inet_pton(AF_INET6, normalized, &addr) != 1) {
987 return SET_ERRNO(ERROR_INVALID_PARAM, "Failed to convert IPv6 address");
988 }
989
990 // Use inet_ntop which produces canonical form
991 if (inet_ntop(AF_INET6, &addr, canonical_out, (socklen_t)out_size) == NULL) {
992 return SET_ERRNO(ERROR_NETWORK, "Failed to format IPv6 address");
993 }
994
995 return ASCIICHAT_OK;
996}
997
998// Compress IPv6 address using :: notation (alias for canonicalize)
999asciichat_error_t compact_ipv6(const char *ipv6, char *compact_out, size_t out_size) {
1000 return canonicalize_ipv6(ipv6, compact_out, out_size);
1001}
1002
1003// Check if IPv6 address is anycast
1004int is_anycast_ipv6(const char *ipv6) {
1005 if (!ipv6) {
1006 return 0;
1007 }
1008
1009 // Normalize
1010 char normalized[INET6_ADDRSTRLEN];
1011 if (parse_ipv6_address(ipv6, normalized, sizeof(normalized)) != 0) {
1012 return 0;
1013 }
1014
1015 // Validate
1016 if (!is_valid_ipv6(normalized)) {
1017 return 0;
1018 }
1019
1020 struct in6_addr addr;
1021 if (inet_pton(AF_INET6, normalized, &addr) != 1) {
1022 return 0;
1023 }
1024
1025 // 6to4 relay anycast: 192.88.99.0/24 (mapped to 2002:c058:6301::)
1026 // Check if it matches 2002:c058:6301::
1027 if (addr.s6_addr[0] == 0x20 && addr.s6_addr[1] == 0x02 && addr.s6_addr[2] == 0xc0 && addr.s6_addr[3] == 0x58 &&
1028 addr.s6_addr[4] == 0x63 && addr.s6_addr[5] == 0x01) {
1029 // Check if remaining bytes are zero (anycast)
1030 int is_zero = 1;
1031 for (int i = 6; i < 16; i++) {
1032 if (addr.s6_addr[i] != 0) {
1033 is_zero = 0;
1034 break;
1035 }
1036 }
1037 if (is_zero) {
1038 return 1;
1039 }
1040 }
1041
1042 // Subnet-router anycast: would need subnet context to determine
1043 // We can't detect this without knowing the subnet
1044
1045 return 0;
1046}
1047
1048// ============================================================================
1049// IP Address Classification Functions
1050// ============================================================================
1051
1052// Extract IP address (without port) from formatted address string
1053int extract_ip_from_address(const char *addr_with_port, char *ip_out, size_t ip_out_size) {
1054 if (!addr_with_port || !ip_out || ip_out_size == 0) {
1055 return -1;
1056 }
1057
1058 // Check for IPv6 bracket notation: [2001:db8::1]:27224
1059 if (addr_with_port[0] == '[') {
1060 const char *bracket_end = strchr(addr_with_port, ']');
1061 if (!bracket_end) {
1062 return -1;
1063 }
1064 size_t ip_len = (size_t)(bracket_end - addr_with_port - 1);
1065 if (ip_len >= ip_out_size) {
1066 return -1;
1067 }
1068 memcpy(ip_out, addr_with_port + 1, ip_len);
1069 ip_out[ip_len] = '\0';
1070 return 0;
1071 }
1072
1073 // IPv4: Find last colon and extract IP part
1074 const char *colon = strrchr(addr_with_port, ':');
1075 if (!colon) {
1076 // No port, just copy the whole thing
1077 SAFE_STRNCPY(ip_out, addr_with_port, ip_out_size);
1078 return 0;
1079 }
1080
1081 size_t ip_len = (size_t)(colon - addr_with_port);
1082 if (ip_len >= ip_out_size) {
1083 return -1;
1084 }
1085 memcpy(ip_out, addr_with_port, ip_len);
1086 ip_out[ip_len] = '\0';
1087 return 0;
1088}
1089
1090// Get human-readable IP type string (Localhost/LAN/Internet)
1091const char *get_ip_type_string(const char *ip) {
1092 if (!ip || ip[0] == '\0') {
1093 return "";
1094 }
1095
1096 // Check for wildcard "bind to all interfaces" addresses
1097 // IPv4: 0.0.0.0
1098 if (strcmp(ip, "0.0.0.0") == 0) {
1099 return "All Interfaces";
1100 }
1101
1102 // IPv6: :: (unspecified address)
1103 if (strcmp(ip, "::") == 0) {
1104 return "All Interfaces";
1105 }
1106
1107 // Check IPv4
1108 if (is_localhost_ipv4(ip)) {
1109 return "Localhost";
1110 }
1111 if (is_lan_ipv4(ip)) {
1112 return "LAN";
1113 }
1114 if (is_internet_ipv4(ip)) {
1115 return "Internet";
1116 }
1117
1118 // Check IPv6
1119 if (is_localhost_ipv6(ip)) {
1120 return "Localhost";
1121 }
1122 if (is_lan_ipv6(ip)) {
1123 return "LAN";
1124 }
1125 if (is_internet_ipv6(ip)) {
1126 return "Internet";
1127 }
1128
1129 return "Unknown";
1130}
1131
1132// Compare two "IP:port" strings with IPv6 normalization
1133int compare_ip_port_strings(const char *ip_port1, const char *ip_port2) {
1134 if (!ip_port1 || !ip_port2) {
1135 return 0; // Not equal
1136 }
1137
1138 // Fast path: if strings are identical, no parsing needed
1139 if (strcmp(ip_port1, ip_port2) == 0) {
1140 return 1; // Equal
1141 }
1142
1143 // Parse both IP:port strings
1144 char ip1[64], ip2[64];
1145 uint16_t port1 = 0, port2 = 0;
1146
1147 if (parse_ip_with_port(ip_port1, ip1, sizeof(ip1), &port1) != 0) {
1148 return 0; // Invalid format - not equal
1149 }
1150 if (parse_ip_with_port(ip_port2, ip2, sizeof(ip2), &port2) != 0) {
1151 return 0; // Invalid format - not equal
1152 }
1153
1154 // Ports must match exactly
1155 if (port1 != port2) {
1156 return 0; // Not equal
1157 }
1158
1159 // For IPv6, normalize both addresses before comparison
1160 int version1 = get_ip_version(ip1);
1161 int version2 = get_ip_version(ip2);
1162
1163 // IP versions must match
1164 if (version1 != version2 || version1 == 0) {
1165 return 0; // Not equal
1166 }
1167
1168 if (version1 == 6) {
1169 // Normalize both IPv6 addresses
1170 char canonical1[INET6_ADDRSTRLEN], canonical2[INET6_ADDRSTRLEN];
1171 if (canonicalize_ipv6(ip1, canonical1, sizeof(canonical1)) != ASCIICHAT_OK) {
1172 return 0; // Failed to normalize - not equal
1173 }
1174 if (canonicalize_ipv6(ip2, canonical2, sizeof(canonical2)) != ASCIICHAT_OK) {
1175 return 0; // Failed to normalize - not equal
1176 }
1177 return (strcmp(canonical1, canonical2) == 0) ? 1 : 0;
1178 } else {
1179 // IPv4: use ip_equals for robust comparison
1180 return ip_equals(ip1, ip2) ? 1 : 0;
1181 }
1182}
1183
1184// Check if IPv4 address is a private/LAN address
1185int is_lan_ipv4(const char *ip) {
1186 if (!is_valid_ipv4(ip)) {
1187 return 0;
1188 }
1189
1190 struct in_addr addr;
1191 if (inet_pton(AF_INET, ip, &addr) != 1) {
1192 return 0;
1193 }
1194
1195 uint32_t ip_val = ntohl(addr.s_addr);
1196
1197 // 10.0.0.0/8: 10.0.0.0 - 10.255.255.255
1198 if ((ip_val & 0xFF000000) == 0x0A000000) {
1199 return 1;
1200 }
1201
1202 // 172.16.0.0/12: 172.16.0.0 - 172.31.255.255
1203 if ((ip_val & 0xFFF00000) == 0xAC100000) {
1204 return 1;
1205 }
1206
1207 // 192.168.0.0/16: 192.168.0.0 - 192.168.255.255
1208 if ((ip_val & 0xFFFF0000) == 0xC0A80000) {
1209 return 1;
1210 }
1211
1212 return 0;
1213}
1214
1215// Check if IPv6 address is a private/LAN address (ULA)
1216int is_lan_ipv6(const char *ip) {
1217 if (!ip) {
1218 return 0;
1219 }
1220
1221 // Remove brackets if present
1222 char normalized[INET6_ADDRSTRLEN];
1223 if (parse_ipv6_address(ip, normalized, sizeof(normalized)) != 0) {
1224 return 0;
1225 }
1226
1227 // Validate the normalized address
1228 if (!is_valid_ipv6(normalized)) {
1229 return 0;
1230 }
1231
1232 struct in6_addr addr;
1233 if (inet_pton(AF_INET6, normalized, &addr) != 1) {
1234 return 0;
1235 }
1236
1237 // fc00::/7: Unique Local Addresses (ULA)
1238 // First byte should be 0xfc or 0xfd
1239 if ((addr.s6_addr[0] & 0xFE) == 0xFC) {
1240 return 1;
1241 }
1242
1243 return 0;
1244}
1245
1246// Check if IPv4 address is a broadcast address
1247int is_broadcast_ipv4(const char *ip) {
1248 if (!is_valid_ipv4(ip)) {
1249 return 0;
1250 }
1251
1252 struct in_addr addr;
1253 if (inet_pton(AF_INET, ip, &addr) != 1) {
1254 return 0;
1255 }
1256
1257 uint32_t ip_val = ntohl(addr.s_addr);
1258
1259 // Limited broadcast: 255.255.255.255
1260 if (ip_val == 0xFFFFFFFF) {
1261 return 1;
1262 }
1263
1264 return 0;
1265}
1266
1267// Check if IPv6 address is a multicast address
1268int is_broadcast_ipv6(const char *ip) {
1269 if (!ip) {
1270 return 0;
1271 }
1272
1273 // Remove brackets if present
1274 char normalized[INET6_ADDRSTRLEN];
1275 if (parse_ipv6_address(ip, normalized, sizeof(normalized)) != 0) {
1276 return 0;
1277 }
1278
1279 // Validate the normalized address
1280 if (!is_valid_ipv6(normalized)) {
1281 return 0;
1282 }
1283
1284 struct in6_addr addr;
1285 if (inet_pton(AF_INET6, normalized, &addr) != 1) {
1286 return 0;
1287 }
1288
1289 // ff00::/8: Multicast addresses
1290 // First byte should be 0xff
1291 if (addr.s6_addr[0] == 0xFF) {
1292 return 1;
1293 }
1294
1295 return 0;
1296}
1297
1298// Check if IPv4 address is localhost
1299int is_localhost_ipv4(const char *ip) {
1300 if (!is_valid_ipv4(ip)) {
1301 return 0;
1302 }
1303
1304 struct in_addr addr;
1305 if (inet_pton(AF_INET, ip, &addr) != 1) {
1306 return 0;
1307 }
1308
1309 uint32_t ip_val = ntohl(addr.s_addr);
1310
1311 // 127.0.0.0/8: Loopback addresses
1312 if ((ip_val & 0xFF000000) == 0x7F000000) {
1313 return 1;
1314 }
1315
1316 return 0;
1317}
1318
1319// Check if IPv6 address is localhost
1320int is_localhost_ipv6(const char *ip) {
1321 if (!ip) {
1322 return 0;
1323 }
1324
1325 // Remove brackets if present
1326 char normalized[INET6_ADDRSTRLEN];
1327 if (parse_ipv6_address(ip, normalized, sizeof(normalized)) != 0) {
1328 return 0;
1329 }
1330
1331 // Validate the normalized address
1332 if (!is_valid_ipv6(normalized)) {
1333 return 0;
1334 }
1335
1336 struct in6_addr addr;
1337 if (inet_pton(AF_INET6, normalized, &addr) != 1) {
1338 return 0;
1339 }
1340
1341 // ::1: Loopback address
1342 // All bytes should be 0 except the last byte which should be 1
1343 for (int i = 0; i < 15; i++) {
1344 if (addr.s6_addr[i] != 0) {
1345 return 0;
1346 }
1347 }
1348 if (addr.s6_addr[15] != 1) {
1349 return 0;
1350 }
1351
1352 return 1;
1353}
1354
1355// Check if IPv4 address is a link-local address
1356int is_link_local_ipv4(const char *ip) {
1357 if (!is_valid_ipv4(ip)) {
1358 return 0;
1359 }
1360
1361 struct in_addr addr;
1362 if (inet_pton(AF_INET, ip, &addr) != 1) {
1363 return 0;
1364 }
1365
1366 uint32_t ip_val = ntohl(addr.s_addr);
1367
1368 // 169.254.0.0/16: Link-local addresses (APIPA)
1369 if ((ip_val & 0xFFFF0000) == 0xA9FE0000) {
1370 return 1;
1371 }
1372
1373 return 0;
1374}
1375
1376// Check if IPv6 address is a link-local address
1377int is_link_local_ipv6(const char *ip) {
1378 if (!ip) {
1379 return 0;
1380 }
1381
1382 // Remove brackets if present
1383 char normalized[INET6_ADDRSTRLEN];
1384 if (parse_ipv6_address(ip, normalized, sizeof(normalized)) != 0) {
1385 return 0;
1386 }
1387
1388 // Validate the normalized address
1389 if (!is_valid_ipv6(normalized)) {
1390 return 0;
1391 }
1392
1393 struct in6_addr addr;
1394 if (inet_pton(AF_INET6, normalized, &addr) != 1) {
1395 return 0;
1396 }
1397
1398 // fe80::/10: Link-local addresses
1399 // First byte should be 0xfe, second byte & 0xc0 should be 0x80
1400 if (addr.s6_addr[0] == 0xFE && (addr.s6_addr[1] & 0xC0) == 0x80) {
1401 return 1;
1402 }
1403
1404 return 0;
1405}
1406
1407// Check if IPv4 address is a public internet address
1408int is_internet_ipv4(const char *ip) {
1409 if (!is_valid_ipv4(ip)) {
1410 return 0;
1411 }
1412
1413 struct in_addr addr;
1414 if (inet_pton(AF_INET, ip, &addr) != 1) {
1415 return 0;
1416 }
1417
1418 uint32_t ip_val = ntohl(addr.s_addr);
1419
1420 // Exclude private/LAN addresses
1421 if (is_lan_ipv4(ip)) {
1422 return 0;
1423 }
1424
1425 // Exclude loopback
1426 if (is_localhost_ipv4(ip)) {
1427 return 0;
1428 }
1429
1430 // Exclude link-local
1431 if (is_link_local_ipv4(ip)) {
1432 return 0;
1433 }
1434
1435 // Exclude broadcast
1436 if (is_broadcast_ipv4(ip)) {
1437 return 0;
1438 }
1439
1440 // Exclude multicast: 224.0.0.0/4
1441 if ((ip_val & 0xF0000000) == 0xE0000000) {
1442 return 0;
1443 }
1444
1445 // Exclude 0.0.0.0/8: "This network"
1446 if ((ip_val & 0xFF000000) == 0x00000000) {
1447 return 0;
1448 }
1449
1450 // Exclude 100.64.0.0/10: Shared Address Space (RFC 6598)
1451 if ((ip_val & 0xFFC00000) == 0x64400000) {
1452 return 0;
1453 }
1454
1455 // Exclude 192.0.0.0/24: IETF Protocol Assignments
1456 if ((ip_val & 0xFFFFFF00) == 0xC0000000) {
1457 return 0;
1458 }
1459
1460 // Exclude 192.0.2.0/24: TEST-NET-1
1461 if ((ip_val & 0xFFFFFF00) == 0xC0000200) {
1462 return 0;
1463 }
1464
1465 // Exclude 198.18.0.0/15: Benchmarking
1466 if ((ip_val & 0xFFFE0000) == 0xC6120000) {
1467 return 0;
1468 }
1469
1470 // Exclude 198.51.100.0/24: TEST-NET-2
1471 if ((ip_val & 0xFFFFFF00) == 0xC6336400) {
1472 return 0;
1473 }
1474
1475 // Exclude 203.0.113.0/24: TEST-NET-3
1476 if ((ip_val & 0xFFFFFF00) == 0xCB007100) {
1477 return 0;
1478 }
1479
1480 // Exclude 240.0.0.0/4: Reserved for future use
1481 if ((ip_val & 0xF0000000) == 0xF0000000) {
1482 return 0;
1483 }
1484
1485 // If none of the exclusions matched, it's a public internet address
1486 return 1;
1487}
1488
1489// Check if IPv6 address is a public internet address
1490int is_internet_ipv6(const char *ip) {
1491 if (!ip) {
1492 return 0;
1493 }
1494
1495 // Remove brackets if present
1496 char normalized[INET6_ADDRSTRLEN];
1497 if (parse_ipv6_address(ip, normalized, sizeof(normalized)) != 0) {
1498 return 0;
1499 }
1500
1501 // Validate the normalized address
1502 if (!is_valid_ipv6(normalized)) {
1503 return 0;
1504 }
1505
1506 struct in6_addr addr;
1507 if (inet_pton(AF_INET6, normalized, &addr) != 1) {
1508 return 0;
1509 }
1510
1511 // Exclude loopback
1512 if (is_localhost_ipv6(ip)) {
1513 return 0;
1514 }
1515
1516 // Exclude link-local
1517 if (is_link_local_ipv6(ip)) {
1518 return 0;
1519 }
1520
1521 // Exclude ULA
1522 if (is_lan_ipv6(ip)) {
1523 return 0;
1524 }
1525
1526 // Exclude multicast
1527 if (is_broadcast_ipv6(ip)) {
1528 return 0;
1529 }
1530
1531 // Exclude unspecified address: ::
1532 int is_unspecified = 1;
1533 for (int i = 0; i < 16; i++) {
1534 if (addr.s6_addr[i] != 0) {
1535 is_unspecified = 0;
1536 break;
1537 }
1538 }
1539 if (is_unspecified) {
1540 return 0;
1541 }
1542
1543 // Exclude IPv4-mapped IPv6: ::ffff:0:0/96
1544 if (addr.s6_addr[0] == 0 && addr.s6_addr[1] == 0 && addr.s6_addr[2] == 0 && addr.s6_addr[3] == 0 &&
1545 addr.s6_addr[4] == 0 && addr.s6_addr[5] == 0 && addr.s6_addr[6] == 0 && addr.s6_addr[7] == 0 &&
1546 addr.s6_addr[8] == 0 && addr.s6_addr[9] == 0 && addr.s6_addr[10] == 0xFF && addr.s6_addr[11] == 0xFF) {
1547 return 0;
1548 }
1549
1550 // Exclude IPv4-compatible IPv6: ::/96 (deprecated)
1551 if (addr.s6_addr[0] == 0 && addr.s6_addr[1] == 0 && addr.s6_addr[2] == 0 && addr.s6_addr[3] == 0 &&
1552 addr.s6_addr[4] == 0 && addr.s6_addr[5] == 0 && addr.s6_addr[6] == 0 && addr.s6_addr[7] == 0 &&
1553 addr.s6_addr[8] == 0 && addr.s6_addr[9] == 0 && addr.s6_addr[10] == 0 && addr.s6_addr[11] == 0) {
1554 // But not ::, which we already excluded above
1555 if (addr.s6_addr[12] != 0 || addr.s6_addr[13] != 0 || addr.s6_addr[14] != 0 || addr.s6_addr[15] != 0) {
1556 return 0;
1557 }
1558 }
1559
1560 // Exclude documentation prefix: 2001:db8::/32
1561 if (addr.s6_addr[0] == 0x20 && addr.s6_addr[1] == 0x01 && addr.s6_addr[2] == 0x0D && addr.s6_addr[3] == 0xB8) {
1562 return 0;
1563 }
1564
1565 // Exclude 6to4: 2002::/16
1566 if (addr.s6_addr[0] == 0x20 && addr.s6_addr[1] == 0x02) {
1567 return 0;
1568 }
1569
1570 // If none of the exclusions matched, it's likely a global unicast address
1571 // Global unicast is typically 2000::/3, but let's accept anything that's not excluded
1572 return 1;
1573}
int ip_compare(const char *ip1, const char *ip2)
Definition ip.c:601
asciichat_error_t expand_ipv6(const char *ipv6, char *expanded_out, size_t out_size)
Definition ip.c:932
int is_lan_ipv6(const char *ip)
Definition ip.c:1216
asciichat_error_t format_ip_with_port(const char *ip, uint16_t port, char *output, size_t output_size)
Definition ip.c:221
int is_broadcast_ipv6(const char *ip)
Definition ip.c:1268
int ip_in_cidr(const char *ip, const char *cidr)
Definition ip.c:820
int is_link_local_ipv6(const char *ip)
Definition ip.c:1377
asciichat_error_t extract_ipv4_from_mapped_ipv6(const char *ipv6, char *ipv4_out, size_t out_size)
Definition ip.c:896
int is_localhost_ipv6(const char *ip)
Definition ip.c:1320
int is_internet_ipv4(const char *ip)
Definition ip.c:1408
int is_valid_ipv4(const char *ip)
Definition ip.c:58
int extract_ip_from_address(const char *addr_with_port, char *ip_out, size_t ip_out_size)
Definition ip.c:1053
const char * get_ip_type_string(const char *ip)
Definition ip.c:1091
int parse_ip_with_port(const char *input, char *ip_output, size_t ip_output_size, uint16_t *port_output)
Definition ip.c:296
int is_lan_ipv4(const char *ip)
Definition ip.c:1185
asciichat_error_t compact_ipv6(const char *ipv6, char *compact_out, size_t out_size)
Definition ip.c:999
int parse_cidr(const char *cidr, char *ip_out, size_t ip_out_size, int *prefix_out)
Definition ip.c:665
int is_valid_ipv6(const char *ip)
Definition ip.c:105
int is_ipv4_mapped_ipv6(const char *ipv6)
Definition ip.c:860
int is_broadcast_ipv4(const char *ip)
Definition ip.c:1247
int is_localhost_ipv4(const char *ip)
Definition ip.c:1299
int ip_equals(const char *ip1, const char *ip2)
Definition ip.c:557
int parse_address_with_optional_port(const char *input, char *address_output, size_t address_output_size, uint16_t *port_output, uint16_t default_port)
Definition ip.c:370
int is_link_local_ipv4(const char *ip)
Definition ip.c:1356
int is_valid_ip(const char *ip)
Definition ip.c:534
int is_internet_ipv6(const char *ip)
Definition ip.c:1490
int ip_in_cidr_parsed(const char *ip, const char *network, int prefix_len)
Definition ip.c:766
asciichat_error_t canonicalize_ipv6(const char *ipv6, char *canonical_out, size_t out_size)
Definition ip.c:969
int get_ip_version(const char *ip)
Definition ip.c:510
int parse_ipv6_address(const char *input, char *output, size_t output_size)
Definition ip.c:158
int compare_ip_port_strings(const char *ip_port1, const char *ip_port2)
Definition ip.c:1133
int is_anycast_ipv6(const char *ipv6)
Definition ip.c:1004
asciichat_error_t format_ip_address(int family, const struct sockaddr *addr, char *output, size_t output_size)
Definition ip.c:196
asciichat_error_t ipv4_to_ipv6_mapped(const char *ipv4, char *ipv6_out, size_t out_size)
Definition ip.c:840
asciichat_error_t parse_port(const char *str, uint16_t *out_port)
Definition parsing.c:251
pcre2_code * asciichat_pcre2_singleton_get_code(pcre2_singleton_t *singleton)
Get the compiled pcre2_code from a singleton handle.
Definition pcre2.c:95
Represents a thread-safe compiled PCRE2 regex singleton.
Definition pcre2.c:21