mirror of
https://github.com/traviscross/mtr.git
synced 2024-09-21 02:17:11 +00:00
Added mtr-packet subprocess
The mtr-packet tool isolates the raw socket sending/receiving of packets from the mtr user interface. By isolating the socket interactions to a separate process, we can be sure that any security flaws in the user-interface code doesn't expose a raw socket interface to an attacker attempting to escalate privileges. This is a bare-bones implementation, only support ICMP, only support IP version 4, and missing many of the probe customization features available in mtr. It will require some more work to reach feature parity with the current mtr implementation. But it's a start. The include mtr-packet man page explains the protocol format used to communicate with this new process. Included is an automated test for mtr-packet, implemented using Python's unittest module. Though the code actually being tested is implemented in C, Python make it easy to write test cases. 'make check' will test the current build. An alternate code-path for Windows is included in the mtr-packet tool. The mechanism for sending and receiving network probes is significantly different for Windows, as compared to Unix-like operating systems, but the interface provided by mtr-packet is the same. 'make dist-windows-bin' will make a Windows binary distribution. A Cygwin build environment is required, but the resulting binary distribution doesn't require that Cygwin be already installed. Tested on: Ubuntu 16.10, FreeBSD 11.0, MacOS 10.12.1 (Sierra), Windows 7 Since the code changes are significant, more esoteric operating systems may require changes.
This commit is contained in:
parent
7e13a55af9
commit
5d26cb0c05
2
.gitignore
vendored
2
.gitignore
vendored
@ -19,9 +19,11 @@ stamp-h1*
|
||||
|
||||
/autom4te.cache/
|
||||
/.deps/
|
||||
/packet/.deps/
|
||||
/ChangeLog
|
||||
/INSTALL
|
||||
/mtr
|
||||
/mtr-packet
|
||||
/mtr.8
|
||||
|
||||
/mtr-*.tar.gz
|
||||
|
77
Makefile.am
77
Makefile.am
@ -1,6 +1,12 @@
|
||||
EXTRA_DIST = SECURITY img/mtr_icon.xpm
|
||||
EXTRA_DIST = \
|
||||
SECURITY \
|
||||
mtr.bat \
|
||||
img/mtr_icon.xpm \
|
||||
packet/testpacket.py \
|
||||
packet/lint.sh
|
||||
|
||||
sbin_PROGRAMS = mtr
|
||||
sbin_PROGRAMS = mtr mtr-packet
|
||||
TESTS = packet/testpacket.py
|
||||
|
||||
PATHFILES =
|
||||
CLEANFILES = $(PATHFILES)
|
||||
@ -8,7 +14,7 @@ EXTRA_DIST += $(PATHFILES:=.in)
|
||||
edit_cmd = sed \
|
||||
-e 's|@VERSION[@]|$(VERSION)|g'
|
||||
|
||||
$(PATHFILES): Makefile
|
||||
%.8: $(srcdir)/%.8.in
|
||||
@ rm -f $@ $@.tmp
|
||||
$(AM_V_at) $(MKDIR_P) $$(dirname $@)
|
||||
$(AM_V_GEN) srcdir=''; \
|
||||
@ -16,12 +22,14 @@ $(PATHFILES): Makefile
|
||||
$(edit_cmd) $${srcdir}$@.in >$@.tmp
|
||||
@ mv $@.tmp $@
|
||||
|
||||
dist_man_MANS = mtr.8
|
||||
PATHFILES += mtr.8
|
||||
$(PATHFILES): Makefile
|
||||
|
||||
dist_man_MANS = mtr.8 mtr-packet.8
|
||||
PATHFILES += mtr.8 mtr-packet.8
|
||||
|
||||
install-exec-hook:
|
||||
`setcap cap_net_raw+ep $(DESTDIR)$(sbindir)/mtr` \
|
||||
|| chmod u+s $(DESTDIR)$(sbindir)/mtr
|
||||
`setcap cap_net_raw+ep $(DESTDIR)$(sbindir)/mtr-packet` \
|
||||
|| chmod u+s $(DESTDIR)$(sbindir)/mtr-packet
|
||||
|
||||
mtr_SOURCES = mtr.c mtr.h \
|
||||
net.c net.h \
|
||||
@ -32,6 +40,7 @@ mtr_SOURCES = mtr.c mtr.h \
|
||||
report.c report.h \
|
||||
select.c select.h \
|
||||
utils.c utils.h \
|
||||
packet/cmdparse.c packet/cmdparse.h \
|
||||
mtr-curses.h \
|
||||
img/mtr_icon.xpm \
|
||||
mtr-gtk.h
|
||||
@ -65,6 +74,60 @@ mtr_INCLUDES = $(GLIB_CFLAGS) -I$(top_builddir) -I$(top_srcdir)
|
||||
mtr_CFLAGS = $(GTK_CFLAGS) $(NCURSES_CFLAGS)
|
||||
mtr_LDADD = $(GTK_LIBS) $(NCURSES_LIBS) $(RESOLV_LIBS)
|
||||
|
||||
|
||||
mtr_packet_SOURCES = \
|
||||
packet/packet.c \
|
||||
packet/cmdparse.c packet/cmdparse.h \
|
||||
packet/command.c packet/command.h \
|
||||
packet/platform.h \
|
||||
packet/probe.c packet/probe.h \
|
||||
packet/protocols.h \
|
||||
packet/timeval.c packet/timeval.h \
|
||||
packet/wait.h
|
||||
|
||||
|
||||
if CYGWIN
|
||||
|
||||
mtr_packet_SOURCES += \
|
||||
packet/command_cygwin.c packet/command_cygwin.h \
|
||||
packet/probe_cygwin.c packet/probe_cygwin.h \
|
||||
packet/wait_cygwin.c
|
||||
mtr_packet_LDADD = -lcygwin -licmp -lws2_32
|
||||
|
||||
dist_windows_aux = \
|
||||
$(srcdir)/mtr.bat \
|
||||
$(srcdir)/AUTHORS \
|
||||
$(srcdir)/COPYING \
|
||||
$(srcdir)/README \
|
||||
$(srcdir)/NEWS
|
||||
|
||||
distwindir = $(distdir)-win-$(host_cpu)
|
||||
|
||||
# Bundle necessary files for a Windows binary distribution
|
||||
distdir-win: $(dist_windows_aux) mtr.exe mtr-packet.exe
|
||||
rm -fr $(distwindir)
|
||||
mkdir -p $(distwindir) $(distwindir)/bin $(distwindir)/terminfo
|
||||
cp $(dist_windows_aux) -t $(distwindir)
|
||||
cp mtr.exe mtr-packet.exe -t $(distwindir)/bin
|
||||
ldd mtr.exe | grep -v cygdrive | awk '{ print $$3 }' | xargs cp -t $(distwindir)/bin
|
||||
cp `find /usr/share/terminfo -name cygwin | xargs dirname` -r $(distwindir)/terminfo
|
||||
|
||||
# Zip up a Windows binary distribution
|
||||
dist-windows-bin: distdir-win
|
||||
rm -f $(distwindir).zip
|
||||
zip -rq $(distwindir).zip $(distwindir)
|
||||
rm -fr $(distwindir)
|
||||
|
||||
else # if CYGWIN
|
||||
|
||||
mtr_packet_SOURCES += \
|
||||
packet/command_unix.c packet/command_unix.h \
|
||||
packet/probe_unix.c packet/probe_unix.h \
|
||||
packet/wait_unix.c
|
||||
|
||||
endif # if CYGWIN
|
||||
|
||||
|
||||
if BUILD_BASH_COMPLETION
|
||||
dist_bashcompletion_DATA = bash-completion/mtr
|
||||
endif
|
||||
|
@ -3,5 +3,4 @@
|
||||
aclocal $ACLOCAL_OPTS
|
||||
autoheader
|
||||
automake --add-missing --copy --foreign
|
||||
autoconf
|
||||
|
||||
autoconf --force
|
||||
|
@ -17,6 +17,7 @@ m4_ifdef([AM_SILENT_RULES],
|
||||
[AM_SILENT_RULES([yes])],
|
||||
[AC_SUBST([AM_DEFAULT_VERBOSITY], [1])])
|
||||
|
||||
AC_CANONICAL_HOST
|
||||
AC_PROG_CC
|
||||
|
||||
# Check pkg-config availability.
|
||||
@ -29,6 +30,8 @@ before running ./bootstrap.sh again.])
|
||||
])
|
||||
PKG_PROG_PKG_CONFIG
|
||||
|
||||
AM_CONDITIONAL([CYGWIN], [test "$host_os" = cygwin])
|
||||
|
||||
# Check bytes in types.
|
||||
AC_CHECK_SIZEOF([unsigned char], [1])
|
||||
AC_CHECK_SIZEOF([unsigned short], [2])
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "mtr.h"
|
||||
#include "display.h"
|
||||
@ -107,8 +108,12 @@ extern void display_open(struct mtr_ctl *ctl)
|
||||
}
|
||||
|
||||
|
||||
extern void display_close(struct mtr_ctl *ctl, time_t now)
|
||||
extern void display_close(struct mtr_ctl *ctl)
|
||||
{
|
||||
time_t now;
|
||||
|
||||
now = time(NULL);
|
||||
|
||||
switch(ctl->DisplayMode) {
|
||||
case DisplayReport:
|
||||
report_close(ctl);
|
||||
|
@ -53,7 +53,7 @@ enum {
|
||||
/* Prototypes for display.c */
|
||||
extern void display_detect(struct mtr_ctl *ctl, int *argc, char ***argv);
|
||||
extern void display_open(struct mtr_ctl *ctl);
|
||||
extern void display_close(struct mtr_ctl *ctl, time_t now);
|
||||
extern void display_close(struct mtr_ctl *ctl);
|
||||
extern void display_redraw(struct mtr_ctl *ctl);
|
||||
extern void display_rawxmit(struct mtr_ctl *ctl, int hostnum, int seq);
|
||||
extern void display_rawping(struct mtr_ctl *ctl, int hostnum, int msec, int seq);
|
||||
|
226
mtr-packet.8.in
Normal file
226
mtr-packet.8.in
Normal file
@ -0,0 +1,226 @@
|
||||
.TH MTR-PACKET 8 "@VERSION@" "mtr-packet" "System Administration"
|
||||
.SH NAME
|
||||
mtr-packet - send and receive network probes
|
||||
.SH DESCRIPTION
|
||||
.B mtr-packet
|
||||
reads command requests from
|
||||
.I stdin,
|
||||
each separated by a newline character, and responds with command replies
|
||||
to
|
||||
.I stdout\c
|
||||
, also each separated by a newline character. The syntactic structure of
|
||||
requests and replies are the same. The following format is used:
|
||||
.LP
|
||||
.RS
|
||||
.I TOKEN
|
||||
.I COMMAND
|
||||
[\c
|
||||
.I ARGUMENT-NAME
|
||||
.I ARGUMENT-VALUE
|
||||
\&...]
|
||||
.RE
|
||||
.LP
|
||||
.I TOKEN
|
||||
is a unique integer value. The same value will be used as the
|
||||
.I TOKEN
|
||||
for the response. This is necessary for associating replies with
|
||||
requests, as commands may be completed in a different order than they are
|
||||
requested. The invoker of
|
||||
.B mtr-packet
|
||||
should always use the
|
||||
.I TOKEN
|
||||
value to determine which command request has completed.
|
||||
.LP
|
||||
.I COMMAND
|
||||
is a string identifying the command request type. A common command
|
||||
is
|
||||
.B send-probe\c
|
||||
, which will transmit one network probe.
|
||||
.LP
|
||||
.I ARGUMENT-NAME
|
||||
strings and
|
||||
.I ARGUMENT-VALUE
|
||||
strings always come in pairs. It is a syntactic error to provide an
|
||||
.I ARGUMENT-NAME
|
||||
without a corresponding
|
||||
.I ARGUMENT-VALUE\c
|
||||
\&. Valid
|
||||
.I ARGUMENT-NAME
|
||||
strings depend on the
|
||||
.I COMMAND
|
||||
being used.
|
||||
.SH REQUESTS
|
||||
.TP
|
||||
.B send-probe
|
||||
Send a network probe to a particular IP address. An IP address must
|
||||
be provided as an argument.
|
||||
.B send-probe
|
||||
will reply with
|
||||
.B reply\c
|
||||
,
|
||||
.B no-reply\c
|
||||
, or
|
||||
.B ttl-expired\c
|
||||
\&.
|
||||
|
||||
The following arguments may be used:
|
||||
|
||||
.B ip-4
|
||||
.I IP-ADDRESS
|
||||
|
||||
The Internet Protocol version 4 address to probe.
|
||||
|
||||
.B timeout
|
||||
.I TIMEOUT-SECONDS
|
||||
|
||||
The number of seconds to wait for a response to the probe before
|
||||
discarding the probe as lost, and generating a
|
||||
.B no-reply
|
||||
command reply.
|
||||
|
||||
.B ttl
|
||||
.I TIME-TO-LIVE
|
||||
|
||||
The time-to-live value for the Internet Protocol packet header used
|
||||
in constructing the probe. This value determines the number of
|
||||
network hops through which the probe will travel before a response
|
||||
is generated by an intermediate network host.
|
||||
|
||||
.TP
|
||||
.B check-support
|
||||
Check for support for a particular feature in this version of
|
||||
.B mtr-packet
|
||||
and in this particular operating environment.
|
||||
.B check-support
|
||||
will reply with
|
||||
.B feature-supported\c
|
||||
\&. A
|
||||
.B feature
|
||||
argument is required.
|
||||
|
||||
.B feature
|
||||
.I FEATURE-NAME
|
||||
|
||||
The name of a feature requested. Some features which can be checked
|
||||
are
|
||||
.B send-probe
|
||||
and
|
||||
.B ip-4\c
|
||||
\&. The feature
|
||||
.B version
|
||||
can be checked to retrieve the version of
|
||||
.B mtr-packet\c
|
||||
\&.
|
||||
|
||||
.SH REPLIES
|
||||
.TP
|
||||
.B reply
|
||||
The destination host received the
|
||||
.B send-probe
|
||||
probe and replied. Arguments of the reply are the following:
|
||||
|
||||
.B ip-4
|
||||
.I IP-ADDRESS
|
||||
|
||||
The Internet Protocol address of the host which replied to the
|
||||
probe.
|
||||
|
||||
.B round-trip-time
|
||||
.I TIME
|
||||
|
||||
The time which passed between the transmission of the probe and its
|
||||
response. The time is provided as a integral number of
|
||||
microseconds elapsed.
|
||||
|
||||
.TP
|
||||
.B no-reply
|
||||
No response to the probe request was received before the timeout
|
||||
expired.
|
||||
.TP
|
||||
.B ttl-expired
|
||||
The time-to-live value of the transmitted probe expired before
|
||||
the probe arrived at its intended destination. Arguments of
|
||||
.B ttl-expired
|
||||
are:
|
||||
|
||||
.B ip-4
|
||||
.I IP-ADDRESS
|
||||
|
||||
The Internet Protocol address of the host at which the time-to-live
|
||||
value expired.
|
||||
|
||||
.B round-trip-time
|
||||
.I TIME
|
||||
|
||||
The time which passed between the transmission of the probe and its
|
||||
response. The time is provided as a integral number of
|
||||
microseconds elapsed.
|
||||
|
||||
.TP
|
||||
.B feature-support
|
||||
A reply to provided to
|
||||
.B check-support
|
||||
indicating the availability of a particular feature. The argument
|
||||
provided is:
|
||||
|
||||
.B support
|
||||
.I PRESENT
|
||||
|
||||
In most cases, the
|
||||
.I PRESENT
|
||||
value will be either
|
||||
.B ok\c
|
||||
, indicating the feature is supported, or
|
||||
.B no\c
|
||||
, indicating no support for the feature.
|
||||
|
||||
In the case that
|
||||
.B version
|
||||
is the requested
|
||||
.I FEATURE-NAME\c
|
||||
, the version of
|
||||
.B mtr-packet
|
||||
is provided as the
|
||||
.I PRESENT
|
||||
value.
|
||||
|
||||
.SH EXAMPLE
|
||||
A controlling program may start
|
||||
.B mtr-packet
|
||||
as a child process and issue the following command on
|
||||
.I stdin\c
|
||||
:
|
||||
.LP
|
||||
.RS
|
||||
42 send-probe ip-4 127.0.0.1
|
||||
.RE
|
||||
.LP
|
||||
This will send a network probe to the loopback interface. When the probe
|
||||
completes,
|
||||
.B
|
||||
mtr-packet
|
||||
will provide a response on
|
||||
.I stdout
|
||||
such as the following:
|
||||
.LP
|
||||
.RS
|
||||
42 reply ip-4 127.0.0.1 round-trip-time 126
|
||||
.RE
|
||||
.LP
|
||||
This indicates that the loopback address replied to the probe, and the
|
||||
round-trip time of the probe was 126 microseconds.
|
||||
.SH CONTACT INFORMATION
|
||||
.PP
|
||||
For the latest version, see the mtr web page at
|
||||
.UR http://\:www.\:bitwizard.\:nl/\:mtr/
|
||||
.UE
|
||||
.PP
|
||||
The mtr mailinglist was little used and is no longer active.
|
||||
.PP
|
||||
For patches, bug reports, or feature requests, please open an issue on
|
||||
GitHub at:
|
||||
.UR https://\:github\:.com/\:traviscross/\:mtr
|
||||
.UE .
|
||||
.SH "SEE ALSO"
|
||||
.BR mtr (8),
|
||||
TCP/IP Illustrated (Stevens, ISBN 0201633469).
|
14
mtr.8.in
14
mtr.8.in
@ -448,8 +448,19 @@ passed in
|
||||
.B MTR_OPTIONS\c
|
||||
).
|
||||
.TP
|
||||
.B MTR_PACKET
|
||||
A path to the
|
||||
.I mtr-packet
|
||||
executable, to be used for sending and receiving network probes. If
|
||||
.B MTR_PACKET
|
||||
is unset, the
|
||||
.B PATH
|
||||
will be used to search for an
|
||||
.I mtr-packet
|
||||
executable.
|
||||
.TP
|
||||
.B DISPLAY
|
||||
Used for the GTK+ frontend.
|
||||
Specifies an X11 server for the GTK+ frontend.
|
||||
.SH BUGS
|
||||
Some modern routers give a lower priority to ICMP ECHO packets than
|
||||
to other network traffic. Consequently, the reliability of these
|
||||
@ -470,6 +481,7 @@ GitHub at:
|
||||
.UR https://\:github\:.com/\:traviscross/\:mtr
|
||||
.UE .
|
||||
.SH "SEE ALSO"
|
||||
.BR mtr-packet (8),
|
||||
.BR traceroute (8),
|
||||
.BR ping (8),
|
||||
.BR socket (7),
|
||||
|
32
mtr.bat
Executable file
32
mtr.bat
Executable file
@ -0,0 +1,32 @@
|
||||
@echo off
|
||||
rem
|
||||
rem mtr -- a network diagnostic tool
|
||||
rem Copyright (C) 2016 Matt Kimball
|
||||
rem
|
||||
rem This program is free software; you can redistribute it and/or modify
|
||||
rem it under the terms of the GNU General Public License version 2 as
|
||||
rem published by the Free Software Foundation.
|
||||
rem
|
||||
rem This program is distributed in the hope that it will be useful,
|
||||
rem but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
rem MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
rem GNU General Public License for more details.
|
||||
rem
|
||||
rem You should have received a copy of the GNU General Public License
|
||||
rem along with this program; if not, write to the Free Software
|
||||
rem Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
rem
|
||||
|
||||
rem Assume the path of this batch file is the mtr installation location
|
||||
set MTR_DIR=%~dp0
|
||||
|
||||
set MTR_BIN=%MTR_DIR%\bin
|
||||
|
||||
rem ncurses needs to locate the cygwin terminfo file
|
||||
set TERMINFO=%MTR_DIR%\terminfo
|
||||
|
||||
rem mtr needs to know the location to the packet generator
|
||||
set MTR_PACKET=%MTR_BIN%\mtr-packet.exe
|
||||
|
||||
rem Pass along commandline arguments
|
||||
%MTR_BIN%\mtr %*
|
50
mtr.c
50
mtr.c
@ -41,7 +41,6 @@
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <time.h>
|
||||
#include <ctype.h>
|
||||
#include <assert.h>
|
||||
#include <fcntl.h>
|
||||
@ -623,7 +622,6 @@ static void init_rand(void)
|
||||
extern int main(int argc, char **argv)
|
||||
{
|
||||
struct hostent * host = NULL;
|
||||
int net_preopen_result;
|
||||
struct addrinfo hints, *res;
|
||||
int gai_error;
|
||||
struct hostent trhost;
|
||||
@ -632,7 +630,6 @@ extern int main(int argc, char **argv)
|
||||
#ifdef ENABLE_IPV6
|
||||
struct sockaddr_in6 * sa6;
|
||||
#endif
|
||||
time_t now;
|
||||
names_t *names_root = NULL;
|
||||
names_t **names_head = &names_root;
|
||||
|
||||
@ -656,20 +653,13 @@ extern int main(int argc, char **argv)
|
||||
ctl.ipinfo_max = -1;
|
||||
xstrncpy(ctl.fld_active, "LS NABWV", 2 * MAXFLD);
|
||||
|
||||
/* Get the raw sockets first thing, so we can drop to user euid immediately */
|
||||
|
||||
if ( ( net_preopen_result = net_preopen () ) ) {
|
||||
error(EXIT_FAILURE, errno, "Unable to get raw sockets");
|
||||
}
|
||||
|
||||
/* Now drop to user permissions */
|
||||
if (setgid(getgid()) || setuid(getuid())) {
|
||||
error(EXIT_FAILURE, errno, "Unable to drop permissions");
|
||||
}
|
||||
|
||||
/* Double check, just in case */
|
||||
/*
|
||||
mtr used to be suid root. It should not be with this version.
|
||||
We'll check so that we can notify people using installation
|
||||
mechanisms with obsolete assumptions.
|
||||
*/
|
||||
if ((geteuid() != getuid()) || (getegid() != getgid())) {
|
||||
error(EXIT_FAILURE, errno, "Unable to drop permissions");
|
||||
error(EXIT_FAILURE, errno, "mtr should not run suid");
|
||||
}
|
||||
|
||||
/* This will check if stdout/stderr writing is successful */
|
||||
@ -694,11 +684,6 @@ extern int main(int argc, char **argv)
|
||||
append_to_names(names_head, name);
|
||||
}
|
||||
|
||||
/* Now that we know mtrtype we can select which socket to use */
|
||||
if (net_selectsocket(&ctl) != 0) {
|
||||
error(EXIT_FAILURE, 0, "Couldn't determine raw socket type");
|
||||
}
|
||||
|
||||
if (!names_root) append_to_names (names_head, "localhost"); /* default: localhost. */
|
||||
|
||||
names_head = &names_root;
|
||||
@ -709,16 +694,6 @@ extern int main(int argc, char **argv)
|
||||
xstrncpy(ctl.LocalHostname, "UNKNOWNHOST", sizeof(ctl.LocalHostname));
|
||||
}
|
||||
|
||||
if (net_preopen_result != 0) {
|
||||
error(0, 0, "Unable to get raw socket. (Executable not suid?)");
|
||||
if (ctl.DisplayMode != DisplayCSV)
|
||||
exit(EXIT_FAILURE);
|
||||
else {
|
||||
names_root = names_root->next;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* gethostbyname2() is deprecated so we'll use getaddrinfo() instead. */
|
||||
memset( &hints, 0, sizeof hints );
|
||||
hints.ai_family = ctl.af;
|
||||
@ -777,16 +752,6 @@ extern int main(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
if (net_set_interfaceaddress (&ctl) != 0) {
|
||||
error(0, 0, "Couldn't set interface address: %s", ctl.InterfaceAddress);
|
||||
if (ctl.DisplayMode != DisplayCSV)
|
||||
exit(EXIT_FAILURE);
|
||||
else {
|
||||
names_root = names_root->next;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
lock(stdout);
|
||||
dns_open(&ctl);
|
||||
@ -795,8 +760,7 @@ extern int main(int argc, char **argv)
|
||||
display_loop(&ctl);
|
||||
|
||||
net_end_transit();
|
||||
now = time(NULL);
|
||||
display_close(&ctl, now);
|
||||
display_close(&ctl);
|
||||
unlock(stdout);
|
||||
|
||||
if (ctl.DisplayMode != DisplayCSV)
|
||||
|
4
net.h
4
net.h
@ -30,11 +30,8 @@
|
||||
|
||||
#include "mtr.h"
|
||||
|
||||
extern int net_preopen(void);
|
||||
extern int net_selectsocket(struct mtr_ctl *ctl);
|
||||
extern int net_open(struct mtr_ctl *ctl, struct hostent *host);
|
||||
extern void net_reopen(struct mtr_ctl *ctl, struct hostent *address);
|
||||
extern int net_set_interfaceaddress (struct mtr_ctl *ctl);
|
||||
extern void net_reset(struct mtr_ctl *ctl);
|
||||
extern void net_close(void);
|
||||
extern int net_waitfd(void);
|
||||
@ -79,4 +76,3 @@ extern int addrcmp( char * a, char * b, int af );
|
||||
extern void addrcpy( char * a, char * b, int af );
|
||||
|
||||
extern void net_add_fds(fd_set *writefd, int *maxfd);
|
||||
extern void net_process_fds(struct mtr_ctl *ctl, fd_set *writefd);
|
||||
|
125
packet/cmdparse.c
Normal file
125
packet/cmdparse.c
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
mtr -- a network diagnostic tool
|
||||
Copyright (C) 2016 Matt Kimball
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "cmdparse.h"
|
||||
|
||||
#include <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;
|
||||
}
|
47
packet/cmdparse.h
Normal file
47
packet/cmdparse.h
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
mtr -- a network diagnostic tool
|
||||
Copyright (C) 2016 Matt Kimball
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#ifndef CMDPARSE_H
|
||||
#define CMDPARSE_H
|
||||
|
||||
#define MAX_COMMAND_ARGUMENTS 16
|
||||
|
||||
/* Parsed commands, or command replies, ready for semantic interpretation */
|
||||
struct command_t
|
||||
{
|
||||
/* A unique value for matching command requests with replies */
|
||||
int token;
|
||||
|
||||
/* Text indiciating the command type, or reply type */
|
||||
char *command_name;
|
||||
|
||||
/* The number of key, value argument pairs used */
|
||||
int argument_count;
|
||||
|
||||
/* Names for each argument */
|
||||
char *argument_name[MAX_COMMAND_ARGUMENTS];
|
||||
|
||||
/* Values for each argument, parallel to the argument_name array */
|
||||
char *argument_value[MAX_COMMAND_ARGUMENTS];
|
||||
};
|
||||
|
||||
int parse_command(
|
||||
struct command_t *command,
|
||||
char *command_string);
|
||||
|
||||
#endif
|
242
packet/command.c
Normal file
242
packet/command.c
Normal file
@ -0,0 +1,242 @@
|
||||
/*
|
||||
mtr -- a network diagnostic tool
|
||||
Copyright (C) 2016 Matt Kimball
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "command.h"
|
||||
|
||||
#include <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;
|
||||
}
|
||||
}
|
60
packet/command.h
Normal file
60
packet/command.h
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
mtr -- a network diagnostic tool
|
||||
Copyright (C) 2016 Matt Kimball
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#ifndef COMMAND_H
|
||||
#define COMMAND_H
|
||||
|
||||
#include "platform.h"
|
||||
#include "probe.h"
|
||||
|
||||
#define COMMAND_BUFFER_SIZE 4096
|
||||
|
||||
#ifdef PLATFORM_CYGWIN
|
||||
#include "command_cygwin.h"
|
||||
#else
|
||||
#include "command_unix.h"
|
||||
#endif
|
||||
|
||||
/* Storage for incoming commands, prior to command parsing */
|
||||
struct command_buffer_t
|
||||
{
|
||||
/* The file descriptor of the incoming command stream */
|
||||
int command_stream;
|
||||
|
||||
/* Storage to read commands into */
|
||||
char incoming_buffer[COMMAND_BUFFER_SIZE];
|
||||
|
||||
/* The number of bytes read so far in incoming_buffer */
|
||||
int incoming_read_position;
|
||||
|
||||
/* Platform specific */
|
||||
struct command_buffer_platform_t platform;
|
||||
};
|
||||
|
||||
void init_command_buffer(
|
||||
struct command_buffer_t *command_buffer,
|
||||
int command_stream);
|
||||
|
||||
int read_commands(
|
||||
struct command_buffer_t *buffer);
|
||||
|
||||
void dispatch_buffer_commands(
|
||||
struct command_buffer_t *buffer,
|
||||
struct net_state_t *net_state);
|
||||
|
||||
#endif
|
153
packet/command_cygwin.c
Normal file
153
packet/command_cygwin.c
Normal file
@ -0,0 +1,153 @@
|
||||
/*
|
||||
mtr -- a network diagnostic tool
|
||||
Copyright (C) 2016 Matt Kimball
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "command.h"
|
||||
|
||||
#include <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;
|
||||
}
|
48
packet/command_cygwin.h
Normal file
48
packet/command_cygwin.h
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
mtr -- a network diagnostic tool
|
||||
Copyright (C) 2016 Matt Kimball
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#ifndef COMMAND_CYGWIN_H
|
||||
#define COMMAND_CYGWIN_H
|
||||
|
||||
/*
|
||||
Though Cygwin supports the usual Unix non-blocking reads on
|
||||
the command stream, we've got to use Overlapped I/O instead because
|
||||
ICMP.DLL's support for sending probes requires Overlapped I/O
|
||||
and alertable waits for notification of replies. Since we need
|
||||
alertable waits, we can't use Cygwin's select to determine when
|
||||
command stream data is available, but Overlapped I/O completion
|
||||
will work.
|
||||
*/
|
||||
|
||||
/* Overlapped I/O manament for Windows command buffer reads */
|
||||
struct command_buffer_platform_t
|
||||
{
|
||||
/* true if an overlapped I/O read is active */
|
||||
bool read_active;
|
||||
|
||||
/* true if the command pipe is still open */
|
||||
bool pipe_open;
|
||||
|
||||
/* Windows OVERLAPPED I/O data */
|
||||
OVERLAPPED overlapped;
|
||||
|
||||
/* The buffer which active OVERLAPPED reads read into */
|
||||
char overlapped_buffer[COMMAND_BUFFER_SIZE];
|
||||
};
|
||||
|
||||
#endif
|
89
packet/command_unix.c
Normal file
89
packet/command_unix.c
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
mtr -- a network diagnostic tool
|
||||
Copyright (C) 2016 Matt Kimball
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "command.h"
|
||||
|
||||
#include <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;
|
||||
}
|
27
packet/command_unix.h
Normal file
27
packet/command_unix.h
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
mtr -- a network diagnostic tool
|
||||
Copyright (C) 2016 Matt Kimball
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#ifndef COMMAND_UNIX_H
|
||||
#define COMMAND_UNIX_H
|
||||
|
||||
/* No platform specific data is required for Unix command streams */
|
||||
struct command_buffer_platform_t
|
||||
{
|
||||
};
|
||||
|
||||
#endif
|
9
packet/lint.sh
Executable file
9
packet/lint.sh
Executable file
@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Check the Python test source for good style
|
||||
|
||||
PYTHON_SOURCE=testpacket.py
|
||||
|
||||
pep8 $PYTHON_SOURCE
|
||||
pylint --reports=n $PYTHON_SOURCE 2>/dev/null
|
||||
mypy --py2 $PYTHON_SOURCE
|
103
packet/packet.c
Normal file
103
packet/packet.c
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
mtr -- a network diagnostic tool
|
||||
Copyright (C) 2016 Matt Kimball
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include <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;
|
||||
}
|
53
packet/platform.h
Normal file
53
packet/platform.h
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
mtr -- a network diagnostic tool
|
||||
Copyright (C) 2016 Matt Kimball
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#ifndef PLATFORM_H
|
||||
#define PLATFORM_H
|
||||
|
||||
/*
|
||||
Determine the most appropriate PLATFORM_* define for our
|
||||
current target.
|
||||
*/
|
||||
|
||||
#if defined(__CYGWIN__)
|
||||
|
||||
#define PLATFORM_CYGWIN
|
||||
|
||||
#elif defined(__APPLE__) && defined(__MACH__)
|
||||
|
||||
#define PLATFORM_OS_X
|
||||
|
||||
#elif defined(__gnu_linux__)
|
||||
|
||||
#define PLATFORM_LINUX
|
||||
|
||||
#elif defined (__FreeBSD__)
|
||||
|
||||
#define PLATFORM_FREEBSD
|
||||
|
||||
#elif defined(__unix__)
|
||||
|
||||
#define PLATFORM_UNIX_UNKNOWN
|
||||
|
||||
#else
|
||||
|
||||
#error Unsupported platform
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
176
packet/probe.c
Normal file
176
packet/probe.c
Normal file
@ -0,0 +1,176 @@
|
||||
/*
|
||||
mtr -- a network diagnostic tool
|
||||
Copyright (C) 2016 Matt Kimball
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "probe.h"
|
||||
|
||||
#include <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);
|
||||
}
|
117
packet/probe.h
Normal file
117
packet/probe.h
Normal file
@ -0,0 +1,117 @@
|
||||
/*
|
||||
mtr -- a network diagnostic tool
|
||||
Copyright (C) 2016 Matt Kimball
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#ifndef PROBE_H
|
||||
#define PROBE_H
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
#include <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
|
181
packet/probe_cygwin.c
Normal file
181
packet/probe_cygwin.c
Normal file
@ -0,0 +1,181 @@
|
||||
/*
|
||||
mtr -- a network diagnostic tool
|
||||
Copyright (C) 2016 Matt Kimball
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "probe.h"
|
||||
|
||||
#include <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;
|
||||
}
|
42
packet/probe_cygwin.h
Normal file
42
packet/probe_cygwin.h
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
mtr -- a network diagnostic tool
|
||||
Copyright (C) 2016 Matt Kimball
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#ifndef PROBE_CYGWIN_H
|
||||
#define PROBE_CYGWIN_H
|
||||
|
||||
#include <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
|
526
packet/probe_unix.c
Normal file
526
packet/probe_unix.c
Normal file
@ -0,0 +1,526 @@
|
||||
/*
|
||||
mtr -- a network diagnostic tool
|
||||
Copyright (C) 2016 Matt Kimball
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "probe.h"
|
||||
|
||||
#include <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;
|
||||
}
|
49
packet/probe_unix.h
Normal file
49
packet/probe_unix.h
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
mtr -- a network diagnostic tool
|
||||
Copyright (C) 2016 Matt Kimball
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#ifndef PROBE_UNIX_H
|
||||
#define PROBE_UNIX_H
|
||||
|
||||
/* We need to track the transmission and timeouts on Unix systems */
|
||||
struct probe_platform_t
|
||||
{
|
||||
/* The time at which the probe is considered lost */
|
||||
struct timeval timeout_time;
|
||||
|
||||
/* The time at which the probe was sent */
|
||||
struct timeval departure_time;
|
||||
|
||||
};
|
||||
|
||||
/* We'll use rack sockets to send and recieve probes on Unix systems */
|
||||
struct net_state_platform_t
|
||||
{
|
||||
/* Socket used to send raw packets */
|
||||
int ipv4_send_socket;
|
||||
|
||||
/* Socket used to receive ICMP replies */
|
||||
int ipv4_recv_socket;
|
||||
|
||||
/*
|
||||
true if we should encode the IP header length in host order.
|
||||
(as opposed to network order)
|
||||
*/
|
||||
bool ip_length_host_order;
|
||||
};
|
||||
|
||||
#endif
|
84
packet/protocols.h
Normal file
84
packet/protocols.h
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
mtr -- a network diagnostic tool
|
||||
Copyright (C) 2016 Matt Kimball
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#ifndef PROTOCOLS_H
|
||||
#define PROTOCOLS_H
|
||||
|
||||
/* ICMP type codes */
|
||||
#define ICMP_ECHOREPLY 0
|
||||
#define ICMP_DEST_UNREACH 3
|
||||
#define ICMP_ECHO 8
|
||||
#define ICMP_TIME_EXCEEDED 11
|
||||
|
||||
/* We can't rely on header files to provide this information, because
|
||||
the fields have different names between, for instance, Linux and
|
||||
Solaris */
|
||||
struct ICMPHeader {
|
||||
uint8_t type;
|
||||
uint8_t code;
|
||||
uint16_t checksum;
|
||||
uint16_t id;
|
||||
uint16_t sequence;
|
||||
};
|
||||
|
||||
/* Structure of an UDP header. */
|
||||
struct UDPHeader {
|
||||
uint16_t srcport;
|
||||
uint16_t dstport;
|
||||
uint16_t length;
|
||||
uint16_t checksum;
|
||||
};
|
||||
|
||||
/* Structure of an TCP header, as far as we need it. */
|
||||
struct TCPHeader {
|
||||
uint16_t srcport;
|
||||
uint16_t dstport;
|
||||
uint32_t seq;
|
||||
};
|
||||
|
||||
/* Structure of an SCTP header */
|
||||
struct SCTPHeader {
|
||||
uint16_t srcport;
|
||||
uint16_t dstport;
|
||||
uint32_t veri_tag;
|
||||
};
|
||||
|
||||
/* Structure of an IPv4 UDP pseudoheader. */
|
||||
struct UDPv4PHeader {
|
||||
uint32_t saddr;
|
||||
uint32_t daddr;
|
||||
uint8_t zero;
|
||||
uint8_t protocol;
|
||||
uint16_t len;
|
||||
};
|
||||
|
||||
/* Structure of an IP header. */
|
||||
struct IPHeader {
|
||||
uint8_t version;
|
||||
uint8_t tos;
|
||||
uint16_t len;
|
||||
uint16_t id;
|
||||
uint16_t frag;
|
||||
uint8_t ttl;
|
||||
uint8_t protocol;
|
||||
uint16_t check;
|
||||
uint32_t saddr;
|
||||
uint32_t daddr;
|
||||
};
|
||||
|
||||
#endif
|
348
packet/testpacket.py
Executable file
348
packet/testpacket.py
Executable file
@ -0,0 +1,348 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# mtr -- a network diagnostic tool
|
||||
# Copyright (C) 2016 Matt Kimball
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License version 2 as
|
||||
# published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
#
|
||||
|
||||
|
||||
'''Test mtr-packet's functionality
|
||||
|
||||
Test the ability to send probes and receive replies using mtr-packet.
|
||||
'''
|
||||
|
||||
# pylint: disable=locally-disabled, import-error
|
||||
import fcntl
|
||||
import os
|
||||
import re
|
||||
import select
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import unittest
|
||||
|
||||
|
||||
class ReadReplyTimeout(Exception):
|
||||
'Exception raised by TestProbe.read_reply upon timeout'
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class TestProbe(unittest.TestCase):
|
||||
'Test cases for sending and receiving probes'
|
||||
|
||||
def __init__(self, *args):
|
||||
self.reply_buffer = None # type: str
|
||||
self.packet_process = None # type: subprocess.Popen
|
||||
self.stdout_fd = None # type: int
|
||||
|
||||
super(TestProbe, self).__init__(*args)
|
||||
|
||||
def setUp(self):
|
||||
'Set up a test case by spawning a mtr-packet process'
|
||||
|
||||
packet_path = os.environ.get('MTR_PACKET', './mtr-packet')
|
||||
|
||||
self.reply_buffer = ''
|
||||
self.packet_process = subprocess.Popen(
|
||||
[packet_path],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE)
|
||||
|
||||
# Put the mtr-packet process's stdout in non-blocking mode
|
||||
# so that we can read from it without a timeout when
|
||||
# no reply is available.
|
||||
self.stdout_fd = self.packet_process.stdout.fileno()
|
||||
flags = fcntl.fcntl(self.stdout_fd, fcntl.F_GETFL)
|
||||
|
||||
# pylint: disable=locally-disabled, no-member
|
||||
fcntl.fcntl(self.stdout_fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
|
||||
|
||||
def tearDown(self):
|
||||
'After a test, kill the running mtr-packet instance'
|
||||
|
||||
try:
|
||||
self.packet_process.kill()
|
||||
except OSError:
|
||||
return
|
||||
|
||||
def write_command(self, cmd): # type: (str) -> None
|
||||
'Send a command string to the mtr-packet instance'
|
||||
|
||||
self.packet_process.stdin.write(cmd + '\n')
|
||||
self.packet_process.stdin.flush()
|
||||
|
||||
def read_reply(self, timeout=10.0): # type: (float) -> str
|
||||
'''Read the next reply from mtr-packet.
|
||||
|
||||
Attempt to read the next command reply from mtr-packet. If no reply
|
||||
is available withing the timeout time, raise ReadReplyTimeout
|
||||
instead.'''
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
# Read from mtr-packet until either the timeout time has elapsed
|
||||
# or we read a newline character, which indicates a finished
|
||||
# reply.
|
||||
while True:
|
||||
now = time.time()
|
||||
elapsed = now - start_time
|
||||
|
||||
select_time = timeout - elapsed
|
||||
if select_time < 0:
|
||||
select_time = 0
|
||||
|
||||
select.select([self.stdout_fd], [], [], select_time)
|
||||
|
||||
try:
|
||||
self.reply_buffer += os.read(self.stdout_fd, 1024)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
# If we have read a newline character, we can stop waiting
|
||||
# for more input.
|
||||
newline_ix = self.reply_buffer.find('\n')
|
||||
if newline_ix != -1:
|
||||
break
|
||||
|
||||
if elapsed >= timeout:
|
||||
raise ReadReplyTimeout()
|
||||
|
||||
reply = self.reply_buffer[:newline_ix]
|
||||
self.reply_buffer = self.reply_buffer[newline_ix + 1:]
|
||||
return reply
|
||||
|
||||
def test_unknown_command(self):
|
||||
'Test sending a command unknown to mtr-packet'
|
||||
|
||||
self.write_command('13 argle-bargle')
|
||||
self.assertEqual(self.read_reply(), '13 unknown-command')
|
||||
|
||||
def test_malformed_command(self):
|
||||
'Test sending a malformed command request to mtr-packet'
|
||||
|
||||
self.write_command('malformed')
|
||||
self.assertEqual(self.read_reply(), '0 command-parse-error')
|
||||
|
||||
def test_exit_on_stdin_closed(self):
|
||||
'''Test that the packet process terminates after stdin is closed
|
||||
|
||||
Test that, when outstanding requests are complete, the process
|
||||
terminates following stdin being closed.'''
|
||||
|
||||
self.write_command('15 send-probe ip-4 8.8.254.254 timeout 1')
|
||||
self.packet_process.stdin.close()
|
||||
time.sleep(2)
|
||||
self.read_reply()
|
||||
exit_code = self.packet_process.poll()
|
||||
self.assertIsNotNone(exit_code)
|
||||
|
||||
def test_probe(self):
|
||||
'Test sending regular ICMP probes to known addresses'
|
||||
|
||||
reply_regex = r'^14 reply ip-4 8.8.8.8 round-trip-time [0-9]+$'
|
||||
|
||||
# Probe Google's well-known DNS server and expect a reply
|
||||
self.write_command('14 send-probe ip-4 8.8.8.8')
|
||||
reply = self.read_reply()
|
||||
match = re.match(reply_regex, reply)
|
||||
self.assertIsNotNone(match)
|
||||
|
||||
def test_invalid_argument(self):
|
||||
'Test sending invalid arguments with probe requests'
|
||||
|
||||
invalid_argument_regex = r'^[0-9]+ invalid-argument$'
|
||||
|
||||
bad_commands = [
|
||||
'22 send-probe',
|
||||
'23 send-probe ip-4 str-value',
|
||||
'24 send-probe ip-4 8.8.8.8 timeout str-value',
|
||||
'25 send-probe ip-4 8.8.8.8 ttl str-value',
|
||||
]
|
||||
|
||||
for cmd in bad_commands:
|
||||
self.write_command(cmd)
|
||||
reply = self.read_reply()
|
||||
match = re.match(invalid_argument_regex, reply)
|
||||
self.assertIsNotNone(match)
|
||||
|
||||
def test_timeout(self):
|
||||
'Test timeouts when sending to a non-existant address'
|
||||
|
||||
no_reply_regex = r'^15 no-reply$'
|
||||
|
||||
#
|
||||
# Probe a non-existant address, and expect no reply
|
||||
#
|
||||
# I'm not sure what the best way to find an address that doesn't
|
||||
# exist, but is still route-able. If we use a reserved IP
|
||||
# address range, Windows will tell us it is non-routeable,
|
||||
# rather than timing out when transmitting to that address.
|
||||
#
|
||||
# We're just using a currently unused address in Google's
|
||||
# range instead. This is probably not the best solution.
|
||||
#
|
||||
|
||||
# pylint: disable=locally-disabled, unused-variable
|
||||
for i in range(16):
|
||||
self.write_command('15 send-probe ip-4 8.8.254.254 timeout 1')
|
||||
reply = self.read_reply()
|
||||
match = re.match(no_reply_regex, reply)
|
||||
self.assertIsNotNone(match)
|
||||
|
||||
def test_exhaust_probes(self):
|
||||
'Test exhausting all available probes'
|
||||
|
||||
exhausted_regex = r'^[0-9]+ probes-exhausted$'
|
||||
|
||||
match = None
|
||||
probe_count = 4 * 1024
|
||||
id = 1024
|
||||
for i in range(probe_count):
|
||||
command = str(id) + ' send-probe ip-4 8.8.254.254 timeout 60'
|
||||
id += 1
|
||||
self.write_command(command)
|
||||
|
||||
reply = None
|
||||
try:
|
||||
reply = self.read_reply(0)
|
||||
except ReadReplyTimeout:
|
||||
pass
|
||||
|
||||
if reply:
|
||||
match = re.match(exhausted_regex, reply)
|
||||
if match:
|
||||
break
|
||||
|
||||
self.assertIsNotNone(match)
|
||||
|
||||
def test_timeout_values(self):
|
||||
'''Test that timeout values wait the right amount of time
|
||||
|
||||
Give each probe a half-second grace period to probe a timeout
|
||||
reply after the expected timeout time.'''
|
||||
|
||||
begin = time.time()
|
||||
self.write_command('19 send-probe ip-4 8.8.254.254 timeout 0')
|
||||
self.read_reply()
|
||||
elapsed = time.time() - begin
|
||||
self.assertLess(elapsed, 0.5)
|
||||
|
||||
begin = time.time()
|
||||
self.write_command('20 send-probe ip-4 8.8.254.254 timeout 1')
|
||||
self.read_reply()
|
||||
elapsed = time.time() - begin
|
||||
self.assertGreaterEqual(elapsed, 1.0)
|
||||
self.assertLess(elapsed, 1.5)
|
||||
|
||||
begin = time.time()
|
||||
self.write_command('21 send-probe ip-4 8.8.254.254 timeout 3')
|
||||
self.read_reply()
|
||||
elapsed = time.time() - begin
|
||||
self.assertGreaterEqual(elapsed, 3.0)
|
||||
self.assertLess(elapsed, 3.5)
|
||||
|
||||
def test_ttl_expired(self):
|
||||
'Test sending a probe which will have its time-to-live expire'
|
||||
|
||||
ttl_expired_regex = \
|
||||
r'^16 ttl-expired ip-4 [0-9\.]+ round-trip-time [0-9]+$'
|
||||
|
||||
# Probe Goolge's DNS server, but give the probe only one hop
|
||||
# to live.
|
||||
self.write_command('16 send-probe ip-4 8.8.8.8 ttl 1')
|
||||
reply = self.read_reply()
|
||||
match = re.match(ttl_expired_regex, reply)
|
||||
self.assertIsNotNone(match)
|
||||
|
||||
def test_parallel_probes(self):
|
||||
'''Test sending multiple probes in parallel
|
||||
|
||||
We will expect the probes to complete out-of-order by sending
|
||||
a probe to a distant host immeidately followed by a probe to
|
||||
the local host.'''
|
||||
|
||||
reply_regex = \
|
||||
r'^[0-9]+ reply ip-4 [0-9\.]+ round-trip-time ([0-9]+)$'
|
||||
|
||||
success_count = 0
|
||||
loop_count = 32
|
||||
|
||||
# pylint: disable=locally-disabled, unused-variable
|
||||
for i in range(loop_count):
|
||||
# Probe the distant host before the local host.
|
||||
self.write_command('17 send-probe ip-4 8.8.8.8 timeout 1')
|
||||
self.write_command('18 send-probe ip-4 127.0.0.1 timeout 1')
|
||||
|
||||
reply = self.read_reply()
|
||||
match = re.match(reply_regex, reply)
|
||||
if not match:
|
||||
continue
|
||||
first_time = int(match.group(1))
|
||||
|
||||
reply = self.read_reply()
|
||||
match = re.match(reply_regex, reply)
|
||||
if not match:
|
||||
continue
|
||||
second_time = int(match.group(1))
|
||||
|
||||
# Ensure we got a reply from the host with the lowest latency
|
||||
# first.
|
||||
self.assertLess(first_time, second_time)
|
||||
|
||||
success_count += 1
|
||||
|
||||
# We need 95% success to pass. This allows a few probes to be
|
||||
# occasionally dropped by the network without failing the test.
|
||||
required_success = int(loop_count * 0.95)
|
||||
self.assertGreaterEqual(success_count, required_success)
|
||||
|
||||
def test_versioning(self):
|
||||
'Test version checks and feature support checks'
|
||||
|
||||
feature_tests = [
|
||||
('30 check-support feature version',
|
||||
r'^30 feature-support support [0-9]+\.[0-9a-z\-\.]+$'),
|
||||
('31 check-support feature ip-4',
|
||||
r'^31 feature-support support ok$'),
|
||||
('32 check-support feature send-probe',
|
||||
r'^32 feature-support support ok$'),
|
||||
('33 check-support feature bogus-feature',
|
||||
r'^33 feature-support support no$')
|
||||
]
|
||||
|
||||
for (request, regex) in feature_tests:
|
||||
self.write_command(request)
|
||||
reply = self.read_reply()
|
||||
match = re.match(regex, reply)
|
||||
self.assertIsNotNone(match)
|
||||
|
||||
def test_command_overflow(self):
|
||||
'Test overflowing the incoming command buffer'
|
||||
|
||||
big_buffer = 'x' * (64 * 1024)
|
||||
self.write_command(big_buffer)
|
||||
|
||||
reply = self.read_reply()
|
||||
self.assertEqual(reply, '0 command-buffer-overflow')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# pylint: disable=locally-disabled, no-member
|
||||
if sys.platform != 'cygwin' and os.getuid() > 0:
|
||||
sys.stderr.write(
|
||||
"Warning: Many tests require running as root\n")
|
||||
|
||||
unittest.main()
|
77
packet/timeval.c
Normal file
77
packet/timeval.c
Normal file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
mtr -- a network diagnostic tool
|
||||
Copyright (C) 2016 Matt Kimball
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "timeval.h"
|
||||
|
||||
/*
|
||||
Ensure that a timevalue has a microsecond value in the range
|
||||
[0.0, 1.0e6) microseconds by converting microseconds to full seconds.
|
||||
*/
|
||||
void normalize_timeval(
|
||||
struct timeval *timeval)
|
||||
{
|
||||
int full_sec;
|
||||
|
||||
/*
|
||||
If tv_usec has overflowed a full second, convert the overflow
|
||||
to tv_sec.
|
||||
*/
|
||||
full_sec = timeval->tv_usec / 1000000;
|
||||
timeval->tv_sec += full_sec;
|
||||
timeval->tv_usec -= 1000000 * full_sec;
|
||||
|
||||
/* If tv_usec is negative, make it positive by rolling tv_sec back */
|
||||
if (timeval->tv_usec < 0) {
|
||||
timeval->tv_sec--;
|
||||
timeval->tv_usec += 1000000;
|
||||
}
|
||||
|
||||
/* If the entire time value is negative, clamp to zero */
|
||||
if (timeval->tv_sec < 0) {
|
||||
timeval->tv_sec = 0;
|
||||
timeval->tv_usec = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Compare two time values. Return:
|
||||
|
||||
-1 if a < b
|
||||
0 if a == b
|
||||
1 if a > b
|
||||
*/
|
||||
int compare_timeval(
|
||||
struct timeval a,
|
||||
struct timeval b)
|
||||
{
|
||||
if (a.tv_sec > b.tv_sec) {
|
||||
return 1;
|
||||
}
|
||||
if (a.tv_sec < b.tv_sec) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a.tv_usec > b.tv_usec) {
|
||||
return 1;
|
||||
}
|
||||
if (a.tv_usec < b.tv_usec) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
31
packet/timeval.h
Normal file
31
packet/timeval.h
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
mtr -- a network diagnostic tool
|
||||
Copyright (C) 2016 Matt Kimball
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#ifndef TIMEVAL_H
|
||||
#define TIMEVAL_H
|
||||
|
||||
#include <sys/time.h>
|
||||
|
||||
void normalize_timeval(
|
||||
struct timeval *timeval);
|
||||
|
||||
int compare_timeval(
|
||||
struct timeval a,
|
||||
struct timeval b);
|
||||
|
||||
#endif
|
29
packet/wait.h
Normal file
29
packet/wait.h
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
mtr -- a network diagnostic tool
|
||||
Copyright (C) 2016 Matt Kimball
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#ifndef WAIT_H
|
||||
#define WAIT_H
|
||||
|
||||
#include "command.h"
|
||||
#include "probe.h"
|
||||
|
||||
void wait_for_activity(
|
||||
const struct command_buffer_t *command_buffer,
|
||||
const struct net_state_t *net_state);
|
||||
|
||||
#endif
|
44
packet/wait_cygwin.c
Normal file
44
packet/wait_cygwin.c
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
mtr -- a network diagnostic tool
|
||||
Copyright (C) 2016 Matt Kimball
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "wait.h"
|
||||
|
||||
#include <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);
|
||||
}
|
||||
}
|
86
packet/wait_unix.c
Normal file
86
packet/wait_unix.c
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
mtr -- a network diagnostic tool
|
||||
Copyright (C) 2016 Matt Kimball
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "wait.h"
|
||||
|
||||
#include <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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user