diff -ru tcptraceroute-1.2/Makefile tcptraceroute-1.3beta1/Makefile --- tcptraceroute-1.2/Makefile Tue Jul 31 23:52:40 2001 +++ tcptraceroute-1.3beta1/Makefile Thu Aug 9 13:58:18 2001 @@ -1,3 +1,5 @@ +# vim:set ts=4 sw=4 ai: + # tcptraceroute -- A traceroute implementation using TCP packets # Copyright (c) 2001, Michael C. Toren @@ -18,9 +20,6 @@ distrib: clean changelog man -clean: - rm -f core a.out tcptraceroute *~ - changelog: tcptraceroute.c Makefile perl -000 -ne 'next unless (/\*\s+Revision\s+history:/); \ print "Extracted from tcptraceroute.c:\n\n$$_"; exit;' \ @@ -29,3 +28,6 @@ man: tcptraceroute.8.html Makefile tcptraceroute.8.html: tcptraceroute.8 rman -fHTML -r- tcptraceroute.8 > tcptraceroute.8.html + +clean: + rm -f core a.out tcptraceroute *~ diff -ru tcptraceroute-1.2/changelog tcptraceroute-1.3beta1/changelog --- tcptraceroute-1.2/changelog Tue Jul 31 23:53:11 2001 +++ tcptraceroute-1.3beta1/changelog Tue Aug 14 23:29:21 2001 @@ -3,24 +3,58 @@ /* * Revision history: * + * Version 1.3beta1 (2001-08-14) + * + * Display "!" instead of "!?" for unknown ICMP codes, as + * suggested by Kevin McAllister + * + * Attempts to find virtual addresses under OpenBSD, based on a + * patch by Scott Gifford + * + * Moves the datalinkoffset and datalinkname information into a + * single data structure, which is much more logical, and less + * prone to error. + * + * Improved command line argument handling a good deal, based on + * suggestions by Scott Fenton . "-q 3", + * "-q3", "-qw 3 1", and "-q3w1" are all perfectly legal, now. + * + * It is now possible to traceroute to yourself, by switching the + * device to the loopback interface if the destination matches the + * address of a local interface. Additionally, as learned by + * looking through the nmap source, we now never set a libpcap + * filter on the loopback interface to avoid apparent libpcap bugs + * which previously made it impossible to traceroute to 127.0.0.1 + * + * Added -S and -A command line arguments to control the state of + * the SYN and ACK flags in outgoing packets. By using -A, it is + * now possible to traceroute through stateless firewalls which + * permit hosts behind the firewalls to establish outgoing TCP + * connections. In the absence of either -A or -S, -S is set. + * + * Added -N command line argument which takes the place of the + * previous RESOLVE_RFC1918 #define. + * + * Now displays if the remote host is ECN capable when using -E + * * Version 1.2 (2001-07-31) * * Contains large portions of code and ideas contributed by * Scott Gifford * * Attempt to determine what outgoing interface to use based on the - * destination address and the local system's interface list. Could - * still use a good deal of work on BSD systems, though, especially - * when it comes to virtual addresses which reside on subnets - * different than the primary address. - * - * The timeout code has been reworked significantly, and should now - * be much more reliable. + * destination address and the local system's interface list. Could still + * use a good deal of work on BSD systems, though, especially when it + * comes to virtual addresses which reside on subnets different than the + * primary address. + * + * The timeout code has been reworked significantly, and should now be + * much more reliable. * * Added -E command line argument to send ECN (RFC2481) packets. * Requested by Christophe Barb and * Jim Penny - * + * * Added -l command line argument to set the total packet length, * including IP header. * @@ -36,8 +70,8 @@ * to be looking for is there. This could have been very ugly had the * snaplen not been set so conservatively. * - * Print banner information to stderr, not stdout, to be compatible - * with traceroute(8). Reported by Scott Fenton + * Print banner information to stderr, not stdout, to be compatible with + * traceroute(8). Reported by Scott Fenton * * Fixed an endian bug reported by Zoran Dzelajlija , * which prevented users from specifying the destination port number by @@ -47,12 +81,12 @@ * * Now drops root privileges after sockets have been opened. * - * Must now be root to use -s or -p, making it now safe to to - * install tcptraceroute suid root, without fear that users can - * generate arbitrary SYN packets. + * Must now be root to use -s or -p, making it now safe to to install + * tcptraceroute suid root, without fear that users can generate arbitrary + * SYN packets. * * Version 1.0 (2001-04-10) * - * Initial Release + * Initial Release. */ Only in tcptraceroute-1.3beta1: tcptraceroute diff -ru tcptraceroute-1.2/tcptraceroute.c tcptraceroute-1.3beta1/tcptraceroute.c --- tcptraceroute-1.2/tcptraceroute.c Tue Jul 31 21:05:52 2001 +++ tcptraceroute-1.3beta1/tcptraceroute.c Wed Aug 15 11:21:35 2001 @@ -34,24 +34,58 @@ /* * Revision history: * + * Version 1.3beta1 (2001-08-14) + * + * Display "!" instead of "!?" for unknown ICMP codes, as + * suggested by Kevin McAllister + * + * Attempts to find virtual addresses under OpenBSD, based on a + * patch by Scott Gifford + * + * Moves the datalinkoffset and datalinkname information into a + * single data structure, which is much more logical, and less + * prone to error. + * + * Improved command line argument handling a good deal, based on + * suggestions by Scott Fenton . "-q 3", + * "-q3", "-qw 3 1", and "-q3w1" are all perfectly legal, now. + * + * It is now possible to traceroute to yourself, by switching the + * device to the loopback interface if the destination matches the + * address of a local interface. Additionally, as learned by + * looking through the nmap source, we now never set a libpcap + * filter on the loopback interface to avoid apparent libpcap bugs + * which previously made it impossible to traceroute to 127.0.0.1 + * + * Added -S and -A command line arguments to control the state of + * the SYN and ACK flags in outgoing packets. By using -A, it is + * now possible to traceroute through stateless firewalls which + * permit hosts behind the firewalls to establish outgoing TCP + * connections. In the absence of either -A or -S, -S is set. + * + * Added -N command line argument which takes the place of the + * previous RESOLVE_RFC1918 #define. + * + * Now displays if the remote host is ECN capable when using -E + * * Version 1.2 (2001-07-31) * * Contains large portions of code and ideas contributed by * Scott Gifford * * Attempt to determine what outgoing interface to use based on the - * destination address and the local system's interface list. Could - * still use a good deal of work on BSD systems, though, especially - * when it comes to virtual addresses which reside on subnets - * different than the primary address. - * - * The timeout code has been reworked significantly, and should now - * be much more reliable. + * destination address and the local system's interface list. Could still + * use a good deal of work on BSD systems, though, especially when it + * comes to virtual addresses which reside on subnets different than the + * primary address. + * + * The timeout code has been reworked significantly, and should now be + * much more reliable. * * Added -E command line argument to send ECN (RFC2481) packets. * Requested by Christophe Barb and * Jim Penny - * + * * Added -l command line argument to set the total packet length, * including IP header. * @@ -67,8 +101,8 @@ * to be looking for is there. This could have been very ugly had the * snaplen not been set so conservatively. * - * Print banner information to stderr, not stdout, to be compatible - * with traceroute(8). Reported by Scott Fenton + * Print banner information to stderr, not stdout, to be compatible with + * traceroute(8). Reported by Scott Fenton * * Fixed an endian bug reported by Zoran Dzelajlija , * which prevented users from specifying the destination port number by @@ -78,23 +112,18 @@ * * Now drops root privileges after sockets have been opened. * - * Must now be root to use -s or -p, making it now safe to to - * install tcptraceroute suid root, without fear that users can - * generate arbitrary SYN packets. + * Must now be root to use -s or -p, making it now safe to to install + * tcptraceroute suid root, without fear that users can generate arbitrary + * SYN packets. * * Version 1.0 (2001-04-10) * - * Initial Release + * Initial Release. */ /* * TODO: * - * - RESOLVE_1918 should be a runtime, command line option. - * - Command line arguments could be handled better. - * - Display if the remote host is ECN capable when using o_ecn? - * - Currently it is not possible to traceroute to yourself. - * - finddev() doesn't detect virtual addresses on BSD systems. * - We really should be using GNU autoconf. */ @@ -111,7 +140,11 @@ #include #include #include -#include + +#ifndef __OpenBSD__ +#include /* Why doesn't OpenBSD deal with this for us? */ +#endif + #include #include #include @@ -124,6 +157,10 @@ #define AF_LINK AF_INET /* BSD defines some AF_INET network interfaces as AF_LINK */ #endif +#ifdef __OpenBSD__ +#define HASSALEN /* Awful, awful hack to make subinterfaces work on OpenBSD. */ +#endif + /* ECN (RFC2481) */ #ifndef TH_ECN #define TH_ECN 0x40 @@ -132,16 +169,13 @@ #define TH_CWR 0x80 #endif -#define VERSION "tcptraceroute 1.2 (2001-07-31)" +#define VERSION "tcptraceroute 1.3beta1 (2001-08-09)" #define BANNER "Copyright (c) 2001, Michael C. Toren \n\ Updates are available from http://michael.toren.net/code/tcptraceroute/\n" /* Buffer size used for a few strings, including the pcap filter */ #define TEXTSIZE 1024 -/* Should we attempt to resolve RFC1918 address space? */ -#undef RESOLVE_1918 - /* * How many bytes should we examine on every packet that comes off the * wire? This doesn't include the link layer which is accounted for @@ -153,6 +187,73 @@ #define SNAPLEN (LIBNET_IP_H * 2 + \ (LIBNET_TCP_H > LIBNET_ICMP_H ? LIBNET_TCP_H : LIBNET_ICMP_H) + 32) +/* + * To add support for additional link layers, add entries to the following + * table. The numbers I have in here now I believe are correct, and were + * obtained by looking through other pcap programs, however I have only + * tested tcptraceroute on ethernet, and PPP, and loopback interfaces. + */ + +struct datalinktype { + int type, offset; + char *name; +} datalinktypes[] = { + +#ifdef DLT_EN10MB + { DLT_EN10MB, 14, "ETHERNET" }, +#endif +#ifdef DLT_PPP + { DLT_PPP, 4, "PPP" }, +#endif +#ifdef DLT_SLIP + { DLT_SLIP, 16, "SLIP" }, +#endif +#ifdef DLT_PPP_BSDOS + { DLT_PPP_BSDOS, 24, "PPP_BSDOS" }, +#endif +#ifdef DLT_SLIP_BSDOS + { DLT_SLIP_BSDOS, 24, "SLIP_BSDOS" }, +#endif +#ifdef DLT_FDDI + { DLT_FDDI, 21, "FDDI" }, +#endif +#ifdef DLT_IEEE802 + { DLT_IEEE802, 22, "IEEE802" }, +#endif +#ifdef DLT_NULL + { DLT_NULL, 4, "DLT_NULL" }, +#endif +#ifdef DLT_LOOP + { DLT_LOOP, 4, "DLT_LOOP" }, +#endif + + /* Does anyone know correct values for these? */ +#ifdef DLT_RAW + { DLT_RAW, -1, "RAW" }, +#endif +#ifdef DLT_ATM_RFC1483 + { DLT_ATM_RFC1483, -1, "ATM_RFC1483" }, +#endif +#ifdef DLT_EN3MB + { DLT_EN3MB, -1, "EN3MB" }, +#endif +#ifdef DLT_AX25 + { DLT_AX25, -1, "AX25" }, +#endif +#ifdef DLT_PRONET + { DLT_PRONET, -1, "PRONET" }, +#endif +#ifdef DLT_CHAOS + { DLT_CHAOS, -1, "CHAOS" }, +#endif +#ifdef DLT_ARCNET + { DLT_ARCNET, -1, "ARCNET" }, +#endif + + /* End of the road */ + { -1, -1, NULL } +}; + /* Various globals */ u_long dst_ip, src_ip; u_short src_prt, dst_prt; @@ -163,7 +264,15 @@ struct timeval t1, t2; int sockfd, datalink, offset; int o_minttl, o_maxttl, o_timeout, o_debug, o_numeric, o_pktlen, - o_nqueries, o_dontfrag, o_tos, o_forceport, o_ecn; + o_nqueries, o_dontfrag, o_tos, o_forceport, o_syn, o_ack, o_ecn, + o_nofilter, o_nogetinterfaces; + +/* interface linked list, built later by getinterfaces() */ +struct interface_entry { + char *name; + u_long addr; + struct interface_entry *next; +} *interfaces; extern char pcap_version[]; extern int errno; @@ -253,7 +362,7 @@ } /* - * Same as strncpy, but always be sure the result is terminated. + * Same as strncpy and snprintf, but always be sure the result is terminated. */ char *safe_strncpy(char *dst, const char *src, int size) @@ -262,6 +371,19 @@ return strncpy(dst, src, size-1); } +int safe_snprintf(char *s, int size, char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = vsnprintf(s, size, fmt, ap); + s[size] = '\0'; + va_end(ap); + + return ret; +} + /* * return a pointer to a string containing only the * printable characters of the string passed to it. @@ -288,6 +410,30 @@ return buf; } +int datalinkoffset(int type) +{ + int i; + + for (i = 0; datalinktypes[i].name; i++) + if (datalinktypes[i].type == type) + return datalinktypes[i].offset; + + return -1; +} + +char *datalinkname(int type) +{ + static char name[TEXTSIZE]; + int i; + + for (i = 0; datalinktypes[i].name; i++) + if (datalinktypes[i].type == type) + return datalinktypes[i].name; + + safe_snprintf(name, TEXTSIZE, "#%d", type); + return name; +} + /* * Compute the difference between two timeval structures. */ @@ -349,83 +495,56 @@ p = (u_char *)∈ which = (which + 1 == IPTOSBUFFERS ? 0 : which + 1); - sprintf(output[which], "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); + safe_snprintf(output[which], 3*4+3+1, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); return output[which]; } /* - * A wrapper for libnet_host_lookup() with the option not to resolve - * 1918 space. This #define should be moved to a command line argument - * at some point. + * A wrapper for libnet_host_lookup(), with the option not to resolve + * RFC1918 space. */ char *iptohost(u_long in) { u_char *p = (u_char *)∈ -#ifndef RESOLVE_1918 - /* Don't attempt to resolve RFC1918 space */ - if ((p[0] == 10) || + if ((o_numeric > -1) && + ((p[0] == 10) || (p[0] == 192 && p[1] == 168) || - (p[0] == 172 && p[1] >= 16 && p[1] <= 31)) + (p[0] == 172 && p[1] >= 16 && p[1] <= 31))) { debug("Not attempting to resolve RFC1918 address %s\n", iptos(in)); return iptos(in); } -#endif - return libnet_host_lookup(in, ~o_numeric & 1); + return libnet_host_lookup(in, o_numeric > 0 ? 0 : 1); } /* - * Determines the source address that should be used to reach the - * given destination address. + * Fetches the interface list, storing it in struct interface_entry interfaces */ -u_long findsrc(u_long dest) -{ - struct sockaddr_in sinsrc, sindest; - int s, size; - - if ((s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) - pfatal("socket error"); - - memset(&sinsrc, 0, sizeof(struct sockaddr_in)); - memset(&sindest, 0, sizeof(struct sockaddr_in)); - - sindest.sin_family = AF_INET; - sindest.sin_addr.s_addr = dest; - sindest.sin_port = htons(53); /* can be anything */ - - if (connect(s, (struct sockaddr *)&sindest, sizeof(sindest)) < 0) - pfatal("connect"); - - size = sizeof(sinsrc); - if (getsockname(s, (struct sockaddr *)&sinsrc, &size) < 0) - pfatal("getsockname"); - - close(s); - debug("Determined source address of %s to reach %s\n", - iptos(sinsrc.sin_addr.s_addr), iptos(dest)); - return sinsrc.sin_addr.s_addr; -} - -/* - * Locates the device name matching the given source address. For - * virtual hosts under Linux and Solaris, returns the portions of the - * name before the first ":" character. Unfortunately, this won't - * match virtual hosts under OpenBSD. - */ - -char *finddev(u_long src) +void getinterfaces(void) { + struct interface_entry *p; struct ifconf ifc; struct ifreq *ifrp, ifr; - int numreqs, n, i, s; - char *device; + int numreqs, i, s; + u_long addr; + int salen; + char *x; + + if (o_nogetinterfaces) + { + debug("Not fetching the interface list\n"); + return; + } + + if (interfaces) + fatal("Double call to getinterfaces()\n"); - device = NULL; ifc.ifc_buf = NULL; + p = NULL; /* * The initial request length needs to be somewhat large, because @@ -449,10 +568,12 @@ if (ioctl(s, SIOCGIFCONF, &ifc) < 0) pfatal("ioctl"); - if (ifc.ifc_len >= sizeof(struct ifreq) * numreqs) + if (ifc.ifc_len >= sizeof(struct ifreq) * numreqs) { /* Assume it overflowed and try again */ numreqs += 1024; + if (numreqs > 200000) + break; /* Too big! */ debug("ifreq buffer grown to %d\n", numreqs); continue; } @@ -462,15 +583,36 @@ debug("successfully retrieved interface list\n"); - for (n = 0, ifrp = ifc.ifc_req; - n < ifc.ifc_len; - n += sizeof(struct ifreq), ifrp++) +#ifdef HASSALEN + debug("Using HASALEN method for finding addresses.\n"); +#endif + + for (x = ifc.ifc_buf; x < (ifc.ifc_buf + ifc.ifc_len); x += salen) { - u_long addr; + ifrp = (struct ifreq *)x; memset(&ifr, 0, sizeof(struct ifreq)); strcpy(ifr.ifr_name, ifrp->ifr_name); +#ifdef HASSALEN + salen = sizeof(ifrp->ifr_name) + ifrp->ifr_addr.sa_len; + if (salen < sizeof(*ifrp)) + salen = sizeof(*ifrp); + + addr = ((struct sockaddr_in *)&ifrp->ifr_addr)->sin_addr.s_addr; + if (ioctl(s, SIOCGIFFLAGS, &ifr) < 0) + pfatal("ioctl(SIOCGIFFLAGS)"); + +#else /* HASALEN */ + + salen = sizeof(*ifrp); + + if (ioctl(s, SIOCGIFADDR, &ifr) < 0) + pfatal("ioctl(SIOCGIFADDR)"); + addr = ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr.s_addr; + +#endif /* HASSALEN else */ + if (ifrp->ifr_addr.sa_family != AF_INET && ifrp->ifr_addr.sa_family != AF_LINK) { @@ -480,82 +622,98 @@ if (ioctl(s, SIOCGIFFLAGS, &ifr) < 0) pfatal("ioctl(SIOCGIFFLAGS)"); - if ((ifr.ifr_flags & IFF_UP) == 0) { debug("Ignoring down interface %s\n", sprintable(ifr.ifr_name)); continue; } - if (ioctl(s, SIOCGIFADDR, &ifr) < 0) - pfatal("ioctl(SIOCGIFADDR)"); + /* Deal with virtual hosts */ + for (i = 0; ifr.ifr_name[i]; i++) + if (ifr.ifr_name[i] == ':') + ifr.ifr_name[i] = '\0'; + + /* Grow another node on the linked list... */ + if (!p) + p = interfaces = xrealloc(NULL, sizeof(struct interface_entry)); + else + p = p->next = xrealloc(NULL, sizeof(struct interface_entry)); - addr = ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr.s_addr; + p->next = NULL; - debug("Discovered interface %s with address %s\n", - sprintable(ifr.ifr_name), iptos(addr)); + /* ... and fill it in */ + p->addr = addr; + p->name = xrealloc(NULL, sizeof(ifr.ifr_name + 1)); + strcpy(p->name, ifr.ifr_name); - if (addr == src) - { - debug("Interface %s matches source address %s\n", - sprintable(ifr.ifr_name), iptos(src)); - device = xrealloc(NULL, sizeof(ifr.ifr_name+1)); - strcpy(device, ifr.ifr_name); - - /* Deal with virtual hosts */ - for (i = 0; device[i]; i++) - if (device[i] == ':') - device[i] = '\0'; - } + debug("Discovered interface %s with address %s\n", + sprintable(p->name), iptos(p->addr)); } free(ifc.ifc_buf); - return device; } /* - * To add support for additional link layers, add entries to datalinkoffset() - * and datalinkname(). The numbers I have in here now I believe are correct, - * and were obtained by looking through other pcap programs, however I have - * only tested tcptraceroute on ethernet and Linux PPP interfaces. + * Determines the source address that should be used to reach the + * given destination address. */ -int datalinkoffset(int type) +u_long findsrc(u_long dest) { - switch (type) - { - case DLT_EN10MB: return 14; - case DLT_PPP: return 4; - case DLT_PPP_BSDOS: return 24; - case DLT_SLIP: return 16; - case DLT_SLIP_BSDOS: return 24; - case DLT_FDDI: return 21; - case DLT_IEEE802: return 22; - case DLT_RAW: return 0; - default: return -1; - } + struct sockaddr_in sinsrc, sindest; + int s, size; + + if ((s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) + pfatal("socket error"); + + memset(&sinsrc, 0, sizeof(struct sockaddr_in)); + memset(&sindest, 0, sizeof(struct sockaddr_in)); + + sindest.sin_family = AF_INET; + sindest.sin_addr.s_addr = dest; + sindest.sin_port = htons(53); /* can be anything */ + + if (connect(s, (struct sockaddr *)&sindest, sizeof(sindest)) < 0) + pfatal("connect"); + + size = sizeof(sinsrc); + if (getsockname(s, (struct sockaddr *)&sinsrc, &size) < 0) + pfatal("getsockname"); + + close(s); + debug("Determined source address of %s to reach %s\n", + iptos(sinsrc.sin_addr.s_addr), iptos(dest)); + return sinsrc.sin_addr.s_addr; } -char *datalinkname(int type) +/* + * Find an appropriate device to use given the specified source address. + * However, if we find an interface matching the global dst_ip address, set + * the source address we're looking for to 127.0.0.1 in an attempt to select + * the loopback. Ofcourse, this entirely depends on the fact that a loopback + * interface exists with an address of 127.0.0.1. + */ + +char *finddev(u_long with_src) { - static char name[TEXTSIZE]; + struct interface_entry *p; + char *device = NULL; - switch (type) - { - case DLT_EN10MB: return "ETHERNET"; - case DLT_SLIP: return "SLIP"; - case DLT_SLIP_BSDOS: return "SLIP_BSDOS"; - case DLT_PPP: return "PPP"; - case DLT_PPP_BSDOS: return "PPP_BSDOS"; - case DLT_FDDI: return "FDDI"; - case DLT_IEEE802: return "IEEE802"; - case DLT_ATM_RFC1483: return "ATM"; - case DLT_RAW: return "RAW"; - - default: - snprintf(name, TEXTSIZE, "#%d", type); - return name; - } + for (p = interfaces; p; p = p->next) + if (p->addr == dst_ip) + { + debug("Destination address matches address of interface %s\n", p->name); + debug("Attempting to find loopback interface ...\n"); + with_src = libnet_name_resolve("127.0.0.1", 0); + o_nofilter = 1; + } + + for (p = interfaces; p; p = p->next) + if (p->addr == with_src) + device = p->name; + + debug("finddev returning %s\n", device); + return device; } /* @@ -577,6 +735,14 @@ int printflag = 0; va_list ap; + /* kludge to make debug mode usable */ + if (o_debug) + { + fflush(stdout); + fprintf(stderr, "debug: displayed hop\n"); + fflush(stderr); + } + /* ttl */ if (lastttl != ttl) { @@ -630,10 +796,15 @@ if (state) safe_strncpy(laststate, state, TEXTSIZE); + /* kludge to make debug mode usable */ + if (o_debug) + fprintf(stdout, "\n"); + if (o_debug && q != o_nqueries) + fprintf(stdout, "\n"); + fflush(stdout); } - /* * Check command line arguments for sanity, and fill in the blanks. */ @@ -645,6 +816,8 @@ int insize; u_long recommended_src; + getinterfaces(); + if ((dst_ip = libnet_name_resolve(dst, 1)) == 0xFFFFFFFF) fatal("Bad destination address: %s\n", dst); @@ -656,7 +829,9 @@ fatal("Bad source address: %s\n", src); } else + { src_ip = recommended_src; + } if (device == NULL) /* not specified on command line */ @@ -664,7 +839,7 @@ if (device == NULL) { - /* couldn't find an interface matching recommended_src */ + /* couldn't find an appropriate interface */ warn("Could not determine appropriate device; resorting to pcap_lookupdev()\n"); device = pcap_lookupdev(errbuf); } @@ -733,14 +908,20 @@ pfatal("socket allocation"); if (strcmp(dst, iptos(dst_ip)) == 0) - snprintf(dst_name, TEXTSIZE, "%s", dst); + safe_snprintf(dst_name, TEXTSIZE, "%s", dst); else - snprintf(dst_name, TEXTSIZE, "%s (%s)", dst, iptos(dst_ip)); + safe_snprintf(dst_name, TEXTSIZE, "%s (%s)", dst, iptos(dst_ip)); if ((serv = getservbyport(dst_prt, "tcp")) == NULL) - snprintf(dst_prt_name, TEXTSIZE, "%d", dst_prt); + safe_snprintf(dst_prt_name, TEXTSIZE, "%d", dst_prt); else - snprintf(dst_prt_name, TEXTSIZE, "%d (%s)", dst_prt, serv->s_name); + safe_snprintf(dst_prt_name, TEXTSIZE, "%d (%s)", dst_prt, serv->s_name); + + if (! (o_syn|o_ack)) + { + debug("Setting o_syn, in absence of either o_syn or o_ack\n"); + o_syn = 1; + } fprintf(stderr, "Selected device %s, address %s, port %d for outgoing packets\n", device, iptos(src_ip), src_prt); @@ -758,10 +939,14 @@ if (! (pcap = pcap_open_live(device, offset + SNAPLEN, 0, 10, errbuf))) fatal("pcap_open_live failed: %s", errbuf); - snprintf(filter, TEXTSIZE, + safe_snprintf(filter, TEXTSIZE, "(tcp and src host %s and src port %d and dst host %s and dst port %d) or ((icmp[0] == 11 or icmp[0] == 3) and dst host %s)", iptos(dst_ip), dst_prt, iptos(src_ip), src_prt, iptos(src_ip)); + + if (o_nofilter) + filter[0] = '\0'; + debug("pcap filter is:\n'%s'\n",filter); localnet = 0; @@ -806,7 +991,7 @@ } memset(buf, 0, size); - id = libnet_get_prand(PRu32); + do { id = libnet_get_prand(PRu32); } while (id < 10); libnet_build_ip( LIBNET_TCP_H+o_pktlen, /* len */ @@ -826,7 +1011,11 @@ dst_prt, /* dest port */ 0, /* seq number */ 0, /* ack number */ - TH_SYN | (o_ecn ? TH_CWR|TH_ECN : 0), /* control */ + + (o_syn ? TH_SYN : 0) | + (o_ack ? TH_ACK : 0) | + (o_ecn ? TH_CWR|TH_ECN : 0), /* control */ + 0, /* window */ 0, /* urgent? */ payload, /* data */ @@ -841,7 +1030,7 @@ if ((ret = libnet_write_ip(sockfd, buf, size)) < size) fatal("libnet_write_ip failed? Attempted to write %d bytes, only wrote %d\n", size, ret); - + return id; } @@ -895,6 +1084,7 @@ if (tvsign(&timeleft) <= 0) { + debug("timeout\n"); showprobe(ttl, q, INADDR_ANY, NULL, "*"); return 0; } @@ -927,7 +1117,7 @@ if ((packet = (u_char *)pcap_next(pcap, &packet_hdr)) == NULL) { - debug("null pointer from pcap_next()\n"); + /* debug("null pointer from pcap_next()\n"); */ continue; } @@ -943,6 +1133,13 @@ ip_hdr = (struct libnet_ip_hdr *)packet; + if (ip_hdr->ip_dst.s_addr != src_ip) + { + debug("Ignoring IP packet not addressed to us (%s, not %s)\n", + iptos(ip_hdr->ip_dst.s_addr), iptos(src_ip)); + continue; + } + if (gettimeofday(&t2, NULL) < 0) pfatal("gettimeofday"); @@ -970,7 +1167,8 @@ /* * The IP header that generated the ICMP packet is quoted - * here. I don't know what the +4 is, but it works. */ + * here. I don't know what the +4 is, but it works. + */ if (len < LIBNET_IP_H + LIBNET_ICMP_H + 4 + LIBNET_IP_H + 4) { @@ -985,10 +1183,24 @@ old_tcp_hdr = (struct libnet_tcp_hdr *)(packet + LIBNET_IP_H + LIBNET_ICMP_H + 4 + LIBNET_IP_H); + if (old_ip_hdr->ip_dst.s_addr != dst_ip) + { + debug("Ignoring ICMP packet with incorrect quoted destination (%s, not %s)\n", + iptos(old_ip_hdr->ip_dst.s_addr), iptos(dst_ip)); + continue; + } + + if (old_ip_hdr->ip_src.s_addr != src_ip) + { + debug("Ignoring ICMP packet with incorrect quoted source (%s, not %s)\n", + iptos(old_ip_hdr->ip_src.s_addr), iptos(src_ip)); + continue; + } + /* These are not the droids you are looking for */ if (old_ip_hdr->ip_p != IPPROTO_TCP) { - debug("icmp packet doesn't quote a tcp ip header\n"); + debug("icmp packet doesn't quote a tcp header\n"); continue; } @@ -1000,7 +1212,8 @@ } /* Move along, move along */ - if (ntohs(old_tcp_hdr->th_sport) != src_prt) + if ((ntohs(old_tcp_hdr->th_sport) != src_prt) + || (ntohs(old_tcp_hdr->th_dport) != dst_prt)) { debug("icmp packet doesn't contain the correct tcp port numbers\n"); continue; @@ -1008,48 +1221,48 @@ if (icmp_hdr->icmp_type == ICMP_UNREACH) { - char *s; + char s[TEXTSIZE]; switch(icmp_hdr->icmp_code) { case ICMP_UNREACH_NET: - s = "!N"; break; + safe_strncpy(s, "!N", TEXTSIZE); break; case ICMP_UNREACH_HOST: - s = "!H"; break; + safe_strncpy(s, "!H", TEXTSIZE); break; case ICMP_UNREACH_PROTOCOL: - s = "!P"; break; + safe_strncpy(s, "!P", TEXTSIZE); break; case ICMP_UNREACH_NEEDFRAG: - s = "!F"; break; + safe_strncpy(s, "!F", TEXTSIZE); break; case ICMP_UNREACH_SRCFAIL: - s = "!S"; break; + safe_strncpy(s, "!S", TEXTSIZE); break; case ICMP_UNREACH_NET_PROHIB: case ICMP_UNREACH_FILTER_PROHIB: - s = "!A"; break; + safe_strncpy(s, "!A", TEXTSIZE); break; case ICMP_UNREACH_HOST_PROHIB: - s = "!C"; break; + safe_strncpy(s, "!C", TEXTSIZE); break; case ICMP_UNREACH_NET_UNKNOWN: case ICMP_UNREACH_HOST_UNKNOWN: - s = "!U"; break; + safe_strncpy(s, "!U", TEXTSIZE); break; case ICMP_UNREACH_ISOLATED: - s = "!I"; break; + safe_strncpy(s, "!I", TEXTSIZE); break; case ICMP_UNREACH_TOSNET: case ICMP_UNREACH_TOSHOST: - s = "!T"; break; + safe_strncpy(s, "!T", TEXTSIZE); break; case ICMP_UNREACH_PORT: case ICMP_UNREACH_HOST_PRECEDENCE: case ICMP_UNREACH_PRECEDENCE_CUTOFF: default: - s = "!?"; break; + safe_snprintf(s, TEXTSIZE, "!<%d>", icmp_hdr->icmp_code); } showprobe(ttl, q, ip_hdr->ip_src.s_addr, NULL, @@ -1064,13 +1277,20 @@ return 0; } - fatal("something bad happened\n"); + fatal("Something bad happened\n"); } if (ip_hdr->ip_p == IPPROTO_TCP) { char *s; - + + if (ip_hdr->ip_src.s_addr != dst_ip) + { + debug("tcp packet's origin does not match our target's address (%s, not %s)\n", + iptos(ip_hdr->ip_dst.s_addr), iptos(src_ip)); + continue; + } + if (len < LIBNET_IP_H + LIBNET_TCP_H) { debug("Ignoring partial tcp packet\n"); @@ -1078,12 +1298,39 @@ } tcp_hdr = (struct libnet_tcp_hdr *)(packet + LIBNET_IP_H); - debug("received tcp packet\n"); + + debug("Received tcp packet %s:%d -> %s:%d, flags %s%s%s%s%s%s%s%s%s\n", + iptos(ip_hdr->ip_src.s_addr), ntohs(tcp_hdr->th_sport), + iptos(ip_hdr->ip_dst.s_addr), ntohs(tcp_hdr->th_dport), + tcp_hdr->th_flags & TH_RST ? "RST " : "", + tcp_hdr->th_flags & TH_SYN ? "SYN " : "", + tcp_hdr->th_flags & TH_ACK ? "ACK " : "", + tcp_hdr->th_flags & TH_PUSH ? "PSH " : "", + tcp_hdr->th_flags & TH_FIN ? "FIN " : "", + tcp_hdr->th_flags & TH_URG ? "URG " : "", + tcp_hdr->th_flags & TH_CWR ? "CWR " : "", + tcp_hdr->th_flags & TH_ECN ? "ECN " : "", + tcp_hdr->th_flags ? "" : "(none)"); + + if ((ntohs(tcp_hdr->th_sport) != dst_prt) + || (ntohs(tcp_hdr->th_dport) != src_prt)) + { + debug("tcp packet doesn't contain the correct port numbers\n"); + continue; + } if (tcp_hdr->th_flags & TH_RST) s = "closed"; - else if (tcp_hdr->th_flags & TH_SYN && tcp_hdr->th_flags & TH_ACK) + + else if ((tcp_hdr->th_flags & TH_SYN) + && (tcp_hdr->th_flags & TH_ACK) + && (tcp_hdr->th_flags & TH_ECN)) + s = "open, ecn capable"; + + else if ((tcp_hdr->th_flags & TH_SYN) + && (tcp_hdr->th_flags & TH_ACK)) s = "open"; + else s = "unknown"; @@ -1093,7 +1340,8 @@ return 1; } - fatal("something bad happened\n"); + debug("Ignoring non-ICMP and non-TCP received packet\n"); + continue; } } @@ -1111,10 +1359,15 @@ for (ttl = o_minttl, done = 0; !done && ttl <= o_maxttl; ttl++) { - for (q = 0; q < o_nqueries; q++) + for (q = 1; q < o_nqueries + 1; q++) { id = probe(ttl); - done |= capture(ttl, q + 1, id); + debug("Sent probe %d of %d for hop %d, IP ID %d, %s%s%s\n", + q, o_nqueries, ttl, id, + o_syn ? "SYN " : "", + o_ack ? "ACK " : "", + o_ecn ? "CWR ECN " : ""); + done |= capture(ttl, q, id); } } @@ -1122,6 +1375,53 @@ fprintf(stderr, "Destination not reached\n"); } +/* + * Kludge to suck in a numeric argument to a command line switch. It's a + * little ugly, but by not using getopt(3), we're able to support "-q 3", + * "-q3", "-qw 3 1", and "-q3w1". + */ + +int getnopt(char **in, int *argc, char **argv[]) +{ + int value, i; + char *s, opt, buf[TEXTSIZE]; + + s = (*in); + opt = s[0]; + s++; + + if (isdigit(s[0])) + { + safe_strncpy(buf, s, TEXTSIZE); + + for (i = 0; buf[i]; i++) + if (!isdigit(buf[i])) + { + buf[i] = '\0'; + break; + } + + value = atoi(buf); + (*in) += i; + } + + else + { + if (*argc < 2) + fatal("Argument required for -%c\n", opt); + + (*argc)--, (*argv)++; + + for (s = (*argv)[0], i = 0; s[i]; i++) + if (!isdigit(s[i])) + fatal("Numeric argument required for -%c\n", opt); + + value = atoi(s); + } + + return value; +} + int main(int argc, char *argv[]) { struct servent *serv; @@ -1132,6 +1432,7 @@ dst_prt = 0; src = NULL; device = NULL; + interfaces = NULL; o_minttl = 1; o_maxttl = 30; @@ -1142,33 +1443,53 @@ o_pktlen = 0; o_tos = 0; o_ecn = 0; + o_syn = 0; + o_ack = 0; o_dontfrag = 0; o_timeout = 3; + o_nofilter = 0; + o_nogetinterfaces = 0; /* strip out path from argv[0] */ for (name = s = argv[0]; s[0]; s++) if (s[0] == '/' && s[1]) name = &s[1]; - for (argc--, argv++; argc > 0; argc--, argv++) + for (argc--, argv++; argc; argc--, argv++) { s = argv[0]; + if (s[0] != '-') + break; + if (strcmp("--", s) == 0) { argc--, argv++; break; } - if (s[0] != '-') - break; - if (strcmp("--help", s) == 0) s = "-h"; if (strcmp("--version", s) == 0) s = "-v"; + /* undocumented, for debugging only */ + if (strcmp("--no-filter", s) == 0) + { + o_nofilter = 1; + debug("o_nofilter set\n"); + continue; + } + + /* undocumented, for debugging only */ + if (strcmp("--no-getinterfaces", s) == 0) + { + o_nogetinterfaces = 1; + debug("o_nogetinterfaces set\n"); + continue; + } + for (s++; s[0]; s++) switch(s[0]) { @@ -1185,75 +1506,84 @@ case 'n': o_numeric = 1; + debug("o_numeric set to 1\n"); + break; + + case 'N': + o_numeric = -1; + debug("o_numeric set to -1\n"); break; case 'i': if (argc < 2) fatal("Argument required for -i\n"); - device = argv[1]; argc--, argv++; + device = argv[0]; + debug("device set to %s\n", device); break; case 'l': - if (argc < 2) fatal("Argument required for -l\n"); - o_pktlen = atoi(argv[1]); - argc--, argv++; + o_pktlen = getnopt(&s, &argc, &argv); + debug("o_pktlen set to %d\n", o_pktlen); break; case 'f': - if (argc < 2) fatal("Argument required for -f\n"); - o_minttl = atoi(argv[1]); - argc--, argv++; + o_minttl = getnopt(&s, &argc, &argv); + debug("o_minttl set to %d\n", o_minttl); break; case 'F': o_dontfrag = 1; - debug("Will set DF bit in outgoing packets\n"); + debug("o_dontfrag set\n"); break; case 'm': - if (argc < 2) fatal("Argument required for -m\n"); - o_maxttl = atoi(argv[1]); - argc--, argv++; + o_maxttl = getnopt(&s, &argc, &argv); + debug("o_maxttl set to %d\n", o_maxttl); break; case 'P': o_forceport = 1; case 'p': - if (argc < 2) fatal("Argument required for -p\n"); if (getuid()) fatal("Sorry, must be root to use -p\n"); - src_prt = atoi(argv[1]); - argc--, argv++; + src_prt = getnopt(&s, &argc, &argv); + debug("src_prt set to %d\n", src_prt); break; case 'q': - if (argc < 2) fatal("Argument required for -q\n"); - o_nqueries = atoi(argv[1]); - argc--,argv++; + o_nqueries = getnopt(&s, &argc, &argv); + debug("o_nqueries set to %d\n", o_nqueries); break; case 'w': - if (argc < 2) fatal("Argument required for -w\n"); - o_timeout = atoi(argv[1]); - argc--, argv++; + o_timeout = getnopt(&s, &argc, &argv); + debug("o_timeout set to %d\n", o_timeout); break; case 's': if (argc < 2) fatal("Argument required for -s\n"); if (getuid()) fatal("Sorry, must be root to use -s\n"); - src = argv[1]; argc--, argv++; + src = argv[0]; break; case 't': - if (argc < 2) fatal("Argument required for -t\n"); - o_tos = atoi(argv[1]); - debug("TOS set to %d\n", o_tos); - argc--, argv++; + o_tos = getnopt(&s, &argc, &argv); + debug("o_tos set to %d\n", o_tos); + break; + + case 'S': + o_syn = 1; + debug("o_syn set\n"); + break; + + case 'A': + o_ack = 1; + debug("o_ack set\n"); break; case 'E': o_ecn = 1; - debug("Enabled ECN support\n"); + debug("o_ecn set\n"); break; default: