]> git.ipfire.org Git - thirdparty/mtr.git/commitdiff
mtr-packet: packet customization options (size, fill, mark, tos)
authorMatt Kimball <matt.kimball@gmail.com>
Sat, 17 Dec 2016 15:24:07 +0000 (07:24 -0800)
committerMatt Kimball <matt.kimball@gmail.com>
Sat, 17 Dec 2016 15:24:07 +0000 (07:24 -0800)
Added send-probe arguments to customize the packet size,
the bitpattern which fills the packet, the IP "type of service"
and the Linux routing mark.  These arguments are documented
in the mtr-packet man page, and have been hooked up to mtr's
commandline parameters.

Internally to mtr-packet, the code is now more consistent about
using errno to return error codes.

Added instrutions for building on Windows to the README.

Fixed the Makefile to build the man pages with BSD make, for FreeBSD.
BSD make lacks the GNU make extension for '%' wildcard matching.

Added a helper program for testing which listens for an ICMP
packet with a particular sequence and prints information about
that packet.

20 files changed:
Makefile.am
README
mangen.sh [new file with mode: 0755]
mtr-packet.8.in
net.c
packet/cmdparse.c
packet/command.c
packet/command_cygwin.c
packet/command_unix.c
packet/construct_unix.c
packet/packet.c
packet/probe.c
packet/probe.h
packet/probe_cygwin.c
packet/probe_cygwin.h
packet/probe_unix.c
test/mtrpacket.py
test/packet_listen.c [new file with mode: 0644]
test/param.py [new file with mode: 0755]
test/probe.py [changed mode: 0644->0755]

index 97ce4a1b884ac6d8bb289df66a22fe47af78d3d3..50e2970d5b5a5b1aeb8874a1eb8518c5cb229a8e 100644 (file)
@@ -7,6 +7,7 @@ EXTRA_DIST = \
 sbin_PROGRAMS = mtr mtr-packet
 TESTS = \
        test/cmdparse.py \
+       test/param.py \
        test/probe.py
 
 TEST_FILES = \
@@ -19,16 +20,18 @@ EXTRA_DIST += $(TEST_FILES)
 PATHFILES =
 CLEANFILES = $(PATHFILES)
 EXTRA_DIST += $(PATHFILES:=.in)
-edit_cmd = sed \
-       -e 's|@VERSION[@]|$(VERSION)|g'
 
-%.8: $(srcdir)/%.8.in
-       @ rm -f $@ $@.tmp
-       $(AM_V_at) $(MKDIR_P) $$(dirname $@)
-       $(AM_V_GEN) srcdir=''; \
-               test -f ./$@.in || srcdir=$(srcdir)/; \
-               $(edit_cmd) $${srcdir}$@.in >$@.tmp
-       @ mv $@.tmp $@
+#
+#  We would use % pattern matching here, but that is a GNU make
+#  extension and doesn't work on FreeBSD.
+#
+mtr-packet.8: $(srcdir)/mtr-packet.8.in
+       $(AM_V_GEN) $(srcdir)/mangen.sh "$(VERSION)" \
+               $(srcdir)/mtr-packet.8.in $@
+
+mtr.8: $(srcdir)/mtr.8.in
+       $(AM_V_GEN) $(srcdir)/mangen.sh "$(VERSION)" \
+               $(srcdir)/mtr.8.in $@
 
 $(PATHFILES): Makefile
 
@@ -128,6 +131,8 @@ dist-windows-bin: distdir-win
 
 else  # if CYGWIN
 
+check_PROGRAMS = mtr-packet-listen
+
 mtr_packet_SOURCES += \
        packet/command_unix.c packet/command_unix.h \
        packet/construct_unix.c packet/construct_unix.h \
@@ -135,6 +140,9 @@ mtr_packet_SOURCES += \
        packet/probe_unix.c packet/probe_unix.h \
        packet/wait_unix.c
 
+mtr_packet_listen_SOURCES = \
+       test/packet_listen.c
+
 endif  # if CYGWIN
 
 
diff --git a/README b/README
index a92763499190d921fc89dff4486c42290352cf5a..3ff0477b07dd3f1f46a56c0adcc6c3aa25869fa3 100644 (file)
--- a/README
+++ b/README
@@ -57,16 +57,28 @@ INSTALLING
   doing the linking into a terminal window, and add "-lcurses" by hand. 
   Then it will link. Help on how to catch this in autoconf appreciated.
 
-  On Mac OS X the nameserver8_compat.h needs to be included. I put the 
-  include inside an "#if 0" section in the file "dns.c". If someone 
-  knows how to make this automatic using autoconf / the configure script, 
-  please tell me.... 
+  Building on MacOS should not require any special steps.
 
-  This should now also work: 
-  ./configure CFLAGS="-arch i386 -arch x86_64" LIBS="-lresolv" \
-       --without-gtk --disable-endian-check --disable-dependency-tracking
+BUILDING FOR WINDOWS
 
+  Building for Windows requires Cygwin.  To obtain Cygwin, see
+  https://cygwin.com/install.html.  When installing Cygwin, select
+  the 'lynx' package for installation.  lynx is required by apt-cyg.
 
+  Next, install apt-cyg for easy installation of the remaining
+  components.  See https://github.com/transcode-open/apt-cyg.
+
+  Install the packages required for building:
+
+  apt-cyg install automake pkg-config make gcc-core libncurses-devel
+
+  Build as under Unix:
+
+  ./bootstrap.sh && ./configure && make
+
+  Finally, install the built binaries:
+
+  make install
 
 WHERE CAN I GET THE LATEST VERSION OR MORE INFORMATION?
 
diff --git a/mangen.sh b/mangen.sh
new file mode 100755 (executable)
index 0000000..2c12d87
--- /dev/null
+++ b/mangen.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+#
+#  Generate the man pages.
+#
+#  We are just here to substitute the @VERSION@ string with our real version.
+#
+
+if [ $# -lt 3 ]; then
+    echo Usage: mangen.sh VERSION IN OUT
+    exit 1
+fi
+
+sed -e "s|@VERSION[@]|$1|g" $2 >$3
index 463e962a63bb954e9570f1665d02df33f6a42f4e..31336f31412b1e127e9e7fddadb83c1ace5b7134 100644 (file)
@@ -131,6 +131,40 @@ 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.
 .HP 7
+.IP
+.B size
+.I PACKET-SIZE
+.HP 14
+.IP
+The size of the packet used to send the probe, in bytes, including the
+Internet Protocol header and transport protocol header.
+.HP 7
+.IP
+.B bitpattern
+.I PATTERN-VALUE
+.HP 14
+.IP
+The packet payload is filled with bytes of the value specified.
+Valid pattern values are in the range 0 through 255.
+.HP 7
+.IP
+.IP
+.B tos
+.I TYPE-OF-SERVICE
+.HP 14
+.IP
+In the case of IPv4, the "type of service" field in the IP header
+is set to this value.  In the case of IPv6, the "traffic class"
+field is set.
+.HP 7
+.IP
+.B mark
+.I ROUTING-MARK
+.HP 14
+.IP
+The packet mark value to be used by mark-based routing.
+(Available only on Linux.)
+.HP 7
 .TP
 .B check-support
 Check for support for a particular feature in this version of
@@ -154,8 +188,11 @@ The name of a feature requested.
 Some features which can be checked are
 .BR send-probe ,
 .BR ip-4 ,
+.BR ip-6 ,
+.BR icmp ,
+.BR udp ,
 and
-.BR ip-6 .
+.BR mark .
 The feature
 .B version
 can be checked to retrieve the version of
@@ -243,6 +280,10 @@ A probe could not be sent because the network is down.
 A probe could not be sent because there are already too many unresolved
 probes in flight.
 .TP
+.B permission-denied
+The operating system denied permission to send the probe with the
+specified options.
+.TP
 .B invalid-argument
 The command request contained arguments which are invalid.
 .TP
diff --git a/net.c b/net.c
index f413f89cb9eb33100a5b5c01b3e6639a00b00d69..abfffec519f0c4c32b7e0f299c0293040ad3d573 100644 (file)
--- a/net.c
+++ b/net.c
@@ -192,15 +192,10 @@ static int new_sequence(struct mtr_ctl *ctl, int index)
   return seq;
 }
 
