/*
 * Copyright (c) 2003
 *	Fraunhofer Institute for Open Communication Systems (FhG Fokus).
 * 	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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
 *
 * Author: Hartmut Brandt <harti@freebsd.org>
 */
#include <sys/cdefs.h>
__FBSDID("$FreeBSD: release/9.1.0/sbin/atm/atmconfig/natm.c 132800 2004-07-28 16:32:17Z harti $");

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <net/if_atm.h>
#include <net/if_dl.h>
#include <net/route.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "atmconfig.h"
#include "private.h"
#include "diag.h"

static void natm_add(int, char *[]);
static void natm_delete(int, char *[]);
static void natm_show(int, char *[]);

const struct cmdtab natm_tab[] = {
	{ "add",	NULL,		natm_add },
	{ "delete",	NULL,		natm_delete },
	{ "show",	NULL,		natm_show },
	{ NULL, 	NULL, 		NULL }
};

/*
 * Structure to hold a route
 */
struct natm_route {
	TAILQ_ENTRY(natm_route) link;
	struct in_addr	host;
	struct diagif	*aif;
	u_int		flags;
	int		llcsnap;
	u_int		vpi, vci;
	u_int		traffic;
	u_int		pcr, scr, mbs, icr, mcr;
	u_int		tbe, nrm, trm, adtf, rif, rdf, cdf;
};
static TAILQ_HEAD(, natm_route) natm_route_list =
    TAILQ_HEAD_INITIALIZER(natm_route_list);

static void
store_route(struct rt_msghdr *rtm)
{
	u_int i;
	struct natm_route *r;
	char *cp;
	struct sockaddr *sa;
	struct sockaddr_in *sain;
	struct sockaddr_dl *sdl;
	struct diagif *aif;
	u_int n;

	r = malloc(sizeof(*r));
	if (r == NULL)
		err(1, "allocate route");

	r->flags = rtm->rtm_flags;
	cp = (char *)(rtm + 1);
	for (i = 1; i != 0; i <<= 1) {
		if (rtm->rtm_addrs & i) {
			sa = (struct sockaddr *)cp;
			cp += roundup(sa->sa_len, sizeof(long));
			switch (i) {

			  case RTA_DST:
				if (sa->sa_family != AF_INET) {
					warnx("RTA_DST not AF_INET %u", sa->sa_family);
					goto fail;
				}
				sain = (struct sockaddr_in *)(void *)sa;
				if (sain->sin_len < 4)
					r->host.s_addr = INADDR_ANY;
				else
					r->host = sain->sin_addr;
				break;

			  case RTA_GATEWAY:
				if (sa->sa_family != AF_LINK) {
					warnx("RTA_GATEWAY not AF_LINK");
					goto fail;
				}
				sdl = (struct sockaddr_dl *)(void *)sa;
				TAILQ_FOREACH(aif, &diagif_list, link)
					if (strlen(aif->ifname) ==
					    sdl->sdl_nlen &&
					    strncmp(aif->ifname, sdl->sdl_data,
					    sdl->sdl_nlen) == 0)
						break;
				if (aif == NULL) {
					warnx("interface '%.*s' not found",
					    sdl->sdl_nlen, sdl->sdl_data);
					goto fail;
				}
				r->aif = aif;

				/* parse ATM stuff */

#define	GET3()	(((sdl->sdl_data[n] & 0xff) << 16) |	\
		 ((sdl->sdl_data[n + 1] & 0xff) << 8) |	\
		 ((sdl->sdl_data[n + 2] & 0xff) << 0))
#define	GET2()	(((sdl->sdl_data[n] & 0xff) << 8) |	\
		 ((sdl->sdl_data[n + 1] & 0xff) << 0))
#define	GET1()	(((sdl->sdl_data[n] & 0xff) << 0))

				n = sdl->sdl_nlen;
				if (sdl->sdl_alen < 4) {
					warnx("RTA_GATEWAY alen too short");
					goto fail;
				}
				r->llcsnap = GET1() & ATM_PH_LLCSNAP;
				n++;
				r->vpi = GET1();
				n++;
				r->vci = GET2();
				n += 2;
				if (sdl->sdl_alen == 4) {
					/* old address */
					r->traffic = ATMIO_TRAFFIC_UBR;
					r->pcr = 0;
					break;
				}
				/* new address */
				r->traffic = GET1();
				n++;
				switch (r->traffic) {

				  case ATMIO_TRAFFIC_UBR:
					if (sdl->sdl_alen >= 5 + 3) {
						r->pcr = GET3();
						n += 3;
					} else
						r->pcr = 0;
					break;

				  case ATMIO_TRAFFIC_CBR:
					if (sdl->sdl_alen < 5 + 3) {
						warnx("CBR address too short");
						goto fail;
					}
					r->pcr = GET3();
					n += 3;
					break;

				  case ATMIO_TRAFFIC_VBR:
					if (sdl->sdl_alen < 5 + 3 * 3) {
						warnx("VBR address too short");
						goto fail;
					}
					r->pcr = GET3();
					n += 3;
					r->scr = GET3();
					n += 3;
					r->mbs = GET3();
					n += 3;
					break;

				  case ATMIO_TRAFFIC_ABR:
					if (sdl->sdl_alen < 5 + 4 * 3 + 2 +
					    1 * 2 + 3) {
						warnx("ABR address too short");
						goto fail;
					}
					r->pcr = GET3();
					n += 3;
					r->mcr = GET3();
					n += 3;
					r->icr = GET3();
					n += 3;
					r->tbe = GET3();
					n += 3;
					r->nrm = GET1();
					n++;
					r->trm = GET1();
					n++;
					r->adtf = GET2();
					n += 2;
					r->rif = GET1();
					n++;
					r->rdf = GET1();
					n++;
					r->cdf = GET1();
					n++;
					break;

				  default:
					goto fail;
				}
				break;
			}
		}
	}

	TAILQ_INSERT_TAIL(&natm_route_list, r, link);

	return;
  fail:
	free(r);
}

