/*
 *
 * This program is a KLUDGE to see if a remote host
 * will accept udp datagrams with BAD udp checksums.
 *
 * The whole code is based on traceroute by Van Jacobson
 * and that a host receiving an UDP datagram with a bad UDP checksum
 * should discard it without sending a ICMP message.
 *
 * The idea is if this hack comes from Francis.Dupont@inria.fr
 * 
 *  -- Benoit Grange (Benoit.Grange@inria.fr)
 *     Tue May  2 15:50:02 MET DST 1995
 *
 * Based on traceroute by
 *
 *  -- Van Jacobson (van@helios.ee.lbl.gov)
 *     Tue Dec 20 03:50:13 PST 1988
 *
 * Copyright (c) 1988 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by the University of California, Berkeley.  The name of the
 * University may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.  */

#ifndef lint
static char Version[] = "@(#)ckudpcksum.c	Benoit.Grange@inria.fr (Benoit Grange) 950502";
#endif lint

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/time.h>

#include <sys/param.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <sys/ioctl.h>

#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/udp.h>
#include <netinet/ip_var.h>
#include <netinet/udp_var.h>
#include <netdb.h>

#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN	64	/* maximum length of hostname */
#endif
#ifndef MAXDNAME
#define MAXDNAME	256	/* maximum length of domain name */
#endif

#define	MAX_START_TTL	255	/* Cisco and BSD4.3 */
#define	NEW_BSD_TCP	60

#ifndef FD_SET
#define NFDBITS         (8*sizeof(fd_set))
#define FD_SETSIZE      NFDBITS
#define FD_SET(n, p)    ((p)->fds_bits[(n)/NFDBITS] |= (1 << ((n) % NFDBITS)))
#define FD_CLR(n, p)    ((p)->fds_bits[(n)/NFDBITS] &= ~(1 << ((n) % NFDBITS)))
#define FD_ISSET(n, p)  ((p)->fds_bits[(n)/NFDBITS] & (1 << ((n) % NFDBITS)))
#define FD_ZERO(p)      bzero((char *)(p), sizeof(*(p)))
#endif

#define sameword(a,b)	(strcasecmp(a,b) == 0)

#define NOT_DOTTED_QUAD	((u_long)-1)

#define TRUE	1
#define FALSE	0

#ifdef lint
#define EXTERN
#else
#define EXTERN extern
#endif

EXTERN int errno;

extern void exit();
extern char *index();
extern char *rindex();
extern char *strcpy();
extern char *malloc();
extern char *inet_ntoa();
extern u_long inet_addr();

char *pr_type();

/*
 * format of a (udp) probe packet.
 */
struct probe
{
	struct ip ip;
	struct udphdr udp;
};

#define MAXPKT	65535		/* max ip packet size */
#define MAXDATA	(MAXPKT - sizeof(struct probe))

struct probe *opacket;		/* last output (udp) packet */
u_char ipacket[4096];		/* last inbound (icmp) packet */

int sndsock;			/* send (udp) socket file descriptor */
int rcvsock;			/* receive (icmp) socket file descriptor */

struct sockaddr whereto;	/* who to try to reach */
struct sockaddr wherefrom;	/* who we got response from */

u_short ident;			/* packet ident */
u_short port = 32768+666;	/* start udp dest port # for probe packets */
int datalen = 0;		/* how much data in raw ip packet */
int udplen = 0;			/* how much data in udp packet */

char hnamebuf[MAXDNAME+1];
char *hostname;			/* remote host to query about */
char *source = NULL;		/* specific source for multi-homed sender */
char *program;			/* name of this program */

int nprobes = 3;		/* number of probe packets to send */
int max_ttl = NEW_BSD_TCP;	/* max number of hops */

int options = 0;		/* socket options */
int waittime = 1;		/* time to wait for response (in seconds) */
int numeric = FALSE;		/* print addresses numerically */

#ifndef MAX_IPOPTLEN
#define MAX_IPOPTLEN 40
#endif