-/*  Attempt to find the host at a particular number of hops away  */
-static void net_send_query(struct mtr_ctl *ctl, int index)
+static void net_construct_base_command(
+  struct mtr_ctl *ctl, char *command, int buffer_size, int command_token)
 {
-  int seq = new_sequence(ctl, index);
-  int time_to_live = index + 1;
   char ip_string[INET6_ADDRSTRLEN];
-  char command[COMMAND_BUFFER_SIZE];
-  char argument[COMMAND_BUFFER_SIZE];
-  int remaining_size;
   const char *ip_type;
   const char *protocol = NULL;
 
@@ -228,16 +223,56 @@ static void net_send_query(struct mtr_ctl *ctl, int index)
   }
 
   snprintf(
-    command, COMMAND_BUFFER_SIZE,
-    "%d send-probe %s %s protocol %s ttl %d",
-    seq, ip_type, ip_string, protocol, time_to_live);
+    command, buffer_size,
+    "%d send-probe %s %s protocol %s",
+    command_token, ip_type, ip_string, protocol);
+}
+
+static void net_append_command_argument(
+  char *command, int buffer_size, char *name, int value)
+{
+  char argument[COMMAND_BUFFER_SIZE];
+  int remaining_size;
+
+  remaining_size = buffer_size - strlen(command) - 1;
+
+  snprintf(argument, buffer_size, " %s %d", name, value);
+  strncat(command, argument, remaining_size);
+}
+
+/*  Attempt to find the host at a particular number of hops away  */
+static void net_send_query(struct mtr_ctl *ctl, int index, int packet_size)
+{
+  int seq = new_sequence(ctl, index);
+  int time_to_live = index + 1;
+  char command[COMMAND_BUFFER_SIZE];
+  int remaining_size;
+
+  net_construct_base_command(ctl, command, COMMAND_BUFFER_SIZE, seq);
+
+  net_append_command_argument(
+    command, COMMAND_BUFFER_SIZE, "size", packet_size);
+
+  net_append_command_argument(
+    command, COMMAND_BUFFER_SIZE, "bitpattern", ctl->bitpattern);
+
+  net_append_command_argument(
+    command, COMMAND_BUFFER_SIZE, "tos", ctl->tos);
+
+  net_append_command_argument(
+    command, COMMAND_BUFFER_SIZE, "ttl", time_to_live);
 
   if (ctl->remoteport) {
-    remaining_size = COMMAND_BUFFER_SIZE - strlen(command) - 1;
+    net_append_command_argument(
+      command, COMMAND_BUFFER_SIZE, "port", ctl->remoteport);
+  }
 
-    snprintf(argument, COMMAND_BUFFER_SIZE, " port %d", ctl->remoteport);
-    strncat(command, argument, remaining_size);
+#ifdef SO_MARK
+  if (ctl->mark) {
+    net_append_command_argument(
+      command, COMMAND_BUFFER_SIZE, "mark", ctl->mark);
   }
+#endif
 
   remaining_size = COMMAND_BUFFER_SIZE - strlen(command) - 1;
   strncat(command, "\n", remaining_size);
@@ -431,6 +466,16 @@ static void net_handle_command_reply_errors(
     display_close(ctl);
     error(EXIT_FAILURE, 0, "mtr-packet reported invalid argument");
   }
+
+  if (!strcmp(reply_name, "permission-denied")) {
+    display_close(ctl);
+    error(EXIT_FAILURE, 0, "Permission denied");
+  }
+
+  if (!strcmp(reply_name, "unexpected-error")) {
+    display_close(ctl);
+    error(EXIT_FAILURE, 0, "Unexpected mtr-packet error");
+  }
 }
 
 
@@ -790,7 +835,7 @@ extern int net_send_batch(struct mtr_ctl *ctl)
     }
   }
 
