aboutsummaryrefslogtreecommitdiff
path: root/eperd
diff options
context:
space:
mode:
authorBjørn Mork <bjorn@mork.no>2015-05-15 10:23:51 +0200
committerBjørn Mork <bjorn@mork.no>2015-05-15 10:23:51 +0200
commit02013228914a1d17e8df15d4e2b7950469395a5c (patch)
tree48d2fbe2f5a5adb60cbeabc26fadaec8e0fa82ed /eperd
parent9b3dbb454e8f8a463d5fe4541ee2001585527bc6 (diff)
ripe-atlas-fw: imported version 45204520
Signed-off-by: Bjørn Mork <bjorn@mork.no>
Diffstat (limited to 'eperd')
-rw-r--r--eperd/Config.in56
-rw-r--r--eperd/Kbuild8
-rw-r--r--eperd/condmv.c166
-rw-r--r--eperd/eooqd.c805
-rw-r--r--eperd/eperd.c1163
-rw-r--r--eperd/eperd.h51
-rw-r--r--eperd/evhttpget.c57
-rw-r--r--eperd/evping.c58
-rw-r--r--eperd/evtdig.c2131
-rw-r--r--eperd/evtraceroute.c56
-rw-r--r--eperd/httpget.c1760
-rw-r--r--eperd/ping.c1288
-rw-r--r--eperd/readresolv.c56
-rw-r--r--eperd/readresolv.h5
-rw-r--r--eperd/tcputil.c249
-rw-r--r--eperd/tcputil.h42
-rw-r--r--eperd/traceroute.c2866
17 files changed, 10817 insertions, 0 deletions
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 <atlas@ripe.net>
+#
+# 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 <atlas@ripe.net>
+ * 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 <atlas@ripe.net>
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ * eooqd.c Libevent-based One-off queue daemon
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include <libbb.h>
+#include <event2/event.h>
+#include <event2/event_struct.h>
+#include <event2/dns.h>
+
+#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; i<argc; i++)
+ crondlog(LVL7 "atlas_run: argv[%d] = '%s'", i, argv[i]);
+
+ cmdstate= bp->testops->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; i<state->max_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; i<state->max_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; i<argc; i++)
+ report("argv[%d] = '%s'", i, argv[i]);
+
+ saved_fd= -1; /* lint */
+ if (outfile)
+ {
+ /* Redirect I/O */
+ report("sending output to '%s'", outfile);
+ if (!validate_filename(outfile, SAFE_PREFIX))
+ {
+ report("insecure output file '%s'", outfile);
+ return;
+ }
+ flags= O_CREAT | O_WRONLY;
+ if (do_append)
+ flags |= O_APPEND;
+ out_fd= open(outfile, flags, 0644);
+ if (out_fd == -1)
+ {
+ report_err("unable to create output file '%s'",
+ outfile);
+ return;
+ }
+ fflush(stdout);
+ saved_fd= dup(1);
+ if (saved_fd == -1)
+ {
+ report("unable to dub stdout");
+ close(out_fd);
+ return;
+ }
+ dup2(out_fd, 1);
+ close(out_fd);
+ }
+
+ bp->func(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 <crondir> -f -b
+ *
+ * run as root, but NOT setuid root
+ *
+ * Copyright(c) 2013 RIPE NCC <atlas@ripe.net>
+ * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com)
+ * (version 2.3.2)
+ * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+#include <syslog.h>
+#include <event2/event.h>
+#include <event2/event_struct.h>
+#include <event2/dns.h>
+
+#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; i<argc; i++)
+ crondlog(LVL7 "atlas_run: argv[%d] = '%s'", i, argv[i]);
+
+ state= bp->testops->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 <atlas@ripe.net>
+ * 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 <atlas@ripe.net>
+ * 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 <syslog.h>
+#include <event2/event.h>
+#include <event2/event_struct.h>
+#include <event2/dns.h>
+
+#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 <atlas@ripe.net>
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ * Standalone version of the event-based ping.
+ */
+
+#include "libbb.h"
+#include <syslog.h>
+#include <event2/dns.h>
+#include <event2/event.h>
+#include <event2/event_struct.h>
+
+#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 <atlas@ripe.net>
+ * Copyright (c) 2009 Rocco Carbone <ro...@tecsiel.it>
+ * 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 <netdb.h>
+#include <getopt.h>
+#include <netinet/in.h>
+#include <netinet/ip_icmp.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <math.h>
+#include <assert.h>
+
+#include "eperd.h"
+#include "resolv.h"
+#include "readresolv.h"
+#include "tcputil.h"
+
+#include <event2/event.h>
+#include <event2/event_struct.h>
+#include <event2/dns.h>
+#include <event2/bufferevent.h>
+#include <event2/buffer.h>
+#include <event2/util.h>
+#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(;lock<i;lock++) {
+ *dns++=qry[lock];
+ }
+ lock++; //or lock=i+1;
+ }
+ }
+ *dns++=0;
+}
+
+
+static void free_qry_inst(struct query_state *qry)
+{
+ struct timeval asap = { 0, 0 };
+ BLURT(LVL5 "freeing instance of %s ", qry->server_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;i<iMax;i++)
+ {
+ answers[i].name=ReadName(result,wire_size,
+ reader-result,&stop);
+ reader = reader + stop;
+
+ answers[i].resource = (struct R_DATA*)(reader);
+ reader = reader + sizeof(struct R_DATA);
+
+ answers[i].rdata = NULL;
+
+
+ if(ntohs(answers[i].resource->type)==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;i<iMax;i++)
+ {
+ free(answers[i].name);
+ }
+
+ fprintf (fh , " }"); //result
+ }
+ if(qry->err.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 <atlas@ripe.net>
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ * Standalone version of the event-based traceroute.
+ */
+
+#include "libbb.h"
+#include <syslog.h>
+#include <event2/event.h>
+#include <event2/event_struct.h>
+
+#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 <atlas@ripe.net>
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ * httpget.c -- libevent-based version of httpget
+ */
+
+#include "libbb.h"
+#include <assert.h>
+#include <getopt.h>
+#include <event2/buffer.h>
+#include <event2/bufferevent.h>
+#include <event2/dns.h>
+#include <event2/event.h>
+#include <event2/event_struct.h>
+
+#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; i<hg_base->tabsiz; 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; i<newsiz; i++)
+ hg_base->table[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->subid<state->submax)
+ {
+ 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 <atlas@ripe.net>
+ * 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 <event2/dns.h>
+#include <event2/event.h>
+#include <event2/event_struct.h>
+
+#include <netinet/in.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/icmp6.h>
+
+#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; i<nrecv; i++)
+ printf(" %02x", base->packet[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; i<ping_base->tabsiz; 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; i<newsiz; i++)
+ ping_base->table[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 <atlas@ripe.net>
+ * 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 <atlas@ripe.net>
+ * 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 <atlas@ripe.net>
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ * tcputil.c
+ */
+
+#include "libbb.h"
+#include "eperd.h"
+#include <event2/bufferevent.h>
+#include <event2/dns.h>
+#include <event2/event.h>
+
+#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 <atlas@ripe.net>
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ * tcputil.h
+ */
+
+#include <event2/event_struct.h>
+
+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 <atlas@ripe.net>
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ * traceroute.c
+ */
+
+#include "libbb.h"
+#include <event2/dns.h>
+#include <event2/event.h>
+#include <event2/event_struct.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/ip6.h>
+#include <netinet/icmp6.h>
+#include <netinet/udp.h>
+
+#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; i<trt_base->tabsiz; 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; i<newsiz; i++)
+ trt_base->table[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 };
+