/*
 * $USAGI: ndp.c,v 1.2 2001/08/24 05:02:08 yoshfuji Exp $
 *
 * Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000 and 2001
 * USAGI/WIDE Project.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 *		This program is free software; you can redistribute it and/or
 *		modify it under the terms of the GNU General Public License
 *		as published by the Free Software Foundation; either version
 *		2 of the License, or (at your option) any later version.
 *
 * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
 *
 *
 * Changes:
 *
 * Rani Assaf <rani@magic.metawire.com> 980929:	resolve addresses
 */

#include <stdio.h>
#include <unistd.h>
#include <resolv.h>
#include "utils.h"
#include <sys/types.h>
#include <net/if.h>
#include <netdb.h>

static int pid;
static int cflag;
int resolve_hosts = 1;
static int tflag;
static int repeat;

static struct
{
	int family;
        int index;
	int state;
	int unused_only;
	inet_prefix pfx;
	int flushed;
	char *flushb;
	int flushp;
	int flushe;
	struct rtnl_handle *rth;
} filter;

#define W_ADDR	31
#define W_LL	17
#define W_IF	6

void ipneigh_reset_filter(void);
int do_dump(void);
int print_neigh(struct sockaddr_nl *, struct nlmsghdr *, void *);
static int flush_update(void);
static char *sec2str(unsigned long sec);
static const char * nud_state_n2a(__u8 state);
void usage(void);


int main(int argc, char **argv)
{
	int ch;
	int aflag = 0, dflag = 0, sflag = 0, Hflag = 0,
		pflag = 0, rflag = 0, Pflag = 0, Rflag = 0;

	pid = getpid();
	while ((ch = getopt(argc, argv, "acndfIiprstA:HPR")) != EOF)
		switch ((char)ch) {
		case 'a':
			aflag = 1;
			break;
		case 'c':
			cflag = 1;
			break;
		case 'd':
			dflag = 1;
			break;
		case 'I':
#if 0
			if (argc > 2)
				setdefif(argv[2]);
			getdefif(); /* always call it to print the result */
			exit(0);
#else
			errx(1, "Sorry, not implemented yet.");
#endif
		case 'i' :
#if 0
			argc -= optind;
			argv += optind;
			if (argc < 1)
				usage();

			ifinfo(argc, argv);
			exit(0);
#else
			errx(1, "Sorry, not implemented yet.");
#endif
		case 'n':
			resolve_hosts = 0;
			continue;
		case 'p':
			pflag = 1;
			break;
		case 'f' :
#if 0
			if (argc != 3)
				usage();
			file(argv[2]);
			exit(0);
#else
			errx(1, "Sorry, not implemented yet.");
#endif
		case 'r' :
			rflag = 1;
			break;
		case 's':
			sflag = 1;
			break;
		case 't':
			tflag = 1;
			break;
		case 'A':
#if 0
			aflag = 1;
			repeat = atoi(optarg);
			if (repeat < 0)
				usage();
#else
			errx(1, "Sorry, not implemented yet.");
#endif
			break;
		case 'H' :
			Hflag = 1;
			break;
		case 'P':
			Pflag = 1;
			break;
		case 'R':
			Rflag = 1;
			break;
		default:
			usage();
		}

	argc -= optind;
	argv += optind;

	if (aflag) {
		do_dump();
		exit(0);
	}
	if (cflag) {
#if 0
		flush();
#else
		errx(1, "Sorry, not implemented yet.");
#endif
		exit(0);
	}

	if (dflag) {
#if 0
		if (argc != 1)
			usage();
		delete(argv[0]);
#else
		errx(1, "Sorry, not implemented yet.");
#endif
		exit(0);
	}
	if (pflag) {
#if 0
		plist();
#else
		errx(1, "Sorry, not implemented yet.");
#endif
		exit(0);
	}
	if (rflag) {
#if 0
		rtrlist();
#else
		errx(1, "Sorry, not implemented yet.");
#endif
		exit(0);
	}
	if (sflag) {
#if 0
		if (argc < 2 || argc > 4)
			usage();
		exit(set(argc, argv) ? 1 : 0);
#else
		errx(1, "Sorry, not implemented yet.");
#endif
	}
	if (Hflag) {
#if 0
		harmonize_rtr();
#else
		errx(1, "Sorry, not implemented yet.");
#endif
		exit(0);
	}
	if (Pflag) {
#if 0
		pfx_flush();
#else
		errx(1, "Sorry, not implemented yet.");
#endif
		exit(0);
	}
	if (Rflag) {
#if 0
		rtr_flush();
#else
		errx(1, "Sorry, not implemented yet.");
#endif
		exit(0);
	}

	if (argc != 1)
		usage();
#if 0
	get(argv[0]);
#else
	errx(1, "Sorry, not implemented yet.");
#endif
	exit(0);
}

