/autom4te.cache/
/.deps/
+/packet/.deps/
/ChangeLog
/INSTALL
/mtr
+/mtr-packet
/mtr.8
/mtr-*.tar.gz
-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)
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=''; \
$(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 \
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
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
aclocal $ACLOCAL_OPTS
autoheader
automake --add-missing --copy --foreign
-autoconf
-
+autoconf --force
[AM_SILENT_RULES([yes])],
[AC_SUBST([AM_DEFAULT_VERBOSITY], [1])])
+AC_CANONICAL_HOST
AC_PROG_CC
# Check pkg-config availability.
])
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])
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
+#include <time.h>
#include "mtr.h"
#include "display.h"
}
-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);
/* 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);
--- /dev/null
+.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).
.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
.UR https://\:github\:.com/\:traviscross/\:mtr
.UE .
.SH "SEE ALSO"
+.BR mtr-packet (8),
.BR traceroute (8),
.BR ping (8),
.BR socket (7),
--- /dev/null
+@echo off\r
+rem\r
+rem mtr -- a network diagnostic tool\r
+rem Copyright (C) 2016 Matt Kimball\r
+rem\r
+rem This program is free software; you can redistribute it and/or modify\r
+rem it under the terms of the GNU General Public License version 2 as\r
+rem published by the Free Software Foundation.\r
+rem\r
+rem This program is distributed in the hope that it will be useful,\r
+rem but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+rem MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+rem GNU General Public License for more details.\r
+rem\r
+rem You should have received a copy of the GNU General Public License\r
+rem along with this program; if not, write to the Free Software\r
+rem Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\r
+rem\r
+\r
+rem Assume the path of this batch file is the mtr installation location\r
+set MTR_DIR=%~dp0\r
+\r
+set MTR_BIN=%MTR_DIR%\bin\r
+\r
+rem ncurses needs to locate the cygwin terminfo file\r
+set TERMINFO=%MTR_DIR%\terminfo\r
+\r
+rem mtr needs to know the location to the packet generator\r
+set MTR_PACKET=%MTR_BIN%\mtr-packet.exe\r
+\r
+rem Pass along commandline arguments\r
+%MTR_BIN%\mtr %*\r
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
-#include <time.h>
#include <ctype.h>
#include <assert.h>
#include <fcntl.h>
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;
#ifdef ENABLE_IPV6
struct sockaddr_in6 * sa6;
#endif
- time_t now;
names_t *names_root = NULL;
names_t **names_head = &names_root;
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 */
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;
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;
}
}
- 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);
display_loop(&ctl);
net_end_transit();
- now = time(NULL);
- display_close(&ctl, now);
+ display_close(&ctl);
unlock(stdout);
if (ctl.DisplayMode != DisplayCSV)
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/select.h>
+#include <sys/wait.h>
#include <netinet/in.h>
+#include <assert.h>
#include <memory.h>
#include <unistd.h>
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif
+#include <signal.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#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;
};
+/* 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;
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;
}
-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);
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 */
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)
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 */
}
-/* 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);
}
}
-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);
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);
error(EXIT_FAILURE, 0, "net_open bad address type");
}
- len = sizeof name_struct;
- getsockname (recvsock, name, &len);
- sockaddrtop( name, localaddr, sizeof localaddr );
-
return 0;
}
}
-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;
}
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;
}
}
-/* 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; (i<mpls->labels) && (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)
{
}
}
-/* 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)
{
tv.tv_usec = 0;
net_add_fds(&writefd, &maxfd);
select(maxfd, NULL, &writefd, NULL, &tv);
- net_process_fds(ctl, &writefd);
}
#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);
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);
--- /dev/null
+/*
+ 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 <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*
+ 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;
+}
--- /dev/null
+/*
+ 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
--- /dev/null
+/*
+ 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 <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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;
+ }
+}
--- /dev/null
+/*
+ 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
--- /dev/null
+/*
+ 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 <io.h>
+#include <stdio.h>
+
+/*
+ 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;
+}
--- /dev/null
+/*
+ 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
--- /dev/null
+/*
+ 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 <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+/*
+ 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;
+}
--- /dev/null
+/*
+ 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
--- /dev/null
+#!/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
--- /dev/null
+/*
+ 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 <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#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;
+}
--- /dev/null
+/*
+ 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
--- /dev/null
+/*
+ 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 <arpa/inet.h>
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#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);
+}
--- /dev/null
+/*
+ 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 <netinet/in.h>
+#include <stdbool.h>
+#include <sys/time.h>
+
+#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
--- /dev/null
+/*
+ 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 <stdio.h>
+#include <winternl.h>
+
+#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;
+}
--- /dev/null
+/*
+ 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 <arpa/inet.h>
+#include <windows.h>
+#include <iphlpapi.h>
+#include <icmpapi.h>
+
+/*
+ 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
--- /dev/null
+/*
+ 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 <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#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;
+}
--- /dev/null
+/*
+ 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
--- /dev/null
+/*
+ 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
--- /dev/null
+#!/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()
--- /dev/null
+/*
+ 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;
+}
--- /dev/null
+/*
+ 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 <sys/time.h>
+
+void normalize_timeval(
+ struct timeval *timeval);
+
+int compare_timeval(
+ struct timeval a,
+ struct timeval b);
+
+#endif
--- /dev/null
+/*
+ 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
--- /dev/null
+/*
+ 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 <io.h>
+#include <stdio.h>
+#include <windows.h>
+
+/*
+ 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);
+ }
+}
--- /dev/null
+/*
+ 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 <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/select.h>
+
+/*
+ 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);
+ }
+ }
+}
}
anyset = 1;
}
-
- /* Check for activity on open sockets */
- if (ctl->mtrtype == IPPROTO_TCP)
- net_process_fds(ctl, &writefd);
}
return;
}