/* -*- coding: utf-8 -*- * ---------------------------------------------------------------------- * Copyright © 2011-2013, RedJack, LLC. * All rights reserved. * * Please see the COPYING file in this distribution for license details. * ---------------------------------------------------------------------- */ #include #include #include "libcork/core/byte-order.h" #include "libcork/core/error.h" #include "libcork/core/net-addresses.h" #include "libcork/core/types.h" #ifndef CORK_IP_ADDRESS_DEBUG #define CORK_IP_ADDRESS_DEBUG 0 #endif #if CORK_IP_ADDRESS_DEBUG #include #define DEBUG(...) \ do { \ fprintf(stderr, __VA_ARGS__); \ } while (0) #else #define DEBUG(...) /* nothing */ #endif /*----------------------------------------------------------------------- * IP addresses */ /*** IPv4 ***/ static inline const char * cork_ipv4_parse(struct cork_ipv4 *addr, const char *str) { const char *ch; bool seen_digit_in_octet = false; unsigned int octets = 0; unsigned int digit = 0; uint8_t result[4]; for (ch = str; *ch != '\0'; ch++) { DEBUG("%2u: %c\t", (unsigned int) (ch-str), *ch); switch (*ch) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': seen_digit_in_octet = true; digit *= 10; digit += (*ch - '0'); DEBUG("digit = %u\n", digit); if (CORK_UNLIKELY(digit > 255)) { DEBUG("\t"); goto parse_error; } break; case '.': /* If this would be the fourth octet, it can't have a trailing * period. */ if (CORK_UNLIKELY(octets == 3)) { goto parse_error; } DEBUG("octet %u = %u\n", octets, digit); result[octets] = digit; digit = 0; octets++; seen_digit_in_octet = false; break; default: /* Any other character is a parse error. */ goto parse_error; } } /* If we have a valid octet at the end, and that would be the fourth octet, * then we've got a valid final parse. */ DEBUG("%2u:\t", (unsigned int) (ch-str)); if (CORK_LIKELY(seen_digit_in_octet && octets == 3)) { #if CORK_IP_ADDRESS_DEBUG char parsed_ipv4[CORK_IPV4_STRING_LENGTH]; #endif DEBUG("octet %u = %u\n", octets, digit); result[octets] = digit; cork_ipv4_copy(addr, result); #if CORK_IP_ADDRESS_DEBUG cork_ipv4_to_raw_string(addr, parsed_ipv4); DEBUG("\tParsed address: %s\n", parsed_ipv4); #endif return ch; } parse_error: DEBUG("parse error\n"); cork_parse_error("Invalid IPv4 address: \"%s\"", str); return NULL; } int cork_ipv4_init(struct cork_ipv4 *addr, const char *str) { return cork_ipv4_parse(addr, str) == NULL? -1: 0; } bool cork_ipv4_equal_(const struct cork_ipv4 *addr1, const struct cork_ipv4 *addr2) { return cork_ipv4_equal(addr1, addr2); } void cork_ipv4_to_raw_string(const struct cork_ipv4 *addr, char *dest) { snprintf(dest, CORK_IPV4_STRING_LENGTH, "%u.%u.%u.%u", addr->_.u8[0], addr->_.u8[1], addr->_.u8[2], addr->_.u8[3]); } bool cork_ipv4_is_valid_network(const struct cork_ipv4 *addr, unsigned int cidr_prefix) { uint32_t cidr_mask; if (cidr_prefix > 32) { return false; } else if (cidr_prefix == 32) { /* This handles undefined behavior for overflow bit shifts. */ cidr_mask = 0; } else { cidr_mask = 0xffffffff >> cidr_prefix; } return (CORK_UINT32_BIG_TO_HOST(addr->_.u32) & cidr_mask) == 0; } /*** IPv6 ***/ int cork_ipv6_init(struct cork_ipv6 *addr, const char *str) { const char *ch; uint16_t digit = 0; unsigned int before_count = 0; uint16_t before_double_colon[8]; uint16_t after_double_colon[8]; uint16_t *dest = before_double_colon; unsigned int digits_seen = 0; unsigned int hextets_seen = 0; bool another_required = true; bool digit_allowed = true; bool colon_allowed = true; bool double_colon_allowed = true; bool just_saw_colon = false; for (ch = str; *ch != '\0'; ch++) { DEBUG("%2u: %c\t", (unsigned int) (ch-str), *ch); switch (*ch) { #define process_digit(base) \ /* Make sure a digit is allowed here. */ \ if (CORK_UNLIKELY(!digit_allowed)) { \ goto parse_error; \ } \ /* If we've already seen 4 digits, it's a parse error. */ \ if (CORK_UNLIKELY(digits_seen == 4)) { \ goto parse_error; \ } \ \ digits_seen++; \ colon_allowed = true; \ just_saw_colon = false; \ digit <<= 4; \ digit |= (*ch - (base)); \ DEBUG("digit = %04x\n", digit); case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': process_digit('0'); break; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': process_digit('a'-10); break; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': process_digit('A'-10); break; #undef process_digit case ':': /* We can only see a colon immediately after a hextet or as part * of a double-colon. */ if (CORK_UNLIKELY(!colon_allowed)) { goto parse_error; } /* If this is a double-colon, start parsing hextets into our * second array. */ if (just_saw_colon) { DEBUG("double-colon\n"); colon_allowed = false; digit_allowed = true; another_required = false; double_colon_allowed = false; before_count = hextets_seen; dest = after_double_colon; continue; } /* If this would end the eighth hextet (regardless of the * placement of a double-colon), then there can't be a trailing * colon. */ if (CORK_UNLIKELY(hextets_seen == 8)) { goto parse_error; } /* If this is the very beginning of the string, then we can only * have a double-colon, not a single colon. */ if (digits_seen == 0 && hextets_seen == 0) { DEBUG("initial colon\n"); colon_allowed = true; digit_allowed = false; just_saw_colon = true; another_required = true; continue; } /* Otherwise this ends the current hextet. */ DEBUG("hextet %u = %04x\n", hextets_seen, digit); *(dest++) = CORK_UINT16_HOST_TO_BIG(digit); digit = 0; hextets_seen++; digits_seen = 0; colon_allowed = double_colon_allowed; just_saw_colon = true; another_required = true; break; case '.': { /* If we see a period, then we must be in the middle of an IPv4 * address at the end of the IPv6 address. */ struct cork_ipv4 *ipv4 = (struct cork_ipv4 *) dest; DEBUG("Detected IPv4 address %s\n", ch-digits_seen); /* Ensure that we have space for the two hextets that the IPv4 * address will take up. */ if (CORK_UNLIKELY(hextets_seen >= 7)) { goto parse_error; } /* Parse the IPv4 address directly into our current hextet * buffer. */ ch = cork_ipv4_parse(ipv4, ch - digits_seen); if (CORK_LIKELY(ch != NULL)) { hextets_seen += 2; digits_seen = 0; another_required = false; /* ch now points at the NUL terminator, but we're about to * increment ch. */ ch--; break; } /* The IPv4 parse failed, so we have an IPv6 parse error. */ goto parse_error; } default: /* Any other character is a parse error. */ goto parse_error; } } /* If we have a valid hextet at the end, and we've either seen a * double-colon, or we have eight hextets in total, then we've got a valid * final parse. */ DEBUG("%2u:\t", (unsigned int) (ch-str)); if (CORK_LIKELY(digits_seen > 0)) { DEBUG("hextet %u = %04x\n\t", hextets_seen, digit); *(dest++) = CORK_UINT16_HOST_TO_BIG(digit); hextets_seen++; } else if (CORK_UNLIKELY(another_required)) { goto parse_error; } if (!double_colon_allowed) { /* We've seen a double-colon, so use 0000 for any hextets that weren't * present. */ #if CORK_IP_ADDRESS_DEBUG char parsed_result[CORK_IPV6_STRING_LENGTH]; #endif unsigned int after_count = hextets_seen - before_count; DEBUG("Saw double-colon; %u hextets before, %u after\n", before_count, after_count); memset(addr, 0, sizeof(struct cork_ipv6)); memcpy(addr, before_double_colon, sizeof(uint16_t) * before_count); memcpy(&addr->_.u16[8-after_count], after_double_colon, sizeof(uint16_t) * after_count); #if CORK_IP_ADDRESS_DEBUG cork_ipv6_to_raw_string(addr, parsed_result); DEBUG("\tParsed address: %s\n", parsed_result); #endif return 0; } else if (hextets_seen == 8) { /* No double-colon, so we must have exactly eight hextets. */ #if CORK_IP_ADDRESS_DEBUG char parsed_result[CORK_IPV6_STRING_LENGTH]; #endif DEBUG("No double-colon\n"); cork_ipv6_copy(addr, before_double_colon); #if CORK_IP_ADDRESS_DEBUG cork_ipv6_to_raw_string(addr, parsed_result); DEBUG("\tParsed address: %s\n", parsed_result); #endif return 0; } parse_error: DEBUG("parse error\n"); cork_parse_error("Invalid IPv6 address: \"%s\"", str); return -1; } bool cork_ipv6_equal_(const struct cork_ipv6 *addr1, const struct cork_ipv6 *addr2) { return cork_ipv6_equal(addr1, addr2); } #define NS_IN6ADDRSZ 16 #define NS_INT16SZ 2 void cork_ipv6_to_raw_string(const struct cork_ipv6 *addr, char *dest) { const uint8_t *src = addr->_.u8; /* * Note that int32_t and int16_t need only be "at least" large enough * to contain a value of the specified size. On some systems, like * Crays, there is no such thing as an integer variable with 16 bits. * Keep this in mind if you think this function should have been coded * to use pointer overlays. All the world's not a VAX. */ char *tp; struct { int base, len; } best, cur; unsigned int words[NS_IN6ADDRSZ / NS_INT16SZ]; int i; /* * Preprocess: * Copy the input (bytewise) array into a wordwise array. * Find the longest run of 0x00's in src[] for :: shorthanding. */ memset(words, '\0', sizeof words); for (i = 0; i < NS_IN6ADDRSZ; i++) words[i / 2] |= (src[i] << ((1 - (i % 2)) << 3)); best.base = -1; best.len = 0; cur.base = -1; cur.len = 0; for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) { if (words[i] == 0) { if (cur.base == -1) cur.base = i, cur.len = 1; else cur.len++; } else { if (cur.base != -1) { if (best.base == -1 || cur.len > best.len) best = cur; cur.base = -1; } } } if (cur.base != -1) { if (best.base == -1 || cur.len > best.len) best = cur; } if (best.base != -1 && best.len < 2) best.base = -1; /* * Format the result. */ tp = dest; for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) { /* Are we inside the best run of 0x00's? */ if (best.base != -1 && i >= best.base && i < (best.base + best.len)) { if (i == best.base) *tp++ = ':'; continue; } /* Are we following an initial run of 0x00s or any real hex? */ if (i != 0) *tp++ = ':'; /* Is this address an encapsulated IPv4? */ if (i == 6 && best.base == 0 && (best.len == 6 || (best.len == 5 && words[5] == 0xffff))) { tp += sprintf(tp, "%u.%u.%u.%u", src[12], src[13], src[14], src[15]); break; } tp += sprintf(tp, "%x", words[i]); } /* Was it a trailing run of 0x00's? */ if (best.base != -1 && (best.base + best.len) == (NS_IN6ADDRSZ / NS_INT16SZ)) *tp++ = ':'; *tp++ = '\0'; } bool cork_ipv6_is_valid_network(const struct cork_ipv6 *addr, unsigned int cidr_prefix) { uint64_t cidr_mask[2]; if (cidr_prefix > 128) { return false; } else if (cidr_prefix == 128) { /* This handles undefined behavior for overflow bit shifts. */ cidr_mask[0] = cidr_mask[1] = 0; } else if (cidr_prefix == 64) { /* This handles undefined behavior for overflow bit shifts. */ cidr_mask[0] = 0; cidr_mask[1] = UINT64_C(0xffffffffffffffff); } else if (cidr_prefix > 64) { cidr_mask[0] = 0; cidr_mask[1] = UINT64_C(0xffffffffffffffff) >> (cidr_prefix-64); } else { cidr_mask[0] = UINT64_C(0xffffffffffffffff) >> cidr_prefix; cidr_mask[1] = UINT64_C(0xffffffffffffffff); } return (CORK_UINT64_BIG_TO_HOST(addr->_.u64[0] & cidr_mask[0]) == 0) && (CORK_UINT64_BIG_TO_HOST(addr->_.u64[1] & cidr_mask[1]) == 0); } /*** IP ***/ void cork_ip_from_ipv4_(struct cork_ip *addr, const void *src) { cork_ip_from_ipv4(addr, src); } void cork_ip_from_ipv6_(struct cork_ip *addr, const void *src) { cork_ip_from_ipv6(addr, src); } int cork_ip_init(struct cork_ip *addr, const char *str) { int rc; /* Try IPv4 first */ rc = cork_ipv4_init(&addr->ip.v4, str); if (rc == 0) { /* successful parse */ addr->version = 4; return 0; } /* Then try IPv6 */ cork_error_clear(); rc = cork_ipv6_init(&addr->ip.v6, str); if (rc == 0) { /* successful parse */ addr->version = 6; return 0; } /* Parse error for both address types */ cork_parse_error("Invalid IP address: \"%s\"", str); return -1; } bool cork_ip_equal_(const struct cork_ip *addr1, const struct cork_ip *addr2) { return cork_ip_equal(addr1, addr2); } void cork_ip_to_raw_string(const struct cork_ip *addr, char *dest) { switch (addr->version) { case 4: cork_ipv4_to_raw_string(&addr->ip.v4, dest); return; case 6: cork_ipv6_to_raw_string(&addr->ip.v6, dest); return; default: strncpy(dest, "", CORK_IP_STRING_LENGTH); return; } } bool cork_ip_is_valid_network(const struct cork_ip *addr, unsigned int cidr_prefix) { switch (addr->version) { case 4: return cork_ipv4_is_valid_network(&addr->ip.v4, cidr_prefix); case 6: return cork_ipv6_is_valid_network(&addr->ip.v6, cidr_prefix); default: return false; } }