void usage(void)
{
	printf("usage: ndp hostname\n");
	printf("       ndp -a[nt]\n");
	printf("       ndp [-nt] -A wait\n");
	printf("       ndp -c[nt]\n");
	printf("       ndp -d[nt] hostname\n");
	printf("       ndp -f[nt] filename\n");
	printf("       ndp -i interface [flags...]\n");
	printf("       ndp -I [interface|delete]\n");
	printf("       ndp -p\n");
	printf("       ndp -r\n");
	printf("       ndp -s hostname ether_addr [temp] [proxy]\n");
	printf("       ndp -H\n");
	printf("       ndp -P\n");
	printf("       ndp -R\n");
	exit(1);
}


void dump()
{
}

void delete()
{
}

void plist()
{
}

void rtrlist()
{
}

void set()
{
}

void harmonize_rtr()
{
}

void pfx_flush()
{
}

void rtr_flush()
{
}

void get()
{
}

void flush()
{
}


int do_dump(void)
{
	char *filter_dev = NULL;
	struct rtnl_handle rth;
	int state_given = 0;

	ipneigh_reset_filter();

	if (!filter.family)
		filter.family = PF_INET6;

	if (flush) {
		filter.state = ~(NUD_PERMANENT|NUD_NOARP);
	} else
		filter.state = 0xFF & ~NUD_NOARP;

	if (rtnl_open(&rth, 0) < 0)
		exit(1);

	ll_init_map(&rth);

	if (rtnl_wilddump_request(&rth, filter.family, RTM_GETNEIGH) < 0) {
		perror("Cannot send dump request");
		exit(1);
	}

	printf("%-*.*s %-*.*s %*.*s %-9.9s %2s %4s %4s\n",
		W_ADDR, W_ADDR, "Neighbor", W_LL, W_LL, "Linklayer Address",
		W_IF, W_IF, "Netif", "Expire", "St", "Flgs", "Prbs");

	if (rtnl_dump_filter(&rth, print_neigh, stdout, NULL, NULL) < 0) {
		fprintf(stderr, "Dump terminated\n");
		exit(1);
	}

	return 0;
}

void ipneigh_reset_filter(void)
{
	memset(&filter, 0, sizeof(filter));
	filter.state = ~0;
}

