File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / dhcp / common / inet.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Tue Oct 9 09:06:54 2012 UTC (12 years, 2 months ago) by misho
Branches: dhcp, MAIN
CVS tags: v4_1_R7p0, v4_1_R7, v4_1_R4, HEAD
dhcp 4.1 r7

/* 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 ISC_R_INVALIDARG;
	}
	if (*result != NULL) {
		return ISC_R_INVALIDARG;
	}
	if ((lo == NULL) || (hi == NULL) || (lo->len != hi->len)) {
		return ISC_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 ISC_R_INVALIDARG;
	}
	if (*result == NULL) {
		return ISC_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);
}

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>