/*
 * addr.c
 *
 * Network address operations.
 *
 * Copyright (c) 2000 Dug Song <dugsong@monkey.org>
 *
 * $Id: addr.c 610 2005-06-26 18:23:26Z dugsong $
 */

#include "config.h"

#include <sys/types.h>
#ifdef HAVE_NET_IF_H
# include <sys/socket.h>
# include <net/if.h>
#endif
#ifdef HAVE_NET_IF_DL_H
# include <net/if_dl.h>
#endif
#ifdef HAVE_NET_RAW_H
# include <net/raw.h>
#endif

#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "dnet.h"

#ifndef MAXHOSTNAMELEN
# define MAXHOSTNAMELEN	256
#endif

union sockunion {
#ifdef HAVE_NET_IF_DL_H
	struct sockaddr_dl	sdl;
#endif
	struct sockaddr_in	sin;
#ifdef HAVE_SOCKADDR_IN6
	struct sockaddr_in6	sin6;
#endif
	struct sockaddr		sa;
#ifdef AF_RAW
	struct sockaddr_raw	sr;
#endif
};

int
addr_cmp(const struct addr *a, const struct addr *b)
{
	int i, j, k;

	/* XXX */
	if ((i = a->addr_type - b->addr_type) != 0)
		return (i);
	
	/* XXX - 10.0.0.1 is "smaller" than 10.0.0.0/8? */
	if ((i = a->addr_bits - b->addr_bits) != 0)
		return (i);
	
	j = b->addr_bits / 8;

	for (i = 0; i < j; i++) {
		if ((k = a->addr_data8[i] - b->addr_data8[i]) != 0)
			return (k);
	}
	if ((k = b->addr_bits % 8) == 0)
		return (0);

	k = ~0 << (8 - k);
	i = b->addr_data8[j] & k;
	j = a->addr_data8[j] & k;
	
	return (j - i);
}

int
addr_net(const struct addr *a, struct addr *b)
{
	uint32_t mask;
	int i, j;

	if (a->addr_type == ADDR_TYPE_IP) {
		addr_btom(a->addr_bits, &mask, IP_ADDR_LEN);
		b->addr_type = ADDR_TYPE_IP;
		b->addr_bits = IP_ADDR_BITS;
		b->addr_ip = a->addr_ip & mask;
	} else if (a->addr_type == ADDR_TYPE_ETH) {
		memcpy(b, a, sizeof(*b));
		if (a->addr_data8[0] & 0x1)
			memset(b->addr_data8 + 3, 0, 3);
		b->addr_bits = ETH_ADDR_BITS;
	} else if (a->addr_type == ADDR_TYPE_IP6) {
		b->addr_type = ADDR_TYPE_IP6;
		b->addr_bits = IP6_ADDR_BITS;
		memset(&b->addr_ip6, 0, IP6_ADDR_LEN);
		
		switch ((i = a->addr_bits / 32)) {
		case 4: b->addr_data32[3] = a->addr_data32[3];
		case 3: b->addr_data32[2] = a->addr_data32[2];
		case 2: b->addr_data32[1] = a->addr_data32[1];
		case 1: b->addr_data32[0] = a->addr_data32[0];
		}
		if ((j = a->addr_bits % 32) > 0) {
			addr_btom(j, &mask, sizeof(mask));
			b->addr_data32[i] = a->addr_data32[i] & mask;
		}
	} else
		return (-1);
	
	return (0);
}

int
addr_bcast(const struct addr *a, struct addr *b)
{
	struct addr mask;
	
	if (a->addr_type == ADDR_TYPE_IP) {
		addr_btom(a->addr_bits, &mask.addr_ip, IP_ADDR_LEN);
		b->addr_type = ADDR_TYPE_IP;
		b->addr_bits = IP_ADDR_BITS;
		b->addr_ip = (a->addr_ip & mask.addr_ip) |
		    (~0L & ~mask.addr_ip);
	} else if (a->addr_type == ADDR_TYPE_ETH) {
		b->addr_type = ADDR_TYPE_ETH;
		b->addr_bits = ETH_ADDR_BITS;
		memcpy(&b->addr_eth, ETH_ADDR_BROADCAST, ETH_ADDR_LEN);
	} else {
		/* XXX - no broadcast addresses in IPv6 */
		errno = EINVAL;
		return (-1);
	}
	return (0);
}

char *
addr_ntop(const struct addr *src, char *dst, size_t size)
{
	if (src->addr_type == ADDR_TYPE_IP && size >= 20) {
		if (ip_ntop(&src->addr_ip, dst, size) != NULL) {
			if (src->addr_bits != IP_ADDR_BITS)
				sprintf(dst + strlen(dst), "/%d",
				    src->addr_bits);
			return (dst);
		}
	} else if (src->addr_type == ADDR_TYPE_IP6 && size >= 42) {
		if (ip6_ntop(&src->addr_ip6, dst, size) != NULL) {
			if (src->addr_bits != IP6_ADDR_BITS)
				sprintf(dst + strlen(dst), "/%d",
				    src->addr_bits);
			return (dst);
		}
	} else if (src->addr_type == ADDR_TYPE_ETH && size >= 18) {
		if (src->addr_bits == ETH_ADDR_BITS)
			return (eth_ntop(&src->addr_eth, dst, size));
	}
	errno = EINVAL;
	return (NULL);
}

