From 02013228914a1d17e8df15d4e2b7950469395a5c Mon Sep 17 00:00:00 2001 From: Bjørn Mork Date: Fri, 15 May 2015 10:23:51 +0200 Subject: ripe-atlas-fw: imported version 4520 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Bjørn Mork --- eperd/Config.in | 56 + eperd/Kbuild | 8 + eperd/condmv.c | 166 +++ eperd/eooqd.c | 805 ++++++++++++++ eperd/eperd.c | 1163 ++++++++++++++++++++ eperd/eperd.h | 51 + eperd/evhttpget.c | 57 + eperd/evping.c | 58 + eperd/evtdig.c | 2131 +++++++++++++++++++++++++++++++++++++ eperd/evtraceroute.c | 56 + eperd/httpget.c | 1760 +++++++++++++++++++++++++++++++ eperd/ping.c | 1288 +++++++++++++++++++++++ eperd/readresolv.c | 56 + eperd/readresolv.h | 5 + eperd/tcputil.c | 249 +++++ eperd/tcputil.h | 42 + eperd/traceroute.c | 2866 ++++++++++++++++++++++++++++++++++++++++++++++++++ 17 files changed, 10817 insertions(+) create mode 100644 eperd/Config.in create mode 100644 eperd/Kbuild create mode 100644 eperd/condmv.c create mode 100644 eperd/eooqd.c create mode 100644 eperd/eperd.c create mode 100644 eperd/eperd.h create mode 100644 eperd/evhttpget.c create mode 100644 eperd/evping.c create mode 100644 eperd/evtdig.c create mode 100644 eperd/evtraceroute.c create mode 100644 eperd/httpget.c create mode 100644 eperd/ping.c create mode 100644 eperd/readresolv.c create mode 100644 eperd/readresolv.h create mode 100644 eperd/tcputil.c create mode 100644 eperd/tcputil.h create mode 100644 eperd/traceroute.c (limited to 'eperd') diff --git a/eperd/Config.in b/eperd/Config.in new file mode 100644 index 0000000..f547875 --- /dev/null +++ b/eperd/Config.in @@ -0,0 +1,56 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Eperd" + +config EOOQD + bool "Eooqd" + default n + select FEATURE_SUID + select FEATURE_SYSLOG + help + Eooqd runs Atlas measurements just once. + +config EPERD + bool "Eperd" + default n + select FEATURE_SUID + select FEATURE_SYSLOG + help + Eperd periodically runs Atlas measurements. It is based on crond. + +config EVHTTPGET + bool "evhttpget" + default n + help + standalone version of event-driven httpget + +config EVPING + bool "evping" + default n + help + standalone version of event-driven ping + +config EVTDIG + bool "evtdig" + default n + depends on EPERD + help + tiny dig event driven version. support only limited queries id.sever + txt chaos. RIPE NCC 2011 + +config FEATURE_EVTDIG_DEBUG + bool "Enable debug support in evtdig" + default n + depends on EVTDIG + help + extra debug info. Also may cause segfault or/and memory leak. Add at your own risk. + +config EVTRACEROUTE + bool "evtraceroute" + default n + help + standalone version of event-driven traceroute +endmenu diff --git a/eperd/Kbuild b/eperd/Kbuild new file mode 100644 index 0000000..24d257a --- /dev/null +++ b/eperd/Kbuild @@ -0,0 +1,8 @@ +# Makefile for busybox +# +# Copyright (c) 2013 RIPE NCC +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +lib-y:= +lib-$(CONFIG_EPERD) += eooqd.o eperd.o condmv.o httpget.o ping.o traceroute.o evhttpget.o evping.o evtdig.o evtraceroute.o tcputil.o readresolv.o diff --git a/eperd/condmv.c b/eperd/condmv.c new file mode 100644 index 0000000..78bc9be --- /dev/null +++ b/eperd/condmv.c @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2013 RIPE NCC + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * condmv.c -- move a file only if the destination doesn't exist + */ + +#include "libbb.h" +#include "eperd.h" + +#define SAFE_PREFIX_FROM ATLAS_DATA_NEW +#define SAFE_PREFIX_TO ATLAS_DATA_OUT + +#define A_FLAG (1 << 0) +#define F_FLAG (1 << 1) + +#define DEFAULT_INTERVAL 60 + +struct condmvstate +{ + char *from; + char *to; + char *atlas; + int force; + int interval; +}; + +static void *condmv_init(int argc, char *argv[], + void (*done)(void *state) UNUSED_PARAM) +{ + char *opt_add, *opt_interval, *from, *to, *check; + int interval; + uint32_t opt; + struct condmvstate *state; + + opt_add= NULL; + opt_interval= NULL; + opt_complementary= NULL; /* For when we are called by crond */ + opt= getopt32(argv, "!A:fi:", &opt_add, &opt_interval); + if (opt == (uint32_t)-1) + return NULL; + + if (argc != optind + 2) + { + crondlog(LVL8 "too many or too few arguments (required 2)"); + return NULL; + } + + if (opt_interval) + { + interval= strtoul(opt_interval, &check, 0); + if (interval <= 0) + { + crondlog(LVL8 "unable to parse interval '%s'", + opt_interval); + return NULL; + } + } + else + interval= DEFAULT_INTERVAL; + + from= argv[optind]; + to= argv[optind+1]; + + if (!validate_filename(from, SAFE_PREFIX_FROM)) + { + fprintf(stderr, "insecure from file '%s'\n", from); + return NULL; + } + if (!validate_filename(to, SAFE_PREFIX_TO)) + { + fprintf(stderr, "insecure to file '%s'\n", to); + return NULL; + } + + state= malloc(sizeof(*state)); + state->from= strdup(from); + state->to= strdup(to); + state->atlas= opt_add ? strdup(opt_add) : NULL; + state->force= !!(opt & F_FLAG); + state->interval= interval; + + return state; +} + +static void condmv_start(void *state) +{ + size_t len; + time_t mytime; + char *to; + FILE *file; + struct condmvstate *condmvstate; + struct stat sb; + + condmvstate= state; + + len= strlen(condmvstate->to) + 20; + to= malloc(len); + snprintf(to, len, "%s.%ld", condmvstate->to, + (long)time(NULL)/condmvstate->interval); + + crondlog(LVL7 "condmv_start: destination '%s'\n", to); + + if (stat(to, &sb) == 0 && !condmvstate->force) + { + free(to); + return; + } + + if (condmvstate->atlas) + { + mytime = time(NULL); + /* We have to add something to the existing file before moving + * to. + */ + file= fopen(condmvstate->from, "a"); + if (file == NULL) + { + crondlog(LVL9 "condmv: unable to append to '%s': %s\n", + condmvstate->from, strerror(errno)); + free(to); + return; + } + if (fprintf(file, "%s %lu %s\n", condmvstate->atlas, mytime, + condmvstate->from) < 0) + { + crondlog(LVL9 "condmv: unable to append to '%s': %s\n", + condmvstate->from, strerror(errno)); + fclose(file); + free(to); + return; + } + if (fclose(file) != 0) + { + crondlog(LVL9 "condmv: unable to close '%s': %s\n", + condmvstate->from, strerror(errno)); + free(to); + return; + } + } + if (rename(condmvstate->from, to) != 0) + { + crondlog(LVL9 "condmv: unable to rename '%s' to '%s': %s\n", + condmvstate->from, to, strerror(errno)); + } + free(to); +} + +static int condmv_delete(void *state) +{ + struct condmvstate *condmvstate; + + condmvstate= state; + free(condmvstate->from); + condmvstate->from= NULL; + free(condmvstate->to); + condmvstate->to= NULL; + free(condmvstate->atlas); + condmvstate->atlas= NULL; + + free(condmvstate); + + return 1; +} + +struct testops condmv_ops = { condmv_init, condmv_start, condmv_delete }; + diff --git a/eperd/eooqd.c b/eperd/eooqd.c new file mode 100644 index 0000000..298e63c --- /dev/null +++ b/eperd/eooqd.c @@ -0,0 +1,805 @@ +/* + * Copyright (c) 2013 RIPE NCC + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * eooqd.c Libevent-based One-off queue daemon + */ + +#include +#include + +#include +#include +#include +#include + +#include "eperd.h" + +#define SUFFIX ".curr" +#define OOQD_NEW_PREFIX "/home/atlas/data/new/ooq" +#define OOQD_OUT "/home/atlas/data/ooq.out/ooq" + +#define ATLAS_NARGS 64 /* Max arguments to a built-in command */ +#define ATLAS_ARGSIZE 512 /* Max size of the command line */ + +#define SAFE_PREFIX ATLAS_DATA_NEW + +#define DBQ(str) "\"" #str "\"" + +struct slot +{ + void *cmdstate; +}; + +static struct +{ + char *queue_file; + char *atlas_id; + char curr_qfile[256]; + FILE *curr_file; + int max_busy; + int curr_busy; + int curr_index; + struct slot *slots; +} *state; + +static struct builtin +{ + const char *cmd; + struct testops *testops; +} builtin_cmds[]= +{ + { "evhttpget", &httpget_ops }, + { "evping", &ping_ops }, + { "evtdig", &tdig_ops }, + { "evtraceroute", &traceroute_ops }, + { NULL, NULL } +}; + + +static void process(FILE *file); +static void report(const char *fmt, ...); +static void report_err(const char *fmt, ...); + +static void checkQueue(evutil_socket_t fd, short what, void *arg); +static void add_line(void); +static void cmddone(void *cmdstate); +static void re_post(evutil_socket_t fd, short what, void *arg); +static void post_results(void); +static void skip_space(char *cp, char **ncpp); +static void skip_nonspace(char *cp, char **ncpp); +static void find_eos(char *cp, char **ncpp); + +int eooqd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int eooqd_main(int argc, char *argv[]) +{ + int r; + uint32_t opt; + char *atlas_id, *pid_file_name; + struct event *checkQueueEvent, *rePostEvent; + struct timeval tv; + + atlas_id= NULL; + pid_file_name= NULL; + + opt = getopt32(argv, "A:P:", &atlas_id, &pid_file_name); + + if (argc != optind+1) + { + bb_show_usage(); + return 1; + } + + if(pid_file_name) + { + write_pidfile(pid_file_name); + } + + state = xzalloc(sizeof(*state)); + + state->atlas_id= atlas_id; + state->queue_file= argv[optind]; + + state->max_busy= 10; + + state->slots= xzalloc(sizeof(*state->slots) * state->max_busy); + + if (strlen(state->queue_file) + strlen(SUFFIX) + 1 > + sizeof(state->curr_qfile)) + { + report("filename too long ('%s')", state->queue_file); + return 1; + } + + strlcpy(state->curr_qfile, state->queue_file, + sizeof(state->curr_qfile)); + strlcat(state->curr_qfile, SUFFIX, sizeof(state->curr_qfile)); + + /* Create libevent event base */ + EventBase= event_base_new(); + if (!EventBase) + { + crondlog(DIE9 "event_base_new failed"); /* exits */ + } + DnsBase= evdns_base_new(EventBase, 1 /*initialize*/); + if (!DnsBase) + { + crondlog(DIE9 "evdns_base_new failed"); /* exits */ + } + + DnsBase = evdns_base_new(EventBase, 1); + if(!DnsBase) { + event_base_free(EventBase); + crondlog(DIE9 "evdns_base_new failed"); /* exits */ + } + + checkQueueEvent= event_new(EventBase, -1, EV_TIMEOUT|EV_PERSIST, + checkQueue, NULL); + if (!checkQueueEvent) + crondlog(DIE9 "event_new failed"); /* exits */ + tv.tv_sec= 1; + tv.tv_usec= 0; + event_add(checkQueueEvent, &tv); + + rePostEvent= event_new(EventBase, -1, EV_TIMEOUT|EV_PERSIST, + re_post, NULL); + if (!rePostEvent) + crondlog(DIE9 "event_new failed"); /* exits */ + tv.tv_sec= 60; + tv.tv_usec= 0; + event_add(rePostEvent, &tv); +#if 0 + for(;;) + { + /* Try to move queue_file to curr_qfile. This provide at most + * once behavior and allows producers to create a new + * queue_file while we process the old one. + */ + if (rename(queue_file, curr_qfile) == -1) + { + if (errno == ENOENT) + { + sleep(WAIT_TIME); + continue; + } + report_err("rename failed"); + return 1; + } + + file= fopen(curr_qfile, "r"); + if (file == NULL) + { + report_err("open '%s' failed", curr_qfile); + continue; + } + + process(file); + + fclose(file); + + /* No need to delete curr_qfile */ + } +#endif + r= event_base_loop(EventBase, 0); + if (r != 0) + crondlog(LVL9 "event_base_loop failed"); + return 0; +} + +static void checkQueue(evutil_socket_t fd UNUSED_PARAM, + short what UNUSED_PARAM, void *arg UNUSED_PARAM) +{ + if (!state->curr_file) + { + /* Try to move queue_file to curr_qfile. This provide at most + * once behavior and allows producers to create a new + * queue_file while we process the old one. + */ + if (rename(state->queue_file, state->curr_qfile) == -1) + { + if (errno == ENOENT) + { + return; + } + report_err("rename failed"); + return; + } + + state->curr_file= fopen(state->curr_qfile, "r"); + if (state->curr_file == NULL) + { + report_err("open '%s' failed", state->curr_qfile); + return; + } + } + + while (state->curr_file && state->curr_busy < state->max_busy) + { + add_line(); + } +} + +static void add_line(void) +{ + char c; + int i, argc, skip, slot; + size_t len; + char *cp, *ncp; + struct builtin *bp; + char *p; + const char *reason; + void *cmdstate; + FILE *fn; + const char *argv[ATLAS_NARGS]; + char args[ATLAS_ARGSIZE]; + char cmdline[256]; + char filename[80]; + struct stat sb; + + if (fgets(cmdline, sizeof(cmdline), state->curr_file) == NULL) + { + if (ferror(state->curr_file)) + report_err("error reading queue file"); + fclose(state->curr_file); + state->curr_file= NULL; + return; + } + + cp= strchr(cmdline, '\n'); + if (cp) + *cp= '\0'; + + crondlog(LVL7 "atlas_run: looking for '%s'", cmdline); + + cmdstate= NULL; + reason= NULL; + for (bp= builtin_cmds; bp->cmd != NULL; bp++) + { + len= strlen(bp->cmd); + if (strncmp(cmdline, bp->cmd, len) != 0) + continue; + if (cmdline[len] != ' ') + continue; + break; + } + if (bp->cmd == NULL) + { + reason="command not found"; + goto error; + } + + crondlog(LVL7 "found cmd '%s' for '%s'", bp->cmd, cmdline); + + len= strlen(cmdline); + if (len+1 > ATLAS_ARGSIZE) + { + crondlog(LVL8 "atlas_run: command line too big: '%s'", cmdline); + reason="command line too big"; + goto error; + } + strcpy(args, cmdline); + + /* Split the command line */ + cp= args; + argc= 0; + argv[argc]= cp; + skip_nonspace(cp, &ncp); + cp= ncp; + + for(;;) + { + /* End of list */ + if (cp[0] == '\0') + { + argc++; + break; + } + + /* Find start of next argument */ + skip_space(cp, &ncp); + + /* Terminate current one */ + cp[0]= '\0'; + argc++; + + if (argc >= ATLAS_NARGS-1) + { + crondlog( + LVL8 "atlas_run: command line '%s', too many arguments", + cmdline); + reason="too many arguments"; + goto error; + } + + cp= ncp; + argv[argc]= cp; + if (cp[0] == '"') + { + /* Special code for strings */ + find_eos(cp+1, &ncp); + if (ncp[0] != '"') + { + crondlog( + LVL8 "atlas_run: command line '%s', end of string not found", + cmdline); + reason="end of string not found"; + goto error; + } + argv[argc]= cp+1; + cp= ncp; + cp[0]= '\0'; + cp++; + } + else + { + skip_nonspace(cp, &ncp); + cp= ncp; + } + } + + if (argc >= ATLAS_NARGS-2) + { + crondlog( + LVL8 "atlas_run: command line '%s', too many arguments", + cmdline); + reason="too many arguments"; + goto error; + } + + /* find a slot for this command */ + for (skip= 1; skip <= state->max_busy; skip++) + { + slot= (state->curr_index+skip) % state->max_busy; + if (state->slots[slot].cmdstate == NULL) + break; + } + if (state->slots[slot].cmdstate != NULL) + crondlog(DIE9 "no empty slot?"); + argv[argc++]= "-O"; + snprintf(filename, sizeof(filename), OOQD_NEW_PREFIX ".%d", slot); + argv[argc++]= filename; + + argv[argc]= NULL; + + for (i= 0; itestops->init(argc, argv, cmddone); + crondlog(LVL7 "init returned %p for '%s'", cmdstate, cmdline); + + if (cmdstate != NULL) + { + state->slots[slot].cmdstate= cmdstate; + state->curr_index= slot; + state->curr_busy++; + + bp->testops->start(cmdstate); + } + +error: + if (cmdstate == NULL) + { + fn= fopen(OOQD_NEW_PREFIX, "a"); + if (!fn) + { + crondlog(DIE9 "unable to append to '%s'", + OOQD_NEW_PREFIX); + } + fprintf(fn, "RESULT { "); + if (state->atlas_id) + fprintf(fn, DBQ(id) ":" DBQ(%s) ", ", state->atlas_id); + fprintf(fn, DBQ(fw) ":" DBQ(%d) ", " DBQ(time) ":%ld, ", + get_atlas_fw_version(), (long)time(NULL)); + if (reason) + fprintf(fn, DBQ(reason) ":" DBQ(%s) ", ", reason); + fprintf(fn, DBQ(cmd) ": \""); + for (p= cmdline; *p; p++) + { + c= *p; + if (c == '"' || c == '\\') + fprintf(fn, "\\%c", c); + else if (isprint((unsigned char)c)) + fputc(c, fn); + else + fprintf(fn, "\\u%04x", (unsigned char)c); + } + fprintf(fn, "\""); + fprintf(fn, " }\n"); + fclose(fn); + + if (stat(OOQD_OUT, &sb) == -1 && + stat(OOQD_NEW_PREFIX, &sb) == 0) + { + if (rename(OOQD_NEW_PREFIX, OOQD_OUT) == -1) + { + report_err("move '%s' to '%s' failed", + OOQD_NEW_PREFIX, OOQD_OUT); + } + } + post_results(); + } +} + +static void cmddone(void *cmdstate) +{ + int i; + char from_filename[80]; + char to_filename[80]; + struct stat sb; + + report("command is done for cmdstate %p", cmdstate); + + /* Find command */ + for (i= 0; imax_busy; i++) + { + if (state->slots[i].cmdstate == cmdstate) + break; + } + if (i >= state->max_busy) + { + report("cmddone: state state %p", cmdstate); + return; + } + state->slots[i].cmdstate= NULL; + state->curr_busy--; + + snprintf(from_filename, sizeof(from_filename), + "/home/atlas/data/new/ooq.%d", i); + snprintf(to_filename, sizeof(to_filename), + "/home/atlas/data/ooq.out/%d", i); + if (stat(to_filename, &sb) == 0) + { + report("output file '%s' is busy", to_filename); + + /* continue, we may have to post */ + } + else if (rename(from_filename, to_filename) == -1) + { + report_err("move '%s' to '%s' failed", + from_filename, to_filename); + } + + if (state->curr_busy == 0) + { + post_results(); + } +} + +static void re_post(evutil_socket_t fd UNUSED_PARAM, short what UNUSED_PARAM, + void *arg UNUSED_PARAM) +{ + /* Just call post_results every once in awhile in case some results + * were left behind. + */ + post_results(); +} + +static void post_results(void) +{ + int i, j, r, need_post; + const char *argv[20]; + char from_filename[80]; + char to_filename[80]; + struct stat sb; + + for (j= 0; j<5; j++) + { + /* Grab results and see if something need to be done. */ + need_post= 0; + + if (stat(OOQD_OUT, &sb) == 0) + { + /* There is more to post */ + need_post= 1; + } else if (stat(OOQD_NEW_PREFIX, &sb) == 0) + { + if (rename(OOQD_NEW_PREFIX, OOQD_OUT) == 0) + need_post= 1; + else + { + report_err("move '%s' to '%s' failed", + OOQD_NEW_PREFIX, OOQD_OUT); + } + } + for (i= 0; imax_busy; i++) + { + snprintf(from_filename, sizeof(from_filename), + "/home/atlas/data/new/ooq.%d", i); + snprintf(to_filename, sizeof(to_filename), + "/home/atlas/data/ooq.out/%d", i); + if (stat(to_filename, &sb) == 0) + { + /* There is more to post */ + need_post= 1; + continue; + } + if (stat(from_filename, &sb) == -1) + { + /* Nothing to do */ + continue; + } + + need_post= 1; + if (rename(from_filename, to_filename) == -1) + { + report_err("move '%s' to '%s' failed", + from_filename, to_filename); + } + } + + if (!need_post) + break; + + i= 0; + argv[i++]= "httppost"; + argv[i++]= "-A"; + argv[i++]= "9015"; + argv[i++]= "--delete-file"; + argv[i++]= "--post-header"; + argv[i++]= "/home/atlas/status/p_to_c_report_header"; + argv[i++]= "--post-dir"; + argv[i++]= "/home/atlas/data/ooq.out"; + argv[i++]= "--post-footer"; + argv[i++]= "/home/atlas/status/con_session_id.txt"; + argv[i++]= "-O"; + argv[i++]= "/home/atlas/data/new/ooq_sent.vol"; + argv[i++]= "http://127.0.0.1:8080/"; + argv[i]= NULL; + r= httppost_main(i, argv); + if (r != 0) + { + report("httppost failed with %d", r); + return; + } + + } +} + +static void skip_space(char *cp, char **ncpp) +{ + while (cp[0] != '\0' && isspace(*(unsigned char *)cp)) + cp++; + *ncpp= cp; +} + +static void skip_nonspace(char *cp, char **ncpp) +{ + while (cp[0] != '\0' && !isspace(*(unsigned char *)cp)) + cp++; + *ncpp= cp; +} + +static void find_eos(char *cp, char **ncpp) +{ + while (cp[0] != '\0' && cp[0] != '"') + cp++; + *ncpp= cp; +} + +#if 0 +static void process(FILE *file) +{ + int i, argc, do_append, saved_fd, out_fd, flags; + size_t len; + char *cp, *ncp, *outfile; + struct builtin *bp; + char line[256]; + char *argv[NARGS]; + +printf("in process\n"); + while (cp= fgets(line, sizeof(line), file), cp != NULL) + { +printf("got cp %p, line %p, '%s'\n", cp, line, cp); + if (strchr(line, '\n') == NULL) + { + report("line '%s' too long", line); + return; + } + + /* Skip leading white space */ + cp= line; + while (cp[0] != '\0' && isspace((unsigned char)cp[0])) + cp++; + + if (cp[0] == '\0' || cp[0] == '#') + continue; /* Empty or comment line */ + + for (bp= builtin_cmds; bp->cmd != NULL; bp++) + { + len= strlen(bp->cmd); + if (strncmp(cp, bp->cmd, len) != 0) + continue; + if (cp[len] != ' ') + continue; + break; + } + if (bp->cmd == NULL) + { + report("nothing found for '%s'", cp); + return; /* Nothing found */ + } + + /* Remove trailing white space */ + len= strlen(cp); + while (len > 0 && isspace((unsigned char)cp[len-1])) + { + cp[len-1]= '\0'; + len--; + } + + outfile= NULL; + do_append= 0; + + /* Split the command line */ + argc= 0; + argv[argc]= cp; + skip_nonspace(cp, &ncp); + cp= ncp; + + for(;;) + { + /* End of list */ + if (cp[0] == '\0') + { + argc++; + break; + } + + /* Find start of next argument */ + skip_space(cp, &ncp); + + /* Terminate current one */ + cp[0]= '\0'; + + /* Special case for '>' */ + if (argv[argc][0] == '>') + { + cp= argv[argc]+1; + if (cp[0] == '>') + { + /* Append */ + do_append= 1; + cp++; + } + if (cp[0] != '\0') + { + /* Filename immediately follows '>' */ + outfile= cp; + + /* And move on with the next option */ + } + else + { + /* Get the next argument */ + outfile= ncp; + cp= ncp; + skip_nonspace(cp, &ncp); + cp= ncp; + + if (cp[0] == '\0') + break; + + /* Find start of next argument */ + skip_space(cp, &ncp); + *cp= '\0'; + + if (ncp[0] == '\0') + break; /* No more arguments */ + } + } + else + { + argc++; + } + + if (argc >= NARGS-1) + { + report("command line '%s', too arguments", + line); + continue; /* Just skip it */ + } + + cp= ncp; + argv[argc]= cp; + if (cp[0] == '"') + { + /* Special code for strings */ + find_eos(cp+1, &ncp); + if (ncp[0] != '"') + { + report( + "command line '%s', end of string not found", + line); + continue; /* Just skip it */ + } + argv[argc]= cp+1; + cp= ncp; + cp[0]= '\0'; + cp++; + } + else + { + skip_nonspace(cp, &ncp); + cp= ncp; + } + } + + if (argc >= NARGS) + { + report("command line '%s', too many arguments", line); + return; + } + argv[argc]= NULL; + + for (i= 0; ifunc(argc, argv); + + if (outfile) + { + fflush(stdout); + dup2(saved_fd, 1); + close(saved_fd); + } + } +} +#endif + +static void report(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + fprintf(stderr, "ooqd: "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + + va_end(ap); +} + +static void report_err(const char *fmt, ...) +{ + int terrno; + va_list ap; + + terrno= errno; + + va_start(ap, fmt); + fprintf(stderr, "ooqd: "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, ": %s\n", strerror(terrno)); + + va_end(ap); +} diff --git a/eperd/eperd.c b/eperd/eperd.c new file mode 100644 index 0000000..48a1ef0 --- /dev/null +++ b/eperd/eperd.c @@ -0,0 +1,1163 @@ +/* vi: set sw=4 ts=4: + * eperd formerly crond but now heavily hacked for Atlas + * + * crond -d[#] -c -f -b + * + * run as root, but NOT setuid root + * + * Copyright(c) 2013 RIPE NCC + * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com) + * (version 2.3.2) + * Vladimir Oleynik (C) 2002 + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "libbb.h" +#include +#include +#include +#include + +#include "eperd.h" + +/* glibc frees previous setenv'ed value when we do next setenv() + * of the same variable. uclibc does not do this! */ +#if (defined(__GLIBC__) && !defined(__UCLIBC__)) /* || OTHER_SAFE_LIBC... */ +#define SETENV_LEAKS 0 +#else +#define SETENV_LEAKS 1 +#endif + +#define DBQ(str) "\"" #str "\"" + +#ifndef CRONTABS +#define CRONTABS "/var/spool/cron/crontabs" +#endif +#ifndef TMPDIR +#define TMPDIR "/var/spool/cron" +#endif +#ifndef SENDMAIL +#define SENDMAIL "sendmail" +#endif +#ifndef SENDMAIL_ARGS +#define SENDMAIL_ARGS "-ti", "oem" +#endif +#ifndef CRONUPDATE +#define CRONUPDATE "cron.update" +#endif +#ifndef MAXLINES +#define MAXLINES 256 /* max lines in non-root crontabs */ +#endif + +#define URANDOM_DEV "/dev/urandom" +#define ATLAS_FW_VERSION "/home/atlas/state/FIRMWARE_APPS_VERSION" + +struct CronLine { + struct CronLine *cl_Next; + char *cl_Shell; /* shell command */ + pid_t cl_Pid; /* running pid, 0, or armed (-1) */ +#if ENABLE_FEATURE_CROND_CALL_SENDMAIL + int cl_MailPos; /* 'empty file' size */ + smallint cl_MailFlag; /* running pid is for mail */ + char *cl_MailTo; /* whom to mail results */ +#endif + unsigned interval; + time_t nextcycle; + time_t start_time; + time_t end_time; + enum distribution { DISTR_NONE, DISTR_UNIFORM } distribution; + int distr_param; /* Parameter for distribution, if any */ + int distr_offset; /* Current offset to randomize the interval */ + struct event event; + struct testops *testops; + void *teststate; + + /* For cleanup */ + char needs_delete; + + /* For debugging */ + time_t lasttime; +}; + + +#define DaemonUid 0 + + +enum { + OPT_l = (1 << 0), + OPT_L = (1 << 1), + OPT_f = (1 << 2), + OPT_b = (1 << 3), + OPT_S = (1 << 4), + OPT_c = (1 << 5), + OPT_A = (1 << 6), + OPT_D = (1 << 7), + OPT_d = (1 << 8) * ENABLE_FEATURE_CROND_D, +}; +#if ENABLE_FEATURE_CROND_D +#define DebugOpt (option_mask32 & OPT_d) +#else +#define DebugOpt 0 +#endif + + +struct globals G; +#define INIT_G() do { \ + LogLevel = 8; \ + CDir = CRONTABS; \ +} while (0) + +static int do_kick_watchdog; +static char *out_filename= NULL; +static char *atlas_id= NULL; + +static void CheckUpdates(evutil_socket_t fd, short what, void *arg); +static void CheckUpdatesHour(evutil_socket_t fd, short what, void *arg); +static void SynchronizeDir(void); +#if ENABLE_FEATURE_CROND_CALL_SENDMAIL +static void EndJob(const char *user, CronLine *line); +#else +#define EndJob(user, line) ((line)->cl_Pid = 0) +#endif +static void DeleteFile(void); +static int Insert(CronLine *line); +static void Start(CronLine *line); +static void atlas_init(CronLine *line); +static void RunJob(evutil_socket_t fd, short what, void *arg); + +void crondlog(const char *ctl, ...) +{ + va_list va; + int level = (ctl[0] & 0x1f); + + va_start(va, ctl); + if (level >= (int)LogLevel) { + /* Debug mode: all to (non-redirected) stderr, */ + /* Syslog mode: all to syslog (logmode = LOGMODE_SYSLOG), */ + if (!DebugOpt && LogFile) { + /* Otherwise (log to file): we reopen log file at every write: */ + int logfd = open3_or_warn(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600); + if (logfd >= 0) + xmove_fd(logfd, STDERR_FILENO); + } +// TODO: ERR -> error, WARN -> warning, LVL -> info + bb_verror_msg(ctl + 1, va, /* strerr: */ NULL); + } + va_end(va); + if (ctl[0] & 0x80) + exit(20); +} + +int get_atlas_fw_version(void) +{ + static int fw_version= -1; + + int r, fw; + FILE *file; + + if (fw_version != -1) + return fw_version; + + file= fopen(ATLAS_FW_VERSION, "r"); + if (file == NULL) + { + crondlog(LVL9 "get_atlas_fw_version: unable to open '%s': %s", + ATLAS_FW_VERSION, strerror(errno)); + return -1; + } + r= fscanf(file, "%d", &fw); + fclose(file); + if (r == -1) + { + crondlog(LVL9 "get_atlas_fw_version: unable to read from '%s'", + ATLAS_FW_VERSION); + return -1; + } + + fw_version= fw; + return fw; +} + +static void my_exit(void) +{ + crondlog(LVL8 "in my_exit (exit was called!)"); + abort(); +} + +static void kick_watchdog(void) +{ + if(do_kick_watchdog) + { + int fdwatchdog = open("/dev/watchdog", O_RDWR); + write(fdwatchdog, "1", 1); + close(fdwatchdog); + } +} + +#if 0 +static void FAST_FUNC Xbb_daemonize_or_rexec(int flags, char **argv) +{ + int fd; + + if (flags & DAEMON_CHDIR_ROOT) + xchdir("/"); + + if (flags & DAEMON_DEVNULL_STDIO) { + close(0); + close(1); + close(2); + } + + fd = open(bb_dev_null, O_RDWR); + if (fd < 0) { + /* NB: we can be called as bb_sanitize_stdio() from init + * or mdev, and there /dev/null may legitimately not (yet) exist! + * Do not use xopen above, but obtain _ANY_ open descriptor, + * even bogus one as below. */ + fd = xopen("/", O_RDONLY); /* don't believe this can fail */ + } + + while ((unsigned)fd < 2) + fd = dup(fd); /* have 0,1,2 open at least to /dev/null */ + + if (!(flags & DAEMON_ONLY_SANITIZE)) { + //forkexit_or_rexec(argv); + /* if daemonizing, make sure we detach from stdio & ctty */ + setsid(); + dup2(fd, 0); + dup2(fd, 1); + dup2(fd, 2); + } + while (fd > 2) { + close(fd--); + if (!(flags & DAEMON_CLOSE_EXTRA_FDS)) + return; + /* else close everything after fd#2 */ + } +} +#endif + +int eperd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int eperd_main(int argc UNUSED_PARAM, char **argv) +{ + unsigned opt; + int r, fd; + unsigned seed; + struct event *updateEventMin, *updateEventHour; + struct timeval tv; + + const char *PidFileName = NULL; + + atexit(my_exit); + + INIT_G(); + + /* "-b after -f is ignored", and so on for every pair a-b */ + opt_complementary = "f-b:b-f:S-L:L-S" USE_FEATURE_PERD_D(":d-l") + ":l+:d+"; /* -l and -d have numeric param */ + opt = getopt32(argv, "l:L:fbSc:A:DP:" USE_FEATURE_PERD_D("d:") "O:", + &LogLevel, &LogFile, &CDir, &atlas_id, &PidFileName + USE_FEATURE_PERD_D(,&LogLevel), &out_filename); + /* both -d N and -l N set the same variable: LogLevel */ + + if (!(opt & OPT_f)) { + /* close stdin, stdout, stderr. + * close unused descriptors - don't need them. */ + bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, argv); + } + + if (!DebugOpt && LogFile == NULL) { + /* logging to syslog */ + openlog(applet_name, LOG_CONS | LOG_PID, LOG_LOCAL6); + logmode = LOGMODE_SYSLOG; + } + + do_kick_watchdog= !!(opt & OPT_D); + + xchdir(CDir); + //signal(SIGHUP, SIG_IGN); /* ? original crond dies on HUP... */ + xsetenv("SHELL", DEFAULT_SHELL); /* once, for all future children */ + crondlog(LVL9 "crond (busybox "BB_VER") started, log level %d", LogLevel); + + /* Create libevent event base */ + EventBase= event_base_new(); + if (!EventBase) + { + crondlog(DIE9 "event_base_new failed"); /* exits */ + } + DnsBase= evdns_base_new(EventBase, 1 /*initialize*/); + if (!DnsBase) + { + crondlog(DIE9 "evdns_base_new failed"); /* exits */ + } + + fd= open(URANDOM_DEV, O_RDONLY); + + /* Best effort, just ignore errors */ + if (fd != -1) + { + read(fd, &seed, sizeof(seed)); + close(fd); + } + crondlog(LVL7 "using seed '%u'", seed); + srandom(seed); + + SynchronizeDir(); + + updateEventMin= event_new(EventBase, -1, EV_TIMEOUT|EV_PERSIST, + CheckUpdates, NULL); + if (!updateEventMin) + crondlog(DIE9 "event_new failed"); /* exits */ + tv.tv_sec= 60; + tv.tv_usec= 0; + event_add(updateEventMin, &tv); + + updateEventHour= event_new(EventBase, -1, EV_TIMEOUT|EV_PERSIST, + CheckUpdatesHour, NULL); + if (!updateEventHour) + crondlog(DIE9 "event_new failed"); /* exits */ + tv.tv_sec= 3600; + tv.tv_usec= 0; + event_add(updateEventHour, &tv); + + if(PidFileName) + { + write_pidfile(PidFileName); + } + else + { + write_pidfile("/var/run/crond.pid"); + } +#if 0 + /* main loop - synchronize to 1 second after the minute, minimum sleep + * of 1 second. */ + { + time_t t1 = time(NULL); + time_t next; + time_t last_minutely= 0; + time_t last_hourly= 0; + int sleep_time = 10; /* AA previously 60 */ + for (;;) { + kick_watchdog(); + sleep(sleep_time); + + kick_watchdog(); + + if (t1 >= last_minutely + 60) + { + last_minutely= t1; + CheckUpdates(); + } + if (t1 >= last_hourly + 3600) + { + last_hourly= t1; + SynchronizeDir(); + } + { + sleep_time= 60; + if (do_kick_watchdog) + sleep_time= 10; + TestJobs(&next); + crondlog(LVL7 "got next %d, now %d", + next, time(NULL)); + if (!next) + { + crondlog(LVL7 "calling RunJobs at %d", + time(NULL)); + RunJobs(); + crondlog(LVL7 "RunJobs ended at %d", + time(NULL)); + sleep_time= 1; + } else if (next > t1 && next < t1+sleep_time) + sleep_time= next-t1; + if (CheckJobs() > 0) { + sleep_time = 10; + } + crondlog( + LVL7 "t1 = %d, next = %d, sleep_time = %d", + t1, next, sleep_time); + } + t1= time(NULL); + } + } +#endif + r= event_base_loop(EventBase, 0); + if (r != 0) + crondlog(LVL9 "event_base_loop failed"); + return 0; /* not reached */ +} + +#if SETENV_LEAKS +/* We set environment *before* vfork (because we want to use vfork), + * so we cannot use setenv() - repeated calls to setenv() may leak memory! + * Using putenv(), and freeing memory after unsetenv() won't leak */ +static void safe_setenv4(char **pvar_val, const char *var, const char *val /*, int len*/) +{ + const int len = 4; /* both var names are 4 char long */ + char *var_val = *pvar_val; + + if (var_val) { + var_val[len] = '\0'; /* nuke '=' */ + unsetenv(var_val); + free(var_val); + } + *pvar_val = xasprintf("%s=%s", var, val); + putenv(*pvar_val); +} +#endif + +static void do_distr(CronLine *line) +{ + long n, r, modulus, max; + + line->distr_offset= 0; /* Safe default */ + if (line->distribution == DISTR_UNIFORM) + { + /* Generate a random number in the range [0..distr_param] */ + modulus= line->distr_param+1; + n= LONG_MAX/modulus; + max= n*modulus; + do + { + r= random(); + } while (r >= max); + r %= modulus; + line->distr_offset= r - line->distr_param/2; + } + crondlog(LVL7 "do_distr: using %d", line->distr_offset); +} + +static void SynchronizeFile(const char *fileName) +{ + struct parser_t *parser; + struct stat sbuf; + int r, maxLines; + char *tokens[6]; +#if ENABLE_FEATURE_CROND_CALL_SENDMAIL + char *mailTo = NULL; +#endif + char *check0, *check1, *check2; + CronLine *line; + + if (!fileName) + return; + + for (line= LineBase; line; line= line->cl_Next) + line->needs_delete= 1; + + parser = config_open(fileName); + if (!parser) + { + /* We have to get rid of the old entries if the file is not + * there. Assume a non-existant file is the only reason for + * failure. + */ + DeleteFile(); + return; + } + + maxLines = (strcmp(fileName, "root") == 0) ? 65535 : MAXLINES; + + if (fstat(fileno(parser->fp), &sbuf) == 0 /* && sbuf.st_uid == DaemonUid */) { + int n; + + while (1) { + if (!--maxLines) + break; + n = config_read(parser, tokens, 6, 1, "# \t", PARSE_NORMAL | PARSE_KEEP_COPY); + if (!n) + break; + + if (DebugOpt) + crondlog(LVL5 "user:%s entry:%s", fileName, parser->data); + + /* check if line is setting MAILTO= */ + if (0 == strncmp(tokens[0], "MAILTO=", 7)) { +#if ENABLE_FEATURE_CROND_CALL_SENDMAIL + free(mailTo); + mailTo = (tokens[0][7]) ? xstrdup(&tokens[0][7]) : NULL; +#endif /* otherwise just ignore such lines */ + continue; + } + /* check if a minimum of tokens is specified */ + if (n < 6) + continue; + line = xzalloc(sizeof(*line)); + line->interval= strtoul(tokens[0], &check0, 10); + line->start_time= strtoul(tokens[1], &check1, 10); + line->end_time= strtoul(tokens[2], &check2, 10); + + if (line->interval <= 0 || check0[0] != '\0' || + check1[0] != '\0' || + check2[0] != '\0') + { + crondlog(LVL9 "bad crontab line"); + free(line); + continue; + } + + if (strcmp(tokens[3], "NONE") == 0) + { + line->distribution= DISTR_NONE; + } + else if (strcmp(tokens[3], "UNIFORM") == 0) + { + line->distribution= DISTR_UNIFORM; + line->distr_param= + strtoul(tokens[4], &check0, 10); + if (check0[0] != '\0') + { + crondlog(LVL9 "bad crontab line"); + free(line); + continue; + } + if (line->distr_param == 0 || + LONG_MAX/line->distr_param == 0) + { + line->distribution= DISTR_NONE; + } + } + + line->lasttime= 0; +#if ENABLE_FEATURE_CROND_CALL_SENDMAIL + /* copy mailto (can be NULL) */ + line->cl_MailTo = xstrdup(mailTo); +#endif + /* copy command */ + line->cl_Shell = xstrdup(tokens[5]); + if (DebugOpt) { + crondlog(LVL5 " command:%s", tokens[5]); + } +//bb_error_msg("M[%s]F[%s][%s][%s][%s][%s][%s]", mailTo, tokens[0], tokens[1], tokens[2], tokens[3], tokens[4], tokens[5]); + + evtimer_assign(&line->event, EventBase, RunJob, line); + + r= Insert(line); + if (!r) + { + /* Existing line. Delete new one */ +#if ENABLE_FEATURE_CROND_CALL_SENDMAIL + free(line->cl_MailTo); +#endif + free(line->cl_Shell); + free(line); + continue; + } + + /* New line, should schedule start event */ + Start(line); + + kick_watchdog(); + } + + if (maxLines == 0) { + crondlog(WARN9 "user %s: too many lines", fileName); + } + } + config_close(parser); + + DeleteFile(); +} + +static void CheckUpdates(evutil_socket_t __attribute__ ((unused)) fd, + short __attribute__ ((unused)) what, + void __attribute__ ((unused)) *arg) +{ + FILE *fi; + char buf[256]; + + fi = fopen_for_read(CRONUPDATE); + if (fi != NULL) { + unlink(CRONUPDATE); + while (fgets(buf, sizeof(buf), fi) != NULL) { + /* use first word only */ + SynchronizeFile(strtok(buf, " \t\r\n")); + } + fclose(fi); + } +} + +static void CheckUpdatesHour(evutil_socket_t __attribute__ ((unused)) fd, + short __attribute__ ((unused)) what, + void __attribute__ ((unused)) *arg) +{ + SynchronizeDir(); +} + +static void SynchronizeDir(void) +{ + /* + * Remove cron update file + * + * Re-chdir, in case directory was renamed & deleted, or otherwise + * screwed up. + * + * Only load th crontab for 'root' + */ + unlink(CRONUPDATE); + if (chdir(CDir) < 0) { + crondlog(DIE9 "can't chdir(%s)", CDir); + } + + SynchronizeFile("root"); + DeleteFile(); +} + +/* + * Insert - insert if not already there + */ +static int Insert(CronLine *line) +{ + CronLine *last; + struct timeval tv; + time_t now; + + if (oldLine) + { + /* Try to match line expected to be next */ + if (oldLine->interval == line->interval && + oldLine->start_time == line->start_time && + strcmp(oldLine->cl_Shell, line->cl_Shell) == 0) + { + crondlog(LVL9 "next line matches"); + ; /* okay */ + } + else + oldLine= NULL; + } + + if (!oldLine) + { + /* Try to find one */ + for (last= NULL, oldLine= LineBase; oldLine; + last= oldLine, oldLine= oldLine->cl_Next) + { + if (oldLine->interval == line->interval && + oldLine->start_time == line->start_time && + strcmp(oldLine->cl_Shell, line->cl_Shell) == 0) + { + break; + } + } + } + + if (oldLine) + { + crondlog(LVL7 "Insert: found match for line '%s'", + line->cl_Shell); + crondlog(LVL7 "Insert: setting end time to %d", line->end_time); + oldLine->end_time= line->end_time; + oldLine->needs_delete= 0; + + /* Reschedule event */ + now= time(NULL); + tv.tv_sec= oldLine->nextcycle*oldLine->interval + + oldLine->start_time + + oldLine->distr_offset - now; + if (tv.tv_sec < 0) + tv.tv_sec= 0; + tv.tv_usec= 0; + crondlog(LVL7 "Insert: nextcycle %d, interval %d, start_time %d, distr_offset %d, now %d, tv_sec %d", + oldLine->nextcycle, oldLine->interval, + oldLine->start_time, oldLine->distr_offset, now, + tv.tv_sec); + event_add(&oldLine->event, &tv); + + return 0; + } + + crondlog(LVL7 "found no match for line '%s'", line->cl_Shell); + line->cl_Next= NULL; + if (last) + last->cl_Next= line; + else + LineBase= line; + return 1; +} + +static void Start(CronLine *line) +{ + time_t now; + struct timeval tv; + + line->testops= NULL; + + /* Parse command line and init test */ + atlas_init(line); + if (!line->testops) + return; /* Test failed to initialize */ + + now= time(NULL); + if (now > line->end_time) + return; /* This job has expired */ + + if (now < line->start_time) + line->nextcycle= 0; + else + line->nextcycle= (now-line->start_time)/line->interval + 1; + do_distr(line); + + tv.tv_sec= line->nextcycle*line->interval + line->start_time + + line->distr_offset - now; + if (tv.tv_sec < 0) + tv.tv_sec= 0; + tv.tv_usec= 0; + crondlog(LVL7 "Start: nextcycle %d, interval %d, start_time %d, distr_offset %d, now %d, tv_sec %d", + line->nextcycle, line->interval, + line->start_time, line->distr_offset, now, + tv.tv_sec); + event_add(&line->event, &tv); +} + +/* + * DeleteFile() - delete user database + * + * Note: multiple entries for same user may exist if we were unable to + * completely delete a database due to running processes. + */ +static void DeleteFile(void) +{ + int r; + CronLine **pline = &LineBase; + CronLine *line; + + oldLine= NULL; + + while ((line = *pline) != NULL) { + if (!line->needs_delete) + { + pline= &line->cl_Next; + continue; + } + kick_watchdog(); + if (!line->teststate) + { + crondlog(LVL8 "DeleteFile: no state to delete for '%s'", + line->cl_Shell); + } + if (line->testops && line->teststate) + { + r= line->testops->delete(line->teststate); + if (r != 1) + { + crondlog(LVL9 "DeleteFile: line is busy"); + pline= &line->cl_Next; + continue; + } + line->testops= NULL; + line->teststate= NULL; + } + event_del(&line->event); + free(line->cl_Shell); + line->cl_Shell= NULL; + + *pline= line->cl_Next; + free(line); + } +} + +static void skip_space(char *cp, char **ncpp) +{ + while (cp[0] != '\0' && isspace(*(unsigned char *)cp)) + cp++; + *ncpp= cp; +} + +static void skip_nonspace(char *cp, char **ncpp) +{ + while (cp[0] != '\0' && !isspace(*(unsigned char *)cp)) + cp++; + *ncpp= cp; +} + +static void find_eos(char *cp, char **ncpp) +{ + while (cp[0] != '\0' && cp[0] != '"') + cp++; + *ncpp= cp; +} + +static struct builtin +{ + const char *cmd; + struct testops *testops; +} builtin_cmds[]= +{ + { "evhttpget", &httpget_ops }, + { "evping", &ping_ops }, + { "evtdig", &tdig_ops }, + { "evtraceroute", &traceroute_ops }, + { "condmv", &condmv_ops }, + { NULL, NULL } +}; + + +#define ATLAS_NARGS 64 /* Max arguments to a built-in command */ +#define ATLAS_ARGSIZE 512 /* Max size of the command line */ + +static void atlas_init(CronLine *line) +{ + char c; + int i, argc; + size_t len; + char *cp, *ncp; + struct builtin *bp; + char *cmdline, *p; + const char *reason; + void *state; + FILE *fn; + char *argv[ATLAS_NARGS]; + char args[ATLAS_ARGSIZE]; + + cmdline= line->cl_Shell; + crondlog(LVL7 "atlas_run: looking for %p '%s'", cmdline, cmdline); + + state= NULL; + reason= NULL; + for (bp= builtin_cmds; bp->cmd != NULL; bp++) + { + len= strlen(bp->cmd); + if (strncmp(cmdline, bp->cmd, len) != 0) + continue; + if (cmdline[len] != ' ') + continue; + break; + } + if (bp->cmd == NULL) + { + reason="command not found"; + goto error; + } + + crondlog(LVL7 "found cmd '%s' for '%s'", bp->cmd, cmdline); + + len= strlen(cmdline); + if (len+1 > ATLAS_ARGSIZE) + { + crondlog(LVL8 "atlas_run: command line too big: '%s'", cmdline); + reason="command line too big"; + goto error; + } + strcpy(args, cmdline); + + /* Split the command line */ + cp= args; + argc= 0; + argv[argc]= cp; + skip_nonspace(cp, &ncp); + cp= ncp; + + for(;;) + { + /* End of list */ + if (cp[0] == '\0') + { + argc++; + break; + } + + /* Find start of next argument */ + skip_space(cp, &ncp); + + /* Terminate current one */ + cp[0]= '\0'; + argc++; + + if (argc >= ATLAS_NARGS-1) + { + crondlog( + LVL8 "atlas_run: command line '%s', too many arguments", + cmdline); + reason="too many arguments"; + goto error; + } + + cp= ncp; + argv[argc]= cp; + if (cp[0] == '"') + { + /* Special code for strings */ + find_eos(cp+1, &ncp); + if (ncp[0] != '"') + { + crondlog( + LVL8 "atlas_run: command line '%s', end of string not found", + cmdline); + reason="end of string not found"; + goto error; + } + argv[argc]= cp+1; + cp= ncp; + cp[0]= '\0'; + cp++; + } + else + { + skip_nonspace(cp, &ncp); + cp= ncp; + } + } + + if (argc >= ATLAS_NARGS) + { + crondlog( + LVL8 "atlas_run: command line '%s', too many arguments", + cmdline); + reason="too many arguments"; + goto error; + } + argv[argc]= NULL; + + for (i= 0; itestops->init(argc, argv, 0); + crondlog(LVL7 "init returned %p for '%s'", state, line->cl_Shell); + line->teststate= state; + line->testops= bp->testops; + +error: + if (state == NULL && out_filename) + { + fn= fopen(out_filename, "a"); + if (!fn) + crondlog(DIE9 "unable to append to '%s'", out_filename); + fprintf(fn, "RESULT { "); + if (atlas_id) + fprintf(fn, DBQ(id) ":" DBQ(%s) ", ", atlas_id); + fprintf(fn, DBQ(fw) ":" DBQ(%d) ", " DBQ(time) ":%ld, ", + get_atlas_fw_version(), (long)time(NULL)); + if (reason) + fprintf(fn, DBQ(reason) ":" DBQ(%s) ", ", reason); + fprintf(fn, DBQ(cmd) ": \""); + for (p= line->cl_Shell; *p; p++) + { + c= *p; + if (c == '"' || c == '\\') + fprintf(fn, "\\%c", c); + else if (isprint((unsigned char)c)) + fputc(c, fn); + else + fprintf(fn, "\\u%04x", (unsigned char)c); + } + fprintf(fn, "\""); + fprintf(fn, " }\n"); + fclose(fn); + } +} + +#if ENABLE_FEATURE_CROND_CALL_SENDMAIL + +// TODO: sendmail should be _run-time_ option, not compile-time! + +static void +ForkJob(const char *user, CronLine *line, int mailFd, + const char *prog, const char *cmd, const char *arg, + const char *mail_filename) +{ + struct passwd *pas; + pid_t pid; + + /* prepare things before vfork */ + pas = getpwnam(user); + if (!pas) { + crondlog(LVL9 "can't get uid for %s", user); + goto err; + } + SetEnv(pas); + + pid = vfork(); + if (pid == 0) { + /* CHILD */ + /* change running state to the user in question */ + ChangeUser(pas); + if (DebugOpt) { + crondlog(LVL5 "child running %s", prog); + } + if (mailFd >= 0) { + xmove_fd(mailFd, mail_filename ? 1 : 0); + dup2(1, 2); + } + /* crond 3.0pl1-100 puts tasks in separate process groups */ + bb_setpgrp(); + execlp(prog, prog, cmd, arg, NULL); + crondlog(ERR20 "can't exec, user %s cmd %s %s %s", user, prog, cmd, arg); + if (mail_filename) { + fdprintf(1, "Exec failed: %s -c %s\n", prog, arg); + } + _exit(EXIT_SUCCESS); + } + + line->cl_Pid = pid; + if (pid < 0) { + /* FORK FAILED */ + crondlog(ERR20 "can't vfork"); + err: + line->cl_Pid = 0; + if (mail_filename) { + unlink(mail_filename); + } + } else if (mail_filename) { + /* PARENT, FORK SUCCESS + * rename mail-file based on pid of process + */ + char mailFile2[128]; + + snprintf(mailFile2, sizeof(mailFile2), "%s/cron.%s.%d", TMPDIR, user, pid); + rename(mail_filename, mailFile2); // TODO: xrename? + } + + /* + * Close the mail file descriptor.. we can't just leave it open in + * a structure, closing it later, because we might run out of descriptors + */ + if (mailFd >= 0) { + close(mailFd); + } +} + +static void RunJob(const char *user, CronLine *line) +{ + char mailFile[128]; + int mailFd = -1; + + line->cl_Pid = 0; + line->cl_MailFlag = 0; + + if (line->cl_MailTo) { + /* open mail file - owner root so nobody can screw with it. */ + snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", TMPDIR, user, getpid()); + mailFd = open(mailFile, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL | O_APPEND, 0600); + + if (mailFd >= 0) { + line->cl_MailFlag = 1; + fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", line->cl_MailTo, + line->cl_Shell); + line->cl_MailPos = lseek(mailFd, 0, SEEK_CUR); + } else { + crondlog(ERR20 "cannot create mail file %s for user %s, " + "discarding output", mailFile, user); + } + } + + + if (atlas_outfile && atlas_run(line->cl_Shell)) + { + /* Internal command */ + return; + } + + ForkJob(user, line, mailFd, DEFAULT_SHELL, "-c", line->cl_Shell, mailFile); +} + +/* + * EndJob - called when job terminates and when mail terminates + */ +static void EndJob(const char *user, CronLine *line) +{ + int mailFd; + char mailFile[128]; + struct stat sbuf; + + /* No job */ + if (line->cl_Pid <= 0) { + line->cl_Pid = 0; + return; + } + + /* + * End of job and no mail file + * End of sendmail job + */ + snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", TMPDIR, user, line->cl_Pid); + line->cl_Pid = 0; + + if (line->cl_MailFlag == 0) { + return; + } + line->cl_MailFlag = 0; + + /* + * End of primary job - check for mail file. If size has increased and + * the file is still valid, we sendmail it. + */ + mailFd = open(mailFile, O_RDONLY); + unlink(mailFile); + if (mailFd < 0) { + return; + } + + if (fstat(mailFd, &sbuf) < 0 || sbuf.st_uid != DaemonUid + || sbuf.st_nlink != 0 || sbuf.st_size == line->cl_MailPos + || !S_ISREG(sbuf.st_mode) + ) { + close(mailFd); + return; + } + if (line->cl_MailTo) + ForkJob(user, line, mailFd, SENDMAIL, SENDMAIL_ARGS, NULL); +} + +#else /* crond without sendmail */ + +static void RunJob(evutil_socket_t __attribute__ ((unused)) fd, + short __attribute__ ((unused)) what, void *arg) +{ + time_t now; + CronLine *line; + struct timeval tv; + + line= arg; + + now= time(NULL); + + crondlog(LVL7 "RunJob for %p, '%s'\n", arg, line->cl_Shell); + crondlog(LVL7 "RubJob, now %d, end_time %d\n", now, line->end_time); + + if (now > line->end_time) + { + crondlog(LVL7 "RunJob: expired\n"); + return; /* This job has expired */ + } + + if (line->needs_delete) + { + crondlog(LVL7 "RunJob: needs delete\n"); + return; /* Line is to be deleted */ + } + + if (!line->teststate) + { + crondlog(LVL8 "not starting cmd '%s' (not init)\n", + line->cl_Shell); + return; + } + + // crondlog(LVL8 "starting cmd '%s'\n", line->cl_Shell); + + line->testops->start(line->teststate); + + // crondlog(LVL8 "after cmd '%s'\n", line->cl_Shell); + + line->nextcycle++; + if (line->start_time + line->nextcycle*line->interval < now) + { + crondlog(LVL7 "recomputing nextcycle"); + line->nextcycle= (now-line->start_time)/line->interval + 1; + } + + do_distr(line); + tv.tv_sec= line->nextcycle*line->interval + line->start_time + + line->distr_offset - now; + if (tv.tv_sec < 0) + tv.tv_sec= 0; + tv.tv_usec= 0; + crondlog(LVL7 "RunJob: nextcycle %d, interval %d, start_time %d, distr_offset %d, now %d, tv_sec %d", + line->nextcycle, line->interval, + line->start_time, line->distr_offset, now, + tv.tv_sec); + event_add(&line->event, &tv); +} + +#endif /* ENABLE_FEATURE_CROND_CALL_SENDMAIL */ diff --git a/eperd/eperd.h b/eperd/eperd.h new file mode 100644 index 0000000..ecdab64 --- /dev/null +++ b/eperd/eperd.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2013 RIPE NCC + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * eperd.h + */ + +typedef struct CronLine CronLine; + +struct globals { + unsigned LogLevel; /* = 8; */ + const char *LogFile; + const char *CDir; /* = CRONTABS; */ + CronLine *LineBase; + CronLine *oldLine; + struct event_base *EventBase; + struct evdns_base *DnsBase; +}; +extern struct globals G; +#define LogLevel (G.LogLevel ) +#define LogFile (G.LogFile ) +#define CDir (G.CDir ) +#define LineBase (G.LineBase ) +#define FileBase (G.FileBase ) +#define oldLine (G.oldLine ) +#define EventBase (G.EventBase ) +#define DnsBase (G.DnsBase ) + +#define LVL5 "\x05" +#define LVL7 "\x07" +#define LVL8 "\x08" +#define LVL9 "\x09" +#define WARN9 "\x49" +#define DIE9 "\xc9" +/* level >= 20 is "error" */ +#define ERR20 "\x14" + +struct testops +{ + void *(*init)(int argc, char *argv[], void (*done)(void *teststate)); + void (*start)(void *teststate); + int (*delete)(void *teststate); +}; + +extern struct testops condmv_ops; +extern struct testops httpget_ops; +extern struct testops ping_ops; +extern struct testops tdig_ops; +extern struct testops traceroute_ops; + +void crondlog(const char *ctl, ...); +int get_atlas_fw_version(void); diff --git a/eperd/evhttpget.c b/eperd/evhttpget.c new file mode 100644 index 0000000..91e26ab --- /dev/null +++ b/eperd/evhttpget.c @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2013 RIPE NCC + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * Standalone version of the event-based httpget. + */ + +#include "libbb.h" +#include +#include +#include +#include + +#include "eperd.h" + +static void done(void *state UNUSED_PARAM) +{ + fprintf(stderr, "And we are done\n"); + exit(0); +} + +int evhttpget_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int evhttpget_main(int argc UNUSED_PARAM, char **argv) +{ + int r; + void *state; + + /* Create libevent event base */ + EventBase= event_base_new(); + if (!EventBase) + { + fprintf(stderr, "evhttpget_base_new failed\n"); + exit(1); + } + DnsBase= evdns_base_new(EventBase, 1 /*initialize*/); + if (!DnsBase) + { + fprintf(stderr, "evdns_base_new failed\n"); + exit(1); + } + + state= httpget_ops.init(argc, argv, done); + if (!state) + { + fprintf(stderr, "evhttpget: traceroute_ops.init failed\n"); + exit(1); + } + httpget_ops.start(state); + + r= event_base_loop(EventBase, 0); + if (r != 0) + { + fprintf(stderr, "evhttpget: event_base_loop failed\n"); + exit(1); + } + return 0; /* not reached */ +} + diff --git a/eperd/evping.c b/eperd/evping.c new file mode 100644 index 0000000..a11d51d --- /dev/null +++ b/eperd/evping.c @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2013 RIPE NCC + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * Standalone version of the event-based ping. + */ + +#include "libbb.h" +#include +#include +#include +#include + +#include "eperd.h" + +static void done(void *state UNUSED_PARAM) +{ + fprintf(stderr, "And we are done\n"); + exit(0); +} + +int evping_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int evping_main(int argc UNUSED_PARAM, char **argv) +{ + int r; + void *state; + + /* Create libevent event base */ + EventBase= event_base_new(); + if (!EventBase) + { + fprintf(stderr, "evping_base_new failed\n"); + exit(1); + } + DnsBase= evdns_base_new(EventBase, 1 /*initialize*/); + if (!DnsBase) + { + fprintf(stderr, "evdns_base_new failed\n"); + exit(1); + } + + + state= ping_ops.init(argc, argv, done); + if (!state) + { + fprintf(stderr, "evping_ops.init failed\n"); + exit(1); + } + ping_ops.start(state); + + r= event_base_loop(EventBase, 0); + if (r != 0) + { + fprintf(stderr, "evping_base_loop failed\n"); + exit(1); + } + return 0; /* not reached */ +} + diff --git a/eperd/evtdig.c b/eperd/evtdig.c new file mode 100644 index 0000000..ee6139b --- /dev/null +++ b/eperd/evtdig.c @@ -0,0 +1,2131 @@ +/* + * Copyright (c) 2011-2013 RIPE NCC + * Copyright (c) 2009 Rocco Carbone + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include "atlas_bb64.h" +#include "atlas_probe.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include "eperd.h" +#include "resolv.h" +#include "readresolv.h" +#include "tcputil.h" + +#include +#include +#include +#include +#include +#include +#define DQ(str) "\"" #str "\"" +#define DQC(str) "\"" #str "\" : " +#define JS(key, val) fprintf(fh, "\"" #key"\" : \"%s\" , ", val); +#define JS_NC(key, val) fprintf(fh, "\"" #key"\" : \"%s\" ", val); +#define JSDOT(key, val) fprintf(fh, "\"" #key"\" : \"%s.\" , ", val); +#define JS1(key, fmt, val) fprintf(fh, "\"" #key"\" : "#fmt" , ", val); +#define JD(key, val) fprintf(fh, "\"" #key"\" : %d , ", val); +#define JD_NC(key, val) fprintf(fh, "\"" #key"\" : %d ", val); +#define JU(key, val) fprintf(fh, "\"" #key"\" : %u , ", val); +#define JU_NC(key, val) fprintf(fh, "\"" #key"\" : %u", val); +#define JC fprintf(fh, ","); + +#define SAFE_PREFIX ATLAS_DATA_NEW + +#define BLURT crondlog (LVL5 "%s:%d %s()", __FILE__, __LINE__, __func__);crondlog +#define IAMHERE crondlog (LVL5 "%s:%d %s()", __FILE__, __LINE__, __func__); + +#undef MIN /* just in case */ +#undef MAX /* also, just in case */ +#define Q_RESOLV_CONF -1 +#define O_RESOLV_CONF 1003 +#define O_PREPEND_PROBE_ID 1004 +#define O_EVDNS 1005 + +#define DNS_FLAG_RD 0x0100 + +#define MIN(a, b) (a < b ? a : b) +#define MAX(a, b) (a > b ? a : b) + +#define ENV2QRY(env) \ + ((struct query_state *)((char *)env - offsetof(struct query_state, tu_env))) + +#define MAX_DNS_BUF_SIZE 5120 +#define MAX_DNS_OUT_BUF_SIZE 512 + +/* Intervals and timeouts (all are in milliseconds unless otherwise specified) */ +#define DEFAULT_NOREPLY_TIMEOUT 5000 /* 1000 msec - 0 is illegal */ +#define DEFAULT_LINE_LENGTH 80 +#define DEFAULT_STATS_REPORT_INTERVEL 180 /* in seconds */ +#define CONN_TO 5 /* TCP connection time out in seconds */ + +/* state of the dns query */ +#define STATUS_DNS_RESOLV 1001 +#define STATUS_TCP_CONNECTING 1002 +#define STATUS_TCP_CONNECTED 1003 +#define STATUS_TCP_WRITE 1004 +#define STATUS_NEXT_QUERY 1005 +#define STATUS_FREE 0 + +// seems T_DNSKEY is not defined header files of lenny and sdk +#ifndef ns_t_dnskey +#define ns_t_dnskey 48 +#endif + +#ifndef T_DNSKEY +#define T_DNSKEY ns_t_dnskey +#endif + +#ifndef ns_t_rrsig +#define ns_t_rrsig 46 +#endif + +#ifndef T_RRSIG +#define T_RRSIG ns_t_rrsig +#endif + +#ifndef ns_t_nsec +#define ns_t_nsec 47 +#endif + +#ifndef T_NSEC +#define T_NSEC ns_t_nsec +#endif + +#ifndef T_NSEC3 +#define T_NSEC3 ns_t_nsec3 +#endif + +#ifndef ns_t_nsec3 +#define ns_t_nsec3 50 +#endif + + +#ifndef ns_t_ds +#define ns_t_ds 43 +#endif + +#ifndef T_DS +#define T_DS ns_t_ds +#endif + + +/* Definition for various types of counters */ +typedef uint32_t counter_t; + +/* How to keep track of a DNS query session */ +struct tdig_base { + struct event_base *event_base; + + evutil_socket_t rawfd_v4; /* Raw socket used to nsm hosts */ + evutil_socket_t rawfd_v6; /* Raw socket used to nsm hosts */ + + struct timeval tv_noreply; /* DNS query Reply timeout */ + + /* A circular list of user queries */ + struct query_state *qry_head; + + struct event event4; /* Used to detect read events on raw socket */ + struct event event6; /* Used to detect read events on raw socket */ + struct event statsReportEvent; + int resolv_max; + char nslist[MAXNS][INET6_ADDRSTRLEN * 2]; + + counter_t sendfail; /* # of failed sendto() */ + counter_t sentok; /* # of successful sendto() */ + counter_t recvfail; /* # of failed recvfrom() */ + counter_t recvok; /* # of successful recvfrom() */ + counter_t martian; /* # of DNS replies we are not looking for */ + counter_t shortpkt; /* # of DNS payload with size < sizeof(struct DNS_HEADER) == 12 bytes */ + counter_t sentbytes; + counter_t recvbytes; + counter_t timeout; + counter_t queries; + counter_t activeqry; + + u_char packet [MAX_DNS_BUF_SIZE] ; + /* used only for the stand alone version */ + void (*done)(void *state); +}; + +static struct tdig_base *tdig_base; + +/* How to keep track of each user query to send dns query */ +struct query_state { + + struct tdig_base *base; + char * name; /* Host identifier as given by the user */ + char * fqname; /* Full qualified hostname */ + char * ipname; /* Remote address in dot notation */ + u_int16_t qryid; /* query id 16 bit */ + int tcp_fd; + FILE *tcp_file; + int wire_size; + + struct bufferevent *bev_tcp; + struct tu_env tu_env; + + int opt_v4_only ; + int opt_v6_only ; + int opt_AF; + int opt_proto; + int opt_edns0; + int opt_dnssec; + int opt_nsid; + int opt_qbuf; + int opt_abuf; + int opt_resolv_conf; + int opt_rd; + int opt_prepend_probe_id; + int opt_evdns; + + char * str_Atlas; + u_int16_t qtype; + u_int16_t qclass; + + char *lookupname; + char * server_name; + char *out_filename ; + + uint32_t pktsize; /* Packet size in bytes */ + struct addrinfo *res, *ressave, *ressent; + + struct sockaddr_in remote; /* store the reply packet src address */ + + + struct event noreply_timer; /* Timer to handle timeout */ + struct event nsm_timer; /* Timer to send UDP */ + struct event next_qry_timer; /* Timer event to start next query */ + + struct timeval xmit_time; + double triptime; + + //tdig_callback_type user_callback; + void *user_callback; + void *user_pointer; /* the pointer given to us for this qry */ + + /* these objects are kept in a circular list */ + struct query_state *next, *prev; + + struct buf err; + struct buf qbuf; + struct buf packet; + int qst ; + char dst_addr_str[(INET6_ADDRSTRLEN+1)]; + char loc_addr_str[(INET6_ADDRSTRLEN+1)]; + unsigned short dst_ai_family ; + unsigned short loc_ai_family ; + struct sockaddr_in6 loc_sin6; + socklen_t loc_socklen; + + + u_char *outbuff; +}; +//DNS header structure +struct DNS_HEADER +{ + u_int16_t id; // identification number + + u_int16_t flags; +/* + u_int16_t rd :1, // recursion desired + tc :1, // truncated message + aa :1, // authoritive answer + opcode :4, // purpose of message + qr :1, // query/response flag + rcode :4, // response code + cd :1, // checking disabled + ad :1, // authenticated data + z :1, // its z! reserved + ra :1; // recursion available + +*/ + u_int16_t q_count; // number of question entries + u_int16_t ans_count; // number of answer entries + u_int16_t ns_count; // number of authority entries + u_int16_t add_count; // number of resource entries +}; + +// EDNS OPT pseudo-RR : EDNS0 +struct EDNS0_HEADER +{ + /** EDNS0 available buffer size, see RFC2671 */ + u_int16_t otype; + uint16_t _edns_udp_size; + u_int8_t _edns_x; // combined rcode and edns version both zeros. + u_int8_t _edns_y; // combined rcode and edns version both zeros. + u_int16_t Z ; // first bit is the D0 bit. +}; + +// EDNS OPT pseudo-RR : eg NSID RFC 5001 +struct EDNS_NSID +{ + uint16_t len; + u_int16_t otype; + u_int16_t odata; +}; + + +//Constant sized fields of query structure +struct QUESTION +{ + u_int16_t qtype; + u_int16_t qclass; +}; + +//Constant sized fields of the resource record structure +#pragma pack(push, 1) +struct R_DATA +{ + u_int16_t type; + u_int16_t _class; + u_int32_t ttl; + u_int16_t data_len; +}; +#pragma pack(pop) + +//Pointers to resource record contents +struct RES_RECORD +{ + unsigned char *name; + struct R_DATA *resource; + unsigned char *rdata; +}; + +static struct option longopts[]= +{ + // class IN + { "a", required_argument, NULL, (100000 + T_A) }, + { "ns", required_argument, NULL, (100000 + T_NS) }, + { "cname", required_argument, NULL, (100000 + T_CNAME) }, + { "ptr", required_argument, NULL, (100000 + T_PTR ) }, + { "mx", required_argument, NULL, (100000 + T_MX ) }, + { "txt", required_argument, NULL, (100000 + T_TXT ) }, + { "aaaa", required_argument, NULL, (100000 + T_AAAA) }, + { "axfr", required_argument, NULL, (100000 + T_AXFR ) }, //yet to be tested. + { "any", required_argument, NULL, (100000 + T_ANY) }, + { "dnskey", required_argument, NULL, (100000 + T_DNSKEY) }, + { "nsec", required_argument, NULL, (100000 + T_NSEC) }, + { "nsec3", required_argument, NULL, (100000 + T_NSEC3) }, + { "ds", required_argument, NULL, (100000 + T_DS) }, + { "rrsig", required_argument, NULL, (100000 + T_RRSIG) }, + { "soa", required_argument, NULL, 's' }, + + // clas CHAOS + { "hostname.bind", no_argument, NULL, 'h' }, + { "id.server", no_argument, NULL, 'i' }, + { "version.bind", no_argument, NULL, 'b' }, + { "version.server", no_argument, NULL, 'r' }, + + // flags + { "edns0", required_argument, NULL, 'e' }, + { "nsid", no_argument, NULL, 'n' }, + { "d0", no_argument, NULL, 'd' }, + + { "resolv", no_argument, NULL, O_RESOLV_CONF }, + { "qbuf", no_argument, NULL, 1001 }, + { "noabuf", no_argument, NULL, 1002 }, + + { "evdns", no_argument, NULL, O_EVDNS }, + { "out-file", required_argument, NULL, 'O' }, + { "p_probe_id", no_argument, NULL, O_PREPEND_PROBE_ID }, + { NULL, } +}; +static char line[DEFAULT_LINE_LENGTH]; + +static void tdig_stats(int unused UNUSED_PARAM, const short event UNUSED_PARAM, void *h); +static int tdig_delete(void *state); +static void ChangetoDnsNameFormat(u_char *dns, char * qry) ; +struct tdig_base *tdig_base_new(struct event_base *event_base); +void tdig_start (struct query_state *qry); +void printReply(struct query_state *qry, int wire_size, unsigned char *result); +void printErrorQuick (struct query_state *qry); +static void local_exit(void *state); +static void *tdig_init(int argc, char *argv[], void (*done)(void *state)); +static void process_reply(void * arg, int nrecv, struct timeval now, int af, void *remote); +static void mk_dns_buff(struct query_state *qry, u_char *packet); +int ip_addr_cmp (u_int16_t af_a, void *a, u_int16_t af_b, void *b); +static void udp_dns_cb(int err, struct evutil_addrinfo *ev_res, struct query_state *qry); + +/* move the next functions from tdig.c */ +u_int32_t get32b (char *p); +void ldns_write_uint16(void *dst, uint16_t data); +uint16_t ldns_read_uint16(const void *src); +unsigned char* ReadName(unsigned char *base, size_t size, size_t offset, + int* count); +/* from tdig.c */ + +void print_txt_json(unsigned char *rdata, int txt_len, FILE *fh); + +int evtdig_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int evtdig_main(int argc, char **argv) +{ + struct query_state *qry; + + EventBase=event_base_new(); + if (!EventBase) + { + crondlog(LVL9 "event_base_new failed"); /* exits */ + } + + qry = tdig_init(argc, argv, NULL); + if(!qry) { + crondlog(DIE9 "evdns_base_new failed"); /* exits */ + event_base_free (EventBase); + return 1; + } + + DnsBase = evdns_base_new(EventBase, 1); + if (!DnsBase) { + crondlog(DIE9 "evdns_base_new failed"); /* exits */ + event_base_free (EventBase); + return 1; + } + + tdig_start(qry); + printf ("starting query\n"); + + event_base_dispatch (EventBase); + event_base_loopbreak (EventBase); + if(EventBase) + event_base_free(EventBase); + return 0; +} + +void print_txt_json(unsigned char *rdata, int txt_len, FILE *fh) +{ + int i; + + fprintf(fh, ", \"RDATA\" : \""); + for(i = 0; i < txt_len; i++) { + if( (*rdata == 34 ) || (*rdata == 92 )) { + fprintf(fh, "\\%c", *(char *)rdata ); + } + // Space - DEL + else if ((*rdata > 31 ) && (*rdata < 128)) { + fprintf(fh, "%c", *(char *)rdata ); + } + else { + fprintf(fh, "\\u00%02X", *rdata ); + } + rdata++; + } + + fprintf(fh, "\""); +} + +static void local_exit(void *state UNUSED_PARAM) +{ + //fprintf(stderr, "And we are done\n"); + exit(0); +} + + +/* Initialize a struct timeval by converting milliseconds */ +static void msecstotv(time_t msecs, struct timeval *tv) +{ + tv->tv_sec = msecs / 1000; + tv->tv_usec = msecs % 1000 * 1000; +} + +int ip_addr_cmp (u_int16_t af_a, void *a, u_int16_t af_b, void *b) +{ + struct sockaddr_in *a4; + struct sockaddr_in *b4; + struct sockaddr_in6 *a6; + struct sockaddr_in6 *b6; + char buf[INET6_ADDRSTRLEN]; + + if(af_a != af_b) { + crondlog(LVL5 "address family mismatch in %d ", __LINE__); + return -1; + } + + if(af_a == AF_INET ) { + a4 = (struct sockaddr_in *) a; + b4 = (struct sockaddr_in *) b; + if( memcmp ( &(a4->sin_addr), &(b4->sin_addr), sizeof(struct in_addr)) == 0) { + return 0; + } + else + return 1; + } + else if(af_a == AF_INET6 ) { + a6 = (struct sockaddr_in6 *) a; + b6 = (struct sockaddr_in6 *) b; + if( memcmp ( &(a6->sin6_addr), &(b6->sin6_addr), sizeof(struct in6_addr)) == 0) { + inet_ntop(AF_INET6, &(a6->sin6_addr), buf, sizeof(buf)); + crondlog(LVL5 "address6 match A %s", buf); + inet_ntop(AF_INET6, &(b6->sin6_addr), buf, sizeof(buf)); + crondlog(LVL5 "address6 match B %s", buf); + + return 0; + } + else { + inet_ntop(AF_INET6, &(a6->sin6_addr), buf, sizeof(buf)); + crondlog(LVL5 "address6 mismatch A %s", buf); + inet_ntop(AF_INET6, &(b6->sin6_addr), buf, sizeof(buf)); + crondlog(LVL5 "address mismatch B %s", buf); + + + return 1; + } + } + return 1; +} + +/* Lookup for a query by its index */ +static struct query_state* tdig_lookup_query( struct tdig_base * base, int idx, int af, void * remote) +{ + int i = 0; + struct query_state *qry; + + qry = base->qry_head; + if (!qry) + return NULL; + do { + i++; + if (qry->qryid == idx) + { + //AA chnage to LVL5 + crondlog(LVL7 "found matching query id %d", idx); + if( qry->ressent && ip_addr_cmp (af, remote, qry->ressent->ai_family, qry->ressent->ai_addr) == 0) { + crondlog(LVL7 "matching id and address id %d", idx); + return qry; + } + else { + crondlog(LVL7 "matching id and address mismatch id %d", idx); + } + } + qry = qry->next; + if (i > (2*base->activeqry) ) { + crondlog(LVL7 "i am looping %d AA", idx); + return NULL; + } + + } while (qry != base->qry_head); + + return NULL; +} + +static void mk_dns_buff(struct query_state *qry, u_char *packet) +{ + struct DNS_HEADER *dns = NULL; + u_char *qname; + struct QUESTION *qinfo = NULL; + struct EDNS0_HEADER *e; + struct EDNS_NSID *n; + int r; + struct buf pbuf; + char *lookup_prepend; + int probe_id; + + dns = (struct DNS_HEADER *)packet; + r = random(); + r %= 65535; + qry->qryid = (uint16_t) r; // host is storing int host byte order + crondlog(LVL5 "%s %s() : %d base address %p",__FILE__, __func__, __LINE__, qry->base); + BLURT(LVL5 "dns qyery id %d", qry->qryid); + dns->id = (uint16_t) htons(r); + /* + dns->qr = 0; //This is a query + dns->opcode = 0; //This is a standard query + dns->aa = 0; //Not Authoritative + dns->tc = 0; //This message is not truncated + dns->rd = 0; //Recursion not Desired + dns->ra = 1; //Recursion not available! hey we dont have it (lol) + dns->z = 0; + dns->ad = 0; + dns->cd = 0; + dns->rcode = 0; +*/ + dns->q_count = htons(1); //we have only 1 question + dns->ans_count = 0; + dns->ns_count = 0; + dns->add_count = htons(0); + + if (( qry->opt_resolv_conf > Q_RESOLV_CONF ) || (qry->opt_rd )){ + // if you need more falgs do a bitwise and here. + dns->flags = htons(DNS_FLAG_RD); + } + + //point to the query portion + qname =(u_char *)&packet[sizeof(struct DNS_HEADER)]; + + // should it be limited to clas C_IN ? + if(qry->opt_prepend_probe_id ) { + probe_id = get_probe_id(); + probe_id = MAX(probe_id, 0); + + + lookup_prepend = xzalloc(DEFAULT_LINE_LENGTH + sizeof(qry->lookupname)); + snprintf(lookup_prepend, (sizeof(qry->lookupname) + DEFAULT_LINE_LENGTH - 1), "%d.%lu.%s", probe_id, qry->xmit_time.tv_sec, qry->lookupname); + + ChangetoDnsNameFormat(qname, lookup_prepend); // fill the query portion. + + free(lookup_prepend); + } + else { + ChangetoDnsNameFormat(qname, qry->lookupname); // fill the query portion. + } + qinfo =(struct QUESTION*)&packet[sizeof(struct DNS_HEADER) + (strlen((const char*)qname) + 1)]; + + qinfo->qtype = htons(qry->qtype); + qinfo->qclass = htons(qry->qclass); + + qry->pktsize = (strlen((const char*)qname) + 1) + sizeof(struct DNS_HEADER) + sizeof(struct QUESTION) ; + if(qry->opt_nsid || qry->opt_dnssec || (qry->opt_edns0 > 512)) { + e=(struct EDNS0_HEADER*)&packet[ qry->pktsize + 1 ]; + e->otype = htons(ns_t_opt); + e->_edns_udp_size = htons(qry->opt_edns0); + if(qry->opt_dnssec) { + e->Z = htons(0x8000); + } + else { + e->Z = 0x0; + } + crondlog(LVL5 "opt header in hex | %02X %02X %02X %02X %02X %02X %02X %02X %02X | %02X", + packet[qry->pktsize], + packet[qry->pktsize + 1], + packet[qry->pktsize + 2], + packet[qry->pktsize + 3], + packet[qry->pktsize + 4], + packet[qry->pktsize + 5], + packet[qry->pktsize + 6], + packet[qry->pktsize + 7], + packet[qry->pktsize + 8], + packet[qry->pktsize + 9]); + + qry->pktsize += sizeof(struct EDNS0_HEADER) ; + + if(qry->opt_nsid ) { + dns->add_count = htons(1); + n=(struct EDNS_NSID*)&packet[ qry->pktsize + 1 ]; + n->len = htons(4); + n->otype = htons(3); + } + qry->pktsize += sizeof(struct EDNS_NSID) + 1; + dns->add_count = htons(1); + /* Transmit the request over the network */ + } + buf_init(&pbuf, -1); + + if(qry->pktsize) { + snprintf(line, DEFAULT_LINE_LENGTH, "%0d bytes ", qry->pktsize); + buf_add(&pbuf, line, strlen(line)); + + line[0] = '"'; + buf_add(&pbuf, line, 1); + for(int x = 0; x < qry->pktsize; x++) { + snprintf(line, DEFAULT_LINE_LENGTH, "%02X ", packet[x]); + buf_add(&pbuf, line, 3); + } + line[0] = '"'; + line[1] = '\0'; + buf_add(&pbuf, line, 2 ); + crondlog(LVL5 "payload : %s", pbuf.buf); + buf_cleanup(&pbuf); + } +} + + + +/* Attempt to transmit a UDP DNS Request to a serveri. TCP is else where */ +static void tdig_send_query_callback(int unused UNUSED_PARAM, const short event UNUSED_PARAM, void *h) +{ + struct query_state *qry = h; + struct tdig_base *base = qry->base; + uint32_t nsent = 0; + u_char *outbuff; + int err = 0; + int sockfd; + + /* Clean the no reply timer (if any was previously set) */ + evtimer_del(&qry->noreply_timer); + + outbuff = xzalloc(MAX_DNS_OUT_BUF_SIZE); + bzero(outbuff, MAX_DNS_OUT_BUF_SIZE); + //AA delete qry->outbuff = outbuff; + gettimeofday(&qry->xmit_time, NULL); + mk_dns_buff(qry, outbuff); + do { + switch (qry->res->ai_family) { + case AF_INET: + nsent = sendto(base->rawfd_v4, outbuff,qry->pktsize, MSG_DONTWAIT, qry->res->ai_addr, qry->res->ai_addrlen); + break; + case AF_INET6: + nsent = sendto(base->rawfd_v6, outbuff,qry->pktsize, MSG_DONTWAIT, qry->res->ai_addr, qry->res->ai_addrlen); + break; + } + qry->ressent = qry->res; + + if (nsent == qry->pktsize) { + // the packet is send. Now lets try to the source address we would have used. + // create another sock with same dest, connect and get the source address + // delete that socket and hope the the source address is the right one. + if ((sockfd = socket(qry->res->ai_family, SOCK_DGRAM, 0 ) ) < 0 ) { + snprintf(line, DEFAULT_LINE_LENGTH, "%s \"socket\" : \"temp socket to get src address failed %s\"", qry->err.size ? ", " : "", strerror(errno)); + buf_add(&qry->err, line, strlen(line)); + return; + } + else { + qry->loc_socklen = sizeof(qry->loc_sin6); + connect(sockfd, qry->res->ai_addr, qry->res->ai_addrlen); + if (getsockname(sockfd,(struct sockaddr *)&qry->loc_sin6, &qry->loc_socklen) == -1) { + snprintf(line, DEFAULT_LINE_LENGTH, "%s \"getscokname\" : \"%s\"", qry->err.size ? ", " : "", strerror(errno)); + buf_add(&qry->err, line, strlen(line)); + } + close(sockfd); + } + + /* One more DNS Query is sent */ + base->sentok++; + base->sentbytes += nsent; + err = 0; + /* Add the timer to handle no reply condition in the given timeout */ + evtimer_add(&qry->noreply_timer, &base->tv_noreply); + if(qry->opt_qbuf) { + buf_init(&qry->qbuf, -1); + buf_add_b64(&qry->qbuf, outbuff, qry->pktsize, 0); + } + + } + else { + err = 1; + base->sendfail++; + snprintf(line, DEFAULT_LINE_LENGTH, "%s \"senderror\" : \"AF %s, %s\"", qry->err.size ? ", " : "" + , strerror(errno) , qry->res->ai_family == AF_INET ? "AF_INET" :"NOT AF_INET"); + buf_add(&qry->err, line, strlen(line)); + } + } while ((qry->res = qry->res->ai_next) != NULL); + free (outbuff); + outbuff = NULL; + if(err) { + printReply (qry, 0, NULL); + return; + } +} + +static void next_qry_cb(int unused UNUSED_PARAM, const short event UNUSED_PARAM, void *h) { + struct query_state *qry = h; + BLURT(LVL5 "next query for %s", qry->server_name); + tdig_start(qry); +} + +/* The callback to handle timeouts due to destination host unreachable condition */ +static void noreply_callback(int unused UNUSED_PARAM, const short event UNUSED_PARAM, void *h) +{ + struct query_state *qry = h; + qry->base->timeout++; + snprintf(line, DEFAULT_LINE_LENGTH, "%s \"timeout\" : %d", qry->err.size ? ", " : "", DEFAULT_NOREPLY_TIMEOUT); + buf_add(&qry->err, line, strlen(line)); + + BLURT(LVL5 "AAA timeout for %s ", qry->server_name); + printReply (qry, 0, NULL); + return; +} + +static void tcp_timeout_callback (int __attribute((unused)) unused, + const short __attribute((unused)) event, void *s) +{ + struct query_state * qry; + qry = ENV2QRY(s); + noreply_callback(0, 0, qry); +} + +static void tcp_reporterr(struct tu_env *env, enum tu_err cause, + const char *str) +{ + struct query_state * qry; + qry = ENV2QRY(env); + + // if (env != &state->tu_env) abort(); // Why do i need this? AA + + switch(cause) + { + case TU_DNS_ERR: + + snprintf(line, DEFAULT_LINE_LENGTH, "%s \"TUDNS\" : \"%s\"", qry->err.size ? ", " : "", str ); + buf_add(&qry->err, line, strlen(line)); + break; + + case TU_READ_ERR: + // need more than this reporting for this case AA + snprintf(line, DEFAULT_LINE_LENGTH, "%s \"TU_READ_ERR\" : \"%s\"", qry->err.size ? ", " : "", str ); + buf_add(&qry->err, line, strlen(line)); + break; + + case TU_CONNECT_ERR: + snprintf(line, DEFAULT_LINE_LENGTH, "%s \"TUCONNECT\" : \"%s\"", qry->err.size ? ", " : "", str ); + buf_add(&qry->err, line, strlen(line)); + //reconnect next one AA + break; + + case TU_OUT_OF_ADDRS: + snprintf(line, DEFAULT_LINE_LENGTH, "%s \"TU_OUT_OF_ADDRESS\" : \"%s\"", qry->err.size ? ", " : "", str ); + buf_add(&qry->err, line, strlen(line)); + break; + + default: + snprintf(line, DEFAULT_LINE_LENGTH, "%s \"TU_UNKNOWN\" : \"%d %s\"", qry->err.size ? ", " : "", cause, str ); + crondlog(DIE9 "reporterr: bad cause %d", cause); + break; + } + printReply (qry, 0, NULL); +} + +static void tcp_dnscount(struct tu_env *env, int count) +{ + struct query_state * qry; + qry = ENV2QRY(env); + BLURT(LVL5 "dns count for %s : %d", qry->server_name , count); +} + +static void tcp_beforeconnect(struct tu_env *env, + struct sockaddr *addr, socklen_t addrlen) +{ + struct query_state * qry; + qry = ENV2QRY(env); + gettimeofday(&qry->xmit_time, NULL); + qry->dst_ai_family = addr->sa_family; + BLURT(LVL5 "time : %d", qry->xmit_time.tv_sec); + getnameinfo(addr, addrlen, qry->dst_addr_str, INET6_ADDRSTRLEN , NULL, 0, NI_NUMERICHOST); +} + +static void tcp_connected(struct tu_env *env, struct bufferevent *bev) +{ + uint16_t payload_len ; + u_char *outbuff; + u_char *wire; + struct query_state * qry; + qry = ENV2QRY(env); + + qry->loc_socklen= sizeof(qry->loc_sin6); + getsockname(bufferevent_getfd(bev), &qry->loc_sin6, &qry->loc_socklen); + + qry->bev_tcp = bev; + outbuff = xzalloc(MAX_DNS_BUF_SIZE); + bzero(outbuff, MAX_DNS_OUT_BUF_SIZE); + mk_dns_buff(qry, outbuff); + payload_len = (uint16_t) qry->pktsize; + wire = xzalloc (payload_len + 4); + ldns_write_uint16(wire, qry->pktsize); + memcpy(wire + 2, outbuff, qry->pktsize); + evbuffer_add(bufferevent_get_output(qry->bev_tcp), wire, (qry->pktsize +2)); + qry->base->sentok++; + qry->base->sentbytes+= (qry->pktsize +2); + BLURT(LVL5 "send %u bytes", payload_len ); + + if(qry->opt_qbuf) { + buf_init(&qry->qbuf, -1); + buf_add_b64(&qry->qbuf, outbuff, qry->pktsize, 0); + } + free(outbuff); + free(wire); +} + +static void tcp_readcb(struct bufferevent *bev UNUSED_PARAM, void *ptr) +{ + + struct query_state *qry = ptr; + int n; + u_char b2[2]; + struct timeval rectime; + struct evbuffer *input ; + struct DNS_HEADER *dnsR = NULL; + + qry = ENV2QRY(ptr); + BLURT(LVL5 "TCP readcb %s", qry->server_name ); + + if( qry->packet.size && (qry->packet.size >= qry->wire_size)) { + snprintf(line, DEFAULT_LINE_LENGTH, "%s \"TCPREADSIZE\" : " + " \"red more bytes than expected %d, got %zu\"" + , qry->err.size ? ", " : "" + , qry->wire_size, qry->packet.size); + buf_add(&qry->err, line, strlen(line)); + printReply (qry, 0, NULL); + return; + } + + gettimeofday(&rectime, NULL); + bzero(qry->base->packet, MAX_DNS_BUF_SIZE); + + input = bufferevent_get_input(bev); + if(qry->wire_size == 0) { + n = evbuffer_remove(input, b2, 2 ); + if(n == 2){ + qry->wire_size = ldns_read_uint16(b2); + buf_init(&qry->packet, -1); + } + else { + + snprintf(line, DEFAULT_LINE_LENGTH, "%s \"TCPREAD\" : \"expected 2 bytes and got %d\"", qry->err.size ? ", " : "", n ); + buf_add(&qry->err, line, strlen(line)); + } + } + while ((n = evbuffer_remove(input,line , DEFAULT_LINE_LENGTH )) > 0) { + buf_add(&qry->packet, line, n); + if(qry->wire_size == qry->packet.size) { + crondlog(LVL5 "in readcb %s %s red %d bytes ", qry->str_Atlas, qry->server_name, qry->wire_size); + crondlog(LVL5 "qry pointer address readcb %p qry.id, %d", qry->qryid); + crondlog(LVL5 "DBG: base pointer address readcb %p", qry->base ); + dnsR = (struct DNS_HEADER*) qry->packet.buf; + if ( ntohs(dnsR->id) == qry->qryid ) { + qry->triptime = (rectime.tv_sec - qry->xmit_time.tv_sec)*1000 + (rectime.tv_usec-qry->xmit_time.tv_usec)/1e3; + printReply (qry, qry->packet.size, qry->packet.buf); + } + else { + bzero(line, DEFAULT_LINE_LENGTH); + snprintf(line, DEFAULT_LINE_LENGTH, " %s \"idmismatch\" : \"mismatch id from tcp fd %d\"", qry->err.size ? ", " : "", n); + buf_add(&qry->err, line, strlen(line)); + printReply (qry, 0, NULL); + } + return; + } + } +} + +static void tcp_writecb(struct bufferevent *bev, void *ptr) +{ + /* + struct query_state * qry; + qry = ENV2QRY(ptr); + */ + BLURT(LVL5 "TCP writecb"); +} + + + +/* + * Called by libevent when the kernel says that the raw socket is ready for reading. + * + * It reads a packet from the wire and attempt to decode and relate DNS Request/Reply. + * + * To be legal the packet received must be: + * o of enough size (> DNS Header size) + * o the one we are looking for (matching the same identifier of all the packets the program is able to send) + */ + +static void process_reply(void * arg, int nrecv, struct timeval now, int af, void *remote ) +{ + struct tdig_base *base = arg; + + struct DNS_HEADER *dnsR = NULL; + + struct query_state * qry; + + if (nrecv < sizeof (struct DNS_HEADER)) { + base->shortpkt++; + return; + } + + dnsR = (struct DNS_HEADER*) base->packet; + base->recvok++; + + + crondlog(LVL7 "DBG: base address process reply %p, nrec %d", base, nrecv); + /* Get the pointer to the qry descriptor in our internal table */ + qry = tdig_lookup_query(base, ntohs(dnsR->id), af, remote); + + if ( ! qry) { + base->martian++; + crondlog(LVL7 "DBG: no match found for qry id i %d",\ +ntohs(dnsR->id)); + return; + } + + qry->base->recvbytes += nrecv; + gettimeofday(&now, NULL); // lave this till fix now from ready_callback6 corruption; ghoost + qry->triptime = (now.tv_sec-qry->xmit_time.tv_sec)*1000 + (now.tv_usec-qry->xmit_time.tv_usec)/1e3; + + /* Clean the noreply timer */ + evtimer_del(&qry->noreply_timer); + printReply (qry, nrecv, base->packet); + return; +} + +static void ready_callback4 (int unused UNUSED_PARAM, const short event UNUSED_PARAM, void * arg) +{ + struct tdig_base *base = arg; + int nrecv; + struct sockaddr_in remote4; /* responding internet address */ + socklen_t slen; + struct timeval rectime; + + slen = sizeof(struct sockaddr); + bzero(base->packet, MAX_DNS_BUF_SIZE); + /* Time the packet has been received */ + + gettimeofday(&rectime, NULL); + /* Receive data from the network */ + nrecv = recvfrom(base->rawfd_v4, base->packet, sizeof(base->packet), MSG_DONTWAIT, &remote4, &slen); + if (nrecv < 0) { + /* One more failure */ + base->recvfail++; + return ; + } + process_reply(arg, nrecv, rectime, remote4.sin_family, &remote4); + return; +} + +static void ready_callback6 (int unused UNUSED_PARAM, const short event UNUSED_PARAM, void * arg) +{ + struct tdig_base *base = arg; + int nrecv; + struct timeval rectime; + struct msghdr msg; + struct iovec iov[1]; + //char buf[INET6_ADDRSTRLEN]; + struct sockaddr_in6 remote6; + char cmsgbuf[256]; + + /* Time the packet has been received */ + gettimeofday(&rectime, NULL); + + iov[0].iov_base= base->packet; + iov[0].iov_len= sizeof(base->packet); + + msg.msg_name= &remote6; + msg.msg_namelen= sizeof( struct sockaddr_in6); + msg.msg_iov= iov; + msg.msg_iovlen= 1; + msg.msg_control= cmsgbuf; + msg.msg_controllen= sizeof(cmsgbuf); + msg.msg_flags= 0; /* Not really needed */ + + nrecv= recvmsg(base->rawfd_v6, &msg, MSG_DONTWAIT); + if (nrecv == -1) { + /* Strange, read error */ + printf("ready_callback6: read error '%s'\n", strerror(errno)); + return; + } + process_reply(arg, nrecv, rectime, remote6.sin6_family, &remote6); + + return; +} + +/* this called for each query/line in eperd */ +static void *tdig_init(int argc, char *argv[], void (*done)(void *state)) +{ + char *check; + struct query_state *qry; + int c; + + if(!tdig_base) + tdig_base = tdig_base_new(EventBase); + + if(!tdig_base) + crondlog(DIE9 "tdig_base_new failed"); + + tdig_base->done = done; + + qry=xzalloc(sizeof(*qry)); + + // initialize per query state variables; + qry->qtype = T_TXT; /* TEXT */ + qry->qclass = C_CHAOS; + qry->opt_v4_only = 0; + qry->opt_v6_only = 0; + qry->str_Atlas = NULL; + qry->out_filename = NULL; + qry->opt_proto = 17; + qry->tcp_file = NULL; + qry->tcp_fd = -1; + qry->server_name = NULL; + qry->str_Atlas = NULL; + tdig_base->activeqry++; + qry->qst = 0; + qry->wire_size = 0; + qry->triptime = 0; + qry->opt_edns0 = 512; + qry->opt_dnssec = 0; + qry->opt_nsid = 0; + qry->opt_qbuf = 0; + qry->opt_abuf = 1; + qry->opt_rd = 0; + qry->opt_evdns = 0; + qry->opt_prepend_probe_id = 0; + qry->ressave = NULL; + qry->ressent = NULL; + buf_init(&qry->err, -1); + buf_init(&qry->packet, -1); + qry->opt_resolv_conf = (Q_RESOLV_CONF - 1); + qry->lookupname = NULL; + qry->dst_ai_family = 0; + qry->loc_ai_family = 0; + qry->loc_sin6.sin6_family = 0; + + /* initialize callbacks : */ + /* sendpacket called by UDP send */ + evtimer_assign(&qry->nsm_timer, tdig_base->event_base, + tdig_send_query_callback, qry); + /* no reply timeout for udp queries */ + evtimer_assign(&qry->noreply_timer, tdig_base->event_base, + noreply_callback, qry); + + /* callback/timer used for restarting query by --resove */ + evtimer_assign(&qry->next_qry_timer, tdig_base->event_base, next_qry_cb + ,qry); + + optind = 0; + while (c= getopt_long(argc, argv, "46adD:e:tbhinqO:Rrs:A:?", longopts, NULL), c != -1) { + switch(c) { + case '4': + qry->opt_v4_only = 1; + qry->opt_AF = AF_INET; + break; + case '6': + qry->opt_v6_only = 1; + qry->opt_AF = AF_INET6; + break; + + case 'a': + qry->opt_v6_only = 1; + qry->opt_v4_only = 1; + break; + + case 'A': + qry->str_Atlas = strdup(optarg); + break; + case 'b': + qry->lookupname = strdup ("version.bind."); + break; + + case 'd': + qry->opt_dnssec = 1; + break; + + case 'e': + qry->opt_edns0= strtoul(optarg, &check, 10); + break; + + case 'h': + qry->lookupname = strdup("hostname.bind."); + break; + + case 'i': + qry->lookupname = strdup("id.server."); + break; + + case 'n': + qry->opt_nsid = 1; + break; + + case 'O': + qry->out_filename = strdup(optarg); + break; + + case 'r': + qry->lookupname = strdup("version.server."); + break; + + case 'R': + qry->opt_rd = 1; + break; + + case 's': + qry->qtype = T_SOA; + qry->qclass = C_IN; + qry->lookupname = strdup(optarg); + break; + + case 't': + qry->opt_proto = 6; + break; + + case 1001: + qry->opt_qbuf = 1; + break; + + case 1002: + qry->opt_abuf = 0; + break; + + case O_RESOLV_CONF : + qry->opt_resolv_conf = Q_RESOLV_CONF ; + qry->opt_v6_only = 1; + qry->opt_v4_only = 1; + break; + + case O_PREPEND_PROBE_ID: + qry->opt_prepend_probe_id = 1; + break; + + case O_EVDNS: + qry->opt_evdns = 1; + break; + + case (100000 + T_A): + qry->qtype = T_A; + qry->qclass = C_IN; + qry->lookupname = strdup(optarg); + break; + + case (100000 + T_NS): + qry->qtype = T_NS; + qry->qclass = C_IN; + qry->lookupname = strdup(optarg); + break; + + case (100000 + T_CNAME): + qry->qtype = T_CNAME; + qry->qclass = C_IN; + qry->lookupname = strdup(optarg); + break; + + case (100000 + T_PTR): + qry->qtype = T_PTR; + qry->qclass = C_IN; + qry->lookupname = strdup(optarg); + break; + + case (100000 + T_MX): + qry->qtype = T_MX; + qry->qclass = C_IN; + qry->lookupname = strdup(optarg); + break; + + case (100000 + T_TXT): + qry->qtype = T_TXT; + qry->qclass = C_IN; + qry->lookupname = strdup(optarg); + break; + + case (100000 + T_AAAA ): + qry->qtype = T_AAAA ; + qry->qclass = C_IN; + qry->lookupname = strdup(optarg); + break; + + case (100000 + T_AXFR ): + qry->qtype = T_AXFR ; + qry->qclass = C_IN; + qry->lookupname = strdup(optarg); + break; + + case (100000 + T_ANY): + qry->qtype = T_ANY ; + qry->qclass = C_IN; + qry->lookupname = strdup(optarg); + break; + + case (100000 + T_DS): + qry->qtype = T_DS; + qry->qclass = C_IN; + qry->lookupname = strdup(optarg); + break; + + case (100000 + T_NSEC): + qry->qtype = T_NSEC; + qry->qclass = C_IN; + qry->lookupname = strdup(optarg); + break; + + case (100000 + T_NSEC3): + qry->qtype = T_NSEC3; + qry->qclass = C_IN; + qry->lookupname = strdup(optarg); + break; + + case (100000 + T_DNSKEY): + qry->qtype = T_DNSKEY; + qry->qclass = C_IN; + qry->lookupname = strdup(optarg); + break; + + case (100000 + T_RRSIG): + qry->qtype = T_RRSIG; + qry->qclass = C_IN; + qry->lookupname = strdup(optarg); + break;break; + + default: + fprintf(stderr, "ERROR unknown option %d ??\n", c); + tdig_delete(qry); + return (0); + break; + } + } + if( qry->opt_resolv_conf == Q_RESOLV_CONF ) { + if(tdig_base->resolv_max ) { + qry->opt_resolv_conf = 1; + qry->server_name = strdup(tdig_base->nslist[0]); + } + else { + // may be the /etc/resolv.conf is yet to red. + // try once then use it || give up + tdig_base->resolv_max = get_local_resolvers (tdig_base->nslist); + if(tdig_base->resolv_max ){ + qry->opt_resolv_conf = 1; + qry->server_name = strdup(tdig_base->nslist[0]); + } + else { + tdig_delete(qry); + return NULL; + } + } + } + else if (optind != argc-1) { + crondlog(LVL9 "ERROR no server IP address in input"); + tdig_delete(qry); + return NULL; + } + else + qry->server_name = strdup(argv[optind]); + + if(qry->lookupname == NULL) { + crondlog(LVL9 "ERROR no query in command line"); + tdig_delete(qry); + return NULL; + } + + if (qry->out_filename && + !validate_filename(qry->out_filename, SAFE_PREFIX)) + { + crondlog(LVL8 "insecure file '%s'", qry->out_filename); + tdig_delete(qry); + return NULL; + } + + + if(qry->opt_v6_only == 0) + { + qry->opt_v4_only = 1; + qry->opt_AF = AF_INET; + } + qry->base = tdig_base; + + /* insert this qry into the list of queries */ + if (!tdig_base->qry_head) { + qry->next = qry->prev = qry; + tdig_base->qry_head = qry; + tdig_stats( 0, 0, tdig_base); // call this first time to initial values. + crondlog(LVL7 "new head qry %s qry->prev %s qry->next %s", qry->str_Atlas, qry->prev->str_Atlas, qry->next->str_Atlas); + } + else { + crondlog(LVL7 "old head hea %s hea->prev %s hea->next %s", tdig_base->qry_head->str_Atlas, tdig_base->qry_head->prev->str_Atlas, tdig_base->qry_head->next->str_Atlas); + if (tdig_base->qry_head->prev == tdig_base->qry_head) { + tdig_base->qry_head->prev = qry; + crondlog(LVL7 "head->prev == head quereis %d AA", tdig_base->activeqry); + } + qry->next = tdig_base->qry_head->next; + qry->prev = tdig_base->qry_head; + tdig_base->qry_head->next->prev = qry; + tdig_base->qry_head->next = qry; + crondlog(LVL7 " qry %s qry->prev %s qry->next %s", qry->str_Atlas, qry->prev->str_Atlas, qry->next->str_Atlas); + crondlog(LVL7 "new head hea %s hea->prev %s hea->next %s", tdig_base->qry_head->str_Atlas, tdig_base->qry_head->prev->str_Atlas, tdig_base->qry_head->next->str_Atlas); + } + return qry; +} + +/* called only once. Initialize tdig_base variables here */ +struct tdig_base * tdig_base_new(struct event_base *event_base) +{ + evutil_socket_t fd6; + evutil_socket_t fd4; + struct addrinfo hints; + int on = 1; + struct timeval tv; + + bzero(&hints,sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_flags = 0; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = 0; + + /* Create an endpoint for communication using raw socket for ICMP calls */ + if ((fd4 = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol) ) < 0 ) + { + return NULL; + } + + hints.ai_family = AF_INET6; + if ((fd6 = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol) ) < 0 ) + { + close(fd4); + return NULL; + } + + tdig_base= xzalloc(sizeof( struct tdig_base)); + if (tdig_base == NULL) + { + close(fd4); + close(fd6); + return (NULL); + } + + tdig_base->qry_head = NULL; + tdig_base->sendfail = 0; + tdig_base->sentok = 0; + tdig_base->recvfail = 0; + tdig_base->recvok = 0; + tdig_base->martian = 0; + tdig_base->shortpkt = 0; + tdig_base->sentbytes = 0; + tdig_base->recvbytes = 0; + tdig_base->timeout = 0; + tdig_base->activeqry = 0; + tdig_base->resolv_max = 0; + + memset(tdig_base, 0, sizeof(struct tdig_base)); + tdig_base->event_base = event_base; + + tdig_base->rawfd_v4 = fd4; + tdig_base->rawfd_v6 = fd6; + + setsockopt(fd6, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)); + + on = 1; + setsockopt(fd6, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof(on)); + + //memset(&tdig_base-->loc_sin6, '\0', sizeof(tdig_base-->loc_sin6)); + //tdig_base-->loc_socklen= 0; + + evutil_make_socket_nonblocking(tdig_base->rawfd_v4); + + msecstotv(DEFAULT_NOREPLY_TIMEOUT, &tdig_base->tv_noreply); + + // Define the callback to handle UDP Reply + // add the raw file descriptor to those monitored for read events + + event_assign(&tdig_base->event4, tdig_base->event_base, tdig_base->rawfd_v4, + EV_READ | EV_PERSIST, ready_callback4, tdig_base); + event_add(&tdig_base->event4, NULL); + + event_assign(&tdig_base->event6, tdig_base->event_base, tdig_base->rawfd_v6, + EV_READ | EV_PERSIST, ready_callback6, tdig_base); + event_add(&tdig_base->event6, NULL); + + evtimer_assign(&tdig_base->statsReportEvent, tdig_base->event_base, tdig_stats, tdig_base); + tv.tv_sec = DEFAULT_STATS_REPORT_INTERVEL; + tv.tv_usec = 0; + event_add(&tdig_base->statsReportEvent, &tv); + + return tdig_base; +} + +static void udp_dns_cb(int err, struct evutil_addrinfo *ev_res, struct query_state *qry) { + + if (err) { + qry->qst = STATUS_FREE; + snprintf(line, DEFAULT_LINE_LENGTH, "\"evdns_getaddrinfo\": \"%s\"", evutil_gai_strerror(err)); + buf_add(&qry->err, line, strlen(line)); + printReply (qry, 0, NULL); + return ; + + } + else { + qry->res = ev_res; + qry->ressave = ev_res; + tdig_send_query_callback(0, 0, qry); + } +} + +void tdig_start (struct query_state *qry) +{ + struct timeval asap = { 0, 0 }; + struct timeval interval; + + int err_num; + struct addrinfo hints, *res; + char port[] = "domain"; + char port_as_char[] = "53"; + + switch(qry->qst) + { + case STATUS_NEXT_QUERY : + case STATUS_FREE : + break; + default: + printErrorQuick(qry); + return ; + } + + if(qry->opt_resolv_conf > tdig_base->resolv_max) { + qry->opt_resolv_conf = 0; + free (qry->server_name); + qry->server_name = strdup(tdig_base->nslist[qry->opt_resolv_conf]); + qry->opt_resolv_conf++; + } + + bzero(&hints, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_flags = 0; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = 0; + + gettimeofday(&qry->xmit_time, NULL); + qry->qst = STATUS_DNS_RESOLV; + + if(qry->opt_v6_only == 1) + { + hints.ai_family = AF_INET6; + } + else if(qry->opt_v4_only == 1) + { + hints.ai_family = AF_INET; + } + + if( (qry->opt_v4_only == 1 ) && (qry->opt_v6_only == 1) ) + { + hints.ai_family = AF_UNSPEC; + } + + if(qry->opt_proto == 17) { //UDP + if(qry->opt_evdns ) { + // use EVDNS asynchronous call + evdns_getaddrinfo(DnsBase, qry->server_name, port_as_char , &hints, udp_dns_cb, qry); + } + else { + // using getaddrinfo; blocking call + if ( ( err_num = getaddrinfo(qry->server_name, port , &hints, &res))) + { + snprintf(line, DEFAULT_LINE_LENGTH, "%s \"getaddrinfo\": \"port %s, AF %d %s\"", qry->err.size ? ", " : "", port, hints.ai_family, gai_strerror(err_num)); + buf_add(&qry->err, line, strlen(line)); + + printReply (qry, 0, NULL); + qry->qst = STATUS_FREE; + return ; + } + + qry->res = res; + qry->ressave = res; + + evtimer_add(&qry->nsm_timer, &asap); + } + } + else { // TCP Query + + qry->wire_size = 0; + crondlog(LVL5 "TCP QUERY %s", qry->server_name); + interval.tv_sec = CONN_TO; + interval.tv_usec= 0; + tu_connect_to_name (&qry->tu_env, qry->server_name, port_as_char, + &interval, &hints, tcp_timeout_callback, tcp_reporterr, + tcp_dnscount, tcp_beforeconnect, + tcp_connected, tcp_readcb, tcp_writecb); + + } + return ; +} + +#if 0 +int tdig_base_count_queries(struct tdig_base *base) +{ + const struct query_state *qry; + int n = 0; + + qry = base->qry_head; + if (!qry) + return 0; + do { + ++n; + qry = qry->next; + } while (qry != base->qry_head); + + return n; +} + +#endif + +static void tdig_stats(int unusg_statsed UNUSED_PARAM, const short event UNUSED_PARAM, void *h) +{ + struct timeval now; + FILE *fh; + struct tdig_base *base; + struct query_state *qry; + + base = h; + if(!base->qry_head ) + return; + + qry = base->qry_head; + + if(! base->sentok ) + return; + + if (qry->out_filename) { + fh= fopen(qry->out_filename, "a"); + if (!fh) + crondlog(DIE9 "unable to append to '%s'", qry->out_filename); + } + else + fh = stdout; + + fprintf(fh, "RESULT { "); + JS(id, "9201" ); + gettimeofday(&now, NULL); + JS1(time, %ld, now.tv_sec); + JU(sok , base->sentok); + JU(rok , base->recvok); + JU(sent , base->sentbytes); + JU(recv , base->recvbytes); + JU(serr , base->sendfail); + JU(rerr , base->recvfail); + JU(timeout , base->timeout); + JU(short , base->shortpkt); + JU(martian, base->martian); + JU_NC(q, base->activeqry); + + fprintf(fh, " }\n"); + if (qry->out_filename) + fclose (fh); + // reuse timeval now + now.tv_sec = DEFAULT_STATS_REPORT_INTERVEL; + now.tv_usec = 0; + event_add(&tdig_base->statsReportEvent, &now); +} + + +static void ChangetoDnsNameFormat(u_char * dns, char* qry) +{ + int lock = 0, i; + + for(i = 0 ; i < (int)strlen((char*)qry) ; i++) + { + //printf ("%c", qry[i] ); + if(qry[i]=='.') + { + *dns++=i-lock; + for(;lockserver_name); + + if(qry->err.size) + { + buf_cleanup(&qry->err); + } + if(qry->qbuf.size) + buf_cleanup(&qry->qbuf); + + if(qry->ressave && qry->opt_evdns) { + evutil_freeaddrinfo(qry->ressave); + qry->ressave = NULL; + qry->ressent = NULL; + } + else if (qry->ressave ) + { + freeaddrinfo(qry->ressave); + qry->ressave = NULL; + qry->ressent = NULL; + } + qry->qst = STATUS_FREE; + qry->wire_size = 0; + + if(qry->packet.size) + { + buf_cleanup(&qry->packet); + } + + if(qry->opt_proto == 6) + tu_cleanup(&qry->tu_env); + + if ( qry->opt_resolv_conf > Q_RESOLV_CONF ) { + // this loop goes over servers in /etc/resolv.conf + // select the next server and restart + if(qry->opt_resolv_conf < tdig_base->resolv_max) { + free (qry->server_name); + qry->server_name = strdup(tdig_base->nslist[qry->opt_resolv_conf]); + qry->opt_resolv_conf++; + qry->qst = STATUS_NEXT_QUERY; + evtimer_add(&qry->next_qry_timer, &asap); + return; + } + else + qry->opt_resolv_conf++; + } + + if(qry->base->done) + { + qry->base->done(qry); + /* + void (*terminator)(void *state); + struct event_base *event_base; + struct tdig_base *tbase; + terminator = qry->base->done; + event_base = qry->base->event_base; + if(DnsBase) { + evdns_base_free(DnsBase, 0); + DnsBase = NULL; + } + tbase = qry->base; + tdig_delete(qry); + free(tbase); + event_base_loopbreak(event_base); + event_base_free(event_base); + terminator(qry); + */ + } + +} + + +static int tdig_delete(void *state) +{ + struct query_state *qry; + + qry = state; + + if (qry->qst ) + return 0; + + if(qry->out_filename) + { + free(qry->out_filename); + qry->out_filename = NULL ; + } + if(qry->lookupname) + { + free(qry->lookupname); + qry->lookupname = NULL; + } + + /* Delete timers */ + evtimer_del(&qry->noreply_timer); + evtimer_del(&qry->nsm_timer); + + if((qry->next == qry->prev) && (qry->next == qry)) { + qry->base->qry_head = NULL; + crondlog(LVL7 "deleted last query qry %s", qry->str_Atlas); + } + else { +#if ENABLE_FEATURE_EVTDIG_DEBUG + crondlog(LVL7 "deleted qry %s qry->prev %s qry->next %s qry_head %s", qry->str_Atlas, qry->prev->str_Atlas, qry->next->str_Atlas, qry->base->qry_head->str_Atlas); + crondlog(LVL7 "old qry->next->prev %s qry->prev->next %s", qry->next->prev->str_Atlas, qry->prev->next->str_Atlas); +#endif + if(qry->next) + qry->next->prev = qry->prev; + if(qry->prev) + qry->prev->next = qry->next; + if(qry->base && qry->base->qry_head == qry) + qry->base->qry_head = qry->next; + +#if ENABLE_FEATURE_EVTDIG_DEBUG + crondlog(LVL7 "new qry->next->prev %s qry->prev->next %s", qry->next->prev->str_Atlas, qry->prev->next->str_Atlas); +#endif + } + if( qry->str_Atlas) + { + free( qry->str_Atlas); + qry->str_Atlas = NULL; + } + if(qry->server_name) + { + free(qry->server_name); + qry->server_name = NULL; + } + if(qry->base) + qry->base->activeqry--; + free(qry); + qry = NULL; + return 1; +} + +void printErrorQuick (struct query_state *qry) +{ + FILE *fh; + struct timeval now; + if (qry->out_filename) + { + fh= fopen(qry->out_filename, "a"); + if (!fh) + crondlog(DIE9 "unable to append to '%s'", + qry->out_filename); + } + else + fh = stdout; + + fprintf(fh, "RESULT { "); + if(qry->str_Atlas) + { + JS(id, qry->str_Atlas); + } + gettimeofday(&now, NULL); + JS1(time, %ld, now.tv_sec); + + snprintf(line, DEFAULT_LINE_LENGTH, "\"query busy\": \"too frequent. previous one is not done yet\""); + fprintf(fh, "\"error\" : { %s }" , line); + + fprintf(fh, " }"); + fprintf(fh, "\n"); + if (qry->out_filename) + fclose(fh); +} + + +void printReply(struct query_state *qry, int wire_size, unsigned char *result ) +{ + int i, stop=0; + unsigned char *qname, *reader; + struct DNS_HEADER *dnsR = NULL; + struct RES_RECORD answers[20]; //the replies from the DNS server + void *ptr = NULL; + char addrstr[100]; + FILE *fh; + //char buf[INET6_ADDRSTRLEN]; + u_int32_t serial; + struct buf tmpbuf; + char str[4]; + int iMax ; + int flagAnswer = 1; + int data_len; + + if (qry->out_filename) + { + fh= fopen(qry->out_filename, "a"); + if (!fh) + crondlog(DIE9 "unable to append to '%s'", + qry->out_filename); + } + else + fh = stdout; + + fprintf(fh, "RESULT { "); + if(qry->str_Atlas) + { + JS(id, qry->str_Atlas); + } + JS1(time, %ld, qry->xmit_time.tv_sec); + if ( qry->opt_resolv_conf > Q_RESOLV_CONF ) { + JD (subid, qry->opt_resolv_conf); + JD (submax, qry->base->resolv_max); + } + if( qry->ressent) + { // started to send query + // historic resaons only works with UDP + switch (qry->ressent->ai_family) + { + case AF_INET: + ptr = &((struct sockaddr_in *) qry->ressent->ai_addr)->sin_addr; + break; + case AF_INET6: + ptr = &((struct sockaddr_in6 *) qry->ressent->ai_addr)->sin6_addr; + break; + } + inet_ntop (qry->ressent->ai_family, ptr, addrstr, 100); + if(strcmp(addrstr, qry->server_name)) { + JS(name, qry->server_name); + } + JS(dst_addr, addrstr); + JD(af, qry->ressent->ai_family == PF_INET6 ? 6 : 4); + } + else if(qry->dst_ai_family) + { + if(strcmp(qry->dst_addr_str, qry->server_name)) { + JS(dst_name, qry->server_name); + } + JS(dst_addr , qry->dst_addr_str); + JD(af, qry->dst_ai_family == PF_INET6 ? 6 : 4); + } + else { + JS(dst_name, qry->server_name); + } + if(qry->loc_sin6.sin6_family) { + line[0] = '\0'; + getnameinfo((struct sockaddr *)&qry->loc_sin6, + qry->loc_socklen, line, sizeof(line), + NULL, 0, NI_NUMERICHOST); + if(strlen(line)) + JS(src_addr, line); + } + + JS_NC(proto, qry->opt_proto == 6 ? "TCP" : "UDP" ); + if(qry->opt_qbuf && qry->qbuf.size) { + str[0] = '\0'; + buf_add(&qry->qbuf, str, 1); + JC; + JS_NC(qbuf, qry->qbuf.buf ); + } + + + if(result) + { + dnsR = (struct DNS_HEADER*) result; + + //point to the query portion + qname =(unsigned char*)&result[sizeof(struct DNS_HEADER)]; + + //move ahead of the dns header and the query field + reader = &result[sizeof(struct DNS_HEADER) + (strlen((const char*)qname)+1) + sizeof(struct QUESTION)]; + + fprintf (fh, ", \"result\" : { "); + fprintf (fh, " \"rt\" : %.3f", qry->triptime); + fprintf (fh, " , \"size\" : %d", wire_size); + fprintf (fh, " , \"ID\" : %d", ntohs(dnsR->id)); + /* + fprintf (fh, " , \"RCODE\" : %d", dnsR->rcode); + fprintf (fh, " , \"AA\" : %d", dnsR->aa); + fprintf (fh, " , \"TC\" : %d", dnsR->tc); + */ + fprintf (fh, " , \"ANCOUNT\" : %d ", ntohs(dnsR->ans_count )); + fprintf (fh, " , \"QDCOUNT\" : %u ",ntohs(dnsR->q_count)); + fprintf (fh, " , \"NSCOUNT\" : %d" , ntohs(dnsR->ns_count)); + fprintf (fh, " , \"ARCOUNT\" : %d ",ntohs(dnsR->add_count)); + + str[0] = '\0'; + if(qry->opt_abuf) { + JC; + buf_init(&tmpbuf, -1); + buf_add_b64(&tmpbuf, result, wire_size, 0); + buf_add(&tmpbuf, str, 1); + JS_NC(abuf, tmpbuf.buf ); + buf_cleanup(&tmpbuf); + } + + stop=0; + iMax = 0; + + if (dnsR->ans_count > 0) + { + iMax = MIN(2, ntohs(dnsR->ans_count)); + + for(i=0;itype)==T_TXT) //txt + { + answers[i].rdata = NULL; + data_len = ntohs(answers[i].resource->data_len) - 1; + + if(flagAnswer) { + fprintf (fh, ", \"answers\" : [ "); + flagAnswer = 0; + } + if (flagAnswer == 0) { + if(i > 0) + fprintf(fh, ", "); + fprintf(fh, " { "); + } + fprintf(fh, " \"TYPE\" : \"TXT\""); + fprintf(fh, " , \"NAME\" : \"%s.\" ",answers[i].name); + print_txt_json(&result[reader-result+1], data_len, fh); + reader = reader + ntohs(answers[i].resource->data_len); + if(flagAnswer == 0) + fprintf(fh, " } "); + + } + else if (ntohs(answers[i].resource->type)== T_SOA) + { + if(flagAnswer) { + fprintf (fh, ", \"answers\" : [ "); + flagAnswer = 0; + } + if (flagAnswer == 0) { + if(i > 0) + fprintf(fh, ", "); + fprintf(fh, " { "); + } + + + JS(TYPE, "SOA"); + JSDOT(NAME, answers[i].name); + JU(TTL, ntohl(answers[i].resource->ttl)); + answers[i].rdata = ReadName( + result,wire_size, + reader-result,&stop); + JSDOT( MNAME, answers[i].rdata); + reader = reader + stop; + free(answers[i].rdata); + answers[i].rdata = ReadName( + result,wire_size, + reader-result,&stop); + JSDOT( RNAME, answers[i].rdata); + reader = reader + stop; + serial = get32b(reader); + JU_NC(SERIAL, serial); + reader = reader + 4; + reader = reader + 16; // skip REFRESH, RETRY, EXIPIRE, and MINIMUM + if(flagAnswer == 0) + fprintf(fh, " } "); + } + else + { + // JU(TYPE, ntohs(answers[i].resource->type)); + // JU_NC(RDLENGTH, ntohs(answers[i].resource->data_len)) + reader = reader + ntohs(answers[i].resource->data_len); + } + + fflush(fh); + // free mem + if(answers[i].rdata != NULL) + free (answers[i].rdata); + } + if(flagAnswer == 0) + fprintf (fh, " ]"); + } + + for(i=0;ierr.size) + { + line[0] = '\0'; + buf_add(&qry->err, line, 1 ); + fprintf(fh, ", \"error\" : { %s }" , qry->err.buf); + } + fprintf(fh, " }"); + fprintf(fh, "\n"); + if (qry->out_filename) + fclose(fh); + free_qry_inst(qry); +} + +unsigned char* ReadName(unsigned char *base, size_t size, size_t offset, + int* count) +{ + unsigned char *name; + unsigned int p=0,jumped=0, len; + + *count = 0; + name = (unsigned char*)malloc(256); + + name[0]= '\0'; + + //read the names in 3www6google3com format + while(len= base[offset], len !=0) + { + if (len & 0xc0) + { + if ((len & 0xc0) != 0xc0) + { + /* Bad format */ + strcpy(name, "format-error"); + printf("format-error: len = %d\n", + len); + abort(); + return name; + } + + offset= ((len & ~0xc0) << 8) | base[offset+1]; + if (offset >= size) + { + strcpy(name, "offset-error"); + printf("offset-error\n"); + abort(); + return name; + } + if(jumped==0) + { + /* if we havent jumped to another location + * then we can count up + */ + *count += 2; + } + jumped= 1; + continue; + } + if (offset+len+1 > size) + { + strcpy(name, "buf-bounds-error"); + printf("buf-bounds-error\n"); + abort(); + return name; + } + + if (p+len+1 > 255) + { + strcpy(name, "name-length-error"); + printf("name-length-error\n"); + abort(); + return name; + } + memcpy(name+p, base+offset+1, len); + name[p+len]= '.'; + p += len+1; + offset += len+1; + + if(jumped==0) + { + /* if we havent jumped to another location then we + * can count up + */ + *count += len+1; + } + } + + if (!jumped) + (*count)++; /* Terminating zero length */ + + name[p]= '\0'; //string complete + + if(p > 0) + name[p-1]= '\0'; //remove the last dot + return name; +} + +/* get 4 bytes from memory + * eg. used to extract serial number from soa packet + */ + u_int32_t +get32b (char *p) +{ + u_int32_t var; + + var = (0x000000ff & *(p)) << 24; + var |= (0x000000ff & *(p+1)) << 16; + var |= (0x000000ff & *(p+2)) << 8; + var |= (0x000000ff & *(p+3)); + + return (var); +} + +/* + * Copy data allowing for unaligned accesses in network byte order + * (big endian). + */ +void ldns_write_uint16(void *dst, uint16_t data) +{ +#ifdef ALLOW_UNALIGNED_ACCESSES + * (uint16_t *) dst = htons(data); +#else + uint8_t *p = (uint8_t *) dst; + p[0] = (uint8_t) ((data >> 8) & 0xff); + p[1] = (uint8_t) (data & 0xff); +#endif +} + +uint16_t +ldns_read_uint16(const void *src) +{ +#ifdef ALLOW_UNALIGNED_ACCESSES + return ntohs(*(uint16_t *) src); +#else + uint8_t *p = (uint8_t *) src; + return ((uint16_t) p[0] << 8) | (uint16_t) p[1]; +#endif +} + +struct testops tdig_ops = { tdig_init, tdig_start, tdig_delete }; diff --git a/eperd/evtraceroute.c b/eperd/evtraceroute.c new file mode 100644 index 0000000..d5e0d36 --- /dev/null +++ b/eperd/evtraceroute.c @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2013 RIPE NCC + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * Standalone version of the event-based traceroute. + */ + +#include "libbb.h" +#include +#include +#include + +#include "eperd.h" + +static void done(void *state UNUSED_PARAM) +{ + fprintf(stderr, "And we are done\n"); + exit(0); +} + +int evtraceroute_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int evtraceroute_main(int argc UNUSED_PARAM, char **argv) +{ + int r; + void *state; + + /* Create libevent event base */ + EventBase= event_base_new(); + if (!EventBase) + { + fprintf(stderr, "evtraceroute: event_base_new failed\n"); + exit(1); + } + DnsBase= evdns_base_new(EventBase, 1 /*initialize*/); + if (!DnsBase) + { + fprintf(stderr, "evdns_base_new failed\n"); + exit(1); + } + + state= traceroute_ops.init(argc, argv, done); + if (!state) + { + fprintf(stderr, "evtraceroute: traceroute_ops.init failed\n"); + exit(1); + } + traceroute_ops.start(state); + + r= event_base_loop(EventBase, 0); + if (r != 0) + { + fprintf(stderr, "evtraceroute: event_base_loop failed\n"); + exit(1); + } + return 0; /* not reached */ +} + diff --git a/eperd/httpget.c b/eperd/httpget.c new file mode 100644 index 0000000..c380f18 --- /dev/null +++ b/eperd/httpget.c @@ -0,0 +1,1760 @@ +/* + * Copyright (c) 2013 RIPE NCC + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * httpget.c -- libevent-based version of httpget + */ + +#include "libbb.h" +#include +#include +#include +#include +#include +#include +#include + +#include "eperd.h" +#include "tcputil.h" + +#define SAFE_PREFIX_IN ATLAS_DATA_OUT +#define SAFE_PREFIX_OUT ATLAS_DATA_NEW + +#define CONN_TO 5 + +#define ENV2STATE(env) \ + ((struct hgstate *)((char *)env - offsetof(struct hgstate, tu_env))) + +#define DBQ(str) "\"" #str "\"" + +#define MAX_LINE_LEN 2048 /* We don't deal with lines longer than this */ +#define POST_BUF_SIZE 2048 /* Big enough to be efficient? */ + +static struct option longopts[]= +{ + { "all", no_argument, NULL, 'a' }, + { "combine", no_argument, NULL, 'c' }, + { "get", no_argument, NULL, 'g' }, + { "head", no_argument, NULL, 'E' }, + { "post", no_argument, NULL, 'P' }, + { "post-file", required_argument, NULL, 'p' }, + { "post-header", required_argument, NULL, 'h' }, + { "post-footer", required_argument, NULL, 'f' }, + { "store-headers", required_argument, NULL, 'H' }, + { "store-body", required_argument, NULL, 'B' }, + { "user-agent", required_argument, NULL, 'u' }, + { NULL, } +}; + +enum readstate { READ_STATUS, READ_HEADER, READ_BODY, READ_SIMPLE, + READ_CHUNKED, READ_CHUNK_BODY, READ_CHUNK_END, READ_CHUNKED_TRAILER, + READ_DONE }; +enum writestate { WRITE_HEADER, WRITE_POST_HEADER, WRITE_POST_FILE, + WRITE_POST_FOOTER, WRITE_DONE }; + +struct hgbase +{ + struct event_base *event_base; + + struct hgstate **table; + int tabsiz; + + /* For standalone httpget. Called when a httpget instance is + * done. Just one pointer for all instances. It is up to the caller + * to keep it consistent. + */ + void (*done)(void *state); +}; + +struct hgstate +{ + /* Parameters */ + char *output_file; + char *atlas; + char do_all; + char do_combine; + char only_v4; + char only_v6; + char do_get; + char do_head; + char do_post; + char do_http10; + char *user_agent; + char *post_header; + char *post_file; + char *post_footer; + int max_headers; + int max_body; + + /* State */ + char busy; + struct tu_env tu_env; + char dnserr; + char connecting; + char *host; + char *port; + char *hostport; + char *path; + struct bufferevent *bev; + enum readstate readstate; + enum writestate writestate; + int http_result; + char res_major; + char res_minor; + int headers_size; + int tot_headers; + int chunked; + int tot_chunked; + int content_length; + int content_offset; + int subid; + int submax; + time_t gstart; + struct timeval start; + double resptime; + FILE *post_fh; + char *post_buf; + + char *line; + size_t linemax; /* Allocated size of line */ + size_t linelen; /* Current amount of data in line */ + size_t lineoffset; /* Offset in line where to start processing */ + + /* Base and index in table */ + struct hgbase *base; + int index; + + struct sockaddr_in6 sin6; + socklen_t socklen; + struct sockaddr_in6 loc_sin6; + socklen_t loc_socklen; + + char *result; + size_t reslen; + size_t resmax; +}; + +static struct hgbase *hg_base; + +static void report(struct hgstate *state); +static void add_str(struct hgstate *state, const char *str); +static void add_str_quoted(struct hgstate *state, char *str); + +static struct hgbase *httpget_base_new(struct event_base *event_base) +{ + struct hgbase *base; + + base= xzalloc(sizeof(*base)); + + base->event_base= event_base; + + base->tabsiz= 10; + base->table= xzalloc(base->tabsiz * sizeof(*base->table)); + + return base; +} + +static int parse_url(char *url, char **hostp, char **portp, char **hostportp, + char **pathp) +{ + char *item; + const char *cp, *np, *prefix; + size_t len; + + *hostp= NULL; + *portp= NULL; + *hostportp= NULL; + *pathp= NULL; + + /* the url must start with 'http://' */ + prefix= "http://"; + len= strlen(prefix); + if (strncasecmp(prefix, url, len) != 0) + { + crondlog(LVL8 "bad prefix in url '%s'", url); + goto fail; + } + + cp= url+len; + + /* Get hostport part */ + np= strchr(cp, '/'); + if (np != NULL) + len= np-cp; + else + { + len= strlen(cp); + np= cp+len; + } + if (len == 0) + { + crondlog(LVL8 "missing host part in url '%s'", url); + return 0; + } + item= xmalloc(len+1); + memcpy(item, cp, len); + item[len]= '\0'; + *hostportp= item; + + /* The remainder is the path */ + cp= np; + if (cp[0] == '\0') + cp= "/"; + len= strlen(cp); + item= xmalloc(len+1); + memcpy(item, cp, len); + item[len]= '\0'; + *pathp= item; + + /* Extract the host name from hostport */ + cp= *hostportp; + np= cp; + if (cp[0] == '[') + { + /* IPv6 address literal */ + np= strchr(cp, ']'); + if (np == NULL || np == cp+1) + { + crondlog(LVL8 + "malformed IPv6 address literal in url '%s'", + url); + goto fail; + } + } + + np= strchr(np, ':'); + if (np != NULL) + len= np-cp; + else + { + len= strlen(cp); + np= cp+len; + } + if (len == 0) + { + crondlog(LVL8 "missing host part in url '%s'", url); + goto fail; + } + item= xmalloc(len+1); + if (cp[0] == '[') + { + /* Leave out the square brackets */ + memcpy(item, cp+1, len-2); + item[len-2]= '\0'; + } + else + { + memcpy(item, cp, len); + item[len]= '\0'; + } + *hostp= item; + + /* Port */ + cp= np; + if (cp[0] == '\0') + cp= "80"; + else + cp++; + len= strlen(cp); + item= xmalloc(len+1); + memcpy(item, cp, len); + item[len]= '\0'; + *portp= item; + + return 1; + +fail: + if (*hostp) + { + free(*hostp); + *hostp= NULL; + } + if (*portp) + { + free(*portp); + *portp= NULL; + } + if (*hostportp) + { + free(*hostportp); + *hostportp= NULL; + } + if (*pathp) + { + free(*pathp); + *pathp= NULL; + } + return 0; +} + +static void timeout_callback(int __attribute((unused)) unused, + const short __attribute((unused)) event, void *s) +{ + struct hgstate *state; + + state= ENV2STATE(s); + + if (state->connecting) + { + add_str(state, DBQ(err) ":" DBQ(connect: timeout) ", "); + if (state->do_all) + report(state); + else + tu_restart_connect(&state->tu_env); + return; + } + switch(state->readstate) + { + case READ_STATUS: + add_str(state, DBQ(err) ":" DBQ(timeout reading status) ", "); + report(state); + break; + case READ_HEADER: + if (state->max_headers) + add_str(s, " ], "); + add_str(state, ", " DBQ(err) ":" DBQ(timeout reading headers)); + report(state); + break; + case READ_SIMPLE: +#if 0 /* Enable when adding storing bodies */ + if (state->max_body) + add_str(s, " ]"); +#endif + add_str(state, DBQ(err) ":" DBQ(timeout reading body) ", "); + report(state); + break; + case READ_CHUNKED: + case READ_CHUNK_BODY: +#if 0 /* Enable when adding storing bodies */ + if (state->max_body) + add_str(s, " ]"); +#endif + add_str(state, DBQ(err) ":" DBQ(timeout reading chunk) ", "); + report(state); + break; + default: + printf("in timeout_callback, unhandled cased: %d\n", + state->readstate); + } +} + +static void *httpget_init(int __attribute((unused)) argc, char *argv[], + void (*done)(void *state)) +{ + int c, i, do_combine, do_get, do_head, do_post, + max_headers, max_body, only_v4, only_v6, + do_all, do_http10; + size_t newsiz; + char *url, *check; + char *post_file, *output_file, *post_footer, *post_header, + *A_arg, *store_headers, *store_body; + const char *user_agent; + char *host, *port, *hostport, *path; + struct hgstate *state; + FILE *fh; + + /* Arguments */ + do_http10= 0; + do_all= 0; + do_combine= 0; + do_get= 1; + do_head= 0; + do_post= 0; + post_file= NULL; + post_footer=NULL; + post_header=NULL; + output_file= NULL; + store_headers= NULL; + store_body= NULL; + A_arg= NULL; + only_v4= 0; + only_v6= 0; + user_agent= "httpget for atlas.ripe.net"; + + if (!hg_base) + { + hg_base= httpget_base_new(EventBase); + if (!hg_base) + crondlog(DIE9 "httpget_base_new failed"); + } + + + /* Allow us to be called directly by another program in busybox */ + optind= 0; + while (c= getopt_long(argc, argv, "01aA:cO:46", longopts, NULL), c != -1) + { + switch(c) + { + case '0': + do_http10= 1; + break; + case '1': + do_http10= 0; + break; + case 'a': /* --all */ + do_all= 1; + break; + case 'A': + A_arg= optarg; + break; + case 'c': /* --combine */ + do_combine= 1; + break; + case 'O': + output_file= optarg; + break; + case 'g': /* --get */ + do_get = 1; + do_head = 0; + do_post = 0; + break; + case 'E': /* --head */ + do_get = 0; + do_head = 1; + do_post = 0; + break; + case 'P': /* --post */ + do_get = 0; + do_head = 0; + do_post = 1; + break; + case 'h': /* --post-header */ + post_header= optarg; + break; + case 'f': /* --post-footer */ + post_footer= optarg; + break; + case 'p': /* --post-file */ + post_file= optarg; + break; + case 'H': /* --store-headers */ + store_headers= optarg; + break; + case 'B': /* --store-body */ + store_body= optarg; + break; + case '4': + only_v4= 1; + only_v6= 0; + break; + case '6': + only_v6= 1; + only_v4= 0; + break; + case 'u': /* --user-agent */ + user_agent= optarg; + break; + default: + crondlog(LVL8 "bad option '%c'", c); + return NULL; + } + } + + if (optind != argc-1) + { + crondlog(LVL8 "exactly one url expected"); + return NULL; + } + url= argv[optind]; + + if (output_file) + { + if (!validate_filename(output_file, SAFE_PREFIX_OUT)) + { + crondlog(LVL8 "insecure file '%s'", output_file); + return NULL; + } + fh= fopen(output_file, "a"); + if (!fh) + { + crondlog(LVL8 "unable to append to '%s'", + output_file); + return NULL; + } + fclose(fh); + } + if (post_header && !validate_filename(post_header, SAFE_PREFIX_IN)) + { + crondlog(LVL8 "insecure file '%s'", post_header); + return NULL; + } + if (post_file && !validate_filename(post_file, SAFE_PREFIX_IN)) + { + crondlog(LVL8 "insecure file '%s'", post_file); + return NULL; + } + if (post_footer && !validate_filename(post_footer, SAFE_PREFIX_IN)) + { + crondlog(LVL8 "insecure file '%s'", post_footer); + return NULL; + } + + max_headers= 0; + max_body= UINT_MAX; /* default is to write out the entire body */ + + if (store_headers) + { + max_headers= strtoul(store_headers, &check, 10); + if (check[0] != '\0') + { + crondlog(LVL8 + "unable to parse argument (--store-headers) '%s'", + store_headers); + return NULL; + } + } + + if (store_body) + { + max_body= strtoul(store_body, &check, 10); + if (check[0] != '\0') + { + crondlog(LVL8 + "unable to parse argument (--store-body) '%s'", + store_body); + return NULL; + } + } + + if (!parse_url(url, &host, &port, &hostport, &path)) + { + /* Do we need to report an error? */ + return NULL; + } + + //printf("host: %s\n", host); + //printf("port: %s\n", port); + //printf("hostport: %s\n", hostport); + //printf("path: %s\n", path); + + state= xzalloc(sizeof(*state)); + state->base= hg_base; + state->atlas= A_arg ? strdup(A_arg) : NULL; + state->output_file= output_file ? strdup(output_file) : NULL; + state->host= host; + state->port= port; + state->hostport= hostport; + state->path= path; + state->do_all= do_all; + state->do_combine= !!do_combine; + state->do_get= do_get; + state->do_head= do_head; + state->do_post= do_post; + state->post_header= post_header ? strdup(post_header) : NULL; + state->post_file= post_file ? strdup(post_file) : NULL; + state->post_footer= post_footer ? strdup(post_footer) : NULL; + state->do_http10= do_http10; + state->user_agent= user_agent ? strdup(user_agent) : NULL; + state->max_headers= max_headers; + state->max_body= max_body; + + state->only_v4= 2; + + state->only_v4= !!only_v4; /* Gcc bug? */ + state->only_v6= !!only_v6; + + //evtimer_assign(&state->timer, state->base->event_base, + // timeout_callback, state); + + state->line= NULL; + state->linemax= 0; + state->linelen= 0; + state->lineoffset= 0; + + for (i= 0; itabsiz; i++) + { + if (hg_base->table[i] == NULL) + break; + } + if (i >= hg_base->tabsiz) + { + newsiz= 2*hg_base->tabsiz; + hg_base->table= xrealloc(hg_base->table, + newsiz*sizeof(*hg_base->table)); + for (i= hg_base->tabsiz; itable[i]= NULL; + i= hg_base->tabsiz; + hg_base->tabsiz= newsiz; + } + state->index= i; + hg_base->table[i]= state; + hg_base->done= done; + + return state; +} + +static void report(struct hgstate *state) +{ + int done, do_output; + FILE *fh; + char namebuf[NI_MAXHOST]; + char line[160]; + + //event_del(&state->timer); + + state->subid++; + + do_output= 1; + if (state->do_all && state->do_combine && state->subidsubmax) + { + do_output= 0; + } + + fh= NULL; + if (do_output) + { + if (state->output_file) + { + fh= fopen(state->output_file, "a"); + if (!fh) + crondlog(DIE9 "unable to append to '%s'", + state->output_file); + } + else + fh= stdout; + + fprintf(fh, "RESULT { "); + if (state->atlas) + { + fprintf(fh, DBQ(id) ":" DBQ(%s) ", " + DBQ(fw) ":%d, " + DBQ(time) ":%ld, ", + state->atlas, get_atlas_fw_version(), + state->gstart); + } + fprintf(fh, DBQ(result) ":[ "); + } + + if (state->do_all && !state->dnserr) + { + if (state->do_combine) + { + snprintf(line, sizeof(line), DBQ(time) ":%ld, ", + state->start.tv_sec); + } + else + { + snprintf(line, sizeof(line), DBQ(subid) ":%d, " + DBQ(submax) ":%d, ", + state->subid, state->submax); + } + add_str(state, line); + } + + if (!state->dnserr) + { + snprintf(line, sizeof(line), + DBQ(method) ":" DBQ(%s) ", " DBQ(af) ": %d", + state->do_get ? "GET" : state->do_head ? "HEAD" : + "POST", + state->sin6.sin6_family == AF_INET6 ? 6 : 4); + add_str(state, line); + + getnameinfo((struct sockaddr *)&state->sin6, state->socklen, + namebuf, sizeof(namebuf), NULL, 0, NI_NUMERICHOST); + + snprintf(line, sizeof(line), ", " DBQ(dst_addr) ":" DBQ(%s), + namebuf); + add_str(state, line); + } + + if (!state->connecting && !state->dnserr) + { + namebuf[0]= '\0'; + getnameinfo((struct sockaddr *)&state->loc_sin6, + state->loc_socklen, namebuf, sizeof(namebuf), + NULL, 0, NI_NUMERICHOST); + + snprintf(line, sizeof(line), ", " DBQ(src_addr) ":" DBQ(%s), + namebuf); + add_str(state, line); + } + + done= (state->readstate == READ_DONE); + if (done) + { + snprintf(line, sizeof(line), + ", " DBQ(rt) ":%f" + ", " DBQ(res) ":%d" + ", " DBQ(ver) ":" DBQ(%d.%d) + ", " DBQ(hsize) ":%d" + ", " DBQ(bsize) ":%d", + state->resptime, + state->http_result, + state->res_major, state->res_minor, + state->headers_size, + state->content_offset); + add_str(state, line); + } + + if (!state->dnserr) + { + add_str(state, " }"); + } + if (!do_output) + add_str(state, ", "); + else + add_str(state, " ]"); + + if (do_output) + { + fprintf(fh, "%s }\n", state->result); + free(state->result); + state->result= NULL; + state->resmax= 0; + state->reslen= 0; + + if (state->output_file) + fclose(fh); + } + + free(state->post_buf); + state->post_buf= NULL; + + if (state->do_all && state->subid < state->submax) + { + tu_restart_connect(&state->tu_env); + return; + } + if (state->linemax) + { + state->linemax= 0; + free(state->line); + state->line= NULL; + } + + state->bev= NULL; + + tu_cleanup(&state->tu_env); + + if (state->base->done) + state->base->done(state); + state->busy= 0; +} + +static int get_input(struct hgstate *state) +{ + int n; + + /* Assume that we always end up with a full buffer anyway */ + if (state->linemax == 0) + { + if (state->line) + crondlog(DIE9 "line is not empty"); + + state->linemax= MAX_LINE_LEN; + state->line= xmalloc(state->linemax); + } + + if (state->lineoffset) + { + if (state->linelen > state->lineoffset) + { + memmove(state->line, &state->line[state->lineoffset], + state->linelen-state->lineoffset); + state->linelen -= state->lineoffset; + } + else + { + state->linelen= 0; + } + state->lineoffset= 0; + } + if (state->linelen >= state->linemax) + { + return -1; /* We cannot get more data */ + } + + n= bufferevent_read(state->bev, + &state->line[state->linelen], + state->linemax-state->linelen); + if (n < 0) + return -1; + state->linelen += n; + return 0; +} + +static void skip_spaces(const char *cp, char **ncp) +{ + const unsigned char *ucp; + + ucp= (const unsigned char *)cp; + while (ucp[0] != '\0' && isspace(ucp[0])) + ucp++; + *ncp= (char *)ucp; +} + +static void add_str(struct hgstate *state, const char *str) +{ + size_t len; + + len= strlen(str); + if (state->reslen + len+1 > state->resmax) + { + state->resmax= state->reslen + len+1 + 80; + state->result= xrealloc(state->result, state->resmax); + } + memcpy(state->result+state->reslen, str, len+1); + state->reslen += len; + //printf("add_str: result = '%s'\n", state->result); +} + +static void add_str_quoted(struct hgstate *state, char *str) +{ + char c; + char *p; + char buf[20]; + + for (p= str; *p; p++) + { + c= *p; + if (c == '"' || c == '\\') + snprintf(buf, sizeof(buf), "\\%c", c); + else if (isprint((unsigned char)c)) + { + buf[0]= c; + buf[1]= '\0'; + } + else + { + snprintf(buf, sizeof(buf), "\\u%04x", + (unsigned char)c); + } + add_str(state, buf); + } +} + +static void err_status(struct hgstate *state, const char *reason) +{ + char line[80]; + snprintf(line, sizeof(line), + DBQ(err) ":" DBQ(bad status line: %s) ", ", + reason); + add_str(state, line); + report(state); +} + +static void err_header(struct hgstate *state, const char *reason) +{ + char line[80]; + if (state->max_headers != 0) + add_str(state, " ], "); + snprintf(line, sizeof(line), + DBQ(err) ":" DBQ(bad header line: %s) ", ", reason); + add_str(state, line); + report(state); +} + +static void err_chunked(struct hgstate *state, const char *reason) +{ + char line[80]; + snprintf(line, sizeof(line), DBQ(err) ":" DBQ(bad chunk line: %s) ", ", + reason); + add_str(state, line); + report(state); +} + +static void readcb(struct bufferevent *bev UNUSED_PARAM, void *ptr) +{ + int r, major, minor, need_line, no_body; + size_t len; + char *cp, *ncp, *check, *line; + const char *prefix, *kw; + struct hgstate *state; + struct timeval endtime; + + state= ENV2STATE(ptr); + + for (;;) + { + switch(state->readstate) + { + case READ_STATUS: + case READ_HEADER: + case READ_CHUNKED: + case READ_CHUNK_END: + case READ_CHUNKED_TRAILER: + need_line= 1; + break; + default: + need_line= 0; + break; + } + + if (need_line) + { + /* Wait for a complete line */ + if (state->linemax == 0 || + memchr(&state->line[state->lineoffset], '\n', + state->linelen-state->lineoffset) == NULL) + { + r= get_input(state); + if (r == -1) + { + printf( + "readcb: get_input failed, should do something\n"); + return; + } + + /* Did we get what we want? */ + if (memchr(&state->line[state->lineoffset], + '\n', state->linelen-state->lineoffset) + == NULL) + { + /* No */ + if (state->linelen-state->lineoffset >= + MAX_LINE_LEN) + { + add_str(state, DBQ(err) ":" + DBQ(line too long) + ", "); + report(state); + } + return; + } + } + } + + switch(state->readstate) + { + case READ_STATUS: + line= &state->line[state->lineoffset]; + cp= strchr(line, '\n'); + if (cp == NULL) + { + /* Contains nul */ + err_status(state, "contains nul"); + return; + } + + state->lineoffset += (cp-line+1); + + cp[0]= '\0'; + if (cp > line && cp[-1] == '\r') + cp[-1]= '\0'; + + /* Check http version */ + prefix= "http/"; + len= strlen(prefix); + if (strncasecmp(prefix, line, len) != 0) + { + err_status(state, "bad prefix"); + return; + } + cp= line+len; + + major= strtoul(cp, &check, 10); + if (check == cp || check[0] != '.') + { + err_status(state, "bad major"); + return; + } + + cp= check+1; + minor= strtoul(cp, &check, 10); + if (check == cp || check[0] == '\0' || + !isspace(*(unsigned char *)check)) + { + err_status(state, "bad minor"); + return; + } + + skip_spaces(check, &cp); + + if (!isdigit(*(unsigned char *)cp)) + { + err_status(state, "bad status code"); + return; + } + state->http_result= strtoul(cp, NULL, 10); + state->res_major= major; + state->res_minor= minor; + + state->readstate= READ_HEADER; + state->content_length= -1; + + if (state->max_headers) + { + add_str(state, DBQ(header) ": ["); + } + + continue; + + case READ_HEADER: + line= &state->line[state->lineoffset]; + cp= strchr(line, '\n'); + if (cp == NULL) + { + err_header(state, "contains nul"); + return; + } + + len= (cp-line+1); + state->lineoffset += len; + + cp[0]= '\0'; + if (cp > line && cp[-1] == '\r') + cp[-1]= '\0'; + + if (line[0] == '\0') + { + if (state->tot_headers <= state->max_headers && + state->max_headers != 0) + { + if (state->tot_headers != 0) + add_str(state, ","); + add_str(state, " \"\""); + } + if (state->max_headers) + add_str(state, " ], "); + state->readstate= READ_BODY; + continue; + } + + state->headers_size += len; + + len= strlen(line); + if (state->tot_headers+len+1 <= state->max_headers) + { + if (state->tot_headers != 0) + add_str(state, ","); + add_str(state, " \""); + add_str_quoted(state, line); + add_str(state, "\""); + state->tot_headers += len; + } else if (state->tot_headers <= state->max_headers && + state->max_headers != 0) + { + /* Fill up remaining space and report + * truncation */ + if (state->tot_headers != 0) + add_str(state, ","); + add_str(state, " \""); + if (state->tot_headers < state->max_headers) + { + line[state->max_headers- + state->tot_headers]= '\0'; + add_str_quoted(state, line); + } + add_str(state, "[...]\""); + + state->tot_headers += len+1; + } + + cp= line; + skip_spaces(cp, &ncp); + if (ncp != line) + continue; /* Continuation line */ + + cp= ncp; + while (ncp[0] != '\0' && ncp[0] != ':' && + !isspace((unsigned char)ncp[0])) + { + ncp++; + } + + kw= "Transfer-Encoding"; + len= strlen(kw); + if (strncasecmp(cp, kw, len) == 0) + { + /* Skip optional white space */ + cp= ncp; + skip_spaces(cp, &cp); + + if (cp[0] != ':') + { + err_header(state, + "malformed transfer-encoding"); + return; + } + cp++; + + /* Skip more white space */ + skip_spaces(cp, &cp); + + /* Should have the value by now */ + kw= "chunked"; + len= strlen(kw); + if (strncasecmp(cp, kw, len) != 0) + continue; + /* make sure we have end of line or white + * space */ + if (cp[len] != '\0' && + isspace((unsigned char)cp[len])) + { + continue; + } + state->chunked= 1; + continue; + } + + kw= "Content-length"; + len= strlen(kw); + if (strncasecmp(cp, kw, len) != 0) + continue; + + /* Skip optional white space */ + cp= ncp; + skip_spaces(cp, &cp); + + if (cp[0] != ':') + { + err_header(state, + "malformed content-length"); + return; + } + cp++; + + /* Skip more white space */ + skip_spaces(cp, &cp); + + /* Should have the value by now */ + state->content_length= strtoul(cp, &check, 10); + if (check == cp) + { + err_header(state, + "malformed content-length"); + return; + } + + /* And after that we should have just white space */ + cp= check; + skip_spaces(cp, &cp); + + if (cp[0] != '\0') + { + err_header(state, + "malformed content-length"); + return; + } + continue; + + case READ_BODY: + no_body= (state->do_head || state->http_result == 204 || + state->http_result == 304 || + state->http_result/100 == 1); + + if (no_body) + { + /* This reply will not have a body even if + * there is a content-length line. + */ + state->readstate= READ_DONE; + } + else if (state->chunked) + state->readstate= READ_CHUNKED; + else + { + state->readstate= READ_SIMPLE; + state->content_offset= 0; + } + + continue; + + case READ_CHUNKED: + line= &state->line[state->lineoffset]; + cp= strchr(line, '\n'); + if (cp == NULL) + { + err_chunked(state, "contains nul"); + return; + } + + len= (cp-line+1); + state->lineoffset += len; + + cp[0]= '\0'; + if (cp > line && cp[-1] == '\r') + cp[-1]= '\0'; + + len= strtoul(line, &check, 16); + if (check == line || (check[0] != '\0' && + !isspace(*(unsigned char *)check))) + { + err_chunked(state, "not a number"); + return; + } + + if (!len) + { + state->readstate= READ_CHUNKED_TRAILER; + continue; + } + + state->tot_chunked += len; + state->readstate= READ_CHUNK_BODY; + continue; + + case READ_CHUNK_BODY: + if (state->content_offset >= state->tot_chunked) + { + state->readstate= READ_CHUNK_END; + continue; + } + + /* Do we need more input? */ + if (state->linemax == 0 || + state->lineoffset >= state->linelen) + { + r= get_input(state); + if (r == -1) + { + printf( + "readcb: get_input failed, should do something\n"); + return; + } + + /* Did we get what we want? */ + if (state->lineoffset >= state->linelen) + { + /* No */ + return; + } + } + + len= state->linelen-state->lineoffset; + if (state->content_offset+len > state->tot_chunked) + len= state->tot_chunked-state->content_offset; + + if (state->content_offset+len <= state->max_body) + { +#if 0 + printf( + "readcb: should report %ld bytes worth of content\n", + len); +#endif + } + else if (state->content_offset <= state->max_body && + state->max_body != 0) + { + /* Fill up remaining space and report + * truncation */ + if (state->content_offset < state->max_body) + { + len= state->max_body - + state->content_offset; +#if 0 + printf( + "readcb: should report %ld bytes worth of content\n", + len); +#endif + + } + printf( + "readcb: should add truncation indicator\n"); + } + + state->content_offset += len; + state->lineoffset += len; + + continue; + + case READ_CHUNK_END: + line= &state->line[state->lineoffset]; + cp= strchr(line, '\n'); + if (cp == NULL) + { + err_chunked(state, "contains nul"); + return; + } + + len= (cp-line+1); + state->lineoffset += len; + + cp[0]= '\0'; + if (cp > line && cp[-1] == '\r') + cp[-1]= '\0'; + + if (strlen(line) != 0) + { + err_chunked(state, + "garbage at the end of chunk"); + return; + } + + state->readstate= READ_CHUNKED; + continue; + + case READ_CHUNKED_TRAILER: + line= &state->line[state->lineoffset]; + cp= strchr(line, '\n'); + if (cp == NULL) + { + err_chunked(state, "contains nul"); + return; + } + + len= (cp-line+1); + state->lineoffset += len; + + cp[0]= '\0'; + if (cp > line && cp[-1] == '\r') + cp[-1]= '\0'; + + if (line[0] == '\0') + { + state->readstate= READ_DONE; + continue; + } + continue; + + case READ_SIMPLE: + if (state->content_length >= 0 && + state->content_offset >= state->content_length) + { + state->readstate= READ_DONE; + continue; + } + + /* Do we need more input? */ + if (state->linemax == 0 || + state->lineoffset >= state->linelen) + { + r= get_input(state); + if (r == -1) + { + printf( + "readcb: get_input failed, should do something\n"); + return; + } + + /* Did we get what we want? */ + if (state->lineoffset >= state->linelen) + { + /* No */ + return; + } + } + + len= state->linelen-state->lineoffset; + if (state->content_offset+len <= state->max_body) + { +#if 0 + printf( + "readcb: should report %ld bytes worth of content\n", + len); +#endif + } + else if (state->content_offset <= state->max_body && + state->max_body != 0) + { + /* Fill up remaining space and report + * truncation */ + if (state->content_offset < state->max_body) + { + len= state->max_body - + state->content_offset; +#if 0 + printf( + "readcb: should report %ld bytes worth of content\n", + len); +#endif + + } + printf( + "readcb: should add truncation indicator\n"); + } + + state->content_offset += len; + state->lineoffset += len; + + continue; + + case READ_DONE: + if (state->bev) + { + state->bev= NULL; + gettimeofday(&endtime, NULL); + state->resptime= + (endtime.tv_sec- + state->start.tv_sec)*1e3 + + (endtime.tv_usec- + state->start.tv_usec)/1e3; + report(state); + } + return; + default: + printf("readcb: readstate = %d\n", state->readstate); + return; + } + } +} + +static int post_file(struct hgstate *state, const char *filename) +{ + int r; + FILE *fh; + + if (!state->post_fh) + { + fh= fopen(filename, "r"); + if (fh == NULL) + { + printf("post_file: unable to open '%s': %s\n", + filename, strerror(errno)); + return -1; + } + state->post_fh= fh; + } + if (!state->post_buf) + state->post_buf= xmalloc(POST_BUF_SIZE); + r= fread(state->post_buf, 1, POST_BUF_SIZE, state->post_fh); + if (r == -1) + { + printf("post_file: error reading from '%s': %s\n", + filename, strerror(errno)); + return -1; + } + if (r == 0) + { + fclose(state->post_fh); + state->post_fh= NULL; + return 1; + } + r= bufferevent_write(state->bev, state->post_buf, r); + if (r == -1) + { + printf("post_file: bufferevent_write failed\n"); + } + return r; +} + +static void writecb(struct bufferevent *bev, void *ptr) +{ + int r; + struct hgstate *state; + struct evbuffer *output; + off_t cLength; + struct stat sb; + + state= ENV2STATE(ptr); + + for(;;) + { + switch(state->writestate) + { + case WRITE_HEADER: + output= bufferevent_get_output(bev); + evbuffer_add_printf(output, "%s %s HTTP/1.%c\r\n", + state->do_get ? "GET" : + state->do_head ? "HEAD" : "POST", state->path, + state->do_http10 ? '0' : '1'); + evbuffer_add_printf(output, "Host: %s\r\n", + state->host); + evbuffer_add_printf(output, "Connection: close\r\n"); + evbuffer_add_printf(output, "User-Agent: %s\r\n", + state->user_agent); + if (state->do_post) + { + evbuffer_add_printf(output, + "Content-Type: application/x-www-form-urlencoded\r\n"); + } + + cLength= 0; + if (state->do_post) + { + if (state->post_header) + { + if (stat(state->post_header, &sb) == 0) + cLength += sb.st_size; + } + if (state->post_file) + { + if (stat(state->post_file, &sb) == 0) + cLength += sb.st_size; + } + if (state->post_footer) + { + if (stat(state->post_footer, &sb) == 0) + cLength += sb.st_size; + } + evbuffer_add_printf(output, + "Content-Length: %lu\r\n", + (unsigned long)cLength); + } + + evbuffer_add_printf(output, "\r\n"); + if (state->do_post) + state->writestate = WRITE_POST_HEADER; + else + state->writestate = WRITE_DONE; + return; + case WRITE_POST_HEADER: + if (!state->post_header) + { + state->writestate= WRITE_POST_FILE; + continue; + } + r= post_file(state, state->post_header); + if (r != 1) + return; + + /* Done */ + state->writestate= WRITE_POST_FILE; + continue; + + case WRITE_POST_FILE: + if (!state->post_file) + { + state->writestate= WRITE_POST_FOOTER; + continue; + } + r= post_file(state, state->post_file); + if (r != 1) + return; + + /* Done */ + state->writestate= WRITE_POST_FOOTER; + continue; + case WRITE_POST_FOOTER: + if (!state->post_footer) + { + state->writestate= WRITE_DONE; + continue; + } + r= post_file(state, state->post_footer); + if (r != 1) + return; + + /* Done */ + state->writestate= WRITE_DONE; + continue; + case WRITE_DONE: + return; + default: + printf("writecb: unknown write state: %d\n", + state->writestate); + return; + } + } + +} + +static void err_reading(struct hgstate *state) +{ + struct timeval endtime; + + switch(state->readstate) + { + case READ_STATUS: + add_str(state, ", " DBQ(err) ":" DBQ(error reading status)); + report(state); + break; + case READ_HEADER: + if (state->max_headers) + add_str(state, " ], "); + add_str(state, DBQ(err) ":" DBQ(error reading headers) ", "); + report(state); + break; + case READ_SIMPLE: +#if 0 + if (state->max_body) + add_str(state, " ]"); +#endif + if (state->content_length == -1) + { + /* EOF is normal */ + state->readstate= READ_DONE; + } + else + { + add_str(state, DBQ(err) ":" DBQ(error reading body) + ", "); + } + gettimeofday(&endtime, NULL); + state->resptime= (endtime.tv_sec-state->start.tv_sec)*1e3 + + (endtime.tv_usec-state->start.tv_usec)/1e3; + report(state); + break; + default: + printf("in err_reading, unhandled case\n"); + } +} + +static void dnscount(struct tu_env *env, int count) +{ + struct hgstate *state; + + state= ENV2STATE(env); + state->subid= 0; + state->submax= count; +} + +static void beforeconnect(struct tu_env *env, + struct sockaddr *addr, socklen_t addrlen) +{ + struct hgstate *state; + + state= ENV2STATE(env); + + state->socklen= addrlen; + memcpy(&state->sin6, addr, state->socklen); + + state->connecting= 1; + state->readstate= READ_STATUS; + state->writestate= WRITE_HEADER; + + state->linelen= 0; + state->lineoffset= 0; + state->headers_size= 0; + state->tot_headers= 0; + + /* Clear result */ + if (!state->do_all || !state->do_combine) + state->reslen= 0; + + add_str(state, "{ "); + + gettimeofday(&state->start, NULL); +} + + +static void reporterr(struct tu_env *env, enum tu_err cause, + const char *str) +{ + struct hgstate *state; + char line[80]; + + state= ENV2STATE(env); + + if (env != &state->tu_env) abort(); + + switch(cause) + { + case TU_DNS_ERR: + snprintf(line, sizeof(line), + "{ " DBQ(dnserr) ":" DBQ(%s) " }", str); + add_str(state, line); + state->dnserr= 1; + report(state); + break; + + case TU_READ_ERR: + err_reading(state); + break; + + case TU_CONNECT_ERR: + snprintf(line, sizeof(line), + DBQ(err) ":" DBQ(connect: %s) ", ", str); + add_str(state, line); + + if (state->do_all) + report(state); + else + tu_restart_connect(&state->tu_env); + break; + + case TU_OUT_OF_ADDRS: + report(state); + break; + + default: + crondlog(DIE9 "reporterr: bad cause %d", cause); + } +} + +static void connected(struct tu_env *env, struct bufferevent *bev) +{ + struct hgstate *state; + + state= ENV2STATE(env); + + if (env != &state->tu_env) abort(); + + state->connecting= 0; + state->bev= bev; + + state->loc_socklen= sizeof(state->loc_sin6); + getsockname(bufferevent_getfd(bev), + &state->loc_sin6, &state->loc_socklen); +} + +static void httpget_start(void *state) +{ + struct hgstate *hgstate; + struct evutil_addrinfo hints; + struct timeval interval; + + hgstate= state; + + if (hgstate->busy) + { + printf("httget_start: busy\n"); + return; + } + hgstate->busy= 1; + + hgstate->dnserr= 0; + hgstate->connecting= 0; + hgstate->readstate= READ_STATUS; + hgstate->writestate= WRITE_HEADER; + hgstate->gstart= time(NULL); + + memset(&hints, '\0', sizeof(hints)); + hints.ai_socktype= SOCK_STREAM; + if (hgstate->only_v4) + hints.ai_family= AF_INET; + else if (hgstate->only_v6) + hints.ai_family= AF_INET6; + interval.tv_sec= CONN_TO; + interval.tv_usec= 0; + tu_connect_to_name(&hgstate->tu_env, hgstate->host, hgstate->port, + &interval, &hints, timeout_callback, + reporterr, dnscount, beforeconnect, + connected, readcb, writecb); +} + +static int httpget_delete(void *state) +{ + int ind; + struct hgstate *hgstate; + struct hgbase *base; + + hgstate= state; + + printf("httpget_delete: state %p, index %d, busy %d\n", + state, hgstate->index, hgstate->busy); + + if (hgstate->busy) + return 0; + + if (hgstate->line) + crondlog(DIE9 "line is not empty"); + + base= hgstate->base; + ind= hgstate->index; + + if (base->table[ind] != hgstate) + crondlog(DIE9 "strange, state not in table"); + base->table[ind]= NULL; + + //event_del(&hgstate->timer); + + free(hgstate->atlas); + hgstate->atlas= NULL; + free(hgstate->output_file); + hgstate->output_file= NULL; + free(hgstate->host); + hgstate->host= NULL; + free(hgstate->hostport); + hgstate->hostport= NULL; + free(hgstate->port); + hgstate->port= NULL; + free(hgstate->path); + hgstate->path= NULL; + free(hgstate->user_agent); + hgstate->user_agent= NULL; + free(hgstate->post_header); + hgstate->post_header= NULL; + free(hgstate->post_file); + hgstate->post_file= NULL; + free(hgstate->post_footer); + hgstate->post_footer= NULL; + + free(hgstate); + + return 1; +} + +struct testops httpget_ops = { httpget_init, httpget_start, + httpget_delete }; + diff --git a/eperd/ping.c b/eperd/ping.c new file mode 100644 index 0000000..639ce9c --- /dev/null +++ b/eperd/ping.c @@ -0,0 +1,1288 @@ +/* + * Copyright (c) 2013 RIPE NCC + * Copyright (c) 2009 Rocco Carbone + * This includes code Copyright (c) 2009 Rocco Carbone + * taken from the libevent-based ping. + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * ping.c + */ + +#include "libbb.h" +#include +#include +#include + +#include +#include +#include + +#include "eperd.h" + +#define SAFE_PREFIX ATLAS_DATA_NEW + +#define DBQ(str) "\"" #str "\"" + +#define PING_OPT_STRING ("!46rc:s:A:O:") + +enum +{ + opt_4 = (1 << 0), + opt_6 = (1 << 1), + opt_r = (1 << 2), +}; + +/* Intervals and timeouts (all are in milliseconds unless otherwise specified) + */ +#define DEFAULT_PING_INTERVAL 1000 /* 1 sec - 0 means flood mode */ + +/* Max IP packet size is 65536 while fixed IP header size is 20; + * the traditional ping program transmits 56 bytes of data, so the + * default data size is calculated as to be like the original + */ +#define IPHDR 20 +#define MAX_DATA_SIZE (4096 - IPHDR - ICMP_MINLEN) + +/* Error codes */ +#define PING_ERR_NONE 0 +#define PING_ERR_TIMEOUT 1 /* Communication with the host timed out */ +#define PING_ERR_DUP 2 /* Duplicate packet */ +#define PING_ERR_DONE 3 /* Max number of packets to send has been + * reached. + */ +#define PING_ERR_SENDTO 4 /* Sendto system call failed */ +#define PING_ERR_DNS 5 /* DNS error */ +#define PING_ERR_DNS_NO_ADDR 6 /* DNS no suitable addresses */ +#define PING_ERR_SHUTDOWN 10 /* The request was canceled because the PING subsystem was shut down */ +#define PING_ERR_CANCEL 12 /* The request was canceled via a call to evping_cancel_request */ +#define PING_ERR_UNKNOWN 16 /* An unknown error occurred */ + + +/* Definition for various types of counters */ +typedef uint64_t counter_t; + +/* How to keep track of a PING session */ +struct pingbase +{ + struct event_base *event_base; + + evutil_socket_t rawfd4; /* Raw socket used to ping hosts (IPv4) + */ + evutil_socket_t rawfd6; /* Raw socket used to ping hosts (IPv6) + */ + + pid_t pid; /* Identifier to send with each ICMP + * Request */ + + struct timeval tv_interval; /* Ping interval between two subsequent + * pings */ + + /* A list of hosts to ping. */ + struct pingstate **table; + int tabsiz; + + struct event event4; /* Used to detect read events on raw + * socket */ + struct event event6; /* Used to detect read events on raw + * socket */ + void (*done)(void *state); /* Called when a ping is done */ + + u_char packet [MAX_DATA_SIZE]; +}; + +struct pingstate +{ + /* Parameters */ + char *atlas; + char *hostname; + int pingcount; + char *out_filename; + char delay_name_res; + + /* State */ + struct sockaddr_in6 sin6; + socklen_t socklen; + struct sockaddr_in6 loc_sin6; + socklen_t loc_socklen; + int busy; + char got_reply; + char first; + char no_dst; + unsigned char ttl; + unsigned size; + + char *result; + size_t reslen; + size_t resmax; + + struct pingbase *base; + + sa_family_t af; /* Desired address family */ + struct evutil_addrinfo *dns_res; + struct evutil_addrinfo *dns_curr; + + size_t maxsize; + + int maxpkts; /* Number of packets to send */ + + int index; /* Index into the array of hosts */ + u_int8_t seq; /* ICMP sequence (modulo 256) for next + * run + */ + u_int8_t rcvd_ttl; /* TTL in (last) reply packet */ + char dnsip; + char send_error; + + struct event ping_timer; /* Timer to ping host at given + * intervals + */ + + /* Packets Counters */ + size_t cursize; + counter_t sentpkts; /* Total # of ICMP Echo Requests sent */ +}; + +/* User Data added to the ICMP header + * + * The 'ts' is the time the request is sent on the wire + * and it is used to compute the network round-trip value. + * + * The 'index' parameter is an index value in the array of hosts to ping + * and it is used to relate each response with the corresponding request + */ +struct evdata { + struct timeval ts; + uint32_t index; +}; + + + +/* Initialize a struct timeval by converting milliseconds */ +static void +msecstotv(time_t msecs, struct timeval *tv) +{ + tv->tv_sec = msecs / 1000; + tv->tv_usec = msecs % 1000 * 1000; +} + +/* The time since 'tv' in microseconds */ +static time_t +tvtousecs (struct timeval *tv) +{ + return tv->tv_sec * 1000000.0 + tv->tv_usec; +} + +static void add_str(struct pingstate *state, const char *str) +{ + size_t len; + + len= strlen(str); + if (state->reslen + len+1 > state->resmax) + { + state->resmax= state->reslen + len+1 + 80; + state->result= xrealloc(state->result, state->resmax); + } + memcpy(state->result+state->reslen, str, len+1); + state->reslen += len; + //printf("add_str: result = '%s'\n", state->result); +} + +static int get_timesync(void) +{ + FILE *fh; + int lastsync; + + fh= fopen(ATLAS_TIMESYNC_FILE, "r"); + if (!fh) + return -1; + fscanf(fh, "%d", &lastsync); + fclose(fh); + return time(NULL)-lastsync; +} + +static void report(struct pingstate *state) +{ + FILE *fh; + char namebuf[NI_MAXHOST]; + + if (state->out_filename) + { + fh= fopen(state->out_filename, "a"); + if (!fh) + crondlog(DIE9 "unable to append to '%s'", + state->out_filename); + } + else + fh= stdout; + + fprintf(fh, "RESULT { "); + if (state->atlas) + { + fprintf(fh, DBQ(id) ":" DBQ(%s) + ", " DBQ(fw) ":%d" + ", " DBQ(lts) ":%d" + ", " DBQ(time) ":%ld, ", + state->atlas, get_atlas_fw_version(), get_timesync(), + (long)time(NULL)); + } + + fprintf(fh, DBQ(dst_name) ":" DBQ(%s), + state->hostname); + + if (!state->no_dst) + { + getnameinfo((struct sockaddr *)&state->sin6, state->socklen, + namebuf, sizeof(namebuf), NULL, 0, NI_NUMERICHOST); + + fprintf(fh, ", " DBQ(dst_addr) ":" DBQ(%s) ", " DBQ(af) ":%d", + namebuf, state->sin6.sin6_family == AF_INET6 ? 6 : 4); + } + + if (state->got_reply) + { + namebuf[0]= '\0'; + getnameinfo((struct sockaddr *)&state->loc_sin6, + state->loc_socklen, namebuf, sizeof(namebuf), + NULL, 0, NI_NUMERICHOST); + + fprintf(fh, ", \"src_addr\":\"%s\"", namebuf); + } + + fprintf(fh, ", " DBQ(proto) ":" DBQ(ICMP)); + + if (state->got_reply) + fprintf(fh, ", " DBQ(ttl) ":%d", state->ttl); + + fprintf(fh, ", " DBQ(size) ":%d", state->size); + + fprintf(fh, ", \"result\": [ %s ] }\n", state->result); + free(state->result); + state->result= NULL; + state->busy= 0; + + if (state->out_filename) + fclose(fh); +} + +static void ping_cb(int result, int bytes, + struct sockaddr *sa, socklen_t socklen, + struct sockaddr *loc_sa, socklen_t loc_socklen, + int seq, int ttl, + struct timeval * elapsed, void * arg) +{ + struct pingstate *pingstate; + unsigned long usecs; + char namebuf[NI_MAXHOST]; + char line[256]; + + pingstate= arg; + +#if 0 + crondlog(LVL7 "in ping_cb: result %d, bytes %d, seq %d, ttl %d", + result, bytes, seq, ttl); +#endif + + if (!pingstate->busy) + { + crondlog(LVL8 "ping_cb: not busy for state %p, '%s'", + pingstate, pingstate->hostname); + return; + } + + if (pingstate->first) + { + pingstate->size= bytes; + pingstate->ttl= ttl; + } + + if (result == PING_ERR_NONE || result == PING_ERR_DUP) + { + /* Got a ping reply */ + usecs= (elapsed->tv_sec * 1000000 + elapsed->tv_usec); + + snprintf(line, sizeof(line), + "%s{ ", pingstate->first ? "" : ", "); + add_str(pingstate, line); + pingstate->first= 0; + pingstate->no_dst= 0; + if (result == PING_ERR_DUP) + { + add_str(pingstate, DBQ(dup) ":1, "); + } + + snprintf(line, sizeof(line), + DBQ(rtt) ":%f", + usecs/1000.); + add_str(pingstate, line); + + if (!pingstate->got_reply) + { + memcpy(&pingstate->loc_sin6, loc_sa, loc_socklen); + pingstate->loc_socklen= loc_socklen; + + pingstate->got_reply= 1; + } + + if (pingstate->size != bytes) + { + snprintf(line, sizeof(line), + ", " DBQ(size) ":%d", bytes); + add_str(pingstate, line); + pingstate->size= bytes; + } + if (pingstate->ttl != ttl) + { + snprintf(line, sizeof(line), + ", " DBQ(ttl) ":%d", ttl); + add_str(pingstate, line); + pingstate->ttl= ttl; + } + if (memcmp(&pingstate->loc_sin6, loc_sa, loc_socklen) != 0) + { + namebuf[0]= '\0'; + getnameinfo(loc_sa, loc_socklen, namebuf, + sizeof(namebuf), NULL, 0, NI_NUMERICHOST); + + snprintf(line, sizeof(line), + ", " DBQ(srcaddr) ":" DBQ(%s), namebuf); + add_str(pingstate, line); + } + + add_str(pingstate, " }"); + } + if (result == PING_ERR_TIMEOUT) + { + /* No ping reply */ + + snprintf(line, sizeof(line), + "%s{ " DBQ(x) ":" DBQ(*), + pingstate->first ? "" : ", "); + add_str(pingstate, line); + pingstate->no_dst= 0; + } + if (result == PING_ERR_SENDTO) + { + snprintf(line, sizeof(line), + "%s{ " DBQ(error) ":" DBQ(sendto failed: %s), + pingstate->first ? "" : ", ", strerror(seq)); + add_str(pingstate, line); + pingstate->no_dst= 0; + } + if (result == PING_ERR_TIMEOUT || result == PING_ERR_SENDTO) + { + if (pingstate->first && pingstate->loc_socklen != 0) + { + namebuf[0]= '\0'; + getnameinfo((struct sockaddr *)&pingstate->loc_sin6, + pingstate->loc_socklen, + namebuf, sizeof(namebuf), + NULL, 0, NI_NUMERICHOST); + + snprintf(line, sizeof(line), + ", " DBQ(srcaddr) ":" DBQ(%s), namebuf); + add_str(pingstate, line); + } + add_str(pingstate, " }"); + pingstate->first= 0; + } + if (result == PING_ERR_DNS) + { + pingstate->size= bytes; + snprintf(line, sizeof(line), + "%s{ " DBQ(error) ":" DBQ(dns resolution failed: %s) " }", + pingstate->first ? "" : ", ", (char *)sa); + add_str(pingstate, line); + report(pingstate); + } + if (result == PING_ERR_DONE) + { + report(pingstate); + } +} + +/* + * Checksum routine for Internet Protocol family headers (C Version). + * From ping examples in W. Richard Stevens "Unix Network Programming" book. + */ +static int mkcksum(u_short *p, int n) +{ + u_short answer; + long sum = 0; + u_short odd_byte = 0; + + while (n > 1) + { + sum += *p++; + n -= 2; + } + + /* mop up an odd byte, if necessary */ + if (n == 1) + { + * (u_char *) &odd_byte = * (u_char *) p; + sum += odd_byte; + } + + sum = (sum >> 16) + (sum & 0xffff); /* add high 16 to low 16 */ + sum += (sum >> 16); /* add carry */ + answer = ~sum; /* ones-complement, truncate */ + + return answer; +} + + +/* + * Format an ICMP Echo Request packet to be sent over the wire. + * + * o the IP packet will be added on by the kernel + * o the ID field is the Unix process ID + * o the sequence number is an ascending integer + * + * The first 8 bytes of the data portion are used + * to hold a Unix "timeval" struct in VAX byte-order, + * to compute the network round-trip value. + * + * The second 8 bytes of the data portion are used + * to keep an unique integer used as index in the array + * ho hosts being monitored + */ +static void fmticmp4(u_char *buffer, size_t *sizep, u_int8_t seq, + uint32_t idx, pid_t pid) +{ + size_t minlen; + struct icmp *icmp = (struct icmp *) buffer; + struct evdata *data = (struct evdata *) (buffer + ICMP_MINLEN); + + struct timeval now; + + minlen= ICMP_MINLEN + sizeof(*data); + if (*sizep < minlen) + *sizep= minlen; + if (*sizep > MAX_DATA_SIZE) + *sizep= MAX_DATA_SIZE; + + if (*sizep > minlen) + memset(buffer+minlen, '\0', *sizep-minlen); + + /* The ICMP header (no checksum here until user data has been filled in) */ + icmp->icmp_type = ICMP_ECHO; /* type of message */ + icmp->icmp_code = 0; /* type sub code */ + icmp->icmp_id = 0xffff & pid; /* unique process identifier */ + icmp->icmp_seq = htons(seq); /* message identifier */ + + /* User data */ + gettimeofday(&now, NULL); + data->ts = now; /* current time */ + data->index = idx; /* index into an array */ + + /* Last, compute ICMP checksum */ + icmp->icmp_cksum = 0; + icmp->icmp_cksum = mkcksum((u_short *) icmp, *sizep); /* ones complement checksum of struct */ +} + + +/* + * Format an ICMPv6 Echo Request packet to be sent over the wire. + * + * o the IP packet will be added on by the kernel + * o the ID field is the Unix process ID + * o the sequence number is an ascending integer + * + * The first 8 bytes of the data portion are used + * to hold a Unix "timeval" struct in VAX byte-order, + * to compute the network round-trip value. + * + * The second 8 bytes of the data portion are used + * to keep an unique integer used as index in the array + * ho hosts being monitored + */ +static void fmticmp6(u_char *buffer, size_t *sizep, + u_int8_t seq, uint32_t idx, pid_t pid) +{ + size_t minlen; + struct icmp6_hdr *icmp = (struct icmp6_hdr *) buffer; + struct evdata *data = (struct evdata *) (buffer + offsetof(struct icmp6_hdr, icmp6_data16[2])); + + struct timeval now; + + minlen= offsetof(struct icmp6_hdr, icmp6_data16[2]) + sizeof(*data); + if (*sizep < minlen) + *sizep= minlen; + if (*sizep > MAX_DATA_SIZE) + *sizep= MAX_DATA_SIZE; + + if (*sizep > minlen) + memset(buffer+minlen, '\0', *sizep-minlen); + + /* The ICMP header (no checksum here until user data has been filled in) */ + icmp->icmp6_type = ICMP6_ECHO_REQUEST; /* type of message */ + icmp->icmp6_code = 0; /* type sub code */ + icmp->icmp6_id = 0xffff & pid; /* unique process identifier */ + icmp->icmp6_seq = htons(seq); /* message identifier */ + + /* User data */ + gettimeofday(&now, NULL); + data->ts = now; /* current time */ + data->index = idx; /* index into an array */ + + icmp->icmp6_cksum = 0; +} + + +/* Attempt to transmit an ICMP Echo Request to a given host */ +static void ping_xmit(struct pingstate *host) +{ + struct pingbase *base = host->base; + + int nsent, fd4, fd6, t_errno, r; + + host->send_error= 0; + if (host->sentpkts >= host->maxpkts) + { + /* Done. */ + ping_cb(PING_ERR_DONE, host->cursize, + (struct sockaddr *)&host->sin6, host->socklen, + (struct sockaddr *)&host->loc_sin6, host->loc_socklen, + 0, host->rcvd_ttl, NULL, + host); + if (host->base->done) + host->base->done(host); + + /* Fake packet sent to kill timer */ + host->sentpkts++; + + return; + } + + /* Transmit the request over the network */ + if (host->sin6.sin6_family == AF_INET6) + { + /* Format the ICMP Echo Reply packet to send */ + fmticmp6(base->packet, &host->cursize, host->seq, host->index, + base->pid); + + fd6 = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); + if (fd6 != -1) + { + r= connect(fd6, (struct sockaddr *)&host->sin6, + host->socklen); + if (r == 0) + { + host->loc_socklen= + sizeof(host->loc_sin6); + getsockname(fd6, &host->loc_sin6, + &host->loc_socklen); + } + } + + nsent = sendto(fd6, base->packet, host->cursize, + MSG_DONTWAIT, (struct sockaddr *)&host->sin6, + host->socklen); + + t_errno= errno; + close(fd6); + errno= t_errno; + } + else + { + /* Format the ICMP Echo Reply packet to send */ + fmticmp4(base->packet, &host->cursize, host->seq, host->index, + base->pid); + + fd4 = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + if (fd4 != -1) + { + r= connect(fd4, (struct sockaddr *)&host->sin6, + host->socklen); + if (r == 0) + { + host->loc_socklen= + sizeof(host->loc_sin6); + getsockname(fd4, &host->loc_sin6, + &host->loc_socklen); + } + } + + + nsent = sendto(fd4, base->packet, host->cursize, + MSG_DONTWAIT, (struct sockaddr *)&host->sin6, + host->socklen); + + t_errno= errno; + close(fd4); + errno= t_errno; + } + + if (nsent > 0) + { + /* Update timestamps and counters */ + host->sentpkts++; + + } + else + { + host->sentpkts++; + host->send_error= 1; + + /* Report the failure and stop */ + ping_cb(PING_ERR_SENDTO, host->cursize, + (struct sockaddr *)&host->sin6, host->socklen, + (struct sockaddr *)&host->loc_sin6, host->loc_socklen, + errno, 0, NULL, + host); + } +} + + +/* The callback to handle timeouts due to destination host unreachable condition */ +static void noreply_callback(int __attribute((unused)) unused, const short __attribute((unused)) event, void *h) +{ + struct pingstate *host = h; + + if (!host->got_reply && !host->send_error) + { + ping_cb(PING_ERR_TIMEOUT, host->cursize, + (struct sockaddr *)&host->sin6, host->socklen, + NULL, 0, + host->seq, -1, &host->base->tv_interval, + host); + + /* Update the sequence number for the next run */ + host->seq = (host->seq + 1) % 256; + } + + ping_xmit(host); + + if (host->sentpkts <= host->maxpkts) + { + evtimer_add(&host->ping_timer, &host->base->tv_interval); + } +} + +/* + * Called by libevent when the kernel says that the raw socket is ready for reading. + * + * It reads a packet from the wire and attempt to decode and relate ICMP Echo Request/Reply. + * + * To be legal the packet received must be: + * o of enough size (> IPHDR + ICMP_MINLEN) + * o of ICMP Protocol + * o of type ICMP_ECHOREPLY + * o the one we are looking for (matching the same identifier of all the packets the program is able to send) + */ +static void ready_callback4 (int __attribute((unused)) unused, + const short __attribute((unused)) event, void * arg) +{ + struct pingbase *base = arg; + + int nrecv, isDup; + struct sockaddr_in remote; /* responding internet address */ + socklen_t slen = sizeof(struct sockaddr); + struct sockaddr_in *sin4p; + struct sockaddr_in loc_sin4; + + /* Pointer to relevant portions of the packet (IP, ICMP and user data) */ + struct ip * ip = (struct ip *) base->packet; + struct icmphdr * icmp; + struct evdata * data = (struct evdata *) (base->packet + IPHDR + ICMP_MINLEN); + int hlen = 0; + + struct timeval now; + struct pingstate * host; + + /* Time the packet has been received */ + gettimeofday(&now, NULL); + +// printf("ready_callback4: before recvfrom\n"); + /* Receive data from the network */ + nrecv = recvfrom(base->rawfd4, base->packet, sizeof(base->packet), MSG_DONTWAIT, (struct sockaddr *) &remote, &slen); + if (nrecv < 0) + { + goto done; + } + +#if 0 + { int i; + printf("received:"); + for (i= 0; ipacket[i]); + printf("\n"); + } +#endif + + /* Calculate the IP header length */ + hlen = ip->ip_hl * 4; + + /* Check the IP header */ + if (nrecv < hlen + ICMP_MINLEN || ip->ip_hl < 5) + { + /* One more too short packet */ +printf("ready_callback4: too short\n"); + goto done; + } + + /* The ICMP portion */ + icmp = (struct icmphdr *) (base->packet + hlen); + + /* Check the ICMP header to drop unexpected packets due to unrecognized id */ + if (icmp->un.echo.id != base->pid) + { +#if 0 + printf("ready_callback4: bad pid: got %d, expect %d\n", + icmp->un.echo.id, base->pid); +#endif + goto done; + } + + /* Check the ICMP payload for legal values of the 'index' portion */ + if (data->index >= base->tabsiz || base->table[data->index] == NULL) + { + goto done; + } + + /* Get the pointer to the host descriptor in our internal table */ + host= base->table[data->index]; + + /* Check for Destination Host Unreachable */ + if (icmp->type == ICMP_ECHO) + { + /* Completely ignore ECHO requests */ + } + else if (icmp->type == ICMP_ECHOREPLY) + { + /* Use the User Data to relate Echo Request/Reply and evaluate the Round Trip Time */ + struct timeval elapsed; /* response time */ + time_t usecs; + + /* Compute time difference to calculate the round trip */ + evutil_timersub (&now, &data->ts, &elapsed); + + /* Update counters */ + usecs = tvtousecs(&elapsed); + + /* Set destination address of packet as local address */ + sin4p= &loc_sin4; + memset(sin4p, '\0', sizeof(*sin4p)); + sin4p->sin_family= AF_INET; + sin4p->sin_addr= ip->ip_dst; + host->rcvd_ttl= ip->ip_ttl; + + /* Report everything with the wrong sequence number as a dup. + * This is not quite right, it could be a late packet. Do we + * care? + */ + isDup= (ntohs(icmp->un.echo.sequence) != host->seq); + ping_cb(isDup ? PING_ERR_DUP : PING_ERR_NONE, + nrecv - IPHDR, + (struct sockaddr *)&host->sin6, host->socklen, + (struct sockaddr *)&loc_sin4, sizeof(loc_sin4), + ntohs(icmp->un.echo.sequence), ip->ip_ttl, &elapsed, + host); + + /* Update the sequence number for the next run */ + host->seq = (host->seq + 1) % 256; + + if (!isDup) + host->got_reply= 1; + } + else + { +printf("ready_callback4: not an echo reply\n"); + /* Handle this condition exactly as the request has expired */ + noreply_callback (-1, -1, host); + } + +done: + ; +} + +/* + * Called by libevent when the kernel says that the raw socket is ready for reading. + * + * It reads a packet from the wire and attempt to decode and relate ICMP Echo Request/Reply. + * + * To be legal the packet received must be: + * o of enough size (> IPHDR + ICMP_MINLEN) + * o of ICMP Protocol + * o of type ICMP_ECHOREPLY + * o the one we are looking for (matching the same identifier of all the packets the program is able to send) + */ +static void ready_callback6 (int __attribute((unused)) unused, + const short __attribute((unused)) event, void * arg) +{ + struct pingbase *base = arg; + + int nrecv, isDup; + struct sockaddr_in remote; /* responding internet address */ + + /* Pointer to relevant portions of the packet (IP, ICMP and user data) */ + struct icmp6_hdr * icmp = (struct icmp6_hdr *) base->packet; + struct evdata * data = (struct evdata *) (base->packet + + offsetof(struct icmp6_hdr, icmp6_data16[2])); + + struct timeval now; + struct pingstate * host; + struct cmsghdr *cmsgptr; + struct sockaddr_in6 *sin6p; + struct msghdr msg; + struct sockaddr_in6 loc_sin6; + struct iovec iov[1]; + char cmsgbuf[256]; + + /* Time the packet has been received */ + gettimeofday(&now, NULL); + + iov[0].iov_base= base->packet; + iov[0].iov_len= sizeof(base->packet); + msg.msg_name= &remote; + msg.msg_namelen= sizeof(remote); + msg.msg_iov= iov; + msg.msg_iovlen= 1; + msg.msg_control= cmsgbuf; + msg.msg_controllen= sizeof(cmsgbuf); + msg.msg_flags= 0; /* Not really needed */ + + /* Receive data from the network */ + nrecv= recvmsg(base->rawfd6, &msg, MSG_DONTWAIT); + if (nrecv < 0) + { + goto done; + } + + /* Check the ICMP header to drop unexpected packets due to + * unrecognized id + */ + if (icmp->icmp6_id != base->pid) + { + goto done; + } + + /* Check the ICMP payload for legal values of the 'index' portion */ + if (data->index >= base->tabsiz || base->table[data->index] == NULL) + { + goto done; + } + + /* Get the pointer to the host descriptor in our internal table */ + host= base->table[data->index]; + + /* Check for Destination Host Unreachable */ + if (icmp->icmp6_type == ICMP6_ECHO_REPLY) + { + /* Use the User Data to relate Echo Request/Reply and evaluate the Round Trip Time */ + struct timeval elapsed; /* response time */ + time_t usecs; + + /* Compute time difference to calculate the round trip */ + evutil_timersub (&now, &data->ts, &elapsed); + + /* Update counters */ + usecs = tvtousecs(&elapsed); + + /* Set destination address of packet as local address */ + memset(&loc_sin6, '\0', sizeof(loc_sin6)); + for (cmsgptr= CMSG_FIRSTHDR(&msg); cmsgptr; + cmsgptr= CMSG_NXTHDR(&msg, cmsgptr)) + { + if (cmsgptr->cmsg_len == 0) + break; /* Can this happen? */ + if (cmsgptr->cmsg_level == IPPROTO_IPV6 && + cmsgptr->cmsg_type == IPV6_PKTINFO) + { + sin6p= &loc_sin6; + sin6p->sin6_family= AF_INET6; + sin6p->sin6_addr= ((struct in6_pktinfo *) + CMSG_DATA(cmsgptr))->ipi6_addr; + } + if (cmsgptr->cmsg_level == IPPROTO_IPV6 && + cmsgptr->cmsg_type == IPV6_HOPLIMIT) + { + host->rcvd_ttl= *(int *)CMSG_DATA(cmsgptr); + } + } + + /* Report everything with the wrong sequence number as a dup. + * This is not quite right, it could be a late packet. Do we + * care? + */ + isDup= (ntohs(icmp->icmp6_seq) != host->seq); + ping_cb(isDup ? PING_ERR_DUP : PING_ERR_NONE, + nrecv - IPHDR,\ + (struct sockaddr *)&host->sin6, host->socklen, + (struct sockaddr *)&loc_sin6, sizeof(loc_sin6), + ntohs(icmp->icmp6_seq), host->rcvd_ttl, &elapsed, + host); + + /* Update the sequence number for the next run */ + host->seq = (host->seq + 1) % 256; + + if (!isDup) + host->got_reply= 1; + } + else + /* Handle this condition exactly as the request has expired */ + noreply_callback (-1, -1, host); + +done: + ; +} + + +static void *ping_init(int __attribute((unused)) argc, char *argv[], + void (*done)(void *state)) +{ + static struct pingbase *ping_base; + + int i, newsiz, delay_name_res; + uint32_t opt; + unsigned pingcount; /* must be int-sized */ + unsigned size; + sa_family_t af; + const char *hostname; + char *str_Atlas; + char *out_filename; + struct pingstate *state; + len_and_sockaddr *lsa; + FILE *fh; + + if (!ping_base) + { + int p_proto, on; + struct protoent *protop; + evutil_socket_t fd4, fd6; + + /* Check if the ICMP protocol is available on this system */ + protop = getprotobyname("icmp"); + if (protop) + p_proto= protop->p_proto; + else + p_proto= IPPROTO_ICMP; + + /* Create an endpoint for communication using raw socket for ICMP calls */ + if ((fd4 = socket(AF_INET, SOCK_RAW, p_proto)) == -1) { + return NULL; + } + + /* Check if the ICMP6 protocol is available on this system */ + protop = getprotobyname("icmp6"); + if (protop) + p_proto= protop->p_proto; + else + p_proto= IPPROTO_ICMPV6; + + if ((fd6 = socket(AF_INET6, SOCK_RAW, p_proto)) == -1) { + close(fd4); + return NULL; + } + + on = 1; + setsockopt(fd6, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, + sizeof(on)); + + on = 1; + setsockopt(fd6, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, + sizeof(on)); + + ping_base = malloc(sizeof(*ping_base)); + if (ping_base == NULL) + return (NULL); + memset(ping_base, 0, sizeof(*ping_base)); + + ping_base->event_base = EventBase; + + ping_base->tabsiz= 10; + ping_base->table= xzalloc(ping_base->tabsiz * + sizeof(*ping_base->table)); + + ping_base->rawfd4 = fd4; + ping_base->rawfd6 = fd6; + evutil_make_socket_nonblocking(ping_base->rawfd4); + evutil_make_socket_nonblocking(ping_base->rawfd6); + + /* Set default values */ + ping_base->pid = getpid(); + + msecstotv(DEFAULT_PING_INTERVAL, &ping_base->tv_interval); + + /* Define the callback to handle ICMP Echo Reply and add the + * raw file descriptor to those monitored for read events */ + event_assign(&ping_base->event4, ping_base->event_base, + ping_base->rawfd4, EV_READ | EV_PERSIST, + ready_callback4, ping_base); + event_assign(&ping_base->event6, ping_base->event_base, + ping_base->rawfd6, EV_READ | EV_PERSIST, + ready_callback6, ping_base); + event_add(&ping_base->event4, NULL); + event_add(&ping_base->event6, NULL); + + ping_base->done= 0; + } + + /* Parse arguments */ + pingcount= 3; + size= 0; + str_Atlas= NULL; + out_filename= NULL; + /* exactly one argument needed; -c NUM */ + opt_complementary = "=1:c+:s+"; + opt = getopt32(argv, PING_OPT_STRING, &pingcount, &size, + &str_Atlas, &out_filename); + hostname = argv[optind]; + + if (opt == 0xffffffff) + { + crondlog(LVL8 "bad options"); + return NULL; + } + + if (out_filename) + { + if (!validate_filename(out_filename, SAFE_PREFIX)) + { + crondlog(LVL8 "insecure file '%s'", out_filename); + return NULL; + } + fh= fopen(out_filename, "a"); + if (!fh) + { + crondlog(LVL8 "unable to append to '%s'", + out_filename); + return NULL; + } + fclose(fh); + } + + af= AF_UNSPEC; + if (opt & opt_4) + af= AF_INET; + if (opt & opt_6) + af= AF_INET6; + delay_name_res= !!(opt & opt_r); + + if (!delay_name_res) + { + /* Attempt to resolv 'name' */ + lsa= host_and_af2sockaddr(hostname, 0, af); + if (!lsa) + return NULL; + + if (lsa->len > sizeof(state->sin6)) + { + free(lsa); + return NULL; + } + } + + state= xzalloc(sizeof(*state)); + + memset(&state->loc_sin6, '\0', sizeof(state->loc_sin6)); + state->loc_socklen= 0; + if (!delay_name_res) + { + state->socklen= lsa->len; + memcpy(&state->sin6, &lsa->u.sa, state->socklen); + free(lsa); lsa= NULL; + } + + state->base = ping_base; + state->af= af; + state->delay_name_res= delay_name_res; + + state->seq = 1; + + /* Define here the callbacks to ping the host and to handle no reply + * timeouts + */ + evtimer_assign(&state->ping_timer, state->base->event_base, + noreply_callback, state); + + for (i= 0; itabsiz; i++) + { + if (ping_base->table[i] == NULL) + break; + } + if (i >= ping_base->tabsiz) + { + newsiz= 2*ping_base->tabsiz; + ping_base->table= xrealloc(ping_base->table, + newsiz*sizeof(*ping_base->table)); + for (i= ping_base->tabsiz; itable[i]= NULL; + i= ping_base->tabsiz; + ping_base->tabsiz= newsiz; + } + state->index= i; + ping_base->table[i]= state; + + state->pingcount= pingcount; + state->atlas= str_Atlas ? strdup(str_Atlas) : NULL; + state->hostname= strdup(hostname); + state->out_filename= out_filename ? strdup(out_filename) : NULL; + + state->result= NULL; + state->reslen= 0; + state->resmax= 0; + + state->maxsize = size; + state->base->done= done; + + return state; +} + +static void ping_start2(void *state) +{ + struct pingstate *pingstate; + + pingstate= state; + + pingstate->sentpkts= 0; + pingstate->cursize= pingstate->maxsize; + + ping_xmit(pingstate); + + /* Add the timer to handle no reply condition in the given timeout */ + evtimer_add(&pingstate->ping_timer, &pingstate->base->tv_interval); +} + +static void dns_cb(int result, struct evutil_addrinfo *res, void *ctx) +{ + int count; + struct pingstate *env; + struct evutil_addrinfo *cur; + + env= ctx; + + if (!env->dnsip) + { + crondlog(LVL7 + "dns_cb: in dns_cb but not doing dns at this time"); + if (res) + evutil_freeaddrinfo(res); + return; + } + + env->dnsip= 0; + + if (result != 0) + { + ping_cb(PING_ERR_DNS, env->maxsize, + (struct sockaddr *)evutil_gai_strerror(result), 0, + (struct sockaddr *)NULL, 0, + 0, 0, NULL, + env); + ping_cb(PING_ERR_DONE, env->maxsize, + (struct sockaddr *)NULL, 0, + (struct sockaddr *)NULL, 0, + 0, 0, NULL, + env); + if (env->base->done) + env->base->done(env); + return; + } + + env->dns_res= res; + env->dns_curr= res; + + count= 0; + for (cur= res; cur; cur= cur->ai_next) + count++; + + // env->reportcount(env, count); + + while (env->dns_curr) + { + env->socklen= env->dns_curr->ai_addrlen; + if (env->socklen > sizeof(env->sin6)) + continue; /* Weird */ + memcpy(&env->sin6, env->dns_curr->ai_addr, + env->socklen); + + ping_start2(env); + + return; + } + + /* Something went wrong */ + evutil_freeaddrinfo(env->dns_res); + env->dns_res= NULL; + env->dns_curr= NULL; + ping_cb(PING_ERR_DNS_NO_ADDR, env->cursize, + (struct sockaddr *)NULL, 0, + (struct sockaddr *)NULL, 0, + 0, 0, NULL, + env); + if (env->base->done) + env->base->done(env); +} + +static void ping_start(void *state) +{ + struct pingstate *pingstate; + struct evdns_getaddrinfo_request *evdns_req; + struct evutil_addrinfo hints; + + pingstate= state; + + if (pingstate->result) free(pingstate->result); + pingstate->resmax= 80; + pingstate->result= xmalloc(pingstate->resmax); + pingstate->reslen= 0; + + pingstate->first= 1; + pingstate->got_reply= 0; + pingstate->no_dst= 1; + pingstate->busy= 1; + + pingstate->maxpkts= pingstate->pingcount; + + if (!pingstate->delay_name_res) + { + ping_start2(state); + return; + } + + pingstate->dnsip= 1; + + memset(&hints, '\0', sizeof(hints)); + hints.ai_socktype= SOCK_DGRAM; + hints.ai_family= pingstate->af; + printf("hostname '%s', family %d\n", + pingstate->hostname, hints.ai_family); + evdns_req= evdns_getaddrinfo(DnsBase, pingstate->hostname, + NULL, &hints, dns_cb, pingstate); +} + +static int ping_delete(void *state) +{ + struct pingstate *pingstate; + struct pingbase *base; + + pingstate= state; + + if (pingstate->busy) + { + crondlog(LVL8 + "ping_delete: not deleting, busy for state %p, '%s'", + pingstate, pingstate->hostname); + return 0; + } + + base= pingstate->base; + + evtimer_del(&pingstate->ping_timer); + + base->table[pingstate->index]= NULL; + + free(pingstate->atlas); + pingstate->atlas= NULL; + free(pingstate->hostname); + pingstate->hostname= NULL; + free(pingstate->out_filename); + pingstate->out_filename= NULL; + + free(pingstate); + + return 1; +} + +struct testops ping_ops = { ping_init, ping_start, ping_delete }; + diff --git a/eperd/readresolv.c b/eperd/readresolv.c new file mode 100644 index 0000000..9d3b8c9 --- /dev/null +++ b/eperd/readresolv.c @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2013 RIPE NCC + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#define LINEL (INET6_ADDRSTRLEN * 2) +#include "libbb.h" +#include "resolv.h" + +static void nameserver_ip_add (char *nsentry, char *ip_as_string) +{ + + strncpy (nsentry, ip_as_string, LINEL); + // printf("AA added nameserver %s\n", ip_as_string); + // printf("AA added nameserver to ns %s\n", nsentry); + return; +} + +static int resolv_conf_parse_line (char *nsentry, char *line) +{ + +#define NEXT_TOKEN strtok_r(NULL, delims, &strtok_state) + char *strtok_state; + static const char *const delims = " \t"; + char *const first_token = strtok_r(line, delims, &strtok_state); + + if (!first_token) return 0; + + if (!strcmp(first_token, "nameserver")) { + char *const nameserver = NEXT_TOKEN; + if (nameserver) { + if(nameserver[(strlen(nameserver) - 1)] == '\n') + nameserver[(strlen(nameserver) - 1)] = NULL; + nameserver_ip_add(nsentry, nameserver); + //printf("AA added nameserver %s\n", nsentry); + return 1; + } + } + return 0; +} + +int get_local_resolvers(char nslist[MAXNS][INET6_ADDRSTRLEN * 2]) +{ + char buf[LINEL]; + int i = 0; + + FILE *R = fopen ("/etc/resolv.conf", "r"); + if (R != NULL) { + while ( (fgets (buf, LINEL, R)) && (i < MAXNS)) { + if(resolv_conf_parse_line(nslist[i], buf) ) + i++; + } + fclose (R); + } + return i; +} diff --git a/eperd/readresolv.h b/eperd/readresolv.h new file mode 100644 index 0000000..a195aaa --- /dev/null +++ b/eperd/readresolv.h @@ -0,0 +1,5 @@ +/* + * Copyright (c) 2013 RIPE NCC + * Licensed under GPLv2 or later, see file LICENSE in this tarball for detai + */ +int get_local_resolvers(char nslist[MAXNS][INET6_ADDRSTRLEN * 2]); diff --git a/eperd/tcputil.c b/eperd/tcputil.c new file mode 100644 index 0000000..0e5d221 --- /dev/null +++ b/eperd/tcputil.c @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2013 RIPE NCC + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * tcputil.c + */ + +#include "libbb.h" +#include "eperd.h" +#include +#include +#include + +#include "tcputil.h" + +static void dns_cb(int result, struct evutil_addrinfo *res, void *ctx); +static void create_bev(struct tu_env *env); +static void eventcb(struct bufferevent *bev, short events, void *ptr); + +void tu_connect_to_name(struct tu_env *env, char *host, char *port, + struct timeval *interval, + struct evutil_addrinfo *hints, + void (*timeout_callback)(int unused, const short event, void *s), + void (*reporterr)(struct tu_env *env, enum tu_err cause, + const char *err), + void (*reportcount)(struct tu_env *env, int count), + void (*beforeconnect)(struct tu_env *env, + struct sockaddr *addr, socklen_t addrlen), + void (*connected)(struct tu_env *env, struct bufferevent *bev), + void (*readcb)(struct bufferevent *bev, void *ptr), + void (*writecb)(struct bufferevent *bev, void *ptr)) +{ + struct evdns_getaddrinfo_request *evdns_req; + + env->interval= *interval; + env->reporterr= reporterr; + env->reportcount= reportcount; + env->beforeconnect= beforeconnect; + env->connected= connected; + env->readcb= readcb; + env->writecb= writecb; + env->dns_res= NULL; + env->bev= NULL; + + evtimer_assign(&env->timer, EventBase, + timeout_callback, env); + + env->dnsip= 1; + env->connecting= 0; + evdns_req= evdns_getaddrinfo(DnsBase, host, port, + hints, dns_cb, env); +} + +void tu_restart_connect(struct tu_env *env) +{ + struct bufferevent *bev; + + /* Delete old bev */ + if (env->bev) + { + bufferevent_free(env->bev); + env->bev= NULL; + } + + /* And create a new one */ + create_bev(env); + bev= env->bev; + + /* Connect failed, try next address */ + if (env->dns_curr) + /* Just to be on the safe side */ + { + env->dns_curr= env->dns_curr->ai_next; + } + while (env->dns_curr) + { + evtimer_add(&env->timer, &env->interval); + + env->beforeconnect(env, + env->dns_curr->ai_addr, env->dns_curr->ai_addrlen); + if (bufferevent_socket_connect(bev, + env->dns_curr->ai_addr, + env->dns_curr->ai_addrlen) == 0) + { + /* Connecting, wait for callback */ + return; + } + + /* Immediate error? */ + printf("connect error\n"); + env->dns_curr= env->dns_curr->ai_next; + } + + /* Something went wrong */ + bufferevent_free(env->bev); + env->bev= NULL; + if (env->dns_res) + { + evutil_freeaddrinfo(env->dns_res); + env->dns_res= NULL; + env->dns_curr= NULL; + } + env->reporterr(env, TU_OUT_OF_ADDRS, ""); +} + +void tu_cleanup(struct tu_env *env) +{ + if (env->dns_res) + { + evutil_freeaddrinfo(env->dns_res); + env->dns_res= NULL; + env->dns_curr= NULL; + } + if (env->bev) + { + bufferevent_free(env->bev); + env->bev= NULL; + } + + event_del(&env->timer); +} + +static void dns_cb(int result, struct evutil_addrinfo *res, void *ctx) +{ + int count; + struct tu_env *env; + struct bufferevent *bev; + struct evutil_addrinfo *cur; + + env= ctx; + + if (!env->dnsip) + { + crondlog(LVL7 + "dns_cb: in dns_cb but not doing dns at this time"); + if (res) + evutil_freeaddrinfo(res); + return; + } + + env->dnsip= 0; + + if (result != 0) + { + env->reporterr(env, TU_DNS_ERR, evutil_gai_strerror(result)); + return; + } + + env->dns_res= res; + env->dns_curr= res; + + count= 0; + for (cur= res; cur; cur= cur->ai_next) + count++; + + env->reportcount(env, count); + + create_bev(env); + + while (env->dns_curr) + { + evtimer_add(&env->timer, &env->interval); + + env->beforeconnect(env, + env->dns_curr->ai_addr, env->dns_curr->ai_addrlen); + bev= env->bev; + if (bufferevent_socket_connect(bev, + env->dns_curr->ai_addr, + env->dns_curr->ai_addrlen) == 0) + { + /* Connecting, wait for callback */ + return; + } + + /* Immediate error? */ + printf("dns_cb: connect error\n"); + + /* It is possible that the callback already freed dns_curr. */ + if (!env->dns_curr) + { + printf("dns_cb: callback ate dns_curr\n"); + if (env->dns_res) + crondlog(DIE9 "dns_cb: dns_res not null"); + return; + } + env->dns_curr= env->dns_curr->ai_next; + } + + /* Something went wrong */ + printf("dns_cb: Connect failed\n"); + bufferevent_free(env->bev); + env->bev= NULL; + evutil_freeaddrinfo(env->dns_res); + env->dns_res= NULL; + env->dns_curr= NULL; + env->reporterr(env, TU_OUT_OF_ADDRS, ""); +} + +static void create_bev(struct tu_env *env) +{ + struct bufferevent *bev; + + bev= bufferevent_socket_new(EventBase, -1, + BEV_OPT_CLOSE_ON_FREE); + if (!bev) + { + crondlog(DIE9 "bufferevent_socket_new failed"); + } + bufferevent_setcb(bev, env->readcb, env->writecb, eventcb, env); + bufferevent_enable(bev, EV_WRITE); + env->bev= bev; + env->connecting= 1; + +} + +static void eventcb(struct bufferevent *bev, short events, void *ptr) +{ + struct tu_env *env; + + env= ptr; + + if (events & BEV_EVENT_READING) + { + env->reporterr(env, TU_READ_ERR, ""); + events &= ~BEV_EVENT_READING; + return; + } + if (events & BEV_EVENT_ERROR) + { + if (env->connecting) + { + env->reporterr(env, TU_CONNECT_ERR, + strerror(errno)); + return; + } + events &= ~BEV_EVENT_ERROR; + } + if (events & BEV_EVENT_CONNECTED) + { + events &= ~BEV_EVENT_CONNECTED; + env->connecting= 0; + bufferevent_enable(bev, EV_READ); + + env->connected(env, bev); + env->writecb(bev, ptr); + } + if (events) + printf("events = 0x%x\n", events); +} + diff --git a/eperd/tcputil.h b/eperd/tcputil.h new file mode 100644 index 0000000..d2b11ec --- /dev/null +++ b/eperd/tcputil.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2013 RIPE NCC + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * tcputil.h + */ + +#include + +enum tu_err { TU_DNS_ERR, TU_READ_ERR, TU_CONNECT_ERR, TU_OUT_OF_ADDRS }; +struct tu_env +{ + char dnsip; + char connecting; + struct evutil_addrinfo *dns_res; + struct evutil_addrinfo *dns_curr; + struct bufferevent *bev; + struct timeval interval; + struct event timer; + void (*reporterr)(struct tu_env *env, enum tu_err cause, + const char *str); + void (*reportcount)(struct tu_env *env, int count); + void (*beforeconnect)(struct tu_env *env, + struct sockaddr *addr, socklen_t addrlen); + void (*connected)(struct tu_env *env, struct bufferevent *bev); + void (*readcb)(struct bufferevent *bev, void *env); + void (*writecb)(struct bufferevent *bev, void *env); +}; + +void tu_connect_to_name(struct tu_env *env, char *host, char *port, + struct timeval *timeout, + struct evutil_addrinfo *hints, + void (*timeout_callback)(int unused, const short event, void *env), + void (*reporterr)(struct tu_env *env, enum tu_err cause, + const char *err), + void (*reportcount)(struct tu_env *env, int count), + void (*beforeconnect)(struct tu_env *env, + struct sockaddr *addr, socklen_t addrlen), + void (*connected)(struct tu_env *env, struct bufferevent *bev), + void (*readcb)(struct bufferevent *bev, void *env), + void (*writecb)(struct bufferevent *bev, void *env)); +void tu_restart_connect(struct tu_env *env); +void tu_cleanup(struct tu_env *env); diff --git a/eperd/traceroute.c b/eperd/traceroute.c new file mode 100644 index 0000000..014849b --- /dev/null +++ b/eperd/traceroute.c @@ -0,0 +1,2866 @@ +/* + * Copyright (c) 2013 RIPE NCC + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * traceroute.c + */ + +#include "libbb.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include "eperd.h" + +#define SAFE_PREFIX ATLAS_DATA_NEW + +#define DBQ(str) "\"" #str "\"" + +#ifndef STANDALONE_BUSYBOX +#define uh_sport source +#define uh_dport dest +#define uh_ulen len +#define uh_sum check +#endif + +#define TRACEROUTE_OPT_STRING ("!46IUFra:c:f:g:m:w:z:A:O:S:") + +#define OPT_4 (1 << 0) +#define OPT_6 (1 << 1) +#define OPT_I (1 << 2) +#define OPT_U (1 << 3) +#define OPT_F (1 << 4) +#define OPT_r (1 << 5) + +#define BASE_PORT (0x8000 + 666) +#define SRC_BASE_PORT (20480) +#define MAX_DATA_SIZE (4096) + +#define DBQ(str) "\"" #str "\"" + +#define ICMPEXT_VERSION_SHIFT 4 + +#define ICMPEXT_MPLS 1 +#define ICMPEXT_MPLS_IN 1 + +#define MPLS_LABEL_SHIFT 12 +#define MPLS_EXT_SHIFT 9 +#define MPLS_EXT_MASK 0x7 +#define MPLS_S_BIT 0x100 +#define MPLS_TTL_MASK 0xff + +struct trtbase +{ + struct event_base *event_base; + + int v4icmp_rcv; + int v6icmp_rcv; + int v4icmp_snd; + int v6icmp_snd; + int v4udp_snd; + + int my_pid; + + struct event event4; + struct event event6; + + struct trtstate **table; + int tabsiz; + + /* For standalone traceroute. Called when a traceroute instance is + * done. Just one pointer for all instances. It is up to the caller + * to keep it consistent. + */ + void (*done)(void *state); + + u_char packet[MAX_DATA_SIZE]; +}; + +struct trtstate +{ + /* Parameters */ + char *atlas; + char *hostname; + char *out_filename; + char do_icmp; + char do_v6; + char dont_fragment; + char delay_name_res; + char trtcount; + unsigned short maxpacksize; + unsigned char firsthop; + unsigned char maxhops; + unsigned char gaplimit; + unsigned char parismod; + unsigned duptimeout; + unsigned timeout; + + /* Base and index in table */ + struct trtbase *base; + int index; + + struct sockaddr_in6 sin6; + socklen_t socklen; + struct sockaddr_in6 loc_sin6; + socklen_t loc_socklen; + + int sent; + uint8_t hop; + uint16_t paris; + uint16_t seq; + unsigned short curpacksize; + + uint8_t last_response_hop; /* Hop at which we last got something + * back. + */ + unsigned done:1; /* We got something from the target + * host or a destination unreachable. + */ + unsigned not_done:1; /* Not got something else */ + unsigned lastditch:1; /* In last-ditch hop */ + unsigned busy:1; /* Busy, do not start another one */ + unsigned gotresp:1; /* Got a response to the last packet + * we sent. For dup detection. + */ + unsigned dnsip:1; /* Busy with dns name resolution */ + struct evutil_addrinfo *dns_res; + struct evutil_addrinfo *dns_curr; + + time_t starttime; + struct timeval xmit_time; + + struct event timer; + + unsigned long min; + unsigned long max; + unsigned long sum; + int sentpkts; + int rcvdpkts; + int duppkts; + + char *result; + size_t reslen; + size_t resmax; +}; + +static struct trtbase *trt_base; + +struct udp_ph +{ + struct in_addr src; + struct in_addr dst; + uint8_t zero; + uint8_t proto; + uint16_t len; +}; + +struct v6_ph +{ + struct in6_addr src; + struct in6_addr dst; + uint32_t len; + uint8_t zero[3]; + uint8_t nxt; +}; + +struct v6info +{ + uint16_t fuzz; + uint32_t pid; + uint32_t id; + uint32_t seq; + struct timeval tv; +}; + +static int in_cksum(unsigned short *buf, int sz) +{ + int nleft = sz; + int sum = 0; + unsigned short *w = buf; + unsigned short ans = 0; + + while (nleft > 1) { + sum += *w++; + nleft -= 2; + } + + if (nleft == 1) { + *(unsigned char *) (&ans) = *(unsigned char *) w; + sum += ans; + } + + sum = (sum >> 16) + (sum & 0xFFFF); + sum += (sum >> 16); + ans = ~sum; + return ans; +} + +static int in_cksum_udp(struct udp_ph *udp_ph, struct udphdr *udp, + unsigned short *buf, int sz) +{ + int nleft = sz; + int sum = 0; + unsigned short *w = buf; + unsigned short ans = 0; + + nleft= sizeof(*udp_ph); + w= (unsigned short *)udp_ph; + while (nleft > 1) { + sum += *w++; + nleft -= 2; + } + + nleft= sizeof(*udp); + w= (unsigned short *)udp; + while (nleft > 1) { + sum += *w++; + nleft -= 2; + } + + nleft= sz; + w= buf; + + while (nleft > 1) { + sum += *w++; + nleft -= 2; + } + + if (nleft == 1) { + *(unsigned char *) (&ans) = *(unsigned char *) w; + sum += ans; + } + + sum = (sum >> 16) + (sum & 0xFFFF); + sum += (sum >> 16); + ans = ~sum; + return ans; +} + +static int in_cksum_icmp6(struct v6_ph *v6_ph, unsigned short *buf, int sz) +{ + int nleft = sz; + int sum = 0; + unsigned short *w = buf; + unsigned short ans = 0; + + nleft= sizeof(*v6_ph); + w= (unsigned short *)v6_ph; + while (nleft > 1) { + sum += *w++; + nleft -= 2; + } + + nleft= sz; + w= buf; + + while (nleft > 1) { + sum += *w++; + nleft -= 2; + } + + if (nleft == 1) { + *(unsigned char *) (&ans) = *(unsigned char *) w; + sum += ans; + } + + sum = (sum >> 16) + (sum & 0xFFFF); + sum += (sum >> 16); + ans = ~sum; + return ans; +} + +static void add_str(struct trtstate *state, const char *str) +{ + size_t len; + + len= strlen(str); + if (state->reslen + len+1 > state->resmax) + { + state->resmax= state->reslen + len+1 + 80; + state->result= xrealloc(state->result, state->resmax); + } + memcpy(state->result+state->reslen, str, len+1); + state->reslen += len; + //printf("add_str: result = '%s'\n", state->result); +} + +static void report(struct trtstate *state) +{ + FILE *fh; + char namebuf[NI_MAXHOST]; + + event_del(&state->timer); + + if (state->out_filename) + { + fh= fopen(state->out_filename, "a"); + if (!fh) + crondlog(DIE9 "unable to append to '%s'", + state->out_filename); + } + else + fh= stdout; + + fprintf(fh, "RESULT { "); + if (state->atlas) + { + fprintf(fh, DBQ(id) ":" DBQ(%s) + ", " DBQ(fw) ":%d" + ", " DBQ(time) ":%ld" + ", " DBQ(endtime) ":%ld, ", + state->atlas, get_atlas_fw_version(), + state->starttime, + (long)time(NULL)); + } + + fprintf(fh, DBQ(dst_name) ":" DBQ(%s), + state->hostname); + + if (!state->dnsip) + { + getnameinfo((struct sockaddr *)&state->sin6, state->socklen, + namebuf, sizeof(namebuf), NULL, 0, NI_NUMERICHOST); + + fprintf(fh, ", " DBQ(dst_addr) ":" DBQ(%s), namebuf); + + namebuf[0]= '\0'; + getnameinfo((struct sockaddr *)&state->loc_sin6, + state->loc_socklen, + namebuf, sizeof(namebuf), NULL, 0, NI_NUMERICHOST); + + fprintf(fh, ", " DBQ(src_addr) ":" DBQ(%s), namebuf); + } + + fprintf(fh, ", " DBQ(proto) ":" DBQ(%s) ", " DBQ(af) ": %d", + state->do_icmp ? "ICMP" : "UDP", + state->dnsip ? (state->do_v6 ? 6 : 4) : + (state->sin6.sin6_family == AF_INET6 ? 6 : 4)); + + fprintf(fh, ", \"size\":%d", state->maxpacksize); + if (state->parismod) + { + fprintf(fh, ", \"paris_id\":%d", + state->paris % state->parismod); + } + fprintf(fh, ", \"result\": [ %s ] }\n", state->result); + free(state->result); + state->result= NULL; + state->busy= 0; + + if (state->out_filename) + fclose(fh); + + if (state->base->done) + state->base->done(state); +} + +static void send_pkt(struct trtstate *state) +{ + int r, hop, len, on, sock, serrno; + uint16_t sum, val; + unsigned usum; + struct trtbase *base; + struct icmp *icmp_hdr; + struct icmp6_hdr *icmp6_hdr; + struct v6info *v6info; + struct udp_ph udp_ph; + struct v6_ph v6_ph; + struct udphdr udp; + struct timeval interval; + char line[80]; + char id[]= "http://atlas.ripe.net Atlas says Hi!"; + + state->gotresp= 0; + + base= state->base; + + if (state->sent >= state->trtcount) + { + add_str(state, " } ] }"); + if (state->hop >= state->maxhops || + (state->done && !state->not_done)) + { + /* We are done */ + report(state); + return; + } + + state->hop++; + state->sent= 0; + state->done= 0; + state->not_done= 0; + + if (state->hop - state->last_response_hop > + state->gaplimit) + { +#if 0 + printf("gaplimit reached: %d > %d + %d\n", + state->hop, state->last_response_hop, + state->gaplimit); +#endif + if (state->lastditch) + { + /* Also done with last-ditch probe. */ + report(state); + return; + } + state->lastditch= 1; + state->hop= 255; + } + + snprintf(line, sizeof(line), + ", { \"hop\":%d, \"result\": [ ", state->hop); + add_str(state, line); + } + state->seq++; + + gettimeofday(&state->xmit_time, NULL); + + if (state->sin6.sin6_family == AF_INET6) + { + hop= state->hop; + + if (state->do_icmp) + { + /* Set hop count */ + setsockopt(base->v6icmp_snd, SOL_IPV6, + IPV6_UNICAST_HOPS, &hop, sizeof(hop)); + + /* Set/clear don't fragment */ + on= (state->dont_fragment ? IPV6_PMTUDISC_DO : + IPV6_PMTUDISC_DONT); + setsockopt(base->v6icmp_snd, IPPROTO_IPV6, + IPV6_MTU_DISCOVER, &on, sizeof(on)); + + icmp6_hdr= (struct icmp6_hdr *)base->packet; + icmp6_hdr->icmp6_type= ICMP6_ECHO_REQUEST; + icmp6_hdr->icmp6_code= 0; + icmp6_hdr->icmp6_cksum= 0; + icmp6_hdr->icmp6_id= htons(base->my_pid); + icmp6_hdr->icmp6_seq= htons(state->seq); + + v6info= (struct v6info *)&icmp6_hdr[1]; + v6info->fuzz= 0; + v6info->pid= htonl(base->my_pid); + v6info->id= htonl(state->index); + v6info->seq= htonl(state->seq); + v6info->tv= state->xmit_time; + + len= sizeof(*icmp6_hdr)+sizeof(*v6info); + + if (state->curpacksize < len) + state->curpacksize= len; + if (state->curpacksize > len) + { + memset(&base->packet[len], '\0', + state->curpacksize-len); + strcpy((char *)&base->packet[len], id); + len= state->curpacksize; + } + + if (state->parismod) + { + memset(&v6_ph, '\0', sizeof(v6_ph)); + v6_ph.src= state->loc_sin6.sin6_addr; + v6_ph.dst= state->sin6.sin6_addr; + v6_ph.len= htonl(len); + v6_ph.nxt= IPPROTO_ICMPV6; + + sum= in_cksum_icmp6(&v6_ph, + (unsigned short *)base->packet, len); + + /* Avoid 0 */ + val= state->paris % state->parismod + 1; + + sum= ntohs(sum); + usum= sum + (0xffff - val); + sum= usum + (usum >> 16); + + v6info->fuzz= htons(sum); + + sum= in_cksum_icmp6(&v6_ph, + (unsigned short *)base->packet, len); + +#if 0 + printf( + "send_pkt: seq %d, paris %d, cksum= htons(0x%x)\n", + state->seq, state->paris, + ntohs(sum)); +#endif + } + + r= sendto(base->v6icmp_snd, base->packet, len, 0, + (struct sockaddr *)&state->sin6, + state->socklen); + +#if 0 + { static int doit=1; if (doit && r != -1) + { errno= ENOSYS; r= -1; } doit= !doit; } +#endif + + if (r == -1) + { + if (errno != EMSGSIZE) + { + serrno= errno; + + snprintf(line, sizeof(line), + "%s{ " DBQ(error) ":" DBQ(sendto failed: %s) " } ] }", + state->sent ? " }, " : "", + strerror(serrno)); + add_str(state, line); + report(state); + return; + } + } + } + else + { + sock= socket(AF_INET6, SOCK_DGRAM, 0); + if (sock == -1) + { + crondlog(DIE9 "socket failed"); + } + + on= 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, + sizeof(on)); + + /* Bind to source addr/port */ + r= bind(sock, + (struct sockaddr *)&state->loc_sin6, + state->loc_socklen); + if (r == -1) + { + serrno= errno; + + snprintf(line, sizeof(line), + "%s{ " DBQ(error) ":" DBQ(bind failed: %s) " } ] }", + state->sent ? " }, " : "", + strerror(serrno)); + add_str(state, line); + report(state); + close(sock); + return; + } + + /* Set port */ + if (state->parismod) + { + state->sin6.sin6_port= htons(BASE_PORT + + (state->paris % state->parismod)); + } + else + { + state->sin6.sin6_port= htons(BASE_PORT + + state->seq); + } + + /* Set hop count */ + setsockopt(sock, SOL_IPV6, IPV6_UNICAST_HOPS, + &hop, sizeof(hop)); + + /* Set/clear don't fragment */ + on= (state->dont_fragment ? IPV6_PMTUDISC_DO : + IPV6_PMTUDISC_DONT); + setsockopt(sock, IPPROTO_IPV6, + IPV6_MTU_DISCOVER, &on, sizeof(on)); + + v6info= (struct v6info *)base->packet; + v6info->fuzz= 0; + v6info->pid= htonl(base->my_pid); + v6info->id= htonl(state->index); + v6info->seq= htonl(state->seq); + v6info->tv= state->xmit_time; + +#if 0 + printf( +"send_pkt: IPv6 UDP: pid = htonl(%d), id = htonl(%d), seq = htonl(%d)\n", + ntohl(v6info->pid), + ntohl(v6info->id), + ntohl(v6info->seq)); +#endif + + len= sizeof(*v6info); + + if (state->curpacksize < len) + state->curpacksize= len; + if (state->curpacksize > len) + { + memset(&base->packet[len], '\0', + state->curpacksize-len); + strcpy((char *)&base->packet[len], id); + len= state->curpacksize; + } + + r= sendto(sock, base->packet, len, 0, + (struct sockaddr *)&state->sin6, + state->socklen); + +#if 0 + { static int doit=1; if (doit && r != -1) + { errno= ENOSYS; r= -1; } doit= !doit; } +#endif + serrno= errno; + close(sock); + + if (r == -1) + { + if (serrno != EACCES && + serrno != ECONNREFUSED && + serrno != EMSGSIZE) + { + snprintf(line, sizeof(line), + "%s{ " DBQ(error) ":" DBQ(sendto failed: %s) " } ] }", + state->sent ? " }, " : "", + strerror(serrno)); + add_str(state, line); + report(state); + return; + } + } + } + } + else + { +#if 0 + printf( +"send_pkt: sending IPv4 packet, do_icmp %d, parismod %d, index %d, state %p\n", + state->do_icmp, state->parismod, state->index, state); +#endif + + if (state->do_icmp) + { + hop= state->hop; + + icmp_hdr= (struct icmp *)base->packet; + icmp_hdr->icmp_type= ICMP_ECHO; + icmp_hdr->icmp_code= 0; + icmp_hdr->icmp_cksum= 0; + icmp_hdr->icmp_id= htons(state->index); + icmp_hdr->icmp_seq= htons(state->seq); + icmp_hdr->icmp_data[0]= '\0'; + icmp_hdr->icmp_data[1]= '\0'; + + len= offsetof(struct icmp, icmp_data[2]); + + if (state->curpacksize < len) + state->curpacksize= len; + if (state->curpacksize > len) + { + memset(&base->packet[len], '\0', + state->curpacksize-len); + strcpy((char *)&base->packet[len], id); + len= state->curpacksize; + } + + if (state->parismod) + { + sum= in_cksum((unsigned short *)icmp_hdr, len); + + sum= ntohs(sum); + usum= sum + (0xffff - state->paris); + sum= usum + (usum >> 16); + + icmp_hdr->icmp_data[0]= sum >> 8; + icmp_hdr->icmp_data[1]= sum; + } + + icmp_hdr->icmp_cksum= + in_cksum((unsigned short *)icmp_hdr, len); + +#if 0 + printf( + "send_pkt: seq %d, paris %d, icmp_cksum= htons(%d)\n", + state->seq, state->paris, + ntohs(icmp_hdr->icmp_cksum)); +#endif + + /* Set hop count */ + setsockopt(base->v4icmp_snd, IPPROTO_IP, IP_TTL, + &hop, sizeof(hop)); + + /* Set/clear don't fragment */ + on= (state->dont_fragment ? IP_PMTUDISC_DO : + IP_PMTUDISC_DONT); + setsockopt(base->v4icmp_snd, IPPROTO_IP, + IP_MTU_DISCOVER, &on, sizeof(on)); + + r= sendto(base->v4icmp_snd, base->packet, len, 0, + (struct sockaddr *)&state->sin6, + state->socklen); + +#if 0 + { static int doit=1; if (doit && r != -1) + { errno= ENOSYS; r= -1; } doit= !doit; } +#endif + + if (r == -1) + { + if (errno != EMSGSIZE) + { + serrno= errno; + + snprintf(line, sizeof(line), + "%s{ " DBQ(error) ":" DBQ(sendto failed: %s) " } ] }", + state->sent ? " }, " : "", + strerror(serrno)); + add_str(state, line); + report(state); + return; + } + } + } + else + { + sock= socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) + { + crondlog(DIE9 "socket failed"); + } + + on= 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, + sizeof(on)); + + /* Bind to source addr/port */ + r= bind(sock, + (struct sockaddr *)&state->loc_sin6, + state->loc_socklen); +#if 0 + { static int doit=1; if (doit && r != -1) + { errno= ENOSYS; r= -1; } doit= !doit; } +#endif + if (r == -1) + { + serrno= errno; + + snprintf(line, sizeof(line), + "%s{ " DBQ(error) ":" DBQ(bind failed: %s) " } ] }", + state->sent ? " }, " : "", + strerror(serrno)); + add_str(state, line); + report(state); + close(sock); + return; + } + + hop= state->hop; + + /* Set port */ + if (state->parismod) + { + ((struct sockaddr_in *)&state->sin6)->sin_port= + htons(BASE_PORT + + (state->paris % state->parismod)); + } + else + { + ((struct sockaddr_in *)&state->sin6)->sin_port= + htons(BASE_PORT + state->seq); + } + + base->packet[0]= '\0'; + base->packet[1]= '\0'; + len= 2; /* We need to fudge checksum */ + + if (state->curpacksize < len) + state->curpacksize= len; + if (state->curpacksize > len) + { + memset(&base->packet[len], '\0', + state->curpacksize-len); + strcpy((char *)&base->packet[len], id); + len= state->curpacksize; + } + + udp_ph.src= ((struct sockaddr_in *)&state->loc_sin6)-> + sin_addr; + udp_ph.dst= ((struct sockaddr_in *)&state->sin6)-> + sin_addr; + udp_ph.zero= 0; + udp_ph.proto= IPPROTO_UDP; + udp_ph.len= htons(sizeof(udp)+len); + udp.uh_sport= + ((struct sockaddr_in *)&state->loc_sin6)-> + sin_port; + udp.uh_dport= ((struct sockaddr_in *)&state->sin6)-> + sin_port; + udp.uh_ulen= udp_ph.len; + udp.uh_sum= 0; + + sum= in_cksum_udp(&udp_ph, &udp, + (unsigned short *)base->packet, len); + + if (state->parismod) + { + /* Make sure that the sequence number ends + * up in the checksum field. We can't store + * 0. So we add 1. + */ + if (state->seq == 0) + state->seq++; + val= state->seq; + } + else + { + /* Use id+1 */ + val= state->index+1; + } + + sum= ntohs(sum); + usum= sum + (0xffff - val); + sum= usum + (usum >> 16); + + base->packet[0]= sum >> 8; + base->packet[1]= sum; + + sum= in_cksum_udp(&udp_ph, &udp, + (unsigned short *)base->packet, len); + + /* Set hop count */ + setsockopt(sock, IPPROTO_IP, IP_TTL, + &hop, sizeof(hop)); + + /* Set/clear don't fragment */ + on= (state->dont_fragment ? IP_PMTUDISC_DO : + IP_PMTUDISC_DONT); + setsockopt(sock, IPPROTO_IP, + IP_MTU_DISCOVER, &on, sizeof(on)); + + r= sendto(sock, base->packet, len, 0, + (struct sockaddr *)&state->sin6, + state->socklen); + +#if 0 + { static int doit=0; if (doit && r != -1) + { errno= ENOSYS; r= -1; } doit= !doit; } +#endif + + serrno= errno; + close(sock); + if (r == -1) + { + if (serrno != EMSGSIZE) + { + serrno= errno; + + snprintf(line, sizeof(line), + "%s{ " DBQ(error) ":" DBQ(sendto failed: %s) " } ] }", + state->sent ? " }, " : "", + strerror(serrno)); + add_str(state, line); + report(state); + return; + } + } + } + } + + if (state->sent) + add_str(state, " }, "); + add_str(state, "{ "); + + /* Increment packets sent */ + state->sent++; + + /* Set timer */ + interval.tv_sec= state->timeout/1000000; + interval.tv_usec= state->timeout % 1000000; + evtimer_add(&state->timer, &interval); + +} + +static void do_mpls(struct trtstate *state, unsigned char *packet, + size_t size) +{ + int o, exp, s, ttl; + uint32_t v, label; + char line[256]; + + add_str(state, ", " DBQ(mpls) ": ["); + + for (o= 0; o+4 <= size; o += 4) + { + v= (ntohl(*(uint32_t *)&packet[o])); + label= (v >> MPLS_LABEL_SHIFT); + exp= ((v >> MPLS_EXT_SHIFT) & MPLS_EXT_MASK); + s= !!(v & MPLS_S_BIT); + ttl= (v & MPLS_TTL_MASK); + + snprintf(line, sizeof(line), "%s { " DBQ(label) ":%d, " + DBQ(exp) ":%d, " DBQ(s) ":%d, " DBQ(ttl) ":%d }", + o == 0 ? "" : ",", + label, exp, s, ttl); + add_str(state, line); + } + + add_str(state, " ]"); +} + +static void do_icmp_multi(struct trtstate *state, + unsigned char *packet, size_t size, int pre_rfc4884) +{ + int o, len; + uint16_t cksum; + uint8_t class, ctype, version; + char line[256]; + + if (size < 4) + { + printf("do_icmp_multi: not enough for ICMP extension header\n"); + return; + } + cksum= in_cksum((unsigned short *)packet, size); + if (cksum != 0) + { + /* There is also anoption for a zero checksum. */ + if (!pre_rfc4884) + printf("do_icmp_multi: bad checksum\n"); + return; + } + + version= (*(uint8_t *)packet >> ICMPEXT_VERSION_SHIFT); + + snprintf(line, sizeof(line), ", " DBQ(icmpext) ": { " + DBQ(version) ":%d" ", " DBQ(rfc4884) ":%d", + version, !pre_rfc4884); + add_str(state, line); + + add_str(state, ", " DBQ(obj) ": ["); + + o= 4; + while (o+4 < size) + { + len= ntohs(*(uint16_t *)&packet[o]); + class= packet[o+2]; + ctype= packet[o+3]; + + snprintf(line, sizeof(line), "%s { " DBQ(class) ":%d, " + DBQ(type) ":%d", + o == 4 ? "" : ",", class, ctype); + add_str(state, line); + + if (len < 4 || o+len > size) + { + add_str(state, " }"); + printf("do_icmp_multi: bad len %d\n", len); + break; + } + if (class == ICMPEXT_MPLS && ctype == ICMPEXT_MPLS_IN) + do_mpls(state, packet+o+4, len-4); + o += len; + + add_str(state, " }"); + } + + add_str(state, " ] }"); +} + +static void ready_callback4(int __attribute((unused)) unused, + const short __attribute((unused)) event, void *s) +{ + struct trtbase *base; + struct trtstate *state; + int hlen, ehlen, ind, nextmtu, late, isDup, icmp_prefixlen, offset; + unsigned seq; + ssize_t nrecv; + socklen_t slen; + struct ip *ip, *eip; + struct icmp *icmp, *eicmp; + struct udphdr *eudp; + double ms; + struct timeval now, interval; + struct sockaddr_in remote; + char line[80]; + + gettimeofday(&now, NULL); + + base= s; + + slen= sizeof(remote); + nrecv= recvfrom(base->v4icmp_rcv, base->packet, sizeof(base->packet), + MSG_DONTWAIT, (struct sockaddr *)&remote, &slen); + if (nrecv == -1) + { + /* Strange, read error */ + printf("ready_callback4: read error '%s'\n", strerror(errno)); + return; + } + // printf("ready_callback4: got packet\n"); + + ip= (struct ip *)base->packet; + hlen= ip->ip_hl*4; + + if (nrecv < hlen + ICMP_MINLEN || ip->ip_hl < 5) + { + /* Short packet */ + printf("ready_callback4: too short %d\n", (int)nrecv); + return; + } + + icmp= (struct icmp *)(base->packet+hlen); + + if (icmp->icmp_type == ICMP_TIME_EXCEEDED || + icmp->icmp_type == ICMP_DEST_UNREACH) + { + eip= &icmp->icmp_ip; + ehlen= eip->ip_hl*4; + + /* Make sure the packet we have is big enough */ + if (nrecv < hlen + ICMP_MINLEN + ehlen || eip->ip_hl < 5) + { + printf("ready_callback4: too short %d\n", (int)nrecv); + return; + } + + if (eip->ip_p == IPPROTO_UDP) + { + /* Now check if there is also a UDP header in the + * packet + */ + if (nrecv < hlen + ICMP_MINLEN + ehlen + sizeof(*eudp)) + { + printf("ready_callback4: too short %d\n", + (int)nrecv); + return; + } + + eudp= (struct udphdr *)((char *)eip+ehlen); + + /* We store the id in the source port. + */ + ind= ntohs(eudp->uh_sport) - SRC_BASE_PORT; + + state= NULL; + if (ind >= 0 && ind < base->tabsiz) + state= base->table[ind]; + if (state && state->sin6.sin6_family != AF_INET) + state= NULL; + if (state && state->do_icmp) + state= NULL; + + if (!state) + { + /* Nothing here */ + // printf("ready_callback4: no state\n"); + return; + } + +#if 0 + printf("ready_callback4: from %s", + inet_ntoa(remote.sin_addr)); + printf(" for %s hop %d\n", + inet_ntoa(((struct sockaddr_in *) + &state->sin6)->sin_addr), state->hop); +#endif + + if (!state->busy) + { +#if 0 + printf( + "ready_callback4: index (%d) is not busy\n", + ind); +#endif + return; + } + + late= 0; + isDup= 0; + if (state->parismod) + { + /* Sequence number is in checksum field */ + seq= ntohs(eudp->uh_sum); + + /* Unfortunately, cheap home routers may + * forget to restore the checksum field + * when they are doing NAT. Ignore the + * sequence number if it seems wrong. + */ + if (seq > state->seq) + seq= state->seq; + } + else + { + /* Sequence number is in destination field */ + seq= ntohs(eudp->uh_dport)-BASE_PORT; + } + + if (seq != state->seq) + { + if (seq > state->seq) + { +#if 0 + printf( + "ready_callback4: mismatch for seq, got 0x%x, expected 0x%x (for %s)\n", + seq, state->seq, + state->hostname); +#endif + return; + } + late= 1; + + snprintf(line, sizeof(line), "\"late\":%d", + state->seq-seq); + add_str(state, line); + } + else if (state->gotresp) + { + isDup= 1; + add_str(state, " }, { \"dup\":true"); + } + + if (!late && !isDup) + state->last_response_hop= state->hop; + + ms= (now.tv_sec-state->xmit_time.tv_sec)*1000 + + (now.tv_usec-state->xmit_time.tv_usec)/1e3; + + snprintf(line, sizeof(line), "%s\"from\":\"%s\"", + (late || isDup) ? ", " : "", + inet_ntoa(remote.sin_addr)); + add_str(state, line); + snprintf(line, sizeof(line), + ", \"ttl\":%d, \"size\":%d", + ip->ip_ttl, (int)nrecv); + add_str(state, line); + if (!late) + { + snprintf(line, sizeof(line), ", \"rtt\":%.3f", + ms); + add_str(state, line); + } + if (eip->ip_ttl != 1) + { + snprintf(line, sizeof(line), ", \"ittl\":%d", + eip->ip_ttl); + add_str(state, line); + } + + if (memcmp(&eip->ip_src, + &((struct sockaddr_in *)&state->loc_sin6)-> + sin_addr, sizeof(eip->ip_src)) != 0) + { + printf("ready_callback4: changed source %s\n", + inet_ntoa(eip->ip_src)); + } + if (memcmp(&eip->ip_dst, + &((struct sockaddr_in *)&state->sin6)-> + sin_addr, sizeof(eip->ip_dst)) != 0) + { + snprintf(line, sizeof(line), + ", \"edst\":\"%s\"", + inet_ntoa(eip->ip_dst)); + add_str(state, line); + } + if (memcmp(&ip->ip_dst, + &((struct sockaddr_in *)&state->loc_sin6)-> + sin_addr, sizeof(eip->ip_src)) != 0) + { + printf("ready_callback4: weird destination %s\n", + inet_ntoa(ip->ip_dst)); + } + +#if 0 + printf("ready_callback4: from %s, ttl %d", + inet_ntoa(remote.sin_addr), ip->ip_ttl); + printf(" for %s hop %d\n", + inet_ntoa(((struct sockaddr_in *) + &state->sin6)->sin_addr), state->hop); +#endif + + if (icmp->icmp_type == ICMP_TIME_EXCEEDED) + { + if (!late) + state->not_done= 1; + } + else if (icmp->icmp_type == ICMP_DEST_UNREACH) + { + if (!late) + state->done= 1; + switch(icmp->icmp_code) + { + case ICMP_UNREACH_NET: + add_str(state, ", \"err\":\"N\""); + break; + case ICMP_UNREACH_HOST: + add_str(state, ", \"err\":\"H\""); + break; + case ICMP_UNREACH_PROTOCOL: + add_str(state, ", \"err\":\"P\""); + break; + case ICMP_UNREACH_PORT: + break; + case ICMP_UNREACH_NEEDFRAG: + nextmtu= ntohs(icmp->icmp_nextmtu); + snprintf(line, sizeof(line), + ", \"mtu\":%d", + nextmtu); + add_str(state, line); + if (!late && nextmtu >= sizeof(*ip)+ + sizeof(*eudp)) + { + nextmtu -= sizeof(*ip)+ + sizeof(*eudp); + if (nextmtu < + state->curpacksize) + { + state->curpacksize= + nextmtu; + } + } +printf("curpacksize: %d\n", state->curpacksize); + if (!late) + state->not_done= 1; + break; + case ICMP_UNREACH_FILTER_PROHIB: + add_str(state, ", \"err\":\"A\""); + break; + default: + snprintf(line, sizeof(line), + ", \"err\":%d", + icmp->icmp_code); + add_str(state, line); + break; + } + } + } + else if (eip->ip_p == IPPROTO_ICMP) + { + /* Now check if there is also an ICMP header in the + * packet + */ + if (nrecv < hlen + ICMP_MINLEN + ehlen + + offsetof(struct icmp, icmp_data[0])) + { + printf("ready_callback4: too short %d\n", + (int)nrecv); + return; + } + + eicmp= (struct icmp *)((char *)eip+ehlen); + + if (eicmp->icmp_type != ICMP_ECHO || + eicmp->icmp_code != 0) + { + printf("ready_callback4: not ECHO\n"); + return; + } + + ind= ntohs(eicmp->icmp_id); + + if (ind >= base->tabsiz) + { + /* Out of range */ +#if 0 + printf( + "ready_callback4: index out of range (%d)\n", + ind); +#endif + return; + } + + state= base->table[ind]; + if (!state) + { + /* Nothing here */ + printf( + "ready_callback4: nothing at index (%d)\n", + ind); + return; + } + + if (state->sin6.sin6_family != AF_INET) + { + // printf("ready_callback4: bad family\n"); + return; + } + + if (!state->do_icmp) + { + printf( + "ready_callback4: index (%d) is not doing ICMP\n", + ind); + return; + } + if (!state->busy) + { +printf("%s, %d: sin6_family = %d\n", __FILE__, __LINE__, state->sin6.sin6_family); + printf( + "ready_callback4: index (%d) is not busy\n", + ind); + return; + } + + if (state->parismod && + ntohs(eicmp->icmp_cksum) != state->paris) + { + printf( + "ready_callback4: mismatch for paris, got 0x%x, expected 0x%x (%s)\n", + ntohs(eicmp->icmp_cksum), state->paris, + state->hostname); + } + + late= 0; + isDup= 0; + seq= ntohs(eicmp->icmp_seq); + if (seq != state->seq) + { + if (seq > state->seq) + { +#if 0 + printf( + "ready_callback4: mismatch for seq, got 0x%x, expected 0x%x (for %s)\n", + seq, state->seq, + state->hostname); +#endif + return; + } + late= 1; + + snprintf(line, sizeof(line), "\"late\":%d", + state->seq-seq); + add_str(state, line); + } + else if (state->gotresp) + { + isDup= 1; + add_str(state, " }, { \"dup\":true"); + } + + if (!late && !isDup) + state->last_response_hop= state->hop; + + ms= (now.tv_sec-state->xmit_time.tv_sec)*1000 + + (now.tv_usec-state->xmit_time.tv_usec)/1e3; + + snprintf(line, sizeof(line), "%s\"from\":\"%s\"", + (late || isDup) ? ", " : "", + inet_ntoa(remote.sin_addr)); + add_str(state, line); + snprintf(line, sizeof(line), + ", \"ttl\":%d, \"size\":%d", + ip->ip_ttl, (int)nrecv); + add_str(state, line); + if (!late) + { + snprintf(line, sizeof(line), ", \"rtt\":%.3f", + ms); + add_str(state, line); + } + + if (eip->ip_ttl != 1) + { + snprintf(line, sizeof(line), ", \"ittl\":%d", + eip->ip_ttl); + add_str(state, line); + } + + if (memcmp(&eip->ip_src, + &((struct sockaddr_in *)&state->loc_sin6)-> + sin_addr, sizeof(eip->ip_src)) != 0) + { + printf("ready_callback4: changed source %s\n", + inet_ntoa(eip->ip_src)); + } + if (memcmp(&eip->ip_dst, + &((struct sockaddr_in *)&state->sin6)-> + sin_addr, sizeof(eip->ip_dst)) != 0) + { + snprintf(line, sizeof(line), + ", \"edst\":\"%s\"", + inet_ntoa(eip->ip_dst)); + add_str(state, line); + } + if (memcmp(&ip->ip_dst, + &((struct sockaddr_in *)&state->loc_sin6)-> + sin_addr, sizeof(eip->ip_src)) != 0) + { + printf("ready_callback4: weird destination %s\n", + inet_ntoa(ip->ip_dst)); + } + +#if 0 + printf("ready_callback4: from %s, ttl %d", + inet_ntoa(remote.sin_addr), ip->ip_ttl); + printf(" for %s hop %d\n", + inet_ntoa(((struct sockaddr_in *) + &state->sin6)->sin_addr), state->hop); +#endif + + if (icmp->icmp_type == ICMP_TIME_EXCEEDED) + { + if (!late && !isDup) + state->not_done= 1; + } + else if (icmp->icmp_type == ICMP_DEST_UNREACH) + { + if (!late) + state->done= 1; + switch(icmp->icmp_code) + { + case ICMP_UNREACH_NET: + add_str(state, ", \"err\":\"N\""); + break; + case ICMP_UNREACH_HOST: + add_str(state, ", \"err\":\"H\""); + break; + case ICMP_UNREACH_PROTOCOL: + add_str(state, ", \"err\":\"P\""); + break; + case ICMP_UNREACH_PORT: + add_str(state, ", \"err\":\"p\""); + break; + case ICMP_UNREACH_NEEDFRAG: + nextmtu= ntohs(icmp->icmp_nextmtu); + snprintf(line, sizeof(line), + ", \"mtu\":%d", + nextmtu); + add_str(state, line); + if (!late && nextmtu >= sizeof(*ip)) + { + nextmtu -= sizeof(*ip); + if (nextmtu < + state->curpacksize) + { + state->curpacksize= + nextmtu; + } + } + if (!late) + state->not_done= 1; + break; + case ICMP_UNREACH_FILTER_PROHIB: + add_str(state, ", \"err\":\"A\""); + break; + default: + snprintf(line, sizeof(line), + ", \"err\":%d", + icmp->icmp_code); + add_str(state, line); + break; + } + } + else + { + printf("imcp type %d\n", icmp->icmp_type); + } + } + else + { + printf("ready_callback4: not UDP or ICMP (%d\n", + eip->ip_p); + return; + } + + /* RFC-4884, Multi-Part ICMP messages */ + icmp_prefixlen= (ntohs(icmp->icmp_pmvoid) & 0xff) * 4; + if (icmp_prefixlen != 0) + { + + printf("icmp_pmvoid: 0x%x for %s\n", icmp->icmp_pmvoid, state->hostname); + printf("icmp_prefixlen: 0x%x for %s\n", icmp_prefixlen, inet_ntoa(remote.sin_addr)); + offset= hlen + ICMP_MINLEN + icmp_prefixlen; + if (nrecv > offset) + { + do_icmp_multi(state, base->packet+offset, + nrecv-offset, 0 /*!pre_rfc4884*/); + } + else + { + printf( + "ready_callback4: too short %d (Multi-Part ICMP)\n", + (int)nrecv); + } + } + else if (nrecv > hlen + ICMP_MINLEN + 128) + { + /* Try old style extensions */ + icmp_prefixlen= 128; + offset= hlen + ICMP_MINLEN + icmp_prefixlen; + if (nrecv > offset) + { + do_icmp_multi(state, base->packet+offset, + nrecv-offset, 1 /*pre_rfc4884*/); + } + else + { + printf( + "ready_callback4: too short %d (Multi-Part ICMP)\n", + (int)nrecv); + } + } + + if (late) + add_str(state, " }, { "); + + if (!late && !isDup) + { + if (state->duptimeout) + { + state->gotresp= 1; + interval.tv_sec= state->duptimeout/1000000; + interval.tv_usec= state->duptimeout % 1000000; + evtimer_add(&state->timer, &interval); + } + else + send_pkt(state); + } + } + else if (icmp->icmp_type == ICMP_ECHOREPLY) + { + if (icmp->icmp_code != 0) + { + printf("ready_callback4: not proper ECHO REPLY\n"); + return; + } + + ind= ntohs(icmp->icmp_id); + + if (ind >= base->tabsiz) + { + /* Out of range */ +#if 0 + printf( + "ready_callback4: index out of range (%d)\n", + ind); +#endif + return; + } + + state= base->table[ind]; + if (!state) + { + /* Nothing here */ + printf( + "ready_callback4: nothing at index (%d)\n", + ind); + return; + } + + if (state->sin6.sin6_family != AF_INET) + { + // printf("ready_callback4: bad family\n"); + return; + } + + if (!state->busy) + { +printf("%s, %d: sin6_family = %d\n", __FILE__, __LINE__, state->sin6.sin6_family); + printf( + "ready_callback4: index (%d) is not busy\n", + ind); + return; + } + + late= 0; + isDup= 0; + seq= ntohs(icmp->icmp_seq); + if (seq != state->seq) + { + if (seq > state->seq) + { +#if 0 + printf( +"ready_callback4: mismatch for seq, got 0x%x, expected 0x%x, for %s\n", + seq, state->seq, state->hostname); +#endif + return; + } + late= 1; + + snprintf(line, sizeof(line), "\"late\":%d", + state->seq-seq); + add_str(state, line); + } + else if (state->gotresp) + { + isDup= 1; + add_str(state, " }, { \"dup\":true"); + } + + if (memcmp(&ip->ip_dst, + &((struct sockaddr_in *)&state->loc_sin6)-> + sin_addr, sizeof(eip->ip_src)) != 0) + { + printf("ready_callback4: weird destination %s\n", + inet_ntoa(ip->ip_dst)); + } + + ms= (now.tv_sec-state->xmit_time.tv_sec)*1000 + + (now.tv_usec-state->xmit_time.tv_usec)/1e3; + + snprintf(line, sizeof(line), "%s\"from\":\"%s\"", + (late || isDup) ? ", " : "", + inet_ntoa(remote.sin_addr)); + add_str(state, line); + snprintf(line, sizeof(line), ", \"ttl\":%d, \"size\":%d", + ip->ip_ttl, (int)nrecv); + add_str(state, line); + if (!late) + { + snprintf(line, sizeof(line), ", \"rtt\":%.3f", ms); + add_str(state, line); + } + +#if 0 + printf("ready_callback4: from %s, ttl %d", + inet_ntoa(remote.sin_addr), ip->ip_ttl); + printf(" for %s hop %d\n", + inet_ntoa(((struct sockaddr_in *) + &state->sin6)->sin_addr), state->hop); +#endif + + /* Done */ + state->done= 1; + + if (late) + add_str(state, " }, { "); + + if (!late && !isDup) + { + if (state->duptimeout) + { + state->gotresp= 1; + interval.tv_sec= state->duptimeout/1000000; + interval.tv_usec= state->duptimeout % 1000000; + evtimer_add(&state->timer, &interval); + } + else + send_pkt(state); + } + + return; + } + else if (icmp->icmp_type == ICMP_ECHO || + icmp->icmp_type == ICMP_ROUTERADVERT) + { + /* No need to do anything */ + } + else + { + printf("ready_callback4: got type %d\n", icmp->icmp_type); + return; + } +} + +static void ready_callback6(int __attribute((unused)) unused, + const short __attribute((unused)) event, void *s) +{ + ssize_t nrecv; + int ind, rcvdttl, late, isDup, nxt, icmp_prefixlen, offset; + unsigned nextmtu, seq; + size_t ehdrsiz, siz; + struct trtbase *base; + struct trtstate *state; + struct ip6_hdr *eip; + struct ip6_frag *frag; + struct icmp6_hdr *icmp, *eicmp; + struct udphdr *eudp; + struct v6info *v6info; + struct cmsghdr *cmsgptr; + void *ptr; + double ms; + struct timeval now; + struct sockaddr_in6 remote; + struct in6_addr dstaddr; + struct msghdr msg; + struct iovec iov[1]; + struct timeval interval; + char buf[INET6_ADDRSTRLEN]; + char line[80]; + char cmsgbuf[256]; + + gettimeofday(&now, NULL); + + base= s; + + iov[0].iov_base= base->packet; + iov[0].iov_len= sizeof(base->packet); + msg.msg_name= &remote; + msg.msg_namelen= sizeof(remote); + msg.msg_iov= iov; + msg.msg_iovlen= 1; + msg.msg_control= cmsgbuf; + msg.msg_controllen= sizeof(cmsgbuf); + msg.msg_flags= 0; /* Not really needed */ + + nrecv= recvmsg(base->v6icmp_rcv, &msg, MSG_DONTWAIT); + if (nrecv == -1) + { + /* Strange, read error */ + printf("ready_callback6: read error '%s'\n", strerror(errno)); + return; + } + + rcvdttl= -42; /* To spot problems */ + memset(&dstaddr, '\0', sizeof(dstaddr)); + for (cmsgptr= CMSG_FIRSTHDR(&msg); cmsgptr; + cmsgptr= CMSG_NXTHDR(&msg, cmsgptr)) + { + if (cmsgptr->cmsg_len == 0) + break; /* Can this happen? */ + if (cmsgptr->cmsg_level == IPPROTO_IPV6 && + cmsgptr->cmsg_type == IPV6_HOPLIMIT) + { + rcvdttl= *(int *)CMSG_DATA(cmsgptr); + } + if (cmsgptr->cmsg_level == IPPROTO_IPV6 && + cmsgptr->cmsg_type == IPV6_PKTINFO) + { + dstaddr= ((struct in6_pktinfo *) + CMSG_DATA(cmsgptr))->ipi6_addr; + } + } + + if (nrecv < sizeof(*icmp)) + { + /* Short packet */ +#if 0 + printf("ready_callback6: too short %d (icmp)\n", (int)nrecv); +#endif + return; + } + + icmp= (struct icmp6_hdr *)&base->packet; + + if (icmp->icmp6_type == ICMP6_DST_UNREACH || + icmp->icmp6_type == ICMP6_PACKET_TOO_BIG || + icmp->icmp6_type == ICMP6_TIME_EXCEEDED) + { + eip= (struct ip6_hdr *)&icmp[1]; + + /* Make sure the packet we have is big enough */ + if (nrecv < sizeof(*icmp) + sizeof(*eip)) + { +#if 0 + printf("ready_callback6: too short %d (icmp_ip)\n", + (int)nrecv); +#endif + return; + } + + /* Make sure we have UDP or ICMP or a fragment header */ + if (eip->ip6_nxt == IPPROTO_FRAGMENT || + eip->ip6_nxt == IPPROTO_UDP || + eip->ip6_nxt == IPPROTO_ICMPV6) + { + ehdrsiz= 0; + frag= NULL; + nxt= eip->ip6_nxt; + if (nxt == IPPROTO_FRAGMENT) + { + /* Make sure the fragment header is completely + * there. + */ + if (nrecv < sizeof(*icmp) + sizeof(*eip) + + sizeof(*frag)) + { +#if 0 + printf( + "ready_callback6: too short %d (icmp+ip+frag)\n", + (int)nrecv); +#endif + return; + } + frag= (struct ip6_frag *)&eip[1]; + if ((ntohs(frag->ip6f_offlg) & ~3) != 0) + { + /* Not first fragment, just ignore + * it. + */ + return; + } + ehdrsiz= sizeof(*frag); + nxt= frag->ip6f_nxt; + } + + if (nxt == IPPROTO_UDP) + ehdrsiz += sizeof(*eudp); + else + ehdrsiz += sizeof(*eicmp); + + /* Now check if there is also a header in the + * packet. + */ + if (nrecv < sizeof(*icmp) + sizeof(*eip) + + ehdrsiz + sizeof(*v6info)) + { +#if 0 + printf( + "ready_callback6: too short %d (all) from %s\n", + (int)nrecv, inet_ntop(AF_INET6, + &remote.sin6_addr, buf, sizeof(buf))); +#endif + return; + } + + eudp= NULL; + eicmp= NULL; + ptr= (frag ? (void *)&frag[1] : (void *)&eip[1]); + if (nxt == IPPROTO_UDP) + { + eudp= (struct udphdr *)ptr; + v6info= (struct v6info *)&eudp[1]; + } + else + { + eicmp= (struct icmp6_hdr *)ptr; + v6info= (struct v6info *)&eicmp[1]; + } + +#if 0 + printf( +"ready_callback6: pid = htonl(%d), id = htonl(%d), seq = htonl(%d)\n", + ntohl(v6info->pid), + ntohl(v6info->id), + ntohl(v6info->seq)); +#endif + + if (ntohl(v6info->pid) != base->my_pid) + { + /* From a different process */ + return; + } + + ind= ntohl(v6info->id); + + state= NULL; + if (ind >= 0 && ind < base->tabsiz) + state= base->table[ind]; + + if (state && state->sin6.sin6_family != AF_INET6) + state= NULL; + + if (state) + { + if ((eudp && state->do_icmp) || + (eicmp && !state->do_icmp)) + { + state= NULL; + } + } + + if (!state) + { + /* Nothing here */ + return; + } + +#if 0 + printf("ready_callback6: from %s", + inet_ntop(AF_INET6, &remote.sin6_addr, + buf, sizeof(buf))); + printf(" for %s hop %d\n", + inet_ntop(AF_INET6, &state->sin6.sin6_addr, + buf, sizeof(buf)), state->hop); +#endif + + if (!state->busy) + { +printf("%s, %d: sin6_family = %d\n", __FILE__, __LINE__, state->sin6.sin6_family); + printf( + "ready_callback6: index (%d) is not busy\n", + ind); + return; + } + + late= 0; + isDup= 0; + seq= ntohl(v6info->seq); + if (seq != state->seq) + { + if (seq > state->seq) + { + printf( + "ready_callback6: mismatch for seq, got 0x%x, expected 0x%x\n", + ntohl(v6info->seq), + state->seq); + return; + } + late= 1; + + snprintf(line, sizeof(line), "\"late\":%d", + state->seq-seq); + add_str(state, line); + } else if (state->gotresp) + { + isDup= 1; + add_str(state, " }, { \"dup\":true"); + } + + if (!late && !isDup) + state->last_response_hop= state->hop; + + if (memcmp(&eip->ip6_src, + &state->loc_sin6.sin6_addr, + sizeof(eip->ip6_src)) != 0) + { + printf("ready_callback6: changed source %s\n", + inet_ntop(AF_INET6, &eip->ip6_src, + buf, sizeof(buf))); + } + if (memcmp(&eip->ip6_dst, + &state->sin6.sin6_addr, + sizeof(eip->ip6_dst)) != 0) + { + printf( + "ready_callback6: changed destination %s for %s\n", + inet_ntop(AF_INET6, &eip->ip6_dst, + buf, sizeof(buf)), + state->hostname); + } + if (memcmp(&dstaddr, + &state->loc_sin6.sin6_addr, + sizeof(dstaddr)) != 0) + { + printf("ready_callback6: weird destination %s\n", + inet_ntop(AF_INET6, &dstaddr, + buf, sizeof(buf))); + } + + if (eicmp && state->parismod && + ntohs(eicmp->icmp6_cksum) != + state->paris % state->parismod + 1) + { + printf( + "ready_callback6: got checksum 0x%x, expected 0x%x\n", + ntohs(eicmp->icmp6_cksum), + state->paris % state->parismod + 1); + } + + if (!late) + { + ms= (now.tv_sec-state->xmit_time.tv_sec)*1000 + + (now.tv_usec-state->xmit_time.tv_usec)/ + 1e3; + } + else + { + ms= (now.tv_sec-v6info->tv.tv_sec)*1000 + + (now.tv_usec-v6info->tv.tv_usec)/ + 1e3; + } + + snprintf(line, sizeof(line), "%s\"from\":\"%s\"", + (late || isDup) ? ", " : "", + inet_ntop(AF_INET6, &remote.sin6_addr, + buf, sizeof(buf))); + add_str(state, line); + snprintf(line, sizeof(line), + ", \"ttl\":%d, \"rtt\":%.3f, \"size\":%d", + rcvdttl, ms, (int)nrecv); + add_str(state, line); + if (eip->ip6_hops != 1) + { + snprintf(line, sizeof(line), ", \"ittl\":%d", + eip->ip6_hops); + add_str(state, line); + } + +#if 0 + printf("ready_callback6: from %s, ttl %d", + inet_ntop(AF_INET6, &remote.sin6_addr, buf, + sizeof(buf)), rcvdttl); + printf(" for %s hop %d\n", + inet_ntop(AF_INET6, &state->sin6.sin6_addr, buf, + sizeof(buf)), state->hop); +#endif + + if (icmp->icmp6_type == ICMP6_TIME_EXCEEDED) + { + if (!late && !isDup) + state->not_done= 1; + } + else if (icmp->icmp6_type == ICMP6_PACKET_TOO_BIG) + { + nextmtu= ntohl(icmp->icmp6_mtu); + snprintf(line, sizeof(line), ", \"mtu\":%d", + nextmtu); + add_str(state, line); + siz= sizeof(*eip); + if (eudp) + siz += sizeof(*eudp); + if (!late && nextmtu >= siz) + { + nextmtu -= siz; + if (nextmtu < state->curpacksize) + state->curpacksize= nextmtu; + } + if (!late) + state->not_done= 1; + } + else if (icmp->icmp6_type == ICMP6_DST_UNREACH) + { + if (!late) + state->done= 1; + switch(icmp->icmp6_code) + { + case ICMP6_DST_UNREACH_NOROUTE: + add_str(state, ", \"err\":\"N\""); + break; + case ICMP6_DST_UNREACH_ADDR: + add_str(state, ", \"err\":\"H\""); + break; + case ICMP6_DST_UNREACH_NOPORT: + break; + case ICMP6_DST_UNREACH_ADMIN: + add_str(state, ", \"err\":\"A\""); + break; + default: + snprintf(line, sizeof(line), + ", \"err\":%d", + icmp->icmp6_code); + add_str(state, line); + break; + } + } + } + else + { + printf( + "ready_callback6: not UDP or ICMP (ip6_nxt = %d)\n", + eip->ip6_nxt); + return; + } + + /* RFC-4884, Multi-Part ICMP messages */ + icmp_prefixlen= icmp->icmp6_data8[0] * 8; + if (icmp_prefixlen != 0) + { + + printf("icmp6_data8[0]: 0x%x for %s\n", icmp->icmp6_data8[0], state->hostname); + printf("icmp_prefixlen: 0x%x for %s\n", icmp_prefixlen, inet_ntop(AF_INET6, &state->sin6.sin6_addr, buf, sizeof(buf))); + offset= sizeof(*icmp) + icmp_prefixlen; + if (nrecv > offset) + { + do_icmp_multi(state, base->packet+offset, + nrecv-offset, 0 /*!pre_rfc4884*/); + } + else + { +#if 0 + printf( + "ready_callback6: too short %d (Multi-Part ICMP)\n", + (int)nrecv); +#endif + } + } + else if (nrecv > 128) + { + /* Try old style extensions */ + icmp_prefixlen= 128; + offset= sizeof(*icmp) + icmp_prefixlen; + if (nrecv > offset) + { + do_icmp_multi(state, base->packet+offset, + nrecv-offset, 1 /*pre_rfc4884*/); + } + else + { + printf( + "ready_callback6: too short %d (Multi-Part ICMP)\n", + (int)nrecv); + } + } + + if (late) + add_str(state, " }, { "); + + if (!late && !isDup) + { + if (state->duptimeout) + { + state->gotresp= 1; + interval.tv_sec= state->duptimeout/1000000; + interval.tv_usec= state->duptimeout % 1000000; + evtimer_add(&state->timer, &interval); + } + else + send_pkt(state); + } + } + else if (icmp->icmp6_type == ICMP6_ECHO_REPLY) + { + eip= NULL; + + /* Now check if there is also a header in the packet */ + if (nrecv < sizeof(*icmp) + sizeof(*v6info)) + { +#if 0 + printf("ready_callback6: too short %d (echo reply)\n", + (int)nrecv); +#endif + return; + } + + eudp= NULL; + eicmp= NULL; + + v6info= (struct v6info *)&icmp[1]; + + if (ntohl(v6info->pid) != base->my_pid) + { + /* From a different process */ + return; + } + + ind= ntohl(v6info->id); + + state= NULL; + if (ind >= 0 && ind < base->tabsiz) + state= base->table[ind]; + + if (state && state->sin6.sin6_family != AF_INET6) + state= NULL; + + if (state && !state->do_icmp) + { + state= NULL; + } + + if (!state) + { + /* Nothing here */ + return; + } + +#if 0 + printf("ready_callback6: from %s", + inet_ntop(AF_INET6, &remote.sin6_addr, + buf, sizeof(buf))); + printf(" for %s hop %d\n", + inet_ntop(AF_INET6, &state->sin6.sin6_addr, + buf, sizeof(buf)), state->hop); +#endif + + if (!state->busy) + { +printf("%s, %d: sin6_family = %d\n", __FILE__, __LINE__, state->sin6.sin6_family); + printf( + "ready_callback6: index (%d) is not busy\n", + ind); + return; + } + + late= 0; + isDup= 0; + seq= ntohl(v6info->seq); + if (seq != state->seq) + { + if (seq > state->seq) + { + printf( +"ready_callback6: mismatch for seq, got 0x%x, expected 0x%x\n", + ntohl(v6info->seq), + state->seq); + return; + } + late= 1; + + snprintf(line, sizeof(line), "\"late\":%d", + state->seq-seq); + add_str(state, line); + } + else if (state->gotresp) + { + isDup= 1; + add_str(state, " }, { \"dup\":true"); + } + + if (!late && !isDup) + { + state->last_response_hop= state->hop; + state->done= 1; + } + + if (memcmp(&dstaddr, &state->loc_sin6.sin6_addr, + sizeof(dstaddr)) != 0) + { + printf("ready_callback6: weird destination %s\n", + inet_ntop(AF_INET6, &dstaddr, + buf, sizeof(buf))); + } + + if (!late) + { + ms= (now.tv_sec-state->xmit_time.tv_sec)*1000 + + (now.tv_usec-state->xmit_time.tv_usec)/ + 1e3; + } + else + { + ms= (now.tv_sec-v6info->tv.tv_sec)*1000 + + (now.tv_usec-v6info->tv.tv_usec)/ + 1e3; + } + + snprintf(line, sizeof(line), "%s\"from\":\"%s\"", + (late || isDup) ? ", " : "", + inet_ntop(AF_INET6, &remote.sin6_addr, + buf, sizeof(buf))); + add_str(state, line); + snprintf(line, sizeof(line), + ", \"ttl\":%d, \"rtt\":%.3f, \"size\":%d", + rcvdttl, ms, (int)nrecv); + add_str(state, line); + +#if 0 + printf("ready_callback6: from %s, ttl %d", + inet_ntop(AF_INET6, &remote.sin6_addr, buf, + sizeof(buf)), rcvdttl); + printf(" for %s hop %d\n", + inet_ntop(AF_INET6, &state->sin6.sin6_addr, buf, + sizeof(buf)), state->hop); +#endif + + if (late) + add_str(state, " }, { "); + + if (!late && !isDup) + { + if (state->duptimeout) + { + state->gotresp= 1; + interval.tv_sec= state->duptimeout/1000000; + interval.tv_usec= state->duptimeout % 1000000; + evtimer_add(&state->timer, &interval); + } + else + send_pkt(state); + } + } + else if (icmp->icmp6_type == ICMP6_ECHO_REQUEST /* 128 */ || + icmp->icmp6_type == MLD_LISTENER_QUERY /* 130 */ || + icmp->icmp6_type == MLD_LISTENER_REPORT /* 131 */ || + icmp->icmp6_type == ND_ROUTER_ADVERT /* 134 */ || + icmp->icmp6_type == ND_NEIGHBOR_SOLICIT /* 135 */ || + icmp->icmp6_type == ND_NEIGHBOR_ADVERT /* 136 */ || + icmp->icmp6_type == ND_REDIRECT /* 137 */) + { + /* No need to do anything */ + } + else + { + printf("ready_callback6: got type %d\n", icmp->icmp6_type); + return; + } +} + +static struct trtbase *traceroute_base_new(struct event_base + *event_base) +{ + int on; + struct trtbase *base; + + base= xzalloc(sizeof(*base)); + + base->event_base= event_base; + + base->tabsiz= 10; + base->table= xzalloc(base->tabsiz * sizeof(*base->table)); + + base->v4icmp_rcv= xsocket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + base->v6icmp_rcv= xsocket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); + base->v4icmp_snd= xsocket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + base->v6icmp_snd= xsocket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); + base->v4udp_snd= xsocket(AF_INET, SOCK_DGRAM, 0); + + base->my_pid= getpid(); + + on = 1; + setsockopt(base->v6icmp_rcv, IPPROTO_IPV6, IPV6_RECVPKTINFO, + &on, sizeof(on)); + + on = 1; + setsockopt(base->v6icmp_rcv, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, + &on, sizeof(on)); + + event_assign(&base->event4, base->event_base, base->v4icmp_rcv, + EV_READ | EV_PERSIST, ready_callback4, base); + event_assign(&base->event6, base->event_base, base->v6icmp_rcv, + EV_READ | EV_PERSIST, ready_callback6, base); + event_add(&base->event4, NULL); + event_add(&base->event6, NULL); + + return base; +} + +static void noreply_callback(int __attribute((unused)) unused, + const short __attribute((unused)) event, void *s) +{ + struct trtstate *state; + + state= s; + +#if 0 + printf("noreply_callback: gotresp = %d\n", + state->gotresp); +#endif + + if (!state->gotresp) + add_str(state, "\"x\":\"*\""); + + send_pkt(state); +} + +static void *traceroute_init(int __attribute((unused)) argc, char *argv[], + void (*done)(void *state)) +{ + uint32_t opt; + int i, do_icmp, do_v6, dont_fragment, delay_name_res; + unsigned count, duptimeout, firsthop, gaplimit, maxhops, maxpacksize, + parismod, timeout; /* must be int-sized */ + size_t newsiz; + char *str_Atlas; + const char *hostname; + char *out_filename; + struct trtstate *state; + sa_family_t af; + len_and_sockaddr *lsa; + FILE *fh; + + if (!trt_base) + { + trt_base= traceroute_base_new(EventBase); + if (!trt_base) + crondlog(DIE9 "traceroute_base_new failed"); + } + + /* Parse arguments */ + count= 3; + firsthop= 1; + gaplimit= 5; + maxhops= 32; + maxpacksize= 40; + duptimeout= 10; + timeout= 1000; + parismod= 16; + str_Atlas= NULL; + out_filename= NULL; + opt_complementary = "=1:4--6:i--u:a+:c+:f+:g+:m+:w+:z+:S+"; + opt = getopt32(argv, TRACEROUTE_OPT_STRING, &parismod, &count, + &firsthop, &gaplimit, &maxhops, &timeout, &duptimeout, + &str_Atlas, &out_filename, &maxpacksize); + hostname = argv[optind]; + + if (opt == 0xffffffff) + { + crondlog(LVL8 "bad options"); + return NULL; + } + + do_icmp= !!(opt & OPT_I); + do_v6= !!(opt & OPT_6); + dont_fragment= !!(opt & OPT_F); + delay_name_res= !!(opt & OPT_r); + if (maxpacksize > sizeof(trt_base->packet)) + maxpacksize= sizeof(trt_base->packet); + + if (out_filename) + { + if (!validate_filename(out_filename, SAFE_PREFIX)) + { + crondlog(LVL8 "insecure file '%s'", out_filename); + return NULL; + } + fh= fopen(out_filename, "a"); + if (!fh) + { + crondlog(LVL8 "unable to append to '%s'", + out_filename); + return NULL; + } + fclose(fh); + } + + if (!delay_name_res) + { + /* Attempt to resolve 'name' */ + af= do_v6 ? AF_INET6 : AF_INET; + lsa= host_and_af2sockaddr(hostname, 0, af); + if (!lsa) + return NULL; + + if (lsa->len > sizeof(state->sin6)) + { + free(lsa); + return NULL; + } + } + else + { + /* lint */ + lsa= NULL; + af= -1; + } + + state= xzalloc(sizeof(*state)); + state->parismod= parismod; + state->trtcount= count; + state->firsthop= firsthop; + state->maxpacksize= maxpacksize; + state->maxhops= maxhops; + state->gaplimit= gaplimit; + state->duptimeout= duptimeout*1000; + state->timeout= timeout*1000; + state->atlas= str_Atlas ? strdup(str_Atlas) : NULL; + state->hostname= strdup(hostname); + state->do_icmp= do_icmp; + state->do_v6= do_v6; + state->dont_fragment= dont_fragment; + state->delay_name_res= delay_name_res; + state->out_filename= out_filename ? strdup(out_filename) : NULL; + state->base= trt_base; + state->busy= 0; + state->result= NULL; + state->reslen= 0; + state->resmax= 0; + + for (i= 0; itabsiz; i++) + { + if (trt_base->table[i] == NULL) + break; + } + if (i >= trt_base->tabsiz) + { + newsiz= 2*trt_base->tabsiz; + trt_base->table= xrealloc(trt_base->table, + newsiz*sizeof(*trt_base->table)); + for (i= trt_base->tabsiz; itable[i]= NULL; + i= trt_base->tabsiz; + trt_base->tabsiz= newsiz; + } + state->index= i; + trt_base->table[i]= state; + trt_base->done= done; + + printf("traceroute_init: state %p, index %d\n", + state, state->index); + + memset(&state->loc_sin6, '\0', sizeof(state->loc_sin6)); + state->loc_socklen= 0; + + if (!delay_name_res) + { + memcpy(&state->sin6, &lsa->u.sa, lsa->len); + state->socklen= lsa->len; + free(lsa); lsa= NULL; + if (af == AF_INET6) + { + char buf[INET6_ADDRSTRLEN]; + printf("traceroute_init: %s, len %d for %s\n", + inet_ntop(AF_INET6, &state->sin6.sin6_addr, + buf, sizeof(buf)), state->socklen, + state->hostname); + } + } + + evtimer_assign(&state->timer, state->base->event_base, + noreply_callback, state); + + return state; +} + +static void traceroute_start2(void *state) +{ + int r, serrno; + struct trtstate *trtstate; + struct trtbase *trtbase; + struct sockaddr_in loc_sa4; + struct sockaddr_in6 loc_sa6; + char line[80]; + + trtstate= state; + trtbase= trtstate->base; + + if (trtstate->busy) + { + printf("traceroute_start: busy, can't start\n"); + return; + } + trtstate->busy= 1; + + trtstate->min= ULONG_MAX; + trtstate->max= 0; + trtstate->sum= 0; + trtstate->sentpkts= 0; + trtstate->rcvdpkts= 0; + trtstate->duppkts= 0; + + trtstate->hop= trtstate->firsthop; + trtstate->sent= 0; + trtstate->seq= 0; + trtstate->paris++; + trtstate->last_response_hop= 0; /* Should be starting hop */ + trtstate->done= 0; + trtstate->not_done= 0; + trtstate->lastditch= 0; + trtstate->curpacksize= trtstate->maxpacksize; + + if (trtstate->result) free(trtstate->result); + trtstate->resmax= 80; + trtstate->result= xmalloc(trtstate->resmax); + trtstate->reslen= 0; + trtstate->starttime= time(NULL); + + snprintf(line, sizeof(line), "{ \"hop\":%d", trtstate->hop); + add_str(trtstate, line); + + if (trtstate->do_icmp) + { + if (trtstate->do_v6) + { + memset(&loc_sa6, '\0', sizeof(loc_sa6)); + loc_sa6.sin6_family= AF_INET; + + r= connect(trtbase->v6icmp_snd, + (struct sockaddr *)&trtstate->sin6, + trtstate->socklen); +#if 0 + { errno= ENOSYS; r= -1; } +#endif + if (r == -1) + { + serrno= errno; + + snprintf(line, sizeof(line), + ", " DBQ(error) ":" DBQ(connect failed: %s) " }", + strerror(serrno)); + add_str(trtstate, line); + report(trtstate); + return; + } + trtstate->loc_socklen= sizeof(trtstate->loc_sin6); + if (getsockname(trtbase->v6icmp_snd, + &trtstate->loc_sin6, + &trtstate->loc_socklen) == -1) + { + crondlog(DIE9 "getsockname failed"); + } +#if 0 + printf("Got localname: %s\n", + inet_ntop(AF_INET6, + &trtstate->loc_sin6.sin6_addr, + buf, sizeof(buf))); +#endif + } + else + { + memset(&loc_sa4, '\0', sizeof(loc_sa4)); + loc_sa4.sin_family= AF_INET; + ((struct sockaddr_in *)&trtstate->sin6)->sin_port= + htons(0x8000); + + r= connect(trtbase->v4icmp_snd, + (struct sockaddr *)&trtstate->sin6, + trtstate->socklen); +#if 0 + { errno= ENOSYS; r= -1; } +#endif + if (r == -1) + { + serrno= errno; + + snprintf(line, sizeof(line), + ", " DBQ(error) ":" DBQ(connect failed: %s) " }", + strerror(serrno)); + add_str(trtstate, line); + report(trtstate); + return; + } + trtstate->loc_socklen= sizeof(trtstate->loc_sin6); + if (getsockname(trtbase->v4icmp_snd, + &trtstate->loc_sin6, + &trtstate->loc_socklen) == -1) + { + crondlog(DIE9 "getsockname failed"); + } +#if 0 + printf("Got localname: %s\n", + inet_ntoa(((struct sockaddr_in *) + &trtstate->loc_sin6)->sin_addr)); +#endif + } + } + else + { + if (trtstate->do_v6) + { + int sock; + + memset(&loc_sa6, '\0', sizeof(loc_sa6)); + loc_sa6.sin6_family= AF_INET6; + loc_sa6.sin6_port= htons(SRC_BASE_PORT + + trtstate->index);; + + sock= socket(AF_INET6, SOCK_DGRAM, 0); + if (sock == -1) + { + crondlog(DIE9 "socket failed"); + } +printf("traceroute_start2: before bind\n"); + r= bind(sock, (struct sockaddr *)&loc_sa6, + sizeof(loc_sa6)); + if (r == -1) + { + serrno= errno; + + snprintf(line, sizeof(line), + ", " DBQ(error) ":" DBQ(bind failed: %s) " }", + strerror(serrno)); + add_str(trtstate, line); + report(trtstate); + close(sock); + return; + } + + r= connect(sock, (struct sockaddr *)&trtstate->sin6, + trtstate->socklen); +#if 0 + { errno= ENOSYS; r= -1; } +#endif + if (r == -1) + { + serrno= errno; + + snprintf(line, sizeof(line), + ", " DBQ(error) ":" DBQ(connect failed: %s) " }", + strerror(serrno)); + add_str(trtstate, line); + report(trtstate); + return; + } + + trtstate->loc_socklen= sizeof(trtstate->loc_sin6); + if (getsockname(sock, + &trtstate->loc_sin6, + &trtstate->loc_socklen) == -1) + { + crondlog(DIE9 "getsockname failed"); + } + + close(sock); +#if 0 + printf("Got localname: %s:%d\n", + inet_ntop(AF_INET6, + &trtstate->loc_sin6.sin6_addr, + buf, sizeof(buf)), + ntohs(((struct sockaddr_in *)&trtstate-> + loc_sin6)->sin_port)); +#endif + } + else + { + int sock; + + memset(&loc_sa4, '\0', sizeof(loc_sa4)); + loc_sa4.sin_family= AF_INET; + + loc_sa4.sin_port= htons(SRC_BASE_PORT + + trtstate->index);; + + /* Also set destination port */ + ((struct sockaddr_in *)&trtstate->sin6)-> + sin_port= htons(BASE_PORT); + + sock= socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) + { + crondlog(DIE9 "socket failed"); + } + r= bind(sock, (struct sockaddr *)&loc_sa4, + sizeof(loc_sa4)); +#if 0 + { errno= ENOSYS; r= -1; } +#endif + if (r == -1) + { + serrno= errno; + + snprintf(line, sizeof(line), + ", " DBQ(error) ":" DBQ(bind failed: %s) " }", + strerror(serrno)); + add_str(trtstate, line); + report(trtstate); + close(sock); + return; + } + + r= connect(sock, (struct sockaddr *) &trtstate->sin6, + trtstate->socklen); +#if 0 + { errno= ENOSYS; r= -1; } +#endif + if (r == -1) + { + serrno= errno; + + snprintf(line, sizeof(line), + ", " DBQ(error) ":" DBQ(connect failed: %s) " }", + strerror(serrno)); + add_str(trtstate, line); + report(trtstate); + close(sock); + return; + } + trtstate->loc_socklen= sizeof(trtstate->loc_sin6); + if (getsockname(sock, + &trtstate->loc_sin6, + &trtstate->loc_socklen) == -1) + { + crondlog(DIE9 "getsockname failed"); + } + close(sock); +#if 0 + printf("Got localname: %s:%d\n", + inet_ntoa(((struct sockaddr_in *) + &trtstate->loc_sin6)->sin_addr), + ntohs(((struct sockaddr_in *)&trtstate-> + loc_sin6)->sin_port)); +#endif + } + } + + add_str(trtstate, ", \"result\": [ "); + + send_pkt(trtstate); +} + +static void dns_cb(int result, struct evutil_addrinfo *res, void *ctx) +{ + int count; + struct trtstate *env; + struct evutil_addrinfo *cur; + char line[160]; + + env= ctx; + + if (!env->dnsip) + { + crondlog(LVL7 + "dns_cb: in dns_cb but not doing dns at this time"); + if (res) + evutil_freeaddrinfo(res); + return; + } + + if (result != 0) + { + /* Hmm, great. Where do we put this init code */ + if (env->result) free(env->result); + env->resmax= 80; + env->result= xmalloc(env->resmax); + env->reslen= 0; + + env->starttime= time(NULL); + snprintf(line, sizeof(line), + "{ " DBQ(error) ":" DBQ(name resolution failed: %s) " }", + evutil_gai_strerror(result)); + add_str(env, line); + report(env); + return; + } + + env->dnsip= 0; + + env->dns_res= res; + env->dns_curr= res; + + count= 0; + for (cur= res; cur; cur= cur->ai_next) + count++; + + // env->reportcount(env, count); + + while (env->dns_curr) + { + env->socklen= env->dns_curr->ai_addrlen; + if (env->socklen > sizeof(env->sin6)) + continue; /* Weird */ + memcpy(&env->sin6, env->dns_curr->ai_addr, + env->socklen); + + traceroute_start2(env); + + evutil_freeaddrinfo(env->dns_res); + env->dns_res= NULL; + env->dns_curr= NULL; + return; + } + + /* Something went wrong */ + evutil_freeaddrinfo(env->dns_res); + env->dns_res= NULL; + env->dns_curr= NULL; + snprintf(line, sizeof(line), +"%s{ " DBQ(error) ":" DBQ(name resolution failed: out of addresses) " } ] }", + env->sent ? " }, " : ""); + add_str(env, line); + report(env); +} + +static void traceroute_start(void *state) +{ + struct trtstate *trtstate; + struct evutil_addrinfo hints; + + trtstate= state; + + if (!trtstate->delay_name_res) + { + traceroute_start2(state); + return; + } + + memset(&hints, '\0', sizeof(hints)); + hints.ai_socktype= SOCK_DGRAM; + hints.ai_family= trtstate->do_v6 ? AF_INET6 : AF_INET; + trtstate->dnsip= 1; + (void) evdns_getaddrinfo(DnsBase, trtstate->hostname, NULL, + &hints, dns_cb, trtstate); +} + +static int traceroute_delete(void *state) +{ + int ind; + struct trtstate *trtstate; + struct trtbase *base; + + trtstate= state; + + printf("traceroute_delete: state %p, index %d, busy %d\n", + state, trtstate->index, trtstate->busy); + + if (trtstate->busy) + return 0; + + base= trtstate->base; + ind= trtstate->index; + + if (base->table[ind] != trtstate) + crondlog(DIE9 "strange, state not in table"); + base->table[ind]= NULL; + + event_del(&trtstate->timer); + + free(trtstate->atlas); + trtstate->atlas= NULL; + free(trtstate->hostname); + trtstate->hostname= NULL; + free(trtstate->out_filename); + trtstate->out_filename= NULL; + + free(trtstate); + + return 1; +} + +struct testops traceroute_ops = { traceroute_init, traceroute_start, + traceroute_delete }; + -- cgit v1.2.3