/*
 * Copyright (c) 1991, 1999 by Sun Microsystems, Inc.
 * All rights reserved.
 */

#pragma ident	"@(#)nfslog_ipaddr.c	1.7	99/08/24 SMI"

#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <unistd.h>
#include <malloc.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#include <netdb.h>
#include <string.h>
#include <signal.h>
#include <setjmp.h>

/*
 * Note: If making changes to this file, check also the file
 *	 cmd/cmd-inet/usr.sbin/snoop/snoop_ipaddr.c
 *	 as it has the same functions there.
 */
static jmp_buf nisjmp;

#define	MAXHASH 1024  /* must be a power of 2 */

struct hostdata {
	struct hostdata *h_next;
	char		*h_hostname;
	int		h_pktsout;
	int		h_pktsin;
};

struct hostdata4 {
	struct hostdata4	*h4_next;
	char		*h4_hostname;
	int		h4_pktsout;
	int		h4_pktsin;
	struct in_addr	h4_addr;
};

struct hostdata6 {
	struct hostdata6	*h6_next;
	char		*h6_hostname;
	int		h6_pktsout;
	int		h6_pktsin;
	struct in6_addr	h6_addr;
};

static struct hostdata *addhost(int, void *, char *);

static struct hostdata4 *h_table4[MAXHASH];
static struct hostdata6 *h_table6[MAXHASH];

#define	iphash(e)  ((e) & (MAXHASH-1))

/* ARGSUSED */
static void
wakeup(int n)
{
	longjmp(nisjmp, 1);
}

extern char *inet_ntoa();

static struct hostdata *
iplookup(ipaddr)
	struct in_addr *ipaddr;
{
	register struct hostdata4 *h;
	struct hostent *hp = NULL;
	struct netent *np;
	int error_num;

	for (h = h_table4[iphash(ipaddr->s_addr)]; h; h = h->h4_next) {
		if (h->h4_addr.s_addr == ipaddr->s_addr)
			return ((struct hostdata *)h);
	}

	/* not found.  Put it in */

	if (ipaddr->s_addr == htonl(INADDR_BROADCAST))
		return (addhost(AF_INET, ipaddr, "BROADCAST"));
	if (ipaddr->s_addr == htonl(INADDR_ANY))
		return (addhost(AF_INET, ipaddr, "OLD-BROADCAST"));

	/*
	 * Set an alarm here so we don't get held up by
	 * an unresponsive name server.
	 * Give it 3 sec to do its work.
	 */
	if (setjmp(nisjmp) == 0) {
		(void) signal(SIGALRM, wakeup);
		(void) alarm(3);
		hp = getipnodebyaddr((char *)ipaddr, sizeof (struct in_addr),
		    AF_INET, &error_num);
		if (hp == NULL && inet_lnaof(*ipaddr) == 0) {
			np = getnetbyaddr(inet_netof(*ipaddr), AF_INET);
			if (np)
				return (addhost(AF_INET, ipaddr, np->n_name));
		}
		(void) alarm(0);
	} else {
		hp = NULL;
	}

	return (addhost(AF_INET, ipaddr, hp ? hp->h_name : inet_ntoa(*ipaddr)));
}

static struct hostdata *
ip6lookup(ip6addr)
	struct in6_addr *ip6addr;
{
	struct hostdata6 *h;
	struct hostent *hp = NULL;
	int error_num;
	char addrstr[INET6_ADDRSTRLEN];
	char *addname;
	struct hostdata *retval;

	for (h = h_table6[iphash(((uint32_t *)ip6addr)[3])]; h;
	    h = h->h6_next) {
		if (IN6_ARE_ADDR_EQUAL(&h->h6_addr, ip6addr))
			return ((struct hostdata *)h);
	}

	/* not in the hash table, put it in */
	if (IN6_IS_ADDR_UNSPECIFIED(ip6addr))
		return (addhost(AF_INET6, ip6addr, "UNSPECIFIED"));

	/*
	 * Set an alarm here so we don't get held up by
	 * an unresponsive name server.
	 * Give it 3 sec to do its work.
	 */
	if (setjmp(nisjmp) == 0) {
		(void) signal(SIGALRM, wakeup);
		(void) alarm(3);
		hp = getipnodebyaddr(ip6addr, sizeof (struct in6_addr),
		    AF_INET6, &error_num);
		(void) alarm(0);
	} else {
		hp = NULL;
	}

	if (hp != NULL)
		addname = hp->h_name;
	else {
		(void) inet_ntop(AF_INET6, ip6addr, addrstr, INET6_ADDRSTRLEN);
		addname = addrstr;
	}

	retval = addhost(AF_INET6, ip6addr, addname);
	freehostent(hp);
	return (retval);
}

static struct hostdata *
addhost(family, ipaddr, name)
	int family;
	void *ipaddr;
	char *name;
{
	register struct hostdata **hp, *n;
	int hashval;

	switch (family) {
	case AF_INET:
		n = (struct hostdata *)malloc(sizeof (struct hostdata4));
		if (n == NULL)
			goto alloc_failed;

		(void) memset(n, 0, sizeof (struct hostdata4));
		n->h_hostname = strdup(name);
		if (n->h_hostname == NULL)
			goto alloc_failed;

		((struct hostdata4 *)n)->h4_addr = *(struct in_addr *)ipaddr;
		hashval = ((struct in_addr *)ipaddr)->s_addr;
		hp = (struct hostdata **)&h_table4[iphash(hashval)];
		break;
	case AF_INET6:
		n = (struct hostdata *)malloc(sizeof (struct hostdata6));
		if (n == NULL)
			goto alloc_failed;

		(void) memset(n, 0, sizeof (struct hostdata6));
		n->h_hostname = strdup(name);
		if (n->h_hostname == NULL)
			goto alloc_failed;

		(void) memcpy(&((struct hostdata6 *)n)->h6_addr, ipaddr,
		    sizeof (struct in6_addr));
		hashval = ((int *)ipaddr)[3];
		hp = (struct hostdata **)&h_table6[iphash(hashval)];
		break;
	default:
		(void) fprintf(stderr,
			"nfslog: addhost ERROR: Unknown address family: %d",
			family);
		return (NULL);
	}

	n->h_next = *hp;
	*hp = n;

	return (n);

alloc_failed:
	(void) fprintf(stderr, "addhost: no mem\n");
	if (n != NULL)
		free(n);
	return (NULL);
}

char *
addrtoname(void *sockp)
{
	struct hostdata *hostp;
	int family = ((struct sockaddr_in *)sockp)->sin_family;

	switch (family) {
	case AF_INET:
		hostp = iplookup(&((struct sockaddr_in *)sockp)->sin_addr);
		break;
	case AF_INET6:
		hostp = ip6lookup(&((struct sockaddr_in6 *)sockp)->sin6_addr);
		break;
	default:
		(void) fprintf(stderr, "nfslog: ERROR: unknown address " \
		    "family: %d\n", family);
		hostp = NULL;
	}
	return ((hostp != NULL) ? hostp->h_hostname : NULL);
}