/*
 * Fetch the INET routes that a ours
 */
static void
natm_route_fetch(void)
{
	int name[6];
	size_t needed;
	u_char *buf, *next;
	struct rt_msghdr *rtm;

	name[0] = CTL_NET;
	name[1] = PF_ROUTE;
	name[2] = 0;
	name[3] = AF_INET;
	name[4] = NET_RT_DUMP;
	name[5] = 0;

	if (sysctl(name, 6, NULL, &needed, NULL, 0) == -1)
		err(1, "rtable estimate");
	needed *= 2;
	if ((buf = malloc(needed)) == NULL)
		err(1, "rtable buffer (%zu)", needed);
	if (sysctl(name, 6, buf, &needed, NULL, 0) == -1)
		err(1, "rtable get");

	next = buf;
	while (next < buf + needed) {
		rtm = (struct rt_msghdr *)(void *)next;
		next += rtm->rtm_msglen;

		if (rtm->rtm_type == RTM_GET) {
			if ((rtm->rtm_flags & (RTF_UP | RTF_HOST |
			    RTF_STATIC)) == (RTF_UP | RTF_HOST | RTF_STATIC) &&
			    (rtm->rtm_addrs & (RTA_DST | RTA_GATEWAY |
			    RTA_IFP)) == (RTA_DST | RTA_GATEWAY | RTA_IFP))
				store_route(rtm);
		}
	}
}

static u_long
parse_num(const char *arg, const char *name, u_long limit)
{
	u_long res;
	char *end;

	errno = 0;
	res = strtoul(arg, &end, 10);
	if (*end != '\0' || end == arg || errno != 0)
		errx(1, "cannot parse %s '%s'", name, arg);
	if (res > limit)
		errx(1, "%s out of range (0...%lu)", name, limit);
	return (res);
}

