diff options
Diffstat (limited to 'libglouglou/libggnet_traceroute.c')
-rw-r--r-- | libglouglou/libggnet_traceroute.c | 420 |
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; +} +*/ |