aboutsummaryrefslogtreecommitdiffstats
path: root/libglouglou/libggnet_traceroute.c
diff options
context:
space:
mode:
Diffstat (limited to 'libglouglou/libggnet_traceroute.c')
-rw-r--r--libglouglou/libggnet_traceroute.c420
1 files changed, 420 insertions, 0 deletions
diff --git a/libglouglou/libggnet_traceroute.c b/libglouglou/libggnet_traceroute.c
new file mode 100644
index 0000000..96a6201
--- /dev/null
+++ b/libglouglou/libggnet_traceroute.c
@@ -0,0 +1,420 @@
+/*
+ * Traceroute library using libevent, libdnet and libpcap
+ *
+ * 2012 Laurent Ghigonis <laurent@gouloum.fr>
+ *
+ * Inspired from jtrace
+ * http://monkey.org/~jose/software/jtrace/
+ * Copyright (c) 2003-2004 Jose Nazario <jose@monkey.org>
+ * All rights reserved.
+ * Inspired from OpenBSD's traceroute
+ * http://www.openbsd.org/cgi-bin/cvsweb/src/usr.sbin/traceroute/traceroute.c?rev=HEAD
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ * This code is derived from software contributed to Berkeley by
+ * Van Jacobson.
+ * Inspired from netsniff-ng's astraceroute
+ * http://www.netsniff-ng.org/
+ * https://github.com/gnumaniacs/netsniff-ng/blob/master/src/astraceroute.c
+ * By Daniel Borkmann <daniel@netsniff-ng.org>
+ * Copyright 2011 Daniel Borkmann.
+ */
+
+/*
+ * Traceroute algorithm
+ *
+ * Send algo (_cb_send) :
+ *
+ * send 3 * 15 packets to next 15 ttls
+ * add packets in hops_list
+ * set recv timeout in 3s
+ *
+ * Recv algo (_cb_recv) :
+ *
+ * check if dst IP is for us
+ * check if src IP is for us in hop_list
+ * tcp / udp
+ * is it answer from the target ?
+ XXX we might still want to continue a few hops after target
+ * target info write in hop_list
+ * recv timeout in 0.5s
+ * icmp unreach / exceed :
+ * did we send the inner pkt ?
+ * hop info write in hop_list
+ * which ttl ?
+ * is it answer from the target ?
+ * recv timeout in 0.5s
+ * did we receive all answers ?
+ * send more !
+ */
+
+/* TODO
+ * * hop store 3 answer results
+ * * send more packets of 15th answer
+ * * LATER hability to intrace all captured TCP session
+ * check for no incidence on the TCP connection
+ * can do on UDP ?
+ */
+
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <netinet/in.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "libggnet_traceroute.h"
+
+#define TRACE_DPORT 80
+#define TIMEOUT_TOTAL_S 3
+#define TIMEOUT_AFTERTARGET_US 500000
+
+static void _req_free(struct ggnet_traceroute_req *);
+static void _cb_recv(evutil_socket_t, short, void *);
+static void _cb_send(evutil_socket_t, short, void *);
+static int pcap_dloff(pcap_t *); // XXX move to libggnet_utils ?
+
+struct trace_pkt {
+ union {
+ struct ip_hdr ip;
+ } pkt_hdr_i;
+ union {
+ struct tcp_hdr tcp;
+ } pkt_hdr_t;
+};
+
+struct ggnet_traceroute *
+ggnet_traceroute_new(struct event_base *ev_base, char *iface)
+{
+ struct ggnet_traceroute *ggtr;
+ char ebuff[PCAP_ERRBUF_SIZE];
+
+ ggtr = calloc(1, sizeof(struct ggnet_traceroute));
+ if (!ggtr) {
+ printf("could not allocate ggnet_traceroute\n");
+ exit(1);
+ }
+ ggtr->ev_base = ev_base;
+ ggtr->pkt_ip = ip_open();
+ if (!ggtr->pkt_ip) {
+ printf("ip_open() failed\n");
+ ggnet_traceroute_free(ggtr);
+ return NULL;
+ }
+ ggtr->pkt_rand = rand_open();
+
+ /* XXX get iface from route_open, route_get and pcap_lookupnet.
+ * will need to do so in ggnet_traceroute_trace() so user privs, so will need
+ * to prefetch the routes / interfaces, maybe */
+
+ ggtr->pcap = pcap_open_live(iface, 1500, 1, 500, ebuff);
+ if (ggtr->pcap == NULL)
+ err(1, "pcap_open_live()");
+ ggtr->pcap_dllen = pcap_dloff(ggtr->pcap);
+ ggtr->pcap_fd = pcap_fileno(ggtr->pcap);
+
+ return ggtr;
+}
+
+void
+ggnet_traceroute_free(struct ggnet_traceroute *ggtr)
+{
+ struct ggnet_traceroute_req *req;
+
+ if (ggtr->pcap)
+ pcap_close(ggtr->pcap);
+ if (ggtr->pkt_ip)
+ ip_close(ggtr->pkt_ip);
+ if (ggtr->pkt_rand)
+ rand_close(ggtr->pkt_rand);
+ LIST_FOREACH(req, &ggtr->req_list, entry)
+ _req_free(req);
+ free(ggtr);
+}
+
+struct ggnet_traceroute_req *
+ggnet_traceroute_trace(struct ggnet_traceroute *ggtr,
+ struct in_addr *ip,
+ void (*cb_usr)(struct in_addr *, struct ggnet_traceroute_req *, void *),
+ void *data)
+{
+ struct ggnet_traceroute_req *req;
+ struct ggnet_traceroute_hop *target;
+
+ req = calloc(1, sizeof(struct ggnet_traceroute_req));
+ if (!req) {
+ printf("could not allocate ggnet_traceroute_req\n");
+ exit(1);
+ }
+ target = calloc(1, sizeof(struct ggnet_traceroute_hop));
+ if (!target) {
+ printf("cannot allocate ggnet_traceroute_hop\n");
+ exit(1);
+ }
+
+ addr_aton("127.0.0.1", &req->target->ip);
+ addr_aton("127.0.0.1", &req->srcip);
+
+ req->ev_recv = event_new(ggtr->ev_base,
+ ggtr->pcap_fd, EV_READ, _cb_recv, req);
+ event_add(req->ev_recv, NULL);
+ req->ev_send = event_new(ggtr->ev_base,
+ ggtr->pcap_fd, EV_WRITE, _cb_send, req);
+ event_add(req->ev_send, NULL);
+
+ // XXX evtimer timeout !!!
+ evtimer_new(ggtr->ev_base, _cb_trace_timeout, req);
+
+ LIST_INSERT_HEAD(&ggtr->req_list, req, entry);
+ return req;
+}
+
+void
+ggnet_traceroute_cancel(struct ggnet_traceroute *ggtr,
+ struct ggnet_traceroute_req *req)
+{
+ LIST_REMOVE(req, entry);
+ ggtr->req_pending--;
+ _req_free(req);
+}
+
+static void
+_req_free(struct ggnet_traceroute_req *req)
+{
+ if (req->ev_recv)
+ event_free(req->ev_recv);
+ if (req->ev_send)
+ event_free(req->ev_send);
+ free(req);
+}
+
+static void
+_cb_recv(evutil_socket_t fd, short what, void *arg)
+{
+ struct ggnet_traceroute_hop *hop;
+ struct ggnet_traceroute_req *req;
+ struct ggnet_traceroute *ggtr;
+ struct pcap_pkthdr ph;
+ u_char *pread, *tmp;
+ struct ip_hdr *ip, ip_in;
+ struct tcp_hdr *tcp;
+ struct icmp_hdr *icmp;
+ struct addr ip_src;
+ char *reply = "U!";
+ char *p;
+
+ printf("cbggnet_traceroute cb_recv %s\n", addr_ntoa(&req->target->ip));
+ req = arg;
+ ggtr = req->ggtr;
+
+ if ((pread = (u_char *) pcap_next(ggtr->pcap, &ph)) == NULL) {
+ printf("libggnet_traceroute _cb_recv: read error\n");
+ return;
+ }
+
+ /* decode the current packet : ttl, ip, ... */
+ tmp = pread + ctx->dl_len;
+ ip = (struct ip_hdr *) tmp;
+ if (ip_h->ip_v != 4)
+ return;
+ p = inet_ntoasc(ip_h->ip_src);
+ if ((addr_aton(p, &ip_src)) == -1)
+ return;
+
+ switch(ip_h->ip_p) {
+
+ case IP_PROTO_TCP:
+ /* should be our target */
+ if (addr_cmp(&ip_src, &(req->target->ip)) != 0)
+ return;
+ tcp = (struct tcp_hdr *)(ip + IP_HDR_LEN);
+ if (((tcp->th_flags == TH_SYN + TH_ACK) ||
+ (tcp->th_flags == TH_RST + TH_ACK)) &&
+ (htons(tcp->th_sport) == ctx->dport)) {
+ hop = req->target;
+ hop->answer_count++;
+ flags = "SA";
+ if (tcp->th_flags == TH_RST + TH_ACK)
+ flags = "R";
+ strncpy(hop->answer, flags, sizeof(hop->answer));
+ _timeout_set(req, 0, TIMEOUT_AFTERTARGET_US);
+ }
+ break;
+
+ case IP_PROTO_ICMP:
+ /* answer from a hop */
+ icmp = (struct icmp_hdr *)(ip + IP_HDR_LEN);
+ /* XXX ? look for TTL_EXPIRED, PROHIBITED msgs */
+ ip_in = (struct iphdr *) (ip + sizeof(struct iphdr) +
+ sizeof(struct icmphdr));
+ hop = _hop_ttl(req, ip_in->ttl);
+ if (!hop) {
+ printf("libggnet_traceroute _cb_recv: WARNING: hop ttl %d not found !",
+ ip_in->ttl);
+ return;
+ }
+ hop->answer_count++;
+ if (icmp->icmp_type == ICMP_UNREACH) {
+ switch (icmp->icmp_code) {
+ case ICMP_UNREACH_NET:
+ strncpy(hop->answer, "N!", sizeof(hop->answer));
+ break;
+ case ICMP_UNREACH_HOST:
+ strncpy(hop->answer, "H!", sizeof(hop->answer));
+ break;
+ case ICMP_UNREACH_PROTO:
+ strncpy(hop->answer, "P!", sizeof(hop->answer));
+ break;
+ case ICMP_UNREACH_NET_PROHIB:
+ case ICMP_UNREACH_HOST_PROHIB:
+ strncpy(hop->answer, "A!", sizeof(hop->answer));
+ break;
+ }
+ } else if (icmp->icmp_type == ICMP_TIMEXCEED) {
+ strncpy(hop->answer, "A!", sizeof(hop->answer));
+ reply = "X!";
+ event_add(&ctx->send_ev, &ctx->tv);
+ }
+ ctx->hop++;
+ break;
+ }
+ fflush(stdout);
+
+ /* do we need to send more packets ? */
+}
+
+static void
+_cb_send(evutil_socket_t fd, short what, void *arg)
+{
+ struct ggnet_traceroute_req *req;
+ struct ggnet_traceroute *ggtr;
+ u_char buf[BUFSIZ];
+ struct trace_pkt *pkt;
+ int len;
+ int ttl;
+
+ printf("cbggnet_traceroute cb_send %s\n", addr_ntoa(&req->ip));
+ req = arg;
+ ggtr = req->ggtr;
+ pkt = (struct trace_pkt *)buf;
+ len = IP_HDR_LEN + TCP_HDR_LEN;
+
+ for (ttl=req->ttl_sent; i<15; i++) {
+ hop = calloc(1, sizeof(struct ggnet_traceroute_hop));
+ if (!hop) {
+ printf("cannot allocate ggnet_traceroute_hop\n");
+ exit(1);
+ }
+ hop->ttl = ttl;
+ //addr_pack(hop->ip, ADDR_TYPE_IP, IP_ADDR_BITS, ip, IP_ADDR_LEN);
+ ip_pack_hdr(&pkt->pkt_hdr_i.ip, IP_TOS_LOWDELAY, len,
+ rand_uint16(ggtr->pkt_rand), 0, ttl, IP_PROTO_TCP,
+ req->src.addr_ip, req->dst.addr_ip);
+ tcp_pack_hdr(&pkt->pkt_hdr_t.tcp, rand_uint16(ggtr->pkt_rand), TRACE_DPORT,
+ rand_uint32(ggtr->pkt_rand), rand_uint32(ggtr->pkt_rand),
+ TH_SYN, rand_uint16(ggtr->pkt_rand), 0);
+ ip_checksum(pkt, len);
+ TAILQ_INSERT_TAIL(&req->hops_list, hop, entry);
+ for (i=0; i<TRACEROUTE_NB_PROBES; i++)
+ if (ip_send(ggtr->pkt_ip, pkt, len) < 0)
+ warn("_cb_send : ip_send short send");
+ _timeout_set(req, TIMEOUT_TOTAL_S, 0);
+ }
+ req->ttl_sent = ttl;
+}
+
+static void
+_timeout_set(struct ggnet_traceroute_req *req, int sec, int usec)
+{
+ evtimer_del(req->run.ev_timeout);
+ req->run.tv_timeout.secs = sec;
+ req->run.tv_timeout.usecs = usec;
+ evtimer_add(req->run.ev_timeout, req->run.tv_timeout);
+}
+
+static ggnet_traceroute_hop *
+_hop_last(ggnet_traceroute_req *req)
+{
+ struct ggnet_traceroute_hop *h;
+
+ TAILQ_FOREACH_REVERSE(h, req->hop_list, entry) {
+ if (h->answer_count > 0)
+ return h;
+ }
+ return NULL;
+}
+
+void _cb_trace_timeout(evutil_socket_t fd, short what, void *arg)
+{
+ struct ggnet_traceroute_req *req;
+
+ req = arg;
+
+ // XXX IN PROGRESS
+ last = _hop_last(req);
+ req->target->ttl = last->ttl + 1;
+}
+
+static int
+pcap_dloff(pcap_t *pd)
+{
+ int i;
+
+ i = pcap_datalink(pd);
+
+ switch (i) {
+ case DLT_EN10MB:
+ i = 14;
+ break;
+ case DLT_IEEE802:
+ i = 22;
+ break;
+ case DLT_FDDI:
+ i = 21;
+ break;
+ case DLT_NULL:
+ i = 4;
+ break;
+ default:
+ i = -1;
+ break;
+ }
+ return (i);
+}
+
+/* XXX do it with evdns
+static int
+get_asn(struct in_addr in)
+{
+ const u_char *uaddr = (const u_char *)&in.s_addr;
+ int i, counter;
+ struct rrsetinfo *answers = NULL;
+ char qbuf[MAXDNAME];
+ int asn = -1;
+
+ if (snprintf(qbuf, sizeof qbuf, "%u.%u.%u.%u.origin.asn.cymru.com",
+ (uaddr[3] & 0xff), (uaddr[2] & 0xff),
+ (uaddr[1] & 0xff), (uaddr[0] & 0xff)) >= sizeof (qbuf))
+ return;
+ if (getrrsetbyname(qbuf, C_IN, T_TXT, 0, &answers) != 0)
+ return;
+ for (counter = 0; counter < answers->rri_nrdatas; counter++) {
+ char *p, *as = answers->rri_rdatas[counter].rdi_data;
+ as++; // skip first byte, it contains length
+ if (p = strchr(as,'|')) {
+ p[-1] = 0;
+ asn = atoi(as);
+ goto ret;
+ }
+ }
+
+ret:
+ freerrset(answers);
+ return asn;
+}
+*/