#define MAXIPOPT 9		/* MAX_IPOPTLEN-IPOPT_MINOFF / sizeof(u_long) */

char Usage[] =
"\
Usage: %s [-q nqueries] host\n\
Other: [-w waittime] [-p port]\n\
";




/*VARARGS1*/
void error(fmt, a, b, c, d)
char *fmt;
{
	(void) fprintf(stderr, "%s: ", program);
	(void) fprintf(stderr, fmt, a, b, c, d);
	(void) fprintf(stderr, "\n");
}



/*VARARGS1*/
void fatal(fmt, a, b, c, d)
char *fmt;
{
	error(fmt, a, b, c, d);
	exit(1);
}


void fperror(fmt)
char *fmt;
{
	(void) fprintf(stderr, "%s: ", program);
	perror(fmt);
	exit(1);
}


/*
** CHECKSUM -- Checksum routine for Internet Protocol family headers
** -----------------------------------------------------------------
**
**	Returns:
**		Value of checksum over given buffer.
*/

u_short
checksum(buf, len)
u_short *buf;				/* address of packet */
int len;				/* length of packet */
{
	register int nleft = len;	/* remaining short words */
	register u_short *w = buf;	/* address of next short word */
	register int sum = 0;		/* 32 bit accumulator */
	u_short answer = 0;

/*
 * Our algorithm is simple, using a 32 bit accumulator (sum),
 * we add sequential 16 bit words to it, and at the end, fold back
 * all the carry bits from the top 16 bits into the lower 16 bits.
 */
	while (nleft > 1)
	{
		sum += *w++;
		nleft -= 2;
	}

/*
 * Mop up an odd byte, if necessary.
 */
	if (nleft == 1)
	{
		*(u_char *)(&answer) = *(u_char *)w;
		sum += answer;
	}

/*
 * Add back carry outs from top 16 bits to low 16 bits.
 */
	sum = (sum >> 16) + (sum & 0xFFFF);	/* add hi 16 to low 16 */
	sum += (sum >> 16);			/* add carry */
	answer = ~sum;				/* truncate to 16 bits */

	return(answer);
}

/*
** SEND_PROBE -- Send out probe packet
** -----------------------------------
**
**	Inputs:
**		Global variable ident   contains our identification.
**		Global variable port    contains the base port offset.
**		Global variable tos     contains the type of service.
**		Global variable opacket points to the output packet.
**		Global variable datalen contains size of raw ip packet.
**		Global variable udplen  contains size of udp packet.
**
**	Returns:
**		Number of bytes sent.
**		-1 in case of error.
*/

int send_probe(sock, toaddr, seq)
int sock;				/* socket descriptor */
struct sockaddr *toaddr;		/* internet address to send to */
int seq;
{
	struct probe  *pkt = opacket;
	struct ip     *ip  = &pkt->ip;
	struct udphdr *udp = &pkt->udp;
	struct udpiphdr udpip;
	int addrlen;
	int n;

/*
 * Fill in ip header.
 */
	/* ip->ip_v   = (u_char)  IPVERSION;	/* version */
	/* ip->ip_hl  = (u_char)  0;		/* header length */
	   ip->ip_tos = (u_char)  0;		/* type of service */
	   ip->ip_len = (short)   datalen;	/* total size */
	/* ip->ip_id  = (u_short) 0;		/* identification */
	   ip->ip_off = (short)   0;		/* fragment offset */
	   ip->ip_ttl = (u_char)  max_ttl;	/* time-to-live */
	   ip->ip_p   = (u_char)  IPPROTO_UDP;	/* protocol */
	   ip->ip_sum = (u_short) 0;		/* checksum */
	/* ip->ip_src = INADDR_ANY;		/* source address */
	/* ip->ip_dst = toaddr->sin_addr;	/* destination address */

/*
 * Fill in udp header.
 */
	udp->uh_sport = htons(ident);		/* source port */
	udp->uh_dport = htons(port+seq);	/* destination port */
	udp->uh_ulen  = htons((u_short)udplen);	/* udp size */

	/* MUST BE SURE THIS IS NOT THE CORRECT CHECKSUM ... */
	bzero(&udpip, sizeof(udpip));
	udpip.ui_pr = ip->ip_p;
	udpip.ui_len = udp->uh_ulen;
	udpip.ui_src = ip->ip_src;
	udpip.ui_dst = ip->ip_dst;
	udpip.ui_sport = udp->uh_sport;
	udpip.ui_dport = udp->uh_dport;
	udpip.ui_ulen = udp->uh_ulen;

	udp->uh_sum   = checksum(&udpip, sizeof(udpip)); /* checksum */
	
	/* Break the checksum */
	udp->uh_sum++;
	if (udp->uh_sum == 0) udp->uh_sum = 0xFFFF;

/*
 * Send out the probe packet.
 */
	addrlen = sizeof(*toaddr);
#ifdef SYSV_UDPPROTO
	if (setsockopt(sock, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof(ttl)) < 0)
		fperror("IP_TTL");

	n = sendto(sock, (char *)udp, udplen, 0, toaddr, addrlen);
	if (n > 0)
		n += sizeof(struct ip);
#else
	n = sendto(sock, (char *)opacket, datalen, 0, toaddr, addrlen);
#endif SYSV_UDPPROTO
	if (n < 0 || n != datalen)
	{
		if (n < 0)
			perror("\nsendto");
		else
			error("\n%d chars instead of %d to %s",
				n, datalen, hostname);
		return(-1);
	}
	return(n);
}