int
addr_pton(const char *src, struct addr *dst)
{
	struct hostent *hp;
	char *ep, tmp[300];
	long bits = -1;
	int i;
	
	for (i = 0; i < (int)sizeof(tmp) - 1; i++) {
		if (src[i] == '/') {
			tmp[i] = '\0';
			if (strchr(&src[i + 1], '.')) {
				uint32_t m;
				uint16_t b;
				/* XXX - mask is specified like /255.0.0.0 */
				if (ip_pton(&src[i + 1], &m) != 0) {
					errno = EINVAL;
					return (-1);
				}
				addr_mtob(&m, sizeof(m), &b);
				bits = b;
			} else {
				bits = strtol(&src[i + 1], &ep, 10);
				if (ep == src || *ep != '\0' || bits < 0) {
					errno = EINVAL;
					return (-1);
				}
			}
			break;
		} else if ((tmp[i] = src[i]) == '\0')
			break;
	}
	if (ip_pton(tmp, &dst->addr_ip) == 0) {
		dst->addr_type = ADDR_TYPE_IP;
		dst->addr_bits = IP_ADDR_BITS;
	} else if (eth_pton(tmp, &dst->addr_eth) == 0) {
		dst->addr_type = ADDR_TYPE_ETH;
		dst->addr_bits = ETH_ADDR_BITS;
	} else if (ip6_pton(tmp, &dst->addr_ip6) == 0) {
		dst->addr_type = ADDR_TYPE_IP6;
		dst->addr_bits = IP6_ADDR_BITS;
	} else if ((hp = gethostbyname(tmp)) != NULL) {
		memcpy(&dst->addr_ip, hp->h_addr, IP_ADDR_LEN);
		dst->addr_type = ADDR_TYPE_IP;
		dst->addr_bits = IP_ADDR_BITS;
	} else {
		errno = EINVAL;
		return (-1);
	}
	if (bits >= 0) {
		if (bits > dst->addr_bits) {
			errno = EINVAL;
			return (-1);
		}
		dst->addr_bits = (uint16_t)bits;
	}
	return (0);
}

char *
addr_ntoa(const struct addr *a)
{
	static char *p, buf[BUFSIZ];
	char *q = NULL;
	
	if (p == NULL || p > buf + sizeof(buf) - 64 /* XXX */)
		p = buf;
	
	if (addr_ntop(a, p, (buf + sizeof(buf)) - p) != NULL) {
		q = p;
		p += strlen(p) + 1;
	}
	return (q);
}

int
addr_ntos(const struct addr *a, struct sockaddr *sa)
{
	union sockunion *so = (union sockunion *)sa;
	
	switch (a->addr_type) {
	case ADDR_TYPE_ETH:
#ifdef HAVE_NET_IF_DL_H
		memset(&so->sdl, 0, sizeof(so->sdl));
# ifdef HAVE_SOCKADDR_SA_LEN
		so->sdl.sdl_len = sizeof(so->sdl);
# endif
		so->sdl.sdl_family = AF_LINK;
		so->sdl.sdl_alen = ETH_ADDR_LEN;
		memcpy(LLADDR(&so->sdl), &a->addr_eth, ETH_ADDR_LEN);
#else
		memset(sa, 0, sizeof(*sa));
# ifdef AF_LINK
		sa->sa_family = AF_LINK;
# else
		sa->sa_family = AF_UNSPEC;
# endif
		memcpy(sa->sa_data, &a->addr_eth, ETH_ADDR_LEN);
#endif
		break;
#ifdef HAVE_SOCKADDR_IN6
	case ADDR_TYPE_IP6:
		memset(&so->sin6, 0, sizeof(so->sin6));
#ifdef HAVE_SOCKADDR_SA_LEN
		so->sin6.sin6_len = sizeof(so->sin6);
#endif
		so->sin6.sin6_family = AF_INET6;
		memcpy(&so->sin6.sin6_addr, &a->addr_ip6, IP6_ADDR_LEN);
		break;
#endif
	case ADDR_TYPE_IP:
		memset(&so->sin, 0, sizeof(so->sin));
#ifdef HAVE_SOCKADDR_SA_LEN
		so->sin.sin_len = sizeof(so->sin);
#endif
		so->sin.sin_family = AF_INET;
		so->sin.sin_addr.s_addr = a->addr_ip;
		break;
	default:
		errno = EINVAL;
		return (-1);
	}
	return (0);
}

