/* InATMARP tool */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/time.h>
#include <sys/signal.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/in_var.h>
#include <netnatm/natm.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <errno.h>

struct sockaddr_natm snatm = { sizeof(snatm), AF_NATM };
u_char buf[1024];
int s;

u_char llcsnap[8] = { 0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00, 0x08, 0x06 };

struct aar {
	u_int16_t hrd;		/* Hardware type (0x0013) */
	u_int16_t pro;		/* Protocol type (0x0800) */
	u_int8_t  shtl;		/* Type/Length of source ATM address */
	u_int8_t  sstl;		/* Type/Length of source ATM sub-address */
	u_int16_t op;		/* Operation code (8 or 9) */
	u_int8_t  spln;		/* Protocol source address length (4) */
	u_int8_t  thtl;		/* Type/Length of target ATM address */
	u_int8_t  tstl;		/* Type/Length of target ATM sub-address */
	u_int8_t  tpln;		/* Protocol target address length (4) */
	/* sha: source ATM address (empty) */
	/* ssa: source ATM sub-address (empty) */
	u_int8_t  spa[4];	/* Protocol source address */
	/* tha: target ATM address (empty) */
	/* tsa: target ATM sub-address (empty) */
	u_int8_t  tpa[4];	/* Protocol target address */
} *a;

struct ifreq ifr;
struct sockaddr_in *s_in;

int dump = 0;
int request = 0;
int reply = 0;
char *prog;
char *ifname;

void
dodump(len)
	int len;
{
	register int off;
	struct timeval tv;
	struct tm *t;
	time_t clk;
	struct in_addr ia;

	(void) gettimeofday(&tv, NULL);
	clk = (time_t)tv.tv_sec;
	t = localtime(&clk);
	printf("%02d:%02d:%02d.%06u ",
	       t->tm_hour,
	       t->tm_min,
	       t->tm_sec,
	       (u_int)tv.tv_usec);
	switch (a->op) {
	    case 8:
		printf("request: ");
		break;
	    case 9:
		printf("reply: ");
		break;
	}
	off = a->shtl & 0x3f;
	off += a->sstl & 0x3f;
	if ((a->shtl != 0) || (a->sstl != 0))
		printf("[%x+%x] ", a->shtl, a->sstl);
	else
		printf("[] ");
	bcopy(a->spa + off, &ia, 4);
	off += a->thtl & 0x3f;
	off += a->tstl & 0x3f;
	if ((a->thtl != 0) || (a->tstl != 0))
		printf("[%x+%x] ", a->thtl, a->tstl);
	else
		printf("[] ");
	printf("%s ", inet_ntoa(ia));
	bcopy(a->tpa + off, &ia, 4);
	if ((a->op == 8) && (ia.s_addr != INADDR_ANY))
		printf("?");
	printf("%s", inet_ntoa(ia));
	off += 4;
	if (a->tpa + off != buf + len)
		printf(" ...");
	printf("\n");
}

void
doreply(len)
	int len;
{
	register int cc, off;
	struct in_addr ia;

	if ((a->thtl & 0x3f) || (a->tstl & 0x3f))
		return;

	a->hrd = htons(a->hrd);
	a->pro = htons(a->pro);
	a->thtl = a->shtl;
	a->sstl = a->tstl;
	a->op = htons(9);
	a->shtl = 0;
	a->tstl = 0;
	off = a->thtl & 0x3f;
	off += a->tstl & 0x3f;
	if (off) {
		bcopy(a->spa + off, &ia, 4);
		bcopy(a->spa, a->tpa, off);
		bcopy(&ia, a->tpa + off, 4);
	} else
		bcopy(a->spa, a->tpa, 4);
	bcopy(&s_in->sin_addr, a->spa, 4);

	cc = sizeof(llcsnap) + sizeof(*a) + off;
	if (len < cc)
		return;
	len = cc;

	cc = write(s, buf, len);
	if (cc < 0) {
		perror("write(reply)");
		exit(1);
	}
	if (cc != len) {
		fprintf(stderr,
			"truncated write(reply) (%d != %d)\n",
			cc, len);
		exit(1);
	}
	if (dump) {
		printf("send reply\n");
		dodump(len);
	}
}