int print_neigh(struct sockaddr_nl *who, struct nlmsghdr *n, void *arg)
{
	FILE *fp = (FILE*)arg;
	struct ndmsg *r = NLMSG_DATA(n);
	int len = n->nlmsg_len;
	struct rtattr *tb[NDA_MAX+1];
	char host_buf[NI_MAXHOST], *host = "";
	char lladdr_buf[NI_MAXHOST], *lladdr = "";
	char ifname_buf[IF_NAMESIZE], *ifname = "";
	int addrwidth, llwidth, ifwidth;
	
	if (n->nlmsg_type != RTM_NEWNEIGH && n->nlmsg_type != RTM_DELNEIGH) {
		fprintf(stderr, "Not RTM_NEWNEIGH: %08x %08x %08x\n",
			n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
		
		return 0;
	}
	len -= NLMSG_LENGTH(sizeof(*r));

	if (len < 0) {
		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
		return -1;
	}

	if (filter.flushb && n->nlmsg_type != RTM_NEWNEIGH)
		return 0;

	if (filter.family && filter.family != r->ndm_family)
		return 0;
	if (filter.index && filter.index != r->ndm_ifindex)
		return 0;
	if (!(filter.state&r->ndm_state) &&
	    (r->ndm_state || !(filter.state&0x100)) &&
             (r->ndm_family != AF_DECnet))
		return 0;

	memset(tb, 0, sizeof(tb));
	parse_rtattr(tb, NDA_MAX, NDA_RTA(r), n->nlmsg_len - NLMSG_LENGTH(sizeof(*r)));

	if (tb[NDA_DST]) {
		if (filter.pfx.family) {
			inet_prefix dst;
			memset(&dst, 0, sizeof(dst));
			dst.family = r->ndm_family;
			memcpy(&dst.data, RTA_DATA(tb[NDA_DST]), RTA_PAYLOAD(tb[NDA_DST]));
			if (inet_addr_match(&dst, &filter.pfx, filter.pfx.bitlen))
				return 0;
		}
	}
	if (filter.unused_only && tb[NDA_CACHEINFO]) {
		struct nda_cacheinfo *ci = RTA_DATA(tb[NDA_CACHEINFO]);
		if (ci->ndm_refcnt)
			return 0;
	}

	if (filter.flushb) {
		struct nlmsghdr *fn;
		if (NLMSG_ALIGN(filter.flushp) + n->nlmsg_len > filter.flushe) {
			if (flush_update())
				return -1;
		}
		fn = (struct nlmsghdr*)(filter.flushb + NLMSG_ALIGN(filter.flushp));
		memcpy(fn, n, n->nlmsg_len);
		fn->nlmsg_type = RTM_DELNEIGH;
		fn->nlmsg_flags = NLM_F_REQUEST;
		fn->nlmsg_seq = ++filter.rth->seq;
		filter.flushp = (((char*)fn) + n->nlmsg_len) - filter.flushb;
		filter.flushed++;
	}

	if (tb[NDA_DST]) {
		host = (char *)format_host(r->ndm_family,
				   RTA_PAYLOAD(tb[NDA_DST]),
				   RTA_DATA(tb[NDA_DST]),
				   host_buf, sizeof(host_buf));
		addrwidth = strlen(host);
	}
	if (addrwidth < W_ADDR)
		addrwidth = W_ADDR;

	if (tb[NDA_LLADDR]) {
		lladdr = (char *)ll_addr_n2a(RTA_DATA(tb[NDA_LLADDR]),
				   RTA_PAYLOAD(tb[NDA_LLADDR]),
				   ll_index_to_type(r->ndm_ifindex),
				   lladdr_buf, sizeof(lladdr_buf));
		llwidth = strlen(lladdr);
	}
	if (W_ADDR + W_LL - addrwidth > llwidth)
		llwidth = W_ADDR + W_LL - addrwidth;

	if (r->ndm_ifindex) {
		ifname = if_indextoname(r->ndm_ifindex, ifname_buf);
		if (!ifname)
			ifname = "?";
	}
	ifwidth = strlen(ifname);
	if (W_ADDR + W_LL + W_IF - addrwidth - llwidth > ifwidth)
		ifwidth = W_ADDR + W_LL + W_IF - addrwidth - llwidth;

	fprintf(fp, "%-*.*s %-*.*s %*.*s", addrwidth, addrwidth, host_buf,
		llwidth, llwidth, lladdr, ifwidth, ifwidth, ifname);

#ifdef HAVE_STRUCT_NDA_CACHEINFO_NDM_EXPIRES
	if (tb[NDA_CACHEINFO]) {
		static int lifetime;
		struct nda_cacheinfo *ci = RTA_DATA(tb[NDA_CACHEINFO]);

		lifetime = ci->ndm_expires;
		fprintf(fp, " %-9.9s ", sec2str(lifetime/HZ));
	} else {
		fprintf(fp, " %-9.9s ", "unknown");
	}
#else
	/* we wish we had ndm_expires in nda_cacheinfo */
	fprintf(fp, " %-9.9s ", "unknown");
#endif

	fprintf(fp, " %-2s", nud_state_n2a(r->ndm_state));

	if (r->ndm_flags & NTF_ROUTER)
		fprintf(fp, " R");
	else
		fprintf(fp, "  ");

	fprintf(fp, "\n");

	fflush(fp);
	return 0;
}

static int flush_update(void)
{
	if (rtnl_send(filter.rth, filter.flushb, filter.flushp) < 0) {
		perror("Failed to send flush request\n");
		return -1;
	}
	filter.flushp = 0;
	return 0;
}

static char *sec2str(unsigned long s) {
	static char buff[128];
	unsigned long d, h, m;
	char *p = buff;

	m = s / 60;
	h = m / 60;
	d = h / 24;

	if (d)
		p += sprintf(p, "%lud", d);
	if (h)
		p += sprintf(p, "%luh", h % 24);
	if (m)
		p += sprintf(p, "%lum", m % 60);
	sprintf(p, "%lus", s % 60);

	return(buff);
}

static const char * nud_state_n2a(__u8 state)
{
	static char buf[128];
	switch (state) {
	case NUD_NONE:	
		return "-";
	case NUD_INCOMPLETE:	
		return "I";
	case NUD_REACHABLE:	
		return "R";
	case NUD_STALE:	
		return "S";
	case NUD_DELAY:	
		return "D";
	case NUD_PROBE:	
		return "P";
	case NUD_FAILED:	
		return "F";
	case NUD_NOARP:	
		return "n";
	case NUD_PERMANENT:	
		return "p";
	default:	
		sprintf(buf, "%02x", state);
		return buf;
	}
}