/*
** WAIT_FOR_REPLY -- Wait for arrival of input packet
** --------------------------------------------------
**
**	Outputs:
**		Input packet is stored in global buffer ipacket.
**
**	Returns:
**		Number of bytes received.
**		-1 in case of error or timeout.
*/

int wait_for_reply(sock, fromaddr, secs)
int sock;				/* socket descriptor */
struct sockaddr *fromaddr;		/* buffer to store internet address */
int secs;				/* timeout value in secs */
{
	fd_set fds;
	struct timeval wait;
	int n;
	int addrlen;

	wait.tv_sec = secs;
	wait.tv_usec = 0;

	/* FD_ZERO(&fds); */
	bzero((char *)&fds, sizeof(fds));
	FD_SET(sock, &fds);

/*
 * Wait for arrival of input packet, or timeout.
 */
	n = select(FD_SETSIZE, &fds, (fd_set *)0, (fd_set *)0, &wait);
	if (n <= 0)
	{
		if (n == 0)
			errno = ETIMEDOUT;
		return(-1);
	}

/*
 * Fake an error if nothing was actually read.
 */
	addrlen = sizeof(*fromaddr);
	n = recvfrom(sock, (char *)ipacket, sizeof(ipacket), 0, fromaddr, &addrlen);
	if (n == 0)
		errno = ECONNRESET;
	return(n);
}


/*
** PACKET_OK -- Check validity of received input packet
** ----------------------------------------------------
**
**	Inputs:
**		Global variable ident contains our identification.
**		Global variable port  contains the base port offset.
**
**	Returns:
**		ICMP_TIMXCEED if ttl dropped to zero (intermediate host).
**		Subcode of ICMP_UNREACH if destination unreachable.
**		Got to the final destination if ICMP_UNREACH_PORT.
**		-1 if not one of the above.
*/

