You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
529 lines
16 KiB
529 lines
16 KiB
/* -*- coding: utf-8 -*-
|
|
* ----------------------------------------------------------------------
|
|
* Copyright © 2011-2013, RedJack, LLC.
|
|
* All rights reserved.
|
|
*
|
|
* Please see the COPYING file in this distribution for license details.
|
|
* ----------------------------------------------------------------------
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#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 <stdio.h>
|
|
#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, "<INVALID>", 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;
|
|
}
|
|
}
|