Initial import from ToT git.

Bug: 9469682
Change-Id: I6fc32550557dc5e94e9ecbbb57b0ec30a844adb4
Upstream: git://git.linux-ipv6.org/gitroot/iputils.git
Commit: 608419a7804caf36a359875d2fdae0b3eb181387
diff --git a/tracepath.c b/tracepath.c
new file mode 100644
index 0000000..be90231
--- /dev/null
+++ b/tracepath.c
@@ -0,0 +1,453 @@
+/*
+ * tracepath.c
+ *
+ *		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>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <linux/types.h>
+#include <linux/errqueue.h>
+#include <errno.h>
+#include <string.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <resolv.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+#include <arpa/inet.h>
+#ifdef USE_IDN
+#include <idna.h>
+#include <locale.h>
+#endif
+
+#ifndef IP_PMTUDISC_PROBE
+#define IP_PMTUDISC_PROBE	3
+#endif
+
+#define MAX_HOPS_LIMIT		255
+#define MAX_HOPS_DEFAULT	30
+
+struct hhistory
+{
+	int	hops;
+	struct timeval sendtime;
+};
+
+struct hhistory his[64];
+int hisptr;
+
+struct sockaddr_in target;
+__u16 base_port;
+int max_hops = MAX_HOPS_DEFAULT;
+
+const int overhead = 28;
+int mtu = 65535;
+void *pktbuf;
+int hops_to = -1;
+int hops_from = -1;
+int no_resolve = 0;
+int show_both = 0;
+
+#define HOST_COLUMN_SIZE	52
+
+struct probehdr
+{
+	__u32 ttl;
+	struct timeval tv;
+};
+
+void data_wait(int fd)
+{
+	fd_set fds;
+	struct timeval tv;
+	FD_ZERO(&fds);
+	FD_SET(fd, &fds);
+	tv.tv_sec = 1;
+	tv.tv_usec = 0;
+	select(fd+1, &fds, NULL, NULL, &tv);
+}
+
+void print_host(const char *a, const char *b, int both)
+{
+	int plen;
+	plen = printf("%s", a);
+	if (both)
+		plen += printf(" (%s)", b);
+	if (plen >= HOST_COLUMN_SIZE)
+		plen = HOST_COLUMN_SIZE - 1;
+	printf("%*s", HOST_COLUMN_SIZE - plen, "");
+}
+
+int recverr(int fd, int ttl)
+{
+	int res;
+	struct probehdr rcvbuf;
+	char cbuf[512];
+	struct iovec  iov;
+	struct msghdr msg;
+	struct cmsghdr *cmsg;
+	struct sock_extended_err *e;
+	struct sockaddr_in addr;
+	struct timeval tv;
+	struct timeval *rettv;
+	int slot;
+	int rethops;
+	int sndhops;
+	int progress = -1;
+	int broken_router;
+
+restart:
+	memset(&rcvbuf, -1, sizeof(rcvbuf));
+	iov.iov_base = &rcvbuf;
+	iov.iov_len = sizeof(rcvbuf);
+	msg.msg_name = (__u8*)&addr;
+	msg.msg_namelen = sizeof(addr);
+	msg.msg_iov = &iov;
+	msg.msg_iovlen = 1;
+	msg.msg_flags = 0;
+	msg.msg_control = cbuf;
+	msg.msg_controllen = sizeof(cbuf);
+
+	gettimeofday(&tv, NULL);
+	res = recvmsg(fd, &msg, MSG_ERRQUEUE);
+	if (res < 0) {
+		if (errno == EAGAIN)
+			return progress;
+		goto restart;
+	}
+
+	progress = mtu;
+
+	rethops = -1;
+	sndhops = -1;
+	e = NULL;
+	rettv = NULL;
+	slot = ntohs(addr.sin_port) - base_port;
+	if (slot>=0 && slot < 63 && his[slot].hops) {
+		sndhops = his[slot].hops;
+		rettv = &his[slot].sendtime;
+		his[slot].hops = 0;
+	}
+	broken_router = 0;
+	if (res == sizeof(rcvbuf)) {
+		if (rcvbuf.ttl == 0 || rcvbuf.tv.tv_sec == 0) {
+			broken_router = 1;
+		} else {
+			sndhops = rcvbuf.ttl;
+			rettv = &rcvbuf.tv;
+		}
+	}
+
+	for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+		if (cmsg->cmsg_level == SOL_IP) {
+			if (cmsg->cmsg_type == IP_RECVERR) {
+				e = (struct sock_extended_err *) CMSG_DATA(cmsg);
+			} else if (cmsg->cmsg_type == IP_TTL) {
+				memcpy(&rethops, CMSG_DATA(cmsg), sizeof(rethops));
+			} else {
+				printf("cmsg:%d\n ", cmsg->cmsg_type);
+			}
+		}
+	}
+	if (e == NULL) {
+		printf("no info\n");
+		return 0;
+	}
+	if (e->ee_origin == SO_EE_ORIGIN_LOCAL) {
+		printf("%2d?: %*s ", ttl, -(HOST_COLUMN_SIZE - 1), "[LOCALHOST]");
+	} else if (e->ee_origin == SO_EE_ORIGIN_ICMP) {
+		char abuf[128];
+		struct sockaddr_in *sin = (struct sockaddr_in*)(e+1);
+		struct hostent *h = NULL;
+		char *idn = NULL;
+
+		inet_ntop(AF_INET, &sin->sin_addr, abuf, sizeof(abuf));
+
+		if (sndhops>0)
+			printf("%2d:  ", sndhops);
+		else
+			printf("%2d?: ", ttl);
+
+		if (!no_resolve || show_both) {
+			fflush(stdout);
+			h = gethostbyaddr((char *) &sin->sin_addr, sizeof(sin->sin_addr), AF_INET);
+		}
+
+#ifdef USE_IDN
+		if (h && idna_to_unicode_lzlz(h->h_name, &idn, 0) != IDNA_SUCCESS)
+			idn = NULL;
+#endif
+		if (no_resolve)
+			print_host(abuf, h ? (idn ? idn : h->h_name) : abuf, show_both);
+		else
+			print_host(h ? (idn ? idn : h->h_name) : abuf, abuf, show_both);
+
+#ifdef USE_IDN
+		free(idn);
+#endif
+	}
+
+	if (rettv) {
+		int diff = (tv.tv_sec-rettv->tv_sec)*1000000+(tv.tv_usec-rettv->tv_usec);
+		printf("%3d.%03dms ", diff/1000, diff%1000);
+		if (broken_router)
+			printf("(This broken router returned corrupted payload) ");
+	}
+
+	switch (e->ee_errno) {
+	case ETIMEDOUT:
+		printf("\n");
+		break;
+	case EMSGSIZE:
+		printf("pmtu %d\n", e->ee_info);
+		mtu = e->ee_info;
+		progress = mtu;
+		break;
+	case ECONNREFUSED:
+		printf("reached\n");
+		hops_to = sndhops<0 ? ttl : sndhops;
+		hops_from = rethops;
+		return 0;
+	case EPROTO:
+		printf("!P\n");
+		return 0;
+	case EHOSTUNREACH:
+		if (e->ee_origin == SO_EE_ORIGIN_ICMP &&
+		    e->ee_type == 11 &&
+		    e->ee_code == 0) {
+			if (rethops>=0) {
+				if (rethops<=64)
+					rethops = 65-rethops;
+				else if (rethops<=128)
+					rethops = 129-rethops;
+				else
+					rethops = 256-rethops;
+				if (sndhops>=0 && rethops != sndhops)
+					printf("asymm %2d ", rethops);
+				else if (sndhops<0 && rethops != ttl)
+					printf("asymm %2d ", rethops);
+			}
+			printf("\n");
+			break;
+		}
+		printf("!H\n");
+		return 0;
+	case ENETUNREACH:
+		printf("!N\n");
+		return 0;
+	case EACCES:
+		printf("!A\n");
+		return 0;
+	default:
+		printf("\n");
+		errno = e->ee_errno;
+		perror("NET ERROR");
+		return 0;
+	}
+	goto restart;
+}
+
+int probe_ttl(int fd, int ttl)
+{
+	int i;
+	struct probehdr *hdr = pktbuf;
+
+	memset(pktbuf, 0, mtu);
+restart:
+	for (i=0; i<10; i++) {
+		int res;
+
+		hdr->ttl = ttl;
+		target.sin_port = htons(base_port + hisptr);
+		gettimeofday(&hdr->tv, NULL);
+		his[hisptr].hops = ttl;
+		his[hisptr].sendtime = hdr->tv;
+		if (sendto(fd, pktbuf, mtu-overhead, 0, (struct sockaddr*)&target, sizeof(target)) > 0)
+			break;
+		res = recverr(fd, ttl);
+		his[hisptr].hops = 0;
+		if (res==0)
+			return 0;
+		if (res > 0)
+			goto restart;
+	}
+	hisptr = (hisptr + 1)&63;
+
+	if (i<10) {
+		data_wait(fd);
+		if (recv(fd, pktbuf, mtu, MSG_DONTWAIT) > 0) {
+			printf("%2d?: reply received 8)\n", ttl);
+			return 0;
+		}
+		return recverr(fd, ttl);
+	}
+
+	printf("%2d:  send failed\n", ttl);
+	return 0;
+}
+
+static void usage(void) __attribute((noreturn));
+
+static void usage(void)
+{
+	fprintf(stderr, "Usage: tracepath [-n] [-b] [-l <len>] [-p port] <destination>\n");
+	exit(-1);
+}
+
+int
+main(int argc, char **argv)
+{
+	struct hostent *he;
+	int fd;
+	int on;
+	int ttl;
+	char *p;
+	int ch;
+#ifdef USE_IDN
+	int rc;
+	setlocale(LC_ALL, "");
+#endif
+
+	while ((ch = getopt(argc, argv, "nbh?l:m:p:")) != EOF) {
+		switch(ch) {
+		case 'n':
+			no_resolve = 1;
+			break;
+		case 'b':
+			show_both = 1;
+			break;
+		case 'l':
+			if ((mtu = atoi(optarg)) <= overhead) {
+				fprintf(stderr, "Error: pktlen must be > %d and <= %d.\n",
+					overhead, INT_MAX);
+				exit(1);
+			}
+			break;
+		case 'm':
+			max_hops = atoi(optarg);
+			if (max_hops < 0 || max_hops > MAX_HOPS_LIMIT) {
+				fprintf(stderr,
+					"Error: max hops must be 0 .. %d (inclusive).\n",
+					MAX_HOPS_LIMIT);
+			}
+			break;
+		case 'p':
+			base_port = atoi(optarg);
+			break;
+		default:
+			usage();
+		}
+	}
+
+	argc -= optind;
+	argv += optind;
+
+	if (argc != 1)
+		usage();
+
+	fd = socket(AF_INET, SOCK_DGRAM, 0);
+	if (fd < 0) {
+		perror("socket");
+		exit(1);
+	}
+	target.sin_family = AF_INET;
+
+	/* Backward compatiblity */
+	if (!base_port) {
+		p = strchr(argv[0], '/');
+		if (p) {
+			*p = 0;
+			base_port = atoi(p+1);
+		} else
+			base_port = 44444;
+	}
+
+	p = argv[0];
+#ifdef USE_IDN
+	rc = idna_to_ascii_lz(argv[0], &p, 0);
+	if (rc != IDNA_SUCCESS) {
+		fprintf(stderr, "IDNA encoding failed: %s\n", idna_strerror(rc));
+		exit(2);
+	}
+#endif
+
+	he = gethostbyname(p);
+	if (he == NULL) {
+		herror("gethostbyname");
+		exit(1);
+	}
+
+#ifdef USE_IDN
+	free(p);
+#endif
+
+	memcpy(&target.sin_addr, he->h_addr, 4);
+
+	on = IP_PMTUDISC_PROBE;
+	if (setsockopt(fd, SOL_IP, IP_MTU_DISCOVER, &on, sizeof(on)) &&
+	    (on = IP_PMTUDISC_DO,
+	     setsockopt(fd, SOL_IP, IP_MTU_DISCOVER, &on, sizeof(on)))) {
+		perror("IP_MTU_DISCOVER");
+		exit(1);
+	}
+	on = 1;
+	if (setsockopt(fd, SOL_IP, IP_RECVERR, &on, sizeof(on))) {
+		perror("IP_RECVERR");
+		exit(1);
+	}
+	if (setsockopt(fd, SOL_IP, IP_RECVTTL, &on, sizeof(on))) {
+		perror("IP_RECVTTL");
+		exit(1);
+	}
+
+	pktbuf = malloc(mtu);
+	if (!pktbuf) {
+		perror("malloc");
+		exit(1);
+	}
+
+	for (ttl = 1; ttl <= max_hops; ttl++) {
+		int res;
+		int i;
+
+		on = ttl;
+		if (setsockopt(fd, SOL_IP, IP_TTL, &on, sizeof(on))) {
+			perror("IP_TTL");
+			exit(1);
+		}
+
+restart:
+		for (i=0; i<3; i++) {
+			int old_mtu;
+
+			old_mtu = mtu;
+			res = probe_ttl(fd, ttl);
+			if (mtu != old_mtu)
+				goto restart;
+			if (res == 0)
+				goto done;
+			if (res > 0)
+				break;
+		}
+
+		if (res < 0)
+			printf("%2d:  no reply\n", ttl);
+	}
+	printf("     Too many hops: pmtu %d\n", mtu);
+done:
+	printf("     Resume: pmtu %d ", mtu);
+	if (hops_to>=0)
+		printf("hops %d ", hops_to);
+	if (hops_from>=0)
+		printf("back %d ", hops_from);
+	printf("\n");
+	exit(0);
+}