summaryrefslogtreecommitdiff
path: root/astraceroute.c
diff options
context:
space:
mode:
Diffstat (limited to 'astraceroute.c')
-rw-r--r--astraceroute.c1077
1 files changed, 1077 insertions, 0 deletions
diff --git a/astraceroute.c b/astraceroute.c
new file mode 100644
index 0000000..a8c289b
--- /dev/null
+++ b/astraceroute.c
@@ -0,0 +1,1077 @@
+/*
+ * netsniff-ng - the packet sniffing beast
+ * Copyright 2011 - 2013 Daniel Borkmann.
+ * Subject to the GPL, version 2.
+ */
+
+#define _BSD_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <getopt.h>
+#include <ctype.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/fsuid.h>
+#include <fcntl.h>
+#include <time.h>
+#include <string.h>
+#include <asm/byteorder.h>
+#include <linux/tcp.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include <netdb.h>
+#include <sys/time.h>
+#include <arpa/inet.h>
+#include <linux/if_ether.h>
+#include <linux/icmp.h>
+#include <linux/icmpv6.h>
+
+#include "bpf.h"
+#include "die.h"
+#include "tprintf.h"
+#include "pkt_buff.h"
+#include "proto.h"
+#include "xmalloc.h"
+#include "xio.h"
+#include "csum.h"
+#include "geoip.h"
+#include "xutils.h"
+#include "ring_rx.h"
+#include "built_in.h"
+
+struct ctx {
+ char *host, *port, *dev, *payload;
+ int init_ttl, max_ttl, dns_resolv, queries, timeout, totlen, rcvlen;
+ int syn, ack, ecn, fin, psh, rst, urg, tos, nofrag, proto, show;
+ int sd_len, dport, latitude;
+};
+
+struct proto_ops {
+ int (*assembler)(uint8_t *packet, size_t len, int ttl, int proto,
+ const struct ctx *ctx, const struct sockaddr *dst,
+ const struct sockaddr *src);
+ const struct sock_filter *filter;
+ unsigned int flen;
+ unsigned int min_len_tcp, min_len_icmp;
+ int (*check)(uint8_t *packet, size_t len, int ttl, int id,
+ const struct sockaddr *src);
+ void (*handler)(uint8_t *packet, size_t len, int dns_resolv,
+ int latitude);
+};
+
+sig_atomic_t sigint = 0;
+
+static int assemble_ipv4(uint8_t *packet, size_t len, int ttl, int proto,
+ const struct ctx *ctx, const struct sockaddr *dst,
+ const struct sockaddr *src);
+static int assemble_ipv6(uint8_t *packet, size_t len, int ttl, int proto,
+ const struct ctx *ctx, const struct sockaddr *dst,
+ const struct sockaddr *src);
+static int check_ipv4(uint8_t *packet, size_t len, int ttl, int id,
+ const struct sockaddr *ss);
+static void handle_ipv4(uint8_t *packet, size_t len, int dns_resolv,
+ int latitude);
+static int check_ipv6(uint8_t *packet, size_t len, int ttl, int id,
+ const struct sockaddr *ss);
+static void handle_ipv6(uint8_t *packet, size_t len, int dns_resolv,
+ int latitude);
+
+static const char *short_options = "H:p:nNf:m:i:d:q:x:SAEFPURt:Gl:hv46X:ZuL";
+static const struct option long_options[] = {
+ {"host", required_argument, NULL, 'H'},
+ {"port", required_argument, NULL, 'p'},
+ {"init-ttl", required_argument, NULL, 'f'},
+ {"max-ttl", required_argument, NULL, 'm'},
+ {"dev", required_argument, NULL, 'd'},
+ {"num-probes", required_argument, NULL, 'q'},
+ {"timeout", required_argument, NULL, 'x'},
+ {"tos", required_argument, NULL, 't'},
+ {"payload", required_argument, NULL, 'X'},
+ {"totlen", required_argument, NULL, 'l'},
+ {"numeric", no_argument, NULL, 'n'},
+ {"latitude", no_argument, NULL, 'L'},
+ {"update", no_argument, NULL, 'u'},
+ {"dns", no_argument, NULL, 'N'},
+ {"ipv4", no_argument, NULL, '4'},
+ {"ipv6", no_argument, NULL, '6'},
+ {"syn", no_argument, NULL, 'S'},
+ {"ack", no_argument, NULL, 'A'},
+ {"urg", no_argument, NULL, 'U'},
+ {"fin", no_argument, NULL, 'F'},
+ {"psh", no_argument, NULL, 'P'},
+ {"rst", no_argument, NULL, 'R'},
+ {"ecn-syn", no_argument, NULL, 'E'},
+ {"show-packet", no_argument, NULL, 'Z'},
+ {"nofrag", no_argument, NULL, 'G'},
+ {"version", no_argument, NULL, 'v'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+};
+
+static const struct sock_filter ipv4_icmp_type_11[] = {
+ { 0x28, 0, 0, 0x0000000c }, /* ldh [12] */
+ { 0x15, 0, 8, 0x00000800 }, /* jneq #0x800, drop */
+ { 0x30, 0, 0, 0x00000017 }, /* ldb [23] */
+ { 0x15, 0, 6, 0x00000001 }, /* jneq #0x1, drop */
+ { 0x28, 0, 0, 0x00000014 }, /* ldh [20] */
+ { 0x45, 4, 0, 0x00001fff }, /* jset #0x1fff, drop */
+ { 0xb1, 0, 0, 0x0000000e }, /* ldxb 4*([14]&0xf) */
+ { 0x50, 0, 0, 0x0000000e }, /* ldb [x + 14] */
+ { 0x15, 0, 1, 0x0000000b }, /* jneq #0xb, drop */
+ { 0x06, 0, 0, 0xffffffff }, /* ret #-1 */
+ { 0x06, 0, 0, 0x00000000 }, /* drop: ret #0 */
+};
+
+static const struct sock_filter ipv6_icmp6_type_3[] = {
+ { 0x28, 0, 0, 0x0000000c }, /* ldh [12] */
+ { 0x15, 0, 5, 0x000086dd }, /* jneq #0x86dd, drop */
+ { 0x30, 0, 0, 0x00000014 }, /* ldb [20] */
+ { 0x15, 0, 3, 0x0000003a }, /* jneq #0x3a, drop */
+ { 0x30, 0, 0, 0x00000036 }, /* ldb [54] */
+ { 0x15, 0, 1, 0x00000003 }, /* jneq #0x3, drop */
+ { 0x06, 0, 0, 0xffffffff }, /* ret #-1 */
+ { 0x06, 0, 0, 0x00000000 }, /* drop: ret #0 */
+};
+
+static const struct proto_ops af_ops[] = {
+ [IPPROTO_IP] = {
+ .assembler = assemble_ipv4,
+ .handler = handle_ipv4,
+ .check = check_ipv4,
+ .filter = ipv4_icmp_type_11,
+ .flen = array_size(ipv4_icmp_type_11),
+ .min_len_tcp = sizeof(struct iphdr) + sizeof(struct tcphdr),
+ .min_len_icmp = sizeof(struct iphdr) + sizeof(struct icmphdr),
+ },
+ [IPPROTO_IPV6] = {
+ .assembler = assemble_ipv6,
+ .handler = handle_ipv6,
+ .check = check_ipv6,
+ .filter = ipv6_icmp6_type_3,
+ .flen = array_size(ipv6_icmp6_type_3),
+ .min_len_tcp = sizeof(struct ip6_hdr) + sizeof(struct tcphdr),
+ .min_len_icmp = sizeof(struct ip6_hdr) + sizeof(struct icmp6hdr),
+ },
+};
+
+static void signal_handler(int number)
+{
+ switch (number) {
+ case SIGINT:
+ sigint = 1;
+ default:
+ break;
+ }
+}
+
+static void help(void)
+{
+ printf("\nastraceroute %s, autonomous system trace route utility\n", VERSION_STRING);
+ puts("http://www.netsniff-ng.org\n\n"
+ "Usage: astraceroute [options]\n"
+ "Options:\n"
+ " -H|--host <host> Host/IPv4/IPv6 to lookup AS route to\n"
+ " -p|--port <port> Hosts port to lookup AS route to\n"
+ " -i|-d|--dev <device> Networking device, e.g. eth0\n"
+ " -f|--init-ttl <ttl> Set initial TTL\n"
+ " -m|--max-ttl <ttl> Set maximum TTL (def: 30)\n"
+ " -q|--num-probes <num> Number of max probes for each hop (def: 2)\n"
+ " -x|--timeout <sec> Probe response timeout in sec (def: 3)\n"
+ " -X|--payload <string> Specify a payload string to test DPIs\n"
+ " -l|--totlen <len> Specify total packet len\n"
+ " -4|--ipv4 Use IPv4-only requests\n"
+ " -6|--ipv6 Use IPv6-only requests\n"
+ " -n|--numeric Do not do reverse DNS lookup for hops\n"
+ " -u|--update Update GeoIP databases\n"
+ " -L|--latitude Show latitude and longtitude\n"
+ " -N|--dns Do a reverse DNS lookup for hops\n"
+ " -S|--syn Set TCP SYN flag\n"
+ " -A|--ack Set TCP ACK flag\n"
+ " -F|--fin Set TCP FIN flag\n"
+ " -P|--psh Set TCP PSH flag\n"
+ " -U|--urg Set TCP URG flag\n"
+ " -R|--rst Set TCP RST flag\n"
+ " -E|--ecn-syn Send ECN SYN packets (RFC3168)\n"
+ " -t|--tos <tos> Set the IP TOS field\n"
+ " -G|--nofrag Set do not fragment bit\n"
+ " -Z|--show-packet Show returned packet on each hop\n"
+ " -v|--version Print version\n"
+ " -h|--help Print this help\n\n"
+ "Examples:\n"
+ " IPv4 trace of AS with TCP SYN probe (this will most-likely pass):\n"
+ " astraceroute -i eth0 -N -S -H netsniff-ng.org\n"
+ " IPv4 trace of AS with TCP ECN SYN probe:\n"
+ " astraceroute -i eth0 -N -E -H netsniff-ng.org\n"
+ " IPv4 trace of AS with TCP FIN probe:\n"
+ " astraceroute -i eth0 -N -F -H netsniff-ng.org\n"
+ " IPv4 trace of AS with Xmas probe:\n"
+ " astraceroute -i eth0 -N -FPU -H netsniff-ng.org\n"
+ " IPv4 trace of AS with Null probe with ASCII payload:\n"
+ " astraceroute -i eth0 -N -H netsniff-ng.org -X \"censor-me\" -Z\n"
+ " IPv6 trace of AS up to www.6bone.net:\n"
+ " astraceroute -6 -i eth0 -S -E -N -H www.6bone.net\n\n"
+ "Note:\n"
+ " If the TCP probe did not give any results, then astraceroute will\n"
+ " automatically probe for classic ICMP packets! To gather more\n"
+ " information about astraceroute's fetched AS numbers, see e.g.\n"
+ " http://bgp.he.net/AS<number>!\n\n"
+ "Please report bugs to <bugs@netsniff-ng.org>\n"
+ "Copyright (C) 2011-2013 Daniel Borkmann <dborkma@tik.ee.ethz.ch>\n"
+ "Swiss federal institute of technology (ETH Zurich)\n"
+ "License: GNU GPL version 2.0\n"
+ "This is free software: you are free to change and redistribute it.\n"
+ "There is NO WARRANTY, to the extent permitted by law.\n");
+ die();
+}
+
+static void version(void)
+{
+ printf("\nastraceroute %s, autonomous system trace route utility\n", VERSION_STRING);
+ puts("http://www.netsniff-ng.org\n\n"
+ "Please report bugs to <bugs@netsniff-ng.org>\n"
+ "Copyright (C) 2011-2013 Daniel Borkmann <dborkma@tik.ee.ethz.ch>\n"
+ "Swiss federal institute of technology (ETH Zurich)\n"
+ "License: GNU GPL version 2.0\n"
+ "This is free software: you are free to change and redistribute it.\n"
+ "There is NO WARRANTY, to the extent permitted by law.\n");
+ die();
+}
+
+static void __assemble_data(uint8_t *packet, size_t len, const char *payload)
+{
+ int i;
+
+ if (payload == NULL) {
+ for (i = 0; i < len; ++i)
+ packet[i] = (uint8_t) rand();
+ } else {
+ int lmin = min(len, strlen(payload));
+
+ for (i = 0; i < lmin; ++i)
+ packet[i] = (uint8_t) payload[i];
+ for (i = lmin; i < len; ++i)
+ packet[i] = (uint8_t) rand();
+ }
+}
+
+static void __assemble_icmp4(uint8_t *packet, size_t len)
+{
+ struct icmphdr *icmph = (struct icmphdr *) packet;
+
+ bug_on(len < sizeof(struct icmphdr));
+
+ icmph->type = ICMP_ECHO;
+ icmph->code = 0;
+ icmph->checksum = 0;
+}
+
+static void __assemble_icmp6(uint8_t *packet, size_t len)
+{
+ struct icmp6hdr *icmp6h = (struct icmp6hdr *) packet;
+
+ bug_on(len < sizeof(struct icmp6hdr));
+
+ icmp6h->icmp6_type = ICMPV6_ECHO_REQUEST;
+ icmp6h->icmp6_code = 0;
+ icmp6h->icmp6_cksum = 0;
+}
+
+static void __assemble_tcp(uint8_t *packet, size_t len, int syn, int ack,
+ int urg, int fin, int rst, int psh, int ecn,
+ int dport)
+{
+ struct tcphdr *tcph = (struct tcphdr *) packet;
+
+ bug_on(len < sizeof(struct tcphdr));
+
+ tcph->source = htons((uint16_t) rand());
+ tcph->dest = htons((uint16_t) dport);
+
+ tcph->seq = htonl(rand());
+ tcph->ack_seq = (!!ack ? htonl(rand()) : 0);
+
+ tcph->doff = 5;
+
+ tcph->syn = !!syn;
+ tcph->ack = !!ack;
+ tcph->urg = !!urg;
+ tcph->fin = !!fin;
+ tcph->rst = !!rst;
+ tcph->psh = !!psh;
+ tcph->ece = !!ecn;
+ tcph->cwr = !!ecn;
+
+ tcph->window = htons((uint16_t) (100 + (rand() % 65435)));
+ tcph->urg_ptr = (!!urg ? htons((uint16_t) rand()) : 0);
+ tcph->check = 0;
+}
+
+static int assemble_ipv4(uint8_t *packet, size_t len, int ttl, int proto,
+ const struct ctx *ctx, const struct sockaddr *dst,
+ const struct sockaddr *src)
+{
+ uint8_t *data;
+ size_t data_len, off_next = 0;
+ struct iphdr *iph = (struct iphdr *) packet;
+
+ bug_on(!src || !dst);
+ bug_on(src->sa_family != PF_INET || dst->sa_family != PF_INET);
+ bug_on(len < sizeof(*iph) + min(sizeof(struct tcphdr),
+ sizeof(struct icmphdr)));
+
+ iph->ihl = 5;
+ iph->version = 4;
+ iph->tos = (uint8_t) ctx->tos;
+
+ iph->tot_len = htons((uint16_t) len);
+ iph->id = htons((uint16_t) rand());
+
+ iph->frag_off = ctx->nofrag ? IP_DF : 0;
+ iph->ttl = (uint8_t) ttl;
+
+ iph->saddr = ((const struct sockaddr_in *) src)->sin_addr.s_addr;
+ iph->daddr = ((const struct sockaddr_in *) dst)->sin_addr.s_addr;
+
+ iph->protocol = (uint8_t) proto;
+
+ data = packet + sizeof(*iph);
+ data_len = len - sizeof(*iph);
+
+ switch (proto) {
+ case IPPROTO_TCP:
+ __assemble_tcp(data, data_len, ctx->syn, ctx->ack, ctx->urg,
+ ctx->fin, ctx->rst, ctx->psh, ctx->ecn, ctx->dport);
+ off_next = sizeof(struct tcphdr);
+ break;
+ case IPPROTO_ICMP:
+ __assemble_icmp4(data, data_len);
+ off_next = sizeof(struct icmphdr);
+ break;
+ default:
+ bug();
+ }
+
+ data = packet + sizeof(*iph) + off_next;
+ data_len = len - sizeof(*iph) - off_next;
+
+ __assemble_data(data, data_len, ctx->payload);
+
+ iph->check = csum((unsigned short *) packet, ntohs(iph->tot_len) >> 1);
+
+ return ntohs(iph->id);
+}
+
+static int assemble_ipv6(uint8_t *packet, size_t len, int ttl, int proto,
+ const struct ctx *ctx, const struct sockaddr *dst,
+ const struct sockaddr *src)
+{
+ uint8_t *data;
+ size_t data_len, off_next = 0;
+ struct ip6_hdr *ip6h = (struct ip6_hdr *) packet;
+
+ bug_on(!src || !dst);
+ bug_on(src->sa_family != PF_INET6 || dst->sa_family != PF_INET6);
+ bug_on(len < sizeof(*ip6h) + min(sizeof(struct tcphdr),
+ sizeof(struct icmp6hdr)));
+
+ ip6h->ip6_flow = htonl(rand() & 0x000fffff);
+ ip6h->ip6_vfc = 0x60;
+
+ ip6h->ip6_plen = htons((uint16_t) len - sizeof(*ip6h));
+ ip6h->ip6_nxt = (uint8_t) proto;
+ ip6h->ip6_hlim = (uint8_t) ttl;
+
+ memcpy(&ip6h->ip6_src, &(((const struct sockaddr_in6 *)
+ src)->sin6_addr), sizeof(ip6h->ip6_src));
+ memcpy(&ip6h->ip6_dst, &(((const struct sockaddr_in6 *)
+ dst)->sin6_addr), sizeof(ip6h->ip6_dst));
+
+ data = packet + sizeof(*ip6h);
+ data_len = len - sizeof(*ip6h);
+
+ switch (proto) {
+ case IPPROTO_TCP:
+ __assemble_tcp(data, data_len, ctx->syn, ctx->ack, ctx->urg,
+ ctx->fin, ctx->rst, ctx->psh, ctx->ecn, ctx->dport);
+ off_next = sizeof(struct tcphdr);
+ break;
+ case IPPROTO_ICMP:
+ case IPPROTO_ICMPV6:
+ __assemble_icmp6(data, data_len);
+ off_next = sizeof(struct icmp6hdr);
+ break;
+ default:
+ bug();
+ }
+
+ data = packet + sizeof(*ip6h) + off_next;
+ data_len = len - sizeof(*ip6h) - off_next;
+
+ __assemble_data(data, data_len, ctx->payload);
+
+ return ntohl(ip6h->ip6_flow) & 0x000fffff;
+}
+
+static int check_ipv4(uint8_t *packet, size_t len, int ttl, int id,
+ const struct sockaddr *ss)
+{
+ struct iphdr *iph = (struct iphdr *) packet;
+ struct iphdr *iph_inner;
+ struct icmphdr *icmph;
+
+ if (iph->protocol != IPPROTO_ICMP)
+ return -EINVAL;
+ if (iph->daddr != ((const struct sockaddr_in *) ss)->sin_addr.s_addr)
+ return -EINVAL;
+
+ icmph = (struct icmphdr *) (packet + sizeof(struct iphdr));
+ if (icmph->type != ICMP_TIME_EXCEEDED)
+ return -EINVAL;
+ if (icmph->code != ICMP_EXC_TTL)
+ return -EINVAL;
+
+ iph_inner = (struct iphdr *) (packet + sizeof(struct iphdr) +
+ sizeof(struct icmphdr));
+ if (ntohs(iph_inner->id) != id)
+ return -EINVAL;
+
+ return len;
+}
+
+static void handle_ipv4(uint8_t *packet, size_t len, int dns_resolv, int latitude)
+{
+ char hbuff[256];
+ struct iphdr *iph = (struct iphdr *) packet;
+ struct sockaddr_in sd;
+ struct hostent *hent;
+ const char *as, *country, *city;
+
+ memset(hbuff, 0, sizeof(hbuff));
+ memset(&sd, 0, sizeof(sd));
+ sd.sin_family = PF_INET;
+ sd.sin_addr.s_addr = iph->saddr;
+
+ getnameinfo((struct sockaddr *) &sd, sizeof(sd),
+ hbuff, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
+
+ as = geoip4_as_name(sd);
+ country = geoip4_country_name(sd);
+ city = geoip4_city_name(sd);
+
+ if (dns_resolv) {
+ hent = gethostbyaddr(&sd.sin_addr, sizeof(sd.sin_addr), PF_INET);
+ if (hent)
+ printf(" %s (%s)", hent->h_name, hbuff);
+ else
+ printf(" %s", hbuff);
+ } else {
+ printf(" %s", hbuff);
+ }
+ if (as)
+ printf(" in %s", as);
+ if (country) {
+ printf(" in %s", country);
+ if (city)
+ printf(", %s", city);
+ }
+ if (latitude)
+ printf(" (%f/%f)", geoip4_latitude(sd), geoip4_longitude(sd));
+}
+
+static int check_ipv6(uint8_t *packet, size_t len, int ttl, int id,
+ const struct sockaddr *ss)
+{
+ struct ip6_hdr *ip6h = (struct ip6_hdr *) packet;
+ struct ip6_hdr *ip6h_inner;
+ struct icmp6hdr *icmp6h;
+
+ if (ip6h->ip6_nxt != 0x3a)
+ return -EINVAL;
+ if (memcmp(&ip6h->ip6_dst, &(((const struct sockaddr_in6 *)
+ ss)->sin6_addr), sizeof(ip6h->ip6_dst)))
+ return -EINVAL;
+
+ icmp6h = (struct icmp6hdr *) (packet + sizeof(*ip6h));
+ if (icmp6h->icmp6_type != ICMPV6_TIME_EXCEED)
+ return -EINVAL;
+ if (icmp6h->icmp6_code != ICMPV6_EXC_HOPLIMIT)
+ return -EINVAL;
+
+ ip6h_inner = (struct ip6_hdr *) (packet + sizeof(*ip6h) + sizeof(*icmp6h));
+ if ((ntohl(ip6h_inner->ip6_flow) & 0x000fffff) != id)
+ return -EINVAL;
+
+ return len;
+}
+
+static void handle_ipv6(uint8_t *packet, size_t len, int dns_resolv, int latitude)
+{
+ char hbuff[256];
+ struct ip6_hdr *ip6h = (struct ip6_hdr *) packet;
+ struct sockaddr_in6 sd;
+ struct hostent *hent;
+ const char *as, *country, *city;
+
+ memset(hbuff, 0, sizeof(hbuff));
+ memset(&sd, 0, sizeof(sd));
+ sd.sin6_family = PF_INET6;
+ memcpy(&sd.sin6_addr, &ip6h->ip6_src, sizeof(ip6h->ip6_src));
+
+ getnameinfo((struct sockaddr *) &sd, sizeof(sd),
+ hbuff, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
+
+ as = geoip6_as_name(sd);
+ country = geoip6_country_name(sd);
+ city = geoip6_city_name(sd);
+
+ if (dns_resolv) {
+ hent = gethostbyaddr(&sd.sin6_addr, sizeof(sd.sin6_addr), PF_INET6);
+ if (hent)
+ printf(" %s (%s)", hent->h_name, hbuff);
+ else
+ printf(" %s", hbuff);
+ } else {
+ printf(" %s", hbuff);
+ }
+ if (as)
+ printf(" in %s", as);
+ if (country) {
+ printf(" in %s", country);
+ if (city)
+ printf(", %s", city);
+ }
+ if (latitude)
+ printf(" (%f/%f)", geoip6_latitude(sd), geoip6_longitude(sd));
+}
+
+static void show_trace_info(struct ctx *ctx, const struct sockaddr_storage *ss,
+ const struct sockaddr_storage *sd)
+{
+ char hbuffs[256], hbuffd[256];
+
+ memset(hbuffd, 0, sizeof(hbuffd));
+ getnameinfo((struct sockaddr *) sd, sizeof(*sd),
+ hbuffd, sizeof(hbuffd), NULL, 0, NI_NUMERICHOST);
+
+ memset(hbuffs, 0, sizeof(hbuffs));
+ getnameinfo((struct sockaddr *) ss, sizeof(*ss),
+ hbuffs, sizeof(hbuffs), NULL, 0, NI_NUMERICHOST);
+
+ printf("AS path IPv%d TCP trace from %s to %s:%s (%s) with len %d "
+ "Bytes, %u max hops\n", ctx->proto == IPPROTO_IP ? 4 : 6,
+ hbuffs, hbuffd, ctx->port, ctx->host, ctx->totlen, ctx->max_ttl);
+
+ printf("Using flags SYN:%d,ACK:%d,ECN:%d,FIN:%d,PSH:%d,RST:%d,URG:%d\n",
+ ctx->syn, ctx->ack, ctx->ecn, ctx->fin, ctx->psh, ctx->rst, ctx->urg);
+
+ if (ctx->payload)
+ printf("With payload: \'%s\'\n", ctx->payload);
+}
+
+static int get_remote_fd(struct ctx *ctx, struct sockaddr_storage *ss,
+ struct sockaddr_storage *sd)
+{
+ int fd = -1, ret, one = 1;
+ struct addrinfo hints, *ahead, *ai;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+ hints.ai_flags = AI_NUMERICSERV;
+
+ ret = getaddrinfo(ctx->host, ctx->port, &hints, &ahead);
+ if (ret < 0)
+ panic("Cannot get address info!\n");
+
+ for (ai = ahead; ai != NULL && fd < 0; ai = ai->ai_next) {
+ if (!((ai->ai_family == PF_INET6 && ctx->proto == IPPROTO_IPV6) ||
+ (ai->ai_family == PF_INET && ctx->proto == IPPROTO_IP)))
+ continue;
+
+ fd = socket(ai->ai_family, SOCK_RAW, IPPROTO_RAW);
+ if (fd < 0)
+ continue;
+
+ memset(ss, 0, sizeof(*ss));
+ ret = device_address(ctx->dev, ai->ai_family, ss);
+ if (ret < 0)
+ panic("Cannot get own device address!\n");
+
+ ret = bind(fd, (struct sockaddr *) ss, sizeof(*ss));
+ if (ret < 0)
+ panic("Cannot bind socket!\n");
+
+ memset(sd, 0, sizeof(*sd));
+ memcpy(sd, ai->ai_addr, ai->ai_addrlen);
+
+ ctx->sd_len = ai->ai_addrlen;
+ ctx->dport = strtoul(ctx->port, NULL, 10);
+
+ ret = setsockopt(fd, ctx->proto, IP_HDRINCL, &one, sizeof(one));
+ if (ret < 0)
+ panic("Kernel does not support IP_HDRINCL!\n");
+
+ if (ai->ai_family == PF_INET6) {
+ struct sockaddr_in6 *sd6 = (struct sockaddr_in6 *) sd;
+
+ sd6->sin6_port = 0;
+ }
+
+ break;
+ }
+
+ freeaddrinfo(ahead);
+
+ if (fd < 0)
+ panic("Cannot create socket! Does remote "
+ "support IPv%d?!\n",
+ ctx->proto == IPPROTO_IP ? 4 : 6);
+
+ return fd;
+}
+
+static void inject_filter(struct ctx *ctx, int fd)
+{
+ struct sock_fprog bpf_ops;
+
+ enable_kernel_bpf_jit_compiler();
+
+ memset(&bpf_ops, 0, sizeof(bpf_ops));
+ bpf_ops.filter = (struct sock_filter *) af_ops[ctx->proto].filter;
+ bpf_ops.len = af_ops[ctx->proto].flen;
+
+ bpf_attach_to_sock(fd, &bpf_ops);
+}
+
+static int __process_node(struct ctx *ctx, int fd, int fd_cap, int ttl,
+ int inner_proto, uint8_t *pkt_snd, uint8_t *pkt_rcv,
+ const struct sockaddr_storage *ss,
+ const struct sockaddr_storage *sd, struct timeval *diff)
+{
+ int pkt_id, ret, timeout;
+ struct pollfd pfd;
+ struct timeval start, end;
+
+ prepare_polling(fd_cap, &pfd);
+
+ memset(pkt_snd, 0, ctx->totlen);
+ pkt_id = af_ops[ctx->proto].assembler(pkt_snd, ctx->totlen, ttl,
+ inner_proto, ctx,
+ (const struct sockaddr *) sd,
+ (const struct sockaddr *) ss);
+
+ ret = sendto(fd, pkt_snd, ctx->totlen, 0, (struct sockaddr *) sd,
+ ctx->sd_len);
+ if (ret < 0)
+ panic("sendto failed: %s\n", strerror(errno));
+
+ bug_on(gettimeofday(&start, NULL));
+
+ timeout = (ctx->timeout > 0 ? ctx->timeout : 2) * 1000;
+
+ ret = poll(&pfd, 1, timeout);
+ if (ret > 0 && pfd.revents & POLLIN && sigint == 0) {
+ bug_on(gettimeofday(&end, NULL));
+ if (diff)
+ timersub(&end, &start, diff);
+
+ ret = recvfrom(fd_cap, pkt_rcv, ctx->rcvlen, 0, NULL, NULL);
+ if (ret < sizeof(struct ethhdr) + af_ops[ctx->proto].min_len_icmp)
+ return -EIO;
+
+ return af_ops[ctx->proto].check(pkt_rcv + sizeof(struct ethhdr),
+ ret - sizeof(struct ethhdr), ttl,
+ pkt_id, (const struct sockaddr *) ss);
+ } else {
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static void timerdiv(const unsigned long divisor, const struct timeval *tv,
+ struct timeval *result)
+{
+ uint64_t x = ((uint64_t) tv->tv_sec * 1000 * 1000 + tv->tv_usec) / divisor;
+
+ result->tv_sec = x / 1000 / 1000;
+ result->tv_usec = x % (1000 * 1000);
+}
+
+static int timevalcmp(const void *t1, const void *t2)
+{
+ if (timercmp((struct timeval *) t1, (struct timeval *) t2, <))
+ return -1;
+ if (timercmp((struct timeval *) t1, (struct timeval *) t2, >))
+ return 1;
+
+ return 0;
+}
+
+static int __process_time(struct ctx *ctx, int fd, int fd_cap, int ttl,
+ int inner_proto, uint8_t *pkt_snd, uint8_t *pkt_rcv,
+ const struct sockaddr_storage *ss,
+ const struct sockaddr_storage *sd)
+{
+ int good = 0, i, j = 0, ret = -EIO, idx, ret_good = -EIO;
+ struct timeval probes[9], *tmp, sum, res;
+ uint8_t *trash = xmalloc(ctx->rcvlen);
+ char *cwait[] = { "-", "\\", "|", "/" };
+ const char *proto_short[] = {
+ [IPPROTO_TCP] = "t",
+ [IPPROTO_ICMP] = "i",
+ [IPPROTO_ICMPV6] = "i",
+ };
+
+ memset(probes, 0, sizeof(probes));
+ for (i = 0; i < array_size(probes) && sigint == 0; ++i) {
+ ret = __process_node(ctx, fd, fd_cap, ttl, inner_proto,
+ pkt_snd, good == 0 ? pkt_rcv : trash,
+ ss, sd, &probes[i]);
+ if (ret > 0) {
+ if (good == 0)
+ ret_good = ret;
+ good++;
+ }
+
+ if (good == 0 && ctx->queries == i)
+ break;
+
+ usleep(50000);
+
+ printf("\r%2d: %s", ttl, cwait[j++]);
+ fflush(stdout);
+ if (j >= array_size(cwait))
+ j = 0;
+ }
+
+ if (good == 0) {
+ xfree(trash);
+ return -EIO;
+ }
+
+ tmp = xmalloc(sizeof(struct timeval) * good);
+ for (i = j = 0; i < array_size(probes); ++i) {
+ if (probes[i].tv_sec == 0 && probes[i].tv_usec == 0)
+ continue;
+ tmp[j].tv_sec = probes[i].tv_sec;
+ tmp[j].tv_usec = probes[i].tv_usec;
+ j++;
+ }
+
+ qsort(tmp, j, sizeof(struct timeval), timevalcmp);
+
+ printf("\r%2d: %s[", ttl, proto_short[inner_proto]);
+ idx = j / 2;
+ switch (j % 2) {
+ case 0:
+ timeradd(&tmp[idx], &tmp[idx - 1], &sum);
+ timerdiv(2, &sum, &res);
+ if (res.tv_sec > 0)
+ printf("%lu sec ", res.tv_sec);
+ printf("%7lu us", res.tv_usec);
+ break;
+ case 1:
+ if (tmp[idx].tv_sec > 0)
+ printf("%lu sec ", tmp[idx].tv_sec);
+ printf("%7lu us", tmp[idx].tv_usec);
+ break;
+ default:
+ bug();
+ }
+ printf("]");
+
+ xfree(tmp);
+ xfree(trash);
+
+ return ret_good;
+}
+
+static int __probe_remote(struct ctx *ctx, int fd, int fd_cap, int ttl,
+ uint8_t *pkt_snd, uint8_t *pkt_rcv,
+ const struct sockaddr_storage *ss,
+ const struct sockaddr_storage *sd,
+ int inner_proto)
+{
+ int ret = -EIO, tries = ctx->queries;
+
+ while (tries-- > 0 && sigint == 0) {
+ ret = __process_time(ctx, fd, fd_cap, ttl, inner_proto,
+ pkt_snd, pkt_rcv, ss, sd);
+ if (ret < 0)
+ continue;
+
+ af_ops[ctx->proto].handler(pkt_rcv + sizeof(struct ethhdr),
+ ret - sizeof(struct ethhdr),
+ ctx->dns_resolv, ctx->latitude);
+ if (ctx->show) {
+ struct pkt_buff *pkt;
+
+ printf("\n");
+ pkt = pkt_alloc(pkt_rcv, ret);
+ hex_ascii(pkt);
+ tprintf_flush();
+ pkt_free(pkt);
+ }
+
+ break;
+ }
+
+ return ret;
+}
+
+static int __process_ttl(struct ctx *ctx, int fd, int fd_cap, int ttl,
+ uint8_t *pkt_snd, uint8_t *pkt_rcv,
+ const struct sockaddr_storage *ss,
+ const struct sockaddr_storage *sd)
+{
+ int ret = -EIO, i;
+ const int inner_protos[] = {
+ IPPROTO_TCP,
+ IPPROTO_ICMP,
+ };
+
+ printf("%2d: ", ttl);
+ fflush(stdout);
+
+ for (i = 0; i < array_size(inner_protos) && sigint == 0; ++i) {
+ ret = __probe_remote(ctx, fd, fd_cap, ttl, pkt_snd, pkt_rcv, ss, sd,
+ inner_protos[i]);
+ if (ret > 0)
+ break;
+ }
+
+ if (ret <= 0)
+ printf("\r%2d: ?[ no answer]", ttl);
+ if (ctx->show == 0)
+ printf("\n");
+ if (ctx->show && ret <= 0)
+ printf("\n\n");
+
+ fflush(stdout);
+ return 0;
+}
+
+static int main_trace(struct ctx *ctx)
+{
+ int fd, fd_cap, ifindex, ttl;
+ struct ring dummy_ring;
+ struct sockaddr_storage ss, sd;
+ uint8_t *pkt_snd, *pkt_rcv;
+
+ fd = get_remote_fd(ctx, &ss, &sd);
+ fd_cap = pf_socket();
+
+ inject_filter(ctx, fd_cap);
+
+ ifindex = device_ifindex(ctx->dev);
+ bind_rx_ring(fd_cap, &dummy_ring, ifindex);
+
+ if (ctx->totlen < af_ops[ctx->proto].min_len_tcp) {
+ ctx->totlen = af_ops[ctx->proto].min_len_tcp;
+ if (ctx->payload)
+ ctx->totlen += strlen(ctx->payload);
+ }
+
+ ctx->rcvlen = device_mtu(ctx->dev) - sizeof(struct ethhdr);
+ if (ctx->totlen >= ctx->rcvlen)
+ panic("Packet len exceeds device MTU!\n");
+
+ pkt_snd = xmalloc(ctx->totlen);
+ pkt_rcv = xmalloc(ctx->rcvlen);
+
+ show_trace_info(ctx, &ss, &sd);
+
+ for (ttl = ctx->init_ttl; ttl <= ctx->max_ttl && sigint == 0; ++ttl)
+ __process_ttl(ctx, fd, fd_cap, ttl, pkt_snd, pkt_rcv,
+ &ss, &sd);
+
+ xfree(pkt_snd);
+ xfree(pkt_rcv);
+
+ close(fd_cap);
+ close(fd);
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ int c, opt_index, ret;
+ struct ctx ctx;
+
+ setfsuid(getuid());
+ setfsgid(getgid());
+
+ srand(time(NULL));
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.init_ttl = 1;
+ ctx.max_ttl = 30;
+ ctx.queries = 2;
+ ctx.timeout = 2;
+ ctx.proto = IPPROTO_IP;
+ ctx.payload = NULL;
+ ctx.dev = xstrdup("eth0");
+ ctx.port = xstrdup("80");
+
+ while ((c = getopt_long(argc, argv, short_options, long_options,
+ &opt_index)) != EOF) {
+ switch (c) {
+ case 'h':
+ help();
+ break;
+ case 'v':
+ version();
+ break;
+ case 'u':
+ update_geoip();
+ die();
+ break;
+ case 'H':
+ ctx.host = xstrdup(optarg);
+ break;
+ case 'p':
+ if (ctx.port)
+ xfree(ctx.port);
+ ctx.port = xstrdup(optarg);
+ break;
+ case 'n':
+ ctx.dns_resolv = 0;
+ break;
+ case '4':
+ ctx.proto = IPPROTO_IP;
+ break;
+ case '6':
+ ctx.proto = IPPROTO_IPV6;
+ break;
+ case 'Z':
+ ctx.show = 1;
+ break;
+ case 'N':
+ ctx.dns_resolv = 1;
+ break;
+ case 'f':
+ ctx.init_ttl = atoi(optarg);
+ if (ctx.init_ttl <= 0)
+ help();
+ break;
+ case 'm':
+ ctx.max_ttl = atoi(optarg);
+ if (ctx.max_ttl <= 0)
+ help();
+ break;
+ case 'i':
+ case 'd':
+ free(ctx.dev);
+ ctx.dev = xstrdup(optarg);
+ break;
+ case 'q':
+ ctx.queries = atoi(optarg);
+ if (ctx.queries <= 0)
+ help();
+ break;
+ case 'x':
+ ctx.timeout = atoi(optarg);
+ if (ctx.timeout <= 0)
+ help();
+ break;
+ case 'L':
+ ctx.latitude = 1;
+ break;
+ case 'S':
+ ctx.syn = 1;
+ break;
+ case 'A':
+ ctx.ack = 1;
+ break;
+ case 'F':
+ ctx.fin = 1;
+ break;
+ case 'U':
+ ctx.urg = 1;
+ break;
+ case 'P':
+ ctx.psh = 1;
+ break;
+ case 'R':
+ ctx.rst = 1;
+ break;
+ case 'E':
+ ctx.syn = 1;
+ ctx.ecn = 1;
+ break;
+ case 't':
+ ctx.tos = atoi(optarg);
+ if (ctx.tos < 0)
+ help();
+ break;
+ case 'G':
+ ctx.nofrag = 1;
+ break;
+ case 'X':
+ ctx.payload = xstrdup(optarg);
+ break;
+ case 'l':
+ ctx.totlen = atoi(optarg);
+ if (ctx.totlen <= 0)
+ help();
+ break;
+ case '?':
+ switch (optopt) {
+ case 'H':
+ case 'p':
+ case 'f':
+ case 'm':
+ case 'i':
+ case 'd':
+ case 'q':
+ case 'x':
+ case 'X':
+ case 't':
+ case 'l':
+ panic("Option -%c requires an argument!\n",
+ optopt);
+ default:
+ if (isprint(optopt))
+ printf("Unknown option character `0x%X\'!\n", optopt);
+ die();
+ }
+ default:
+ break;
+ }
+ }
+
+ if (argc < 3 || !ctx.host || !ctx.port || ctx.init_ttl > ctx.max_ttl ||
+ ctx.init_ttl > MAXTTL || ctx.max_ttl > MAXTTL)
+ help();
+
+ if (!device_up_and_running(ctx.dev))
+ panic("Networking device not up and running!\n");
+ if (device_mtu(ctx.dev) <= ctx.totlen)
+ panic("Packet larger than device MTU!\n");
+
+ register_signal(SIGHUP, signal_handler);
+ register_signal(SIGINT, signal_handler);
+
+ tprintf_init();
+ init_geoip(1);
+
+ ret = main_trace(&ctx);
+
+ destroy_geoip();
+ tprintf_cleanup();
+
+ free(ctx.dev);
+ free(ctx.host);
+ free(ctx.port);
+ free(ctx.payload);
+
+ return ret;
+}