diff options
Diffstat (limited to 'common/inet.c')
-rw-r--r-- | common/inet.c | 631 |
1 files changed, 631 insertions, 0 deletions
diff --git a/common/inet.c b/common/inet.c new file mode 100644 index 0000000..39845a8 --- /dev/null +++ b/common/inet.c @@ -0,0 +1,631 @@ +/* inet.c + + Subroutines to manipulate internet addresses and ports in a safely portable + way... */ + +/* + * Copyright (c) 2011 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2007-2009 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2004,2005 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1995-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + * This software has been written for Internet Systems Consortium + * by Ted Lemon in cooperation with Vixie Enterprises and Nominum, Inc. + * To learn more about Internet Systems Consortium, see + * ``https://www.isc.org/''. To learn more about Vixie Enterprises, + * see ``http://www.vix.com''. To learn more about Nominum, Inc., see + * ``http://www.nominum.com''. + */ + +#include "dhcpd.h" + +/* Return just the network number of an internet address... */ + +struct iaddr subnet_number (addr, mask) + struct iaddr addr; + struct iaddr mask; +{ + int i; + struct iaddr rv; + + if (addr.len > sizeof(addr.iabuf)) + log_fatal("subnet_number():%s:%d: Invalid addr length.", MDL); + if (addr.len != mask.len) + log_fatal("subnet_number():%s:%d: Addr/mask length mismatch.", + MDL); + + rv.len = 0; + + /* Both addresses must have the same length... */ + if (addr.len != mask.len) + return rv; + + rv.len = addr.len; + for (i = 0; i < rv.len; i++) + rv.iabuf [i] = addr.iabuf [i] & mask.iabuf [i]; + return rv; +} + +/* Combine a network number and a integer to produce an internet address. + This won't work for subnets with more than 32 bits of host address, but + maybe this isn't a problem. */ + +struct iaddr ip_addr (subnet, mask, host_address) + struct iaddr subnet; + struct iaddr mask; + u_int32_t host_address; +{ + int i, j, k; + u_int32_t swaddr; + struct iaddr rv; + unsigned char habuf [sizeof swaddr]; + + if (subnet.len > sizeof(subnet.iabuf)) + log_fatal("ip_addr():%s:%d: Invalid addr length.", MDL); + if (subnet.len != mask.len) + log_fatal("ip_addr():%s:%d: Addr/mask length mismatch.", + MDL); + + swaddr = htonl (host_address); + memcpy (habuf, &swaddr, sizeof swaddr); + + /* Combine the subnet address and the host address. If + the host address is bigger than can fit in the subnet, + return a zero-length iaddr structure. */ + rv = subnet; + j = rv.len - sizeof habuf; + for (i = sizeof habuf - 1; i >= 0; i--) { + if (mask.iabuf [i + j]) { + if (habuf [i] > (mask.iabuf [i + j] ^ 0xFF)) { + rv.len = 0; + return rv; + } + for (k = i - 1; k >= 0; k--) { + if (habuf [k]) { + rv.len = 0; + return rv; + } + } + rv.iabuf [i + j] |= habuf [i]; + break; + } else + rv.iabuf [i + j] = habuf [i]; + } + + return rv; +} + +/* Given a subnet number and netmask, return the address on that subnet + for which the host portion of the address is all ones (the standard + broadcast address). */ + +struct iaddr broadcast_addr (subnet, mask) + struct iaddr subnet; + struct iaddr mask; +{ + int i; + struct iaddr rv; + + if (subnet.len > sizeof(subnet.iabuf)) + log_fatal("broadcast_addr():%s:%d: Invalid addr length.", MDL); + if (subnet.len != mask.len) + log_fatal("broadcast_addr():%s:%d: Addr/mask length mismatch.", + MDL); + + if (subnet.len != mask.len) { + rv.len = 0; + return rv; + } + + for (i = 0; i < subnet.len; i++) { + rv.iabuf [i] = subnet.iabuf [i] | (~mask.iabuf [i] & 255); + } + rv.len = subnet.len; + + return rv; +} + +u_int32_t host_addr (addr, mask) + struct iaddr addr; + struct iaddr mask; +{ + int i; + u_int32_t swaddr; + struct iaddr rv; + + if (addr.len > sizeof(addr.iabuf)) + log_fatal("host_addr():%s:%d: Invalid addr length.", MDL); + if (addr.len != mask.len) + log_fatal("host_addr():%s:%d: Addr/mask length mismatch.", + MDL); + + rv.len = 0; + + /* Mask out the network bits... */ + rv.len = addr.len; + for (i = 0; i < rv.len; i++) + rv.iabuf [i] = addr.iabuf [i] & ~mask.iabuf [i]; + + /* Copy out up to 32 bits... */ + memcpy (&swaddr, &rv.iabuf [rv.len - sizeof swaddr], sizeof swaddr); + + /* Swap it and return it. */ + return ntohl (swaddr); +} + +int addr_eq (addr1, addr2) + struct iaddr addr1, addr2; +{ + if (addr1.len > sizeof(addr1.iabuf)) + log_fatal("addr_eq():%s:%d: Invalid addr length.", MDL); + + if (addr1.len != addr2.len) + return 0; + return memcmp (addr1.iabuf, addr2.iabuf, addr1.len) == 0; +} + +/* addr_match + * + * compares an IP address against a network/mask combination + * by ANDing the IP with the mask and seeing whether the result + * matches the masked network value. + */ +int +addr_match(addr, match) + struct iaddr *addr; + struct iaddrmatch *match; +{ + int i; + + if (addr->len != match->addr.len) + return 0; + + i = 0; + for (i = 0 ; i < addr->len ; i++) { + if ((addr->iabuf[i] & match->mask.iabuf[i]) != + match->addr.iabuf[i]) + return 0; + } + return 1; +} + +/* + * Compares the addresses a1 and a2. + * + * If a1 < a2, returns -1. + * If a1 == a2, returns 0. + * If a1 > a2, returns 1. + * + * WARNING: if a1 and a2 differ in length, returns 0. + */ +int +addr_cmp(const struct iaddr *a1, const struct iaddr *a2) { + int i; + + if (a1->len != a2->len) { + return 0; + } + + for (i=0; i<a1->len; i++) { + if (a1->iabuf[i] < a2->iabuf[i]) { + return -1; + } + if (a1->iabuf[i] > a2->iabuf[i]) { + return 1; + } + } + + return 0; +} + +/* + * Performs a bitwise-OR of two addresses. + * + * Returns 1 if the result is non-zero, or 0 otherwise. + * + * WARNING: if a1 and a2 differ in length, returns 0. + */ +int +addr_or(struct iaddr *result, const struct iaddr *a1, const struct iaddr *a2) { + int i; + int all_zero; + + if (a1->len != a2->len) { + return 0; + } + + all_zero = 1; + + result->len = a1->len; + for (i=0; i<a1->len; i++) { + result->iabuf[i] = a1->iabuf[i] | a2->iabuf[i]; + if (result->iabuf[i] != 0) { + all_zero = 0; + } + } + + return !all_zero; +} + +/* + * Performs a bitwise-AND of two addresses. + * + * Returns 1 if the result is non-zero, or 0 otherwise. + * + * WARNING: if a1 and a2 differ in length, returns 0. + */ +int +addr_and(struct iaddr *result, const struct iaddr *a1, const struct iaddr *a2) { + int i; + int all_zero; + + if (a1->len != a2->len) { + return 0; + } + + all_zero = 1; + + result->len = a1->len; + for (i=0; i<a1->len; i++) { + result->iabuf[i] = a1->iabuf[i] & a2->iabuf[i]; + if (result->iabuf[i] != 0) { + all_zero = 0; + } + } + + return !all_zero; +} + +/* + * Check if a bitmask of the given length is valid for the address. + * This is not the case if any bits longer than the bitmask are 1. + * + * So, this is valid: + * + * 127.0.0.0/8 + * + * But this is not: + * + * 127.0.0.1/8 + * + * Because the final ".1" would get masked out by the /8. + */ +isc_boolean_t +is_cidr_mask_valid(const struct iaddr *addr, int bits) { + int zero_bits; + int zero_bytes; + int i; + char byte; + int shift_bits; + + /* + * Check our bit boundaries. + */ + if (bits < 0) { + return ISC_FALSE; + } + if (bits > (addr->len * 8)) { + return ISC_FALSE; + } + + /* + * Figure out how many low-order bits need to be zero. + */ + zero_bits = (addr->len * 8) - bits; + zero_bytes = zero_bits / 8; + + /* + * Check to make sure the low-order bytes are zero. + */ + for (i=1; i<=zero_bytes; i++) { + if (addr->iabuf[addr->len-i] != 0) { + return ISC_FALSE; + } + } + + /* + * Look to see if any bits not in right-hand bytes are + * non-zero, by making a byte that has these bits set to zero + * comparing to the original byte. If these two values are + * equal, then the right-hand bits are zero, and we are + * happy. + */ + shift_bits = zero_bits % 8; + if (shift_bits == 0) return ISC_TRUE; + byte = addr->iabuf[addr->len-zero_bytes-1]; + return (((byte >> shift_bits) << shift_bits) == byte); +} + +/* + * range2cidr + * + * Converts a range of IP addresses to a set of CIDR networks. + * + * Examples: + * 192.168.0.0 - 192.168.0.255 = 192.168.0.0/24 + * 10.0.0.0 - 10.0.1.127 = 10.0.0.0/24, 10.0.1.0/25 + * 255.255.255.32 - 255.255.255.255 = 255.255.255.32/27, 255.255.255.64/26, + * 255.255.255.128/25 + */ +isc_result_t +range2cidr(struct iaddrcidrnetlist **result, + const struct iaddr *lo, const struct iaddr *hi) { + struct iaddr addr; + struct iaddr mask; + int bit; + struct iaddr end_addr; + struct iaddr dummy; + int ofs, val; + struct iaddrcidrnetlist *net; + int tmp; + + if (result == NULL) { + return DHCP_R_INVALIDARG; + } + if (*result != NULL) { + return DHCP_R_INVALIDARG; + } + if ((lo == NULL) || (hi == NULL) || (lo->len != hi->len)) { + return DHCP_R_INVALIDARG; + } + + /* + * Put our start and end in the right order, if reversed. + */ + if (addr_cmp(lo, hi) > 0) { + const struct iaddr *tmp; + tmp = lo; + lo = hi; + hi = tmp; + } + + /* + * Theory of operation: + * + * ------------------- + * Start at the low end, and keep trying larger networks + * until we get one that is too big (explained below). + * + * We keep a "mask", which is the ones-complement of a + * normal netmask. So, a /23 has a netmask of 255.255.254.0, + * and a mask of 0.0.1.255. + * + * We know when a network is too big when we bitwise-AND the + * mask with the starting address and we get a non-zero + * result, like this: + * + * addr: 192.168.1.0, mask: 0.0.1.255 + * bitwise-AND: 0.0.1.0 + * + * A network is also too big if the bitwise-OR of the mask + * with the starting address is larger than the end address, + * like this: + * + * start: 192.168.1.0, mask: 0.0.1.255, end: 192.168.0.255 + * bitwise-OR: 192.168.1.255 + * + * ------------------- + * Once we have found a network that is too big, we add the + * appropriate CIDR network to our list of found networks. + * + * We then use the next IP address as our low address, and + * begin the process of searching for a network that is + * too big again, starting with an empty mask. + */ + addr = *lo; + bit = 0; + memset(&mask, 0, sizeof(mask)); + mask.len = addr.len; + while (addr_cmp(&addr, hi) <= 0) { + /* + * Bitwise-OR mask with (1 << bit) + */ + ofs = addr.len - (bit / 8) - 1; + val = 1 << (bit % 8); + if (ofs >= 0) { + mask.iabuf[ofs] |= val; + } + + /* + * See if we're too big, and save this network if so. + */ + addr_or(&end_addr, &addr, &mask); + if ((ofs < 0) || + (addr_cmp(&end_addr, hi) > 0) || + addr_and(&dummy, &addr, &mask)) { + /* + * Add a new prefix to our list. + */ + net = dmalloc(sizeof(*net), MDL); + if (net == NULL) { + while (*result != NULL) { + net = (*result)->next; + dfree(*result, MDL); + *result = net; + } + return ISC_R_NOMEMORY; + } + net->cidrnet.lo_addr = addr; + net->cidrnet.bits = (addr.len * 8) - bit; + net->next = *result; + *result = net; + + /* + * Figure out our new starting address, + * by adding (1 << bit) to our previous + * starting address. + */ + tmp = addr.iabuf[ofs] + val; + while ((ofs >= 0) && (tmp > 255)) { + addr.iabuf[ofs] = tmp - 256; + ofs--; + tmp = addr.iabuf[ofs] + 1; + } + if (ofs < 0) { + /* Gone past last address, we're done. */ + break; + } + addr.iabuf[ofs] = tmp; + + /* + * Reset our bit and mask. + */ + bit = 0; + memset(mask.iabuf, 0, sizeof(mask.iabuf)); + memset(end_addr.iabuf, 0, sizeof(end_addr.iabuf)); + } else { + /* + * If we're not too big, increase our network size. + */ + bit++; + } + } + + /* + * We're done. + */ + return ISC_R_SUCCESS; +} + +/* + * Free a list of CIDR networks, such as returned from range2cidr(). + */ +isc_result_t +free_iaddrcidrnetlist(struct iaddrcidrnetlist **result) { + struct iaddrcidrnetlist *p; + + if (result == NULL) { + return DHCP_R_INVALIDARG; + } + if (*result == NULL) { + return DHCP_R_INVALIDARG; + } + + while (*result != NULL) { + p = *result; + *result = p->next; + dfree(p, MDL); + } + + return ISC_R_SUCCESS; +} + +/* piaddr() turns an iaddr structure into a printable address. */ +/* XXX: should use a const pointer rather than passing the structure */ +const char * +piaddr(const struct iaddr addr) { + static char + pbuf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; + /* "255.255.255.255" */ + + /* INSIST((addr.len == 0) || (addr.len == 4) || (addr.len == 16)); */ + + if (addr.len == 0) { + return "<null address>"; + } + if (addr.len == 4) { + return inet_ntop(AF_INET, addr.iabuf, pbuf, sizeof(pbuf)); + } + if (addr.len == 16) { + return inet_ntop(AF_INET6, addr.iabuf, pbuf, sizeof(pbuf)); + } + + log_fatal("piaddr():%s:%d: Invalid address length %d.", MDL, + addr.len); + /* quell compiler warnings */ + return NULL; +} + +/* piaddrmask takes an iaddr structure mask, determines the bitlength of + * the mask, and then returns the printable CIDR notation of the two. + */ +char * +piaddrmask(struct iaddr *addr, struct iaddr *mask) { + int mw; + unsigned int oct, bit; + + if ((addr->len != 4) && (addr->len != 16)) + log_fatal("piaddrmask():%s:%d: Address length %d invalid", + MDL, addr->len); + if (addr->len != mask->len) + log_fatal("piaddrmask():%s:%d: Address and mask size mismatch", + MDL); + + /* Determine netmask width in bits. */ + for (mw = (mask->len * 8) ; mw > 0 ; ) { + oct = (mw - 1) / 8; + bit = 0x80 >> ((mw - 1) % 8); + if (!mask->iabuf[oct]) + mw -= 8; + else if (mask->iabuf[oct] & bit) + break; + else + mw--; + } + + if (mw < 0) + log_fatal("Impossible condition at %s:%d.", MDL); + + return piaddrcidr(addr, mw); +} + +/* Format an address and mask-length into printable CIDR notation. */ +char * +piaddrcidr(const struct iaddr *addr, unsigned int bits) { + static char + ret[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255/128")]; + /* "255.255.255.255/32" */ + + /* INSIST(addr != NULL); */ + /* INSIST((addr->len == 4) || (addr->len == 16)); */ + /* INSIST(bits <= (addr->len * 8)); */ + + if (bits > (addr->len * 8)) + return NULL; + + sprintf(ret, "%s/%d", piaddr(*addr), bits); + + return ret; +} + +/* Validate that the string represents a valid port number and + * return it in network byte order + */ + +u_int16_t +validate_port(char *port) { + long local_port = 0; + long lower = 1; + long upper = 65535; + char *endptr; + + errno = 0; + local_port = strtol(port, &endptr, 10); + + if ((*endptr != '\0') || (errno == ERANGE) || (errno == EINVAL)) + log_fatal ("Invalid port number specification: %s", port); + + if (local_port < lower || local_port > upper) + log_fatal("Port number specified is out of range (%ld-%ld).", + lower, upper); + + return htons((u_int16_t)local_port); +} |