/* * queue_daemon.c * detects bursts and calculates the round trip time by using ICMP packets * * Copyright (C) 2007 Thomas Mader * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "list.h" #define BUFSIZE 4096 #define BURST_LENGTH 5 static LIST_HEAD(list); static float threshold = 1.0; static FILE* logfile = NULL; static struct nfq_handle *h = NULL; #define NIPQUAD(addr) \ ((unsigned char *)&addr)[0], \ ((unsigned char *)&addr)[1], \ ((unsigned char *)&addr)[2], \ ((unsigned char *)&addr)[3] struct conn_id { int id; u_int32_t src_ip, dst_ip; u_int16_t src_port, dst_port; int blength; double tstamp; double sum; struct list_head elem; int echo_request_count; int echo_reply_count; }; u_short in_cksum(const u_short *addr, register int len, u_short csum) { register int nleft = len; const u_short *w = addr; register u_short answer; register int sum = csum; /* * Our algorithm is simple, using a 32 bit accumulator (sum), * we add sequential 16 bit words to it, and at the end, fold * back all the carry bits from the top 16 bits into the lower * 16 bits. */ while (nleft > 1) { sum += *w++; nleft -= 2; } /* mop up an odd byte, if necessary */ if (nleft == 1) sum += htons(*(u_char *)w << 8); /* * add back carry outs from top 16 bits to low 16 bits */ sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */ sum += (sum >> 16); /* add carry */ answer = ~sum; /* truncate to 16 bits */ return (answer); } #define DATALEN sizeof(struct icmphdr)+sizeof(struct timeval) char outpack[DATALEN]; int ntransmitted = 0; struct sockaddr_in whereto; /* who to ping */ static struct { struct cmsghdr cm; struct in_pktinfo ipi; } cmsg = { {sizeof(struct cmsghdr) + sizeof(struct in_pktinfo), SOL_IP, IP_PKTINFO}, {0, }}; int cmsg_len = sizeof(cmsg); int icmp_sock; /* socket file descriptor */ struct sockaddr_in source; int send_probe(char* target) { struct icmphdr *icp; source.sin_family = AF_INET; source.sin_addr.s_addr = INADDR_ANY; bzero((char *)&whereto, sizeof(whereto)); whereto.sin_family = AF_INET; inet_aton(target, &whereto.sin_addr); icp = (struct icmphdr *)outpack; icp->type = ICMP_ECHO; icp->code = 0; icp->checksum = 0; icp->un.echo.sequence = htons(ntransmitted++); icp->un.echo.id = 0; struct timeval ts; gettimeofday(&ts, NULL); memcpy(icp+1, &ts, sizeof(struct timeval)); /* compute ICMP checksum here */ icp->checksum = in_cksum((u_short *)icp, DATALEN, 0); static struct iovec iov = {outpack, 0}; static struct msghdr m = { &whereto, sizeof(whereto), &iov, 1, &cmsg, 0, 0 }; m.msg_controllen = cmsg_len; iov.iov_len = DATALEN; return sendmsg(icmp_sock, &m, 0); } static void writeLog(char* message) { struct timeval tv; gettimeofday(&tv, NULL); fprintf(logfile, "[%li.%.9li] %s", tv.tv_sec, tv.tv_usec, message); fflush(logfile); } static void cleanExit() { if (h) { nfq_close(h); h = NULL; } if (logfile) { fclose(logfile); logfile = NULL; } exit(EXIT_FAILURE); } static int icmp_echo_request(char* target) { return send_probe(target); } static void deal_with_icmp(char* payload) { int id; struct iphdr *iph = (struct iphdr*) payload; struct icmphdr *icmph = (struct icmphdr*) (payload + (4 * iph->ihl)); // TODO check if ICMP reply is really one of ours (random key?) // check if ICMP packet is an echo reply if(icmph->type != ICMP_ECHOREPLY) { return; } // retrieve time from ICMP data struct timeval *intime = (struct timeval*) (icmph + 1); double incoming_time = (double)intime->tv_sec + (double)intime->tv_usec/1000000; // retrieve id from ICMP header id = icmph->un.echo.id; /* compute round-trip-time */ struct timeval ctime; gettimeofday(&ctime, NULL); double current_time = (double)ctime.tv_sec + (double)ctime.tv_usec/1000000; double diff = current_time - incoming_time; /* check if we have an entry with this id and add echo reply and RTT */ if(!list_empty(&list)) { struct conn_id *p; list_for_each_entry(p, &list, elem) { if( id == p->id) { p->echo_reply_count++; p->sum += diff; fprintf(logfile, "new diff: %lf\n", p->sum); break; } } } } static int cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *data) { struct nfqnl_msg_packet_hdr *ph; int id = 0; ph = nfq_get_msg_packet_hdr(nfa); if (ph){ id = ntohl(ph->packet_id); } char *payload; if(nfq_get_payload(nfa, &payload) < 0) { writeLog("could not get payload\n"); } struct iphdr *iph = (struct iphdr*) payload; switch (iph->protocol) { case 1: deal_with_icmp(payload); // ICMP return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); case 17: break; // UDP default: return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); } double time = 0.0; struct timeval tv; if ( nfq_get_timestamp(nfa, &tv) < 0 ) { gettimeofday(&tv, NULL); time = (double)tv.tv_sec + (double)tv.tv_usec/1000000; } else { time = (double)tv.tv_sec + (double)tv.tv_usec/1000000; } struct udphdr *udph = (struct udphdr*) (payload + (4 * iph->ihl)); /* search our list of connections for existing connection with this data */ int found_id = 0; if(!list_empty(&list)) { struct conn_id *p; list_for_each_entry(p, &list, elem) { if ( (iph->saddr == p->src_ip) && (iph->daddr == p->dst_ip) && (udph->source == p->src_port) && (udph->dest == p->dst_port) ) { float diff = time - p->tstamp; found_id = 1; if( diff > threshold ) { p->blength = 1; } // diff <= threshold else { p->blength++; } fprintf(logfile, "new tstamp added to already existing id %d.\n", p->id); // the burst reaches the max burst length if(p->blength >= BURST_LENGTH) { writeLog("Sending ICMP echo request.\n"); char addy[16]; snprintf(addy, 15, "%u.%u.%u.%u", NIPQUAD(iph->daddr)); //TODO change to saddr/daddr!!! if (icmp_echo_request(addy) < 0) { writeLog("Failed to send ICMP echo request.\n"); } else { p->echo_request_count++; } p->blength = 0; } p->tstamp = time; struct timeval tv; gettimeofday(&tv, NULL); fprintf(logfile, "[%li.%li] new timestamp for %u.%u.%u.%u:%hu, TO: %u.%u.%u.%u:%hu\n", tv.tv_sec, tv.tv_usec, NIPQUAD(iph->saddr), ntohs(udph->source), NIPQUAD(iph->daddr), ntohs(udph->dest)); fflush(logfile); break; } } } if(!found_id) { struct conn_id* new_id = (struct conn_id*)malloc(sizeof(struct conn_id)); if(!new_id) { writeLog("Could not allocate struct for entry\n"); cleanExit(); } /* new_id->id = connection_id; */ INIT_LIST_HEAD(&new_id->elem); list_add_tail(&new_id->elem, &list); new_id->tstamp = time; new_id->sum = 0.0; new_id->blength = 1; new_id->echo_request_count = 0; new_id->echo_reply_count = 0; new_id->src_ip = iph->saddr; new_id->src_port = udph->source; new_id->dst_ip = iph->daddr; new_id->dst_port = udph->dest; struct timeval tv; gettimeofday(&tv, NULL); fprintf(logfile, "[%li.%li] new entry for %u.%u.%u.%u:%hu, TO: %u.%u.%u.%u:%hu\n", tv.tv_sec, tv.tv_usec, NIPQUAD(iph->saddr), ntohs(udph->source), NIPQUAD(iph->daddr), ntohs(udph->dest)); } return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); } int main(int argc, char **argv) { pid_t pid, sid; logfile = fopen("log", "w"); if (!logfile) { printf("Could not open logfile\n"); cleanExit(); } writeLog("logfile opened\n"); /* Fork off the parent process */ pid = fork(); if (pid < 0) { writeLog("Failed to fork\n"); cleanExit(); } /* If we got a good PID, then * we can exit the parent process. */ if (pid > 0) { exit(EXIT_SUCCESS); } /* Create a new SID for the child process */ sid = setsid(); if (sid < 0) { writeLog("Could not create new SID\n"); cleanExit(); } /* Close the standard file descriptors */ close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); /* Daemon-specific initialization goes here */ struct nfq_q_handle *qh; struct nfnl_handle *nh; int fd; int rv; char buf[BUFSIZE]; h = nfq_open(); if (!h) { writeLog("error during nfq_open()\n"); cleanExit(); } // unbinding existing nf_queue handler for AF_INET (if any) if (nfq_unbind_pf(h, AF_INET) < 0) { writeLog("error during nfq_unbind_pf()\n"); cleanExit(); } // binding nfnetlink_queue as nf_queue handler for AF_INET if (nfq_bind_pf(h, AF_INET) < 0) { writeLog("error during nfq_bind_pf()\n"); cleanExit(); } // binding this socket to queue '0' qh = nfq_create_queue(h, 0, &cb, NULL); if (!qh) { writeLog("error during nfq_create_queue()\n"); cleanExit(); } // setting copy_packet mode if (nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff) < 0) { writeLog("can't set packet_copy mode\n\n"); cleanExit(); } nh = nfq_nfnlh(h); fd = nfnl_fd(nh); icmp_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (icmp_sock < 0) { writeLog("Could not create socket\n"); cleanExit(); } if (bind(icmp_sock, (struct sockaddr*)&source, sizeof(source)) ) { writeLog("Could not bind socket\n"); cleanExit(); } while ((rv = recv(fd, buf, sizeof(buf), 0)) && rv >= 0) { nfq_handle_packet(h, buf, rv); } /*do { status = ipq_read(h, buf, BUFSIZE, 0); if (status < 0) { writeLog("Could not read the packet\n"); cleanExit(); } switch (ipq_message_type(buf)) { case NLMSG_ERROR: fprintf(logfile, "Received error message: %s\n", ipq_errstr() ); break; case IPQM_PACKET: { ipq_packet_msg_t *m = ipq_get_packet(buf); analysePacket(m); status = ipq_set_verdict(h, m->packet_id, NF_ACCEPT, 0, NULL); if (status < 0) { writeLog("Could not set verdict on packet\n"); cleanExit(); } break; } default: writeLog("Unknown message type!\n"); break; } } while (1);*/ // unbinding from queue 0 nfq_destroy_queue(qh); // closing library handle nfq_close(h); if (logfile) { fclose(logfile); logfile = NULL; } return EXIT_SUCCESS; }