void
dorequest()
{
	register int len, cc;
	struct in_addr ia;

	(void)alarm(60*request);

	len = sizeof(llcsnap) + sizeof(*a);

	bzero(buf, sizeof(buf));
	ia.s_addr = INADDR_ANY;
	bcopy(llcsnap, buf, sizeof(llcsnap));
	a = (struct aar *)(buf + sizeof(llcsnap));
	a->hrd = htons(0x0013);
	a->pro = htons(0x0800);
	a->shtl = 0;
	a->sstl = 0;
	a->op = htons(8);
	a->spln = 4;
	a->thtl = 0;
	a->tstl = 0;
	a->tpln = 4;
	bcopy(&s_in->sin_addr, a->spa, 4);
	bcopy(&ia, a->tpa, 4);

	cc = write(s, buf, len);
	if (cc < 0) {
		perror("write(request)");
		exit(1);
	}
	if (cc != len) {
		fprintf(stderr,
			"truncated write(request) (%d != %d)\n",
			cc, len);
		exit(1);
	}
	if (dump) {
		printf("send request\n");
		dodump(len);
	}
}

main(argc, argv)
	int argc;
	char *argv[];
{
	register int cc;
	register char ch;
	extern char *optarg;
	extern int optind;

	prog = argv[0];
	while ((ch = getopt(argc, argv, "drq:i:p:v:")) != EOF) {
		switch (ch) {
		    case 'd':
			dump = 1;
			setlinebuf(stdout);
			break;
		    case 'r':
			reply = 1;
			break;
		    case 'q':
			request = atoi(optarg);
			if ((request > 0) && (request <= 60))
				break;
			fprintf(stderr,
				"%s: bad request interval (%d)\n",
				request);
			exit(1);
		    case 'i':
			ifname = optarg;
			break;
		    case 'p':
			snatm.snatm_vpi = atoi(optarg);
			break;
		    case 'v':
			snatm.snatm_vci = atoi(optarg);
			break;
		    default:
		    usage:
			fprintf(stderr,
	"usage: %s [dr] [q int] -i ifname [-p vpi] -v vci\n",
				prog);
			exit(1);
		}
	}
	if ((argc != optind) || (ifname == NULL) || (snatm.snatm_vci == 0))
		goto usage;

	strncpy(snatm.snatm_if, ifname, IFNAMSIZ);
	strncpy(ifr.ifr_name, ifname, IFNAMSIZ);

	if (reply || request) {
		if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
			perror("socket(inet)");
			exit(1);
		}

		if (ioctl(s, SIOCGIFADDR, (char *)&ifr) < 0) {
			perror("SIOCGIFADDR");
			exit(1);
		}

		s_in = (struct sockaddr_in *)&ifr.ifr_addr;

		(void) close(s);
	}

	if ((s = socket(AF_NATM, SOCK_STREAM, PROTO_NATMAAL5)) < 0) {
		perror("socket");
		exit(1);
	}


	if (connect(s, (struct sockaddr *)&snatm, sizeof(snatm)) < 0) {
		perror("connect");
		exit(1);
	}

	if (dump) {
		if (reply)
			printf("Waiting for InATMARP on %s (%s) from %d/%d\n",
			       ifname,
			       inet_ntoa(s_in->sin_addr),
			       snatm.snatm_vpi, snatm.snatm_vci);
		else
			printf("Waiting for InATMARP on %s from %d/%d\n",
			       ifname, snatm.snatm_vpi, snatm.snatm_vci);
	}

	if (request) {
		(void) signal(SIGALRM, dorequest);
		dorequest();
	}

	for (;;) {
		cc = read(s, (char *)buf, sizeof(buf));
		if (cc < 0) {
			perror("read");
			exit(1);
		}
		if (cc == 0) {
			fprintf(stderr, "closed ?\n");
			exit(1);
		}
		if (cc < sizeof(llcsnap) + sizeof(*a))
			continue;
		if (bcmp(buf, llcsnap, sizeof(llcsnap)) != 0)
			continue;
		a = (struct aar *)(buf + sizeof(llcsnap));
		a->hrd = ntohs(a->hrd);
		a->pro = ntohs(a->pro);
		a->op = ntohs(a->op);
		if ((a->hrd != 0x0013) ||
		    (a->pro != 0x0800) ||
		    ((a->op != 8) && (a->op != 9)) ||
		    (a->spln != 4) ||
		    (a->tpln != 4)) {
			if (dump)
				fprintf(stderr, "bad packet\n");
			continue;
		}
		if (dump)
			dodump(cc);
		if (reply && (a->op == 8))
			doreply(cc);
	}
}
