From 5d26cb0c0500b85f71a43194f090bb97064f71cf Mon Sep 17 00:00:00 2001 From: Matt Kimball Date: Wed, 30 Nov 2016 17:14:43 -0800 Subject: [PATCH] Added mtr-packet subprocess The mtr-packet tool isolates the raw socket sending/receiving of packets from the mtr user interface. By isolating the socket interactions to a separate process, we can be sure that any security flaws in the user-interface code doesn't expose a raw socket interface to an attacker attempting to escalate privileges. This is a bare-bones implementation, only support ICMP, only support IP version 4, and missing many of the probe customization features available in mtr. It will require some more work to reach feature parity with the current mtr implementation. But it's a start. The include mtr-packet man page explains the protocol format used to communicate with this new process. Included is an automated test for mtr-packet, implemented using Python's unittest module. Though the code actually being tested is implemented in C, Python make it easy to write test cases. 'make check' will test the current build. An alternate code-path for Windows is included in the mtr-packet tool. The mechanism for sending and receiving network probes is significantly different for Windows, as compared to Unix-like operating systems, but the interface provided by mtr-packet is the same. 'make dist-windows-bin' will make a Windows binary distribution. A Cygwin build environment is required, but the resulting binary distribution doesn't require that Cygwin be already installed. Tested on: Ubuntu 16.10, FreeBSD 11.0, MacOS 10.12.1 (Sierra), Windows 7 Since the code changes are significant, more esoteric operating systems may require changes. --- .gitignore | 2 + Makefile.am | 77 ++- bootstrap.sh | 3 +- configure.ac | 3 + display.c | 7 +- display.h | 2 +- mtr-packet.8.in | 226 ++++++ mtr.8.in | 14 +- mtr.bat | 32 + mtr.c | 50 +- net.c | 1463 ++++++++++----------------------------- net.h | 4 - packet/cmdparse.c | 125 ++++ packet/cmdparse.h | 47 ++ packet/command.c | 242 +++++++ packet/command.h | 60 ++ packet/command_cygwin.c | 153 ++++ packet/command_cygwin.h | 48 ++ packet/command_unix.c | 89 +++ packet/command_unix.h | 27 + packet/lint.sh | 9 + packet/packet.c | 103 +++ packet/platform.h | 53 ++ packet/probe.c | 176 +++++ packet/probe.h | 117 ++++ packet/probe_cygwin.c | 181 +++++ packet/probe_cygwin.h | 42 ++ packet/probe_unix.c | 526 ++++++++++++++ packet/probe_unix.h | 49 ++ packet/protocols.h | 84 +++ packet/testpacket.py | 348 ++++++++++ packet/timeval.c | 77 +++ packet/timeval.h | 31 + packet/wait.h | 29 + packet/wait_cygwin.c | 44 ++ packet/wait_unix.c | 86 +++ select.c | 4 - 37 files changed, 3477 insertions(+), 1156 deletions(-) create mode 100644 mtr-packet.8.in create mode 100755 mtr.bat create mode 100644 packet/cmdparse.c create mode 100644 packet/cmdparse.h create mode 100644 packet/command.c create mode 100644 packet/command.h create mode 100644 packet/command_cygwin.c create mode 100644 packet/command_cygwin.h create mode 100644 packet/command_unix.c create mode 100644 packet/command_unix.h create mode 100755 packet/lint.sh create mode 100644 packet/packet.c create mode 100644 packet/platform.h create mode 100644 packet/probe.c create mode 100644 packet/probe.h create mode 100644 packet/probe_cygwin.c create mode 100644 packet/probe_cygwin.h create mode 100644 packet/probe_unix.c create mode 100644 packet/probe_unix.h create mode 100644 packet/protocols.h create mode 100755 packet/testpacket.py create mode 100644 packet/timeval.c create mode 100644 packet/timeval.h create mode 100644 packet/wait.h create mode 100644 packet/wait_cygwin.c create mode 100644 packet/wait_unix.c diff --git a/.gitignore b/.gitignore index 5ac06e86..8087b280 100644 --- a/.gitignore +++ b/.gitignore @@ -19,9 +19,11 @@ stamp-h1* /autom4te.cache/ /.deps/ +/packet/.deps/ /ChangeLog /INSTALL /mtr +/mtr-packet /mtr.8 /mtr-*.tar.gz diff --git a/Makefile.am b/Makefile.am index cdfebd82..249a474f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,6 +1,12 @@ -EXTRA_DIST = SECURITY img/mtr_icon.xpm +EXTRA_DIST = \ + SECURITY \ + mtr.bat \ + img/mtr_icon.xpm \ + packet/testpacket.py \ + packet/lint.sh -sbin_PROGRAMS = mtr +sbin_PROGRAMS = mtr mtr-packet +TESTS = packet/testpacket.py PATHFILES = CLEANFILES = $(PATHFILES) @@ -8,7 +14,7 @@ EXTRA_DIST += $(PATHFILES:=.in) edit_cmd = sed \ -e 's|@VERSION[@]|$(VERSION)|g' -$(PATHFILES): Makefile +%.8: $(srcdir)/%.8.in @ rm -f $@ $@.tmp $(AM_V_at) $(MKDIR_P) $$(dirname $@) $(AM_V_GEN) srcdir=''; \ @@ -16,12 +22,14 @@ $(PATHFILES): Makefile $(edit_cmd) $${srcdir}$@.in >$@.tmp @ mv $@.tmp $@ -dist_man_MANS = mtr.8 -PATHFILES += mtr.8 +$(PATHFILES): Makefile + +dist_man_MANS = mtr.8 mtr-packet.8 +PATHFILES += mtr.8 mtr-packet.8 install-exec-hook: - `setcap cap_net_raw+ep $(DESTDIR)$(sbindir)/mtr` \ - || chmod u+s $(DESTDIR)$(sbindir)/mtr + `setcap cap_net_raw+ep $(DESTDIR)$(sbindir)/mtr-packet` \ + || chmod u+s $(DESTDIR)$(sbindir)/mtr-packet mtr_SOURCES = mtr.c mtr.h \ net.c net.h \ @@ -32,6 +40,7 @@ mtr_SOURCES = mtr.c mtr.h \ report.c report.h \ select.c select.h \ utils.c utils.h \ + packet/cmdparse.c packet/cmdparse.h \ mtr-curses.h \ img/mtr_icon.xpm \ mtr-gtk.h @@ -65,6 +74,60 @@ mtr_INCLUDES = $(GLIB_CFLAGS) -I$(top_builddir) -I$(top_srcdir) mtr_CFLAGS = $(GTK_CFLAGS) $(NCURSES_CFLAGS) mtr_LDADD = $(GTK_LIBS) $(NCURSES_LIBS) $(RESOLV_LIBS) + +mtr_packet_SOURCES = \ + packet/packet.c \ + packet/cmdparse.c packet/cmdparse.h \ + packet/command.c packet/command.h \ + packet/platform.h \ + packet/probe.c packet/probe.h \ + packet/protocols.h \ + packet/timeval.c packet/timeval.h \ + packet/wait.h + + +if CYGWIN + +mtr_packet_SOURCES += \ + packet/command_cygwin.c packet/command_cygwin.h \ + packet/probe_cygwin.c packet/probe_cygwin.h \ + packet/wait_cygwin.c +mtr_packet_LDADD = -lcygwin -licmp -lws2_32 + +dist_windows_aux = \ + $(srcdir)/mtr.bat \ + $(srcdir)/AUTHORS \ + $(srcdir)/COPYING \ + $(srcdir)/README \ + $(srcdir)/NEWS + +distwindir = $(distdir)-win-$(host_cpu) + +# Bundle necessary files for a Windows binary distribution +distdir-win: $(dist_windows_aux) mtr.exe mtr-packet.exe + rm -fr $(distwindir) + mkdir -p $(distwindir) $(distwindir)/bin $(distwindir)/terminfo + cp $(dist_windows_aux) -t $(distwindir) + cp mtr.exe mtr-packet.exe -t $(distwindir)/bin + ldd mtr.exe | grep -v cygdrive | awk '{ print $$3 }' | xargs cp -t $(distwindir)/bin + cp `find /usr/share/terminfo -name cygwin | xargs dirname` -r $(distwindir)/terminfo + +# Zip up a Windows binary distribution +dist-windows-bin: distdir-win + rm -f $(distwindir).zip + zip -rq $(distwindir).zip $(distwindir) + rm -fr $(distwindir) + +else # if CYGWIN + +mtr_packet_SOURCES += \ + packet/command_unix.c packet/command_unix.h \ + packet/probe_unix.c packet/probe_unix.h \ + packet/wait_unix.c + +endif # if CYGWIN + + if BUILD_BASH_COMPLETION dist_bashcompletion_DATA = bash-completion/mtr endif diff --git a/bootstrap.sh b/bootstrap.sh index d75acc49..15ae4166 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -3,5 +3,4 @@ aclocal $ACLOCAL_OPTS autoheader automake --add-missing --copy --foreign -autoconf - +autoconf --force diff --git a/configure.ac b/configure.ac index bc87c865..30bab76e 100644 --- a/configure.ac +++ b/configure.ac @@ -17,6 +17,7 @@ m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])], [AC_SUBST([AM_DEFAULT_VERBOSITY], [1])]) +AC_CANONICAL_HOST AC_PROG_CC # Check pkg-config availability. @@ -29,6 +30,8 @@ before running ./bootstrap.sh again.]) ]) PKG_PROG_PKG_CONFIG +AM_CONDITIONAL([CYGWIN], [test "$host_os" = cygwin]) + # Check bytes in types. AC_CHECK_SIZEOF([unsigned char], [1]) AC_CHECK_SIZEOF([unsigned short], [2]) diff --git a/display.c b/display.c index de80b4a7..41e0ce66 100644 --- a/display.c +++ b/display.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "mtr.h" #include "display.h" @@ -107,8 +108,12 @@ extern void display_open(struct mtr_ctl *ctl) } -extern void display_close(struct mtr_ctl *ctl, time_t now) +extern void display_close(struct mtr_ctl *ctl) { + time_t now; + + now = time(NULL); + switch(ctl->DisplayMode) { case DisplayReport: report_close(ctl); diff --git a/display.h b/display.h index 15355f53..9fb6f3b1 100644 --- a/display.h +++ b/display.h @@ -53,7 +53,7 @@ enum { /* Prototypes for display.c */ extern void display_detect(struct mtr_ctl *ctl, int *argc, char ***argv); extern void display_open(struct mtr_ctl *ctl); -extern void display_close(struct mtr_ctl *ctl, time_t now); +extern void display_close(struct mtr_ctl *ctl); extern void display_redraw(struct mtr_ctl *ctl); extern void display_rawxmit(struct mtr_ctl *ctl, int hostnum, int seq); extern void display_rawping(struct mtr_ctl *ctl, int hostnum, int msec, int seq); diff --git a/mtr-packet.8.in b/mtr-packet.8.in new file mode 100644 index 00000000..de4d72bc --- /dev/null +++ b/mtr-packet.8.in @@ -0,0 +1,226 @@ +.TH MTR-PACKET 8 "@VERSION@" "mtr-packet" "System Administration" +.SH NAME +mtr-packet - send and receive network probes +.SH DESCRIPTION +.B mtr-packet +reads command requests from +.I stdin, +each separated by a newline character, and responds with command replies +to +.I stdout\c +, also each separated by a newline character. The syntactic structure of +requests and replies are the same. The following format is used: +.LP +.RS +.I TOKEN +.I COMMAND +[\c +.I ARGUMENT-NAME +.I ARGUMENT-VALUE +\&...] +.RE +.LP +.I TOKEN +is a unique integer value. The same value will be used as the +.I TOKEN +for the response. This is necessary for associating replies with +requests, as commands may be completed in a different order than they are +requested. The invoker of +.B mtr-packet +should always use the +.I TOKEN +value to determine which command request has completed. +.LP +.I COMMAND +is a string identifying the command request type. A common command +is +.B send-probe\c +, which will transmit one network probe. +.LP +.I ARGUMENT-NAME +strings and +.I ARGUMENT-VALUE +strings always come in pairs. It is a syntactic error to provide an +.I ARGUMENT-NAME +without a corresponding +.I ARGUMENT-VALUE\c +\&. Valid +.I ARGUMENT-NAME +strings depend on the +.I COMMAND +being used. +.SH REQUESTS +.TP +.B send-probe +Send a network probe to a particular IP address. An IP address must +be provided as an argument. +.B send-probe +will reply with +.B reply\c +, +.B no-reply\c +, or +.B ttl-expired\c +\&. + +The following arguments may be used: + +.B ip-4 +.I IP-ADDRESS + +The Internet Protocol version 4 address to probe. + +.B timeout +.I TIMEOUT-SECONDS + +The number of seconds to wait for a response to the probe before +discarding the probe as lost, and generating a +.B no-reply +command reply. + +.B ttl +.I TIME-TO-LIVE + +The time-to-live value for the Internet Protocol packet header used +in constructing the probe. This value determines the number of +network hops through which the probe will travel before a response +is generated by an intermediate network host. + +.TP +.B check-support +Check for support for a particular feature in this version of +.B mtr-packet +and in this particular operating environment. +.B check-support +will reply with +.B feature-supported\c +\&. A +.B feature +argument is required. + +.B feature +.I FEATURE-NAME + +The name of a feature requested. Some features which can be checked +are +.B send-probe +and +.B ip-4\c +\&. The feature +.B version +can be checked to retrieve the version of +.B mtr-packet\c +\&. + +.SH REPLIES +.TP +.B reply +The destination host received the +.B send-probe +probe and replied. Arguments of the reply are the following: + +.B ip-4 +.I IP-ADDRESS + +The Internet Protocol address of the host which replied to the +probe. + +.B round-trip-time +.I TIME + +The time which passed between the transmission of the probe and its +response. The time is provided as a integral number of +microseconds elapsed. + +.TP +.B no-reply +No response to the probe request was received before the timeout +expired. +.TP +.B ttl-expired +The time-to-live value of the transmitted probe expired before +the probe arrived at its intended destination. Arguments of +.B ttl-expired +are: + +.B ip-4 +.I IP-ADDRESS + +The Internet Protocol address of the host at which the time-to-live +value expired. + +.B round-trip-time +.I TIME + +The time which passed between the transmission of the probe and its +response. The time is provided as a integral number of +microseconds elapsed. + +.TP +.B feature-support +A reply to provided to +.B check-support +indicating the availability of a particular feature. The argument +provided is: + +.B support +.I PRESENT + +In most cases, the +.I PRESENT +value will be either +.B ok\c +, indicating the feature is supported, or +.B no\c +, indicating no support for the feature. + +In the case that +.B version +is the requested +.I FEATURE-NAME\c +, the version of +.B mtr-packet +is provided as the +.I PRESENT +value. + +.SH EXAMPLE +A controlling program may start +.B mtr-packet +as a child process and issue the following command on +.I stdin\c +: +.LP +.RS +42 send-probe ip-4 127.0.0.1 +.RE +.LP +This will send a network probe to the loopback interface. When the probe +completes, +.B +mtr-packet +will provide a response on +.I stdout +such as the following: +.LP +.RS +42 reply ip-4 127.0.0.1 round-trip-time 126 +.RE +.LP +This indicates that the loopback address replied to the probe, and the +round-trip time of the probe was 126 microseconds. +.SH CONTACT INFORMATION +.PP +For the latest version, see the mtr web page at +.UR http://\:www.\:bitwizard.\:nl/\:mtr/ +.UE +.PP +The mtr mailinglist was little used and is no longer active. +.PP +For patches, bug reports, or feature requests, please open an issue on +GitHub at: +.UR https://\:github\:.com/\:traviscross/\:mtr +.UE . +.SH "SEE ALSO" +.BR mtr (8), +TCP/IP Illustrated (Stevens, ISBN 0201633469). diff --git a/mtr.8.in b/mtr.8.in index 404024d4..58cd0815 100644 --- a/mtr.8.in +++ b/mtr.8.in @@ -448,8 +448,19 @@ passed in .B MTR_OPTIONS\c ). .TP +.B MTR_PACKET +A path to the +.I mtr-packet +executable, to be used for sending and receiving network probes. If +.B MTR_PACKET +is unset, the +.B PATH +will be used to search for an +.I mtr-packet +executable. +.TP .B DISPLAY -Used for the GTK+ frontend. +Specifies an X11 server for the GTK+ frontend. .SH BUGS Some modern routers give a lower priority to ICMP ECHO packets than to other network traffic. Consequently, the reliability of these @@ -470,6 +481,7 @@ GitHub at: .UR https://\:github\:.com/\:traviscross/\:mtr .UE . .SH "SEE ALSO" +.BR mtr-packet (8), .BR traceroute (8), .BR ping (8), .BR socket (7), diff --git a/mtr.bat b/mtr.bat new file mode 100755 index 00000000..bda3fdd4 --- /dev/null +++ b/mtr.bat @@ -0,0 +1,32 @@ +@echo off +rem +rem mtr -- a network diagnostic tool +rem Copyright (C) 2016 Matt Kimball +rem +rem This program is free software; you can redistribute it and/or modify +rem it under the terms of the GNU General Public License version 2 as +rem published by the Free Software Foundation. +rem +rem This program is distributed in the hope that it will be useful, +rem but WITHOUT ANY WARRANTY; without even the implied warranty of +rem MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +rem GNU General Public License for more details. +rem +rem You should have received a copy of the GNU General Public License +rem along with this program; if not, write to the Free Software +rem Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +rem + +rem Assume the path of this batch file is the mtr installation location +set MTR_DIR=%~dp0 + +set MTR_BIN=%MTR_DIR%\bin + +rem ncurses needs to locate the cygwin terminfo file +set TERMINFO=%MTR_DIR%\terminfo + +rem mtr needs to know the location to the packet generator +set MTR_PACKET=%MTR_BIN%\mtr-packet.exe + +rem Pass along commandline arguments +%MTR_BIN%\mtr %* diff --git a/mtr.c b/mtr.c index a42fe2ad..48307ff2 100644 --- a/mtr.c +++ b/mtr.c @@ -41,7 +41,6 @@ #include #include #include -#include #include #include #include @@ -623,7 +622,6 @@ static void init_rand(void) extern int main(int argc, char **argv) { struct hostent * host = NULL; - int net_preopen_result; struct addrinfo hints, *res; int gai_error; struct hostent trhost; @@ -632,7 +630,6 @@ extern int main(int argc, char **argv) #ifdef ENABLE_IPV6 struct sockaddr_in6 * sa6; #endif - time_t now; names_t *names_root = NULL; names_t **names_head = &names_root; @@ -656,20 +653,13 @@ extern int main(int argc, char **argv) ctl.ipinfo_max = -1; xstrncpy(ctl.fld_active, "LS NABWV", 2 * MAXFLD); - /* Get the raw sockets first thing, so we can drop to user euid immediately */ - - if ( ( net_preopen_result = net_preopen () ) ) { - error(EXIT_FAILURE, errno, "Unable to get raw sockets"); - } - - /* Now drop to user permissions */ - if (setgid(getgid()) || setuid(getuid())) { - error(EXIT_FAILURE, errno, "Unable to drop permissions"); - } - - /* Double check, just in case */ + /* + mtr used to be suid root. It should not be with this version. + We'll check so that we can notify people using installation + mechanisms with obsolete assumptions. + */ if ((geteuid() != getuid()) || (getegid() != getgid())) { - error(EXIT_FAILURE, errno, "Unable to drop permissions"); + error(EXIT_FAILURE, errno, "mtr should not run suid"); } /* This will check if stdout/stderr writing is successful */ @@ -694,11 +684,6 @@ extern int main(int argc, char **argv) append_to_names(names_head, name); } - /* Now that we know mtrtype we can select which socket to use */ - if (net_selectsocket(&ctl) != 0) { - error(EXIT_FAILURE, 0, "Couldn't determine raw socket type"); - } - if (!names_root) append_to_names (names_head, "localhost"); /* default: localhost. */ names_head = &names_root; @@ -709,16 +694,6 @@ extern int main(int argc, char **argv) xstrncpy(ctl.LocalHostname, "UNKNOWNHOST", sizeof(ctl.LocalHostname)); } - if (net_preopen_result != 0) { - error(0, 0, "Unable to get raw socket. (Executable not suid?)"); - if (ctl.DisplayMode != DisplayCSV) - exit(EXIT_FAILURE); - else { - names_root = names_root->next; - continue; - } - } - /* gethostbyname2() is deprecated so we'll use getaddrinfo() instead. */ memset( &hints, 0, sizeof hints ); hints.ai_family = ctl.af; @@ -777,16 +752,6 @@ extern int main(int argc, char **argv) } } - if (net_set_interfaceaddress (&ctl) != 0) { - error(0, 0, "Couldn't set interface address: %s", ctl.InterfaceAddress); - if (ctl.DisplayMode != DisplayCSV) - exit(EXIT_FAILURE); - else { - names_root = names_root->next; - continue; - } - } - lock(stdout); dns_open(&ctl); @@ -795,8 +760,7 @@ extern int main(int argc, char **argv) display_loop(&ctl); net_end_transit(); - now = time(NULL); - display_close(&ctl, now); + display_close(&ctl); unlock(stdout); if (ctl.DisplayMode != DisplayCSV) diff --git a/net.c b/net.c index c7967ea3..c6288e74 100644 --- a/net.c +++ b/net.c @@ -27,12 +27,16 @@ #include #include #include +#include #include +#include #include #include #ifdef HAVE_FCNTL_H # include #endif +#include +#include #include #include #include @@ -53,87 +57,14 @@ #include "display.h" #include "dns.h" #include "utils.h" +#include "packet/cmdparse.h" #define MinSequence 33000 #define MaxSequence 65536 -static int packetsize; /* packet size used by ping */ -static int spacketsize; /* packet size used by sendto */ - -static void sockaddrtop( struct sockaddr * saddr, char * strptr, size_t len ); -static void decodempls(int, char *, struct mplslen *, int); - -/* We can't rely on header files to provide this information, because - the fields have different names between, for instance, Linux and - Solaris */ -struct ICMPHeader { - uint8_t type; - uint8_t code; - uint16_t checksum; - uint16_t id; - uint16_t sequence; -}; - -/* Structure of an UDP header. */ -struct UDPHeader { - uint16_t srcport; - uint16_t dstport; - uint16_t length; - uint16_t checksum; -}; - -/* Structure of an TCP header, as far as we need it. */ -struct TCPHeader { - uint16_t srcport; - uint16_t dstport; - uint32_t seq; -}; - -/* This ifdef is unnecessary. But it should trigger errors if I forget an - ifdef HAS_SCTP further down. (Success! I forgot one and the compiler - told me the line number!) */ -#ifdef HAS_SCTP -/* Structure of an SCTP header */ -struct SCTPHeader { - uint16_t srcport; - uint16_t dstport; - uint32_t veri_tag; -}; -#endif - -/* Structure of an IPv4 UDP pseudoheader. */ -struct UDPv4PHeader { - uint32_t saddr; - uint32_t daddr; - uint8_t zero; - uint8_t protocol; - uint16_t len; -}; - -/* Structure of an IP header. */ -struct IPHeader { - uint8_t version; - uint8_t tos; - uint16_t len; - uint16_t id; - uint16_t frag; - uint8_t ttl; - uint8_t protocol; - uint16_t check; - uint32_t saddr; - uint32_t daddr; -}; - -#ifndef ICMP_ECHOREPLY -# define ICMP_ECHOREPLY 0 -# define ICMP_DEST_UNREACH 3 -# define ICMP_ECHO 8 -# define ICMP_TIME_EXCEEDED 11 -#endif +#define PACKET_REPLY_BUFFER_SIZE 4096 -#ifndef SOL_IP -# define SOL_IP 0 -#endif +static int packetsize; /* packet size used by ping */ struct nethost { ip_t addr; @@ -169,19 +100,28 @@ struct sequence { }; +/* We use a pipe to the mtr-packet subprocess to generate probes */ +struct packet_command_pipe_t { + /* the process id of mtr-packet */ + pid_t pid; + + /* the end of the pipe we read for replies */ + int read_fd; + + /* the end of the pipe we write for commands */ + int write_fd; + + /* storage for incoming replies */ + char reply_buffer[PACKET_REPLY_BUFFER_SIZE]; + + /* the number of bytes currently used in reply_buffer */ + size_t reply_buffer_used; +}; + + static struct nethost host[MaxHost]; static struct sequence sequence[MaxSequence]; - -static int sendsock4; -static int sendsock4_icmp; -static int sendsock4_udp; -static int recvsock4; -static int sendsock6; -static int sendsock6_icmp; -static int sendsock6_udp; -static int recvsock6; -static int sendsock; -static int recvsock; +static struct packet_command_pipe_t packet_command_pipe; #ifdef ENABLE_IPV6 static struct sockaddr_storage sourcesockaddr_struct; @@ -193,7 +133,6 @@ static struct sockaddr_in sourcesockaddr_struct; static struct sockaddr_in remotesockaddr_struct; #endif -static struct sockaddr * sourcesockaddr = (struct sockaddr *) &sourcesockaddr_struct; static struct sockaddr * remotesockaddr = (struct sockaddr *) &remotesockaddr_struct; static struct sockaddr_in * ssa4 = (struct sockaddr_in *) &sourcesockaddr_struct; static struct sockaddr_in * rsa4 = (struct sockaddr_in *) &remotesockaddr_struct; @@ -223,70 +162,6 @@ extern int calc_deltatime (float waittime) } -static int checksum(void *data, int sz) -{ - uint16_t *ch; - uint32_t sum; - uint16_t odd; - - sum = 0; - ch = data; - if (sz % 2) { - ((char *)&odd)[0] = ((char *)data)[sz - 1]; - sum = odd; - } - sz = sz / 2; - while (sz--) { - sum += *(ch++); - } - while (sum >> 16) { - sum = (sum >> 16) + (sum & 0xffff); - } - - return (~sum & 0xffff); -} - - -/* Prepend pseudoheader to the udp datagram and calculate checksum */ -static int udp_checksum(struct mtr_ctl *ctl, void *pheader, void *udata, - int psize, int dsize, int alt_checksum) -{ - size_t tsize = psize + dsize; - char *csumpacket; - int ret; - struct UDPv4PHeader *prepend; - struct UDPv4PHeader *udppheader; - struct UDPHeader *content; - struct UDPHeader *udpdata; - - csumpacket = xmalloc(tsize); - memset(csumpacket, (unsigned char) abs(ctl->bitpattern), tsize); - if (alt_checksum && dsize >= 2) { - csumpacket[psize + sizeof(struct UDPHeader)] = 0; - csumpacket[psize + sizeof(struct UDPHeader) + 1] = 0; - } - - prepend = (struct UDPv4PHeader *) csumpacket; - udppheader = (struct UDPv4PHeader *) pheader; - prepend->saddr = udppheader->saddr; - prepend->daddr = udppheader->daddr; - prepend->zero = 0; - prepend->protocol = udppheader->protocol; - prepend->len = udppheader->len; - - content = (struct UDPHeader *)(csumpacket + psize); - udpdata = (struct UDPHeader *) udata; - content->srcport = udpdata->srcport; - content->dstport = udpdata->dstport; - content->length = udpdata->length; - content->checksum = udpdata->checksum; - - ret = checksum(csumpacket, tsize); - free(csumpacket); - return ret; -} - - static void save_sequence(struct mtr_ctl *ctl, int index, int seq) { display_rawxmit(ctl, index, seq); @@ -317,438 +192,37 @@ static int new_sequence(struct mtr_ctl *ctl, int index) return seq; } -/* Attempt to connect to a TCP port with a TTL */ -static void net_send_tcp(struct mtr_ctl *ctl, int index) -{ - int ttl, s; - int port = 0; - int flags; - struct sockaddr_storage local; - struct sockaddr_storage remote; - struct sockaddr_in *local4 = (struct sockaddr_in *) &local; - struct sockaddr_in *remote4 = (struct sockaddr_in *) &remote; -#ifdef ENABLE_IPV6 - struct sockaddr_in6 *local6 = (struct sockaddr_in6 *) &local; - struct sockaddr_in6 *remote6 = (struct sockaddr_in6 *) &remote; -#endif - socklen_t len; - - ttl = index + 1; - - s = socket(ctl->af, SOCK_STREAM, 0); - if (s < 0) { - display_clear(ctl); - perror("socket()"); - exit(EXIT_FAILURE); - } - - memset(&local, 0, sizeof (local)); - memset(&remote, 0, sizeof (remote)); - local.ss_family = ctl->af; - remote.ss_family = ctl->af; - - switch (ctl->af) { - case AF_INET: - addrcpy((void *) &local4->sin_addr, (void *) &ssa4->sin_addr, ctl->af); - addrcpy((void *) &remote4->sin_addr, (void *) remoteaddress, ctl->af); - remote4->sin_port = htons(ctl->remoteport); - len = sizeof (struct sockaddr_in); - break; -#ifdef ENABLE_IPV6 - case AF_INET6: - addrcpy((void *) &local6->sin6_addr, (void *) &ssa6->sin6_addr, ctl->af); - addrcpy((void *) &remote6->sin6_addr, (void *) remoteaddress, ctl->af); - remote6->sin6_port = htons(ctl->remoteport); - len = sizeof (struct sockaddr_in6); - break; -#endif - } - - if (bind(s, (struct sockaddr *) &local, len)) { - display_clear(ctl); - error(EXIT_FAILURE, errno, "bind()"); - } - - if (getsockname(s, (struct sockaddr *) &local, &len)) { - display_clear(ctl); - error(EXIT_FAILURE, errno, "getsockname()"); - } - - flags = fcntl(s, F_GETFL, 0); - if (flags < 0) { - display_clear(ctl); - error(EXIT_FAILURE, errno, "fcntl(F_GETFL)"); - } - - if (fcntl (s, F_SETFL, flags | O_NONBLOCK) < 0) { - display_clear(ctl); - error(EXIT_FAILURE, errno, "fcntl(F_SETFL, O_NONBLOCK)"); - } - - - switch (ctl->af) { - case AF_INET: - if (setsockopt(s, IPPROTO_IP, IP_TTL, &ttl, sizeof (ttl))) { - display_clear(ctl); - error(EXIT_FAILURE, errno, "setsockopt IP_TTL"); - } - if (setsockopt(s, IPPROTO_IP, IP_TOS, &ctl->tos, sizeof (ctl->tos))) { - display_clear(ctl); - error(EXIT_FAILURE, errno, "setsockopt IP_TOS"); - } - break; -#ifdef ENABLE_IPV6 - case AF_INET6: - if (setsockopt(s, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof (ttl))) { - display_clear(ctl); - error(EXIT_FAILURE, errno, "setsockopt IPPROTO_IPV6 ttl"); - } - break; -#endif - } - -#ifdef SO_MARK - if (ctl->mark && setsockopt( s, SOL_SOCKET, SO_MARK, &ctl->mark, sizeof ctl->mark ) ) { - error(EXIT_FAILURE, errno, "setsockopt SO_MARK"); - } -#endif - - switch (local.ss_family) { - case AF_INET: - port = ntohs(local4->sin_port); - break; -#ifdef ENABLE_IPV6 - case AF_INET6: - port = ntohs(local6->sin6_port); - break; -#endif - default: - display_clear(ctl); - error(EXIT_FAILURE, 0, "unknown address family"); - } - - save_sequence(ctl, index, port); - gettimeofday(&sequence[port].time, NULL); - sequence[port].socket = s; - - connect(s, (struct sockaddr *) &remote, len); -} - -#ifdef HAS_SCTP -/* Attempt to connect to a SCTP port with a TTL */ -static void net_send_sctp(struct mtr_ctl *ctl, int index) -{ - int ttl, s; - int opt = 1; - int port = 0; - struct sockaddr_storage local; - struct sockaddr_storage remote; - struct sockaddr_in *local4 = (struct sockaddr_in *) &local; - struct sockaddr_in *remote4 = (struct sockaddr_in *) &remote; -# ifdef ENABLE_IPV6 - struct sockaddr_in6 *local6 = (struct sockaddr_in6 *) &local; - struct sockaddr_in6 *remote6 = (struct sockaddr_in6 *) &remote; -# endif - socklen_t len; - - ttl = index + 1; - - s = socket(ctl->af, SOCK_STREAM, IPPROTO_SCTP); - if (s < 0) { - display_clear(ctl); - error(EXIT_FAILURE, errno, "socket()"); - } - - memset(&local, 0, sizeof (local)); - memset(&remote, 0, sizeof (remote)); - local.ss_family = ctl->af; - remote.ss_family = ctl->af; - - switch (ctl->af) { - case AF_INET: - addrcpy((void *) &local4->sin_addr, (void *) &ssa4->sin_addr, ctl->af); - addrcpy((void *) &remote4->sin_addr, (void *) remoteaddress, ctl->af); - remote4->sin_port = htons(ctl->remoteport); - len = sizeof (struct sockaddr_in); - break; -# ifdef ENABLE_IPV6 - case AF_INET6: - addrcpy((void *) &local6->sin6_addr, (void *) &ssa6->sin6_addr, ctl->af); - addrcpy((void *) &remote6->sin6_addr, (void *) remoteaddress, ctl->af); - remote6->sin6_port = htons(ctl->remoteport); - len = sizeof (struct sockaddr_in6); - break; -# endif - } - - if (bind(s, (struct sockaddr *) &local, len)) { - display_clear(ctl); - error(EXIT_FAILURE, errno, "bind()"); - } - - if (getsockname(s, (struct sockaddr *) &local, &len)) { - display_clear(ctl); - error(EXIT_FAILURE, errno, "getsockname()"); - } - - opt = 1; - if (ioctl(s, FIONBIO, &opt)) { - display_clear(ctl); - error(EXIT_FAILURE, errno, "ioctl FIONBIO"); - } - - switch (ctl->af) { - case AF_INET: - if (setsockopt(s, IPPROTO_IP, IP_TTL, &ttl, sizeof (ttl))) { - display_clear(ctl); - error(EXIT_FAILURE, errno, "setsockopt IP_TTL"); - } - if (setsockopt(s, IPPROTO_IP, IP_TOS, &ctl->tos, sizeof (ctl->tos))) { - display_clear(ctl); - error(EXIT_FAILURE, errno, "setsockopt IP_TOS"); - } - break; -# ifdef ENABLE_IPV6 - case AF_INET6: - if (setsockopt(s, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof (ttl))) { - display_clear(ctl); - error(EXIT_FAILURE, errno, "setsockopt IPPROTO_IPV6 ttl"); - } - break; -# endif - } - -# ifdef SO_MARK - if (ctl->mark && setsockopt( s, SOL_SOCKET, SO_MARK, &ctl->mark, sizeof ctl->mark ) ) { - error(EXIT_FAILURE, errno, "setsockopt SO_MARK"); - } -# endif - - switch (local.ss_family) { - case AF_INET: - port = ntohs(local4->sin_port); - break; -# ifdef ENABLE_IPV6 - case AF_INET6: - port = ntohs(local6->sin6_port); - break; -# endif - default: - display_clear(ctl); - error(EXIT_FAILURE, 0, "unknown address family"); - } - - save_sequence(ctl, index, port); - gettimeofday(&sequence[port].time, NULL); - sequence[port].socket = s; - - connect(s, (struct sockaddr *) &remote, len); -} -#endif /* HAS_SCTP */ - /* Attempt to find the host at a particular number of hops away */ static void net_send_query(struct mtr_ctl *ctl, int index) { - /*ok char packet[sizeof(struct IPHeader) + sizeof(struct ICMPHeader)];*/ - char packet[MAXPACKET]; - struct IPHeader *ip = (struct IPHeader *) packet; - struct ICMPHeader *icmp = NULL; - struct UDPHeader *udp = NULL; - struct UDPv4PHeader *udpp = NULL; - uint16_t checksum_result; - /*ok int packetsize = sizeof(struct IPHeader) + sizeof(struct ICMPHeader) + datasize;*/ - int rv; - static int first=1; - int ttl, iphsize = 0, echotype = 0, salen = 0; -#ifdef ENABLE_IPV6 - /* offset for ipv6 checksum calculation */ - int offset = 6; -#endif - /* BSD-derived kernels use host byte order for the IP length and offset - fields when using raw sockets. We detect this automatically at - run-time and do the right thing. */ - static int BSDfix = 0; - - if (ctl->mtrtype == IPPROTO_TCP) { - net_send_tcp(ctl, index); - return; - } - -#ifdef HAS_SCTP - if (ctl->mtrtype == IPPROTO_SCTP) { - net_send_sctp(ctl, index); - return; - } -#endif - - ttl = index + 1; - - if ( packetsize < MINPACKET ) packetsize = MINPACKET; - if ( packetsize > MAXPACKET ) packetsize = MAXPACKET; - if ( ctl->mtrtype == IPPROTO_UDP && ctl->remoteport && packetsize < (MINPACKET + 2)) { - packetsize = MINPACKET + 2; - } - - memset(packet, (unsigned char) abs(ctl->bitpattern), abs(packetsize)); - - switch ( ctl->af ) { - case AF_INET: -#if !defined(IP_HDRINCL) && defined(IP_TOS) && defined(IP_TTL) - iphsize = 0; - if ( setsockopt( sendsock, IPPROTO_IP, IP_TOS, &ctl->tos, sizeof ctl->tos ) ) { - error(EXIT_FAILURE, errno, "setsockopt IP_TOS"); - } - if ( setsockopt( sendsock, IPPROTO_IP, IP_TTL, &ttl, sizeof ttl ) ) { - error(EXIT_FAILURE, errno, "setsockopt IP_TTL"); - } -#else - iphsize = sizeof (struct IPHeader); - - ip->version = 0x45; - ip->tos = ctl->tos; - ip->len = BSDfix ? abs(packetsize): htons (abs(packetsize)); - ip->id = 0; - ip->frag = 0; /* 1, if want to find mtu size? Min */ - ip->ttl = ttl; - ip->protocol = ctl->mtrtype; - ip->check = 0; - - /* BSD needs the source address here, Linux & others do not... */ - addrcpy( (void *) &(ip->saddr), (void *) &(ssa4->sin_addr), AF_INET ); - addrcpy( (void *) &(ip->daddr), (void *) remoteaddress, AF_INET ); -#endif - echotype = ICMP_ECHO; - salen = sizeof (struct sockaddr_in); - break; -#ifdef ENABLE_IPV6 - case AF_INET6: - iphsize = 0; - if ( setsockopt( sendsock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, - &ttl, sizeof ttl ) ) { - error(EXIT_FAILURE, errno, "setsockopt IPV6_UNICAST_HOPS"); - } - echotype = ICMP6_ECHO_REQUEST; - salen = sizeof (struct sockaddr_in6); - break; -#endif - } + int seq = new_sequence(ctl, index); + int time_to_live = index + 1; + char ip_string[INET_ADDRSTRLEN]; -#ifdef SO_MARK - if (ctl->mark && setsockopt( sendsock, SOL_SOCKET, SO_MARK, &ctl->mark, sizeof ctl->mark ) ) { - error(EXIT_FAILURE, errno, "setsockopt SO_MARK"); - } -#endif - - switch ( ctl->mtrtype ) { - case IPPROTO_ICMP: - icmp = (struct ICMPHeader *)(packet + iphsize); - icmp->type = echotype; - icmp->code = 0; - icmp->checksum = 0; - icmp->id = getpid(); - icmp->sequence = new_sequence(ctl, index); - icmp->checksum = checksum(icmp, abs(packetsize) - iphsize); - - gettimeofday(&sequence[icmp->sequence].time, NULL); - break; - - case IPPROTO_UDP: - udp = (struct UDPHeader *)(packet + iphsize); - udp->checksum = 0; - if (!ctl->localport) { - ctl->localport = (uint16_t)getpid(); - if (ctl->localport < MinPort) - ctl->localport += MinPort; - } - udp->srcport = htons(ctl->localport); - udp->length = htons(abs(packetsize) - iphsize); - - if (!ctl->remoteport) { - udp->dstport = new_sequence(ctl, index); - gettimeofday(&sequence[udp->dstport].time, NULL); - udp->dstport = htons(udp->dstport); - } else { - /* keep dstport constant, stuff sequence into the checksum */ - udp->dstport = htons(ctl->remoteport); - udp->checksum = new_sequence(ctl, index); - gettimeofday(&sequence[udp->checksum].time, NULL); - udp->checksum = htons(udp->checksum); - } - break; + /* Conver the remote IP address to a string */ + if (inet_ntop(AF_INET, remoteaddress, ip_string, INET_ADDRSTRLEN) == NULL) { + display_close(ctl); + error(EXIT_FAILURE, errno, "failure stringifying remote IP address"); } - switch ( ctl->af ) { - case AF_INET: - switch ( ctl->mtrtype ) { - case IPPROTO_UDP: - /* checksum is not mandatory. only calculate if we know ip->saddr */ - if (udp->checksum) { - udpp = (struct UDPv4PHeader *)(xmalloc(sizeof(struct UDPv4PHeader))); - udpp->saddr = ip->saddr; - udpp->daddr = ip->daddr; - udpp->protocol = ip->protocol; - udpp->len = udp->length; - checksum_result = udp_checksum(ctl, udpp, udp, sizeof(struct UDPv4PHeader), abs(packetsize) - iphsize, 1); - packet[iphsize + sizeof(struct UDPHeader)] = ((char *)&checksum_result)[0]; - packet[iphsize + sizeof(struct UDPHeader) + 1] = ((char *)&checksum_result)[1]; - } else if (ip->saddr) { - udpp = (struct UDPv4PHeader *)(xmalloc(sizeof(struct UDPv4PHeader))); - udpp->saddr = ip->saddr; - udpp->daddr = ip->daddr; - udpp->protocol = ip->protocol; - udpp->len = udp->length; - udp->checksum = udp_checksum(ctl, udpp, udp, sizeof(struct UDPv4PHeader), abs(packetsize) - iphsize, 0); - } - break; - } + /* Send a probe using the mtr-packet subprocess */ + if (dprintf( + packet_command_pipe.write_fd, + "%d send-probe ip-4 %s ttl %d\n", + seq, ip_string, time_to_live) < 0) { - ip->check = checksum(packet, abs(packetsize)); - break; -#ifdef ENABLE_IPV6 - case AF_INET6: - switch ( ctl->mtrtype ) { - case IPPROTO_UDP: - /* kernel checksum calculation */ - if (udp->checksum) { - offset = sizeof(struct UDPHeader); - } - if ( setsockopt(sendsock, IPPROTO_IPV6, IPV6_CHECKSUM, &offset, sizeof(offset)) ) { - error(EXIT_FAILURE, errno, "setsockopt IPV6_CHECKSUM"); - } - break; - } - break; -#endif - } - - /* sendto() assumes packet length includes the IPv4 header but not the - IPv6 header. */ - spacketsize = abs(packetsize) -#ifdef ENABLE_IPV6 - - ( ( ctl->af == AF_INET ) ? 0 : sizeof (struct ip6_hdr) ) -#endif - ; - - rv = sendto(sendsock, packet, spacketsize, 0, remotesockaddr, salen); - if (first && (rv < 0) && ((errno == EINVAL) || (errno == EMSGSIZE))) { - /* Try the first packet again using host byte order. */ - ip->len = spacketsize; - rv = sendto(sendsock, packet, spacketsize, 0, remotesockaddr, salen); - if (rv >= 0) { - BSDfix = 1; - } + display_close(ctl); + error(EXIT_FAILURE, errno, "mtr-packet command pipe write failure"); } - first = 0; } /* We got a return on something we sent out. Record the address and time. */ static void net_process_ping(struct mtr_ctl *ctl, int seq, struct mplslen mpls, - void *addr, struct timeval now) + void *addr, int totusec) { int index; - int totusec; int oldavg; /* usedByMin */ int oldjavg; /* usedByMin */ int i; /* usedByMin */ @@ -758,7 +232,6 @@ static void net_process_ping(struct mtr_ctl *ctl, int seq, struct mplslen mpls, char addrcopy[sizeof(struct in_addr)]; #endif - /* Copy the from address ASAP because it can be overwritten */ addrcpy( (void *) &addrcopy, addr, ctl->af ); if (seq < 0 || seq >= MaxSequence) @@ -775,10 +248,6 @@ static void net_process_ping(struct mtr_ctl *ctl, int seq, struct mplslen mpls, index = sequence[seq].index; - totusec = (now.tv_sec - sequence[seq].time.tv_sec ) * 1000000 + - (now.tv_usec - sequence[seq].time.tv_usec); - /* impossible? if( totusec < 0 ) totusec = 0 */; - if ( addrcmp( (void *) &(host[index].addr), (void *) &ctl->unspec_addr, ctl->af ) == 0 ) { /* should be out of if as addr can change */ @@ -844,248 +313,200 @@ static void net_process_ping(struct mtr_ctl *ctl, int seq, struct mplslen mpls, } -/* We know a packet has come in, because the main select loop has called us, - now we just need to read it, see if it is for us, and if it is a reply - to something we sent, then call net_process_ping() */ -extern void net_process_return(struct mtr_ctl *ctl) +/* + A complete mtr-packet reply line has arrived. Parse it and record + the responding IP and round trip time, if it is a reply that we + understand. +*/ +static void net_process_command_reply( + struct mtr_ctl *ctl, char *reply_str) { - char packet[MAXPACKET]; -#ifdef ENABLE_IPV6 - struct sockaddr_storage fromsockaddr_struct; - struct sockaddr_in6 * fsa6 = (struct sockaddr_in6 *) &fromsockaddr_struct; -#else - struct sockaddr_in fromsockaddr_struct; -#endif - struct sockaddr * fromsockaddr = (struct sockaddr *) &fromsockaddr_struct; - struct sockaddr_in * fsa4 = (struct sockaddr_in *) &fromsockaddr_struct; - socklen_t fromsockaddrsize; - ssize_t num; - struct ICMPHeader *header = NULL; - struct UDPHeader *udpheader = NULL; - struct TCPHeader *tcpheader = NULL; -#ifdef HAS_SCTP - struct SCTPHeader *sctpheader = NULL; -#endif - struct timeval now; - ip_t * fromaddress = NULL; - int echoreplytype = 0, timeexceededtype = 0, unreachabletype = 0; - int seq_num = 0; - - /* MPLS decoding */ + struct command_t reply; + struct in_addr fromaddress; + int seq_num; + int i; + int round_trip_time = 0; + bool found_round_trip; + bool found_ip; + char *reply_name; + char *arg_name; + char *arg_value; struct mplslen mpls; - mpls.labels = 0; - - gettimeofday(&now, NULL); - switch ( ctl->af ) { - case AF_INET: - fromsockaddrsize = sizeof (struct sockaddr_in); - fromaddress = (ip_t *) &(fsa4->sin_addr); - echoreplytype = ICMP_ECHOREPLY; - timeexceededtype = ICMP_TIME_EXCEEDED; - unreachabletype = ICMP_DEST_UNREACH; - break; -#ifdef ENABLE_IPV6 - case AF_INET6: - fromsockaddrsize = sizeof (struct sockaddr_in6); - fromaddress = (ip_t *) &(fsa6->sin6_addr); - echoreplytype = ICMP6_ECHO_REPLY; - timeexceededtype = ICMP6_TIME_EXCEEDED; - unreachabletype = ICMP6_DST_UNREACH; - break; -#endif - } - num = recvfrom(recvsock, packet, MAXPACKET, 0, - fromsockaddr, &fromsockaddrsize); - if(num < 0) { - error(EXIT_FAILURE, errno, "recvfrom failed"); + /* Parse the reply string */ + if (parse_command(&reply, reply_str)) { + /* + If the reply isn't well structured, something is fundamentally + wrong, as we might as well exit. Even if the reply is of an + unknown type, it should still parse. + */ + error(EXIT_FAILURE, errno, "reply parse failure"); + return; } - switch ( ctl->af ) { - case AF_INET: - if((size_t) num < sizeof(struct IPHeader) + sizeof(struct ICMPHeader)) - return; - header = (struct ICMPHeader *)(packet + sizeof(struct IPHeader)); - break; -#ifdef ENABLE_IPV6 - case AF_INET6: - if((size_t) num < sizeof(struct ICMPHeader)) - return; + seq_num = reply.token; + reply_name = reply.command_name; - header = (struct ICMPHeader *) packet; - break; -#endif + /* If the reply type is unknown, ignore it for future compatibility */ + if (strcmp(reply_name, "reply") && strcmp(reply_name, "ttl-expired")) { + return; } - switch ( ctl->mtrtype ) { - case IPPROTO_ICMP: - if (header->type == echoreplytype) { - if(header->id != (uint16_t)getpid()) - return; - - seq_num = header->sequence; - } else if (header->type == timeexceededtype) { - switch ( ctl->af ) { - case AF_INET: - - if ((size_t) num < sizeof(struct IPHeader) + - sizeof(struct ICMPHeader) + - sizeof (struct IPHeader) + - sizeof (struct ICMPHeader)) - return; - header = (struct ICMPHeader *)(packet + sizeof (struct IPHeader) + - sizeof (struct ICMPHeader) + - sizeof (struct IPHeader)); - - if(num > 160) - decodempls(num, packet, &mpls, 156); + found_ip = false; + found_round_trip = false; - break; -#ifdef ENABLE_IPV6 - case AF_INET6: - if ((size_t) num < sizeof (struct ICMPHeader) + - sizeof (struct ip6_hdr) + sizeof (struct ICMPHeader) ) - return; - header = (struct ICMPHeader *) ( packet + - sizeof (struct ICMPHeader) + - sizeof (struct ip6_hdr) ); - - if(num > 140) - decodempls(num, packet, &mpls, 136); - - break; -#endif - } - - if (header->id != (uint16_t)getpid()) - return; - - seq_num = header->sequence; - } - break; - - case IPPROTO_UDP: - if (header->type == timeexceededtype || header->type == unreachabletype) { - switch ( ctl->af ) { - case AF_INET: - - if ((size_t) num < sizeof(struct IPHeader) + - sizeof(struct ICMPHeader) + - sizeof (struct IPHeader) + - sizeof (struct UDPHeader)) - return; - udpheader = (struct UDPHeader *)(packet + sizeof (struct IPHeader) + - sizeof (struct ICMPHeader) + - sizeof (struct IPHeader)); - - if(num > 160) - decodempls(num, packet, &mpls, 156); + /* Examine the reply arguments for known values */ + for (i = 0; i < reply.argument_count; i++) { + arg_name = reply.argument_name[i]; + arg_value = reply.argument_value[i]; - break; -#ifdef ENABLE_IPV6 - case AF_INET6: - if ((size_t) num < sizeof (struct ICMPHeader) + - sizeof (struct ip6_hdr) + sizeof (struct UDPHeader) ) - return; - udpheader = (struct UDPHeader *) ( packet + - sizeof (struct ICMPHeader) + - sizeof (struct ip6_hdr) ); - - if(num > 140) - decodempls(num, packet, &mpls, 136); - - break; -#endif + /* IPv4 address of the responding host */ + if (!strcmp(arg_name, "ip-4")) { + if (inet_pton(AF_INET, arg_value, &fromaddress)) { + found_ip = true; } - if (ntohs(udpheader->srcport) != (uint16_t)ctl->localport) - return; + } - if (ctl->remoteport && ctl->remoteport == ntohs(udpheader->dstport)) { - seq_num = ntohs(udpheader->checksum); - } else if (!ctl->remoteport) { - seq_num = ntohs(udpheader->dstport); + /* The round trip time in microseconds */ + if (!strcmp(arg_name, "round-trip-time")) { + errno = 0; + round_trip_time = strtol(arg_value, NULL, 10); + if (!errno) { + found_round_trip = true; } } - break; + } + + /* + If the reply had an IP address and a round trip time, we can + record the result. + */ + if (found_ip && found_round_trip) { + /* MPLS decoding */ + memset(&mpls, 0, sizeof(struct mplslen)); + mpls.labels = 0; + + net_process_ping( + ctl, seq_num, mpls, (void *) &fromaddress, round_trip_time); + } +} - case IPPROTO_TCP: - if (header->type == timeexceededtype || header->type == unreachabletype) { - switch ( ctl->af ) { - case AF_INET: - if ((size_t) num < sizeof(struct IPHeader) + - sizeof(struct ICMPHeader) + - sizeof (struct IPHeader) + - sizeof (struct TCPHeader)) - return; - tcpheader = (struct TCPHeader *)(packet + sizeof (struct IPHeader) + - sizeof (struct ICMPHeader) + - sizeof (struct IPHeader)); +/* + Check the command pipe for completed replies to commands + we have previously sent. Record the results of those replies. +*/ +static void net_process_pipe_buffer(struct mtr_ctl *ctl) +{ + char *reply_buffer; + char *reply_start; + char *end_of_reply; + int used_size; + int move_size; - if(num > 160) - decodempls(num, packet, &mpls, 156); + reply_buffer = packet_command_pipe.reply_buffer; + /* Terminate the string storing the replies */ + assert(packet_command_pipe.reply_buffer_used < PACKET_REPLY_BUFFER_SIZE); + reply_buffer[packet_command_pipe.reply_buffer_used] = 0; + + reply_start = reply_buffer; + + /* + We may have multiple completed replies. Loop until we don't + have any more newlines termininating replies. + */ + while (true) { + /* If no newline is found, our reply isn't yet complete */ + end_of_reply = index(reply_start, '\n'); + if (end_of_reply == NULL) { + /* No complete replies remaining */ break; -#ifdef ENABLE_IPV6 - case AF_INET6: - if ((size_t) num < sizeof (struct ICMPHeader) + - sizeof (struct ip6_hdr) + sizeof (struct TCPHeader) ) - return; - tcpheader = (struct TCPHeader *) ( packet + - sizeof (struct ICMPHeader) + - sizeof (struct ip6_hdr) ); - - if(num > 140) - decodempls(num, packet, &mpls, 136); - - break; -#endif - } - seq_num = ntohs(tcpheader->srcport); } - break; -#ifdef HAS_SCTP - case IPPROTO_SCTP: - if (header->type == timeexceededtype || header->type == unreachabletype) { - switch ( ctl->af ) { - case AF_INET: + /* + Terminate the reply string at the newline, which + is necessary in the case where we are able to read + mulitple replies arriving simultaneously. + */ + *end_of_reply = 0; - if ((size_t) num < sizeof(struct IPHeader) + - sizeof(struct ICMPHeader) + - sizeof (struct IPHeader) + - sizeof (struct SCTPHeader)) - return; - sctpheader = (struct SCTPHeader *)(packet + sizeof (struct IPHeader) + - sizeof (struct ICMPHeader) + - sizeof (struct IPHeader)); + /* Parse and record the reply results */ + net_process_command_reply(ctl, reply_start); - if(num > 160) - decodempls(num, packet, &mpls, 156); + reply_start = end_of_reply + 1; + } - break; -# ifdef ENABLE_IPV6 - case AF_INET6: - if ((size_t) num < sizeof (struct ICMPHeader) + - sizeof (struct ip6_hdr) + sizeof (struct SCTPHeader) ) - return; - sctpheader = (struct SCTPHeader *) ( packet + - sizeof (struct ICMPHeader) + - sizeof (struct ip6_hdr) ); - - if(num > 140) - decodempls(num, packet, &mpls, 136); - - break; -# endif - } - seq_num = ntohs(sctpheader->srcport); + /* + After replies have been processed, free the space used + by the replies, and move any remaining partial reply text + to the start of the reply buffer. + */ + used_size = reply_start - reply_buffer; + move_size = packet_command_pipe.reply_buffer_used - used_size; + memmove(reply_buffer, reply_start, move_size); + packet_command_pipe.reply_buffer_used -= used_size; + + if (packet_command_pipe.reply_buffer_used >= + PACKET_REPLY_BUFFER_SIZE - 1) { + /* + We've overflowed the reply buffer without a complete reply. + There's not much we can do about it but discard the data + we've got and hope new data coming in fits. + */ + packet_command_pipe.reply_buffer_used = 0; + } +} + + +/* + Invoked when the read pipe from the mtr-packet subprocess is readable. + If we have received a complete reply, process it. +*/ +extern void net_process_return(struct mtr_ctl *ctl) +{ + int read_count; + int buffer_remaining; + char *reply_buffer; + char *read_buffer; + + reply_buffer = packet_command_pipe.reply_buffer; + + /* + Read the available reply text, up to the the remaining + buffer space. (Minus one for the terminating NUL.) + */ + read_buffer = &reply_buffer[packet_command_pipe.reply_buffer_used]; + buffer_remaining = + PACKET_REPLY_BUFFER_SIZE - packet_command_pipe.reply_buffer_used; + read_count = read( + packet_command_pipe.read_fd, read_buffer, buffer_remaining - 1); + + if (read_count < 0) { + /* + EAGAIN simply indicates that there is no data currently + available on our non-blocking pipe. + */ + if (errno == EAGAIN) { + return; } - break; -#endif /* HAS_SCTP */ + + display_close(ctl); + error(EXIT_FAILURE, errno, "command reply read failure"); + return; + } + + if (read_count == 0) { + display_close(ctl); + + errno = EPIPE; + error(EXIT_FAILURE, EPIPE, "unexpected packet generator exit"); } - if (seq_num) - net_process_ping (ctl, seq_num, mpls, (void *) fromaddress, now); + + packet_command_pipe.reply_buffer_used += read_count; + + /* Handle any replies completed by this read */ + net_process_pipe_buffer(ctl); } @@ -1308,105 +729,183 @@ extern int net_send_batch(struct mtr_ctl *ctl) } -static void set_fd_flags(int fd) +/* Set a file descriptor to non-blocking */ +static void set_fd_nonblock(int fd) { -#if defined(HAVE_FCNTL) && defined(FD_CLOEXEC) - int oldflags; + int flags; - if (fd < 0) return; + /* Get the current flags of the file descriptor */ + flags = fcntl(fd, F_GETFL, 0); + if (flags == -1) { + error(EXIT_FAILURE, errno, "F_GETFL failure"); + exit(1); + } - oldflags = fcntl(fd, F_GETFD); - if (oldflags == -1) { - error(0, errno, "Couldn't get fd's flags"); - return; + /* Add the O_NONBLOCK bit to the current flags */ + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { + error(EXIT_FAILURE, errno, "Failure to set O_NONBLOCK"); + exit(1); } - if (fcntl(fd, F_SETFD, oldflags | FD_CLOEXEC)) - error(0, errno, "Couldn't set fd's flags"); -#endif } -extern int net_preopen(void) + +/* Ensure we can communicate with the mtr-packet subprocess */ +static int net_command_pipe_check(struct mtr_ctl *ctl) { -#ifdef IP_HDRINCL - int trueopt = 1; -#endif + const char *check_command = "1 check-support feature send-probe\n"; + struct command_t command; + char reply[PACKET_REPLY_BUFFER_SIZE]; + int command_length; + int write_length; + int read_length; + int parse_result; + + /* Query send-probe support */ + command_length = strlen(check_command); + write_length = write( + packet_command_pipe.write_fd, check_command, command_length); + + if (write_length == -1) { + return -1; + } -#if !defined(IP_HDRINCL) && defined(IP_TOS) && defined(IP_TTL) - sendsock4_icmp = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); - sendsock4_udp = socket(AF_INET, SOCK_RAW, IPPROTO_UDP); -#else - sendsock4 = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); -#endif - if (sendsock4 < 0) + if (write_length != command_length) { + errno = EIO; return -1; -#ifdef ENABLE_IPV6 - sendsock6_icmp = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); - sendsock6_udp = socket(AF_INET6, SOCK_RAW, IPPROTO_UDP); -#endif + } -#ifdef IP_HDRINCL - /* FreeBSD wants this to avoid sending out packets with protocol type RAW - to the network. */ - if (setsockopt(sendsock4, SOL_IP, IP_HDRINCL, &trueopt, sizeof(trueopt))) { - error(0, errno, "setsockopt IP_HDRINCL"); + /* Read the reply to our query */ + read_length = read( + packet_command_pipe.read_fd, reply, PACKET_REPLY_BUFFER_SIZE - 1); + + if (read_length < 0) { return -1; } -#endif /* IP_HDRINCL */ - recvsock4 = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); - if (recvsock4 < 0) + /* Parse the query reply */ + reply[read_length] = 0; + parse_result = parse_command(&command, reply); + if (parse_result) { + errno = parse_result; return -1; - set_fd_flags(recvsock4); -#ifdef ENABLE_IPV6 - recvsock6 = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); - if (recvsock6 >= 0) - set_fd_flags(recvsock6); -#endif + } - return 0; + /* Check that send-probe is supported */ + if (!strcmp(command.command_name, "feature-support") + && command.argument_count >= 1 + && !strcmp(command.argument_name[0], "support") + && !strcmp(command.argument_value[0], "ok")) { + + /* Looks good */ + return 0; + } + + errno = ENOTSUP; + return -1; } -extern int net_selectsocket(struct mtr_ctl *ctl) +/* Create the command pipe to a new mtr-packet subprocess */ +static int net_command_pipe_open(struct mtr_ctl *ctl) { -#if !defined(IP_HDRINCL) && defined(IP_TOS) && defined(IP_TTL) - switch ( ctl->mtrtype ) { - case IPPROTO_ICMP: - sendsock4 = sendsock4_icmp; - break; - case IPPROTO_UDP: - sendsock4 = sendsock4_udp; - break; + int stdin_pipe[2]; + int stdout_pipe[2]; + pid_t child_pid; + int i; + char *mtr_packet_path; + + /* + We actually need two Unix pipes. One for stdin and one for + stdout on the new process. + */ + if (pipe(stdin_pipe) || pipe(stdout_pipe)) { + return errno; } -#endif - if (sendsock4 < 0) - return -1; -#ifdef ENABLE_IPV6 - switch ( ctl->mtrtype ) { - case IPPROTO_ICMP: - sendsock6 = sendsock6_icmp; - break; - case IPPROTO_UDP: - sendsock6 = sendsock6_udp; - break; + + child_pid = fork(); + if (child_pid == -1) { + return errno; } - if ((sendsock6 < 0) && (sendsock4 < 0)) - return -1; -#endif - return 0; + if (child_pid == 0) { + /* In the child process, attach our created pipes to stdin and stdout */ + dup2(stdin_pipe[0], STDIN_FILENO); + dup2(stdout_pipe[1], STDOUT_FILENO); + + /* Close all unnecessary fds */ + for (i = STDERR_FILENO + 1; i <= stdout_pipe[1]; i++) { + close(i); + } + + /* + Allow the MTR_PACKET environment variable to overrride + the path to the mtr-packet executable. This is necessary + for debugging changes for mtr-packet. + */ + mtr_packet_path = getenv("MTR_PACKET"); + if (mtr_packet_path == NULL) { + mtr_packet_path = "mtr-packet"; + } + + /* + First, try to execute using /usr/bin/env, because this + will search the PATH for mtr-packet + */ + execl("/usr/bin/env", "mtr-packet", mtr_packet_path, NULL); + + /* + If env fails to execute, try to use the MTR_PACKET environment as a + full path to the executable. This is necessary because on + Windows, minimal mtr binary distributions will lack /usr/bin/env. + + Note: A side effect is that an mtr-packet in the current directory + could be executed. This will only be the case if /usr/bin/env + doesn't exist. + */ + execl(mtr_packet_path, "mtr-packet", NULL); + + /* Both exec attempts failed, so nothing to do but exit */ + exit(1); + } else { + memset(&packet_command_pipe, 0, sizeof(struct packet_command_pipe_t)); + + /* + In the parent process, save the opposite ends of the pipes + attached as stdin and stdout in the child. + */ + packet_command_pipe.pid = child_pid; + packet_command_pipe.read_fd = stdout_pipe[0]; + packet_command_pipe.write_fd = stdin_pipe[1]; + + /* We don't need the child ends of the pipe open in the parent. */ + close(stdout_pipe[1]); + close(stdin_pipe[0]); + + /* + Check that we can communicate with the client. If we failed to + execute the mtr-packet binary, we will discover that here. + */ + if (net_command_pipe_check(ctl)) { + error(EXIT_FAILURE, errno, "Failure to start mtr-packet"); + } + + /* We will need non-blocking reads from the child */ + set_fd_nonblock(packet_command_pipe.read_fd); + } + + return 0; } extern int net_open(struct mtr_ctl *ctl, struct hostent * hostent) { -#ifdef ENABLE_IPV6 - struct sockaddr_storage name_struct; -#else - struct sockaddr_in name_struct; -#endif - struct sockaddr * name = (struct sockaddr *) &name_struct; - socklen_t len; + int err; + + /* Spawn the mtr-packet child process */ + err = net_command_pipe_open(ctl); + if (err) { + return err; + } net_reset(ctl); @@ -1414,19 +913,12 @@ extern int net_open(struct mtr_ctl *ctl, struct hostent * hostent) switch ( hostent->h_addrtype ) { case AF_INET: - sendsock = sendsock4; - recvsock = recvsock4; addrcpy( (void *) &(rsa4->sin_addr), hostent->h_addr, AF_INET ); sourceaddress = (ip_t *) &(ssa4->sin_addr); remoteaddress = (ip_t *) &(rsa4->sin_addr); break; #ifdef ENABLE_IPV6 case AF_INET6: - if (sendsock6 < 0 || recvsock6 < 0) { - error(EXIT_FAILURE, errno, "Could not open IPv6 socket"); - } - sendsock = sendsock6; - recvsock = recvsock6; addrcpy( (void *) &(rsa6->sin6_addr), hostent->h_addr, AF_INET6 ); sourceaddress = (ip_t *) &(ssa6->sin6_addr); remoteaddress = (ip_t *) &(rsa6->sin6_addr); @@ -1436,10 +928,6 @@ extern int net_open(struct mtr_ctl *ctl, struct hostent * hostent) error(EXIT_FAILURE, 0, "net_open bad address type"); } - len = sizeof name_struct; - getsockname (recvsock, name, &len); - sockaddrtop( name, localaddr, sizeof localaddr ); - return 0; } @@ -1501,135 +989,27 @@ extern void net_reset(struct mtr_ctl *ctl) } -static int net_set_interfaceaddress_udp(struct mtr_ctl *ctl) -{ - struct sockaddr_in * sa4; - struct sockaddr_storage remote; - struct sockaddr_in *remote4 = (struct sockaddr_in *) &remote; -#ifdef ENABLE_IPV6 - struct sockaddr_storage name_struct; - struct sockaddr_in6 * sa6; - struct sockaddr_in6 *remote6 = (struct sockaddr_in6 *) &remote; -#else - struct sockaddr_in name_struct; -#endif - struct sockaddr * name = (struct sockaddr *) &name_struct; - socklen_t len; - int s; - - memset(&remote, 0, sizeof (remote)); - remote.ss_family = ctl->af; - switch (ctl->af) { - case AF_INET: - addrcpy((void *) &remote4->sin_addr, (void *) remoteaddress, ctl->af); - remote4->sin_port = htons(ctl->remoteport); - len = sizeof (struct sockaddr_in); - break; -#ifdef ENABLE_IPV6 - case AF_INET6: - addrcpy((void *) &remote6->sin6_addr, (void *) remoteaddress, ctl->af); - remote6->sin6_port = htons(ctl->remoteport); - len = sizeof (struct sockaddr_in6); - break; -#endif - } - - s = socket (ctl->af, SOCK_DGRAM, 0); - if (s < 0) { - error(EXIT_FAILURE, errno, "udp socket()"); - } - - if (connect(s, (struct sockaddr *) &remote, len)) { - error(EXIT_FAILURE, errno, "udp connect()"); - } - - getsockname(s, name, &len); - sockaddrtop( name, localaddr, sizeof localaddr ); - switch (ctl->af) { - case AF_INET: - sa4 = (struct sockaddr_in *) name; - addrcpy((void*)&ssa4->sin_addr, (void *) &(sa4->sin_addr), ctl->af ); - break; -#ifdef ENABLE_IPV6 - case AF_INET6: - sa6 = (struct sockaddr_in6 *) name; - addrcpy((void*)&ssa6->sin6_addr, (void *) &(sa6->sin6_addr), ctl->af ); - break; -#endif - } - close(s); - - return 0; -} - - -extern int net_set_interfaceaddress (struct mtr_ctl *ctl) +/* Close the pipe to the packet generator process, and kill the process */ +extern void net_close(void) { -#ifdef ENABLE_IPV6 - struct sockaddr_storage name_struct; -#else - struct sockaddr_in name_struct; -#endif - struct sockaddr * name = (struct sockaddr *) &name_struct; - socklen_t len = 0; + int child_exit_value; - if (ctl->mtrtype == IPPROTO_UDP && ctl->remoteport && !ctl->InterfaceAddress) { - return net_set_interfaceaddress_udp(ctl); - } - if (!ctl->InterfaceAddress) return 0; - - sourcesockaddr->sa_family = ctl->af; - switch ( ctl->af ) { - case AF_INET: - ssa4->sin_port = 0; - if ( inet_aton( ctl->InterfaceAddress, &(ssa4->sin_addr) ) < 1 ) { - error(0, 0, "bad interface address: %s", ctl->InterfaceAddress); - return( 1 ); - } - len = sizeof (struct sockaddr); - break; -#ifdef ENABLE_IPV6 - case AF_INET6: - ssa6->sin6_port = 0; - if ( inet_pton( ctl->af, ctl->InterfaceAddress, &(ssa6->sin6_addr) ) < 1 ) { - error(0, 0, "bad interface address: %s", ctl->InterfaceAddress); - return( 1 ); - } - len = sizeof (struct sockaddr_in6); - break; -#endif - } + if (packet_command_pipe.pid) { + close(packet_command_pipe.read_fd); + close(packet_command_pipe.write_fd); - if ( bind( sendsock, sourcesockaddr, len ) == -1 ) { - error(0, 0, "failed to bind to interface: %s", ctl->InterfaceAddress); - return( 1 ); + kill(packet_command_pipe.pid, SIGTERM); + waitpid(packet_command_pipe.pid, &child_exit_value, 0); } - getsockname (sendsock, name, &len); - sockaddrtop( name, localaddr, sizeof localaddr ); - return 0; -} - - -extern void net_close(void) -{ - if (sendsock4 >= 0) { - close(sendsock4_icmp); - close(sendsock4_udp); - } - if (recvsock4 >= 0) close(recvsock4); - if (sendsock6 >= 0) { - close(sendsock6_icmp); - close(sendsock6_udp); - } - if (recvsock6 >= 0) close(recvsock6); + memset(&packet_command_pipe, 0, sizeof(struct packet_command_pipe_t)); } extern int net_waitfd(void) { - return recvsock; + return packet_command_pipe.read_fd; } @@ -1668,32 +1048,6 @@ extern void net_save_return(int at, int seq, int ms) host[at].saved[idx] = ms; } -/* Similar to inet_ntop but uses a sockaddr as it's argument. */ -static void sockaddrtop( struct sockaddr * saddr, char * strptr, size_t len ) { - struct sockaddr_in * sa4; -#ifdef ENABLE_IPV6 - struct sockaddr_in6 * sa6; -#endif - - switch ( saddr->sa_family ) { - case AF_INET: - sa4 = (struct sockaddr_in *) saddr; - xstrncpy( strptr, inet_ntoa( sa4->sin_addr ), len - 1 ); - strptr[ len - 1 ] = '\0'; - return; -#ifdef ENABLE_IPV6 - case AF_INET6: - sa6 = (struct sockaddr_in6 *) saddr; - inet_ntop( sa6->sin6_family, &(sa6->sin6_addr), strptr, len ); - return; -#endif - default: - error(0, 0, "sockaddrtop unknown address type"); - strptr[0] = '\0'; - return; - } -} - /* Address comparison. */ extern int addrcmp( char * a, char * b, int family ) { int rc = -1; @@ -1727,45 +1081,6 @@ extern void addrcpy( char * a, char * b, int family ) { } } -/* Decode MPLS */ -static void decodempls(int num, char *packet, struct mplslen *mpls, int offset) { - - int i; - unsigned int ext_ver, ext_res, ext_chk, obj_hdr_len; - u_char obj_hdr_class, obj_hdr_type; - - /* loosely derived from the traceroute-nanog.c - * decoding by Jorge Boncompte */ - ext_ver = packet[offset]>>4; - ext_res = (packet[offset]&15)+ packet[offset+1]; - ext_chk = ((unsigned int)packet[offset+2]<<8)+packet[offset+3]; - - /* Check for ICMP extension header */ - if (ext_ver == 2 && ext_res == 0 && ext_chk != 0 && num >= (offset+6)) { - obj_hdr_len = ((int)packet[offset+4]<<8)+packet[offset+5]; - obj_hdr_class = packet[offset+6]; - obj_hdr_type = packet[offset+7]; - - /* make sure we have an MPLS extension */ - if (obj_hdr_len >= 8 && obj_hdr_class == 1 && obj_hdr_type == 1) { - /* how many labels do we have? will be at least 1 */ - mpls->labels = (obj_hdr_len-4)/4; - - /* save all label objects */ - for(i=0; (ilabels) && (i < MAXLABELS) && (num >= (offset+8)+(i*4)); i++) { - - /* piece together the 20 byte label value */ - mpls->label[i] = ((unsigned long) (packet[(offset+8)+(i*4)] << 12 & 0xff000) + - (unsigned int) (packet[(offset+9)+(i*4)] << 4 & 0xff0) + - (packet[(offset+10)+(i*4)] >> 4 & 0xf)); - mpls->exp[i] = (packet[(offset+10)+(i*4)] >> 1) & 0x7; - mpls->s[i] = (packet[(offset+10)+(i*4)] & 0x1); /* should be 1 if only one label */ - mpls->ttl[i] = packet[(offset+11)+(i*4)]; - } - } - } -} - /* Add open sockets to select() */ extern void net_add_fds(fd_set *writefd, int *maxfd) { @@ -1780,43 +1095,6 @@ extern void net_add_fds(fd_set *writefd, int *maxfd) } } -/* check if we got connection or error on any fds */ -extern void net_process_fds(struct mtr_ctl *ctl, fd_set *writefd) -{ - int at, fd, r; - struct timeval now; - - /* Can't do MPLS decoding */ - struct mplslen mpls; - mpls.labels = 0; - - gettimeofday(&now, NULL); - - for (at = 0; at < MaxSequence; at++) { - fd = sequence[at].socket; - if (fd > 0 && FD_ISSET(fd, writefd)) { - r = write(fd, "G", 1); - /* if write was successful, or connection refused we have - * (probably) reached the remote address. Anything else happens to the - * connection, we write it off to avoid leaking sockets */ - if (r == 1 || errno == ECONNREFUSED) - net_process_ping(ctl, at, mpls, remoteaddress, now); - else if (errno != EAGAIN) { - close(fd); - sequence[at].socket = 0; - } - } - if (fd > 0) { - struct timeval subtract; - timersub(&now, &sequence[at].time, &subtract); - if ((subtract.tv_sec * 1000000L + subtract.tv_usec) > ctl->tcp_timeout) { - close(fd); - sequence[at].socket = 0; - } - } - } -} - /* for GTK frontend */ extern void net_harvest_fds(struct mtr_ctl *ctl) { @@ -1829,5 +1107,4 @@ extern void net_harvest_fds(struct mtr_ctl *ctl) tv.tv_usec = 0; net_add_fds(&writefd, &maxfd); select(maxfd, NULL, &writefd, NULL, &tv); - net_process_fds(ctl, &writefd); } diff --git a/net.h b/net.h index 0de6d669..a6ef1416 100644 --- a/net.h +++ b/net.h @@ -30,11 +30,8 @@ #include "mtr.h" -extern int net_preopen(void); -extern int net_selectsocket(struct mtr_ctl *ctl); extern int net_open(struct mtr_ctl *ctl, struct hostent *host); extern void net_reopen(struct mtr_ctl *ctl, struct hostent *address); -extern int net_set_interfaceaddress (struct mtr_ctl *ctl); extern void net_reset(struct mtr_ctl *ctl); extern void net_close(void); extern int net_waitfd(void); @@ -79,4 +76,3 @@ extern int addrcmp( char * a, char * b, int af ); extern void addrcpy( char * a, char * b, int af ); extern void net_add_fds(fd_set *writefd, int *maxfd); -extern void net_process_fds(struct mtr_ctl *ctl, fd_set *writefd); diff --git a/packet/cmdparse.c b/packet/cmdparse.c new file mode 100644 index 00000000..2eba7c0a --- /dev/null +++ b/packet/cmdparse.c @@ -0,0 +1,125 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "cmdparse.h" + +#include +#include +#include +#include + +/* + NUL terminate the whitespace separated tokens in the command string. + This modifies command_string in-place with NUL characters. + Fill the tokens array with pointers to the tokens, and return the + number of tokens found. +*/ +static +int tokenize_command( + char **tokens, + int max_tokens, + char *command_string) +{ + int token_count = 0; + int on_space = 1; + int i; + + for (i = 0; command_string[i]; i++) { + if (on_space) { + if (!isspace(command_string[i])) { + /* Take care not to exceed the token array length */ + if (token_count >= max_tokens) { + return -1; + } + + tokens[token_count++] = &command_string[i]; + on_space = 0; + } + } else { + if (isspace(command_string[i])) { + command_string[i] = 0; + on_space = 1; + } + } + } + + return token_count; +} + +/* + Parse a command string (or command reply string) into a command_t + structure for later semantic interpretation. Returns EINVAL if the + command string is unparseable or zero for success. + + comamnd_string will be modified in-place with NUL characters terminating + tokens, and the command_t will use pointers to the conents of + command_string without copying, so any interpretation of the + command_t structure requires that the command_string memory has not yet + been freed or otherwise reused. +*/ +int parse_command( + struct command_t *command, + char *command_string) +{ + const int max_tokens = MAX_COMMAND_ARGUMENTS * 2 + 2; + char *tokens[max_tokens]; + int token_count; + int i; + + memset(command, 0, sizeof(struct command_t)); + + /* Tokenize the string using whitespace */ + token_count = tokenize_command(tokens, max_tokens, command_string); + if (token_count < 2) { + return EINVAL; + } + + /* Expect the command token to be a numerical value */ + errno = 0; + command->token = strtol(tokens[0], NULL, 10); + if (errno) { + return EINVAL; + } + command->command_name = tokens[1]; + + /* + The tokens beyond the command name are expected to be in + name, value pairs. + */ + i = 2; + command->argument_count = 0; + while (i < token_count) { + /* It's an error if we get a name without a key */ + if (i + 1 >= token_count) { + return EINVAL; + } + + /* It's an error if we get more arguments than we have space for */ + if (command->argument_count >= MAX_COMMAND_ARGUMENTS) { + return EINVAL; + } + + command->argument_name[command->argument_count] = tokens[i]; + command->argument_value[command->argument_count] = tokens[i + 1]; + command->argument_count++; + + i += 2; + } + + return 0; +} diff --git a/packet/cmdparse.h b/packet/cmdparse.h new file mode 100644 index 00000000..aae1fe30 --- /dev/null +++ b/packet/cmdparse.h @@ -0,0 +1,47 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef CMDPARSE_H +#define CMDPARSE_H + +#define MAX_COMMAND_ARGUMENTS 16 + +/* Parsed commands, or command replies, ready for semantic interpretation */ +struct command_t +{ + /* A unique value for matching command requests with replies */ + int token; + + /* Text indiciating the command type, or reply type */ + char *command_name; + + /* The number of key, value argument pairs used */ + int argument_count; + + /* Names for each argument */ + char *argument_name[MAX_COMMAND_ARGUMENTS]; + + /* Values for each argument, parallel to the argument_name array */ + char *argument_value[MAX_COMMAND_ARGUMENTS]; +}; + +int parse_command( + struct command_t *command, + char *command_string); + +#endif diff --git a/packet/command.c b/packet/command.c new file mode 100644 index 00000000..5175d256 --- /dev/null +++ b/packet/command.c @@ -0,0 +1,242 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "command.h" + +#include +#include +#include +#include +#include +#include + +#include "cmdparse.h" +#include "platform.h" +#include "config.h" + +/* + Find a parameter with a particular name in a command_t structure. + If no such parameter exists, return NULL. +*/ +static +const char *find_parameter( + const struct command_t *command, + const char *name_request) +{ + const char *name; + const char *value; + int i; + + for (i = 0; i < command->argument_count; i++) { + name = command->argument_name[i]; + value = command->argument_value[i]; + + if (!strcmp(name, name_request)) { + return value; + } + } + + return NULL; +} + +/* Given a feature name, return a string for the check-support reply */ +static +const char *check_support( + const char *feature) +{ + if (!strcmp(feature, "version")) { + return PACKAGE_VERSION; + } + + if (!strcmp(feature, "ip-4")) { + return "ok"; + } + + if (!strcmp(feature, "send-probe")) { + return "ok"; + } + + return "no"; +} + +/* Handle a check-support request by checking for a particular feature */ +static +void check_support_command( + const struct command_t *command) +{ + const char *feature; + const char *support; + + feature = find_parameter(command, "feature"); + if (feature == NULL) { + printf("%d invalid-argument\n", command->token); + return; + } + + support = check_support(feature); + printf("%d feature-support support %s\n", command->token, support); +} + +/* + If a named send_probe argument is recognized, fill in the probe paramters + structure with the argument value. +*/ +static +bool decode_probe_argument( + struct probe_param_t *param, + const char *name, + const char *value) +{ + char *endstr = NULL; + + /* Pass IPv4 addresses as string values */ + if (!strcmp(name, "ip-4")) { + param->ipv4_address = value; + } + + /* Time-to-live values */ + if (!strcmp(name, "ttl")) { + param->ttl = strtol(value, &endstr, 10); + if (*endstr != 0) { + return false; + } + } + + /* Number of seconds to wait for a reply */ + if (!strcmp(name, "timeout")) { + param->timeout = strtol(value, &endstr, 10); + if (*endstr != 0) { + return false; + } + } + + return true; +} + +/* Handle "send-probe" commands */ +static +void send_probe_command( + const struct command_t *command, + struct net_state_t *net_state) +{ + struct probe_param_t param; + int i; + char *name; + char *value; + + /* We will prepare a probe_param_t for send_probe. */ + memset(¶m, 0, sizeof(struct probe_param_t)); + param.command_token = command->token; + param.ttl = 255; + param.timeout = 10; + + for (i = 0; i < command->argument_count; i++) { + name = command->argument_name[i]; + value = command->argument_value[i]; + + if (!decode_probe_argument(¶m, name, value)) { + printf("%d invalid-argument\n", command->token); + return; + } + } + + /* Send the probe using a platform specific mechanism */ + send_probe(net_state, ¶m); +} + +/* + Given a parsed command, dispatch to the handler for specific + command requests. +*/ +static +void dispatch_command( + const struct command_t *command, + struct net_state_t *net_state) +{ + if (!strcmp(command->command_name, "check-support")) { + check_support_command(command); + } else if (!strcmp(command->command_name, "send-probe")) { + send_probe_command(command, net_state); + } else { + /* For unrecognized commands, respond with an error */ + printf("%d unknown-command\n", command->token); + } +} + +/* + With newly read data in our command buffer, dispatch all completed + command requests. +*/ +void dispatch_buffer_commands( + struct command_buffer_t *buffer, + struct net_state_t *net_state) +{ + struct command_t command; + char *end_of_command; + char full_command[COMMAND_BUFFER_SIZE]; + int command_length; + int remaining_count; + + while (true) { + assert(buffer->incoming_read_position < COMMAND_BUFFER_SIZE); + + /* Terminate the buffer string */ + buffer->incoming_buffer[buffer->incoming_read_position] = 0; + + /* Find the next newline, which terminates command requests */ + end_of_command = index(buffer->incoming_buffer, '\n'); + if (end_of_command == NULL) { + /* + No newlines found, so any data we've read so far is + not yet complete. + */ + break; + } + + command_length = end_of_command - buffer->incoming_buffer; + remaining_count = buffer->incoming_read_position - command_length - 1; + + /* Copy the completed command */ + memmove(full_command, buffer->incoming_buffer, command_length); + full_command[command_length] = 0; + + /* + Free the space used by the completed command by advancing the + remaining requests within the buffer. + */ + memmove(buffer->incoming_buffer, end_of_command + 1, remaining_count); + buffer->incoming_read_position -= command_length + 1; + + if (parse_command(&command, full_command)) { + /* If the command fails to parse, respond with an error */ + printf("0 command-parse-error\n"); + } else { + dispatch_command(&command, net_state); + } + } + + if (buffer->incoming_read_position >= COMMAND_BUFFER_SIZE - 1) { + /* + If we've filled the buffer without a complete command, the + only thing we can do is discard what we've read and hope that + new data is better formatted. + */ + printf("0 command-buffer-overflow\n"); + buffer->incoming_read_position = 0; + } +} diff --git a/packet/command.h b/packet/command.h new file mode 100644 index 00000000..03cb30b2 --- /dev/null +++ b/packet/command.h @@ -0,0 +1,60 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef COMMAND_H +#define COMMAND_H + +#include "platform.h" +#include "probe.h" + +#define COMMAND_BUFFER_SIZE 4096 + +#ifdef PLATFORM_CYGWIN +#include "command_cygwin.h" +#else +#include "command_unix.h" +#endif + +/* Storage for incoming commands, prior to command parsing */ +struct command_buffer_t +{ + /* The file descriptor of the incoming command stream */ + int command_stream; + + /* Storage to read commands into */ + char incoming_buffer[COMMAND_BUFFER_SIZE]; + + /* The number of bytes read so far in incoming_buffer */ + int incoming_read_position; + + /* Platform specific */ + struct command_buffer_platform_t platform; +}; + +void init_command_buffer( + struct command_buffer_t *command_buffer, + int command_stream); + +int read_commands( + struct command_buffer_t *buffer); + +void dispatch_buffer_commands( + struct command_buffer_t *buffer, + struct net_state_t *net_state); + +#endif diff --git a/packet/command_cygwin.c b/packet/command_cygwin.c new file mode 100644 index 00000000..4c0857e9 --- /dev/null +++ b/packet/command_cygwin.c @@ -0,0 +1,153 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "command.h" + +#include +#include + +/* + A completion routine to be called by Windows when a read from + the command stream has completed. +*/ +static +void CALLBACK finish_read_command( + DWORD status, + DWORD size_read, + OVERLAPPED *overlapped) +{ + struct command_buffer_t *buffer; + char *read_position; + + /* + hEvent is unusuaed by ReadFileEx, so we use it to pass + our command_buffer structure. + */ + buffer = (struct command_buffer_t *)overlapped->hEvent; + + if (status) { + /* When the stream is closed ERROR_BROKEN_PIPE will be the result */ + if (status == ERROR_BROKEN_PIPE) { + buffer->platform.pipe_open = false; + return; + } + + fprintf(stderr, "ReadFileEx completion failure %d\n", status); + exit(1); + } + + /* Copy from the overlapped I/O buffer to the incoming command buffer */ + read_position = &buffer->incoming_buffer[buffer->incoming_read_position]; + memcpy(read_position, buffer->platform.overlapped_buffer, size_read); + + /* Account for the newly read data */ + buffer->incoming_read_position += size_read; + buffer->platform.read_active = false; +} + +/* + An APC which does nothing, to be used only to wake from the current + alertable wait. +*/ +static +void CALLBACK empty_apc( + ULONG *param) +{ +} + +/* Wake from the next alertable wait without waiting for newly read data */ +static +void queue_empty_apc(void) +{ + if (QueueUserAPC((PAPCFUNC)empty_apc, GetCurrentThread(), 0) == 0) { + fprintf( + stderr, "Unexpected QueueUserAPC failure %d\n", GetLastError()); + exit(1); + } +} + +/* Start a new overlapped I/O read from the command stream */ +static +void start_read_command( + struct command_buffer_t *buffer) +{ + HANDLE command_stream = + (HANDLE)get_osfhandle(buffer->command_stream); + int space_remaining = + COMMAND_BUFFER_SIZE - buffer->incoming_read_position - 1; + int err; + + /* If a read is already active, or the pipe is closed, do nothing */ + if (!buffer->platform.pipe_open || buffer->platform.read_active) { + return; + } + + memset(&buffer->platform.overlapped, 0, sizeof(OVERLAPPED)); + buffer->platform.overlapped.hEvent = (HANDLE)buffer; + + if (!ReadFileEx( + command_stream, buffer->platform.overlapped_buffer, space_remaining, + &buffer->platform.overlapped, finish_read_command)) { + + err = GetLastError(); + + if (err == ERROR_BROKEN_PIPE) { + /* If the command stream has been closed, we need to wake from + the next altertable wait to exit the main loop */ + buffer->platform.pipe_open = false; + queue_empty_apc(); + + return; + } else if (err != WAIT_IO_COMPLETION) { + fprintf( + stderr, "Unexpected ReadFileEx failure %d\n", GetLastError()); + exit(1); + } + } + + /* Remember that we have started an overlapped read already */ + buffer->platform.read_active = true; +} + +/* Initialize the command buffer, and start the first overlapped read */ +void init_command_buffer( + struct command_buffer_t *command_buffer, + int command_stream) +{ + memset(command_buffer, 0, sizeof(struct command_buffer_t)); + command_buffer->command_stream = command_stream; + command_buffer->platform.pipe_open = true; + + start_read_command(command_buffer); +} + +/* + Start the next incoming read, or return EPIPE if the command stream + has been closed. +*/ +int read_commands( + struct command_buffer_t *buffer) +{ + start_read_command(buffer); + + if (!buffer->platform.pipe_open) { + return EPIPE; + } + + return 0; +} diff --git a/packet/command_cygwin.h b/packet/command_cygwin.h new file mode 100644 index 00000000..d1a25fb8 --- /dev/null +++ b/packet/command_cygwin.h @@ -0,0 +1,48 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef COMMAND_CYGWIN_H +#define COMMAND_CYGWIN_H + +/* + Though Cygwin supports the usual Unix non-blocking reads on + the command stream, we've got to use Overlapped I/O instead because + ICMP.DLL's support for sending probes requires Overlapped I/O + and alertable waits for notification of replies. Since we need + alertable waits, we can't use Cygwin's select to determine when + command stream data is available, but Overlapped I/O completion + will work. +*/ + +/* Overlapped I/O manament for Windows command buffer reads */ +struct command_buffer_platform_t +{ + /* true if an overlapped I/O read is active */ + bool read_active; + + /* true if the command pipe is still open */ + bool pipe_open; + + /* Windows OVERLAPPED I/O data */ + OVERLAPPED overlapped; + + /* The buffer which active OVERLAPPED reads read into */ + char overlapped_buffer[COMMAND_BUFFER_SIZE]; +}; + +#endif diff --git a/packet/command_unix.c b/packet/command_unix.c new file mode 100644 index 00000000..08536e2a --- /dev/null +++ b/packet/command_unix.c @@ -0,0 +1,89 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "command.h" + +#include +#include +#include +#include +#include +#include + +/* + Initialize the command buffer and put the command stream in + non-blocking mode. +*/ +void init_command_buffer( + struct command_buffer_t *command_buffer, + int command_stream) +{ + int flags; + + memset(command_buffer, 0, sizeof(struct command_buffer_t)); + command_buffer->command_stream = command_stream; + + /* Get the current command stream flags */ + flags = fcntl(command_stream, F_GETFL, 0); + if (flags == -1) { + perror("Unexpected command stream error"); + exit(1); + } + + /* Set the O_NONBLOCK bit */ + if (fcntl(command_stream, F_SETFL, flags | O_NONBLOCK)) { + perror("Unexpected command stream error"); + exit(1); + } +} + +/* Read currently available data from the command stream */ +int read_commands( + struct command_buffer_t *buffer) +{ + int space_remaining = + COMMAND_BUFFER_SIZE - buffer->incoming_read_position - 1; + char *read_position = + &buffer->incoming_buffer[buffer->incoming_read_position]; + int read_count; + int command_stream = buffer->command_stream; + + read_count = read(command_stream, read_position, space_remaining); + + /* If the command stream has been closed, read will return zero. */ + if (read_count == 0) + { + return EPIPE; + } + + if (read_count > 0) { + /* Account for the newly read data */ + buffer->incoming_read_position += read_count; + } + + if (read_count < 0) { + /* EAGAIN simply means there is no available data to read */ + /* EINTR indicates we received a signal during read */ + if (errno != EINTR && errno != EAGAIN) { + perror("Unexpected command buffer read error"); + exit(1); + } + } + + return 0; +} diff --git a/packet/command_unix.h b/packet/command_unix.h new file mode 100644 index 00000000..84187476 --- /dev/null +++ b/packet/command_unix.h @@ -0,0 +1,27 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef COMMAND_UNIX_H +#define COMMAND_UNIX_H + +/* No platform specific data is required for Unix command streams */ +struct command_buffer_platform_t +{ +}; + +#endif diff --git a/packet/lint.sh b/packet/lint.sh new file mode 100755 index 00000000..0594a8db --- /dev/null +++ b/packet/lint.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +# Check the Python test source for good style + +PYTHON_SOURCE=testpacket.py + +pep8 $PYTHON_SOURCE +pylint --reports=n $PYTHON_SOURCE 2>/dev/null +mypy --py2 $PYTHON_SOURCE diff --git a/packet/packet.c b/packet/packet.c new file mode 100644 index 00000000..3856ce06 --- /dev/null +++ b/packet/packet.c @@ -0,0 +1,103 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include +#include +#include + +#include "wait.h" + +/* Drop SUID privileges. To be used after accquiring raw sockets. */ +static +void drop_suid_permissions(void) +{ + if (setgid(getgid()) || setuid(getuid())) { + perror("Unable to drop suid permissions"); + } + + if (geteuid() != getuid() || getegid() != getgid()) { + perror("Unable to drop suid permissions"); + } +} + +int main( + int argc, + char **argv) +{ + bool command_pipe_open; + int err; + struct command_buffer_t command_buffer; + struct net_state_t net_state; + + init_net_state(&net_state); + + /* + To minimize security risk, the only thing done prior to + dropping SUID should be opening the network state for + raw sockets. + */ + drop_suid_permissions(); + + init_command_buffer(&command_buffer, fileno(stdin)); + + command_pipe_open = true; + + /* + Dispatch commands and respond to probe replies until the + command stream is closed. + */ + while (true) { + /* Ensure any responses are written before waiting */ + fflush(stdout); + wait_for_activity(&command_buffer, &net_state); + + /* + Receive replies first so that the timestamps are as + close to the response arrival time as possible. + */ + receive_replies(&net_state); + + if (command_pipe_open) { + err = read_commands(&command_buffer); + if (err == EPIPE) + { + command_pipe_open = false; + } + } + + check_probe_timeouts(&net_state); + + /* + Dispatch commands late so that the window between probe + departure and arriving replies is as small as possible. + */ + dispatch_buffer_commands(&command_buffer, &net_state); + + /* + If the command pipe has been closed, exit after all + in-flight probes have reported their status. + */ + if (!command_pipe_open) { + if (count_in_flight_probes(&net_state) == 0) { + break; + } + } + } + + return 0; +} diff --git a/packet/platform.h b/packet/platform.h new file mode 100644 index 00000000..cca1274b --- /dev/null +++ b/packet/platform.h @@ -0,0 +1,53 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef PLATFORM_H +#define PLATFORM_H + +/* + Determine the most appropriate PLATFORM_* define for our + current target. +*/ + +#if defined(__CYGWIN__) + +#define PLATFORM_CYGWIN + +#elif defined(__APPLE__) && defined(__MACH__) + +#define PLATFORM_OS_X + +#elif defined(__gnu_linux__) + +#define PLATFORM_LINUX + +#elif defined (__FreeBSD__) + +#define PLATFORM_FREEBSD + +#elif defined(__unix__) + +#define PLATFORM_UNIX_UNKNOWN + +#else + +#error Unsupported platform + +#endif + +#endif diff --git a/packet/probe.c b/packet/probe.c new file mode 100644 index 00000000..83028945 --- /dev/null +++ b/packet/probe.c @@ -0,0 +1,176 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "probe.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "platform.h" +#include "protocols.h" +#include "timeval.h" + +#define IP_TEXT_LENGTH 32 + +/* Convert the destination address from text to sockaddr */ +int decode_dest_addr( + const struct probe_param_t *param, + struct sockaddr_in *dest_sockaddr) +{ + struct in_addr dest_addr; + + if (param->ipv4_address == NULL) { + return EINVAL; + } + + if (inet_pton(AF_INET, param->ipv4_address, &dest_addr) != 1) { + return EINVAL; + } + + dest_sockaddr->sin_family = AF_INET; + dest_sockaddr->sin_port = 0; + dest_sockaddr->sin_addr = dest_addr; + + return 0; +} + +/* Allocate a structure for tracking a new probe */ +struct probe_t *alloc_probe( + struct net_state_t *net_state, + int token) +{ + int i; + struct probe_t *probe; + + for (i = 0; i < MAX_PROBES; i++) { + probe = &net_state->probes[i]; + + if (!probe->used) { + memset(probe, 0, sizeof(struct probe_t)); + + probe->used = true; + probe->token = token; + + return probe; + } + } + + return NULL; +} + +/* Mark a probe tracking structure as unused */ +void free_probe( + struct probe_t *probe) +{ + probe->used = false; +} + +/* + Return the number of probes which haven't yet received a reply + and haven't yet timed out. +*/ +int count_in_flight_probes( + struct net_state_t *net_state) +{ + int i; + int count; + struct probe_t *probe; + + count = 0; + for (i = 0; i < MAX_PROBES; i++) { + probe = &net_state->probes[i]; + + if (probe->used) { + count++; + } + } + + return count; +} + +/* + Find an existing probe structure by ICMP id and sequence number. + Returns NULL if non is found. +*/ +struct probe_t *find_probe( + struct net_state_t *net_state, + int icmp_id, + int icmp_sequence) +{ + int i; + struct probe_t *probe; + + /* + If the ICMP id doesn't match our process ID, it wasn't a + probe generated by this process, so ignore it. + */ + if (icmp_id != htons(getpid())) { + return NULL; + } + + for (i = 0; i < MAX_PROBES; i++) { + probe = &net_state->probes[i]; + + if (probe->used && htons(probe->token) == icmp_sequence) { + return probe; + } + } + + return NULL; +} + +/* + After a probe reply has arrived, respond to the command request which + sent the probe. +*/ +void respond_to_probe( + struct probe_t *probe, + int icmp_type, + struct sockaddr_in remote_addr, + unsigned int round_trip_us) +{ + char ip_text[IP_TEXT_LENGTH]; + const char *result; + + if (inet_ntop( + AF_INET, &remote_addr.sin_addr, + ip_text, IP_TEXT_LENGTH) == NULL) { + + perror("inet_ntop failure"); + exit(1); + } + + if (icmp_type == ICMP_TIME_EXCEEDED) { + result = "ttl-expired"; + } else { + assert(icmp_type == ICMP_ECHOREPLY); + result = "reply"; + } + + printf( + "%d %s ip-4 %s round-trip-time %d\n", + probe->token, result, ip_text, round_trip_us); + + free_probe(probe); +} diff --git a/packet/probe.h b/packet/probe.h new file mode 100644 index 00000000..74dabfcc --- /dev/null +++ b/packet/probe.h @@ -0,0 +1,117 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef PROBE_H +#define PROBE_H + +#include "platform.h" + +#include +#include +#include + +#ifdef PLATFORM_CYGWIN +#include "probe_cygwin.h" +#else +#include "probe_unix.h" +#endif + +#define MAX_PROBES 1024 + +/* Parameters for sending a new probe */ +struct probe_param_t +{ + /* The command token used to identify a probe when it is completed */ + int command_token; + + /* The IP address to probe */ + const char *ipv4_address; + + /* Time to live for the transmited probe */ + int ttl; + + /* The number of seconds to wait before assuming the probe was lost */ + int timeout; +}; + +/* Tracking information for an outstanding probe */ +struct probe_t +{ + /* true if this entry is in use */ + bool used; + + /* Command token of the probe request */ + int token; + + /* Platform specific probe tracking */ + struct probe_platform_t platform; +}; + +/* Global state for interacting with the network */ +struct net_state_t +{ + /* Tracking information for in-flight probes */ + struct probe_t probes[MAX_PROBES]; + + /* Platform specific tracking information */ + struct net_state_platform_t platform; +}; + +void init_net_state( + struct net_state_t *net_state); + +bool get_next_probe_timeout( + const struct net_state_t *net_state, + struct timeval *timeout); + +void send_probe( + struct net_state_t *net_state, + const struct probe_param_t *param); + +void receive_replies( + struct net_state_t *net_state); + +void check_probe_timeouts( + struct net_state_t *net_state); + +void respond_to_probe( + struct probe_t *probe, + int icmp_type, + struct sockaddr_in remote_addr, + unsigned int round_trip_us); + +int decode_dest_addr( + const struct probe_param_t *param, + struct sockaddr_in *dest_sockaddr); + +struct probe_t *alloc_probe( + struct net_state_t *net_state, + int token); + +void free_probe( + struct probe_t *probe); + +int count_in_flight_probes( + struct net_state_t *net_state); + +struct probe_t *find_probe( + struct net_state_t *net_state, + int icmp_id, + int icmp_sequence); + +#endif diff --git a/packet/probe_cygwin.c b/packet/probe_cygwin.c new file mode 100644 index 00000000..355ca5c6 --- /dev/null +++ b/packet/probe_cygwin.c @@ -0,0 +1,181 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "probe.h" + +#include +#include + +#include "protocols.h" + +/* Open the ICMP.DLL interface */ +void init_net_state( + struct net_state_t *net_state) +{ + memset(net_state, 0, sizeof(struct net_state_t)); + + net_state->platform.icmp = IcmpCreateFile(); + if (net_state->platform.icmp == INVALID_HANDLE_VALUE) { + fprintf(stderr, "Failure opening ICMP %d\n", GetLastError()); + exit(1); + } +} + +/* + The overlapped I/O style completion routine to be called by + Windows during an altertable wait when an ICMP probe has + completed, either by reply, or by ICMP.DLL timeout. +*/ +static +void WINAPI on_icmp_reply( + PVOID context, + PIO_STATUS_BLOCK status, + ULONG reserved) +{ + struct probe_t *probe = (struct probe_t *)context; + int icmp_type; + int round_trip_us; + int reply_count; + int err; + struct sockaddr_in remote_addr; + ICMP_ECHO_REPLY32 *reply; + + reply_count = IcmpParseReplies( + &probe->platform.reply, sizeof(ICMP_ECHO_REPLY)); + + if (reply_count == 0) { + err = GetLastError(); + + /* It could be that we got no reply because of timeout */ + if (err == IP_REQ_TIMED_OUT) { + printf("%d no-reply\n", probe->token); + + free_probe(probe); + return; + } + + fprintf(stderr, "IcmpParseReplies failure %d\n", err); + exit(1); + } + + reply = &probe->platform.reply; + + remote_addr.sin_family = AF_INET; + remote_addr.sin_port = 0; + remote_addr.sin_addr.s_addr = reply->Address; + + /* Unfortunately, ICMP.DLL only gives us millisecond precision */ + round_trip_us = reply->RoundTripTime * 1000; + + icmp_type = -1; + if (reply->Status == IP_SUCCESS) { + icmp_type = ICMP_ECHOREPLY; + } else if (reply->Status == IP_TTL_EXPIRED_TRANSIT) { + icmp_type = ICMP_TIME_EXCEEDED; + } + + if (icmp_type != -1) { + /* Record probe result */ + respond_to_probe(probe, icmp_type, remote_addr, round_trip_us); + } +} + +/* Send a new probe using ICMP.DLL's send echo mechanism */ +void send_probe( + struct net_state_t *net_state, + const struct probe_param_t *param) +{ + IP_OPTION_INFORMATION option; + DWORD send_result; + DWORD timeout; + struct probe_t *probe; + struct sockaddr_in dest_sockaddr; + + if (decode_dest_addr(param, &dest_sockaddr)) { + printf("%d invalid-argument\n", param->command_token); + return; + } + + if (param->timeout > 0) { + timeout = 1000 * param->timeout; + } else { + /* + IcmpSendEcho2 will return invalid argument on a timeout of + zero. Our Unix implementation allows it. Bump up the timeout + to 1 millisecond. + */ + timeout = 1; + } + + probe = alloc_probe(net_state, param->command_token); + if (probe == NULL) { + printf("%d probes-exhausted\n", param->command_token); + return; + } + + memset(&option, 0, sizeof(IP_OPTION_INFORMATION32)); + option.Ttl = param->ttl; + + send_result = IcmpSendEcho2( + net_state->platform.icmp, NULL, + (FARPROC)on_icmp_reply, probe, + dest_sockaddr.sin_addr.s_addr, NULL, 0, &option, + &probe->platform.reply, sizeof(ICMP_ECHO_REPLY), timeout); + + if (send_result == 0) { + /* + ERROR_IO_PENDING is expected for asynchronous probes, + but any other error is unexpected. + */ + if (GetLastError() != ERROR_IO_PENDING) { + fprintf(stderr, "IcmpSendEcho2 failure %d\n", GetLastError()); + exit(1); + } + } +} + +/* + On Windows, an implementation of receive_replies is unnecessary, because, + unlike Unix, replies are completed using Overlapped I/O during an + alertable wait, and don't require explicit reads. +*/ +void receive_replies( + struct net_state_t *net_state) +{ +} + +/* + On Windows, an implementation of check_probe_timeout is unnecesary because + timeouts are managed by ICMP.DLL, including a call to the I/O completion + routine when the time fully expires. +*/ +void check_probe_timeouts( + struct net_state_t *net_state) +{ +} + +/* + As in the case of check_probe_timeout, getting the next probe timeout is + unnecessary under Windows, as ICMP.DLL manages timeouts for us. +*/ +bool get_next_probe_timeout( + const struct net_state_t *net_state, + struct timeval *timeout) +{ + return false; +} diff --git a/packet/probe_cygwin.h b/packet/probe_cygwin.h new file mode 100644 index 00000000..b8ceb085 --- /dev/null +++ b/packet/probe_cygwin.h @@ -0,0 +1,42 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef PROBE_CYGWIN_H +#define PROBE_CYGWIN_H + +#include +#include +#include +#include + +/* + Windows requires an echo reply structure for each in-flight + ICMP probe. +*/ +struct probe_platform_t +{ + ICMP_ECHO_REPLY32 reply; +}; + +/* A Windows HANDLE for the ICMP session */ +struct net_state_platform_t +{ + HANDLE icmp; +}; + +#endif diff --git a/packet/probe_unix.c b/packet/probe_unix.c new file mode 100644 index 00000000..3a6bdee6 --- /dev/null +++ b/packet/probe_unix.c @@ -0,0 +1,526 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "probe.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "protocols.h" +#include "timeval.h" + +/* Use the "jumbo" frame size as the max packet size */ +#define PACKET_BUFFER_SIZE 9000 + +/* Compute the IP checksum (or ICMP checksum) of a packet. */ +static +uint16_t compute_checksum( + const void *packet, + int size) +{ + const uint8_t *packet_bytes = (uint8_t *)packet; + uint32_t sum = 0; + int i; + + for (i = 0; i < size; i++) { + if ((i & 1) == 0) { + sum += packet_bytes[i] << 8; + } else { + sum += packet_bytes[i]; + } + } + + /* + Sums which overflow a 16-bit value have the high bits + added back into the low 16 bits. + */ + while (sum >> 16) { + sum = (sum >> 16) + (sum & 0xffff); + } + + /* + The value stored is the one's complement of the + mathematical sum. + */ + return (~sum & 0xffff); +} + +/* Encode the IP header length field in the order required by the OS. */ +static +uint16_t length_byte_swap( + const struct net_state_t *net_state, + uint16_t length) +{ + if (net_state->platform.ip_length_host_order) { + return length; + } else { + return htons(length); + } +} + +/* Construct a probe packet based on the probe parameters */ +static +int construct_packet( + const struct net_state_t *net_state, + char *packet_buffer, + int packet_buffer_size, + struct sockaddr_in dest_sockaddr, + const struct probe_param_t *param) +{ + struct IPHeader *ip; + struct ICMPHeader *icmp; + int packet_size; + int icmp_size; + + ip = (struct IPHeader *)&packet_buffer[0]; + icmp = (struct ICMPHeader *)(ip + 1); + packet_size = sizeof(struct IPHeader) + sizeof(struct ICMPHeader); + icmp_size = packet_size - sizeof(struct IPHeader); + + if (packet_buffer_size < packet_size) { + return -EINVAL; + } + + memset(packet_buffer, 0, packet_size); + + /* Fill the IP header */ + ip->version = 0x45; + ip->len = length_byte_swap(net_state, packet_size); + ip->ttl = param->ttl; + ip->protocol = IPPROTO_ICMP; + memcpy(&ip->daddr, &dest_sockaddr.sin_addr, sizeof(uint32_t)); + + /* Fill the ICMP header */ + icmp->type = ICMP_ECHO; + icmp->id = htons(getpid()); + icmp->sequence = htons(param->command_token); + icmp->checksum = htons(compute_checksum(icmp, icmp_size)); + + return packet_size; +} + +/* + Nearly all fields in the IP header should be encoded in network byte + order prior to passing to send(). However, the required byte order of + the length field of the IP header is inconsistent between operating + systems and operating system versions. FreeBSD 11 requires the length + field in network byte order, but some older versions of FreeBSD + require host byte order. OS X requires the length field in host + byte order. Linux will accept either byte order. + + Test for a byte order which works by sending a ping to localhost. +*/ +static +void check_length_order( + struct net_state_t *net_state) +{ + char packet[PACKET_BUFFER_SIZE]; + struct probe_param_t param; + struct sockaddr_in dest_sockaddr; + ssize_t bytes_sent; + int packet_size; + + memset(¶m, 0, sizeof(struct probe_param_t)); + param.ttl = 255; + param.ipv4_address = "127.0.0.1"; + + if (decode_dest_addr(¶m, &dest_sockaddr)) { + fprintf(stderr, "Error decoding localhost address\n"); + exit(1); + } + + /* First attempt to ping the localhost with network byte order */ + net_state->platform.ip_length_host_order = false; + + packet_size = construct_packet( + net_state, packet, PACKET_BUFFER_SIZE, dest_sockaddr, ¶m); + assert(packet_size > 0); + + bytes_sent = sendto( + net_state->platform.ipv4_send_socket, + packet, packet_size, 0, + (struct sockaddr *)&dest_sockaddr, + sizeof(struct sockaddr_in)); + + if (bytes_sent > 0) { + return; + } + + /* Since network byte order failed, try host byte order */ + net_state->platform.ip_length_host_order = true; + + packet_size = construct_packet( + net_state, packet, PACKET_BUFFER_SIZE, dest_sockaddr, ¶m); + assert(packet_size > 0); + + bytes_sent = sendto( + net_state->platform.ipv4_send_socket, + packet, packet_size, 0, + (struct sockaddr *)&dest_sockaddr, + sizeof(struct sockaddr_in)); + + if (bytes_sent < 0) { + perror("Unable to send with swapped length"); + exit(1); + } +} + +/* Open the raw sockets for transmitting custom crafted packets */ +void init_net_state( + struct net_state_t *net_state) +{ + int send_socket; + int recv_socket; + int flags; + int trueopt = 1; + + memset(net_state, 0, sizeof(struct net_state_t)); + + send_socket = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); + if (send_socket == -1) { + perror("Failure opening raw socket"); + exit(1); + } + + /* + We will be including the IP header in transmitted packets. + Linux doesn't require this, but BSD derived network stacks do. + */ + if (setsockopt( + send_socket, IPPROTO_IP, IP_HDRINCL, &trueopt, sizeof(int))) { + + perror("Failure to set IP_HDRINCL"); + exit(1); + } + + /* + Open a second socket with IPPROTO_ICMP because we are only + interested in receiving ICMP packets, not all packets. + */ + recv_socket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + if (recv_socket == -1) { + perror("Failure opening raw socket"); + exit(1); + } + + flags = fcntl(recv_socket, F_GETFL, 0); + if (flags == -1) { + perror("Unexpected socket error"); + exit(1); + } + + /* Set the receive socket to be non-blocking */ + if (fcntl(recv_socket, F_SETFL, flags | O_NONBLOCK)) { + perror("Unexpected socket error"); + exit(1); + } + + net_state->platform.ipv4_send_socket = send_socket; + net_state->platform.ipv4_recv_socket = recv_socket; + + check_length_order(net_state); +} + +/* Craft a custom ICMP packet for a network probe. */ +void send_probe( + struct net_state_t *net_state, + const struct probe_param_t *param) +{ + char packet[PACKET_BUFFER_SIZE]; + struct sockaddr_in dest_sockaddr; + struct probe_t *probe; + int packet_size; + + if (decode_dest_addr(param, &dest_sockaddr)) { + printf("%d invalid-argument\n", param->command_token); + return; + } + + packet_size = construct_packet( + net_state, packet, PACKET_BUFFER_SIZE, dest_sockaddr, param); + if (packet_size < 0) { + printf("%d invalid-argument\n", param->command_token); + return; + } + + probe = alloc_probe(net_state, param->command_token); + if (probe == NULL) { + printf("%d probes-exhausted\n", param->command_token); + return; + } + + /* + We get the time just before the send call to keep the timing + as tight as possible. + */ + if (gettimeofday(&probe->platform.departure_time, NULL)) { + perror("gettimeofday failure"); + exit(1); + } + + if (sendto( + net_state->platform.ipv4_send_socket, + packet, packet_size, 0, + (struct sockaddr *)&dest_sockaddr, + sizeof(struct sockaddr_in)) == -1) { + + perror("Failure sending probe"); + exit(1); + } + + probe->platform.timeout_time = probe->platform.departure_time; + probe->platform.timeout_time.tv_sec += param->timeout; +} + +/* + Compute the round trip time of a just-received probe and pass it + to the platform agnostic response handling. +*/ +static +void receive_probe( + struct probe_t *probe, + int icmp_type, + struct sockaddr_in remote_addr, + struct timeval timestamp) +{ + unsigned int round_trip_us; + + round_trip_us = + (timestamp.tv_sec - probe->platform.departure_time.tv_sec) * 1000000 + + timestamp.tv_usec - probe->platform.departure_time.tv_usec; + + respond_to_probe(probe, icmp_type, remote_addr, round_trip_us); +} + +/* + Called when we have received a new packet through our raw socket. + We'll check to see that it is a response to one of our probes, and + if so, report the result of the probe to our command stream. +*/ +static +void handle_received_packet( + struct net_state_t *net_state, + struct sockaddr_in remote_addr, + const void *packet, + int packet_length, + struct timeval timestamp) +{ + const int ip_icmp_size = + sizeof(struct IPHeader) + sizeof(struct ICMPHeader); + const int ip_icmp_ip_icmp_size = + sizeof(struct IPHeader) + sizeof(struct ICMPHeader) + + sizeof(struct IPHeader) + sizeof(struct ICMPHeader); + const struct IPHeader *ip; + const struct ICMPHeader *icmp; + const struct IPHeader *inner_ip; + const struct ICMPHeader *inner_icmp; + struct probe_t *probe; + + /* Ensure that we don't access memory beyond the bounds of the packet */ + if (packet_length < ip_icmp_size) { + return; + } + + ip = (struct IPHeader *)packet; + if (ip->protocol != IPPROTO_ICMP) { + return; + } + + icmp = (struct ICMPHeader *)(ip + 1); + + /* If we get an echo reply, our probe reached the destination host */ + if (icmp->type == ICMP_ECHOREPLY) { + probe = find_probe(net_state, icmp->id, icmp->sequence); + if (probe == NULL) { + return; + } + + receive_probe(probe, icmp->type, remote_addr, timestamp); + } + + /* + If we get a time exceeded, we got a response from an intermediate + host along the path to our destination. + */ + if (icmp->type == ICMP_TIME_EXCEEDED) { + if (packet_length < ip_icmp_ip_icmp_size) { + return; + } + + /* + The IP packet inside the ICMP response contains our original + IP header. That's where we can get our original ID and + sequence number. + */ + inner_ip = (struct IPHeader *)(icmp + 1); + inner_icmp = (struct ICMPHeader *)(inner_ip + 1); + + probe = find_probe(net_state, inner_icmp->id, inner_icmp->sequence); + if (probe == NULL) { + return; + } + + receive_probe(probe, icmp->type, remote_addr, timestamp); + } +} + +/* + Read all available packets through our receiving raw socket, and + handle any responses to probes we have preivously sent. +*/ +void receive_replies( + struct net_state_t *net_state) +{ + char packet[PACKET_BUFFER_SIZE]; + int packet_length; + struct sockaddr_in remote_addr; + socklen_t sockaddr_length; + struct timeval timestamp; + + /* Read until no more packets are available */ + while (true) { + sockaddr_length = sizeof(struct sockaddr_in); + packet_length = recvfrom( + net_state->platform.ipv4_recv_socket, + packet, PACKET_BUFFER_SIZE, 0, + (struct sockaddr *)&remote_addr, &sockaddr_length); + + /* + Get the time immediately after reading the packet to + keep the timing as precise as we can. + */ + if (gettimeofday(×tamp, NULL)) { + perror("gettimeofday failure"); + exit(1); + } + + if (packet_length == -1) { + /* + EAGAIN will be returned if there is no current packet + available. + */ + if (errno == EAGAIN) { + return; + } + + /* + EINTER will be returned if we received a signal during + receive. + */ + if (errno == EINTR) { + continue; + } + + perror("Failure receiving replies"); + exit(1); + } + + handle_received_packet( + net_state, remote_addr, packet, packet_length, timestamp); + } +} + +/* + Check for any probes for which we have not received a response + for some time, and report a time-out, assuming that we won't + receive a future reply. +*/ +void check_probe_timeouts( + struct net_state_t *net_state) +{ + struct timeval now; + struct probe_t *probe; + int i; + + if (gettimeofday(&now, NULL)) { + perror("gettimeofday failure"); + exit(1); + } + + for (i = 0; i < MAX_PROBES; i++) { + probe = &net_state->probes[i]; + + /* Don't check probes which aren't currently outstanding */ + if (!probe->used) { + continue; + } + + if (compare_timeval(probe->platform.timeout_time, now) < 0) { + /* Report timeout to the command stream */ + printf("%d no-reply\n", probe->token); + + free_probe(probe); + } + } +} + +/* + Find the remaining time until the next probe times out. + This may be a negative value if the next probe timeout has + already elapsed. + + Returns false if no probes are currently outstanding, and true + if a timeout value for the next probe exists. +*/ +bool get_next_probe_timeout( + const struct net_state_t *net_state, + struct timeval *timeout) +{ + int i; + bool have_timeout; + const struct probe_t *probe; + struct timeval now; + struct timeval probe_timeout; + + if (gettimeofday(&now, NULL)) { + perror("gettimeofday failure"); + exit(1); + } + + have_timeout = false; + for (i = 0; i < MAX_PROBES; i++) { + probe = &net_state->probes[i]; + if (!probe->used) { + continue; + } + + probe_timeout.tv_sec = + probe->platform.timeout_time.tv_sec - now.tv_sec; + probe_timeout.tv_usec = + probe->platform.timeout_time.tv_usec - now.tv_usec; + + normalize_timeval(&probe_timeout); + if (have_timeout) { + if (compare_timeval(probe_timeout, *timeout) < 0) { + /* If this probe has a sooner timeout, store it instead */ + *timeout = probe_timeout; + } + } else { + *timeout = probe_timeout; + have_timeout = true; + } + } + + return have_timeout; +} diff --git a/packet/probe_unix.h b/packet/probe_unix.h new file mode 100644 index 00000000..a9a0dfbb --- /dev/null +++ b/packet/probe_unix.h @@ -0,0 +1,49 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef PROBE_UNIX_H +#define PROBE_UNIX_H + +/* We need to track the transmission and timeouts on Unix systems */ +struct probe_platform_t +{ + /* The time at which the probe is considered lost */ + struct timeval timeout_time; + + /* The time at which the probe was sent */ + struct timeval departure_time; + +}; + +/* We'll use rack sockets to send and recieve probes on Unix systems */ +struct net_state_platform_t +{ + /* Socket used to send raw packets */ + int ipv4_send_socket; + + /* Socket used to receive ICMP replies */ + int ipv4_recv_socket; + + /* + true if we should encode the IP header length in host order. + (as opposed to network order) + */ + bool ip_length_host_order; +}; + +#endif diff --git a/packet/protocols.h b/packet/protocols.h new file mode 100644 index 00000000..f30c2411 --- /dev/null +++ b/packet/protocols.h @@ -0,0 +1,84 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef PROTOCOLS_H +#define PROTOCOLS_H + +/* ICMP type codes */ +#define ICMP_ECHOREPLY 0 +#define ICMP_DEST_UNREACH 3 +#define ICMP_ECHO 8 +#define ICMP_TIME_EXCEEDED 11 + +/* We can't rely on header files to provide this information, because + the fields have different names between, for instance, Linux and + Solaris */ +struct ICMPHeader { + uint8_t type; + uint8_t code; + uint16_t checksum; + uint16_t id; + uint16_t sequence; +}; + +/* Structure of an UDP header. */ +struct UDPHeader { + uint16_t srcport; + uint16_t dstport; + uint16_t length; + uint16_t checksum; +}; + +/* Structure of an TCP header, as far as we need it. */ +struct TCPHeader { + uint16_t srcport; + uint16_t dstport; + uint32_t seq; +}; + +/* Structure of an SCTP header */ +struct SCTPHeader { + uint16_t srcport; + uint16_t dstport; + uint32_t veri_tag; +}; + +/* Structure of an IPv4 UDP pseudoheader. */ +struct UDPv4PHeader { + uint32_t saddr; + uint32_t daddr; + uint8_t zero; + uint8_t protocol; + uint16_t len; +}; + +/* Structure of an IP header. */ +struct IPHeader { + uint8_t version; + uint8_t tos; + uint16_t len; + uint16_t id; + uint16_t frag; + uint8_t ttl; + uint8_t protocol; + uint16_t check; + uint32_t saddr; + uint32_t daddr; +}; + +#endif diff --git a/packet/testpacket.py b/packet/testpacket.py new file mode 100755 index 00000000..67bb82c0 --- /dev/null +++ b/packet/testpacket.py @@ -0,0 +1,348 @@ +#!/usr/bin/env python +# +# mtr -- a network diagnostic tool +# Copyright (C) 2016 Matt Kimball +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + + +'''Test mtr-packet's functionality + +Test the ability to send probes and receive replies using mtr-packet. +''' + +# pylint: disable=locally-disabled, import-error +import fcntl +import os +import re +import select +import subprocess +import sys +import time +import unittest + + +class ReadReplyTimeout(Exception): + 'Exception raised by TestProbe.read_reply upon timeout' + + pass + + +class TestProbe(unittest.TestCase): + 'Test cases for sending and receiving probes' + + def __init__(self, *args): + self.reply_buffer = None # type: str + self.packet_process = None # type: subprocess.Popen + self.stdout_fd = None # type: int + + super(TestProbe, self).__init__(*args) + + def setUp(self): + 'Set up a test case by spawning a mtr-packet process' + + packet_path = os.environ.get('MTR_PACKET', './mtr-packet') + + self.reply_buffer = '' + self.packet_process = subprocess.Popen( + [packet_path], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + + # Put the mtr-packet process's stdout in non-blocking mode + # so that we can read from it without a timeout when + # no reply is available. + self.stdout_fd = self.packet_process.stdout.fileno() + flags = fcntl.fcntl(self.stdout_fd, fcntl.F_GETFL) + + # pylint: disable=locally-disabled, no-member + fcntl.fcntl(self.stdout_fd, fcntl.F_SETFL, flags | os.O_NONBLOCK) + + def tearDown(self): + 'After a test, kill the running mtr-packet instance' + + try: + self.packet_process.kill() + except OSError: + return + + def write_command(self, cmd): # type: (str) -> None + 'Send a command string to the mtr-packet instance' + + self.packet_process.stdin.write(cmd + '\n') + self.packet_process.stdin.flush() + + def read_reply(self, timeout=10.0): # type: (float) -> str + '''Read the next reply from mtr-packet. + + Attempt to read the next command reply from mtr-packet. If no reply + is available withing the timeout time, raise ReadReplyTimeout + instead.''' + + start_time = time.time() + + # Read from mtr-packet until either the timeout time has elapsed + # or we read a newline character, which indicates a finished + # reply. + while True: + now = time.time() + elapsed = now - start_time + + select_time = timeout - elapsed + if select_time < 0: + select_time = 0 + + select.select([self.stdout_fd], [], [], select_time) + + try: + self.reply_buffer += os.read(self.stdout_fd, 1024) + except OSError: + pass + + # If we have read a newline character, we can stop waiting + # for more input. + newline_ix = self.reply_buffer.find('\n') + if newline_ix != -1: + break + + if elapsed >= timeout: + raise ReadReplyTimeout() + + reply = self.reply_buffer[:newline_ix] + self.reply_buffer = self.reply_buffer[newline_ix + 1:] + return reply + + def test_unknown_command(self): + 'Test sending a command unknown to mtr-packet' + + self.write_command('13 argle-bargle') + self.assertEqual(self.read_reply(), '13 unknown-command') + + def test_malformed_command(self): + 'Test sending a malformed command request to mtr-packet' + + self.write_command('malformed') + self.assertEqual(self.read_reply(), '0 command-parse-error') + + def test_exit_on_stdin_closed(self): + '''Test that the packet process terminates after stdin is closed + + Test that, when outstanding requests are complete, the process + terminates following stdin being closed.''' + + self.write_command('15 send-probe ip-4 8.8.254.254 timeout 1') + self.packet_process.stdin.close() + time.sleep(2) + self.read_reply() + exit_code = self.packet_process.poll() + self.assertIsNotNone(exit_code) + + def test_probe(self): + 'Test sending regular ICMP probes to known addresses' + + reply_regex = r'^14 reply ip-4 8.8.8.8 round-trip-time [0-9]+$' + + # Probe Google's well-known DNS server and expect a reply + self.write_command('14 send-probe ip-4 8.8.8.8') + reply = self.read_reply() + match = re.match(reply_regex, reply) + self.assertIsNotNone(match) + + def test_invalid_argument(self): + 'Test sending invalid arguments with probe requests' + + invalid_argument_regex = r'^[0-9]+ invalid-argument$' + + bad_commands = [ + '22 send-probe', + '23 send-probe ip-4 str-value', + '24 send-probe ip-4 8.8.8.8 timeout str-value', + '25 send-probe ip-4 8.8.8.8 ttl str-value', + ] + + for cmd in bad_commands: + self.write_command(cmd) + reply = self.read_reply() + match = re.match(invalid_argument_regex, reply) + self.assertIsNotNone(match) + + def test_timeout(self): + 'Test timeouts when sending to a non-existant address' + + no_reply_regex = r'^15 no-reply$' + + # + # Probe a non-existant address, and expect no reply + # + # I'm not sure what the best way to find an address that doesn't + # exist, but is still route-able. If we use a reserved IP + # address range, Windows will tell us it is non-routeable, + # rather than timing out when transmitting to that address. + # + # We're just using a currently unused address in Google's + # range instead. This is probably not the best solution. + # + + # pylint: disable=locally-disabled, unused-variable + for i in range(16): + self.write_command('15 send-probe ip-4 8.8.254.254 timeout 1') + reply = self.read_reply() + match = re.match(no_reply_regex, reply) + self.assertIsNotNone(match) + + def test_exhaust_probes(self): + 'Test exhausting all available probes' + + exhausted_regex = r'^[0-9]+ probes-exhausted$' + + match = None + probe_count = 4 * 1024 + id = 1024 + for i in range(probe_count): + command = str(id) + ' send-probe ip-4 8.8.254.254 timeout 60' + id += 1 + self.write_command(command) + + reply = None + try: + reply = self.read_reply(0) + except ReadReplyTimeout: + pass + + if reply: + match = re.match(exhausted_regex, reply) + if match: + break + + self.assertIsNotNone(match) + + def test_timeout_values(self): + '''Test that timeout values wait the right amount of time + + Give each probe a half-second grace period to probe a timeout + reply after the expected timeout time.''' + + begin = time.time() + self.write_command('19 send-probe ip-4 8.8.254.254 timeout 0') + self.read_reply() + elapsed = time.time() - begin + self.assertLess(elapsed, 0.5) + + begin = time.time() + self.write_command('20 send-probe ip-4 8.8.254.254 timeout 1') + self.read_reply() + elapsed = time.time() - begin + self.assertGreaterEqual(elapsed, 1.0) + self.assertLess(elapsed, 1.5) + + begin = time.time() + self.write_command('21 send-probe ip-4 8.8.254.254 timeout 3') + self.read_reply() + elapsed = time.time() - begin + self.assertGreaterEqual(elapsed, 3.0) + self.assertLess(elapsed, 3.5) + + def test_ttl_expired(self): + 'Test sending a probe which will have its time-to-live expire' + + ttl_expired_regex = \ + r'^16 ttl-expired ip-4 [0-9\.]+ round-trip-time [0-9]+$' + + # Probe Goolge's DNS server, but give the probe only one hop + # to live. + self.write_command('16 send-probe ip-4 8.8.8.8 ttl 1') + reply = self.read_reply() + match = re.match(ttl_expired_regex, reply) + self.assertIsNotNone(match) + + def test_parallel_probes(self): + '''Test sending multiple probes in parallel + + We will expect the probes to complete out-of-order by sending + a probe to a distant host immeidately followed by a probe to + the local host.''' + + reply_regex = \ + r'^[0-9]+ reply ip-4 [0-9\.]+ round-trip-time ([0-9]+)$' + + success_count = 0 + loop_count = 32 + + # pylint: disable=locally-disabled, unused-variable + for i in range(loop_count): + # Probe the distant host before the local host. + self.write_command('17 send-probe ip-4 8.8.8.8 timeout 1') + self.write_command('18 send-probe ip-4 127.0.0.1 timeout 1') + + reply = self.read_reply() + match = re.match(reply_regex, reply) + if not match: + continue + first_time = int(match.group(1)) + + reply = self.read_reply() + match = re.match(reply_regex, reply) + if not match: + continue + second_time = int(match.group(1)) + + # Ensure we got a reply from the host with the lowest latency + # first. + self.assertLess(first_time, second_time) + + success_count += 1 + + # We need 95% success to pass. This allows a few probes to be + # occasionally dropped by the network without failing the test. + required_success = int(loop_count * 0.95) + self.assertGreaterEqual(success_count, required_success) + + def test_versioning(self): + 'Test version checks and feature support checks' + + feature_tests = [ + ('30 check-support feature version', + r'^30 feature-support support [0-9]+\.[0-9a-z\-\.]+$'), + ('31 check-support feature ip-4', + r'^31 feature-support support ok$'), + ('32 check-support feature send-probe', + r'^32 feature-support support ok$'), + ('33 check-support feature bogus-feature', + r'^33 feature-support support no$') + ] + + for (request, regex) in feature_tests: + self.write_command(request) + reply = self.read_reply() + match = re.match(regex, reply) + self.assertIsNotNone(match) + + def test_command_overflow(self): + 'Test overflowing the incoming command buffer' + + big_buffer = 'x' * (64 * 1024) + self.write_command(big_buffer) + + reply = self.read_reply() + self.assertEqual(reply, '0 command-buffer-overflow') + + +if __name__ == '__main__': + # pylint: disable=locally-disabled, no-member + if sys.platform != 'cygwin' and os.getuid() > 0: + sys.stderr.write( + "Warning: Many tests require running as root\n") + + unittest.main() diff --git a/packet/timeval.c b/packet/timeval.c new file mode 100644 index 00000000..ae8c3f66 --- /dev/null +++ b/packet/timeval.c @@ -0,0 +1,77 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "timeval.h" + +/* + Ensure that a timevalue has a microsecond value in the range + [0.0, 1.0e6) microseconds by converting microseconds to full seconds. +*/ +void normalize_timeval( + struct timeval *timeval) +{ + int full_sec; + + /* + If tv_usec has overflowed a full second, convert the overflow + to tv_sec. + */ + full_sec = timeval->tv_usec / 1000000; + timeval->tv_sec += full_sec; + timeval->tv_usec -= 1000000 * full_sec; + + /* If tv_usec is negative, make it positive by rolling tv_sec back */ + if (timeval->tv_usec < 0) { + timeval->tv_sec--; + timeval->tv_usec += 1000000; + } + + /* If the entire time value is negative, clamp to zero */ + if (timeval->tv_sec < 0) { + timeval->tv_sec = 0; + timeval->tv_usec = 0; + } +} + +/* + Compare two time values. Return: + + -1 if a < b + 0 if a == b + 1 if a > b +*/ +int compare_timeval( + struct timeval a, + struct timeval b) +{ + if (a.tv_sec > b.tv_sec) { + return 1; + } + if (a.tv_sec < b.tv_sec) { + return -1; + } + + if (a.tv_usec > b.tv_usec) { + return 1; + } + if (a.tv_usec < b.tv_usec) { + return -1; + } + + return 0; +} diff --git a/packet/timeval.h b/packet/timeval.h new file mode 100644 index 00000000..d00897a2 --- /dev/null +++ b/packet/timeval.h @@ -0,0 +1,31 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef TIMEVAL_H +#define TIMEVAL_H + +#include + +void normalize_timeval( + struct timeval *timeval); + +int compare_timeval( + struct timeval a, + struct timeval b); + +#endif diff --git a/packet/wait.h b/packet/wait.h new file mode 100644 index 00000000..3008b6ff --- /dev/null +++ b/packet/wait.h @@ -0,0 +1,29 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef WAIT_H +#define WAIT_H + +#include "command.h" +#include "probe.h" + +void wait_for_activity( + const struct command_buffer_t *command_buffer, + const struct net_state_t *net_state); + +#endif diff --git a/packet/wait_cygwin.c b/packet/wait_cygwin.c new file mode 100644 index 00000000..e459c7e3 --- /dev/null +++ b/packet/wait_cygwin.c @@ -0,0 +1,44 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "wait.h" + +#include +#include +#include + +/* + Sleep until we receive a new probe response, a new command on the + command stream, or a probe timeout. On Windows, this means that + we will sleep with an alertable wait, as all of these conditions + use I/O completion routines as notifications of these events. +*/ +void wait_for_activity( + const struct command_buffer_t *command_buffer, + const struct net_state_t *net_state) +{ + DWORD wait_result; + + /* Sleep until an I/O completion routine runs */ + wait_result = SleepEx(INFINITE, TRUE); + + if (wait_result == WAIT_FAILED) { + fprintf(stderr, "SleepEx failure %d\n", GetLastError()); + exit(1); + } +} diff --git a/packet/wait_unix.c b/packet/wait_unix.c new file mode 100644 index 00000000..eba6fd33 --- /dev/null +++ b/packet/wait_unix.c @@ -0,0 +1,86 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "wait.h" + +#include +#include +#include +#include +#include +#include + +/* + Sleep until we receive a new probe response, a new command on the + command stream, or a probe timeout. On Unix systems, this means + we use select to wait on file descriptors for the command stream + and the raw recieve socket. +*/ +void wait_for_activity( + const struct command_buffer_t *command_buffer, + const struct net_state_t *net_state) +{ + int nfds; + fd_set read_set; + struct timeval probe_timeout; + struct timeval *select_timeout; + int ready_count; + int command_stream = command_buffer->command_stream; + int socket = net_state->platform.ipv4_recv_socket; + + FD_ZERO(&read_set); + FD_SET(command_stream, &read_set); + nfds = command_stream + 1; + FD_SET(socket, &read_set); + if (socket >= nfds) { + nfds = socket + 1; + } + + while (true) { + select_timeout = NULL; + + /* Use the soonest probe timeout time as our maximum wait time */ + if (get_next_probe_timeout(net_state, &probe_timeout)) { + assert(probe_timeout.tv_sec >= 0); + select_timeout = &probe_timeout; + } + + ready_count = select(nfds, &read_set, NULL, NULL, select_timeout); + + /* + If we didn't have an error, either one of our descriptors is + readable, or we timed out. So we can now return. + */ + if (ready_count != -1) { + break; + } + + /* + We will get EINTR if we received a signal during the select, so + retry in that case. We may get EAGAIN if "the kernel was + (perhaps temporarily) unable to allocate the requested number of + file descriptors." I haven't seen this in practice, but selecting + again seems like the right thing to do. + */ + if (errno != EINTR && errno != EAGAIN) { + /* We don't expect other errors, so report them */ + perror("unexpected select error"); + exit(1); + } + } +} diff --git a/select.c b/select.c index 3d4bb036..2c0bfd72 100644 --- a/select.c +++ b/select.c @@ -254,10 +254,6 @@ extern void select_loop(struct mtr_ctl *ctl){ } anyset = 1; } - - /* Check for activity on open sockets */ - if (ctl->mtrtype == IPPROTO_TCP) - net_process_fds(ctl, &writefd); } return; }