static void
do_route(u_int type, u_int flags, const struct sockaddr_in *sain,
    const struct sockaddr_dl *sdl)
{
	struct {
		struct rt_msghdr h;
		char	space[512];
	} msg;
	char *ptr;
	int s;
	ssize_t rlen;

	/* create routing message */
	bzero(&msg, sizeof(msg));
	msg.h.rtm_msglen = sizeof(msg.h);
	msg.h.rtm_version = RTM_VERSION;
	msg.h.rtm_type = type;
	msg.h.rtm_index = 0;
	msg.h.rtm_flags = flags;
	msg.h.rtm_addrs = RTA_DST | (sdl != NULL ? RTA_GATEWAY : 0);
	msg.h.rtm_pid = getpid();

	ptr = (char *)&msg + sizeof(msg.h);
	memcpy(ptr, sain, sain->sin_len);
	ptr += roundup(sain->sin_len, sizeof(long));
	msg.h.rtm_msglen += roundup(sain->sin_len, sizeof(long));

	if (sdl != NULL) {
		memcpy(ptr, sdl, sdl->sdl_len);
		ptr += roundup(sdl->sdl_len, sizeof(long));
		msg.h.rtm_msglen += roundup(sdl->sdl_len, sizeof(long));
	}

	/* open socket */
	s = socket(PF_ROUTE, SOCK_RAW, AF_INET);
	if (s == -1)
		err(1, "cannot open routing socket");

	rlen = write(s, &msg, msg.h.rtm_msglen);
	if (rlen == -1)
		err(1, "writing to routing socket");
	if ((size_t)rlen != msg.h.rtm_msglen)
		errx(1, "short write to routing socket: %zu %u",
		    (size_t)rlen, msg.h.rtm_msglen);
	close(s);
}

/*
 * Add a new NATM route
 */