int packet_ok(buf, cc, from, seq)
u_char *buf;				/* address of input packet */
int cc;					/* total size of input packet */
struct sockaddr_in *from;		/* remote internet address */
int seq;				/* sequence no. determines port */
{
	struct ip *ip;
	struct icmp *icmp;
	struct udphdr *udp;
	int iphdrlen;
	u_char type;			/* type of icmp input packet */
	u_char code;			/* subcode of type */

/*
 * The input packet contains the ip header in front of the icmp packet.
 * Make sure the packet contains the icmp header after the ip header.
 */
	ip = (struct ip *)buf;
	iphdrlen = ip->ip_hl << 2;

	if (cc < iphdrlen + ICMP_MINLEN)
	{
		return(-1);
	}

	cc -= iphdrlen;
	icmp = (struct icmp *)(buf + iphdrlen);

/*
 * Check the type of the icmp packet.
 * We should get ICMP_TIMXCEED if we got to an intermediate host.
 * We get ICMP_UNREACH if the desired host is currently unreachable,
 * or if we got to the final destination itself.
 * Make sure this is really our response packet.
 * The original ip packet is stored in the icmp message after the
 * icmp header, with the original udp header after the ip header.
 */
	type = icmp->icmp_type;
	code = icmp->icmp_code;

	if ((type == ICMP_TIMXCEED && code == ICMP_TIMXCEED_INTRANS) ||
	     type == ICMP_UNREACH)
	{
		ip = &icmp->icmp_ip;	/* original ip packet */
		iphdrlen = ip->ip_hl << 2;
		udp = (struct udphdr *)((u_char *)ip + iphdrlen);

		if (cc >= (ICMP_MINLEN + iphdrlen + 2*sizeof(u_short)) &&
		    ip->ip_p == IPPROTO_UDP &&
		    udp->uh_sport == htons(ident) &&
		    udp->uh_dport == htons(port+seq))
			return(type == ICMP_TIMXCEED ? type : code);
	}

	return(-1);
}