-  net_send_query(ctl, batch_at);
+  net_send_query(ctl, batch_at, abs(packetsize));
 
   for (i=ctl->fstTTL-1;i<batch_at;i++) {
     if ( addrcmp( (void *) &(host[i].addr), (void *) &ctl->unspec_addr, ctl->af ) == 0 )
@@ -855,7 +900,6 @@ static int net_synchronous_command(
   int command_length;
   int write_length;
   int read_length;
-  int parse_result;
 
   /*  Query send-probe support  */
   command_length = strlen(cmd);
@@ -881,9 +925,7 @@ static int net_synchronous_command(
 
   /*  Parse the query reply  */
   reply[read_length] = 0;
-  parse_result = parse_command(result, reply);
-  if (parse_result) {
-    errno = parse_result;
+  if (parse_command(result, reply)) {
     return -1;
   }
 
@@ -928,13 +970,27 @@ static int net_check_feature(struct mtr_ctl *ctl, const char *feature)
 static int net_packet_feature_check(struct mtr_ctl *ctl)
 {
   if (ctl->mtrtype == IPPROTO_ICMP) {
-    return net_check_feature(ctl, "icmp");
+    if (net_check_feature(ctl, "icmp")) {
+      return -1;
+    }
   } else if (ctl->mtrtype == IPPROTO_UDP) {
-    return net_check_feature(ctl, "udp");
+    if (net_check_feature(ctl, "udp")) {
+      return -1;
+    }
   } else {
     errno = EINVAL;
     return -1;
   }
+
+#ifdef SO_MARK
+  if (ctl->mark) {
+    if (net_check_feature(ctl, "mark")) {
+      return -1;
+    }
+  }
+#endif
+
+  return 0;
 }
 
 
index 2eba7c0a10419dbd3b7fd6277b41d756c3ebe485..1ce250ad6800bfdce7a1c334a35e08dcb852b5eb 100644 (file)
@@ -86,14 +86,16 @@ int parse_command(
     /*  Tokenize the string using whitespace  */
     token_count = tokenize_command(tokens, max_tokens, command_string);
     if (token_count < 2) {
-        return EINVAL;
+        errno = EINVAL;
+        return -1;
     }
 
     /*  Expect the command token to be a numerical value  */
     errno = 0;
     command->token = strtol(tokens[0], NULL, 10);
     if (errno) {
-        return EINVAL;
+        errno = EINVAL;
+        return -1;
     }
     command->command_name = tokens[1];
 
@@ -106,12 +108,14 @@ int parse_command(
     while (i < token_count) {
         /*  It's an error if we get a name without a key  */
         if (i + 1 >= token_count) {
-            return EINVAL;
+            errno = EINVAL;
+            return -1;
         }
 
         /*  It's an error if we get more arguments than we have space for  */
         if (command->argument_count >= MAX_COMMAND_ARGUMENTS) {
-            return EINVAL;
+            errno = EINVAL;
+            return -1;
         }
 
         command->argument_name[command->argument_count] = tokens[i];
index 5118f936ca91a68d597aaf51d642245fbe66118d..48cfdc93df87e9186038980022b823d4e76051fb 100644 (file)
@@ -97,6 +97,12 @@ const char *check_support(
         return check_protocol_support(net_state, IPPROTO_UDP);
     }
 
+#ifdef SO_MARK
+    if (!strcmp(feature, "mark")) {
+        return "ok";
+    }
+#endif
+
     return "no";
 }
 
@@ -162,6 +168,38 @@ bool decode_probe_argument(
         }
     }
 
+    /*  The "type of service" field for the IP header  */
+    if (!strcmp(name, "tos")) {
+        param->type_of_service = strtol(value, &endstr, 10);
+        if (*endstr != 0) {
+            return false;
+        }
+    }
+
+    /*  The Linux packet mark for mark-based routing  */
+    if (!strcmp(name, "mark")) {
+        param->routing_mark = strtol(value, &endstr, 10);
+        if (*endstr != 0) {
+            return false;
+        }
+    }
+
+    /*  The size of the packet (including headers)  */
+    if (!strcmp(name, "size")) {
+        param->packet_size = strtol(value, &endstr, 10);
+        if (*endstr != 0) {
+            return false;
+        }
+    }
+
+    /*  The packet's bytes will be filled with this value  */
+    if (!strcmp(name, "bitpattern")) {
+        param->bit_pattern = strtol(value, &endstr, 10);
+        if (*endstr != 0) {
+            return false;
+        }
+    }
+
     /*  Time-to-live values  */
     if (!strcmp(name, "ttl")) {
         param->ttl = strtol(value, &endstr, 10);
@@ -198,6 +236,7 @@ void send_probe_command(
     param.protocol = IPPROTO_ICMP;
     param.dest_port = 7; /* Use the 'echo' port as the default destination */
     param.ttl = 255;
+    param.packet_size = 128;
     param.timeout = 10;
 
     for (i = 0; i < command->argument_count; i++) {
index 3ad7cecb2b5790a26229e2946ff46fd14b587c34..d952178e616ac6d9e599a0d8449f1086f715b837 100644 (file)
@@ -134,14 +134,16 @@ void init_command_buffer(
 }
 
 /*
-    Return EPIPE if the command stream has been closed.  Otherwise, not much
-    to do for Cygwin, since we are using Overlapped I/O to read commands.
+    Return with errno EPIPE if the command stream has been closed.
+    Otherwise, not much to do for Cygwin, since we are using Overlapped I/O
+    to read commands.
 */
 int read_commands(
     struct command_buffer_t *buffer)
 {
     if (!buffer->platform.pipe_open) {
-        return EPIPE;
+        errno = EPIPE;
+        return -1;
     }
 
     return 0;
index 08536e2ab3a36bd703eb60094bf0e6fa21aaf15c..0b2f112fc270e9c3f8708ec47796507ae5c4d235 100644 (file)
@@ -68,7 +68,8 @@ int read_commands(
     /*  If the command stream has been closed, read will return zero.  */
     if (read_count == 0)
     {
-        return EPIPE;
+        errno = EPIPE;
+        return -1;
     }
 
     if (read_count > 0) {
index 8b36cde1153bb0d14690fe2861db812f7569ad2d..7cba81530fff874f5c7f33569743ee282ecb283e 100644 (file)
@@ -19,6 +19,7 @@
 #include "construct_unix.h"
 
 #include <errno.h>
+#include <stdio.h>
 #include <string.h>
 #include <sys/socket.h>
 #include <unistd.h>
@@ -94,7 +95,10 @@ void construct_ip4_header(
 
     ip = (struct IPHeader *)&packet_buffer[0];
 
+    memset(ip, 0, sizeof(struct IPHeader));
+
     ip->version = 0x45;
+    ip->tos = param->type_of_service;
     ip->len = length_byte_swap(net_state, packet_size);
     ip->ttl = param->ttl;
     ip->protocol = param->protocol;
@@ -116,6 +120,8 @@ void construct_icmp4_header(
     icmp = (struct ICMPHeader *)&packet_buffer[sizeof(struct IPHeader)];
     icmp_size = packet_size - sizeof(struct IPHeader);
 
+    memset(icmp, 0, sizeof(struct ICMPHeader));
+
     icmp->type = ICMP_ECHO;
     icmp->id = htons(getpid());
     icmp->sequence = htons(param->command_token);
@@ -134,16 +140,12 @@ int construct_icmp6_packet(
 
     icmp = (struct ICMPHeader *)packet_buffer;
 
+    memset(icmp, 0, sizeof(struct ICMPHeader));
+
     icmp->type = ICMP6_ECHO;
     icmp->id = htons(getpid());
     icmp->sequence = htons(param->command_token);
 
-    if (setsockopt(
-            net_state->platform.icmp6_send_socket, IPPROTO_IPV6,
-            IPV6_UNICAST_HOPS, &param->ttl, sizeof(int))) {
-        return -errno;
-    }
-
     return 0;
 }
 
@@ -165,6 +167,8 @@ void construct_udp4_header(
     udp = (struct UDPHeader *)&packet_buffer[sizeof(struct IPHeader)];
     udp_size = packet_size - sizeof(struct IPHeader);
 
+    memset(udp, 0, sizeof(struct UDPHeader));
+
     udp->srcport = htons(param->command_token);
     udp->dstport = htons(param->dest_port);
     udp->length = htons(udp_size);
@@ -186,19 +190,13 @@ int construct_udp6_packet(
     udp = (struct UDPHeader *)packet_buffer;
     udp_size = packet_size;
 
+    memset(udp, 0, sizeof(struct UDPHeader));
+
     udp->srcport = htons(param->command_token);
     udp->dstport = htons(param->dest_port);
     udp->length = htons(udp_size);
     udp->checksum = 0;
 
-    /*  Set the TTL via setsockopt  */
-    if (setsockopt(
-            udp_socket, IPPROTO_IPV6,
-            IPV6_UNICAST_HOPS, &param->ttl, sizeof(int))) {
-
-        return -errno;
-    }
-
     /*
         Instruct the kernel to put the pseudoheader checksum into the
         UDP header.
@@ -207,8 +205,7 @@ int construct_udp6_packet(
     if (setsockopt(
             udp_socket, IPPROTO_IPV6,
             IPV6_CHECKSUM, &chksum_offset, sizeof(int))) {
-
-        return -errno;
+        return -1;
     }
 
     return 0;
@@ -228,12 +225,14 @@ int compute_packet_size(
 {
     int packet_size;
 
+    /*  Start by determining the full size, including omitted headers  */
     if (param->ip_version == 6) {
-        packet_size = 0;
+        packet_size = sizeof(struct IP6Header);
     } else if (param->ip_version == 4) {
         packet_size = sizeof(struct IPHeader);
     } else {
-        return -EINVAL;
+        errno = EINVAL;
+        return -1;
     }
 
     if (param->protocol == IPPROTO_ICMP) {
@@ -241,12 +240,133 @@ int compute_packet_size(
     } else if (param->protocol == IPPROTO_UDP) {
         packet_size += sizeof(struct UDPHeader);
     } else {
-        return -EINVAL;
+        errno = EINVAL;
+        return -1;
+    }
+
+    /*
+        If the requested size from send-probe is greater, extend the
+        packet size.
+    */
+    if (param->packet_size > packet_size) {
+        packet_size = param->packet_size;
+    }
+
+    /*
+        Since we don't explicitly construct the IPv6 header, we
+        need to account for it in our transmitted size.
+    */
+    if (param->ip_version == 6) {
+        packet_size -= sizeof(struct IP6Header);
     }
 
     return packet_size;
 }
 
+/*  Construct a packet for an IPv4 probe  */
+int construct_ip4_packet(
+    const struct net_state_t *net_state,
+    char *packet_buffer,
+    int packet_size,
+    const struct sockaddr_storage *src_sockaddr,
+    const struct sockaddr_storage *dest_sockaddr,
+    const struct probe_param_t *param)
+{
+    construct_ip4_header(
+        net_state, packet_buffer, packet_size,
+        src_sockaddr, dest_sockaddr, param);
+
+    if (param->protocol == IPPROTO_ICMP) {
+        construct_icmp4_header(
+            net_state, packet_buffer, packet_size, param);
+    } else if (param->protocol == IPPROTO_UDP) {
+        construct_udp4_header(
+            net_state, packet_buffer, packet_size, param);
+    } else {
+        errno = EINVAL;
+        return -1;
+    }
+
+    /*
+        The routing mark requires CAP_NET_ADMIN, as opposed to the
+        CAP_NET_RAW which we are sometimes explicitly given.
+        If we don't have CAP_NET_ADMIN, this will fail, so we'll 
+        only set the mark if the user has explicitly requested it.
+
+        Unfortunately, this means that once the mark is set, it won't
+        be set on the socket again until a new mark is explicitly
+        specified.
+    */
+#ifdef SO_MARK
+    if (param->routing_mark) {
+        if (setsockopt(
+                net_state->platform.ip4_send_socket,
+                SOL_SOCKET, SO_MARK, &param->routing_mark, sizeof(int))) {
+            return -1;
+        }
+    }
+#endif
+
+    return 0;
+}
+
+/*  Construct a packet for an IPv6 probe  */
+int construct_ip6_packet(
+    const struct net_state_t *net_state,
+    char *packet_buffer,
+    int packet_size,
+    const struct sockaddr_storage *src_sockaddr,
+    const struct sockaddr_storage *dest_sockaddr,
+    const struct probe_param_t *param)
+{
+    int send_socket;
+
+    if (param->protocol == IPPROTO_ICMP) {
+        send_socket = net_state->platform.icmp6_send_socket;
+
+        if (construct_icmp6_packet(
+                net_state, packet_buffer, packet_size, param)) {
+            return -1;
+        }
+    } else if (param->protocol == IPPROTO_UDP) {
+        send_socket = net_state->platform.udp6_send_socket;
+
+        if (construct_udp6_packet(
+                net_state, packet_buffer, packet_size, param)) {
+            return -1;
+        }
+    } else {
+        errno = EINVAL;
+        return -1;
+    }
+
+    /*  The traffic class in IPv6 is analagous to ToS in IPv4  */
+    if (setsockopt(
+            send_socket, IPPROTO_IPV6,
+            IPV6_TCLASS, &param->type_of_service, sizeof(int))) {
+        return -1;
+    }
+
+    /*  Set the time-to-live  */
+    if (setsockopt(
+            send_socket, IPPROTO_IPV6,
+            IPV6_UNICAST_HOPS, &param->ttl, sizeof(int))) {
+        return -1;
+    }
+
+#ifdef SO_MARK
+    if (param->routing_mark) {
+        if (setsockopt(
+                send_socket,
+                SOL_SOCKET, SO_MARK, &param->routing_mark, sizeof(int))) {
+            return -1;
+        }
+    }
+#endif
+
+    return 0;
+}
+
 /*  Construct a probe packet based on the probe parameters  */
 int construct_packet(
     const struct net_state_t *net_state,
@@ -255,55 +375,40 @@ int construct_packet(
     const struct sockaddr_storage *dest_sockaddr,
     const struct probe_param_t *param)
 {
-    int err;
     int packet_size;
     struct sockaddr_storage src_sockaddr;
 
     packet_size = compute_packet_size(net_state, param);
     if (packet_size < 0) {
-        return packet_size;
+        return -1;
     }
 
     if (packet_buffer_size < packet_size) {
-        return -EINVAL;
+        errno = EINVAL;
+        return -1;
     }
 
-    err = find_source_addr(&src_sockaddr, dest_sockaddr);
-    if (err) {
-        return err;
+    if (find_source_addr(&src_sockaddr, dest_sockaddr)) {
+        return -1;
     }
 
-    memset(packet_buffer, 0, packet_size);
+    memset(packet_buffer, param->bit_pattern, packet_size);
 
-    err = 0;
     if (param->ip_version == 6) {
-        if (param->protocol == IPPROTO_ICMP) {
-            err = construct_icmp6_packet(
-                net_state, packet_buffer, packet_size, param);
-        } else if (param->protocol == IPPROTO_UDP) {
-            err = construct_udp6_packet(
-                net_state, packet_buffer, packet_size, param);
-        } else {
-            return -EINVAL;
+        if (construct_ip6_packet(
+                net_state, packet_buffer, packet_size,
+                &src_sockaddr, dest_sockaddr, param)) {
+            return -1;
         }
-    } else {
-        construct_ip4_header(
-            net_state, packet_buffer, packet_size,
-            &src_sockaddr, dest_sockaddr, param);
-
-        if (param->protocol == IPPROTO_ICMP) {
-            construct_icmp4_header(
-                net_state, packet_buffer, packet_size, param);
-        } else if (param->protocol == IPPROTO_UDP) {
-            construct_udp4_header(
-                net_state, packet_buffer, packet_size, param);
-        } else {
-            return -EINVAL;
+    } else if (param->ip_version == 4) {
+        if (construct_ip4_packet(
+                net_state, packet_buffer, packet_size,
+                &src_sockaddr, dest_sockaddr, param)) {
+            return -1;
         }
-    }
-
-    if (err) {
-        return err;
+    } else {
+        errno = EINVAL;
+        return -1;
     }
 
     return packet_size;
index 6590ba0ffe8213ff75d054a0a75714a3e21a5a19..76edc7ca6d32052c07563443db45461dd9198cf2 100644 (file)
@@ -40,7 +40,6 @@ int main(
     char **argv)
 {
     bool command_pipe_open;
-    int err;
     struct command_buffer_t command_buffer;
     struct net_state_t net_state;
 
@@ -73,10 +72,11 @@ int main(
         receive_replies(&net_state);
 
         if (command_pipe_open) {
-            err = read_commands(&command_buffer);
-            if (err == EPIPE)
-            {
-                command_pipe_open = false;
+            if (read_commands(&command_buffer)) {
+                if (errno == EPIPE)
+                {
+                    command_pipe_open = false;
+                }
             }
         }
 
index b4d4ad93ccacc02fb5490888edef71a27ba0ce21..636585ecd26c8820ec92534490f836ada09de650 100644 (file)
@@ -44,14 +44,16 @@ int decode_dest_addr(
     struct sockaddr_in6 *sockaddr6;
 
     if (param->address == NULL) {
-        return EINVAL;
+        errno = EINVAL;
+        return -1;
     }
 
     if (param->ip_version == 6) {
         sockaddr6 = (struct sockaddr_in6 *)dest_sockaddr;
 
         if (inet_pton(AF_INET6, param->address, &dest_addr6) != 1) {
-            return EINVAL;
+            errno = EINVAL;
+            return -1;
         }
 
         sockaddr6->sin6_family = AF_INET6;
@@ -63,14 +65,16 @@ int decode_dest_addr(
         sockaddr4 = (struct sockaddr_in *)dest_sockaddr;
 
         if (inet_pton(AF_INET, param->address, &dest_addr4) != 1) {
-            return EINVAL;
+            errno = EINVAL;
+            return -1;
         }
 
         sockaddr4->sin_family = AF_INET;
         sockaddr4->sin_port = 0;
         sockaddr4->sin_addr = dest_addr4;
     } else {
-        return EINVAL;
+        errno = EINVAL;
+        return -1;
     }
 
     return 0;
@@ -104,6 +108,8 @@ struct probe_t *alloc_probe(
 void free_probe(
     struct probe_t *probe)
 {
+    platform_free_probe(probe);
+
     probe->used = false;
 }
 
@@ -261,17 +267,17 @@ int find_source_addr(
 
     sock = socket(destaddr->ss_family, SOCK_DGRAM, IPPROTO_UDP);
     if (sock == -1) {
-        return -errno;
+        return -1;
     }
 
     if (connect(sock, (struct sockaddr *)&dest_with_port, len)) {
         close(sock);
-        return -errno;
+        return -1;
     }
 
     if (getsockname(sock, (struct sockaddr *)srcaddr, &len)) {
         close(sock);
-        return -errno;
+        return -1;
     }
 
     close(sock);
index 6b66150654f56a85ec57bad14c3db135a6eb0c65..17b4d70f94d9da9463a23a96d0e798908d4d2c76 100644 (file)
@@ -33,6 +33,9 @@
 
 #define MAX_PROBES 1024
 
+/*  Use the "jumbo" frame size as the max packet size  */
+#define PACKET_BUFFER_SIZE 9000
+
 /*  Parameters for sending a new probe  */
 struct probe_param_t
 {
@@ -51,9 +54,21 @@ struct probe_param_t
     /*  The destination port for non-ICMP probes  */
     int dest_port;
 
+    /*  The "type of service" field in the IP header  */
+    int type_of_service;
+
+    /*  The packet "mark" used for mark-based routing on Linux  */
+    int routing_mark;
+
     /*  Time to live for the transmited probe  */
     int ttl;
 
+    /*  The packet size (in bytes) including protocol headers  */
+    int packet_size;
+
+    /*  The value with which to fill the bytes of the packet.  */
+    int bit_pattern;
+
     /*  The number of seconds to wait before assuming the probe was lost  */
     int timeout;
 };
@@ -119,6 +134,9 @@ struct probe_t *alloc_probe(
     struct net_state_t *net_state,
     int token);
 
+void platform_free_probe(
+    struct probe_t *probe);
+
 void free_probe(
     struct probe_t *probe);
 
index 771d101894b8005168d923663713ee8d4df70081..cbf18a0ba27bb0992bf0f50d619ebb1b9a7d3ba2 100644 (file)
@@ -60,6 +60,16 @@ bool is_protocol_supported(
     return false;
 }
 
+/*  Free the reply buffer when the probe is freed  */
+void platform_free_probe(
+    struct probe_t *probe)
+{
+    if (probe->platform.reply4) {
+        free(probe->platform.reply4);
+        probe->platform.reply4 = NULL;
+    }
+}
+
 /*
     The overlapped I/O style completion routine to be called by
     Windows during an altertable wait when an ICMP probe has
@@ -84,7 +94,7 @@ void WINAPI on_icmp_reply(
     ICMPV6_ECHO_REPLY *reply6;
 
     if (probe->platform.ip_version == 6) {
-        reply6 = &probe->platform.reply6;
+        reply6 = probe->platform.reply6;
         reply_count = Icmp6ParseReplies(reply6, sizeof(ICMPV6_ECHO_REPLY));
 
         if (reply_count > 0) {
@@ -103,7 +113,7 @@ void WINAPI on_icmp_reply(
             remote_addr6->sin6_scope_id = 0;
         }
     } else {
-        reply4 = &probe->platform.reply4;
+        reply4 = probe->platform.reply4;
         reply_count = IcmpParseReplies(reply4, sizeof(ICMP_ECHO_REPLY));
 
         if (reply_count > 0) {
@@ -150,26 +160,25 @@ void WINAPI on_icmp_reply(
     }
 }
 
-/*  Send a new probe using ICMP.DLL's send echo mechanism  */
-void send_probe(
+/*  Use ICMP.DLL's send echo support to send a probe  */
+static
+void icmp_send_probe(
     struct net_state_t *net_state,
-    const struct probe_param_t *param)
+    struct probe_t *probe,
+    const struct probe_param_t *param,
+    struct sockaddr_storage *src_sockaddr,
+    struct sockaddr_storage *dest_sockaddr,
+    char *payload,
+    int payload_size)
 {
     IP_OPTION_INFORMATION option;
-    DWORD send_result;
     DWORD timeout;
-    struct probe_t *probe;
-    struct sockaddr_storage dest_sockaddr;
-    struct sockaddr_storage src_sockaddr;
+    DWORD send_result;
+    int reply_size;
     struct sockaddr_in *dest_sockaddr4;
     struct sockaddr_in6 *src_sockaddr6;
     struct sockaddr_in6 *dest_sockaddr6;
 
-    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 {
@@ -181,38 +190,38 @@ void send_probe(
         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;
+
+    if (param->ip_version == 6) {
+        reply_size = sizeof(ICMPV6_ECHO_REPLY) + payload_size;
+    } else {
+        reply_size = sizeof(ICMP_ECHO_REPLY32) + payload_size;
     }
 
-    if (find_source_addr(&src_sockaddr, &dest_sockaddr)) {
-        fprintf(stderr, "error finding source address\n");
+    probe->platform.reply4 = malloc(reply_size);
+    if (probe->platform.reply4 == NULL) {
+        perror("failure to allocate reply buffer");
         exit(1);
     }
 
-    probe->platform.ip_version = param->ip_version;
-
-    memset(&option, 0, sizeof(IP_OPTION_INFORMATION32));
-    option.Ttl = param->ttl;
-
     if (param->ip_version == 6) {
-        src_sockaddr6 = (struct sockaddr_in6 *)&src_sockaddr;
-        dest_sockaddr6 = (struct sockaddr_in6 *)&dest_sockaddr;
+        src_sockaddr6 = (struct sockaddr_in6 *)src_sockaddr;
+        dest_sockaddr6 = (struct sockaddr_in6 *)dest_sockaddr;
 
         send_result = Icmp6SendEcho2(
             net_state->platform.icmp6, NULL,
             (FARPROC)on_icmp_reply, probe,
-            src_sockaddr6, dest_sockaddr6, NULL, 0, &option,
-            &probe->platform.reply6, sizeof(ICMPV6_ECHO_REPLY), timeout);
+            src_sockaddr6, dest_sockaddr6, payload, payload_size, &option,
+            probe->platform.reply6, reply_size, timeout);
     } else {
-        dest_sockaddr4 = (struct sockaddr_in *)&dest_sockaddr;
+        dest_sockaddr4 = (struct sockaddr_in *)dest_sockaddr;
+
         send_result = IcmpSendEcho2(
             net_state->platform.icmp4, NULL,
             (FARPROC)on_icmp_reply, probe,
-            dest_sockaddr4->sin_addr.s_addr, NULL, 0, &option,
-            &probe->platform.reply4, sizeof(ICMP_ECHO_REPLY), timeout);
+            dest_sockaddr4->sin_addr.s_addr, payload, payload_size, &option,
+            probe->platform.reply4, reply_size, timeout);
     }
 
     if (send_result == 0) {
@@ -227,6 +236,80 @@ void send_probe(
     }
 }
 
+/*  Fill the payload of the packet as specified by the probe parameters  */
+static
+int fill_payload(
+    const struct probe_param_t *param,
+    char *payload,
+    int payload_buffer_size)
+{
+    int ip_icmp_size;
+    int payload_size;
+
+    if (param->ip_version == 6) {
+        ip_icmp_size = sizeof(struct IP6Header) + sizeof(struct ICMPHeader);
+    } else if (param->ip_version == 4) {
+        ip_icmp_size = sizeof(struct IPHeader) + sizeof(struct ICMPHeader);
+    } else {
+        errno = EINVAL;
+        return -1;
+    }
+
+    payload_size = param->packet_size - ip_icmp_size;
+    if (payload_size < 0) {
+        payload_size = 0;
+    }
+
+    if (payload_size > payload_buffer_size) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    memset(payload, param->bit_pattern, payload_size);
+
+    return payload_size;
+}
+
+/*  Decode the probe parameters and send a probe  */
+void send_probe(
+    struct net_state_t *net_state,
+    const struct probe_param_t *param)
+{
+    struct probe_t *probe;
+    struct sockaddr_storage dest_sockaddr;
+    struct sockaddr_storage src_sockaddr;
+    char payload[PACKET_BUFFER_SIZE];
+    int payload_size;
+
+    if (decode_dest_addr(param, &dest_sockaddr)) {
+        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;
+    }
+
+    if (find_source_addr(&src_sockaddr, &dest_sockaddr)) {
+        fprintf(stderr, "error finding source address\n");
+        exit(1);
+    }
+
+    probe->platform.ip_version = param->ip_version;
+
+    payload_size = fill_payload(param, payload, PACKET_BUFFER_SIZE);
+    if (payload_size < 0) {
+        perror("Error construction packet");
+        exit(1);
+    }
+
+    icmp_send_probe(
+        net_state, probe, param,
+        &src_sockaddr, &dest_sockaddr, payload, payload_size);
+}
+
 /*
     On Windows, an implementation of receive_replies is unnecessary, because,
     unlike Unix, replies are completed using Overlapped I/O during an
index a4d5f284822147cc2ab8e280c960a61f49bf16e5..7fab3279e6736f471ddbeda09f4a4d6066f7caa0 100644 (file)
@@ -55,8 +55,8 @@ struct probe_platform_t
     int ip_version;
 
     union {
-        ICMP_ECHO_REPLY32 reply4;
-        ICMPV6_ECHO_REPLY reply6;
+        ICMP_ECHO_REPLY32 *reply4;
+        ICMPV6_ECHO_REPLY *reply6;
     };
 };
 
index e840a01fa7876d48538d1bec5971dc2f35c3d93c..d1bd7cb3d3ebe8ade529b5372b3dfb93ae44e4ad 100644 (file)
@@ -32,9 +32,6 @@
 #include "deconstruct_unix.h"
 #include "timeval.h"
 
-/*  Use the "jumbo" frame size as the max packet size  */
-#define PACKET_BUFFER_SIZE 9000
-
 /*  A wrapper around sendto for mixed IPv4 and IPv6 sending  */
 static
 int send_packet(
@@ -109,7 +106,6 @@ void check_length_order(
     packet_size = construct_packet(
         net_state, packet, PACKET_BUFFER_SIZE, &dest_sockaddr, &param);
     if (packet_size < 0) {
-        errno = -packet_size;
         perror("Unable to send to localhost");
         exit(1);
     }
@@ -126,7 +122,6 @@ void check_length_order(
     packet_size = construct_packet(
         net_state, packet, PACKET_BUFFER_SIZE, &dest_sockaddr, &param);
     if (packet_size < 0) {
-        errno = -packet_size;
         perror("Unable to send to localhost");
         exit(1);
     }
@@ -275,6 +270,24 @@ bool is_protocol_supported(
     return false;
 }
 
+/*  Report an error during send_probe based on the errno value  */
+static
+void report_packet_error(
+    const struct probe_param_t *param)
+{
+    if (errno == EINVAL) {
+        printf("%d invalid-argument\n", param->command_token);
+    } else if (errno == ENETDOWN) {
+        printf("%d network-down\n", param->command_token);
+    } else if (errno == ENETUNREACH) {
+        printf("%d no-route\n", param->command_token);
+    } else if (errno == EPERM) {
+        printf("%d permission-denied\n", param->command_token);
+    } else {
+        printf("%d unexpected-error errno %d\n", param->command_token, errno);
+    }
+}
+
 /*  Craft a custom ICMP packet for a network probe.  */
 void send_probe(
     struct net_state_t *net_state,
@@ -293,17 +306,7 @@ void send_probe(
     packet_size = construct_packet(
         net_state, packet, PACKET_BUFFER_SIZE, &dest_sockaddr, param);
     if (packet_size < 0) {
-        if (packet_size == -EINVAL) {
-            printf("%d invalid-argument\n", param->command_token);
-        } else if (packet_size == -ENETDOWN) {
-            printf("%d network-down\n", param->command_token);
-        } else if (packet_size == -ENETUNREACH) {
-            printf("%d no-route\n", param->command_token);
-        } else {
-            errno = -packet_size;
-            perror("Failure constructing packet");
-            exit(1);
-        }
+        report_packet_error(param);
         return;
     }
 
@@ -325,17 +328,7 @@ void send_probe(
     if (send_packet(
             net_state, param, packet, packet_size, &dest_sockaddr) == -1) {
 
-        if (errno == ENETDOWN) {
-            printf("%d network-down\n", param->command_token);
-        } else if (errno == ENETUNREACH) {
-            printf("%d no-route\n", param->command_token);
-        } else if (errno == EINVAL) {
-            printf("%d invalid-argument\n", param->command_token);
-        } else {
-            perror("Failure sending probe");
-            exit(1);
-        }
-
+        report_packet_error(param);
         free_probe(probe);
         return;
     }
@@ -344,10 +337,17 @@ void send_probe(
     probe->platform.timeout_time.tv_sec += param->timeout;
 }
 
+/*  Nothing is needed for freeing Unix probes  */
+void platform_free_probe(
+    struct probe_t *probe)
+{
+}
+
 /*
     Read all available packets through our receiving raw socket, and
     handle any responses to probes we have preivously sent.
 */
+static
 void receive_replies_from_socket(
     struct net_state_t *net_state,
     int socket,
index 9ffc69c5663966135ed61de57aae964aa623c96c..029836c3591caaff66a1ca8eda483692949c289e 100644 (file)
@@ -21,6 +21,7 @@
 import fcntl
 import os
 import select
+import socket
 import subprocess
 import sys
 import time
@@ -37,6 +38,9 @@ except ImportError:
     pass
 
 
+IPV6_TEST_HOST = 'google-public-dns-a.google.com'
+
+
 class MtrPacketExecuteError(Exception):
     "Exception raised when MtrPacketTest can't execute mtr-packet"
     pass
@@ -60,6 +64,12 @@ class MtrPacketReplyParseError(Exception):
     pass
 
 
+class PacketListenError(Exception):
+    'Exception raised when we have unexpected results from mtr-packet-listen'
+
+    pass
+
+
 def set_nonblocking(file_descriptor):  # type: (int) -> None
     'Put a file descriptor into non-blocking mode'
 
@@ -69,6 +79,39 @@ def set_nonblocking(file_descriptor):  # type: (int) -> None
     fcntl.fcntl(file_descriptor, fcntl.F_SETFL, flags | os.O_NONBLOCK)
 
 
+def check_for_local_ipv6():
+    '''Check for IPv6 support on the test host, to see if we should skip
+    the IPv6 tests'''
+
+    addrinfo = socket.getaddrinfo(IPV6_TEST_HOST, 1, socket.AF_INET6)
+    if len(addrinfo):
+        addr = addrinfo[0][4]
+
+    #  Create a UDP socket and check to see it can be connected to
+    #  IPV6_TEST_HOST.  (Connecting UDP requires no packets sent, just
+    #  a route present.)
+    sock = socket.socket(
+        socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
+
+    connect_success = False
+    try:
+        sock.connect(addr)
+        connect_success = True
+    except socket.error:
+        pass
+
+    sock.close()
+
+    if not connect_success:
+        sys.stderr.write(
+            'This host has no IPv6.  Skipping IPv6 tests.\n')
+
+    return connect_success
+
+
+HAVE_IPV6 = check_for_local_ipv6()
+
+
 # pylint: disable=locally-disabled, too-few-public-methods
 class MtrPacketReply(object):
     'A parsed reply from mtr-packet'
@@ -103,6 +146,76 @@ class MtrPacketReply(object):
             i += 2
 
 
+class PacketListen(object):
+    'A test process which listens for a single packet'
+
+    def __init__(self, *args):
+        self.process_args = list(args)  # type: List[unicode]
+        self.listen_process = None  # type: subprocess.Popen
+        self.token = None  # type: unicode
+        self.attrib = None  # type: Dict[unicode, unicode]
+
+    def __enter__(self):
+        try:
+            self.listen_process = subprocess.Popen(
+                ['./mtr-packet-listen'] + self.process_args,
+                stdin=subprocess.PIPE,
+                stdout=subprocess.PIPE)
+        except OSError:
+            raise PacketListenError('unable to launch mtr-packet-listen')
+
+        status = self.listen_process.stdout.readline().decode('utf-8')
+        if status != 'status listening\n':
+            raise PacketListenError('unexpected status')
+
+        self.token = str(self.listen_process.pid)
+
+        return self
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        self.wait_for_exit()
+
+        self.attrib = {}
+        for line in self.listen_process.stdout.readlines():
+            tokens = line.decode('utf-8').split()
+
+            if len(tokens) >= 2:
+                name = tokens[0]
+                value = tokens[1]
+
+                self.attrib[name] = value
+
+        self.listen_process.stdin.close()
+        self.listen_process.stdout.close()
+
+    def wait_for_exit(self):
+        '''Poll the subprocess for up to ten seconds, until it exits.
+
+        We need to wait for its exit to ensure we are able to read its
+        output.'''
+
+        wait_time = 10
+        wait_step = 0.1
+
+        steps = int(wait_time / wait_step)
+
+        exit_value = None
+
+        # pylint: disable=locally-disabled, unused-variable
+        for i in range(steps):
+            exit_value = self.listen_process.poll()
+            if exit_value is not None:
+                break
+
+            time.sleep(wait_step)
+
+        if exit_value is None:
+            raise PacketListenError('mtr-packet-listen timeout')
+
+        if exit_value != 0:
+            raise PacketListenError('mtr-packet-listen unexpected error')
+
+
 class MtrPacketTest(unittest.TestCase):
     '''Base class for tests invoking mtr-packet.
 
diff --git a/test/packet_listen.c b/test/packet_listen.c
new file mode 100644 (file)
index 0000000..2b0ab90
--- /dev/null
@@ -0,0 +1,243 @@
+/*
+    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 <netinet/in.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include "packet/protocols.h"
+
+#define MAX_PACKET_SIZE 9000
+
+/*
+    Check to see if the packet we've recieved is intended for this test
+    process.  We expected the ICMP sequence number to be equal to our
+    process ID.
+*/
+bool is_packet_for_us4(
+    char *packet,
+    int packet_size)
+{
+    int ip_icmp_size = sizeof(struct IPHeader) + sizeof(struct ICMPHeader);
+    int expected_sequence;
+    struct IPHeader *ip;
+    struct ICMPHeader *icmp;
+
+    if (packet_size < ip_icmp_size) {
+        return false;
+    }
+
+    ip = (struct IPHeader *)packet;
+    icmp = (struct ICMPHeader *)(ip + 1);
+
+    expected_sequence = htons(getpid());
+    if (icmp->sequence == expected_sequence) {
+        return true;
+    }
+
+    return false;
+}
+
+/*
+    Check to see if the ICMPv6 packet is for us.
+    Unlike ICMPv4 packets, ICMPv6 packets don't include the IP header.
+*/
+bool is_packet_for_us6(
+    char *packet,
+    int packet_size)
+{
+    int expected_sequence;
+    struct ICMPHeader *icmp;
+
+    if (packet_size < sizeof(struct ICMPHeader)) {
+        return false;
+    }
+
+    icmp = (struct ICMPHeader *)packet;
+
+    expected_sequence = htons(getpid());
+    if (icmp->sequence == expected_sequence) {
+        return true;
+    }
+
+    return false;
+}
+
+/*
+    Check that all the bytes in the body of the packet have the same value.
+    If so, return that value.  If not, return -1.
+*/
+int get_packet_pattern(
+    unsigned char *packet,
+    int packet_size)
+{
+    int fill_value;
+    int i;
+
+    if (packet_size <= 0) {
+        return -1;
+    }
+
+    fill_value = packet[0];
+    for (i = 1; i < packet_size; i++) {
+        if (packet[i] != fill_value) {
+            return -1;
+        }
+    }
+
+    return fill_value;
+}
+
+/*  Print information about the ICMPv4 packet we received  */
+void dump_packet_info4(
+    char *packet,
+    int packet_size)
+{
+    int ip_icmp_size = sizeof(struct IPHeader) + sizeof(struct ICMPHeader);
+    int pattern;
+    struct IPHeader *ip;
+    struct ICMPHeader *icmp;
+    unsigned char *body;
+    int body_size;
+
+    ip = (struct IPHeader *)packet;
+    icmp = (struct ICMPHeader *)(ip + 1);
+    body = (unsigned char *)(icmp + 1);
+    body_size = packet_size - ip_icmp_size;
+
+    printf("size %d\n", packet_size);
+    printf("tos %d\n", ip->tos);
+
+    pattern = get_packet_pattern(body, body_size);
+    if (pattern < 0) {
+        printf("bitpattern none\n");
+    } else {
+        printf("bitpattern %d\n", pattern);
+    }
+}
+
+/*  Print information about an ICMPv6 packet  */
+void dump_packet_info6(
+    char *packet,
+    int packet_size)
+{
+    int pattern;
+    struct ICMPHeader *icmp;
+    unsigned char *body;
+    int body_size;
+    int total_size;
+
+    icmp = (struct ICMPHeader *)packet;
+    body = (unsigned char *)(icmp + 1);
+    body_size = packet_size - sizeof(struct ICMPHeader);
+
+    total_size = packet_size + sizeof(struct IP6Header);
+    printf("size %d\n", total_size);
+
+    pattern = get_packet_pattern(body, body_size);
+    if (pattern < 0) {
+        printf("bitpattern none\n");
+    } else {
+        printf("bitpattern %d\n", pattern);
+    }
+}
+
+/*  Receive ICMP packets until we get one intended for this test process  */
+void loop_on_receive(
+    int icmp_socket,
+    int ip_version)
+{
+    int packet_size;
+    char packet[MAX_PACKET_SIZE];
+
+    while (true) {
+        packet_size = recv(icmp_socket, packet, MAX_PACKET_SIZE, 0);
+        if (packet_size < -1) {
+            perror("Failure during receive");
+            exit(EXIT_FAILURE);
+        }
+
+        if (ip_version == 6) {
+            if (is_packet_for_us6(packet, packet_size)) {
+                dump_packet_info6(packet, packet_size);
+                return;
+            }
+        } else {
+            if (is_packet_for_us4(packet, packet_size)) {
+                dump_packet_info4(packet, packet_size);
+                return;
+            }
+        }
+    }
+}
+
+/*  Parse the commandline arguments  */
+void parse_cmdline(
+    int argc,
+    char **argv,
+    int *ip_version)
+{
+    int opt;
+
+    *ip_version = 4;
+
+    while ((opt = getopt(argc, argv, "46")) != -1) {
+        if (opt == '4') {
+            *ip_version = 4;
+        }
+
+        if (opt == '6') {
+            *ip_version = 6;
+        }
+    }
+}
+
+/*
+    A helper for mtr-packet testing which waits for an ICMP packet
+    intended for this test process, and then prints information about
+    it.
+*/
+int main(
+    int argc,
+    char **argv)
+{
+    int icmp_socket;
+    int ip_version;
+
+    parse_cmdline(argc, argv, &ip_version);
+
+    if (ip_version == 6) {
+        icmp_socket = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
+    } else {
+        icmp_socket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
+    }
+    if (icmp_socket < 0) {
+        perror("Failure opening listening socket");
+        exit(EXIT_FAILURE);
+    }
+
+    printf("status listening\n");
+    fflush(stdout);
+
+    loop_on_receive(icmp_socket, ip_version);
+
+    return EXIT_SUCCESS;
+}
diff --git a/test/param.py b/test/param.py
new file mode 100755 (executable)
index 0000000..ae03fc2
--- /dev/null
@@ -0,0 +1,83 @@
+#!/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 probe customization parameters'''
+
+import sys
+import unittest
+
+import mtrpacket
+
+
+@unittest.skipIf(sys.platform == 'cygwin', 'No Cygwin test')
+class TestParameters(mtrpacket.MtrPacketTest):
+    'Use parameter arguments to mtr-packet and examine the resulting packet'
+
+    def test_size(self):
+        'Test probes sent with an explicit packet size'
+
+        with mtrpacket.PacketListen('-4') as listen:
+            cmd = listen.token + ' send-probe ip-4 127.0.0.1 size 512'
+
+            self.write_command(cmd)
+
+        self.assertEqual(listen.attrib['size'], '512')
+
+    def test_pattern(self):
+        'Test probes are filled with the requested bit pattern'
+
+        with mtrpacket.PacketListen('-4') as listen:
+            cmd = listen.token + ' send-probe ip-4 127.0.0.1 bitpattern 44'
+
+            self.write_command(cmd)
+
+        self.assertEqual(listen.attrib['bitpattern'], '44')
+
+    def test_tos(self):
+        'Test setting the TOS field'
+
+        with mtrpacket.PacketListen('-4') as listen:
+            cmd = listen.token + ' send-probe ip-4 127.0.0.1 tos 62'
+
+            self.write_command(cmd)
+
+        self.assertEqual(listen.attrib['tos'], '62')
+
+
+@unittest.skipIf(sys.platform == 'cygwin', 'No Cygwin test')
+class TestIPv6Parameters(mtrpacket.MtrPacketTest):
+    'Test packet paramter customization for IPv6'
+
+    @unittest.skipUnless(mtrpacket.HAVE_IPV6, 'No IPv6')
+    def test_param(self):
+        'Test a variety of packet parameters'
+
+        with mtrpacket.PacketListen('-6') as listen:
+            param = 'size 256 bitpattern 51 tos 77'
+            cmd = listen.token + ' send-probe ip-6 ::1 ' + param
+
+            self.write_command(cmd)
+
+        self.assertEqual(listen.attrib['size'], '256')
+        self.assertEqual(listen.attrib['bitpattern'], '51')
+
+
+if __name__ == '__main__':
+    mtrpacket.check_running_as_root()
+    unittest.main()
old mode 100644 (file)
new mode 100755 (executable)
index b4345b8..02c4853
 '''Test sending probes and receiving respones.'''
 
 import socket
-import sys
 import time
 import unittest
 
 import mtrpacket
 
 
-IPV6_TEST_HOST = 'google-public-dns-a.google.com'
-
-
 def resolve_ipv6_address(hostname):  # type: (str) -> str
     'Resolve a hostname to an IP version 6 address'
 
@@ -46,39 +42,6 @@ def resolve_ipv6_address(hostname):  # type: (str) -> str
     raise LookupError(hostname)
 
 
-def check_for_local_ipv6():
-    '''Check for IPv6 support on the test host, to see if we should skip
-    the IPv6 tests'''
-
-    addrinfo = socket.getaddrinfo(IPV6_TEST_HOST, 1, socket.AF_INET6)
-    if len(addrinfo):
-        addr = addrinfo[0][4]
-
-    #  Create a UDP socket and check to see it can be connected to
-    #  IPV6_TEST_HOST.  (Connecting UDP requires no packets sent, just
-    #  a route present.)
-    sock = socket.socket(
-        socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
-
-    connect_success = False
-    try:
-        sock.connect(addr)
-        connect_success = True
-    except socket.error:
-        pass
-
-    sock.close()
-
-    if not connect_success:
-        sys.stderr.write(
-            'This host has no IPv6.  Skipping IPv6 tests.\n')
-
-    return connect_success
-
-
-HAVE_IPV6 = check_for_local_ipv6()
-
-
 class TestProbeICMPv4(mtrpacket.MtrPacketTest):
     '''Test sending probes using IP version 4'''
 
@@ -230,13 +193,13 @@ class TestProbeICMPv6(mtrpacket.MtrPacketTest):
     '''Test sending probes using IP version 6'''
 
     def __init__(self, *args):
-        google_addr = resolve_ipv6_address(IPV6_TEST_HOST)
+        google_addr = resolve_ipv6_address(mtrpacket.IPV6_TEST_HOST)
 
         self.google_addr = google_addr  # type: str
 
         super(TestProbeICMPv6, self).__init__(*args)
 
-    @unittest.skipUnless(HAVE_IPV6, 'No IPv6')
+    @unittest.skipUnless(mtrpacket.HAVE_IPV6, 'No IPv6')
     def test_probe(self):
         "Test a probe to Google's public DNS server"
 
@@ -258,7 +221,7 @@ class TestProbeICMPv6(mtrpacket.MtrPacketTest):
         self.assertIn('round-trip-time', reply.argument)
         self.assertEqual(reply.argument['ip-6'], '::1')
 
-    @unittest.skipUnless(HAVE_IPV6, 'No IPv6')
+    @unittest.skipUnless(mtrpacket.HAVE_IPV6, 'No IPv6')
     def test_ttl_expired(self):
         'Test sending a probe which will have its time-to-live expire'
 
@@ -292,11 +255,11 @@ class TestProbeUDP(mtrpacket.MtrPacketTest):
         self.assertIn('ip-4', reply.argument)
         self.assertEqual(reply.argument['ip-4'], '127.0.0.1')
 
-    @unittest.skipUnless(HAVE_IPV6, 'No IPv6')
+    @unittest.skipUnless(mtrpacket.HAVE_IPV6, 'No IPv6')
     def test_udp_v6(self):
         'Test IPv6 UDP probes'
 
-        test_addr = resolve_ipv6_address(IPV6_TEST_HOST)
+        test_addr = resolve_ipv6_address(mtrpacket.IPV6_TEST_HOST)
 
         cmd = '62 send-probe protocol udp ip-6 ' + test_addr + \
             ' port 164 ttl 1'