├── configure ├── .travis.yml ├── test ├── tinytest_local.h ├── unreach.c ├── tests.c ├── mmtrace.c ├── Makefile ├── tinytest.h ├── tinytest_macros.h ├── check_blackbox.c └── tinytest.c ├── report.c ├── xping.h ├── Controlflow.md ├── README.md ├── dnstask.c ├── xping.8 ├── Makefile ├── tricks.h ├── termio.c ├── xping.c ├── icmp-unpriv.c ├── icmp.c ├── http.c └── utlist.h /configure: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | exit 0 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | addons: 3 | apt: 4 | packages: 5 | - libevent-2.0 6 | compiler: clang 7 | language: c 8 | notifications: 9 | email: false 10 | sudo: required 11 | -------------------------------------------------------------------------------- /test/tinytest_local.h: -------------------------------------------------------------------------------- 1 | 2 | #include "util-internal.h" 3 | #ifdef _WIN32 4 | #include 5 | #endif 6 | 7 | #include "event2/util.h" 8 | 9 | #ifdef snprintf 10 | #undef snprintf 11 | #endif 12 | #define snprintf evutil_snprintf 13 | -------------------------------------------------------------------------------- /test/unreach.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Usage: 3 | * gcc unreach.c -fPIC -shared -o libunreach.so 4 | */ 5 | #include 6 | #include 7 | 8 | int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) 9 | { 10 | 11 | errno = ENETUNREACH; 12 | return -1; 13 | } 14 | -------------------------------------------------------------------------------- /test/tests.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include "tinytest.h" 5 | 6 | extern struct testcase_t tc_blackbox[]; 7 | 8 | struct testgroup_t groups[] = { 9 | {"blackbox/", tc_blackbox}, 10 | END_OF_GROUPS 11 | }; 12 | 13 | int 14 | main(int argc, const char *argv[]) 15 | { 16 | 17 | return tinytest_main(argc, argv, groups); 18 | } 19 | -------------------------------------------------------------------------------- /test/mmtrace.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Memory tracing in LD_PRELOAD 3 | * from https://stackoverflow.com/questions/2593284/enable-mtrace-malloc-trace-for-binary-program 4 | * fetched at 2018-07-18 5 | * 6 | * Usage: 7 | * gcc mtrace.c -fPIC -shared -o libmmtrace.so 8 | * MALLOC_TRACE=echo LD_PRELOAD=./libmmtrace.so /bin/echo 42 9 | */ 10 | #include 11 | #include 12 | 13 | 14 | void __mtracer_on(void) __attribute__((constructor)); 15 | /* 16 | * Avoid deactivating tracer to actually catch libc free()'s as well. 17 | * void __mtracer_off(void) __attribute__((destructor)); 18 | */ 19 | 20 | void __mtracer_on(void) 21 | { 22 | char *p=getenv("MALLOC_TRACE"); 23 | if(!p) 24 | return; 25 | /* 26 | * Avoid deactivating tracer to actually catch libc free()'s as well. 27 | * atexit(&__mtracer_off); 28 | */ 29 | mtrace(); 30 | } 31 | 32 | void __mtracer_off(void) 33 | { 34 | muntrace(); 35 | } 36 | -------------------------------------------------------------------------------- /report.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---------------------------------------------------------------------------- 3 | * "THE BEER-WARE LICENSE" (Revision 42): 4 | * wrote this file. As long as you retain this notice you 5 | * can do whatever you want with this stuff. If we meet some day, and you think 6 | * this stuff is worth it, you can buy me a beer in return Martin Topholm 7 | * ---------------------------------------------------------------------------- 8 | */ 9 | 10 | #include 11 | 12 | #include 13 | 14 | #include "xping.h" 15 | 16 | extern int w_width; 17 | 18 | void report_init() 19 | { 20 | } 21 | 22 | void report_update(struct target *t) 23 | { 24 | } 25 | 26 | void report_cleanup() 27 | { 28 | struct target *t; 29 | int i, imax, ifirst, ilast; 30 | 31 | t = list; 32 | if (t == NULL) 33 | return; 34 | 35 | imax = MIN(t->npkts, NUM); 36 | ifirst = (t->npkts > imax ? t->npkts - imax : 0); 37 | ilast = t->npkts; 38 | 39 | DL_FOREACH(list, t) { 40 | fprintf(stdout, "%*.*s", w_width, w_width, t->host); 41 | if (w_width) 42 | fputc(' ', stdout); 43 | for (i=ifirst; inpkts) fputc(t->res[i % NUM], stdout); 45 | else fputc(' ', stdout); 46 | } 47 | fputc('\n', stdout); 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS+=-Wall -Wpedantic -I.. 2 | 3 | PROFDATA=xping.profdata xping-unpriv.profdata xping-http.profdata 4 | 5 | .PHONY: all test $(PROFDATA) coverage clean 6 | 7 | test: tinytest mmtrace.so unreach.so 8 | test ! -O ../xping || sudo chown root ../xping 9 | test -u ../xping || sudo chmod 4750 ../xping 10 | ./tinytest || (find test.?????? -type f -name "std???" -print0 | xargs -0 head -50; exit 123) 11 | 12 | unreach.so: unreach.c 13 | $(CC) $(CFLAGS) $(LDFLAGS) -fPIC -shared -o $@ $^$> 14 | 15 | mmtrace.so: mmtrace.c 16 | $(CC) $(CFLAGS) $(LDFLAGS) -fPIC -shared -o $@ $^$> 2>/dev/null || \ 17 | echo "The mcheck is unavailable, wont do leak detection." 18 | 19 | tinytest: check_blackbox.c tests.c tinytest.c 20 | $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^$> 21 | 22 | $(PROFDATA): 23 | rm -f $@ 24 | -llvm-profdata merge -o $@ test.??????/$$(basename $@ .profdata).profraw 2>/dev/null 25 | 26 | coverage: $(PROFDATA) 27 | @for X in $(PROFDATA); do \ 28 | B=$$(basename $$X .profdata); \ 29 | printf "%-.20s:" $$B; \ 30 | echo -n "$$(git log --date=short --pretty="format:%ad%x20%h" -1) "; \ 31 | (llvm-cov report -instr-profile $$X ../$$B 2>/dev/null || echo "TOTAL 0.00%") | \ 32 | sed -nE '/TOTAL/s/TOTAL .* ([0-9\.]*)%.*/\1/p'; \ 33 | done 34 | 35 | clean: 36 | rm -rf test.?????? 37 | rm -f tinytest mmtrace.so unreach.so *.profdata 38 | -------------------------------------------------------------------------------- /xping.h: -------------------------------------------------------------------------------- 1 | #ifndef XPING_H 2 | #define XPING_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include "uthash.h" 11 | #include "utlist.h" 12 | 13 | #define NUM 300 14 | #define MAXHOST 64 15 | 16 | extern struct event_base *ev_base; 17 | extern struct target *list; 18 | extern int B_flag; 19 | extern int C_flag; 20 | extern int i_interval; 21 | extern int numtargets; 22 | extern int fd4, fd4errno; 23 | extern int fd6, fd6errno; 24 | 25 | union addr { 26 | struct sockaddr sa; 27 | struct sockaddr_in sin; 28 | struct sockaddr_in6 sin6; 29 | }; 30 | 31 | struct target { 32 | char host[64]; 33 | int npkts; 34 | char res[NUM+1]; 35 | 36 | struct probe *prb; 37 | struct event *ev_write; 38 | 39 | int row; 40 | int af; 41 | 42 | struct target *prev, *next; 43 | }; 44 | 45 | #define sa(x) ((struct sockaddr *)(&x->sa)) 46 | #define sin(x) ((struct sockaddr_in *)(&x->sa)) 47 | #define sin6(x) ((struct sockaddr_in6 *)(&x->sa)) 48 | 49 | void target_mark(struct target *, int, int); 50 | void target_unmark(struct target *, int); 51 | void target_resolved(struct target *, int, void *); 52 | 53 | /* from "version.c" */ 54 | extern const char version[]; 55 | 56 | /* from termio.c */ 57 | void termio_init(void); 58 | void termio_update(struct target *); 59 | void termio_cleanup(void); 60 | 61 | /* from report.c */ 62 | void report_init(void); 63 | void report_update(struct target *); 64 | void report_cleanup(void); 65 | 66 | /* from icmp.c */ 67 | void probe_setup(); 68 | void probe_cleanup(); 69 | struct probe *probe_new(const char *, void *); 70 | void probe_free(struct probe *); 71 | void probe_send(struct probe *, int); 72 | 73 | /* from dnstask.c */ 74 | typedef void (*dnstask_cb_type)(int, void *, void *); 75 | struct dnstask *dnstask_new(const char *, dnstask_cb_type, void *); 76 | void dnstask_free(struct dnstask *); 77 | 78 | #endif /* !XPING_H */ 79 | -------------------------------------------------------------------------------- /Controlflow.md: -------------------------------------------------------------------------------- 1 | FLOW 2 | ---- 3 | 4 | Below is a diagram outlining control flow within xping.c: 5 | 6 | main() [xping.c] | [dnstask.c] 7 | | | 8 | | | .-> dnstask_new 9 | | | | v 10 | | | | v 11 | | | | sendquery ------. 12 | | | | | 13 | | +-------------------------- 14 | | | | | 15 | | | | [icmp.c] | 16 | | | | | 17 | |---------------------------------------------------|-(-> probe_setup ----(--. 18 | | | | | | 19 | | | '----. | | 20 | |--> target_add ------------------------------------|--> probe_add -------(-.| 21 | | | | || 22 | | target_resolved <----------------|--- resolved <<------' || 23 | | | | |--> activate <--'| 24 | | '-> ui_update | '--> deactivate | 25 | | ^ ^ | | 26 | |->> target_probe_sched | | | | 27 | | | | | | | 28 | | '->> target_probe --(---------------------|--> probe_send | 29 | | | | | 30 | |--> ui_init '-- target_mark <-----|--- read_packet <<------' 31 | | | 32 | '--> event_base_dispatch | 33 | 34 | 35 | --> direct execution 36 | ->> event triggered execution 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | xping 2 | ===== 3 | 4 | xping is a simple PING program continiously probing multiple hosts 5 | using ICMP-ECHO. As packets are received dots are printed on the screen. 6 | Hosts not responding before next packet is due will get a questionmark 7 | in the display: 8 | 9 | 192.0.2.1 ..................................... 10 | 192.0.2.2 ????.????????????.???.?.?............ 11 | 192.0.2.3 ..................................... 12 | 192.0.2.4 ..................................... 13 | 14 | It is similar to the second visual display in "my traceroute's" (aka mtr) 15 | and provide a way to spot subtle availability changes. 16 | 17 | 18 | Binary packages 19 | --------------- 20 | 21 | Prebuilt amd64 binary packages for Debian and Arch Linux are available 22 | from http://martin.topholm.eu/pub/xping . 23 | 24 | Previously APT and pacman repositories were maintained as well, but 25 | these are now deprecated. 26 | 27 | 28 | Building 29 | -------- 30 | 31 | xping hasn't surrendered to autoconf yet, but it should build on Linux, 32 | FreeBSD and OSX. There are a few options in the Makefile that can be 33 | enabled by uncommenting them. OSX users with Terminal.app may wish to 34 | use ncurses. 35 | 36 | xping depends on libevent, if you don't have it you can uncomment the 37 | "static linking" option in the Makefile to have it downloaded and linked 38 | static into xping. OSX doesn't use -lrt, but may need 39 | -Wno-deprecated-declarations in order to build WITH_SSL. 40 | 41 | vi Makefile 42 | make 43 | make install 44 | 45 | N.B. xping will be installed set-uid because most platforms requires 46 | superuser privileges to open RAW sockets. xping drops the privileges 47 | when it has opened the sockets. 48 | 49 | 50 | Changes 51 | ------- 52 | 53 | v1.4.2 54 | 55 | * fix segfault in http module 56 | 57 | v1.4.1 58 | 59 | * fix resource leaks in http module 60 | * fix stalled http transfers 61 | 62 | v1.4.0 63 | 64 | * add openssl support (see -DWITH_SSL in Makefile) 65 | * add colorsupport for results 66 | * fixed missed packet checking before -c count limit 67 | * fix startup bell for -aa 68 | 69 | v1.3.1 70 | 71 | * fix icmp sequence number wrap 72 | * fix for http, treat code 200-399 as successful 73 | 74 | v1.3 75 | 76 | * http client probe 77 | * label width adjustment 78 | * marking of replys arriving after timeout 79 | * only open raw socket when needed 80 | 81 | v1.2 82 | 83 | * unprivileged icmp-unpriv module 84 | * split out icmp function in seperate module 85 | * ui improvements (selective updates, disable local-echo) 86 | * probe count (to limit execution) 87 | * report generation (when stdout is a file) 88 | 89 | v1.1 90 | 91 | * replaced BSD queue.h with utlist.h by Troy D. Hanson 92 | * improved dns lookup 93 | * dynamic linking 94 | * target list from stdin 95 | * edge detection 96 | * raw ansi as alternative to ncurses 97 | 98 | v1.0 99 | 100 | * hash table lookup 101 | * ipv6 102 | 103 | 104 | Authors 105 | ------- 106 | 107 | xping was written by Martin Topholm. 108 | 109 | xping uses libevent2 by Niels Provos and Nick Mathewson, and 110 | uthash and utlist by Troy D. Hanson. 111 | -------------------------------------------------------------------------------- /dnstask.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---------------------------------------------------------------------------- 3 | * "THE BEER-WARE LICENSE" (Revision 42): 4 | * wrote this file. As long as you retain this notice you 5 | * can do whatever you want with this stuff. If we meet some day, and you think 6 | * this stuff is worth it, you can buy me a beer in return Martin Topholm 7 | * ---------------------------------------------------------------------------- 8 | */ 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | 20 | #include "xping.h" 21 | 22 | #define MAXHOST 64 23 | 24 | extern int v4_flag; 25 | extern int v6_flag; 26 | extern int T_flag; 27 | extern struct event_base *ev_base; 28 | extern struct evdns_base *dns; 29 | 30 | struct dnstask { 31 | dnstask_cb_type cb; 32 | void *thunk; 33 | char host[MAXHOST]; 34 | struct event *ev_resolve; 35 | }; 36 | 37 | /* 38 | * Reschedule a DNS request. 39 | */ 40 | static void 41 | reschedule(struct event *ev_resolve, int seconds) 42 | { 43 | struct timeval tv; 44 | 45 | evutil_timerclear(&tv); 46 | tv.tv_sec = seconds; 47 | event_add(ev_resolve, &tv); 48 | } 49 | 50 | /* 51 | * Callback for A-records resolver results. When failed (NXDOMAIN, 52 | * SERVFAIL, at al), reschedule a new request. 53 | */ 54 | static void 55 | response_ipv4(int result, char type, int count, int ttl, void *addresses, 56 | void *thunk) 57 | { 58 | struct dnstask *task = thunk; 59 | 60 | if (result == DNS_ERR_NONE && count > 0) { 61 | task->cb(AF_INET, addresses, task->thunk); 62 | /* Schedule new request, enforce a lower bound on ttl. */ 63 | if (T_flag) 64 | reschedule(task->ev_resolve, MAX(ttl, 1)); 65 | } else { 66 | task->cb(0, NULL, task->thunk); 67 | /* neg-TTL might be search domain's */ 68 | reschedule(task->ev_resolve, 60); 69 | } 70 | } 71 | 72 | /* 73 | * Callback for AAAA-records resolver results. When failed (NXDOMAIN, 74 | * SERVFAIL, at al), retry for A-record. Otherwise reschedule a new 75 | * request. All diagnostics about the failed request are useless, since 76 | * they might refer to a search domain request, which is done transparently 77 | * after the real request. 78 | */ 79 | static void 80 | response_ipv6(int result, char type, int count, int ttl, void *addresses, 81 | void *thunk) 82 | { 83 | struct dnstask *task = thunk; 84 | 85 | if (result == DNS_ERR_NONE && count > 0) { 86 | task->cb(AF_INET6, addresses, task->thunk); 87 | /* Schedule new request, enforce a lower bound on ttl. */ 88 | if (T_flag) 89 | reschedule(task->ev_resolve, MAX(ttl, 1)); 90 | } else { 91 | if (!v6_flag) { 92 | evdns_base_resolve_ipv4(dns, task->host, 0, 93 | response_ipv4, thunk); 94 | } else { 95 | /* neg-TTL might be search domain's */ 96 | reschedule(task->ev_resolve, 60); 97 | task->cb(0, NULL, task->thunk); 98 | } 99 | } 100 | } 101 | 102 | /* 103 | * Send DNS request using evdns. Try IPv6 first unless IPv4 is 104 | * forced. Missing AAAA-records are handled in response_ipv6. 105 | */ 106 | static void 107 | sendquery(int fd, short what, void *thunk) 108 | { 109 | struct dnstask *task = thunk; 110 | if (!v4_flag) { 111 | evdns_base_resolve_ipv6(dns, task->host, 0, 112 | response_ipv6, task); 113 | } else { 114 | evdns_base_resolve_ipv4(dns, task->host, 0, 115 | response_ipv4, task); 116 | } 117 | 118 | } 119 | 120 | /* 121 | * Set up a dnstask for resolving a given hostname and schedule 122 | * initial resolving. 123 | */ 124 | struct dnstask * 125 | dnstask_new(const char *hostname, dnstask_cb_type cb, void *thunk) 126 | { 127 | struct dnstask *task; 128 | struct timeval tv; 129 | 130 | task = calloc(1, sizeof(*task)); 131 | if (task == NULL) 132 | return NULL; 133 | assert(strlen(hostname) + 1 <= sizeof(task->host)); 134 | strncat(task->host, hostname, sizeof(task->host) - 1); 135 | task->cb = cb; 136 | task->thunk = thunk; 137 | task->ev_resolve = event_new(ev_base, -1, 0, sendquery, task); 138 | if (task->ev_resolve == NULL) { 139 | free(task); 140 | return NULL; 141 | } 142 | evutil_timerclear(&tv); 143 | event_add(task->ev_resolve, &tv); 144 | return task; 145 | } 146 | 147 | /* 148 | * Remove and free a previous dnstask. 149 | */ 150 | void 151 | dnstask_free(struct dnstask *task) 152 | { 153 | event_free(task->ev_resolve); 154 | free(task); 155 | } 156 | -------------------------------------------------------------------------------- /test/tinytest.h: -------------------------------------------------------------------------------- 1 | /* tinytest.h -- Copyright 2009-2012 Nick Mathewson 2 | * 3 | * Redistribution and use in source and binary forms, with or without 4 | * modification, are permitted provided that the following conditions 5 | * are met: 6 | * 1. Redistributions of source code must retain the above copyright 7 | * notice, this list of conditions and the following disclaimer. 8 | * 2. Redistributions in binary form must reproduce the above copyright 9 | * notice, this list of conditions and the following disclaimer in the 10 | * documentation and/or other materials provided with the distribution. 11 | * 3. The name of the author may not be used to endorse or promote products 12 | * derived from this software without specific prior written permission. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | #ifndef TINYTEST_H_INCLUDED_ 27 | #define TINYTEST_H_INCLUDED_ 28 | 29 | /** Flag for a test that needs to run in a subprocess. */ 30 | #define TT_FORK (1<<0) 31 | /** Runtime flag for a test we've decided to skip. */ 32 | #define TT_SKIP (1<<1) 33 | /** Internal runtime flag for a test we've decided to run. */ 34 | #define TT_ENABLED_ (1<<2) 35 | /** Flag for a test that's off by default. */ 36 | #define TT_OFF_BY_DEFAULT (1<<3) 37 | /** If you add your own flags, make them start at this point. */ 38 | #define TT_FIRST_USER_FLAG (1<<4) 39 | 40 | typedef void (*testcase_fn)(void *); 41 | 42 | struct testcase_t; 43 | 44 | /** Functions to initialize/teardown a structure for a testcase. */ 45 | struct testcase_setup_t { 46 | /** Return a new structure for use by a given testcase. */ 47 | void *(*setup_fn)(const struct testcase_t *); 48 | /** Clean/free a structure from setup_fn. Return 1 if ok, 0 on err. */ 49 | int (*cleanup_fn)(const struct testcase_t *, void *); 50 | }; 51 | 52 | /** A single test-case that you can run. */ 53 | struct testcase_t { 54 | const char *name; /**< An identifier for this case. */ 55 | testcase_fn fn; /**< The function to run to implement this case. */ 56 | unsigned long flags; /**< Bitfield of TT_* flags. */ 57 | const struct testcase_setup_t *setup; /**< Optional setup/cleanup fns*/ 58 | void *setup_data; /**< Extra data usable by setup function */ 59 | }; 60 | #define END_OF_TESTCASES { NULL, NULL, 0, NULL, NULL } 61 | 62 | /** A group of tests that are selectable together. */ 63 | struct testgroup_t { 64 | const char *prefix; /**< Prefix to prepend to testnames. */ 65 | struct testcase_t *cases; /** Array, ending with END_OF_TESTCASES */ 66 | }; 67 | #define END_OF_GROUPS { NULL, NULL} 68 | 69 | struct testlist_alias_t { 70 | const char *name; 71 | const char **tests; 72 | }; 73 | #define END_OF_ALIASES { NULL, NULL } 74 | 75 | /** Implementation: called from a test to indicate failure, before logging. */ 76 | void tinytest_set_test_failed_(void); 77 | /** Implementation: called from a test to indicate that we're skipping. */ 78 | void tinytest_set_test_skipped_(void); 79 | /** Implementation: return 0 for quiet, 1 for normal, 2 for loud. */ 80 | int tinytest_get_verbosity_(void); 81 | /** Implementation: Set a flag on tests matching a name; returns number 82 | * of tests that matched. */ 83 | int tinytest_set_flag_(struct testgroup_t *, const char *, int set, unsigned long); 84 | /** Implementation: Put a chunk of memory into hex. */ 85 | char *tinytest_format_hex_(const void *, unsigned long); 86 | 87 | /** Set all tests in 'groups' matching the name 'named' to be skipped. */ 88 | #define tinytest_skip(groups, named) \ 89 | tinytest_set_flag_(groups, named, 1, TT_SKIP) 90 | 91 | /** Run a single testcase in a single group. */ 92 | int testcase_run_one(const struct testgroup_t *,const struct testcase_t *); 93 | 94 | void tinytest_set_aliases(const struct testlist_alias_t *aliases); 95 | 96 | /** Run a set of testcases from an END_OF_GROUPS-terminated array of groups, 97 | as selected from the command line. */ 98 | int tinytest_main(int argc, const char **argv, struct testgroup_t *groups); 99 | 100 | #endif 101 | -------------------------------------------------------------------------------- /xping.8: -------------------------------------------------------------------------------- 1 | .\" 2 | .\" ---------------------------------------------------------------------------- 3 | .\" "THE BEER-WARE LICENSE" (Revision 42): 4 | .\" wrote this file. As long as you retain this notice you 5 | .\" can do whatever you want with this stuff. If we meet some day, and you think 6 | .\" this stuff is worth it, you can buy me a beer in return Martin Topholm 7 | .\" ---------------------------------------------------------------------------- 8 | .\" 9 | .Dd November 14, 2012 10 | .Dt XPING 8 11 | .Os 12 | .Sh NAME 13 | .Nm xping , 14 | .Nm xping-http 15 | .Nd A terminal based, adhoc, multi target probe tool 16 | .Sh SYNOPSIS 17 | .Nm xping , 18 | .Nm xping-http 19 | .Op Fl 46ABCTVah 20 | .Op Fl c Ar count 21 | .Op Fl i Ar interval 22 | .Op Fl w Ar width 23 | .Op Ar target Op ... 24 | .Sh DESCRIPTION 25 | .Nm 26 | is a simple ping program continiously probing multiple hosts using 27 | ICMP-ECHO. As packets are received dots are printed on the screen. 28 | Hosts not responding before next packet is due will get a questionmark 29 | in the display. The probing stops when SIGINT is received. Host to be 30 | probed are taken from argument list or read from stdin. 31 | .Pp 32 | .Nm xping-http 33 | performs a full TCP connect and a slim HTTP GET request. Responses 34 | are scanned for a HTTP 200 response code and printed as dots on the 35 | screen. Requests time out three times after specified transmit 36 | .Ar interval . 37 | Thus three requests are inflight at a time. 38 | .Pp 39 | .Sh OPTIONS 40 | .Bl -tag -width indent 41 | .It Fl 4 42 | Force xping to resolve IPv4 address only. 43 | .It Fl 6 44 | Force xping to resolve IPv6 address only. 45 | .It Fl A 46 | Print a bell character on stdout when a packet is missed within 47 | .Ar interval . 48 | If given multiple times only print bell if host goes from a responsive 49 | state to unresponsive state (e.g. ..??). 50 | .It Fl B 51 | Show success/failures using ANSI colors (not supported with ncurses). 52 | .It Fl C 53 | Color resolved hostname according to address family (IPv4 red, IPv6 green). 54 | .It Fl T 55 | Track changes to resolved hostname, honoring TTL values. If not specified 56 | xping will still retry unresolved hostnames. 57 | .It Fl V 58 | Print the "version" of xping and exit. 59 | .It Fl a 60 | Print a bell character on stdout on replies. If given multiple times 61 | only print bell if host goes from unresponsive to responsive (e.g. ??..). 62 | .It Fl c Ar count 63 | Send count probes in a non-interactive fashion. Default is to operate 64 | interactively and keep sending probes. 65 | .It Fl h 66 | Display program usage and exit. 67 | .It Fl i Ar interval 68 | Specifies interval between successive packets to a host. Default 69 | is 1.0 seconds. 70 | .It Fl w Ar width 71 | Let host labels be 72 | .Ar width 73 | characters wide. Default is 20. 74 | .El 75 | .Sh LEGEND 76 | .Bl -tag -width indent 77 | .It . 78 | Reply received. 79 | .It : 80 | Reply received after timeout (recovered). 81 | .It ? 82 | Timeout - interval passed without any reply. 83 | .It # 84 | Unreachable - the packet could not be delivered. 85 | .It % 86 | Other error e.g. TTL expired in transit. 87 | .It @ 88 | Resolving - waiting for dns lookup to complete. 89 | .It ! 90 | sendto error e.g. permission denied or no route to destination. 91 | .It \(dq 92 | Duplicate of some other entry. 93 | .El 94 | .Pp 95 | .Nm 96 | inteprets the 97 | .Ar target 98 | as either ipv4 address (in dotted decimal notation), ipv6 address, 99 | or as a hostname. 100 | .Nm xping-http 101 | inteprets the 102 | .Ar target 103 | as an url with optional brackets after the hostname to force a specific 104 | address to be used. 105 | .Bd -literal 106 | http://192.0.2.100/ 107 | http://www.google.com:80/ 108 | http://www.google.com[127.0.0.1]/ 109 | .Ed 110 | .Sh DIAGNOSTICS 111 | .Bl -tag -width indent 112 | .It "socket: Operation not permitted" 113 | .Nm 114 | could not open a raw socket. Program should be setuid root or started 115 | as root. 116 | .It "regcomp: error compiling regular expression" 117 | .Nm xping-http 118 | could not compile the regular expression used for parsing urls. This 119 | usually means a programming error. 120 | .It "probe_add: can't parse ..." 121 | .Nm xping-http 122 | could not parse url to something meaningful. Check that argument is a 123 | valid url. 124 | .El 125 | .Sh AUTHORS 126 | .Nm 127 | was written by 128 | .An Martin Topholm . 129 | .Pp 130 | .Nm 131 | uses 132 | .Nm libevent2 133 | by Niels Provos and Nick Mathewson, and 134 | .Nm uthash 135 | and 136 | .Nm utlist 137 | by Troy D. Hanson. Also used is assertion tricks by Poul-Henning Kamp. 138 | .Sh BUGS 139 | .Nm 140 | is not very informative on errors. Terminal display may flicker when 141 | running without ncurses. 142 | .Pp 143 | Probably more. 144 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile for xping 3 | # 4 | 5 | LIBEVENT=libevent-2.0.22-stable 6 | LIBEVENTDIR=release-2.0.22-stable 7 | 8 | PREFIX?=/usr/local 9 | BINPATH=$(PREFIX)/bin 10 | MANPATH=$(PREFIX)/man 11 | CFLAGS+=-Wall -Werror -Wpedantic -I/usr/local/include 12 | LDFLAGS+=-L/usr/local/lib -L/usr/local/lib/event2 13 | COVFLAGS=-fprofile-instr-generate -fcoverage-mapping 14 | DEPS+=check-libevent.c 15 | OBJS+=termio.o report.o version.o dnstask.o 16 | LIBS+=-levent 17 | VERSION="`git describe --tags --always --dirty=+ 2>/dev/null || echo v1.4.2`" 18 | 19 | # Link with ncurses 20 | #DEPS+=check-curses.c 21 | #CFLAGS+=-DNCURSES 22 | #LIBS+=-lcurses 23 | 24 | # Dynamic libevent_openssl linking 25 | # (OSX may need -Wno-deprecated-declarations) 26 | #CFLAGS+=-Wno-deprecated-declarations 27 | DEPS+=check-openssl.c 28 | CFLAGS+=-DWITH_SSL 29 | LIBS+=-levent_openssl -lssl 30 | 31 | # Static libevent linking (OSX doesn't use -lrt) 32 | #CFLAGS+=-I./$(LIBEVENT)/include 33 | #DEPS=libevent.a 34 | #LIBS=-lrt 35 | # Static libevent_openssl linking 36 | #DEPS+=check-openssl.c libevent_openssl.a 37 | #CFLAGS+=-DWITH_SSL 38 | #LIBS+=-lssl -lcrypto 39 | 40 | .PHONY: version.o all install test test_coverage clean 41 | 42 | all: xping xping.8.gz xping-unpriv xping-http 43 | 44 | check-libevent.c: 45 | @/bin/echo -n 'Checking for libevent... '; \ 46 | (echo '#include '; \ 47 | echo '#include '; \ 48 | echo 'int main()'; \ 49 | echo '{ printf("%s\\n", event_get_version()); return 0; }' \ 50 | ) | $(CC) $(CFLAGS) $(LDFLAGS) -x c -o /dev/null - -levent >/dev/null 2>/dev/null && echo yes || \ 51 | (echo no; \ 52 | echo ""; \ 53 | echo "libevent not available in usual locations"; \ 54 | echo "adjust CFLAGS and LDFLAGS appropriately"; \ 55 | echo ""; false) 56 | @touch $@ 57 | 58 | check-curses.c: 59 | @/bin/echo -n 'Checking for libcurses... '; \ 60 | (echo '#include '; \ 61 | echo '#include '; \ 62 | echo 'int main()'; \ 63 | echo '{ initscr(); return 0; }' \ 64 | ) | $(CC) $(CFLAGS) -x c -o /dev/null - -lcurses >/dev/null 2>/dev/null && echo yes || \ 65 | (echo no; \ 66 | echo ""; \ 67 | echo "libcurses not available in usual locations"; \ 68 | echo "adjust CFLAGS and LDFLAGS appropriately"; \ 69 | echo ""; false) 70 | @touch $@ 71 | 72 | check-openssl.c: 73 | @/bin/echo -n 'Checking for openssl... '; \ 74 | (echo '#include '; \ 75 | echo '#include '; \ 76 | echo 'int main()'; \ 77 | echo '{ printf("%d\\n", SSL_library_init()); return 0; }' \ 78 | ) | $(CC) $(CFLAGS) $(LDFLAGS) -x c -o /dev/null - -lssl >/dev/null 2>/dev/null && echo yes || \ 79 | (echo no; \ 80 | echo ""; \ 81 | echo "openssl not available in usual locations"; \ 82 | echo "adjust CFLAGS and LDFLAGS appropriately"; \ 83 | echo ""; false) 84 | @touch $@ 85 | 86 | $(LIBEVENT)/.libs: 87 | test -f $(LIBEVENT).tar.gz || \ 88 | wget https://github.com/libevent/libevent/releases/download/$(LIBEVENTDIR)/$(LIBEVENT).tar.gz || \ 89 | wget https://sourceforge.net/projects/levent/files/libevent/libevent-2.0/$(LIBEVENT).tar.gz 90 | test -d $(LIBEVENT) || tar -xzvf $(LIBEVENT).tar.gz 91 | test -f ./$(LIBEVENT)/.libs/$@ || cd $(LIBEVENT) && ./configure && make 92 | 93 | libevent.a libevent_openssl.a: $(LIBEVENT) 94 | cp ./$(LIBEVENT)/.libs/$@ . 95 | size $@ 96 | 97 | version.o: 98 | printf "const char version[] = \"%s\";\n" $(VERSION) | \ 99 | $(CC) -x c -c -o $@ - 100 | 101 | xping-raw.o: xping.c 102 | $(CC) $(CFLAGS) -DDO_SOCK_RAW -c -o $@ $^$> 103 | 104 | xping: xping-raw.o icmp.o $(OBJS) $(DEPS) 105 | $(CC) $(LDFLAGS) -o $@ $^$> $(LIBS) 106 | 107 | xping-unpriv: xping.o icmp-unpriv.o $(OBJS) $(DEPS) 108 | $(CC) $(LDFLAGS) -o $@ $^$> $(LIBS) 109 | 110 | xping-http: xping.o http.o $(OBJS) $(DEPS) 111 | $(CC) $(LDFLAGS) -o $@ $^$> $(LIBS) 112 | 113 | xping.8.gz: xping.8 114 | gzip -9 -c $^$> > $@ 115 | 116 | install: 117 | mkdir -p $(BINPATH) 118 | mkdir -p $(MANPATH)/man8 119 | install -m 4555 xping $(BINPATH)/ 120 | install -m 555 xping-http $(BINPATH)/ 121 | install -m 444 xping.8.gz $(MANPATH)/man8/ 122 | ln -f $(MANPATH)/man8/xping.8.gz $(MANPATH)/man8/xping-http.8.gz 123 | 124 | clean: 125 | make -C test clean 126 | rm -f xping xping.8.gz xping-http xping-unpriv \ 127 | xping.o xping-raw.o http.o icmp.o icmp-unpriv.o \ 128 | $(OBJS) $(DEPS) 129 | 130 | test: 131 | make -C test test 132 | 133 | test_coverage: 134 | env CFLAGS="$$CFLAGS $(COVFLAGS)" LDFLAGS="$$LDFLAGS $(COVFLAGS)" \ 135 | make -C . clean all 136 | make -C test test coverage 137 | 138 | # Object dependencies (gcc -MM *.c) 139 | dnstask.o: dnstask.c xping.h uthash.h utlist.h 140 | http.o: http.c xping.h uthash.h utlist.h 141 | icmp.o: icmp.c xping.h uthash.h utlist.h 142 | icmp-unpriv.o: icmp-unpriv.c xping.h uthash.h utlist.h 143 | report.o: report.c xping.h uthash.h utlist.h 144 | termio.o: termio.c xping.h uthash.h utlist.h 145 | xping.o: xping.c xping.h uthash.h utlist.h 146 | -------------------------------------------------------------------------------- /tricks.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2014 Poul-Henning Kamp 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 | * SUCH DAMAGE. 25 | */ 26 | 27 | #ifdef TRICKS_H 28 | #error "tricks.h included multiple times" 29 | #endif 30 | #define TRICKS_H 31 | 32 | #include 33 | 34 | /********************************************************************** 35 | * Assert and friends 36 | * 37 | * We always runs with asserts enabled, and we assert liberally. 38 | * 39 | * Tests which are too expensive for production use should be wrapped 40 | * in "#ifdef DIAGNOSTICS" 41 | * 42 | */ 43 | 44 | #undef NDEBUG // Asserts *always* enabled. 45 | 46 | #define AZ(foo) do { assert((foo) == 0); } while (0) 47 | #define AN(foo) do { assert((foo) != 0); } while (0) 48 | 49 | #define WRONG(foo) \ 50 | do { \ 51 | /*lint -save -e506 */ \ 52 | assert(0 == (uintptr_t)foo); \ 53 | /*lint -restore */ \ 54 | } while (0) 55 | 56 | /********************************************************************** 57 | * Safe printfs into compile-time fixed-size buffers. 58 | */ 59 | 60 | #define bprintf(buf, fmt, ...) \ 61 | do { \ 62 | assert(snprintf(buf, sizeof buf, fmt, __VA_ARGS__) \ 63 | < (int)sizeof buf); \ 64 | } while (0) 65 | 66 | #define vbprintf(buf, fmt, ap) \ 67 | do { \ 68 | assert(vsnprintf(buf, sizeof buf, fmt, ap) \ 69 | < (int)sizeof buf); \ 70 | } while (0) 71 | 72 | /********************************************************************** 73 | * FlexeLint shutuppery 74 | * 75 | * Flexelint is a commercial LINT program from gimpel.com, and a very 76 | * good one at that. We need a few special-case markups to tell it 77 | * our intentions. 78 | */ 79 | 80 | /* 81 | * In OO-light situations, functions have to match their prototype 82 | * even if that means not const'ing a const'able argument. 83 | * The typedef should be specified as argument to the macro. 84 | * 85 | */ 86 | #define __match_proto__(xxx) /*lint -e{818} */ 87 | 88 | /* 89 | * Some functions never return, and there's nothing Turing can do about it. 90 | * Some compilers think you should still include a final return(bla) in 91 | * such functions, while other tools complain about unreachable code. 92 | * Wrapping in a macro means we can shut the tools up. 93 | */ 94 | 95 | #define NEEDLESS_RETURN(foo) return(foo) 96 | 97 | /********************************************************************** 98 | * Compiler tricks 99 | */ 100 | 101 | #ifndef __printflike 102 | #define __printflike(a, b) 103 | #endif 104 | 105 | /********************************************************************** 106 | * Mini object type checking 107 | * 108 | * This is a trivial struct type checking framework I have used with 109 | * a lot of success in a number of high-rel software projects over the 110 | * years. It is particularly valuable when you pass things trough void 111 | * pointers or opaque APIs. 112 | * 113 | * Define your struct like this: 114 | * struct foobar { 115 | * unsigned magic; 116 | * #define FOOBAR_MAGIC 0x23923092 117 | * ... 118 | * } 119 | * 120 | * The "magic" element SHALL be the first element of the struct. 121 | * 122 | * The MAGIC number you get from "od -x < /dev/random | head -1" or 123 | * similar. It is important that each structure has its own distinct 124 | * value. 125 | * 126 | */ 127 | 128 | #define INIT_OBJ(to, type_magic) \ 129 | do { \ 130 | (void)memset(to, 0, sizeof *to); \ 131 | (to)->magic = (type_magic); \ 132 | } while (0) 133 | 134 | #define ALLOC_OBJ(to, type_magic) \ 135 | do { \ 136 | (to) = calloc(1L, sizeof *(to)); \ 137 | if ((to) != NULL) \ 138 | (to)->magic = (type_magic); \ 139 | } while (0) 140 | 141 | #define FREE_OBJ(to) \ 142 | do { \ 143 | (to)->magic = (0); \ 144 | free(to); \ 145 | } while (0) 146 | 147 | #define VALID_OBJ(ptr, type_magic) \ 148 | ((ptr) != NULL && (ptr)->magic == (type_magic)) 149 | 150 | #define CHECK_OBJ(ptr, type_magic) \ 151 | do { \ 152 | assert((ptr)->magic == type_magic); \ 153 | } while (0) 154 | 155 | #define CHECK_OBJ_NOTNULL(ptr, type_magic) \ 156 | do { \ 157 | assert((ptr) != NULL); \ 158 | assert((ptr)->magic == type_magic); \ 159 | } while (0) 160 | 161 | #define CHECK_OBJ_ORNULL(ptr, type_magic) \ 162 | do { \ 163 | if ((ptr) != NULL) \ 164 | assert((ptr)->magic == type_magic); \ 165 | } while (0) 166 | 167 | #define CAST_OBJ(to, from, type_magic) \ 168 | do { \ 169 | (to) = (from); \ 170 | if ((to) != NULL) \ 171 | CHECK_OBJ((to), (type_magic)); \ 172 | } while (0) 173 | 174 | #define CAST_OBJ_NOTNULL(to, from, type_magic) \ 175 | do { \ 176 | (to) = (from); \ 177 | assert((to) != NULL); \ 178 | CHECK_OBJ((to), (type_magic)); \ 179 | } while (0) 180 | -------------------------------------------------------------------------------- /test/tinytest_macros.h: -------------------------------------------------------------------------------- 1 | /* tinytest_macros.h -- Copyright 2009-2012 Nick Mathewson 2 | * 3 | * Redistribution and use in source and binary forms, with or without 4 | * modification, are permitted provided that the following conditions 5 | * are met: 6 | * 1. Redistributions of source code must retain the above copyright 7 | * notice, this list of conditions and the following disclaimer. 8 | * 2. Redistributions in binary form must reproduce the above copyright 9 | * notice, this list of conditions and the following disclaimer in the 10 | * documentation and/or other materials provided with the distribution. 11 | * 3. The name of the author may not be used to endorse or promote products 12 | * derived from this software without specific prior written permission. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | #ifndef TINYTEST_MACROS_H_INCLUDED_ 27 | #define TINYTEST_MACROS_H_INCLUDED_ 28 | 29 | /* Helpers for defining statement-like macros */ 30 | #define TT_STMT_BEGIN do { 31 | #define TT_STMT_END } while (0) 32 | 33 | /* Redefine this if your test functions want to abort with something besides 34 | * "goto end;" */ 35 | #ifndef TT_EXIT_TEST_FUNCTION 36 | #define TT_EXIT_TEST_FUNCTION TT_STMT_BEGIN goto end; TT_STMT_END 37 | #endif 38 | 39 | /* Redefine this if you want to note success/failure in some different way. */ 40 | #ifndef TT_DECLARE 41 | #define TT_DECLARE(prefix, args) \ 42 | TT_STMT_BEGIN \ 43 | printf("\n %s %s:%d: ",prefix,__FILE__,__LINE__); \ 44 | printf args ; \ 45 | TT_STMT_END 46 | #endif 47 | 48 | /* Announce a failure. Args are parenthesized printf args. */ 49 | #define TT_GRIPE(args) TT_DECLARE("FAIL", args) 50 | 51 | /* Announce a non-failure if we're verbose. */ 52 | #define TT_BLATHER(args) \ 53 | TT_STMT_BEGIN \ 54 | if (tinytest_get_verbosity_()>1) TT_DECLARE(" OK", args); \ 55 | TT_STMT_END 56 | 57 | #define TT_DIE(args) \ 58 | TT_STMT_BEGIN \ 59 | tinytest_set_test_failed_(); \ 60 | TT_GRIPE(args); \ 61 | TT_EXIT_TEST_FUNCTION; \ 62 | TT_STMT_END 63 | 64 | #define TT_FAIL(args) \ 65 | TT_STMT_BEGIN \ 66 | tinytest_set_test_failed_(); \ 67 | TT_GRIPE(args); \ 68 | TT_STMT_END 69 | 70 | /* Fail and abort the current test for the reason in msg */ 71 | #define tt_abort_printf(msg) TT_DIE(msg) 72 | #define tt_abort_perror(op) TT_DIE(("%s: %s [%d]",(op),strerror(errno), errno)) 73 | #define tt_abort_msg(msg) TT_DIE(("%s", msg)) 74 | #define tt_abort() TT_DIE(("%s", "(Failed.)")) 75 | 76 | /* Fail but do not abort the current test for the reason in msg. */ 77 | #define tt_failprint_f(msg) TT_FAIL(msg) 78 | #define tt_fail_perror(op) TT_FAIL(("%s: %s [%d]",(op),strerror(errno), errno)) 79 | #define tt_fail_msg(msg) TT_FAIL(("%s", msg)) 80 | #define tt_fail() TT_FAIL(("%s", "(Failed.)")) 81 | 82 | /* End the current test, and indicate we are skipping it. */ 83 | #define tt_skip() \ 84 | TT_STMT_BEGIN \ 85 | tinytest_set_test_skipped_(); \ 86 | TT_EXIT_TEST_FUNCTION; \ 87 | TT_STMT_END 88 | 89 | #define tt_want_(b, msg, fail) \ 90 | TT_STMT_BEGIN \ 91 | if (!(b)) { \ 92 | tinytest_set_test_failed_(); \ 93 | TT_GRIPE(("%s",msg)); \ 94 | fail; \ 95 | } else { \ 96 | TT_BLATHER(("%s",msg)); \ 97 | } \ 98 | TT_STMT_END 99 | 100 | /* Assert b, but do not stop the test if b fails. Log msg on failure. */ 101 | #define tt_want_msg(b, msg) \ 102 | tt_want_(b, msg, ); 103 | 104 | /* Assert b and stop the test if b fails. Log msg on failure. */ 105 | #define tt_assert_msg(b, msg) \ 106 | tt_want_(b, msg, TT_EXIT_TEST_FUNCTION); 107 | 108 | /* Assert b, but do not stop the test if b fails. */ 109 | #define tt_want(b) tt_want_msg( (b), "want("#b")") 110 | /* Assert b, and stop the test if b fails. */ 111 | #define tt_assert(b) tt_assert_msg((b), "assert("#b")") 112 | 113 | #define tt_assert_test_fmt_type(a,b,str_test,type,test,printf_type,printf_fmt, \ 114 | setup_block,cleanup_block,die_on_fail) \ 115 | TT_STMT_BEGIN \ 116 | type val1_ = (a); \ 117 | type val2_ = (b); \ 118 | int tt_status_ = (test); \ 119 | if (!tt_status_ || tinytest_get_verbosity_()>1) { \ 120 | printf_type print_; \ 121 | printf_type print1_; \ 122 | printf_type print2_; \ 123 | type value_ = val1_; \ 124 | setup_block; \ 125 | print1_ = print_; \ 126 | value_ = val2_; \ 127 | setup_block; \ 128 | print2_ = print_; \ 129 | TT_DECLARE(tt_status_?" OK":"FAIL", \ 130 | ("assert(%s): "printf_fmt" vs "printf_fmt, \ 131 | str_test, print1_, print2_)); \ 132 | print_ = print1_; \ 133 | cleanup_block; \ 134 | print_ = print2_; \ 135 | cleanup_block; \ 136 | if (!tt_status_) { \ 137 | tinytest_set_test_failed_(); \ 138 | die_on_fail ; \ 139 | } \ 140 | } \ 141 | TT_STMT_END 142 | 143 | #define tt_assert_test_type(a,b,str_test,type,test,fmt,die_on_fail) \ 144 | tt_assert_test_fmt_type(a,b,str_test,type,test,type,fmt, \ 145 | {print_=value_;},{},die_on_fail) 146 | 147 | #define tt_assert_test_type_opt(a,b,str_test,type,test,fmt,die_on_fail) \ 148 | tt_assert_test_fmt_type(a,b,str_test,type,test,type,fmt, \ 149 | {print_=value_?value_:"";},{},die_on_fail) 150 | 151 | /* Helper: assert that a op b, when cast to type. Format the values with 152 | * printf format fmt on failure. */ 153 | #define tt_assert_op_type(a,op,b,type,fmt) \ 154 | tt_assert_test_type(a,b,#a" "#op" "#b,type,(val1_ op val2_),fmt, \ 155 | TT_EXIT_TEST_FUNCTION) 156 | 157 | #define tt_int_op(a,op,b) \ 158 | tt_assert_test_type(a,b,#a" "#op" "#b,long,(val1_ op val2_), \ 159 | "%ld",TT_EXIT_TEST_FUNCTION) 160 | 161 | #define tt_uint_op(a,op,b) \ 162 | tt_assert_test_type(a,b,#a" "#op" "#b,unsigned long, \ 163 | (val1_ op val2_),"%lu",TT_EXIT_TEST_FUNCTION) 164 | 165 | #define tt_ptr_op(a,op,b) \ 166 | tt_assert_test_type(a,b,#a" "#op" "#b,const void*, \ 167 | (val1_ op val2_),"%p",TT_EXIT_TEST_FUNCTION) 168 | 169 | #define tt_str_op(a,op,b) \ 170 | tt_assert_test_type_opt(a,b,#a" "#op" "#b,const char *, \ 171 | (val1_ && val2_ && strcmp(val1_,val2_) op 0),"<%s>", \ 172 | TT_EXIT_TEST_FUNCTION) 173 | 174 | #define tt_mem_op(expr1, op, expr2, len) \ 175 | tt_assert_test_fmt_type(expr1,expr2,#expr1" "#op" "#expr2, \ 176 | const void *, \ 177 | (val1_ && val2_ && memcmp(val1_, val2_, len) op 0), \ 178 | char *, "%s", \ 179 | { print_ = tinytest_format_hex_(value_, (len)); }, \ 180 | { if (print_) free(print_); }, \ 181 | TT_EXIT_TEST_FUNCTION \ 182 | ); 183 | 184 | #define tt_want_int_op(a,op,b) \ 185 | tt_assert_test_type(a,b,#a" "#op" "#b,long,(val1_ op val2_),"%ld",(void)0) 186 | 187 | #define tt_want_uint_op(a,op,b) \ 188 | tt_assert_test_type(a,b,#a" "#op" "#b,unsigned long, \ 189 | (val1_ op val2_),"%lu",(void)0) 190 | 191 | #define tt_want_ptr_op(a,op,b) \ 192 | tt_assert_test_type(a,b,#a" "#op" "#b,const void*, \ 193 | (val1_ op val2_),"%p",(void)0) 194 | 195 | #define tt_want_str_op(a,op,b) \ 196 | tt_assert_test_type(a,b,#a" "#op" "#b,const char *, \ 197 | (strcmp(val1_,val2_) op 0),"<%s>",(void)0) 198 | 199 | #endif 200 | -------------------------------------------------------------------------------- /test/check_blackbox.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "tinytest.h" 23 | #include "tinytest_macros.h" 24 | 25 | 26 | #define BLACKBOX_OUTDIRS_MAX 3 27 | char blackbox_outdirs[3][16] = { {0}, {0}, {0} }; 28 | 29 | struct context { 30 | const struct testcase_t *testcase; 31 | char path[FILENAME_MAX]; 32 | char name[FILENAME_MAX]; 33 | }; 34 | 35 | #define EXEC_MTRACE 0x01 36 | #define EXEC_UNREACH 0x02 37 | #define EXEC_FDSLIM_MASK 0xf0 38 | #define EXEC_FDSLIM_SHIFT 4 39 | 40 | static pid_t 41 | exec_wd(int flags, char *file, ...) 42 | { 43 | va_list ap; 44 | char *argv[128]; 45 | int i; 46 | pid_t pid; 47 | struct rlimit rlim; 48 | 49 | if (flags & EXEC_MTRACE && flags & EXEC_UNREACH) 50 | return 1; 51 | va_start(ap, file); 52 | argv[0] = file; 53 | for (i = 1; i < sizeof(argv)/sizeof(char *); i ++) { 54 | argv[i] = va_arg(ap, char *); 55 | if (argv[i] == NULL) 56 | break; 57 | } 58 | va_end(ap); 59 | switch (pid = fork()) { 60 | case -1: 61 | return -1; 62 | case 0: 63 | freopen("stdout", "w+", stdout); 64 | freopen("stderr", "w+", stderr); 65 | if (flags & EXEC_MTRACE) { 66 | setenv("MALLOC_TRACE", "trace", 1); 67 | setenv("LD_PRELOAD", "../mmtrace.so", 1); 68 | } 69 | if (flags & EXEC_UNREACH) { 70 | setenv("LD_PRELOAD", "../unreach.so", 1); 71 | } 72 | if (flags & EXEC_FDSLIM_MASK) { 73 | rlim.rlim_cur = rlim.rlim_max = (flags & EXEC_FDSLIM_MASK) >> EXEC_FDSLIM_SHIFT; 74 | if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) 75 | abort(); 76 | } 77 | execvp(file, argv); 78 | perror("execvp"); 79 | abort(); 80 | default: 81 | return pid; 82 | } 83 | } 84 | 85 | static int 86 | sock_listen(unsigned short *port) 87 | { 88 | struct sockaddr_in sin; 89 | socklen_t sa_len; 90 | int fd; 91 | 92 | memset(&sin, 0, sizeof(sin)); 93 | if (inet_pton(AF_INET, "127.0.0.1", 94 | (struct sockaddr *)&sin.sin_addr) == 0) 95 | return -1; 96 | sin.sin_family = AF_INET; 97 | sin.sin_port = htons(*port); 98 | fd = socket(sin.sin_family, SOCK_STREAM, 6); 99 | if (fd < 0) 100 | return -1; 101 | if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) 102 | goto err; 103 | sa_len = sizeof(sin); 104 | if (getsockname(fd, (struct sockaddr *)&sin, &sa_len) < 0) 105 | goto err; 106 | *port = ntohs(sin.sin_port); 107 | if (listen(fd, 8) < 0) 108 | goto err; 109 | return fd; 110 | err: 111 | close(fd); 112 | return -1; 113 | } 114 | 115 | static int 116 | regex(char *filename, const char *regex) 117 | { 118 | char buf[4096]; 119 | int fd; 120 | ssize_t len; 121 | regex_t preg; 122 | int n; 123 | 124 | fd = open(filename, O_RDONLY); 125 | len = read(fd, buf, sizeof(buf) - 1); 126 | close(fd); 127 | if (len < 0) 128 | return -1; 129 | buf[len] = '\0'; 130 | 131 | if (regcomp(&preg, regex, REG_EXTENDED | REG_NOSUB) != 0) 132 | return -1; 133 | n = regexec(&preg, buf, 0, NULL, 0); 134 | regfree(&preg); 135 | if (n != 0) 136 | return -1; 137 | regfree(&preg); 138 | return 0; 139 | 140 | } 141 | 142 | static int 143 | has_dots(char *filename) 144 | { 145 | 146 | if (regex(filename, "\\.{4}") < 0) 147 | return 0; 148 | return 1; 149 | } 150 | 151 | static int 152 | exists(char *filename) 153 | { 154 | struct stat st; 155 | 156 | if (stat(filename, &st) != 0) 157 | return 0; 158 | return 1; 159 | } 160 | 161 | static void 162 | test_xping_localhost(void *ctx_) 163 | { 164 | struct context *ctx = ctx_; 165 | int wstatus; 166 | pid_t pid; 167 | 168 | strcpy(ctx->name, "xping"); 169 | pid = exec_wd(0, "../../xping", "-c", "4", "127.0.0.1", NULL); 170 | tt_uint_op(pid, >, 0); 171 | waitpid(pid, &wstatus, 0); 172 | tt_assert(WIFEXITED(wstatus)); 173 | tt_assert(WEXITSTATUS(wstatus) == 0); 174 | tt_assert(has_dots("stdout")); 175 | 176 | end: 177 | ; 178 | } 179 | 180 | static void 181 | test_xping_unpriv_localhost(void *ctx_) 182 | { 183 | struct context *ctx = ctx_; 184 | int wstatus; 185 | pid_t pid; 186 | 187 | strcpy(ctx->name, "xping-unpriv"); 188 | pid = exec_wd(0, "../../xping-unpriv", "-c", "4", "127.0.0.1", NULL); 189 | tt_uint_op(pid, >, 0); 190 | waitpid(pid, &wstatus, 0); 191 | tt_assert(WEXITSTATUS(wstatus) == 0); 192 | tt_assert(has_dots("stdout")); 193 | 194 | end: 195 | ; 196 | } 197 | 198 | static void 199 | test_xping_http_localhost(void *ctx_) 200 | { 201 | struct context *ctx = ctx_; 202 | char url[32]; 203 | char buf[4096]; 204 | char response[] = "HTTP/1.0 200 OK\r\n\r\n"; 205 | unsigned short listen_port; 206 | struct timeval tv = {2, 0}; 207 | int wstatus; 208 | pid_t pid; 209 | int fd_srv; 210 | int fd; 211 | ssize_t n; 212 | int max_req; 213 | int exec_flags; 214 | 215 | listen_port = 0; 216 | fd_srv = sock_listen(&listen_port); 217 | tt_assert(fd_srv >= 0); 218 | snprintf(url, sizeof(url), "http://127.0.0.1:%hu", listen_port); 219 | 220 | strcpy(ctx->name, "xping-http"); 221 | exec_flags = 0; 222 | if (strcmp(ctx->testcase->name, "xping-http-localhost") == 0) { 223 | strcpy(blackbox_outdirs[2], ctx->path); 224 | if (exists("../mmtrace.so")) 225 | exec_flags |= EXEC_MTRACE; 226 | } else if (strcmp(ctx->testcase->name, "connect-unreach-http") == 0) { 227 | if (!exists("../unreach.so")) 228 | tt_skip(); 229 | exec_flags |= EXEC_UNREACH; 230 | } else if (strcmp(ctx->testcase->name, "fd-leakage-http") == 0) { 231 | exec_flags |= 10 << EXEC_FDSLIM_SHIFT; 232 | } 233 | pid = exec_wd(exec_flags, "../../xping-http", "-c", "4", url, NULL); 234 | tt_assert(pid > 0); 235 | tt_assert(setsockopt(fd_srv, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == 0); 236 | for (max_req = 4; max_req > 0; max_req--) { 237 | fd = accept(fd_srv, NULL, 0); 238 | if (fd < 0) 239 | break; 240 | n = read(fd, buf, sizeof(buf)); 241 | if (n < 1) 242 | break; 243 | write(fd, response, strlen(response)); 244 | close(fd); 245 | } 246 | waitpid(pid, &wstatus, 0); 247 | tt_assert(WIFEXITED(wstatus)); 248 | tt_assert(WEXITSTATUS(wstatus) == 0); 249 | if (strcmp(ctx->testcase->name, "connect-unreach-http") == 0) 250 | tt_assert(regex("stdout", "[!#]{4}") == 0) 251 | else 252 | tt_assert(has_dots("stdout")); 253 | 254 | end: 255 | close(fd_srv); 256 | ; 257 | } 258 | 259 | static void 260 | test_memory_leakage(void *ctx_) 261 | { 262 | char path[FILENAME_MAX]; 263 | int i; 264 | pid_t pid; 265 | int wstatus; 266 | 267 | if (!exists("../mmtrace.so") || LIBEVENT_VERSION_NUMBER < 0x02010001 ) 268 | tt_skip(); 269 | for (i=0; iname); 294 | rename("default.profraw", buf); 295 | if (ctx->path[0]) 296 | if (chdir("..") < 0) 297 | return 0; 298 | free(ctx); 299 | return 1; 300 | } 301 | 302 | static void * 303 | setup(const struct testcase_t *testcase) 304 | { 305 | char buf[512]; 306 | struct context *ctx; 307 | int fd; 308 | 309 | ctx = calloc(1, sizeof(*ctx)); 310 | if (ctx == NULL) 311 | return NULL; 312 | ctx->testcase = testcase; 313 | strcpy(buf, "test.XXXXXX"); 314 | if (mkdtemp(buf) == NULL) 315 | goto err; 316 | if (chdir(buf) < 0) 317 | goto err; 318 | fd = open("default.profraw", O_WRONLY | O_CREAT, 0666); 319 | close(fd); 320 | strcpy(ctx->path, buf); 321 | return ctx; 322 | err: 323 | cleanup(testcase, ctx); 324 | return NULL; 325 | } 326 | 327 | static struct testcase_setup_t tc_setup = {setup, cleanup}; 328 | 329 | struct testcase_t tc_blackbox[] = { 330 | {"xping-localhost", test_xping_localhost, 0, &tc_setup}, 331 | {"xping-unpriv-localhost", test_xping_unpriv_localhost, 332 | TT_OFF_BY_DEFAULT, &tc_setup}, 333 | {"xping-http-localhost", test_xping_http_localhost, 0, &tc_setup}, 334 | {"fd-leakage-http", test_xping_http_localhost, 0, &tc_setup}, 335 | {"connect-unreach-http", test_xping_http_localhost, 0, &tc_setup}, 336 | {"memory-leakage", test_memory_leakage, 0, &tc_setup}, 337 | END_OF_TESTCASES 338 | }; 339 | -------------------------------------------------------------------------------- /termio.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---------------------------------------------------------------------------- 3 | * "THE BEER-WARE LICENSE" (Revision 42): 4 | * wrote this file. As long as you retain this notice you 5 | * can do whatever you want with this stuff. If we meet some day, and you think 6 | * this stuff is worth it, you can buy me a beer in return Martin Topholm 7 | * ---------------------------------------------------------------------------- 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #ifndef NCURSES 21 | #define stdscr 22 | #else /* NCURSES */ 23 | #include 24 | #endif /* !NCURSES */ 25 | 26 | #include "xping.h" 27 | 28 | static int ifirst_state = -1; 29 | static int holding_row = 0; 30 | static int labelwidth; 31 | 32 | extern int w_width; 33 | 34 | #ifndef NCURSES 35 | static int cursor_y; 36 | static char *scrbuffer; 37 | struct termios oterm; 38 | 39 | int 40 | getmaxx(void) 41 | { 42 | struct winsize wsz; 43 | char *p; 44 | 45 | if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &wsz) != -1 && 46 | wsz.ws_col > 0) 47 | return (wsz.ws_col); 48 | else if ((p = getenv("COLUMNS")) != NULL && *p != '\0') 49 | return atoi(p); 50 | else 51 | return 0; 52 | } 53 | 54 | int 55 | getmaxy(void) 56 | { 57 | struct winsize wsz; 58 | char *p; 59 | 60 | if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &wsz) != -1 && 61 | wsz.ws_row > 0) 62 | return (wsz.ws_row); 63 | else if ((p = getenv("ROWS")) != NULL && *p != '\0') 64 | return atoi(p); 65 | else 66 | return 0; 67 | } 68 | void 69 | move(int row, int col) 70 | { 71 | fprintf(stdout, "%c[u", 0x1b); 72 | if (row > 0) 73 | fprintf(stdout, "%c[%dB", 0x1b, row); 74 | if (col > 0) 75 | fprintf(stdout, "%c[%dC", 0x1b, col); 76 | cursor_y = row; 77 | } 78 | 79 | static void 80 | clrtobot(void) 81 | { 82 | fprintf(stdout, "%c[J", 0x1b); 83 | } 84 | 85 | static void 86 | clrtoeol(void) 87 | { 88 | fprintf(stdout, "%c[K", 0x1b); 89 | } 90 | 91 | static void 92 | addch(int ch) 93 | { 94 | 95 | fputc(ch, stdout); 96 | } 97 | 98 | static void 99 | printw(const char *fmt, ...) 100 | { 101 | va_list ap; 102 | 103 | fprintf(stdout, "%c[K", 0x1b); 104 | va_start(ap, fmt); 105 | vfprintf(stdout, fmt, ap); 106 | va_end(ap); 107 | } 108 | 109 | static void 110 | mvprintw(int row, int col, const char *fmt, ...) 111 | { 112 | va_list ap; 113 | 114 | move(row, col); 115 | fprintf(stdout, "%c[K", 0x1b); 116 | va_start(ap, fmt); 117 | vfprintf(stdout, fmt, ap); 118 | va_end(ap); 119 | } 120 | 121 | static void 122 | refresh(void) 123 | { 124 | fflush(stdout); 125 | } 126 | 127 | static void 128 | scrollup(int n) 129 | { 130 | int i; 131 | 132 | for (i=0; i < n; i++) 133 | fprintf(stdout, "%cM", 0x1b); 134 | } 135 | 136 | static void 137 | scrolldown(int n) 138 | { 139 | int i; 140 | 141 | for (i=0; i < n; i++) 142 | fprintf(stdout, "%cD", 0x1b); 143 | } 144 | #endif 145 | 146 | /* 147 | * Window changed: move up from current line (cursor_y) to the "first" 148 | * line to the origin row (cursor_y = 0), clear screen and redraw. 149 | * We may continue in the middle of an update, but even so the rest of the 150 | * interrupted update will use the new reference point for its move(). 151 | */ 152 | #ifndef NCURSES 153 | void 154 | sigwinch(int sig) 155 | { 156 | if (cursor_y > 0) 157 | fprintf(stdout, "%c[%dA\r", 0x1b, cursor_y); 158 | fprintf(stdout, "\r%c[s", 0x1b); 159 | cursor_y = 0; 160 | fprintf(stdout, "%c[2K\r", 0x1b); 161 | clrtobot(); 162 | termio_update(NULL); /* XXX: this is probably a bad idea */ 163 | } 164 | #endif /* !NCURSES */ 165 | 166 | 167 | /* 168 | * Return a color based on success/failure 169 | */ 170 | static char * 171 | getcolor(int ch) 172 | { 173 | 174 | switch(ch) { 175 | case '.': 176 | return "\x1b[2;34m"; /* blue */ 177 | case ':': 178 | return "\x1b[6;33m"; /* yellow */ 179 | case '?': 180 | case '#': 181 | return "\x1b[1;31m"; /* red */ 182 | case '%': 183 | return "\x1b[2;35m"; /* magenta */ 184 | case '@': 185 | return "\x1b[5;32m"; /* green */ 186 | case '!': 187 | return "\x1b[3;31m"; /* red */ 188 | case '"': 189 | return "\x1b[5;33m"; /* yellow */ 190 | } 191 | return ""; 192 | } 193 | 194 | static void 195 | drawchar(int ch) 196 | { 197 | 198 | 199 | if (!B_flag) { 200 | addch(ch); 201 | } else { 202 | printw("%s%c\x1b[0m", getcolor(ch), ch); 203 | } 204 | } 205 | 206 | /* 207 | * Update a single reponse with redrawing entire output. Initial move 208 | * compensates for labelwidth (plus space) and move into the current 209 | * packet position. 210 | */ 211 | static void 212 | updatesingle(int ifirst, struct target *t) 213 | { 214 | move(t->row, labelwidth+(t->npkts-1-ifirst)); 215 | drawchar(t->res[(t->npkts-1) % NUM]); 216 | move(holding_row, 0); 217 | } 218 | 219 | static void 220 | updatefull(int ifirst, int ilast) 221 | { 222 | struct target *t; 223 | int row; 224 | int i; 225 | 226 | row = 0; 227 | DL_FOREACH(list, t) { 228 | t->row = row; /* cache for selective updates */ 229 | if (C_flag && t->af == AF_INET6) 230 | mvprintw(row, 0, "%c[2;32m%*.*s%c[0m", 231 | 0x1b, w_width, w_width, t->host, 0x1b); 232 | else if (C_flag && t->af == AF_INET) 233 | mvprintw(row, 0, "%c[2;31m%*.*s%c[0m", 234 | 0x1b, w_width, w_width, t->host, 0x1b); 235 | else 236 | mvprintw(row, 0, "%*.*s", w_width, w_width, t->host); 237 | if (w_width) 238 | drawchar(' '); 239 | for (i=ifirst; inpkts) 241 | drawchar(t->res[i % NUM]); 242 | else 243 | drawchar(' '); 244 | } 245 | move(++row, 0); 246 | } 247 | holding_row = row; 248 | } 249 | 250 | /* 251 | * Prepares the terminal for drawing. For !NCURSES this means handling 252 | * window resize, output buffering, scroll issues and other output mangling. 253 | */ 254 | void 255 | termio_init(void) 256 | { 257 | #ifndef NCURSES 258 | struct termios term; 259 | struct target *t; 260 | int x, y; 261 | 262 | signal(SIGWINCH, sigwinch); 263 | x = getmaxx(); 264 | y = getmaxy(); 265 | if (x > 0 && y > 0) { 266 | scrbuffer = malloc(x * y); 267 | if (scrbuffer != NULL) 268 | setvbuf(stdout, scrbuffer, _IOFBF, x * y); 269 | else 270 | perror("malloc"); 271 | } else { 272 | scrbuffer = NULL; 273 | } 274 | 275 | /* Reserve space on terminal */ 276 | cursor_y = 0; 277 | DL_FOREACH(list, t) { 278 | cursor_y++; 279 | scrolldown(1); 280 | } 281 | scrollup(cursor_y); 282 | 283 | /* Establish reference point for move() */ 284 | fprintf(stdout, "\r%c[s", 0x1b); 285 | cursor_y = 0; 286 | 287 | /* Avoid mangling output by disabling input echo and wrapping */ 288 | fprintf(stdout, "%c[7l", 0x1b); 289 | if (isatty(STDOUT_FILENO) && tcgetattr(STDOUT_FILENO, &oterm) == 0) { 290 | memcpy(&term, &oterm, sizeof(term)); 291 | term.c_lflag &= ~(ECHO | ECHONL); 292 | tcsetattr(STDOUT_FILENO, TCSAFLUSH, &term); 293 | } 294 | #else /* NCURSES */ 295 | initscr(); 296 | #endif /* !NCURSES */ 297 | labelwidth = (w_width > 0 ? w_width + 1 : 0); 298 | } 299 | 300 | /* 301 | * Draws the recorded replies on the terminal. 302 | */ 303 | void 304 | termio_update(struct target *selective) 305 | { 306 | struct target *t; 307 | int col; 308 | 309 | int imax, ifirst, ilast; 310 | 311 | t = list; 312 | if (t == NULL) 313 | return; 314 | 315 | col = getmaxx(stdscr); 316 | imax = MIN(t->npkts, col - labelwidth); 317 | imax = MIN(imax, NUM); 318 | ifirst = (t->npkts > imax ? t->npkts - imax : 0); 319 | ilast = t->npkts; 320 | 321 | if (selective != NULL && ifirst == ifirst_state) { 322 | updatesingle(ifirst, selective); 323 | } else { 324 | #ifndef NCURSES 325 | /* Re-establish the reference point for move() */ 326 | if (cursor_y > 0) 327 | fprintf(stdout, "%c[%dA\r", 0x1b, cursor_y); 328 | fprintf(stdout, "\r%c[s", 0x1b); 329 | cursor_y = 0; 330 | #endif /* !NCURSES */ 331 | updatefull(ifirst, ilast); 332 | ifirst_state = ifirst; 333 | clrtoeol(); 334 | clrtobot(); 335 | } 336 | refresh(); 337 | } 338 | 339 | /* 340 | * Clean up screen and restore old modes 341 | */ 342 | void 343 | termio_cleanup(void) 344 | { 345 | #ifndef NCURSES 346 | setvbuf(stdout, NULL, _IONBF, 0); 347 | if (scrbuffer) 348 | free(scrbuffer); 349 | if (isatty(STDIN_FILENO)) 350 | tcsetattr(STDIN_FILENO, TCSAFLUSH, &oterm); // XXX: TCASOFT? see openssh 351 | #else /* NCURSES */ 352 | struct target *t; 353 | int col, i; 354 | int imax, ifirst, ilast; 355 | 356 | t = list; 357 | if (t == NULL) 358 | return; 359 | 360 | col = getmaxx(stdscr); 361 | imax = MIN(t->npkts, col - labelwidth); 362 | imax = MIN(imax, NUM); 363 | ifirst = (t->npkts > imax ? t->npkts - imax : 0); 364 | ilast = t->npkts; 365 | 366 | endwin(); 367 | DL_FOREACH(list, t) { 368 | if (C_flag && t->af == AF_INET6) 369 | fprintf(stdout, "%c[2;32m%*.*s%c[0m", 370 | 0x1b, w_width, w_width, t->host, 0x1b); 371 | else if (C_flag && t->af == AF_INET) 372 | fprintf(stdout, "%c[2;31m%*.*s%c[0m", 373 | 0x1b, w_width, w_width, t->host, 0x1b); 374 | else 375 | fprintf(stdout, "%*.*s", w_width, w_width, t->host); 376 | if (w_width) 377 | fputc(' ', stdout); 378 | for (i=ifirst; inpkts) 380 | fputc(t->res[i % NUM], stdout); 381 | else 382 | fputc(' ', stdout); 383 | } 384 | fputc('\n', stdout); 385 | } 386 | #endif /* !NCURSES */ 387 | } 388 | -------------------------------------------------------------------------------- /xping.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---------------------------------------------------------------------------- 3 | * "THE BEER-WARE LICENSE" (Revision 42): 4 | * wrote this file. As long as you retain this notice you 5 | * can do whatever you want with this stuff. If we meet some day, and you think 6 | * this stuff is worth it, you can buy me a beer in return Martin Topholm 7 | * ---------------------------------------------------------------------------- 8 | */ 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | 24 | #include "xping.h" 25 | 26 | extern char *optarg; 27 | extern int optind; 28 | 29 | #define SETRES(t,i,r) t->res[(t->npkts+i) % NUM] = r 30 | #define GETRES(t,i) t->res[(t->npkts+i) % NUM] 31 | 32 | /* Option flags */ 33 | int i_interval = 1000; 34 | int a_flag = 0; 35 | int c_count = 0; 36 | int A_flag = 0; 37 | int B_flag = 0; 38 | int C_flag = 0; 39 | int T_flag = 0; 40 | int v4_flag = 0; 41 | int v6_flag = 0; 42 | int w_width = 20; 43 | 44 | /* Global structures */ 45 | int fd4, fd4errno; 46 | int fd6, fd6errno; 47 | struct event_base *ev_base; 48 | struct evdns_base *dns; 49 | struct timeval tv_interval; 50 | int numtargets = 0; 51 | int numcomplete = 0; 52 | 53 | struct target *list = NULL; 54 | 55 | void (*ui_init)(void) = termio_init; 56 | void (*ui_update)(struct target *) = termio_update; 57 | void (*ui_cleanup)(void) = termio_cleanup; 58 | 59 | /* 60 | * Signal to catch program termination 61 | */ 62 | void 63 | sigint(int sig) 64 | { 65 | 66 | event_base_loopexit(ev_base, NULL); 67 | signal(SIGINT, SIG_DFL); 68 | signal(SIGTERM, SIG_DFL); 69 | } 70 | 71 | /* 72 | * Ring the terminal bell. This includes tricks to avoid gcc alerts when 73 | * control flow doesn't react to the return value of write(), see also 74 | * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25509 75 | */ 76 | static void 77 | bell(void) 78 | { 79 | int ignored; 80 | 81 | ignored = write(STDOUT_FILENO, "\a", 1); 82 | (void)ignored; 83 | } 84 | 85 | /* 86 | * Register status for send and "timed out" requests and send a probe. 87 | */ 88 | static void 89 | target_probe(int fd, short what, void *thunk) 90 | { 91 | struct target *t = thunk; 92 | 93 | /* Missed request */ 94 | if (t->npkts > 0 && GETRES(t, -1) != '.') { 95 | if (GETRES(t, -1) == ' ') 96 | target_mark(t, t->npkts - 1, '?'); 97 | if (A_flag == 1) 98 | bell(); 99 | else if (A_flag >= 2 && 100 | GETRES(t, -4) == '.' && 101 | GETRES(t, -3) == '.' && 102 | GETRES(t, -2) != '.' && 103 | GETRES(t, -1) != '.') 104 | bell(); 105 | } 106 | 107 | /* Check packet count limit */ 108 | if (c_count && t->npkts >= c_count) { 109 | numcomplete++; 110 | event_del(t->ev_write); 111 | if (numcomplete >= numtargets) { 112 | event_base_loopexit(ev_base, NULL); 113 | } 114 | return; 115 | } 116 | 117 | /* Transmit request */ 118 | t->res[t->npkts % NUM] = ' '; 119 | probe_send(t->prb, t->npkts); 120 | t->npkts++; 121 | 122 | ui_update(t); 123 | } 124 | 125 | /* 126 | * Does the scheduling of periodic transmissions. 127 | */ 128 | static void 129 | target_probe_sched(int fd, short what, void *thunk) 130 | { 131 | struct target *t = thunk; 132 | 133 | event_free(t->ev_write); 134 | t->ev_write = event_new(ev_base, -1, EV_PERSIST, target_probe, t); 135 | event_add(t->ev_write, &tv_interval); 136 | target_probe(fd, what, thunk); 137 | } 138 | 139 | /* 140 | * Mark a target and sequence with given symbol 141 | */ 142 | void 143 | target_mark(struct target *t, int seq, int ch) 144 | { 145 | if (ch == '.' && t->res[seq % NUM] != ' ') 146 | t->res[seq % NUM] = ':'; 147 | else 148 | t->res[seq % NUM] = ch; 149 | if (a_flag && ch == '.') { 150 | if (a_flag == 1) 151 | bell(); 152 | else if (a_flag >=2 && t->npkts >= 4 && 153 | t->res[(seq-3) % NUM] != '.' && 154 | t->res[(seq-2) % NUM] != '.' && 155 | t->res[(seq-1) % NUM] == '.' && 156 | t->res[(seq-0) % NUM] == '.') 157 | bell(); 158 | } 159 | 160 | if (seq == t->npkts - 1) 161 | ui_update(t); 162 | else 163 | ui_update(NULL); /* this is a late reply, need full update to redraw this */ 164 | } 165 | 166 | /* 167 | * Target resolved update address family 168 | */ 169 | void 170 | target_resolved(struct target *t, int af, void *address) 171 | { 172 | t->af = af; 173 | ui_update(NULL); 174 | } 175 | 176 | /* 177 | * Create a new a probe target, apply resolver if needed. 178 | */ 179 | static int 180 | target_add(const char *line) 181 | { 182 | struct target *t; 183 | 184 | t = (struct target *)calloc(1, sizeof(*t)); 185 | if (t == NULL) 186 | return -1; 187 | memset(t->res, ' ', sizeof(t->res)); 188 | strncat(t->host, line, sizeof(t->host) - 1); 189 | DL_APPEND(list, t); 190 | t->prb = probe_new(line, t); 191 | if (t->prb == NULL) { 192 | free(t); 193 | return -1; 194 | } 195 | numtargets++; 196 | return 0; 197 | } 198 | 199 | /* 200 | * Clean and free global resources 201 | */ 202 | static void 203 | cleanup(void) 204 | { 205 | struct target *t, *t_tmp; 206 | 207 | DL_FOREACH_SAFE(list, t, t_tmp) { 208 | event_free(t->ev_write); 209 | probe_free(t->prb); 210 | free(t); 211 | } 212 | probe_cleanup(); 213 | evdns_base_free(dns, 0); 214 | event_base_free(ev_base); 215 | #ifdef libevent_global_shutdown 216 | libevent_global_shutdown(); 217 | #endif /* !libevent_global_shutdown */ 218 | close(fd4); 219 | close(fd6); 220 | } 221 | 222 | void 223 | usage(const char *whine) 224 | { 225 | if (whine != NULL) { 226 | fprintf(stderr, "%s\n", whine); 227 | } 228 | fprintf(stderr, 229 | "usage: xping [-46ABCTVah] [-c count] [-i interval] [-w width] " 230 | "host [host [...]]\n" 231 | "\n"); 232 | exit(EX_USAGE); 233 | } 234 | 235 | /* 236 | * Continiously probing multiple hosts using ICMP-ECHO. As packets are 237 | * received dots are printed on the screen. Hosts not responding before 238 | * next packet is due will get a questionmark in the display. The probing 239 | * stops when SIGINT is received. 240 | */ 241 | int 242 | main(int argc, char *argv[]) 243 | { 244 | char buf[BUFSIZ]; 245 | struct timeval tv; 246 | struct target *t; 247 | char *end; 248 | int i; 249 | int len; 250 | char ch; 251 | 252 | #ifdef DO_SOCK_RAW 253 | /* Open RAW-socket and drop root-privs */ 254 | fd4 = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); 255 | fd4errno = errno; 256 | fd6 = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); 257 | fd6errno = errno; 258 | if (setuid(getuid()) < 0) { 259 | perror("setuid"); 260 | exit(EX_OSERR); 261 | } 262 | #else /* !DO_SOCK_RAW */ 263 | fd4 = -1; 264 | fd4errno = EAFNOSUPPORT; 265 | fd6 = -1; 266 | fd6errno = EAFNOSUPPORT; 267 | #endif /* DO_SOCK_RAW */ 268 | 269 | /* Parse command line options */ 270 | while ((ch = getopt(argc, argv, "46ABCTVahc:i:w:")) != -1) { 271 | switch(ch) { 272 | case '4': 273 | v4_flag = 1; 274 | v6_flag = 0; 275 | break; 276 | case '6': 277 | v4_flag = 0; 278 | v6_flag = 1; 279 | break; 280 | case 'a': 281 | a_flag++; 282 | break; 283 | case 'A': 284 | A_flag++; 285 | break; 286 | case 'B': 287 | B_flag = 1; 288 | break; 289 | case 'C': 290 | C_flag = 1; 291 | break; 292 | case 'c': 293 | c_count = strtol(optarg, &end, 10); 294 | if (*optarg != '\0' && *end != '\0') 295 | usage("Invalid count"); 296 | break; 297 | case 'T': 298 | T_flag = 1; 299 | break; 300 | case 'i': 301 | i_interval = strtod(optarg, &end) * 1000; 302 | if (*optarg != '\0' && *end != '\0') 303 | usage("Invalid interval"); 304 | if (i_interval < 1000 && getuid() != 0) 305 | usage("Dangerous interval"); 306 | break; 307 | case 'w': 308 | w_width = strtol(optarg, &end, 10); 309 | if (*optarg != '\0' && *end != '\0') 310 | usage("Invalid width"); 311 | if (w_width < 0) 312 | usage("Invalid width"); 313 | break; 314 | case 'V': 315 | fprintf(stderr, "%s %s\n", "xping", version); 316 | return (0); 317 | case 'h': 318 | usage(NULL); 319 | /* NOTREACHED */ 320 | default: 321 | usage(NULL); 322 | /* NOTREACHED */ 323 | } 324 | } 325 | argc -= optind; 326 | argv += optind; 327 | 328 | tv_interval.tv_sec = i_interval / 1000; 329 | tv_interval.tv_usec = i_interval % 1000 * 1000; 330 | 331 | /* Prepare event system and inbound socket */ 332 | ev_base = event_base_new(); 333 | dns = evdns_base_new(ev_base, 1); 334 | probe_setup(); 335 | 336 | /* Read targets from program arguments and/or stdin. */ 337 | list = NULL; 338 | for (i=0; i 0; len--) { 348 | if (strchr(" \t\n", buf[len]) == NULL) 349 | break; 350 | buf[len] = '\0'; 351 | } 352 | if (buf[0] == '#' || len < 1) 353 | continue; 354 | if (target_add(buf) < 0) { 355 | perror("malloc"); 356 | return 1; 357 | } 358 | } 359 | } 360 | if (!isatty(STDOUT_FILENO)) { 361 | ui_init = report_init; 362 | ui_update = report_update; 363 | ui_cleanup = report_cleanup; 364 | } 365 | if (list == NULL) { 366 | cleanup(); 367 | usage("no arguments"); 368 | /* NEVER REACHED */ 369 | } 370 | 371 | /* Initial scheduling with increasing delay, distributes 372 | * transmissions across the interval and gives a cascading effect. */ 373 | tv.tv_sec = 0; 374 | tv.tv_usec = 0; 375 | DL_FOREACH(list, t) { 376 | t->ev_write = event_new(ev_base, -1, 0, target_probe_sched, t); 377 | event_add(t->ev_write, &tv); 378 | tv.tv_usec += 100*1000; /* target spacing: 100ms */ 379 | tv.tv_sec += (tv.tv_usec >= 1000000 ? 1 : 0); 380 | tv.tv_usec -= (tv.tv_usec >= 1000000 ? 1000000 : 0); 381 | } 382 | 383 | /* Startup UI and probing */ 384 | signal(SIGINT, sigint); 385 | signal(SIGTERM, sigint); 386 | ui_init(); 387 | event_base_dispatch(ev_base); 388 | ui_cleanup(); 389 | cleanup(); 390 | return 0; 391 | } 392 | -------------------------------------------------------------------------------- /icmp-unpriv.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---------------------------------------------------------------------------- 3 | * "THE BEER-WARE LICENSE" (Revision 42): 4 | * wrote this file. As long as you retain this notice you 5 | * can do whatever you want with this stuff. If we meet some day, and you think 6 | * this stuff is worth it, you can buy me a beer in return Martin Topholm 7 | * ---------------------------------------------------------------------------- 8 | */ 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include "xping.h" 21 | #include "tricks.h" 22 | 23 | struct probe { 24 | char host[MAXHOST]; 25 | int resolved; 26 | union addr sa; 27 | int pid; 28 | int fd; 29 | int seqdelta; 30 | int seqlast; 31 | int early_mark; 32 | struct event *ev_read; 33 | struct evbuffer *evbuf; 34 | void *dnstask; 35 | void *owner; 36 | }; 37 | 38 | static regex_t re_reply, re_other, re_xmiterr; 39 | 40 | static void 41 | killping(struct probe *prb) 42 | { 43 | if (prb->pid && kill(prb->pid, 0) == 0) { 44 | kill(prb->pid, SIGTERM); 45 | prb->pid = 0; 46 | } 47 | } 48 | 49 | static void 50 | execping(struct probe *prb) 51 | { 52 | char address[64]; 53 | char interval[24]; 54 | 55 | evutil_snprintf(interval, sizeof(interval), "%f", 56 | (double)i_interval/1000); 57 | 58 | if (sa(prb)->sa_family == AF_INET6) { 59 | evutil_inet_ntop(sa(prb)->sa_family, &sin6(prb)->sin6_addr, 60 | address, sizeof(address)); 61 | execlp("ping6", "ping6", "-ni", interval, address, NULL); 62 | } else { 63 | evutil_inet_ntop(sa(prb)->sa_family, &sin(prb)->sin_addr, 64 | address, sizeof(address)); 65 | execlp("ping", "ping", "-ni", interval, address, NULL); 66 | } 67 | exit(1); /* in case exec fails */ 68 | } 69 | 70 | static void 71 | readping(int fd, short what, void *thunk) 72 | { 73 | struct probe *prb = thunk; 74 | regmatch_t match[5]; 75 | struct evbuffer_ptr evbufptr; 76 | double rtt; 77 | size_t len; 78 | char buf[BUFSIZ]; 79 | char *end; 80 | long int seq; 81 | int mark; 82 | int n; 83 | 84 | evbuffer_read(prb->evbuf, fd, 512); 85 | evbufptr = evbuffer_search_eol(prb->evbuf, NULL, &len, 86 | EVBUFFER_EOL_ANY); 87 | while (evbufptr.pos != -1) { 88 | len += evbufptr.pos; 89 | n = evbuffer_remove(prb->evbuf, buf, MIN(sizeof(buf)-1, len)); 90 | buf[n] = '\0'; 91 | mark = '\0'; 92 | if (regexec(&re_reply, buf, 5, match, 0) == 0) { 93 | seq = strtol(buf + match[1].rm_so, &end, 10); 94 | rtt = strtod(buf + match[2].rm_so, &end); 95 | mark = '.'; 96 | } else if (regexec(&re_other, buf, 5, match, 0) == 0) { 97 | seq = strtol(buf + match[1].rm_so, &end, 10); 98 | if (match[2].rm_so != match[2].rm_eo) 99 | mark = '#'; 100 | else 101 | mark = '%'; 102 | } else if (regexec(&re_xmiterr, buf, 5, match, 0) == 0) { 103 | /* Transmit errors are quickly identified, 104 | * thus assume they refer to most recent packet */ 105 | target_mark(prb->owner, prb->seqlast, '!'); 106 | } 107 | if (mark != '\0') { 108 | /* Adjust sequence adjustment delta. In cast the 109 | * first packet has icmp_seq=0 instead of 1 */ 110 | if (prb->seqlast < 32768 && seq == 0) 111 | prb->seqdelta++; 112 | /* 113 | * seq newer than last sent, thus packet arrived too 114 | * early (before probe_send. Cached mark until 115 | * probe_send is called next_time. 116 | */ 117 | if (prb->seqlast < seq + prb->seqdelta) { 118 | AZ(prb->early_mark == '\0'); 119 | prb->early_mark = mark; 120 | return; 121 | } 122 | target_mark(prb->owner, seq + prb->seqdelta, mark); 123 | 124 | /* Check for timing drift: If packet isn't the 125 | * latest reply and ping thinks rtt is less than 126 | * i_interval it must have drifted */ 127 | if (mark == '.' && 128 | seq + prb->seqdelta < prb->seqlast && 129 | rtt < i_interval) { 130 | killping(prb); 131 | target_mark(prb->owner, prb->seqlast, '!'); 132 | } 133 | } 134 | evbufptr = evbuffer_search_eol(prb->evbuf, NULL, &len, 135 | EVBUFFER_EOL_LF); 136 | } 137 | } 138 | 139 | /* 140 | * Store newly resolved address in target struct (or if af == 0 141 | * unresolved). Only change address and reset probe if address actually 142 | * changed. 143 | */ 144 | static void 145 | resolved(int af, void *address, void *thunk) 146 | { 147 | struct probe *prb = thunk; 148 | if (af == AF_INET6) { 149 | if (sin6(prb)->sin6_family == AF_INET6 && 150 | memcmp(&sin6(prb)->sin6_addr, (struct in6_addr *)address, 151 | sizeof(sin6(prb)->sin6_addr)) == 0) 152 | return; 153 | sin6(prb)->sin6_family = AF_INET6; 154 | memmove(&sin6(prb)->sin6_addr, (struct in6_addr *)address, 155 | sizeof(sin6(prb)->sin6_addr)); 156 | killping(prb); 157 | prb->resolved = 1; 158 | } else if (af == AF_INET) { 159 | if (sin(prb)->sin_family == AF_INET && 160 | memcmp(&sin(prb)->sin_addr, (struct in_addr *)address, 161 | sizeof(sin(prb)->sin_addr)) == 0) 162 | return; 163 | sin(prb)->sin_family = AF_INET; 164 | memmove(&sin(prb)->sin_addr, (struct in_addr *)address, 165 | sizeof(sin(prb)->sin_addr)); 166 | killping(prb); 167 | prb->resolved = 1; 168 | } else if (af == 0) { 169 | prb->resolved = 0; 170 | killping(prb); 171 | } 172 | target_resolved(prb->owner, af, address); 173 | } 174 | 175 | /* 176 | * Set up regular expressions for matching reply lines, unreachable lines, 177 | * and send errors. 178 | * 179 | * FreeBSD-9: 180 | * 64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.030 ms 181 | * ping: sendto: Host is down 182 | * 183 | * iputils-s20121221: 184 | * 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.027 ms 185 | * From 192.0.2.1 icmp_seq=1 Destination Host Unreachable 186 | * 187 | * iputils-sss20101006 188 | * 64 bytes from 127.0.0.1: icmp_req=1 ttl=64 time=0.028 ms 189 | * From 192.0.2.1 icmp_seq=1 Destination Net Unreachable 190 | */ 191 | void 192 | probe_setup(struct event_base *parent_event_base) 193 | { 194 | signal(SIGCHLD, SIG_IGN); 195 | if (regcomp(&re_reply, 196 | "[0-9]+ bytes.*icmp_.eq=([0-9][0-9]*) .*time=([0-9].[0-9]*)", 197 | REG_EXTENDED | REG_NEWLINE) != 0) { 198 | fprintf(stderr, 199 | "regcomp: error compiling regular expression\n"); 200 | exit(1); 201 | } 202 | if (regcomp(&re_other, "From .*icmp_.eq=([0-9][0-9]*)" 203 | "( Destination Host Unreachable| Destination unreachable| )", 204 | REG_EXTENDED | REG_NEWLINE) != 0) { 205 | fprintf(stderr, 206 | "regcomp: error compiling regular expression\n"); 207 | exit(1); 208 | } 209 | if (regcomp(&re_xmiterr, "(ping|ping6|connect): " 210 | "(sentto|UDP connect|sendmsg|Network is unreachable)", 211 | REG_EXTENDED | REG_NEWLINE) != 0) { 212 | fprintf(stderr, 213 | "regcomp: error compiling regular expression\n"); 214 | exit(1); 215 | } 216 | } 217 | 218 | void probe_cleanup(void) 219 | { 220 | 221 | regfree(&re_reply); 222 | regfree(&re_other); 223 | regfree(&re_xmiterr); 224 | } 225 | 226 | struct probe * 227 | probe_new(const char *line, void *owner) 228 | { 229 | struct probe *prb; 230 | union addr sa; 231 | int salen; 232 | 233 | prb = calloc(1, sizeof(*prb)); 234 | if (prb == NULL) { 235 | perror("malloc"); 236 | return (NULL); 237 | } 238 | prb->fd = -1; 239 | prb->owner = owner; 240 | strncat(prb->host, line, sizeof(prb->host) - 1); 241 | prb->evbuf = evbuffer_new(); 242 | if (prb->evbuf == NULL) { 243 | probe_free(prb); 244 | return NULL; 245 | } 246 | 247 | salen = sizeof(sa); 248 | if (evutil_parse_sockaddr_port(prb->host, &sa.sa, &salen) == 0) { 249 | sa(prb)->sa_family = sa.sa.sa_family; 250 | if (sa.sa.sa_family == AF_INET6) { 251 | memcpy(&sin6(prb)->sin6_addr, &sa.sin6.sin6_addr, 252 | sizeof(sin6(prb)->sin6_addr)); 253 | } else { 254 | memcpy(&sin(prb)->sin_addr, &sa.sin.sin_addr, 255 | sizeof(sin(prb)->sin_addr)); 256 | } 257 | prb->resolved = 1; 258 | } else { 259 | prb->dnstask = dnstask_new(prb->host, resolved, prb); 260 | if (prb->dnstask == NULL) { 261 | probe_free(prb); 262 | return NULL; 263 | } 264 | } 265 | return (prb); 266 | } 267 | 268 | void probe_free(struct probe *prb) 269 | { 270 | 271 | if (prb->fd >= 0) 272 | close(prb->fd); 273 | if (prb->ev_read) 274 | event_free(prb->ev_read); 275 | if (prb->dnstask) 276 | dnstask_free(prb->dnstask); 277 | if (prb->evbuf) 278 | evbuffer_free(prb->evbuf); 279 | free(prb); 280 | } 281 | 282 | void 283 | probe_send(struct probe *prb, int seq) 284 | { 285 | int pair[2]; 286 | pid_t pid; 287 | 288 | if (!prb->resolved) { 289 | target_mark(prb->owner, seq, '@'); 290 | return; 291 | } 292 | 293 | /* Save most recent sequence number to close gap between 294 | * probe_send returning (target_probe increasing npkts) and forked 295 | * program outputting */ 296 | prb->seqlast = seq; 297 | 298 | /* Clear ahead to avoid overwriting a result in case of small 299 | * timing indiscrepancies */ 300 | target_mark(prb->owner, seq+1, ' '); 301 | 302 | /* If we have a cached early mark, send it right away. */ 303 | if (prb->early_mark != '\0') { 304 | target_mark(prb->owner, seq, prb->early_mark); 305 | prb->early_mark = '\0'; 306 | } 307 | 308 | /* Check for existing ping process */ 309 | if (prb->pid && kill(prb->pid, 0) == 0) 310 | return; 311 | 312 | /* Create ipc socket pair and fork ping process */ 313 | if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, pair) < 0) { 314 | target_mark(prb->owner, seq, '!'); /* transmit error */ 315 | return; 316 | } 317 | prb->seqdelta = seq - 1; /* linux ping(8) begins icmp_seq=1 */ 318 | evutil_make_socket_nonblocking(pair[0]); 319 | if (prb->fd >= 0) 320 | close(prb->fd); 321 | if (prb->ev_read != NULL) 322 | event_free(prb->ev_read); 323 | prb->ev_read = event_new(ev_base, pair[0], EV_READ|EV_PERSIST, readping, prb); 324 | event_add(prb->ev_read, NULL); 325 | switch (pid = fork()) { 326 | case -1: 327 | target_mark(prb->owner, seq, '!'); /* transmit error */ 328 | return; 329 | case 0: 330 | evutil_closesocket(pair[0]); 331 | dup2(pair[1], 1); 332 | dup2(pair[1], 2); 333 | execping(prb); 334 | /* NEVER REACHED */ 335 | break; 336 | default: 337 | evutil_closesocket(pair[1]); 338 | prb->pid = pid; 339 | prb->fd = pair[0]; 340 | break; 341 | } 342 | } 343 | -------------------------------------------------------------------------------- /icmp.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---------------------------------------------------------------------------- 3 | * "THE BEER-WARE LICENSE" (Revision 42): 4 | * wrote this file. As long as you retain this notice you 5 | * can do whatever you want with this stuff. If we meet some day, and you think 6 | * this stuff is worth it, you can buy me a beer in return Martin Topholm 7 | * ---------------------------------------------------------------------------- 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | #include "xping.h" 24 | 25 | #define ICMP6_MINLEN sizeof(struct icmp6_hdr) 26 | 27 | struct probe { 28 | char host[MAXHOST]; 29 | int resolved; 30 | union addr sa; 31 | struct probe *duplicate; 32 | int last_seq; 33 | UT_hash_handle hh; 34 | void *dnstask; 35 | void *owner; 36 | }; 37 | 38 | struct event *ev_read4; 39 | struct event *ev_read6; 40 | struct probe *hash = NULL; 41 | char outpacket[IP_MAXPACKET]; 42 | char outpacket6[IP_MAXPACKET]; 43 | int datalen = 56; 44 | int ident; 45 | 46 | /* 47 | * From the original ping.c by Mike Muus... 48 | * 49 | * in_cksum -- 50 | * Checksum routine for Internet Protocol family headers (C Version) 51 | */ 52 | static u_short 53 | in_cksum(u_short *addr, int len) 54 | { 55 | int nleft, sum; 56 | u_short *w; 57 | union { 58 | u_short us; 59 | u_char uc[2]; 60 | } last; 61 | u_short answer; 62 | 63 | nleft = len; 64 | sum = 0; 65 | w = addr; 66 | 67 | /* 68 | * Our algorithm is simple, using a 32 bit accumulator (sum), we add 69 | * sequential 16 bit words to it, and at the end, fold back all the 70 | * carry bits from the top 16 bits into the lower 16 bits. 71 | */ 72 | while (nleft > 1) { 73 | sum += *w++; 74 | nleft -= 2; 75 | } 76 | 77 | /* mop up an odd byte, if necessary */ 78 | if (nleft == 1) { 79 | last.uc[0] = *(u_char *)w; 80 | last.uc[1] = 0; 81 | sum += last.us; 82 | } 83 | 84 | /* add back carry outs from top 16 bits to low 16 bits */ 85 | sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */ 86 | sum += (sum >> 16); /* add carry */ 87 | answer = ~sum; /* truncate to 16 bits */ 88 | return(answer); 89 | } 90 | 91 | /* 92 | * Insert a new target into the hash table. Mark as a duplicate if the 93 | * key already exists. 94 | */ 95 | void 96 | activate(struct probe *prb) 97 | { 98 | struct probe *result; 99 | 100 | HASH_FIND(hh, hash, &prb->sa, sizeof(union addr), result); 101 | if (result == prb) 102 | ; /* nothing, already active in hash */ 103 | else if (result) 104 | prb->duplicate = result; 105 | else 106 | HASH_ADD(hh, hash, sa, sizeof(union addr), prb); 107 | } 108 | 109 | /* 110 | * Remove a probe (prb) from the hash table if the probe is the currently 111 | * active for the address. Secondly first probe (t1) which was duplicate 112 | * of probe (prb) is activated, and other duplicates will refer of that 113 | * one (prb) instead. 114 | */ 115 | void 116 | deactivate(struct probe *prb) 117 | { 118 | struct probe *tmp, *tmp2, *t1; 119 | 120 | HASH_FIND(hh, hash, &prb->sa, sizeof(union addr), tmp); 121 | if (tmp == NULL) 122 | return; /* already inactive, i.e. not in hash */ 123 | HASH_DELETE(hh, hash, prb); 124 | t1 = NULL; 125 | HASH_ITER(hh, hash, tmp, tmp2) { 126 | if (tmp->duplicate == prb) { 127 | if (t1 == NULL) { 128 | t1 = tmp; 129 | t1->duplicate = NULL; 130 | activate(t1); 131 | } else { 132 | tmp->duplicate = t1; 133 | } 134 | } 135 | } 136 | } 137 | 138 | /* 139 | * Lookup target in the hash table 140 | */ 141 | struct probe * 142 | find(int af, void *address) 143 | { 144 | struct probe *result; 145 | union addr sa; 146 | 147 | memset(&sa, 0, sizeof(sa)); 148 | if (af == AF_INET) { 149 | sa.sin.sin_family = AF_INET; 150 | memmove(&sa.sin.sin_addr, (struct in_addr *)address, 151 | sizeof(sa.sin.sin_addr)); 152 | } else if (af == AF_INET6) { 153 | sa.sin6.sin6_family = AF_INET6; 154 | memmove(&sa.sin6.sin6_addr, (struct in6_addr *)address, 155 | sizeof(sa.sin6.sin6_addr)); 156 | } else { 157 | return NULL; 158 | } 159 | HASH_FIND(hh, hash, &sa, sizeof(union addr), result); 160 | return (result); 161 | } 162 | 163 | /* 164 | * Send out icmp packet for target. 165 | */ 166 | static int 167 | write_packet4(struct sockaddr *sa, unsigned short seq) 168 | { 169 | struct icmp *icp; 170 | int len; 171 | 172 | len = ICMP_MINLEN + datalen; 173 | icp = (struct icmp *)outpacket; 174 | icp->icmp_type = ICMP_ECHO; 175 | icp->icmp_code = 0; 176 | icp->icmp_cksum = 0; 177 | icp->icmp_seq = htons(seq); 178 | icp->icmp_id = htons(ident); 179 | icp->icmp_cksum = in_cksum((u_short *)icp, len); 180 | 181 | return sendto(fd4, outpacket, len, 0, sa, 182 | sizeof(struct sockaddr_in)); 183 | } 184 | 185 | /* 186 | * Send out icmp6 packet for target. 187 | */ 188 | static int 189 | write_packet6(struct sockaddr *sa, unsigned short seq) 190 | { 191 | struct icmp6_hdr *icmp6h; 192 | int len; 193 | 194 | len = ICMP6_MINLEN + datalen; 195 | icmp6h = (struct icmp6_hdr *)outpacket6; 196 | icmp6h->icmp6_type = ICMP6_ECHO_REQUEST; 197 | icmp6h->icmp6_code = 0; 198 | icmp6h->icmp6_cksum = 0; 199 | icmp6h->icmp6_seq = htons(seq); 200 | icmp6h->icmp6_id = htons(ident); 201 | return sendto(fd6, outpacket6, len, 0, sa, 202 | sizeof(struct sockaddr_in6)); 203 | } 204 | 205 | /* 206 | * Find probe target from address and expand truncated icmp_seq from 207 | * last sent sequence number. 208 | */ 209 | static void 210 | find_marktarget(int af, void *address, int seq, int ch) 211 | { 212 | struct probe *prb; 213 | int npkts; 214 | 215 | prb = find(af, address); 216 | if (prb == NULL) 217 | return; /* unknown source address */ 218 | npkts = prb->last_seq; 219 | if ((npkts & 0xffff) < seq) 220 | npkts -= 1<<16; 221 | target_mark(prb->owner, (npkts & ~0xffff) | seq, ch); 222 | } 223 | 224 | /* 225 | * Receive packet from IPv4 socket, parse it and associate result with 226 | * an active target via find_marktarget. 227 | */ 228 | static void 229 | read_packet4(int fd, short what, void *thunk) 230 | { 231 | char inpacket[IP_MAXPACKET]; 232 | struct sockaddr_in sin; 233 | struct ip *ip; 234 | struct ip *oip; 235 | struct icmp *icp; 236 | struct icmp *oicp; 237 | socklen_t salen; 238 | int hlen; 239 | int seq; 240 | int n; 241 | 242 | salen = sizeof(sin); 243 | memset(inpacket, 0, sizeof(inpacket)); 244 | n = recvfrom(fd, inpacket, sizeof(inpacket), 0, 245 | (struct sockaddr *)&sin, &salen); 246 | if (n < 0) { 247 | return; 248 | } 249 | 250 | ip = (struct ip *)inpacket; 251 | hlen = ip->ip_hl << 2; 252 | if (ip->ip_p != IPPROTO_ICMP) { 253 | return; 254 | } 255 | if (n < hlen + ICMP_MINLEN) { 256 | return; 257 | } 258 | 259 | icp = (struct icmp *)(inpacket + hlen); 260 | if (icp->icmp_type == ICMP_ECHOREPLY) { 261 | if (icp->icmp_id != htons(ident)) 262 | return; /* skip other ping sessions */ 263 | 264 | seq = ntohs(icp->icmp_seq); 265 | find_marktarget(AF_INET, &sin.sin_addr, seq, '.'); 266 | } else { 267 | /* Skip short icmp error packets. */ 268 | if (n < ICMP_MINLEN * 2 + sizeof(struct ip)) 269 | return; 270 | 271 | /* Check aspects of the original packet */ 272 | oip = (struct ip *)icp->icmp_data; 273 | oicp = (struct icmp *)(oip + 1); 274 | if (oip->ip_p != IPPROTO_ICMP) 275 | return; 276 | if (oicp->icmp_type != ICMP_ECHO) 277 | return; 278 | if (oicp->icmp_id != htons(ident)) 279 | return; 280 | 281 | seq = ntohs(oicp->icmp_seq); 282 | if (icp->icmp_type == ICMP_UNREACH) 283 | find_marktarget(AF_INET, &oip->ip_dst, seq, '#'); 284 | else 285 | find_marktarget(AF_INET, &oip->ip_dst, seq, '%'); 286 | } 287 | } 288 | 289 | /* 290 | * Receive packet from IPv6 socket, parse it and associate with an active 291 | * target via find_marktarget. 292 | */ 293 | static void 294 | read_packet6(int fd, short what, void *thunk) 295 | { 296 | char inpacket[IP_MAXPACKET]; 297 | struct sockaddr_in6 sin6; 298 | struct ip6_hdr *oip6; 299 | struct icmp6_hdr *icmp6h; 300 | struct icmp6_hdr *oicmp6h; 301 | socklen_t salen; 302 | int seq; 303 | int n; 304 | 305 | salen = sizeof(sin6); 306 | memset(inpacket, 0, sizeof(inpacket)); 307 | n = recvfrom(fd, inpacket, sizeof(inpacket), 0, 308 | (struct sockaddr *)&sin6, &salen); 309 | if (n < 0) { 310 | return; 311 | } 312 | if (n < ICMP6_MINLEN) { 313 | return; 314 | } 315 | 316 | /* SOCK_RAW for IPPROTO_ICMPV6 doesn't include IPv6 header */ 317 | icmp6h = (struct icmp6_hdr *)(inpacket); 318 | if (icmp6h->icmp6_type == ICMP6_ECHO_REPLY) { 319 | if (icmp6h->icmp6_id != htons(ident)) 320 | return; /* skip other ping sessions */ 321 | if (n != sizeof(struct icmp6_hdr) + datalen) 322 | return; 323 | 324 | seq = ntohs(icmp6h->icmp6_seq); 325 | find_marktarget(AF_INET6, &sin6.sin6_addr, seq, '.'); 326 | } else { 327 | /* Skip short icmp error packets. */ 328 | if (n < ICMP6_MINLEN * 2 + sizeof(struct ip6_hdr)) 329 | return; 330 | 331 | /* Check aspects of the original packet */ 332 | oip6 = (struct ip6_hdr *)(icmp6h + 1); 333 | oicmp6h = (struct icmp6_hdr *)(oip6 + 1); 334 | if (oip6->ip6_nxt != IPPROTO_ICMPV6) 335 | return; 336 | if (oicmp6h->icmp6_type != ICMP6_ECHO_REQUEST) 337 | return; 338 | if (oicmp6h->icmp6_id != htons(ident)) 339 | return; 340 | 341 | seq = ntohs(oicmp6h->icmp6_seq); 342 | if (icmp6h->icmp6_type == ICMP6_DST_UNREACH) 343 | find_marktarget(AF_INET6, &oip6->ip6_dst, seq, '#'); 344 | else 345 | find_marktarget(AF_INET6, &oip6->ip6_dst, seq, '%'); 346 | } 347 | } 348 | 349 | /* 350 | * Handle DNS lookups for targets. 351 | */ 352 | static void 353 | resolved(int af, void *address, void *thunk) 354 | { 355 | struct probe *prb = thunk; 356 | if (af == AF_INET6) { 357 | sin6(prb)->sin6_family = AF_INET6; 358 | memmove(&sin6(prb)->sin6_addr, (struct in6_addr *)address, 359 | sizeof(sin6(prb)->sin6_addr)); 360 | activate(prb); 361 | prb->resolved = 1; 362 | } else if (af == AF_INET) { 363 | sin(prb)->sin_family = AF_INET; 364 | memmove(&sin(prb)->sin_addr, (struct in_addr *)address, 365 | sizeof(sin(prb)->sin_addr)); 366 | activate(prb); 367 | prb->resolved = 1; 368 | } else if (af == 0) { 369 | prb->resolved = 0; 370 | deactivate(prb); 371 | } 372 | target_resolved(prb->owner, af, address); 373 | } 374 | 375 | /* 376 | * Prepare datastructures and events needed for probe 377 | */ 378 | void 379 | probe_setup() 380 | { 381 | int i; 382 | 383 | if (fd4 < 0) { 384 | errno = fd4errno; 385 | perror("socket (IPv4)"); 386 | exit(1); 387 | } 388 | if (fd6 < 0) { 389 | errno = fd6errno; 390 | perror("socket (IPv6)"); 391 | exit(1); 392 | } 393 | 394 | /* Prepare datapacket */ 395 | ident = getpid() & 0xffff; 396 | for (i=0; iowner = owner; 437 | strncat(prb->host, line, sizeof(prb->host) - 1); 438 | 439 | salen = sizeof(sa); 440 | if (evutil_parse_sockaddr_port(prb->host, &sa.sa, &salen) == 0) { 441 | sa(prb)->sa_family = sa.sa.sa_family; 442 | if (sa.sa.sa_family == AF_INET6) { 443 | memcpy(&sin6(prb)->sin6_addr, &sa.sin6.sin6_addr, 444 | sizeof(sin6(prb)->sin6_addr)); 445 | } else { 446 | memcpy(&sin(prb)->sin_addr, &sa.sin.sin_addr, 447 | sizeof(sin(prb)->sin_addr)); 448 | } 449 | prb->resolved = 1; 450 | activate(prb); 451 | } else { 452 | prb->dnstask = dnstask_new(prb->host, resolved, prb); 453 | if (prb->dnstask == NULL) { 454 | free(prb); 455 | return NULL; 456 | } 457 | } 458 | return (prb); 459 | } 460 | 461 | void 462 | probe_free(struct probe *prb) 463 | { 464 | 465 | if (prb->dnstask) 466 | dnstask_free(prb->dnstask); 467 | deactivate(prb); 468 | free(prb); 469 | } 470 | 471 | /* 472 | * Send out a single probe for a target. 473 | */ 474 | void 475 | probe_send(struct probe *prb, int seq) 476 | { 477 | int len; 478 | int n; 479 | 480 | prb->last_seq = seq; 481 | if (!prb->resolved) { 482 | target_mark(prb->owner, seq, '@'); 483 | return; 484 | } 485 | 486 | if (prb->duplicate) { 487 | target_mark(prb->owner, seq, '"'); /* transmit error */ 488 | return; 489 | } 490 | 491 | if (sa(prb)->sa_family == AF_INET6) { 492 | n = write_packet6(sa(prb), seq & 0xffff); 493 | len = ICMP6_MINLEN + datalen; 494 | } else { 495 | n = write_packet4(sa(prb), seq & 0xffff); 496 | len = ICMP_MINLEN + datalen; 497 | } 498 | 499 | if (n < 0) { 500 | target_mark(prb->owner, seq, '!'); /* transmit error */ 501 | } else if (n != len) { 502 | target_mark(prb->owner, seq, '$'); /* partial transmit */ 503 | } 504 | } 505 | -------------------------------------------------------------------------------- /test/tinytest.c: -------------------------------------------------------------------------------- 1 | /* tinytest.c -- Copyright 2009-2012 Nick Mathewson 2 | * 3 | * Redistribution and use in source and binary forms, with or without 4 | * modification, are permitted provided that the following conditions 5 | * are met: 6 | * 1. Redistributions of source code must retain the above copyright 7 | * notice, this list of conditions and the following disclaimer. 8 | * 2. Redistributions in binary form must reproduce the above copyright 9 | * notice, this list of conditions and the following disclaimer in the 10 | * documentation and/or other materials provided with the distribution. 11 | * 3. The name of the author may not be used to endorse or promote products 12 | * derived from this software without specific prior written permission. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | #ifdef TINYTEST_LOCAL 26 | #include "tinytest_local.h" 27 | #endif 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #ifndef NO_FORKING 35 | 36 | #ifdef _WIN32 37 | #include 38 | #else 39 | #include 40 | #include 41 | #include 42 | #endif 43 | 44 | #if defined(__APPLE__) && defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) 45 | #if (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1060 && \ 46 | __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 1070) 47 | /* Workaround for a stupid bug in OSX 10.6 */ 48 | #define FORK_BREAKS_GCOV 49 | #include 50 | #endif 51 | #endif 52 | 53 | #endif /* !NO_FORKING */ 54 | 55 | #ifndef __GNUC__ 56 | #define __attribute__(x) 57 | #endif 58 | 59 | #include "tinytest.h" 60 | #include "tinytest_macros.h" 61 | 62 | #define LONGEST_TEST_NAME 16384 63 | 64 | static int in_tinytest_main = 0; /**< true if we're in tinytest_main().*/ 65 | static int n_ok = 0; /**< Number of tests that have passed */ 66 | static int n_bad = 0; /**< Number of tests that have failed. */ 67 | static int n_skipped = 0; /**< Number of tests that have been skipped. */ 68 | 69 | static int opt_forked = 0; /**< True iff we're called from inside a win32 fork*/ 70 | static int opt_nofork = 0; /**< Suppress calls to fork() for debugging. */ 71 | static int opt_verbosity = 1; /**< -==quiet,0==terse,1==normal,2==verbose */ 72 | const char *verbosity_flag = ""; 73 | 74 | const struct testlist_alias_t *cfg_aliases=NULL; 75 | 76 | enum outcome { SKIP=2, OK=1, FAIL=0 }; 77 | static enum outcome cur_test_outcome = 0; 78 | const char *cur_test_prefix = NULL; /**< prefix of the current test group */ 79 | /** Name of the current test, if we haven't logged is yet. Used for --quiet */ 80 | const char *cur_test_name = NULL; 81 | 82 | #ifdef _WIN32 83 | /* Copy of argv[0] for win32. */ 84 | static char commandname[MAX_PATH+1]; 85 | #endif 86 | 87 | static void usage(struct testgroup_t *groups, int list_groups) 88 | __attribute__((noreturn)); 89 | static int process_test_option(struct testgroup_t *groups, const char *test); 90 | 91 | static enum outcome 92 | testcase_run_bare_(const struct testcase_t *testcase) 93 | { 94 | void *env = NULL; 95 | int outcome; 96 | if (testcase->setup) { 97 | env = testcase->setup->setup_fn(testcase); 98 | if (!env) 99 | return FAIL; 100 | else if (env == (void*)TT_SKIP) 101 | return SKIP; 102 | } 103 | 104 | cur_test_outcome = OK; 105 | testcase->fn(env); 106 | outcome = cur_test_outcome; 107 | 108 | if (testcase->setup) { 109 | if (testcase->setup->cleanup_fn(testcase, env) == 0) 110 | outcome = FAIL; 111 | } 112 | 113 | return outcome; 114 | } 115 | 116 | #define MAGIC_EXITCODE 42 117 | 118 | #ifndef NO_FORKING 119 | 120 | static enum outcome 121 | testcase_run_forked_(const struct testgroup_t *group, 122 | const struct testcase_t *testcase) 123 | { 124 | #ifdef _WIN32 125 | /* Fork? On Win32? How primitive! We'll do what the smart kids do: 126 | we'll invoke our own exe (whose name we recall from the command 127 | line) with a command line that tells it to run just the test we 128 | want, and this time without forking. 129 | 130 | (No, threads aren't an option. The whole point of forking is to 131 | share no state between tests.) 132 | */ 133 | int ok; 134 | char buffer[LONGEST_TEST_NAME+256]; 135 | STARTUPINFOA si; 136 | PROCESS_INFORMATION info; 137 | DWORD exitcode; 138 | 139 | if (!in_tinytest_main) { 140 | printf("\nERROR. On Windows, testcase_run_forked_ must be" 141 | " called from within tinytest_main.\n"); 142 | abort(); 143 | } 144 | if (opt_verbosity>0) 145 | printf("[forking] "); 146 | 147 | snprintf(buffer, sizeof(buffer), "%s --RUNNING-FORKED %s %s%s", 148 | commandname, verbosity_flag, group->prefix, testcase->name); 149 | 150 | memset(&si, 0, sizeof(si)); 151 | memset(&info, 0, sizeof(info)); 152 | si.cb = sizeof(si); 153 | 154 | ok = CreateProcessA(commandname, buffer, NULL, NULL, 0, 155 | 0, NULL, NULL, &si, &info); 156 | if (!ok) { 157 | printf("CreateProcess failed!\n"); 158 | return 0; 159 | } 160 | WaitForSingleObject(info.hProcess, INFINITE); 161 | GetExitCodeProcess(info.hProcess, &exitcode); 162 | CloseHandle(info.hProcess); 163 | CloseHandle(info.hThread); 164 | if (exitcode == 0) 165 | return OK; 166 | else if (exitcode == MAGIC_EXITCODE) 167 | return SKIP; 168 | else 169 | return FAIL; 170 | #else 171 | int outcome_pipe[2]; 172 | pid_t pid; 173 | (void)group; 174 | 175 | if (pipe(outcome_pipe)) 176 | perror("opening pipe"); 177 | 178 | if (opt_verbosity>0) 179 | printf("[forking] "); 180 | pid = fork(); 181 | #ifdef FORK_BREAKS_GCOV 182 | vproc_transaction_begin(0); 183 | #endif 184 | if (!pid) { 185 | /* child. */ 186 | int test_r, write_r; 187 | char b[1]; 188 | close(outcome_pipe[0]); 189 | test_r = testcase_run_bare_(testcase); 190 | assert(0<=(int)test_r && (int)test_r<=2); 191 | b[0] = "NYS"[test_r]; 192 | write_r = (int)write(outcome_pipe[1], b, 1); 193 | if (write_r != 1) { 194 | perror("write outcome to pipe"); 195 | exit(1); 196 | } 197 | exit(0); 198 | return FAIL; /* unreachable */ 199 | } else { 200 | /* parent */ 201 | int status, r; 202 | char b[1]; 203 | /* Close this now, so that if the other side closes it, 204 | * our read fails. */ 205 | close(outcome_pipe[1]); 206 | r = (int)read(outcome_pipe[0], b, 1); 207 | if (r == 0) { 208 | printf("[Lost connection!] "); 209 | return 0; 210 | } else if (r != 1) { 211 | perror("read outcome from pipe"); 212 | } 213 | waitpid(pid, &status, 0); 214 | close(outcome_pipe[0]); 215 | return b[0]=='Y' ? OK : (b[0]=='S' ? SKIP : FAIL); 216 | } 217 | #endif 218 | } 219 | 220 | #endif /* !NO_FORKING */ 221 | 222 | int 223 | testcase_run_one(const struct testgroup_t *group, 224 | const struct testcase_t *testcase) 225 | { 226 | enum outcome outcome; 227 | 228 | if (testcase->flags & (TT_SKIP|TT_OFF_BY_DEFAULT)) { 229 | if (opt_verbosity>0) 230 | printf("%s%s: %s\n", 231 | group->prefix, testcase->name, 232 | (testcase->flags & TT_SKIP) ? "SKIPPED" : "DISABLED"); 233 | ++n_skipped; 234 | return SKIP; 235 | } 236 | 237 | if (opt_verbosity>0 && !opt_forked) { 238 | printf("%s%s: ", group->prefix, testcase->name); 239 | } else { 240 | if (opt_verbosity==0) printf("."); 241 | cur_test_prefix = group->prefix; 242 | cur_test_name = testcase->name; 243 | } 244 | 245 | #ifndef NO_FORKING 246 | if ((testcase->flags & TT_FORK) && !(opt_forked||opt_nofork)) { 247 | outcome = testcase_run_forked_(group, testcase); 248 | } else { 249 | #else 250 | { 251 | #endif 252 | outcome = testcase_run_bare_(testcase); 253 | } 254 | 255 | if (outcome == OK) { 256 | ++n_ok; 257 | if (opt_verbosity>0 && !opt_forked) 258 | puts(opt_verbosity==1?"OK":""); 259 | } else if (outcome == SKIP) { 260 | ++n_skipped; 261 | if (opt_verbosity>0 && !opt_forked) 262 | puts("SKIPPED"); 263 | } else { 264 | ++n_bad; 265 | if (!opt_forked) 266 | printf("\n [%s FAILED]\n", testcase->name); 267 | } 268 | 269 | if (opt_forked) { 270 | exit(outcome==OK ? 0 : (outcome==SKIP?MAGIC_EXITCODE : 1)); 271 | return 1; /* unreachable */ 272 | } else { 273 | return (int)outcome; 274 | } 275 | } 276 | 277 | int 278 | tinytest_set_flag_(struct testgroup_t *groups, const char *arg, int set, unsigned long flag) 279 | { 280 | int i, j; 281 | size_t length = LONGEST_TEST_NAME; 282 | char fullname[LONGEST_TEST_NAME]; 283 | int found=0; 284 | if (strstr(arg, "..")) 285 | length = strstr(arg,"..")-arg; 286 | for (i=0; groups[i].prefix; ++i) { 287 | for (j=0; groups[i].cases[j].name; ++j) { 288 | struct testcase_t *testcase = &groups[i].cases[j]; 289 | snprintf(fullname, sizeof(fullname), "%s%s", 290 | groups[i].prefix, testcase->name); 291 | if (!flag) { /* Hack! */ 292 | printf(" %s", fullname); 293 | if (testcase->flags & TT_OFF_BY_DEFAULT) 294 | puts(" (Off by default)"); 295 | else if (testcase->flags & TT_SKIP) 296 | puts(" (DISABLED)"); 297 | else 298 | puts(""); 299 | } 300 | if (!strncmp(fullname, arg, length)) { 301 | if (set) 302 | testcase->flags |= flag; 303 | else 304 | testcase->flags &= ~flag; 305 | ++found; 306 | } 307 | } 308 | } 309 | return found; 310 | } 311 | 312 | static void 313 | usage(struct testgroup_t *groups, int list_groups) 314 | { 315 | puts("Options are: [--verbose|--quiet|--terse] [--no-fork]"); 316 | puts(" Specify tests by name, or using a prefix ending with '..'"); 317 | puts(" To skip a test, prefix its name with a colon."); 318 | puts(" To enable a disabled test, prefix its name with a plus."); 319 | puts(" Use --list-tests for a list of tests."); 320 | if (list_groups) { 321 | puts("Known tests are:"); 322 | tinytest_set_flag_(groups, "..", 1, 0); 323 | } 324 | exit(0); 325 | } 326 | 327 | static int 328 | process_test_alias(struct testgroup_t *groups, const char *test) 329 | { 330 | int i, j, n, r; 331 | for (i=0; cfg_aliases && cfg_aliases[i].name; ++i) { 332 | if (!strcmp(cfg_aliases[i].name, test)) { 333 | n = 0; 334 | for (j = 0; cfg_aliases[i].tests[j]; ++j) { 335 | r = process_test_option(groups, cfg_aliases[i].tests[j]); 336 | if (r<0) 337 | return -1; 338 | n += r; 339 | } 340 | return n; 341 | } 342 | } 343 | printf("No such test alias as @%s!",test); 344 | return -1; 345 | } 346 | 347 | static int 348 | process_test_option(struct testgroup_t *groups, const char *test) 349 | { 350 | int flag = TT_ENABLED_; 351 | int n = 0; 352 | if (test[0] == '@') { 353 | return process_test_alias(groups, test + 1); 354 | } else if (test[0] == ':') { 355 | ++test; 356 | flag = TT_SKIP; 357 | } else if (test[0] == '+') { 358 | ++test; 359 | ++n; 360 | if (!tinytest_set_flag_(groups, test, 0, TT_OFF_BY_DEFAULT)) { 361 | printf("No such test as %s!\n", test); 362 | return -1; 363 | } 364 | } else { 365 | ++n; 366 | } 367 | if (!tinytest_set_flag_(groups, test, 1, flag)) { 368 | printf("No such test as %s!\n", test); 369 | return -1; 370 | } 371 | return n; 372 | } 373 | 374 | void 375 | tinytest_set_aliases(const struct testlist_alias_t *aliases) 376 | { 377 | cfg_aliases = aliases; 378 | } 379 | 380 | int 381 | tinytest_main(int c, const char **v, struct testgroup_t *groups) 382 | { 383 | int i, j, n=0; 384 | 385 | #ifdef _WIN32 386 | const char *sp = strrchr(v[0], '.'); 387 | const char *extension = ""; 388 | if (!sp || stricmp(sp, ".exe")) 389 | extension = ".exe"; /* Add an exe so CreateProcess will work */ 390 | snprintf(commandname, sizeof(commandname), "%s%s", v[0], extension); 391 | commandname[MAX_PATH]='\0'; 392 | #endif 393 | for (i=1; i= 1) 446 | printf("%d tests ok. (%d skipped)\n", n_ok, n_skipped); 447 | 448 | return (n_bad == 0) ? 0 : 1; 449 | } 450 | 451 | int 452 | tinytest_get_verbosity_(void) 453 | { 454 | return opt_verbosity; 455 | } 456 | 457 | void 458 | tinytest_set_test_failed_(void) 459 | { 460 | if (opt_verbosity <= 0 && cur_test_name) { 461 | if (opt_verbosity==0) puts(""); 462 | printf("%s%s: ", cur_test_prefix, cur_test_name); 463 | cur_test_name = NULL; 464 | } 465 | cur_test_outcome = 0; 466 | } 467 | 468 | void 469 | tinytest_set_test_skipped_(void) 470 | { 471 | if (cur_test_outcome==OK) 472 | cur_test_outcome = SKIP; 473 | } 474 | 475 | char * 476 | tinytest_format_hex_(const void *val_, unsigned long len) 477 | { 478 | const unsigned char *val = val_; 479 | char *result, *cp; 480 | size_t i; 481 | 482 | if (!val) 483 | return strdup("null"); 484 | if (!(result = malloc(len*2+1))) 485 | return strdup(""); 486 | cp = result; 487 | for (i=0;i> 4]; 489 | *cp++ = "0123456789ABCDEF"[val[i] & 0x0f]; 490 | } 491 | *cp = 0; 492 | return result; 493 | } 494 | -------------------------------------------------------------------------------- /http.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * ---------------------------------------------------------------------------- 3 | * "THE BEER-WARE LICENSE" (Revision 42): 4 | * wrote this file. As long as you retain this notice you 5 | * can do whatever you want with this stuff. If we meet some day, and you think 6 | * this stuff is worth it, you can buy me a beer in return Martin Topholm 7 | * ---------------------------------------------------------------------------- 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #ifdef WITH_SSL 18 | #include 19 | #include 20 | #endif /* WITH_SSL */ 21 | #include 22 | #include 23 | #include 24 | #include "xping.h" 25 | 26 | struct probe { 27 | char host[MAXHOST]; 28 | int resolved; 29 | union addr sa; 30 | char query[64]; 31 | #ifdef WITH_SSL 32 | SSL_CTX *ssl_ctx; 33 | #endif /* WITH_SSL */ 34 | struct session *sessions; 35 | void *dnstask; 36 | void *owner; 37 | }; 38 | 39 | struct session { 40 | struct probe *prb; 41 | int seq; 42 | struct bufferevent *bev; 43 | struct event *ev_timeout; 44 | char *statusline; 45 | int completed; 46 | #ifdef WITH_SSL 47 | SSL *ssl; 48 | #endif /* WITH_SSL */ 49 | struct session *next; 50 | }; 51 | 52 | static regex_t re_target; 53 | static struct timeval tv_timeout; 54 | static void session_eventcb(struct bufferevent *, short, void *); 55 | static void session_readcb_drain(struct bufferevent *, void *); 56 | 57 | /* 58 | * Drop a session and free the associated state. bufferevent_free is 59 | * responsible for closing the actual socket 60 | */ 61 | static void 62 | session_free(struct session *session) 63 | { 64 | 65 | if (session->statusline) 66 | free(session->statusline); 67 | LL_DELETE(session->prb->sessions, session); 68 | if (session->ev_timeout) 69 | event_free(session->ev_timeout); 70 | if (session->bev) { 71 | bufferevent_disable(session->bev, EV_READ|EV_WRITE); 72 | bufferevent_free(session->bev); 73 | } 74 | free(session); 75 | } 76 | 77 | /* 78 | * Construct a http request and send it. 79 | */ 80 | static void 81 | session_send(struct session *session) 82 | { 83 | /* XXX: compose http request */ 84 | evbuffer_add_printf(bufferevent_get_output(session->bev), 85 | "GET %s HTTP/1.1\r\n" 86 | "Host: %s\r\n" 87 | "Connection: close\r\n" 88 | "User-Agent: xping/%s\r\n" 89 | "\r\n", session->prb->query, session->prb->host, version); 90 | } 91 | 92 | /* 93 | * Read response status. Attempt to read the status line, but if CRLF 94 | * can not be found in the first 2048 bytes, consider it an error and 95 | * disconnect. Once status line is read switch to draining the rest of 96 | * the data. The server should close the connection due to 97 | * "Connection: close" header otherwise it will be caught by timeout. 98 | * 99 | * Even though status line says 200, the actual success is registered 100 | * on socket close. 101 | */ 102 | static void 103 | session_readcb_status(struct bufferevent *bev, void *thunk) 104 | { 105 | struct session *session = thunk; 106 | struct evbuffer *evbuf = bufferevent_get_input(bev); 107 | size_t len; 108 | char *line; 109 | char *protocol; 110 | char *number; 111 | 112 | line = evbuffer_readln(evbuf, &len, EVBUFFER_EOL_CRLF); 113 | if (line == NULL) { 114 | if (evbuffer_get_length(evbuf) > 2048) { 115 | session_free(session); 116 | return; 117 | } else 118 | return; /* wait for more data */ 119 | } 120 | session->statusline = line; 121 | /* Parse response line */ 122 | protocol = strsep(&line, " "); 123 | if (line == NULL) { 124 | session_free(session); 125 | return; 126 | } 127 | number = strsep(&line, " "); 128 | if (line == NULL) { 129 | session_free(session); 130 | return; 131 | } 132 | (void)protocol; 133 | (void)number; 134 | if (atoi(number) < 400 && atoi(number) >= 200 ) { 135 | session->completed = 1; 136 | } else { 137 | target_mark(session->prb->owner, session->seq, '%'); 138 | } 139 | /* Drain the response on future callbacks */ 140 | bufferevent_setcb(session->bev, session_readcb_drain, NULL, 141 | session_eventcb, session); 142 | session_readcb_drain(bev, session); 143 | } 144 | 145 | /* 146 | * Discard any data received. This is used after reading the response's 147 | * status line. 148 | */ 149 | static void 150 | session_readcb_drain(struct bufferevent *bev, void *thunk) 151 | { 152 | struct evbuffer *evbuf = bufferevent_get_input(bev); 153 | evbuffer_drain(evbuf, evbuffer_get_length(evbuf)); 154 | } 155 | 156 | /* 157 | * Handle socket events, such as connection failure or success. 158 | * - BEV_EVENT_ERROR - network is unreachable 159 | * - BEV_EVENT_ERROR+READING - connection refused or connection timed out 160 | * - BEV_EVENT_TIMEOUT+READING - write timeout, if activated by 161 | * bufferevent_set_timeouts 162 | */ 163 | static void 164 | session_eventcb(struct bufferevent *bev, short what, void *thunk) 165 | { 166 | struct session *session = thunk; 167 | switch (what & ~(BEV_EVENT_READING|BEV_EVENT_WRITING)) { 168 | case BEV_EVENT_CONNECTED: 169 | session_send(session); 170 | return; 171 | case BEV_EVENT_EOF: 172 | bufferevent_disable(bev, EV_READ|EV_WRITE); 173 | if (session->completed) 174 | target_mark(session->prb->owner, session->seq, '.'); 175 | else 176 | target_mark(session->prb->owner, session->seq, '%'); 177 | break; 178 | case BEV_EVENT_ERROR: 179 | bufferevent_disable(bev, EV_READ|EV_WRITE); 180 | target_mark(session->prb->owner, session->seq, '#'); 181 | break; 182 | case BEV_EVENT_TIMEOUT: 183 | target_mark(session->prb->owner, session->seq, '?'); 184 | break; 185 | } 186 | session_free(session); 187 | } 188 | 189 | /* 190 | * React to manual timeout event (not bufferevent_set_timeouts) by 191 | * closing the session. As bufferevent timeouts on either read or write, 192 | * a real slow (loris) server could keep the session alive indefinetly. 193 | */ 194 | static void 195 | session_timeout(int fd, short what, void *thunk) 196 | { 197 | struct session *session = thunk; 198 | 199 | if (session->completed) 200 | target_mark(session->prb->owner, session->seq, '?'); 201 | session_free(session); 202 | } 203 | 204 | /* 205 | * Resolving of target complete store resolved address. 206 | */ 207 | static void 208 | resolved(int af, void *address, void *thunk) 209 | { 210 | struct probe *prb = thunk; 211 | if (af == AF_INET6) { 212 | sin6(prb)->sin6_family = AF_INET6; 213 | memmove(&sin6(prb)->sin6_addr, (struct in6_addr *)address, 214 | sizeof(sin6(prb)->sin6_addr)); 215 | prb->resolved = 1; 216 | } else if (af == AF_INET) { 217 | sin(prb)->sin_family = AF_INET; 218 | memmove(&sin(prb)->sin_addr, (struct in_addr *)address, 219 | sizeof(sin(prb)->sin_addr)); 220 | prb->resolved = 1; 221 | } else if (af == 0) { 222 | prb->resolved = 0; 223 | } 224 | target_resolved(prb->owner, af, address); 225 | } 226 | 227 | /* 228 | * Prepare datastructures needed for probe 229 | * 1. protocol 230 | * 2. separator, protocol 231 | * 3. hostname 232 | * 4. separator, forced address 233 | * 5. address 234 | * 6. port 235 | * 7. url 236 | */ 237 | #define RE_PROTO 1 238 | #define RE_HOST 3 239 | #define RE_FORCED 5 240 | #define RE_PORT 6 241 | #define RE_URL 7 242 | #define RE_MAX 8 243 | void 244 | probe_setup() 245 | { 246 | tv_timeout.tv_sec = 3 * i_interval / 1000; 247 | tv_timeout.tv_usec = 3 * i_interval % 1000 * 1000; 248 | if (regcomp(&re_target, "^(https?:(//)?)?" 249 | "([0-9A-Za-z.-]+)(\\[([0-9A-Fa-f.:]+)\\])?(:[0-9]+)?(/[^ ]*)?$", 250 | REG_EXTENDED | REG_NEWLINE) != 0) { 251 | fprintf(stderr, 252 | "regcomp: error compiling regular expression\n"); 253 | exit(1); 254 | } 255 | #ifdef WITH_SSL 256 | SSL_library_init(); 257 | #endif /* WITH_SSL */ 258 | } 259 | 260 | void 261 | probe_cleanup(void) 262 | { 263 | 264 | regfree(&re_target); 265 | } 266 | 267 | /* 268 | * Helper function to probe_add, will encode a nibble as hex. 269 | */ 270 | static char 271 | to_hex(int ch) { 272 | static char hex[] = "0123456789abcdef"; 273 | return hex[ch & 0xf]; 274 | } 275 | 276 | /* 277 | * Lookup table to determine which characters NOT to encode. Stolen from 278 | * libevents http.c and set slash (/) to not be encoded. 279 | */ 280 | static const char uri_chars[256] = { 281 | /* 0 */ 282 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 283 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 284 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 285 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 286 | /* 64 */ 287 | 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 288 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 289 | 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 290 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 291 | /* 128 */ 292 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 293 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 294 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 295 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 296 | /* 192 */ 297 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 298 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 299 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 300 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 301 | }; 302 | 303 | /* 304 | * Allocate structure for a new target and parse source line into the 305 | * structure. Report errors on stderr. 306 | */ 307 | struct probe * 308 | probe_new(const char *line, void *owner) 309 | { 310 | struct probe *prb; 311 | union addr sa; 312 | int salen; 313 | int port; 314 | regmatch_t match[RE_MAX]; 315 | char forced[512]; 316 | char *end; 317 | int i, j; 318 | 319 | if (regexec(&re_target, line, RE_MAX, match, 0) != 0) { 320 | fprintf(stderr, "probe_add: can't parse %.128s\n", line); 321 | return NULL; 322 | } 323 | 324 | prb = calloc(1, sizeof(*prb)); 325 | if (prb == NULL) { 326 | perror("probe_add: calloc"); 327 | return (prb); 328 | } 329 | prb->owner = owner; 330 | 331 | if (line[match[RE_PROTO].rm_so + 4] == 's') { 332 | #ifdef WITH_SSL 333 | long ssl_options; 334 | prb->ssl_ctx = SSL_CTX_new(SSLv23_method()); 335 | if (prb->ssl_ctx == NULL) { 336 | perror("probe_add: SSL_CTX_new"); 337 | return NULL; 338 | } 339 | ssl_options = SSL_CTX_get_options(prb->ssl_ctx); 340 | ssl_options |= SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1; 341 | SSL_CTX_set_options(prb->ssl_ctx, ssl_options); 342 | #else /* !WITH_SSL */ 343 | free(prb); 344 | fprintf(stderr, "probe_add: ssl support not compiled in\n"); 345 | return NULL; 346 | #endif /* WITH_SSL */ 347 | } 348 | 349 | strncat(prb->host, line + match[RE_HOST].rm_so, 350 | MIN(sizeof(prb->host) - 1, 351 | match[RE_HOST].rm_eo - match[RE_HOST].rm_so)); 352 | if (match[RE_PORT].rm_so != -1) 353 | port = strtol(line + match[RE_PORT].rm_so + 1, &end, 10); 354 | #ifdef WITH_SSL 355 | else if (prb->ssl_ctx != NULL) 356 | port = 443; 357 | #endif /* !WITH_SSL */ 358 | else 359 | port = 80; 360 | 361 | /* prb->query NULL termination is provided by calloc */ 362 | if (match[RE_URL].rm_so != -1) 363 | for (i = match[RE_URL].rm_so, j = 0; i < match[RE_URL].rm_eo && 364 | j + 3 < sizeof(prb->query) - 1; i++) { 365 | const char *src = &line[i]; 366 | if (uri_chars[(unsigned char)*src]) 367 | prb->query[j++] = *src; 368 | else if (*src == ' ') 369 | prb->query[j++] = '+'; 370 | else 371 | prb->query[j++] = '%', 372 | prb->query[j++] = to_hex(*src >> 4), 373 | prb->query[j++] = to_hex(*src & 0xf); 374 | } 375 | else 376 | prb->query[0] = '/'; 377 | 378 | /* 379 | * Check for presence of forced address e.g. example.com[127.0.0.1]. 380 | * If present parse the address or return an error. Otherwise 381 | * check the hostname for an ip literal, if it doesn't parse 382 | * leave it to target_add to resolve. 383 | */ 384 | salen = sizeof(sa); 385 | if (match[RE_FORCED].rm_so != -1) { 386 | forced[0] = '\0'; 387 | strncat(forced, line + match[RE_FORCED].rm_so, 388 | MIN(sizeof(forced) - 1, 389 | match[RE_FORCED].rm_eo - match[RE_FORCED].rm_so)); 390 | if (evutil_parse_sockaddr_port(forced, &sa.sa, &salen) == 0) { 391 | sa(prb)->sa_family = sa.sa.sa_family; 392 | if (sa.sa.sa_family == AF_INET6) { 393 | memcpy(&sin6(prb)->sin6_addr, 394 | &sa.sin6.sin6_addr, 395 | sizeof(sin6(prb)->sin6_addr)); 396 | } else { 397 | memcpy(&sin(prb)->sin_addr, &sa.sin.sin_addr, 398 | sizeof(sin(prb)->sin_addr)); 399 | } 400 | prb->resolved = 1; 401 | } else { 402 | fprintf(stderr, "probe_add: can't parse %.128s\n", 403 | line); 404 | free(prb); 405 | return NULL; 406 | } 407 | } else { 408 | salen = sizeof(sa); 409 | if (evutil_parse_sockaddr_port(prb->host, &sa.sa, 410 | &salen) == 0) { 411 | sa(prb)->sa_family = sa.sa.sa_family; 412 | if (sa.sa.sa_family == AF_INET6) { 413 | memcpy(&sin6(prb)->sin6_addr, 414 | &sa.sin6.sin6_addr, 415 | sizeof(sin6(prb)->sin6_addr)); 416 | } else { 417 | memcpy(&sin(prb)->sin_addr, &sa.sin.sin_addr, 418 | sizeof(sin(prb)->sin_addr)); 419 | } 420 | prb->resolved = 1; 421 | } else { 422 | prb->dnstask = dnstask_new(prb->host, resolved, prb); 423 | if (prb->dnstask == NULL) { 424 | free(prb); 425 | return NULL; 426 | } 427 | } 428 | } 429 | sin(prb)->sin_port = htons(port); 430 | return (prb); 431 | } 432 | 433 | void probe_free(struct probe *prb) 434 | { 435 | struct session *s, *s_tmp; 436 | 437 | LL_FOREACH_SAFE(prb->sessions, s, s_tmp) { 438 | session_free(s); 439 | } 440 | if (prb->dnstask) 441 | dnstask_free(prb->dnstask); 442 | free(prb); 443 | } 444 | 445 | /* 446 | * Allocate session state for a single target probe and launch the probe. 447 | */ 448 | void probe_send(struct probe *prb, int seq) 449 | { 450 | struct session *session; 451 | char buf[512]; 452 | int salen; 453 | 454 | if (!prb->resolved) { 455 | target_mark(prb->owner, seq, '@'); 456 | return; 457 | } 458 | session = calloc(1, sizeof(*session)); 459 | if (session == NULL) { 460 | target_mark(prb->owner, seq, '!'); 461 | return; 462 | } 463 | session->prb = prb; 464 | session->seq = seq; 465 | LL_APPEND(prb->sessions, session); 466 | #ifdef WITH_SSL 467 | if (prb->ssl_ctx != NULL) { 468 | session->ssl = SSL_new(prb->ssl_ctx); 469 | if (session->ssl == NULL) { 470 | target_mark(prb->owner, seq, '!'); 471 | session_free(session); 472 | return; 473 | } 474 | session->bev = bufferevent_openssl_socket_new(ev_base, -1, 475 | session->ssl, BUFFEREVENT_SSL_CONNECTING, 476 | BEV_OPT_DEFER_CALLBACKS | BEV_OPT_CLOSE_ON_FREE); 477 | } else { 478 | session->bev = bufferevent_socket_new(ev_base, -1, 479 | BEV_OPT_CLOSE_ON_FREE); 480 | } 481 | #else /* !WITH_SSL */ 482 | session->bev = bufferevent_socket_new(ev_base, -1, 483 | BEV_OPT_CLOSE_ON_FREE); 484 | #endif 485 | if (session->bev == NULL) { 486 | target_mark(prb->owner, seq, '!'); 487 | session_free(session); 488 | return; 489 | } 490 | bufferevent_setcb(session->bev, session_readcb_status, NULL, 491 | session_eventcb, session); 492 | evutil_inet_ntop(AF_INET, &sin(prb)->sin_addr, buf, sizeof(buf)); 493 | bufferevent_enable(session->bev, EV_READ); 494 | salen = sa(prb)->sa_family == AF_INET6 ? sizeof(struct sockaddr_in6) : 495 | sizeof(struct sockaddr_in); 496 | if (bufferevent_socket_connect(session->bev, sa(prb), salen) < 0) { 497 | target_mark(prb->owner, seq, '!'); 498 | return; 499 | } 500 | bufferevent_setwatermark(session->bev, EV_READ, 0, 4096); 501 | 502 | session->ev_timeout = event_new(ev_base, -1, 0, session_timeout, 503 | session); 504 | if (session->ev_timeout == NULL) { 505 | target_mark(prb->owner, seq, '!'); 506 | session_free(session); 507 | return; 508 | } 509 | event_add(session->ev_timeout, &tv_timeout); 510 | } 511 | -------------------------------------------------------------------------------- /utlist.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2007-2013, Troy D. Hanson http://uthash.sourceforge.net 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 12 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 13 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 14 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 15 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 16 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 17 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 18 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 19 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 20 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | */ 23 | 24 | #ifndef UTLIST_H 25 | #define UTLIST_H 26 | 27 | #define UTLIST_VERSION 1.9.7 28 | 29 | #include 30 | 31 | /* 32 | * This file contains macros to manipulate singly and doubly-linked lists. 33 | * 34 | * 1. LL_ macros: singly-linked lists. 35 | * 2. DL_ macros: doubly-linked lists. 36 | * 3. CDL_ macros: circular doubly-linked lists. 37 | * 38 | * To use singly-linked lists, your structure must have a "next" pointer. 39 | * To use doubly-linked lists, your structure must "prev" and "next" pointers. 40 | * Either way, the pointer to the head of the list must be initialized to NULL. 41 | * 42 | * ----------------.EXAMPLE ------------------------- 43 | * struct item { 44 | * int id; 45 | * struct item *prev, *next; 46 | * } 47 | * 48 | * struct item *list = NULL: 49 | * 50 | * int main() { 51 | * struct item *item; 52 | * ... allocate and populate item ... 53 | * DL_APPEND(list, item); 54 | * } 55 | * -------------------------------------------------- 56 | * 57 | * For doubly-linked lists, the append and delete macros are O(1) 58 | * For singly-linked lists, append and delete are O(n) but prepend is O(1) 59 | * The sort macro is O(n log(n)) for all types of single/double/circular lists. 60 | */ 61 | 62 | /* These macros use decltype or the earlier __typeof GNU extension. 63 | As decltype is only available in newer compilers (VS2010 or gcc 4.3+ 64 | when compiling c++ code), this code uses whatever method is needed 65 | or, for VS2008 where neither is available, uses casting workarounds. */ 66 | #ifdef _MSC_VER /* MS compiler */ 67 | #if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ 68 | #define LDECLTYPE(x) decltype(x) 69 | #else /* VS2008 or older (or VS2010 in C mode) */ 70 | #define NO_DECLTYPE 71 | #define LDECLTYPE(x) char* 72 | #endif 73 | #else /* GNU, Sun and other compilers */ 74 | #define LDECLTYPE(x) __typeof(x) 75 | #endif 76 | 77 | /* for VS2008 we use some workarounds to get around the lack of decltype, 78 | * namely, we always reassign our tmp variable to the list head if we need 79 | * to dereference its prev/next pointers, and save/restore the real head.*/ 80 | #ifdef NO_DECLTYPE 81 | #define _SV(elt,list) _tmp = (char*)(list); {char **_alias = (char**)&(list); *_alias = (elt); } 82 | #define _NEXT(elt,list,next) ((char*)((list)->next)) 83 | #define _NEXTASGN(elt,list,to,next) { char **_alias = (char**)&((list)->next); *_alias=(char*)(to); } 84 | /* #define _PREV(elt,list,prev) ((char*)((list)->prev)) */ 85 | #define _PREVASGN(elt,list,to,prev) { char **_alias = (char**)&((list)->prev); *_alias=(char*)(to); } 86 | #define _RS(list) { char **_alias = (char**)&(list); *_alias=_tmp; } 87 | #define _CASTASGN(a,b) { char **_alias = (char**)&(a); *_alias=(char*)(b); } 88 | #else 89 | #define _SV(elt,list) 90 | #define _NEXT(elt,list,next) ((elt)->next) 91 | #define _NEXTASGN(elt,list,to,next) ((elt)->next)=(to) 92 | /* #define _PREV(elt,list,prev) ((elt)->prev) */ 93 | #define _PREVASGN(elt,list,to,prev) ((elt)->prev)=(to) 94 | #define _RS(list) 95 | #define _CASTASGN(a,b) (a)=(b) 96 | #endif 97 | 98 | /****************************************************************************** 99 | * The sort macro is an adaptation of Simon Tatham's O(n log(n)) mergesort * 100 | * Unwieldy variable names used here to avoid shadowing passed-in variables. * 101 | *****************************************************************************/ 102 | #define LL_SORT(list, cmp) \ 103 | LL_SORT2(list, cmp, next) 104 | 105 | #define LL_SORT2(list, cmp, next) \ 106 | do { \ 107 | LDECLTYPE(list) _ls_p; \ 108 | LDECLTYPE(list) _ls_q; \ 109 | LDECLTYPE(list) _ls_e; \ 110 | LDECLTYPE(list) _ls_tail; \ 111 | LDECLTYPE(list) _ls_oldhead; \ 112 | LDECLTYPE(list) _tmp; \ 113 | int _ls_insize, _ls_nmerges, _ls_psize, _ls_qsize, _ls_i, _ls_looping; \ 114 | if (list) { \ 115 | _ls_insize = 1; \ 116 | _ls_looping = 1; \ 117 | while (_ls_looping) { \ 118 | _CASTASGN(_ls_p,list); \ 119 | _CASTASGN(_ls_oldhead,list); \ 120 | list = NULL; \ 121 | _ls_tail = NULL; \ 122 | _ls_nmerges = 0; \ 123 | while (_ls_p) { \ 124 | _ls_nmerges++; \ 125 | _ls_q = _ls_p; \ 126 | _ls_psize = 0; \ 127 | for (_ls_i = 0; _ls_i < _ls_insize; _ls_i++) { \ 128 | _ls_psize++; \ 129 | _SV(_ls_q,list); _ls_q = _NEXT(_ls_q,list,next); _RS(list); \ 130 | if (!_ls_q) break; \ 131 | } \ 132 | _ls_qsize = _ls_insize; \ 133 | while (_ls_psize > 0 || (_ls_qsize > 0 && _ls_q)) { \ 134 | if (_ls_psize == 0) { \ 135 | _ls_e = _ls_q; _SV(_ls_q,list); _ls_q = \ 136 | _NEXT(_ls_q,list,next); _RS(list); _ls_qsize--; \ 137 | } else if (_ls_qsize == 0 || !_ls_q) { \ 138 | _ls_e = _ls_p; _SV(_ls_p,list); _ls_p = \ 139 | _NEXT(_ls_p,list,next); _RS(list); _ls_psize--; \ 140 | } else if (cmp(_ls_p,_ls_q) <= 0) { \ 141 | _ls_e = _ls_p; _SV(_ls_p,list); _ls_p = \ 142 | _NEXT(_ls_p,list,next); _RS(list); _ls_psize--; \ 143 | } else { \ 144 | _ls_e = _ls_q; _SV(_ls_q,list); _ls_q = \ 145 | _NEXT(_ls_q,list,next); _RS(list); _ls_qsize--; \ 146 | } \ 147 | if (_ls_tail) { \ 148 | _SV(_ls_tail,list); _NEXTASGN(_ls_tail,list,_ls_e,next); _RS(list); \ 149 | } else { \ 150 | _CASTASGN(list,_ls_e); \ 151 | } \ 152 | _ls_tail = _ls_e; \ 153 | } \ 154 | _ls_p = _ls_q; \ 155 | } \ 156 | _SV(_ls_tail,list); _NEXTASGN(_ls_tail,list,NULL,next); _RS(list); \ 157 | if (_ls_nmerges <= 1) { \ 158 | _ls_looping=0; \ 159 | } \ 160 | _ls_insize *= 2; \ 161 | } \ 162 | } else _tmp=NULL; /* quiet gcc unused variable warning */ \ 163 | } while (0) 164 | 165 | 166 | #define DL_SORT(list, cmp) \ 167 | DL_SORT2(list, cmp, prev, next) 168 | 169 | #define DL_SORT2(list, cmp, prev, next) \ 170 | do { \ 171 | LDECLTYPE(list) _ls_p; \ 172 | LDECLTYPE(list) _ls_q; \ 173 | LDECLTYPE(list) _ls_e; \ 174 | LDECLTYPE(list) _ls_tail; \ 175 | LDECLTYPE(list) _ls_oldhead; \ 176 | LDECLTYPE(list) _tmp; \ 177 | int _ls_insize, _ls_nmerges, _ls_psize, _ls_qsize, _ls_i, _ls_looping; \ 178 | if (list) { \ 179 | _ls_insize = 1; \ 180 | _ls_looping = 1; \ 181 | while (_ls_looping) { \ 182 | _CASTASGN(_ls_p,list); \ 183 | _CASTASGN(_ls_oldhead,list); \ 184 | list = NULL; \ 185 | _ls_tail = NULL; \ 186 | _ls_nmerges = 0; \ 187 | while (_ls_p) { \ 188 | _ls_nmerges++; \ 189 | _ls_q = _ls_p; \ 190 | _ls_psize = 0; \ 191 | for (_ls_i = 0; _ls_i < _ls_insize; _ls_i++) { \ 192 | _ls_psize++; \ 193 | _SV(_ls_q,list); _ls_q = _NEXT(_ls_q,list,next); _RS(list); \ 194 | if (!_ls_q) break; \ 195 | } \ 196 | _ls_qsize = _ls_insize; \ 197 | while (_ls_psize > 0 || (_ls_qsize > 0 && _ls_q)) { \ 198 | if (_ls_psize == 0) { \ 199 | _ls_e = _ls_q; _SV(_ls_q,list); _ls_q = \ 200 | _NEXT(_ls_q,list,next); _RS(list); _ls_qsize--; \ 201 | } else if (_ls_qsize == 0 || !_ls_q) { \ 202 | _ls_e = _ls_p; _SV(_ls_p,list); _ls_p = \ 203 | _NEXT(_ls_p,list,next); _RS(list); _ls_psize--; \ 204 | } else if (cmp(_ls_p,_ls_q) <= 0) { \ 205 | _ls_e = _ls_p; _SV(_ls_p,list); _ls_p = \ 206 | _NEXT(_ls_p,list,next); _RS(list); _ls_psize--; \ 207 | } else { \ 208 | _ls_e = _ls_q; _SV(_ls_q,list); _ls_q = \ 209 | _NEXT(_ls_q,list,next); _RS(list); _ls_qsize--; \ 210 | } \ 211 | if (_ls_tail) { \ 212 | _SV(_ls_tail,list); _NEXTASGN(_ls_tail,list,_ls_e,next); _RS(list); \ 213 | } else { \ 214 | _CASTASGN(list,_ls_e); \ 215 | } \ 216 | _SV(_ls_e,list); _PREVASGN(_ls_e,list,_ls_tail,prev); _RS(list); \ 217 | _ls_tail = _ls_e; \ 218 | } \ 219 | _ls_p = _ls_q; \ 220 | } \ 221 | _CASTASGN(list->prev, _ls_tail); \ 222 | _SV(_ls_tail,list); _NEXTASGN(_ls_tail,list,NULL,next); _RS(list); \ 223 | if (_ls_nmerges <= 1) { \ 224 | _ls_looping=0; \ 225 | } \ 226 | _ls_insize *= 2; \ 227 | } \ 228 | } else _tmp=NULL; /* quiet gcc unused variable warning */ \ 229 | } while (0) 230 | 231 | #define CDL_SORT(list, cmp) \ 232 | CDL_SORT2(list, cmp, prev, next) 233 | 234 | #define CDL_SORT2(list, cmp, prev, next) \ 235 | do { \ 236 | LDECLTYPE(list) _ls_p; \ 237 | LDECLTYPE(list) _ls_q; \ 238 | LDECLTYPE(list) _ls_e; \ 239 | LDECLTYPE(list) _ls_tail; \ 240 | LDECLTYPE(list) _ls_oldhead; \ 241 | LDECLTYPE(list) _tmp; \ 242 | LDECLTYPE(list) _tmp2; \ 243 | int _ls_insize, _ls_nmerges, _ls_psize, _ls_qsize, _ls_i, _ls_looping; \ 244 | if (list) { \ 245 | _ls_insize = 1; \ 246 | _ls_looping = 1; \ 247 | while (_ls_looping) { \ 248 | _CASTASGN(_ls_p,list); \ 249 | _CASTASGN(_ls_oldhead,list); \ 250 | list = NULL; \ 251 | _ls_tail = NULL; \ 252 | _ls_nmerges = 0; \ 253 | while (_ls_p) { \ 254 | _ls_nmerges++; \ 255 | _ls_q = _ls_p; \ 256 | _ls_psize = 0; \ 257 | for (_ls_i = 0; _ls_i < _ls_insize; _ls_i++) { \ 258 | _ls_psize++; \ 259 | _SV(_ls_q,list); \ 260 | if (_NEXT(_ls_q,list,next) == _ls_oldhead) { \ 261 | _ls_q = NULL; \ 262 | } else { \ 263 | _ls_q = _NEXT(_ls_q,list,next); \ 264 | } \ 265 | _RS(list); \ 266 | if (!_ls_q) break; \ 267 | } \ 268 | _ls_qsize = _ls_insize; \ 269 | while (_ls_psize > 0 || (_ls_qsize > 0 && _ls_q)) { \ 270 | if (_ls_psize == 0) { \ 271 | _ls_e = _ls_q; _SV(_ls_q,list); _ls_q = \ 272 | _NEXT(_ls_q,list,next); _RS(list); _ls_qsize--; \ 273 | if (_ls_q == _ls_oldhead) { _ls_q = NULL; } \ 274 | } else if (_ls_qsize == 0 || !_ls_q) { \ 275 | _ls_e = _ls_p; _SV(_ls_p,list); _ls_p = \ 276 | _NEXT(_ls_p,list,next); _RS(list); _ls_psize--; \ 277 | if (_ls_p == _ls_oldhead) { _ls_p = NULL; } \ 278 | } else if (cmp(_ls_p,_ls_q) <= 0) { \ 279 | _ls_e = _ls_p; _SV(_ls_p,list); _ls_p = \ 280 | _NEXT(_ls_p,list,next); _RS(list); _ls_psize--; \ 281 | if (_ls_p == _ls_oldhead) { _ls_p = NULL; } \ 282 | } else { \ 283 | _ls_e = _ls_q; _SV(_ls_q,list); _ls_q = \ 284 | _NEXT(_ls_q,list,next); _RS(list); _ls_qsize--; \ 285 | if (_ls_q == _ls_oldhead) { _ls_q = NULL; } \ 286 | } \ 287 | if (_ls_tail) { \ 288 | _SV(_ls_tail,list); _NEXTASGN(_ls_tail,list,_ls_e,next); _RS(list); \ 289 | } else { \ 290 | _CASTASGN(list,_ls_e); \ 291 | } \ 292 | _SV(_ls_e,list); _PREVASGN(_ls_e,list,_ls_tail,prev); _RS(list); \ 293 | _ls_tail = _ls_e; \ 294 | } \ 295 | _ls_p = _ls_q; \ 296 | } \ 297 | _CASTASGN(list->prev,_ls_tail); \ 298 | _CASTASGN(_tmp2,list); \ 299 | _SV(_ls_tail,list); _NEXTASGN(_ls_tail,list,_tmp2,next); _RS(list); \ 300 | if (_ls_nmerges <= 1) { \ 301 | _ls_looping=0; \ 302 | } \ 303 | _ls_insize *= 2; \ 304 | } \ 305 | } else _tmp=NULL; /* quiet gcc unused variable warning */ \ 306 | } while (0) 307 | 308 | /****************************************************************************** 309 | * singly linked list macros (non-circular) * 310 | *****************************************************************************/ 311 | #define LL_PREPEND(head,add) \ 312 | LL_PREPEND2(head,add,next) 313 | 314 | #define LL_PREPEND2(head,add,next) \ 315 | do { \ 316 | (add)->next = head; \ 317 | head = add; \ 318 | } while (0) 319 | 320 | #define LL_CONCAT(head1,head2) \ 321 | LL_CONCAT2(head1,head2,next) 322 | 323 | #define LL_CONCAT2(head1,head2,next) \ 324 | do { \ 325 | LDECLTYPE(head1) _tmp; \ 326 | if (head1) { \ 327 | _tmp = head1; \ 328 | while (_tmp->next) { _tmp = _tmp->next; } \ 329 | _tmp->next=(head2); \ 330 | } else { \ 331 | (head1)=(head2); \ 332 | } \ 333 | } while (0) 334 | 335 | #define LL_APPEND(head,add) \ 336 | LL_APPEND2(head,add,next) 337 | 338 | #define LL_APPEND2(head,add,next) \ 339 | do { \ 340 | LDECLTYPE(head) _tmp; \ 341 | (add)->next=NULL; \ 342 | if (head) { \ 343 | _tmp = head; \ 344 | while (_tmp->next) { _tmp = _tmp->next; } \ 345 | _tmp->next=(add); \ 346 | } else { \ 347 | (head)=(add); \ 348 | } \ 349 | } while (0) 350 | 351 | #define LL_DELETE(head,del) \ 352 | LL_DELETE2(head,del,next) 353 | 354 | #define LL_DELETE2(head,del,next) \ 355 | do { \ 356 | LDECLTYPE(head) _tmp; \ 357 | if ((head) == (del)) { \ 358 | (head)=(head)->next; \ 359 | } else { \ 360 | _tmp = head; \ 361 | while (_tmp->next && (_tmp->next != (del))) { \ 362 | _tmp = _tmp->next; \ 363 | } \ 364 | if (_tmp->next) { \ 365 | _tmp->next = ((del)->next); \ 366 | } \ 367 | } \ 368 | } while (0) 369 | 370 | /* Here are VS2008 replacements for LL_APPEND and LL_DELETE */ 371 | #define LL_APPEND_VS2008(head,add) \ 372 | LL_APPEND2_VS2008(head,add,next) 373 | 374 | #define LL_APPEND2_VS2008(head,add,next) \ 375 | do { \ 376 | if (head) { \ 377 | (add)->next = head; /* use add->next as a temp variable */ \ 378 | while ((add)->next->next) { (add)->next = (add)->next->next; } \ 379 | (add)->next->next=(add); \ 380 | } else { \ 381 | (head)=(add); \ 382 | } \ 383 | (add)->next=NULL; \ 384 | } while (0) 385 | 386 | #define LL_DELETE_VS2008(head,del) \ 387 | LL_DELETE2_VS2008(head,del,next) 388 | 389 | #define LL_DELETE2_VS2008(head,del,next) \ 390 | do { \ 391 | if ((head) == (del)) { \ 392 | (head)=(head)->next; \ 393 | } else { \ 394 | char *_tmp = (char*)(head); \ 395 | while ((head)->next && ((head)->next != (del))) { \ 396 | head = (head)->next; \ 397 | } \ 398 | if ((head)->next) { \ 399 | (head)->next = ((del)->next); \ 400 | } \ 401 | { \ 402 | char **_head_alias = (char**)&(head); \ 403 | *_head_alias = _tmp; \ 404 | } \ 405 | } \ 406 | } while (0) 407 | #ifdef NO_DECLTYPE 408 | #undef LL_APPEND 409 | #define LL_APPEND LL_APPEND_VS2008 410 | #undef LL_DELETE 411 | #define LL_DELETE LL_DELETE_VS2008 412 | #undef LL_DELETE2 413 | #define LL_DELETE2_VS2008 414 | #undef LL_APPEND2 415 | #define LL_APPEND2 LL_APPEND2_VS2008 416 | #undef LL_CONCAT /* no LL_CONCAT_VS2008 */ 417 | #undef DL_CONCAT /* no DL_CONCAT_VS2008 */ 418 | #endif 419 | /* end VS2008 replacements */ 420 | 421 | #define LL_FOREACH(head,el) \ 422 | LL_FOREACH2(head,el,next) 423 | 424 | #define LL_FOREACH2(head,el,next) \ 425 | for(el=head;el;el=(el)->next) 426 | 427 | #define LL_FOREACH_SAFE(head,el,tmp) \ 428 | LL_FOREACH_SAFE2(head,el,tmp,next) 429 | 430 | #define LL_FOREACH_SAFE2(head,el,tmp,next) \ 431 | for((el)=(head);(el) && (tmp = (el)->next, 1); (el) = tmp) 432 | 433 | #define LL_SEARCH_SCALAR(head,out,field,val) \ 434 | LL_SEARCH_SCALAR2(head,out,field,val,next) 435 | 436 | #define LL_SEARCH_SCALAR2(head,out,field,val,next) \ 437 | do { \ 438 | LL_FOREACH2(head,out,next) { \ 439 | if ((out)->field == (val)) break; \ 440 | } \ 441 | } while(0) 442 | 443 | #define LL_SEARCH(head,out,elt,cmp) \ 444 | LL_SEARCH2(head,out,elt,cmp,next) 445 | 446 | #define LL_SEARCH2(head,out,elt,cmp,next) \ 447 | do { \ 448 | LL_FOREACH2(head,out,next) { \ 449 | if ((cmp(out,elt))==0) break; \ 450 | } \ 451 | } while(0) 452 | 453 | #define LL_REPLACE_ELEM(head, el, add) \ 454 | do { \ 455 | LDECLTYPE(head) _tmp; \ 456 | assert(head != NULL); \ 457 | assert(el != NULL); \ 458 | assert(add != NULL); \ 459 | (add)->next = (el)->next; \ 460 | if ((head) == (el)) { \ 461 | (head) = (add); \ 462 | } else { \ 463 | _tmp = head; \ 464 | while (_tmp->next && (_tmp->next != (el))) { \ 465 | _tmp = _tmp->next; \ 466 | } \ 467 | if (_tmp->next) { \ 468 | _tmp->next = (add); \ 469 | } \ 470 | } \ 471 | } while (0) 472 | 473 | #define LL_PREPEND_ELEM(head, el, add) \ 474 | do { \ 475 | LDECLTYPE(head) _tmp; \ 476 | assert(head != NULL); \ 477 | assert(el != NULL); \ 478 | assert(add != NULL); \ 479 | (add)->next = (el); \ 480 | if ((head) == (el)) { \ 481 | (head) = (add); \ 482 | } else { \ 483 | _tmp = head; \ 484 | while (_tmp->next && (_tmp->next != (el))) { \ 485 | _tmp = _tmp->next; \ 486 | } \ 487 | if (_tmp->next) { \ 488 | _tmp->next = (add); \ 489 | } \ 490 | } \ 491 | } while (0) \ 492 | 493 | 494 | /****************************************************************************** 495 | * doubly linked list macros (non-circular) * 496 | *****************************************************************************/ 497 | #define DL_PREPEND(head,add) \ 498 | DL_PREPEND2(head,add,prev,next) 499 | 500 | #define DL_PREPEND2(head,add,prev,next) \ 501 | do { \ 502 | (add)->next = head; \ 503 | if (head) { \ 504 | (add)->prev = (head)->prev; \ 505 | (head)->prev = (add); \ 506 | } else { \ 507 | (add)->prev = (add); \ 508 | } \ 509 | (head) = (add); \ 510 | } while (0) 511 | 512 | #define DL_APPEND(head,add) \ 513 | DL_APPEND2(head,add,prev,next) 514 | 515 | #define DL_APPEND2(head,add,prev,next) \ 516 | do { \ 517 | if (head) { \ 518 | (add)->prev = (head)->prev; \ 519 | (head)->prev->next = (add); \ 520 | (head)->prev = (add); \ 521 | (add)->next = NULL; \ 522 | } else { \ 523 | (head)=(add); \ 524 | (head)->prev = (head); \ 525 | (head)->next = NULL; \ 526 | } \ 527 | } while (0) 528 | 529 | #define DL_CONCAT(head1,head2) \ 530 | DL_CONCAT2(head1,head2,prev,next) 531 | 532 | #define DL_CONCAT2(head1,head2,prev,next) \ 533 | do { \ 534 | LDECLTYPE(head1) _tmp; \ 535 | if (head2) { \ 536 | if (head1) { \ 537 | _tmp = (head2)->prev; \ 538 | (head2)->prev = (head1)->prev; \ 539 | (head1)->prev->next = (head2); \ 540 | (head1)->prev = _tmp; \ 541 | } else { \ 542 | (head1)=(head2); \ 543 | } \ 544 | } \ 545 | } while (0) 546 | 547 | #define DL_DELETE(head,del) \ 548 | DL_DELETE2(head,del,prev,next) 549 | 550 | #define DL_DELETE2(head,del,prev,next) \ 551 | do { \ 552 | assert((del)->prev != NULL); \ 553 | if ((del)->prev == (del)) { \ 554 | (head)=NULL; \ 555 | } else if ((del)==(head)) { \ 556 | (del)->next->prev = (del)->prev; \ 557 | (head) = (del)->next; \ 558 | } else { \ 559 | (del)->prev->next = (del)->next; \ 560 | if ((del)->next) { \ 561 | (del)->next->prev = (del)->prev; \ 562 | } else { \ 563 | (head)->prev = (del)->prev; \ 564 | } \ 565 | } \ 566 | } while (0) 567 | 568 | 569 | #define DL_FOREACH(head,el) \ 570 | DL_FOREACH2(head,el,next) 571 | 572 | #define DL_FOREACH2(head,el,next) \ 573 | for(el=head;el;el=(el)->next) 574 | 575 | /* this version is safe for deleting the elements during iteration */ 576 | #define DL_FOREACH_SAFE(head,el,tmp) \ 577 | DL_FOREACH_SAFE2(head,el,tmp,next) 578 | 579 | #define DL_FOREACH_SAFE2(head,el,tmp,next) \ 580 | for((el)=(head);(el) && (tmp = (el)->next, 1); (el) = tmp) 581 | 582 | /* these are identical to their singly-linked list counterparts */ 583 | #define DL_SEARCH_SCALAR LL_SEARCH_SCALAR 584 | #define DL_SEARCH LL_SEARCH 585 | #define DL_SEARCH_SCALAR2 LL_SEARCH_SCALAR2 586 | #define DL_SEARCH2 LL_SEARCH2 587 | 588 | #define DL_REPLACE_ELEM(head, el, add) \ 589 | do { \ 590 | assert(head != NULL); \ 591 | assert(el != NULL); \ 592 | assert(add != NULL); \ 593 | if ((head) == (el)) { \ 594 | (head) = (add); \ 595 | (add)->next = (el)->next; \ 596 | if ((el)->next == NULL) { \ 597 | (add)->prev = (add); \ 598 | } else { \ 599 | (add)->prev = (el)->prev; \ 600 | (add)->next->prev = (add); \ 601 | } \ 602 | } else { \ 603 | (add)->next = (el)->next; \ 604 | (add)->prev = (el)->prev; \ 605 | (add)->prev->next = (add); \ 606 | if ((el)->next == NULL) { \ 607 | (head)->prev = (add); \ 608 | } else { \ 609 | (add)->next->prev = (add); \ 610 | } \ 611 | } \ 612 | } while (0) 613 | 614 | #define DL_PREPEND_ELEM(head, el, add) \ 615 | do { \ 616 | assert(head != NULL); \ 617 | assert(el != NULL); \ 618 | assert(add != NULL); \ 619 | (add)->next = (el); \ 620 | (add)->prev = (el)->prev; \ 621 | (el)->prev = (add); \ 622 | if ((head) == (el)) { \ 623 | (head) = (add); \ 624 | } else { \ 625 | (add)->prev->next = (add); \ 626 | } \ 627 | } while (0) \ 628 | 629 | 630 | /****************************************************************************** 631 | * circular doubly linked list macros * 632 | *****************************************************************************/ 633 | #define CDL_PREPEND(head,add) \ 634 | CDL_PREPEND2(head,add,prev,next) 635 | 636 | #define CDL_PREPEND2(head,add,prev,next) \ 637 | do { \ 638 | if (head) { \ 639 | (add)->prev = (head)->prev; \ 640 | (add)->next = (head); \ 641 | (head)->prev = (add); \ 642 | (add)->prev->next = (add); \ 643 | } else { \ 644 | (add)->prev = (add); \ 645 | (add)->next = (add); \ 646 | } \ 647 | (head)=(add); \ 648 | } while (0) 649 | 650 | #define CDL_DELETE(head,del) \ 651 | CDL_DELETE2(head,del,prev,next) 652 | 653 | #define CDL_DELETE2(head,del,prev,next) \ 654 | do { \ 655 | if ( ((head)==(del)) && ((head)->next == (head))) { \ 656 | (head) = 0L; \ 657 | } else { \ 658 | (del)->next->prev = (del)->prev; \ 659 | (del)->prev->next = (del)->next; \ 660 | if ((del) == (head)) (head)=(del)->next; \ 661 | } \ 662 | } while (0) 663 | 664 | #define CDL_FOREACH(head,el) \ 665 | CDL_FOREACH2(head,el,next) 666 | 667 | #define CDL_FOREACH2(head,el,next) \ 668 | for(el=head;el;el=((el)->next==head ? 0L : (el)->next)) 669 | 670 | #define CDL_FOREACH_SAFE(head,el,tmp1,tmp2) \ 671 | CDL_FOREACH_SAFE2(head,el,tmp1,tmp2,prev,next) 672 | 673 | #define CDL_FOREACH_SAFE2(head,el,tmp1,tmp2,prev,next) \ 674 | for((el)=(head), ((tmp1)=(head)?((head)->prev):NULL); \ 675 | (el) && ((tmp2)=(el)->next, 1); \ 676 | ((el) = (((el)==(tmp1)) ? 0L : (tmp2)))) 677 | 678 | #define CDL_SEARCH_SCALAR(head,out,field,val) \ 679 | CDL_SEARCH_SCALAR2(head,out,field,val,next) 680 | 681 | #define CDL_SEARCH_SCALAR2(head,out,field,val,next) \ 682 | do { \ 683 | CDL_FOREACH2(head,out,next) { \ 684 | if ((out)->field == (val)) break; \ 685 | } \ 686 | } while(0) 687 | 688 | #define CDL_SEARCH(head,out,elt,cmp) \ 689 | CDL_SEARCH2(head,out,elt,cmp,next) 690 | 691 | #define CDL_SEARCH2(head,out,elt,cmp,next) \ 692 | do { \ 693 | CDL_FOREACH2(head,out,next) { \ 694 | if ((cmp(out,elt))==0) break; \ 695 | } \ 696 | } while(0) 697 | 698 | #define CDL_REPLACE_ELEM(head, el, add) \ 699 | do { \ 700 | assert(head != NULL); \ 701 | assert(el != NULL); \ 702 | assert(add != NULL); \ 703 | if ((el)->next == (el)) { \ 704 | (add)->next = (add); \ 705 | (add)->prev = (add); \ 706 | (head) = (add); \ 707 | } else { \ 708 | (add)->next = (el)->next; \ 709 | (add)->prev = (el)->prev; \ 710 | (add)->next->prev = (add); \ 711 | (add)->prev->next = (add); \ 712 | if ((head) == (el)) { \ 713 | (head) = (add); \ 714 | } \ 715 | } \ 716 | } while (0) 717 | 718 | #define CDL_PREPEND_ELEM(head, el, add) \ 719 | do { \ 720 | assert(head != NULL); \ 721 | assert(el != NULL); \ 722 | assert(add != NULL); \ 723 | (add)->next = (el); \ 724 | (add)->prev = (el)->prev; \ 725 | (el)->prev = (add); \ 726 | (add)->prev->next = (add); \ 727 | if ((head) == (el)) { \ 728 | (head) = (add); \ 729 | } \ 730 | } while (0) \ 731 | 732 | #endif /* UTLIST_H */ 733 | 734 | --------------------------------------------------------------------------------