diff options
author | Daniel Borkmann <dborkman@redhat.com> | 2013-03-15 10:41:48 +0100 |
---|---|---|
committer | Daniel Borkmann <dborkman@redhat.com> | 2013-03-15 10:41:48 +0100 |
commit | 1a9fbac03c684f29cff9ac44875bd9504a89f54e (patch) | |
tree | 1b2e40dbe5dc1899ef5b62c4325c9b94c9c450fc /astraceroute.c |
all: import netsniff-ng 0.5.8-rc0 source
We decided to get rid of the old Git history and start a new one for
several reasons:
*) Allow / enforce only high-quality commits (which was not the case
for many commits in the history), have a policy that is more close
to the one from the Linux kernel. With high quality commits, we
mean code that is logically split into commits and commit messages
that are signed-off and have a proper subject and message body.
We do not allow automatic Github merges anymore, since they are
total bullshit. However, we will either cherry-pick your patches
or pull them manually.
*) The old archive was about ~27MB for no particular good reason.
This basically derived from the bad decision that also some PDF
files where stored there. From this moment onwards, no binary
objects are allowed to be stored in this repository anymore.
The old archive is not wiped away from the Internet. You will still
be able to find it, e.g. on git.cryptoism.org etc.
Signed-off-by: Daniel Borkmann <dborkman@redhat.com>
Signed-off-by: Tobias Klauser <tklauser@distanz.ch>
Diffstat (limited to 'astraceroute.c')
-rw-r--r-- | astraceroute.c | 1077 |
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; +} |