/* itrace - a TCP traceroute implementation 
 * 
 * FX <fx@phenoelit.de>
 * Phenoelit (http://www.phenoelit.de)
 *
 * $Id: tctrace.c,v 1.12 2001/07/08 14:25:14 fx Exp $
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <rpc/types.h>
#include <netdb.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <fcntl.h>
#include <time.h>

#include "protocols.h"
#include "packets.h"

void usage(char *n);

struct {
    int			verbose;
    struct in_addr	dest;
    int			timeout;
    int			maxttl;
    int			reverse;
    int			probes;
    int			port;
    int			sport;
    char		*device;
    char		*tdest;
} cfg;

#define SEQ	12345
#define WIN	2048
#define IP_ID	0xAFFE

void usage(char *n) {
    fprintf(stderr,
	    "Usage: %s [-vn] [-pX] [-mX] [-tX] [-DX]"
		" [-SX] -i<dev> -d<destination>\n\n"
	    "-v\tverbose\n"
	    "-n\treverse lookup IPs\n"
	    "-pX\tsend X probes (default=3)\n"
	    "-mX\tmaximum TTL (default=30)\n"
	    "-tX\ttimeout X sec (default=3)\n"
	    "-DX\tdestination port (default=80)\n"
	    "-SX\tsource port (default=1064)\n"
	    "-i<dev>\tuse this device\n"
	    "-d<des>\ttrace to this destination\n"
	    ,n);
}

int main(int argc, char **argv) {
    char		option;
    extern char		*optarg;
    int			sfd,rfd,tcfd;
    struct sockaddr_in	to,from;
    u_char		*tpacket;
    iphdr_t		*ip,*ip2;
    icmphdr_t		*icmp;
    tcphdr_t		*tcp;
    struct pseudohdr	pshdr;
    int			psize;
    int			rc,addrsize,allrespond,respond=0,reached=0;
    u_int16_t		TTL;
    u_int32_t		tseq;
    unsigned long	start_t;
    struct timespec	sleeper = { 0, 10};

    memset(&cfg,0,sizeof(cfg));
    cfg.probes=cfg.timeout=3;
    cfg.maxttl=30;
    cfg.port=80;
    cfg.sport=1064;
    while ((option=getopt(argc,argv,"vni:d:t:m:p:D:S:"))!=EOF) {
	switch (option) {
	    case 'v': 	/* verbose */
			cfg.verbose++;
			break;
	    case 'd':	/* destination, first as text */
			cfg.tdest=smalloc(strlen(optarg)+1);
			strcpy(cfg.tdest,optarg);
			break;
	    case 't':	if ((cfg.timeout=atoi(optarg))==0) {
			    fprintf(stderr,"%s is an invalid timeout\n",
				    optarg);
			    return (1);
			}
			break;
	    case 'i':	cfg.device=(char *)smalloc(strlen(optarg)+1);
			strcpy(cfg.device,optarg);
			break;
	    case 'm':	if ((cfg.maxttl=atoi(optarg))==0) {
			    fprintf(stderr,"%s is not a valid TTL\n",
				    optarg);
			    return (1);
			}
			break;
	    case 'p':	if ((cfg.probes=atoi(optarg))==0) {
			    fprintf(stderr,"%s is not a valid number of"
				    "probes\n",optarg);
			    return (1);
			}
			break;
	    case 'D':	if ((cfg.port=atoi(optarg))==0) {
			    fprintf(stderr,"%s is not a valid port\n"
				    ,optarg);
			    return (1);
			}
			break;
	    case 'S':	if ((cfg.sport=atoi(optarg))==0) {
			    fprintf(stderr,"%s is not a valid port\n"
				    ,optarg);
			    return (1);
			}
			break;
	    case 'n':	cfg.reverse=1;
			break;
	    default:	usage(argv[0]);
			return (1);
	}
    }

    /* check device and destination */
    if (!cfg.device) {
	fprintf(stderr,"Sorry, but you have to supply the device with -i\n");
	return (1);
    }
    if (!cfg.tdest) {
	fprintf(stderr,"You should really supply a destination with -d\n");
	return (1);
    } else {
	/* try as a normal IP address */
	if (inet_aton(cfg.tdest,&(cfg.dest))==0) {
	    /* ups, wasn't an IP - maybe a hostname */
	    struct hostent	*hd;
	    if ((hd=gethostbyname(cfg.tdest))==NULL) {
		fprintf(stderr,"Could not resolve destination host\n");
		return (1);
	    } else {
		bcopy(hd->h_addr,(char *)&(cfg.dest),hd->h_length);
		/* memcpy((u_int8_t*)&(cfg.dest),
			(u_int8_t*)&(hd->h_addr_list[0]),IP_ADDR_LEN); */
	    }
	}
    }

    /* itrace begins here */
    if (cfg.verbose) {
	printf("Tracing with TCP SYNs to %s\n",inet_ntoa(cfg.dest));
	printf("Timeout %d, interface %s\n",cfg.timeout,cfg.device);
    }

    /* create three sockets: RAW,TCP and ICMP. RAW is needed for the 
     * modification of the std IP header and TCP/ICMP are needed because for 
     * some reason I don't understand fully the recvfrom() call does not 
     * see nothing on a RAW socket. */
    if ((sfd=init_socket_IP4(cfg.device,0))<0) {
	fprintf(stderr,"could not grab socket\n");
	return (-1);
    }
    if ((rfd=socket(AF_INET,SOCK_RAW,IPPROTO_ICMP))<0) {
	perror("socket(IPPROTO_ICMP)");
	return (-1);
    }
    rc= O_NONBLOCK | fcntl(rfd, F_GETFL);
    fcntl(rfd,F_SETFL,rc);
    if ((tcfd=socket(AF_INET,SOCK_RAW,IPPROTO_TCP))<0) {
	perror("socket(IPPROTO_TCP)");
	return (-1);
    }
    rc= O_NONBLOCK | fcntl(tcfd, F_GETFL);
    fcntl(tcfd,F_SETFL,rc);


    TTL=1;
    srand((unsigned int)time(NULL));
    tseq=htonl(1+(long) (65535.0*rand()/(RAND_MAX+1.0)));

    while ((!reached)&&(TTL<=cfg.maxttl)) {
	int		i;
	struct in_addr	last;

	allrespond=0;
	for (i=0;i<cfg.probes;i++) {

	    psize=sizeof(tcphdr_t)+sizeof(iphdr_t);
	    tpacket=(u_char *)smalloc(sizeof(tcphdr_t)+sizeof(iphdr_t)+3);
	    /* bulid IP packet */
	    ip=(iphdr_t *)tpacket;
	    ip->version=4;
	    ip->ihl=sizeof(iphdr_t)/4;
	    ip->tot_len=htons(psize);
	    ip->protocol=IPPROTO_TCP;
	    ip->id=htons(TTL);
	    memcpy(&(ip->saddr.s_addr),
		    &(packet_ifconfig.ip.s_addr),IP_ADDR_LEN);
	    memcpy(&(ip->daddr.s_addr),&(cfg.dest),IP_ADDR_LEN);

	    ip->ttl=TTL;

	    /* make TCP syn packet */
	    tcp=(tcphdr_t *)(tpacket+sizeof(iphdr_t));
	    tcp->th_sport=htons(cfg.sport);
	    tcp->th_dport=htons(cfg.port);
	    tcp->th_seq=tseq;
	    tcp->th_ack=htonl(0);
	    tcp->th_flags=TH_SYN;
	    tcp->th_off=sizeof(tcphdr_t)/4;
	    tcp->th_win=htons(WIN);

	    /* make up checksum */
	    memset(&pshdr,0,sizeof(struct pseudohdr));
	    memcpy(&(pshdr.saddr.s_addr),
		    &(packet_ifconfig.ip.s_addr),IP_ADDR_LEN);
	    memcpy(&(pshdr.daddr.s_addr),&(cfg.dest),IP_ADDR_LEN);
	    pshdr.protocol=IPPROTO_TCP;
	    pshdr.length=htons(sizeof(tcphdr_t));
	    bcopy((char *)tcp,(char *)&pshdr.tcpheader,sizeof(tcphdr_t));
	    tcp->th_sum=chksum((u_char *) &pshdr,
		    sizeof(tcphdr_t)+sizeof(iphdr_t)-8);

	    memset(&to,0,sizeof(struct sockaddr_in));
	    to.sin_family=AF_INET;
	    to.sin_port=htons(0);
	    memcpy(&(to.sin_addr),&(ip->daddr),sizeof(to.sin_addr));

	    if (sendto(sfd,tpacket,psize,0,
			(struct sockaddr *) &to,
			sizeof(struct sockaddr_in)) <0) {
		perror("sendto()");
		return(-1);
	    }
	    
	    /* make sure we have no junk in the mem */
	    free(tpacket);
	    memset(&from,0,sizeof(struct sockaddr_in));
	    addrsize=sizeof(struct sockaddr_in);
	    start_t=(unsigned long)time(NULL);
	    psize=2*sizeof(iphdr_t)+sizeof(icmphdr_t)+30;
	    tpacket=(u_char *)smalloc(psize+3);

	    respond=0;
	    while (start_t+cfg.timeout>=time(NULL)) {
		if ((rc=recvfrom(rfd,(u_char *)tpacket,psize,0,
			    (struct sockaddr *)&from,
			    &addrsize))>=0) {
		    struct hostent	*hr;
		    char		*name;
		    tcphdr_t		*tcp2;

		    ip=(iphdr_t *)tpacket;

		    /* got an ICMP response */
		    icmp=(icmphdr_t *)(tpacket+sizeof(iphdr_t));

		    /* check whenever it contains the former IP header */
		    if (rc>=(2*sizeof(iphdr_t)+sizeof(icmphdr_t)+4)) {
			ip2=(iphdr_t *)(tpacket+
				sizeof(iphdr_t)+sizeof(icmphdr_t)+4);
			tcp2=(tcphdr_t *)(tpacket+
				2*sizeof(iphdr_t)+sizeof(icmphdr_t)+4);

			/* make sure it was our packet */
			if (!( 
			    (ip2->protocol==IPPROTO_TCP)
			    &&
			    (ip2->id==htons(TTL))
			    &&
			    (!memcmp(&(ip2->saddr.s_addr),
				    &(packet_ifconfig.ip.s_addr),IP_ADDR_LEN)) 
			    &&
			    (!memcmp(&(ip2->daddr.s_addr),&(cfg.dest),
				     IP_ADDR_LEN))
			    &&
			    (tcp2->th_dport==htons(cfg.port))
			    &&
			    (tcp2->th_sport==htons(cfg.sport))
			    &&
			    (tcp2->th_seq==tseq)
			    )) {
			    if (cfg.verbose>1) {
				printf("Not in response of probe:\n");
				printf("\t%s->",inet_ntoa(ip2->saddr));
				printf("%s\n",inet_ntoa(ip2->daddr));
			    }
			    continue;
			}
		    } else {
			if (cfg.verbose) {
			    printf("ICMP packet to small to carry"
				" original IP header\n");
			    printf("Should be %d, is %d\n",
				2*sizeof(iphdr_t)+sizeof(icmphdr_t)+4,
				 rc);
			}
		    }

		    /* reverse lookup */
		    if ( (cfg.reverse) &&
			    ((hr=gethostbyaddr((char *)&(from.sin_addr),
				    IP_ADDR_LEN,AF_INET))!=NULL)) {
			name=(char *)smalloc(strlen(hr->h_name)+
				strlen(inet_ntoa(from.sin_addr))+4);
			strcpy(name,hr->h_name);
			strcat(name," [");
			strcat(name,inet_ntoa(from.sin_addr));
			strcat(name,"]");
		    } else {
			name=(char *)smalloc(
				strlen(inet_ntoa(from.sin_addr))+4);
			strcat(name,"[");
			strcat(name,inet_ntoa(from.sin_addr));
			strcat(name,"]");
		    }

		    if (icmp->type==ICMP_TIME_EXCEEDED) {
			if (memcmp(&(last.s_addr),
				    &(ip->saddr.s_addr),IP_ADDR_LEN)) {
			    memcpy(&(last.s_addr),&(ip->saddr.s_addr),
				    IP_ADDR_LEN);
			} else {
			    break;
			    //continue;
			}
			printf("%2d(%d)\t%s\n",TTL,i+1,name);
			allrespond=respond=1;
			break;
		    } else if (icmp->type==ICMP_DEST_UNREACH) {
			if ((icmp->code==ICMP_UNREACH_FIREWALL) ||
			    (icmp->code==ICMP_UNREACH_ADMIN1))
			    printf("%2d(%d)\t%s (firewalled)\n",TTL,i+1,name);
			else 
			    printf("%2d(%d)\t%s (unreachable)\n",TTL,i+1,name);
			allrespond=respond=reached=1;
			break;
		    } 

		    free(name);
		}

		memset(tpacket,0,psize);
		if ((rc=recvfrom(tcfd,(u_char *)tpacket,psize,0,
			    (struct sockaddr *)&from,
			    &addrsize))>=sizeof(tcphdr_t)+sizeof(iphdr_t)) {
		    struct hostent	*hr;
		    char		*name;

		    if (rc<sizeof(tcphdr_t)+sizeof(iphdr_t)) {
			printf("FUCK: %d %d\n",rc,
				sizeof(tcphdr_t)+sizeof(iphdr_t));
			printf("wouldn't make an TCp packet\n");
			continue;
		    }
		    ip=(iphdr_t *)tpacket;
		    tcp=(tcphdr_t *)(tpacket+sizeof(iphdr_t));
		    /* we may receive a lot on a TCP socket, so make sure
		     * the answer is from the recipient 
		     * 
		     * the destination test looks stupid, but how do we know
		     * if someone set the interface to promiscuous mode ?*/
		    if (
			    (!memcmp(&(ip->saddr),&(cfg.dest),IP_ADDR_LEN)) 
			    &&
			    (!memcmp(&(from.sin_addr.s_addr),
				     &(cfg.dest),IP_ADDR_LEN)) 
			    &&
			    (!memcmp(&(ip->daddr),
				    &(packet_ifconfig.ip.s_addr),IP_ADDR_LEN))
			    &&
			    (ip->protocol==IPPROTO_TCP)
			    ){

			if (
				( (ntohl(tcp->th_ack)!=ntohl(tseq)+1)
				  &&(ntohl(tcp->th_ack)!=ntohl(tseq)) )
				||
				(tcp->th_dport!=htons(cfg.sport))
				||
				(tcp->th_sport!=htons(cfg.port))
				){
			    /* SEQ+1 not in ACK - not a response to our probe*/
			    // printf("PS."); fflush(stdout);
			    continue;
			}

			if (memcmp(&(last.s_addr),
				    &(ip->saddr.s_addr),IP_ADDR_LEN)) {
			    memcpy(&(last.s_addr),
				    &(ip->saddr.s_addr),IP_ADDR_LEN);
			} else {
			    break;
			    /* not a response to our probe*/
			    // continue;
			}


			/* reverse lookup */
			if ( (cfg.reverse) &&
				((hr=gethostbyaddr((char *)&(from.sin_addr),
					IP_ADDR_LEN,AF_INET))!=NULL)) {
			    name=(char *)smalloc(strlen(hr->h_name)+
				    strlen(inet_ntoa(from.sin_addr))+4);
			    strcpy(name,hr->h_name);
			    strcat(name," [");
			    strcat(name,inet_ntoa(from.sin_addr));
			    strcat(name,"]");
			} else {
			    name=(char *)smalloc(
				    strlen(inet_ntoa(from.sin_addr))+4);
			    strcat(name,"[");
			    strcat(name,inet_ntoa(from.sin_addr));
			    strcat(name,"]");
			}

			if (tcp->th_flags & TH_RST) {
			    printf("%2d(%d)\t%s (reached; closed)\n",
				    TTL,i+1,name);
			    allrespond=respond=reached=1;
			    break;
			} else if ( 
				(tcp->th_flags & TH_SYN) &&
				(tcp->th_flags & TH_ACK) ) {
			    printf("%2d(%d)\t%s (reached; open)\n",
				    TTL,i+1,name);
			    allrespond=respond=reached=1;
			    break;
			} else {
			    printf("%2d(%d)\t%s (reached) -- Strange Flags!\n",
				    TTL,i+1,name);
			    allrespond=respond=reached=1;
			    break;
			}

			free(name);
		    }
		} /* end of tcp */

		nanosleep(&sleeper,NULL);
	    }
	    if (!respond) {
		if (cfg.verbose) printf("%2d(%d)\tTimeout\n",TTL,i+1);
	    }
	    if (reached||respond) break;

	    free(tpacket);
	} /* end of probe for() loop */
	if (!allrespond) 
	    printf("%d(all)\tTimeout\n",TTL);

	TTL++;
    }

    close(sfd);
    close(rfd);
    close(tcfd);
    return 0;
}