static void
natm_add(int argc, char *argv[])
{
	int opt;
	struct hostent *hp;
	struct sockaddr_in sain;
	struct sockaddr_dl sdl;
	struct diagif *aif;
	u_long num, num1;
	u_int idx;

	static int printonly;

	static const struct option opts[] = {
	    { "printonly", OPT_SIMPLE, &printonly },
	    { NULL, 0, NULL }
	};

	while ((opt = parse_options(&argc, &argv, opts)) != -1)
		switch (opt) {
		}

	if (argc < 5)
		errx(1, "missing arguments for 'natm add'");

	memset(&sdl, 0, sizeof(sdl));
	sdl.sdl_len = sizeof(sdl);
	sdl.sdl_family = AF_LINK;

	/* get the IP address for <dest> */
	memset(&sain, 0, sizeof(sain));
	hp = gethostbyname(argv[0]);
	if (hp == NULL)
		errx(1, "bad hostname %s: %s", argv[0], hstrerror(h_errno));
	if (hp->h_addrtype != AF_INET)
		errx(1, "bad address type for %s", argv[0]);
	sain.sin_len = sizeof(sain);
	sain.sin_family = AF_INET;
	memcpy(&sain.sin_addr, hp->h_addr, sizeof(sain.sin_addr));

	/* find interface */
	diagif_fetch();
	TAILQ_FOREACH(aif, &diagif_list, link)
		if (strcmp(aif->ifname, argv[1]) == 0)
			break;
	if (aif == NULL)
		errx(1, "unknown ATM interface '%s'", argv[1]);
	sdl.sdl_index = aif->index;
	strcpy(sdl.sdl_data, aif->ifname);
	idx = sdl.sdl_nlen = strlen(aif->ifname);
	idx++;

	/* verify VPI/VCI */
	num = parse_num(argv[2], "VPI", (1U << aif->mib.vpi_bits));
	sdl.sdl_data[idx++] = num & 0xff;
	num = parse_num(argv[3], "VCI", (1U << aif->mib.vci_bits));
	if (num == 0)
		errx(1, "VCI may not be 0");
	sdl.sdl_data[idx++] = (num >> 8) & 0xff;
	sdl.sdl_data[idx++] = num & 0xff;

	/* encapsulation */
	if (strcasecmp(argv[4], "llc/snap") == 0) {
		sdl.sdl_data[sdl.sdl_nlen] = ATM_PH_LLCSNAP;
	} else if (strcasecmp(argv[4], "aal5") == 0) {
		sdl.sdl_data[sdl.sdl_nlen] = 0;
	} else
		errx(1, "bad encapsulation type '%s'", argv[4]);

	/* look at the traffic */
	argc -= 5;
	argv += 5;

	if (argc != 0) {
		if (strcasecmp(argv[0], "ubr") == 0) {
			sdl.sdl_data[idx++] = ATMIO_TRAFFIC_UBR;
			if (argc == 1)
				/* ok */;
			else if (argc == 2) {
				num = parse_num(argv[1], "PCR", aif->mib.pcr);
				sdl.sdl_data[idx++] = (num >> 16) & 0xff;
				sdl.sdl_data[idx++] = (num >>  8) & 0xff;
				sdl.sdl_data[idx++] = (num >>  0) & 0xff;
			} else
				errx(1, "too many parameters for UBR");

		} else if (strcasecmp(argv[0], "cbr") == 0) {
			sdl.sdl_data[idx++] = ATMIO_TRAFFIC_CBR;
			if (argc == 1)
				errx(1, "missing PCR for CBR");
			if (argc > 2)
				errx(1, "too many parameters for CBR");
			num = parse_num(argv[1], "PCR", aif->mib.pcr);
			sdl.sdl_data[idx++] = (num >> 16) & 0xff;
			sdl.sdl_data[idx++] = (num >>  8) & 0xff;
			sdl.sdl_data[idx++] = (num >>  0) & 0xff;

		} else if (strcasecmp(argv[0], "vbr") == 0) {
			sdl.sdl_data[idx++] = ATMIO_TRAFFIC_VBR;

			if (argc < 4)
				errx(1, "missing arg(s) for VBR");
			if (argc > 4)
				errx(1, "too many parameters for VBR");

			num = parse_num(argv[1], "PCR", aif->mib.pcr);
			sdl.sdl_data[idx++] = (num >> 16) & 0xff;
			sdl.sdl_data[idx++] = (num >>  8) & 0xff;
			sdl.sdl_data[idx++] = (num >>  0) & 0xff;
			num = parse_num(argv[2], "SCR", num);
			sdl.sdl_data[idx++] = (num >> 16) & 0xff;
			sdl.sdl_data[idx++] = (num >>  8) & 0xff;
			sdl.sdl_data[idx++] = (num >>  0) & 0xff;
			num = parse_num(argv[3], "MBS", 0xffffffLU);
			sdl.sdl_data[idx++] = (num >> 16) & 0xff;
			sdl.sdl_data[idx++] = (num >>  8) & 0xff;
			sdl.sdl_data[idx++] = (num >>  0) & 0xff;

		} else if (strcasecmp(argv[0], "abr") == 0) {
			sdl.sdl_data[idx++] = ATMIO_TRAFFIC_ABR;
			if (argc < 11)
				errx(1, "missing arg(s) for ABR");
			if (argc > 11)
				errx(1, "too many parameters for ABR");

			num = parse_num(argv[1], "PCR", aif->mib.pcr);
			sdl.sdl_data[idx++] = (num >> 16) & 0xff;
			sdl.sdl_data[idx++] = (num >>  8) & 0xff;
			sdl.sdl_data[idx++] = (num >>  0) & 0xff;

			num1 = parse_num(argv[2], "MCR", num);
			sdl.sdl_data[idx++] = (num1 >> 16) & 0xff;
			sdl.sdl_data[idx++] = (num1 >>  8) & 0xff;
			sdl.sdl_data[idx++] = (num1 >>  0) & 0xff;

			num = parse_num(argv[3], "ICR", num);
			sdl.sdl_data[idx++] = (num >> 16) & 0xff;
			sdl.sdl_data[idx++] = (num >>  8) & 0xff;
			sdl.sdl_data[idx++] = (num >>  0) & 0xff;

			if (num < num1)
				errx(1, "ICR must be >= MCR");

			num = parse_num(argv[4], "TBE", 0xffffffUL);
			sdl.sdl_data[idx++] = (num >> 16) & 0xff;
			sdl.sdl_data[idx++] = (num >>  8) & 0xff;
			sdl.sdl_data[idx++] = (num >>  0) & 0xff;

			num = parse_num(argv[5], "NRM", 0x7UL);
			sdl.sdl_data[idx++] = (num >>  0) & 0xff;

			num = parse_num(argv[6], "TRM", 0x7UL);
			sdl.sdl_data[idx++] = (num >>  0) & 0xff;

			num = parse_num(argv[7], "ADTF", 0x3ffUL);
			sdl.sdl_data[idx++] = (num >>  8) & 0xff;
			sdl.sdl_data[idx++] = (num >>  0) & 0xff;

			num = parse_num(argv[8], "RIF", 0xfUL);
			sdl.sdl_data[idx++] = (num >>  0) & 0xff;

			num = parse_num(argv[9], "RDF", 0xfUL);
			sdl.sdl_data[idx++] = (num >>  0) & 0xff;

			num = parse_num(argv[10], "CDF", 0x7UL);
			sdl.sdl_data[idx++] = (num >>  0) & 0xff;

		} else
			errx(1, "bad traffic type '%s'", argv[0]);
	} else
		sdl.sdl_data[idx++] = ATMIO_TRAFFIC_UBR;

	sdl.sdl_alen = idx - sdl.sdl_nlen;
	sdl.sdl_len += sdl.sdl_nlen + sdl.sdl_alen;

	if (printonly) {
		printf("route add -iface %s -link %.*s",
		    inet_ntoa(sain.sin_addr), sdl.sdl_nlen, sdl.sdl_data);
		for (idx = 0; idx < sdl.sdl_alen; idx++)
			printf("%c%x", ".:"[idx == 0],
			    (u_int)sdl.sdl_data[sdl.sdl_nlen + idx] & 0xffU);
		printf("\n");
		exit(0);
	}

	do_route(RTM_ADD, RTF_HOST | RTF_STATIC | RTF_UP, &sain, &sdl);
}