int
addr_ston(const struct sockaddr *sa, struct addr *a)
{
	union sockunion *so = (union sockunion *)sa;
	
	memset(a, 0, sizeof(*a));
	
	switch (sa->sa_family) {
#ifdef HAVE_NET_IF_DL_H
	case AF_LINK:
		if (so->sdl.sdl_alen != ETH_ADDR_LEN) {
			errno = EINVAL;
			return (-1);
		}
		a->addr_type = ADDR_TYPE_ETH;
		a->addr_bits = ETH_ADDR_BITS;
		memcpy(&a->addr_eth, LLADDR(&so->sdl), ETH_ADDR_LEN);
		break;
#endif
	case AF_UNSPEC:
	case ARP_HRD_ETH:	/* XXX- Linux arp(7) */
		a->addr_type = ADDR_TYPE_ETH;
		a->addr_bits = ETH_ADDR_BITS;
		memcpy(&a->addr_eth, sa->sa_data, ETH_ADDR_LEN);
		break;
		
#ifdef AF_RAW
	case AF_RAW:		/* XXX - IRIX raw(7f) */
		a->addr_type = ADDR_TYPE_ETH;
		a->addr_bits = ETH_ADDR_BITS;
		memcpy(&a->addr_eth, so->sr.sr_addr, ETH_ADDR_LEN);
		break;
#endif
#ifdef HAVE_SOCKADDR_IN6
	case AF_INET6:
		a->addr_type = ADDR_TYPE_IP6;
		a->addr_bits = IP6_ADDR_BITS;
		memcpy(&a->addr_ip6, &so->sin6.sin6_addr, IP6_ADDR_LEN);
		break;
#endif
	case AF_INET:
		a->addr_type = ADDR_TYPE_IP;
		a->addr_bits = IP_ADDR_BITS;
		a->addr_ip = so->sin.sin_addr.s_addr;
		break;
	default:
		errno = EINVAL;
		return (-1);
	}
	return (0);
}

int
addr_btos(uint16_t bits, struct sockaddr *sa)
{
	union sockunion *so = (union sockunion *)sa;

#ifdef HAVE_SOCKADDR_IN6
	if (bits > IP_ADDR_BITS && bits <= IP6_ADDR_BITS) {
		memset(&so->sin6, 0, sizeof(so->sin6));
#ifdef HAVE_SOCKADDR_SA_LEN
		so->sin6.sin6_len = IP6_ADDR_LEN + (bits / 8) + (bits % 8);
#endif
		so->sin6.sin6_family = AF_INET6;
		return (addr_btom(bits, &so->sin6.sin6_addr, IP6_ADDR_LEN));
	} else
#endif
	if (bits <= IP_ADDR_BITS) {
		memset(&so->sin, 0, sizeof(so->sin));
#ifdef HAVE_SOCKADDR_SA_LEN
		so->sin.sin_len = IP_ADDR_LEN + (bits / 8) + (bits % 8);
#endif
		so->sin.sin_family = AF_INET;
		return (addr_btom(bits, &so->sin.sin_addr, IP_ADDR_LEN));
	}
	errno = EINVAL;
	return (-1);
}

int
addr_stob(const struct sockaddr *sa, uint16_t *bits)
{
	union sockunion *so = (union sockunion *)sa;
	int i, j, len;
	uint16_t n;
	u_char *p;

#ifdef HAVE_SOCKADDR_IN6
	if (sa->sa_family == AF_INET6) {
		len = IP6_ADDR_LEN;
		p = (u_char *)&so->sin6.sin6_addr;
	} else
#endif
	{
#ifdef HAVE_SOCKADDR_SA_LEN
		if ((len = sa->sa_len - IP_ADDR_LEN) > IP_ADDR_LEN)
#endif
		len = IP_ADDR_LEN;
		p = (u_char *)&so->sin.sin_addr.s_addr;
	}
	for (n = i = 0; i < len; i++, n += 8) {
		if (p[i] != 0xff)
			break;
	}
	if (i != len && p[i]) {
		for (j = 7; j > 0; j--, n++) {
			if ((p[i] & (1 << j)) == 0)
				break;
		}
	}
	*bits = n;
	
	return (0);
}
	
int
addr_btom(uint16_t bits, void *mask, size_t size)
{
	int net, host;
	u_char *p;

	if (size == IP_ADDR_LEN) {
		if (bits > IP_ADDR_BITS) {
			errno = EINVAL;
			return (-1);
		}
		*(uint32_t *)mask = bits ?
		    htonl(~0 << (IP_ADDR_BITS - bits)) : 0;
	} else {
		if (size * 8 < bits) {
			errno = EINVAL;
			return (-1);
		}
		p = (u_char *)mask;
		
		if ((net = bits / 8) > 0)
			memset(p, 0xff, net);
		
		if ((host = bits % 8) > 0) {
			p[net] = 0xff << (8 - host);
			memset(&p[net + 1], 0, size - net - 1);
		} else
			memset(&p[net], 0, size - net);
	}
	return (0);
}

int
addr_mtob(const void *mask, size_t size, uint16_t *bits)
{
	uint16_t n;
	u_char *p;
	int i, j;

	p = (u_char *)mask;
	
	for (n = i = 0; i < (int)size; i++, n += 8) {
		if (p[i] != 0xff)
			break;
	}
	if (i != (int)size && p[i]) {
		for (j = 7; j > 0; j--, n++) {
			if ((p[i] & (1 << j)) == 0)
				break;
		}
	}
	*bits = n;

	return (0);
}