int main(argc, argv)
int argc;
char *argv[];
{
	struct sockaddr_in *from = (struct sockaddr_in *)&wherefrom;
	struct sockaddr_in *to   = (struct sockaddr_in *)&whereto;
	int on = 1;
	int probe;
	int seq = 0;
	u_long addr;
	struct hostent *hp;
	struct protoent *proto;
	register char *opt;

	program = rindex(argv[0], '/');
	if (program++ == NULL)
		program = argv[0];

#if defined(SYSV_SETVBUF)
	setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
#else
	setlinebuf(stdout);
#endif

/*
 * Check command line options.
 */
	while (argc > 2 && argv[1][0] == '-')
	{
	    for (opt = &argv[1][1]; *opt; opt++)
	    {
		switch (*opt)
		{
		    case 'd':
			options |= SO_DEBUG;
			break;

		    case 'p':
			argc--, argv++;
			port = atoi(argv[1]);
			if (port < 1)
				fatal("port must be > 0");
			break;

		    case 'q':
			argc--, argv++;
			nprobes = atoi(argv[1]);
			if (nprobes < 1)
				fatal("nprobes must be > 0");
			break;

		    case 'w':
			argc--, argv++;
			waittime = atoi(argv[1]);
			if (waittime <= 1)
				fatal("wait must be > 1 sec");
			break;

		    default:
			(void) fprintf(stderr, Usage, program);
			exit(1);
		}
	    }

	    argc--, argv++;
	}

	if (argc < 2 || argv[1][0] == '-')
	{
		(void) fprintf(stderr, Usage, program);
		exit(1);
	}

/*
 * Fetch remote host address to probe.
 */
	hostname = argv[1];
	addr = inet_addr(hostname);
	if (addr == NOT_DOTTED_QUAD)
	{
		hp = gethostbyname(hostname);
		if (hp == NULL)
			fatal("unknown host %s", hostname);
		hostname = strcpy(hnamebuf, hp->h_name);

		to->sin_family = hp->h_addrtype;
		to->sin_port = 0;
		bcopy(hp->h_addr, (char *)&to->sin_addr, hp->h_length);
	}
	else
	{
		hp = gethostbyaddr((char *)&addr, sizeof(addr), AF_INET);
		if (hp != NULL)
			hostname = hp->h_name;
		hostname = strcpy(hnamebuf, hostname);

		to->sin_family = AF_INET;
		to->sin_port = 0;
		to->sin_addr.s_addr = addr;
	}

/*
 * Allocate output packet of proper size.
 */
	datalen += sizeof(struct probe);
	opacket = (struct probe *)malloc((unsigned)datalen);
	if (opacket == NULL)
		fatal("out of memory");

	(void) bzero((char *)opacket, datalen);
	opacket->ip.ip_dst = to->sin_addr;

	udplen = datalen - sizeof(struct ip);
	ident = (getpid() & 0xFFFF) | 0x8000;

/*
 * Allocate input socket to receive replies.
 */
	proto = getprotobyname("icmp");
	if (proto == NULL)
		fatal("unknown protocol: icmp");

	rcvsock = socket(AF_INET, SOCK_RAW, proto->p_proto);
	if (rcvsock < 0)
		fperror("icmp socket");

	if (options & SO_DEBUG)
		(void) setsockopt(rcvsock, SOL_SOCKET, SO_DEBUG, (char *)&on, sizeof(on));

	if (options & SO_DONTROUTE)
		(void) setsockopt(rcvsock, SOL_SOCKET, SO_DONTROUTE, (char *)&on, sizeof(on));

/*
 * Allocate output socket to send probe packets.
 */
#ifdef SYSV_UDPPROTO
	sndsock = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
#else
	sndsock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
#endif SYSV_UDPPROTO
	if (sndsock < 0)
		fperror("raw socket");

#ifdef SO_SNDBUF
	if (setsockopt(sndsock, SOL_SOCKET, SO_SNDBUF, (char *)&datalen, sizeof(datalen)) < 0)
		fperror("SO_SNDBUF");
#endif SO_SNDBUF

#ifdef IP_HDRINCL
	if (setsockopt(sndsock, IPPROTO_IP, IP_HDRINCL, (char *)&on, sizeof(on)) < 0)
		fperror("IP_HDRINCL");
#endif IP_HDRINCL

	if (options & SO_DEBUG)
		(void) setsockopt(sndsock, SOL_SOCKET, SO_DEBUG, (char *)&on, sizeof(on));

	if (options & SO_DONTROUTE)
		(void) setsockopt(sndsock, SOL_SOCKET, SO_DONTROUTE, (char *)&on, sizeof(on));

/*
 * Fill in source address in output packet, if specified.
 */
	if (source)
	{
		addr = inet_addr(source);
		if (addr == NOT_DOTTED_QUAD)
			fatal("illegal address %s", source);

		from->sin_family = AF_INET;
		from->sin_port = 0;
		from->sin_addr.s_addr = addr;

		opacket->ip.ip_src = from->sin_addr;
#ifndef IP_HDRINCL
		if (bind(sndsock, &wherefrom, sizeof(wherefrom)) < 0)
			fperror("bind");
#endif IP_HDRINCL
	}

/*
 * Start of main loop.
 */
	printf("ckudpcksum to %s (%s)", hostname, inet_ntoa(to->sin_addr));
	if (source)
		printf(" from %s", source);
	printf(" %d hops max, %d probes, wait %d secs\n", max_ttl, nprobes, waittime);

	{
		int timeouts = 0;
		int got_there = 0;
		int unreachable = 0;

		for (probe = 1; probe <= nprobes; probe++)
		{
			int cc;
			int status;
			struct ip *ip;

			(void) fflush(stdout);

			seq++;
			(void) send_probe(sndsock, &whereto, seq);
retry:
			cc = wait_for_reply(rcvsock, &wherefrom, waittime);
			if (cc <= 0)
			{
				timeouts++;
				continue;	/* next probe */
			}

			status = packet_ok(ipacket, cc, from, seq);
			if (status < 0)
				goto retry;	/* not our reply */

			ip = (struct ip *)ipacket;
			switch(status)
			{
			    case ICMP_UNREACH_PORT:
			  /* Host accepted our BAD checksum */
			  printf ("%d timeouts, %d unreachables\n", timeouts, unreachable);
			  printf ("Host did not verify checksum\n");
			  exit(2);

			    case ICMP_UNREACH_NET:
			    case ICMP_UNREACH_HOST:
			    case ICMP_UNREACH_NEEDFRAG:
				unreachable++;
				break;
			}

		} /* for probe <= nprobes */
		printf ("%d timeouts, %d unreachables\n", timeouts, unreachable);
	      }

	exit(0);
	/*NOTREACHED*/
}