/*
 * Delete an NATM route
 */
static void
natm_delete(int argc, char *argv[])
{
	int opt;
	struct hostent *hp;
	struct sockaddr_in sain;
	u_int vpi, vci;
	struct diagif *aif;
	struct natm_route *r;

	static int printonly;

	static const struct option opts[] = {
	    { "printonly", OPT_SIMPLE, &printonly },
	    { NULL, 0, NULL }
	};

	while ((opt = parse_options(&argc, &argv, opts)) != -1)
		switch (opt) {
		}

	diagif_fetch();
	natm_route_fetch();

	memset(&sain, 0, sizeof(sain));
	sain.sin_len = sizeof(sain);
	sain.sin_family = AF_INET;

	if (argc == 1) {
		/* get the IP address for <dest> */
		hp = gethostbyname(argv[0]);
		if (hp == NULL)
			errx(1, "bad hostname %s: %s", argv[0],
			    hstrerror(h_errno));
		if (hp->h_addrtype != AF_INET)
			errx(1, "bad address type for %s", argv[0]);
		memcpy(&sain.sin_addr, hp->h_addr, sizeof(sain.sin_addr));

		TAILQ_FOREACH(r, &natm_route_list, link)
			if (r->host.s_addr == sain.sin_addr.s_addr)
				break;
		if (r == NULL)
			errx(1, "no NATM route to host '%s' (%s)", argv[0],
			    inet_ntoa(sain.sin_addr));

	} else if (argc == 3) {
		TAILQ_FOREACH(aif, &diagif_list, link)
			if (strcmp(aif->ifname, argv[0]) == 0)
				break;
		if (aif == 0)
			errx(1, "no such interface '%s'", argv[0]);

		vpi = parse_num(argv[1], "VPI", 0xff);
		vci = parse_num(argv[2], "VCI", 0xffff);

		TAILQ_FOREACH(r, &natm_route_list, link)
			if (r->aif == aif && r->vpi == vpi && r->vci == vci)
				break;
		if (r == NULL)
			errx(1, "no such NATM route %s %u %u", argv[0],
			    vpi, vci);
		sain.sin_addr = r->host;

	} else
		errx(1, "bad number of arguments for 'natm delete'");

	if (printonly) {
		printf("route delete %s\n", inet_ntoa(r->host));
		exit(0);
	}

	do_route(RTM_DELETE, r->flags, &sain, NULL);
}

/*
 * Show NATM routes
 */
static void
natm_show(int argc, char *argv[])
{
	int opt;
	struct natm_route *r;
	struct hostent *hp;

	static const char *const traffics[] = {
		[ATMIO_TRAFFIC_UBR] = "UBR",
		[ATMIO_TRAFFIC_CBR] = "CBR",
		[ATMIO_TRAFFIC_VBR] = "VBR",
		[ATMIO_TRAFFIC_ABR] = "ABR"
	};

	static int numeric, abr;

	static const struct option opts[] = {
	    { "abr", OPT_SIMPLE, &abr },
	    { "numeric", OPT_SIMPLE, &numeric },
	    { NULL, 0, NULL }
	};

	static const char head[] =
	    "Destination         Iface       VPI VCI   Encaps   Trf PCR     "
	    "SCR/MCR MBS/ICR\n";
	static const char head_abr[] =
	    "Destination         Iface       VPI VCI   Encaps   Trf PCR     "
	    "SCR/MCR MBS/ICR TBE     NRM TRM ADTF RIF RDF CDF\n";

	while ((opt = parse_options(&argc, &argv, opts)) != -1)
		switch (opt) {
		}

	diagif_fetch();
	natm_route_fetch();

	heading_init();
	TAILQ_FOREACH(r, &natm_route_list, link) {
		heading(abr ? head_abr : head);
		if (numeric)
			printf("%-20s", inet_ntoa(r->host));
		else if (r->host.s_addr == INADDR_ANY)
			printf("%-20s", "default");
		else {
			hp = gethostbyaddr((char *)&r->host, sizeof(r->host),
			    AF_INET);
			if (hp != NULL)
				printf("%-20s", hp->h_name);
			else
				printf("%-20s", inet_ntoa(r->host));
		}
		printf("%-12s%-4u%-6u%-9s%-4s", r->aif->ifname, r->vpi, r->vci,
		    r->llcsnap ? "LLC/SNAP" : "AAL5", traffics[r->traffic]);
		switch (r->traffic) {

		  case ATMIO_TRAFFIC_UBR:
		  case ATMIO_TRAFFIC_CBR:
			printf("%-8u", r->pcr);
			break;

		  case ATMIO_TRAFFIC_VBR:
			printf("%-8u%-8u%-8u", r->pcr, r->scr, r->mbs);
			break;

		  case ATMIO_TRAFFIC_ABR:
			printf("%-8u%-8u%-8u", r->pcr, r->mcr, r->icr);
			if (abr)
				printf("%-8u%-4u%-4u%-5u%-4u%-4u%-4u",
				    r->tbe, r->nrm, r->trm, r->adtf,
				    r->rif, r->rdf, r->cdf);
			break;
		}
		printf("\n");
	}
}
