]> git.ipfire.org Git - thirdparty/mtr.git/commitdiff
mtr-packet: UDP probe support
authorMatt Kimball <matt.kimball@gmail.com>
Wed, 14 Dec 2016 17:18:42 +0000 (09:18 -0800)
committerMatt Kimball <matt.kimball@gmail.com>
Wed, 14 Dec 2016 17:33:38 +0000 (09:33 -0800)
Added support send using UDP as the protocol for sending probes,
rather than ICMP.  Both IPv4 and IPv6 UDP probes are supported.
We are using the source port in the UDP packet for identifying
the particular probe transmitted.  This is a bit less reliable
than ICMP, where we are also able to store our PID for verifying
the probe has been transmitted by this instance of mtr-packet,
but space is limited, and it is what the pre-existing mtr
implementation does.

We report no-route and network-down errors in response to errors
from sendto(), in addition to reporting them from errors in
the connect() used to determine the source address of an outgoing
probe.

The mtr-packet tests now properly parse the replies from
mtr-packet, as opposed to simply matching regular expressions
against the output.  This is better because it give us future
compatibility with additional reply arguments from mtr-packet.

A better introduction to mtr-packet is now included in the
mtr-packet man page.

The dual code paths for sending IPv6 probes between Linux
and non-Linux Unix-likes has been eliminated.  The Linux path
gave us direct control over the IP header, but wasn't necessary
and would make maintainence more difficult, so now Linux uses
the more indirect setsockopt() method of setting IPv6 header
fields.

17 files changed:
mtr-packet.8.in
mtr.8.in
net.c
packet/command.c
packet/construct_unix.c
packet/deconstruct_unix.c
packet/deconstruct_unix.h
packet/probe.c
packet/probe.h
packet/probe_cygwin.c
packet/probe_unix.c
packet/probe_unix.h
packet/protocols.h
packet/wait_unix.c
test/cmdparse.py
test/mtrpacket.py
test/probe.py [changed mode: 0755->0644]

index 7ea5c05deaaa1a4b71fe6c022c8138c1d7fb3ed8..463e962a63bb954e9570f1665d02df33f6a42f4e 100644 (file)
@@ -4,8 +4,19 @@
 mtr-packet - send and receive network probes
 .SH DESCRIPTION
 .B mtr-packet
+is a tool for sending network probes to measure network connectivity and
+performance.  Many network probes can be sent simultaneously by a single
+process instance of
+.B mtr-packet
+and additional probes can be generated by an instance of
+.B mtr-packet
+which already has network probes in flight.  It is intended to be used
+by programs which invoke it with Unix pipes attached to its standard input
+and output streams.
+.LP
+.B mtr-packet
 reads command requests from
-.I stdin,
+.IR stdin ,
 each separated by a newline character, and responds with command replies to
 .IR stdout ,
 also each separated by a newline character.  The syntactic structure of
@@ -80,6 +91,27 @@ The Internet Protocol version 4 address to probe.
 The Internet Protocol version 6 address to probe.
 .HP 7
 .IP
+.B protocol
+.I PROTOCOL
+.HP 14
+.IP
+The protocol to use for the network probe.
+.B icmp
+and
+.B udp
+may be used.  The default protocol is
+.BR icmp.
+.HP 7
+.IP
+.B port
+.I PORT-NUMBER
+.HP 14
+.IP
+The destination port to use for
+.B udp
+probes.
+.HP 7
+.IP
 .B timeout
 .I TIMEOUT-SECONDS
 .HP 14
@@ -321,12 +353,12 @@ 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),
+.BR icmp (7),
+.BR udp (7),
 TCP/IP Illustrated (Stevens, ISBN 0201633469).
index 58cd0815bd3fa2eb426dcb8bd42700099d81f19f..eb9fbe8a7dd95c7856db99ab21607fa54208e6eb 100644 (file)
--- a/mtr.8.in
+++ b/mtr.8.in
@@ -474,8 +474,6 @@ 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
diff --git a/net.c b/net.c
index 7852921e179749658bdd40e163218009c376c935..f413f89cb9eb33100a5b5c01b3e6639a00b00d69 100644 (file)
--- a/net.c
+++ b/net.c
@@ -62,6 +62,7 @@
 #define MinSequence 33000
 #define MaxSequence 65536
 
+#define COMMAND_BUFFER_SIZE 4096
 #define PACKET_REPLY_BUFFER_SIZE 4096
 
 static int packetsize;         /* packet size used by ping */
@@ -197,7 +198,11 @@ static void net_send_query(struct mtr_ctl *ctl, int index)
   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;
 
   /*  Conver the remote IP address to a string  */
   if (inet_ntop(
@@ -213,12 +218,32 @@ static void net_send_query(struct mtr_ctl *ctl, int index)
     ip_type = "ip-4";
   }
 
-  /*  Send a probe using the mtr-packet subprocess  */
-  if (dprintf(
-    packet_command_pipe.write_fd,
-    "%d send-probe %s %s ttl %d\n",
-    seq, ip_type, ip_string, time_to_live) < 0) {
+  if (ctl->mtrtype == IPPROTO_ICMP) {
+    protocol = "icmp";
+  } else if (ctl->mtrtype == IPPROTO_UDP) {
+    protocol = "udp";
+  } else {
+    display_close(ctl);
+    error(EXIT_FAILURE, 0, "protocol unsupported by mtr-packet interface");
+  }
+
+  snprintf(
+    command, COMMAND_BUFFER_SIZE,
+    "%d send-probe %s %s protocol %s ttl %d",
+    seq, ip_type, ip_string, protocol, time_to_live);
+
+  if (ctl->remoteport) {
+    remaining_size = COMMAND_BUFFER_SIZE - strlen(command) - 1;
+
+    snprintf(argument, COMMAND_BUFFER_SIZE, " port %d", ctl->remoteport);
+    strncat(command, argument, remaining_size);
+  }
+
+  remaining_size = COMMAND_BUFFER_SIZE - strlen(command) - 1;
+  strncat(command, "\n", remaining_size);
 
+  /*  Send a probe using the mtr-packet subprocess  */
+  if (write(packet_command_pipe.write_fd, command, strlen(command)) == -1) {
     display_close(ctl);
     error(EXIT_FAILURE, errno, "mtr-packet command pipe write failure");
   }
@@ -817,12 +842,15 @@ static void set_fd_nonblock(int fd)
   }
 }
 
-
-/*  Ensure we can communicate with the mtr-packet subprocess  */
-static int net_command_pipe_check(struct mtr_ctl *ctl)
+/*
+  Send a command synchronously to mtr-packet, blocking until a result
+  is available.  This is intended to be used at start-up to check the
+  capabilities of mtr-packet, but probes should be sent asynchronously
+  to avoid blocking other probes and the user interface.
+*/
+static int net_synchronous_command(
+  struct mtr_ctl *ctl, const char *cmd, struct command_t *result)
 {
-  const char *check_command = "1 check-support feature send-probe\n";
-  struct command_t command;
   char reply[PACKET_REPLY_BUFFER_SIZE];
   int command_length;
   int write_length;
@@ -830,9 +858,9 @@ static int net_command_pipe_check(struct mtr_ctl *ctl)
   int parse_result;
 
   /*  Query send-probe support  */
-  command_length = strlen(check_command);
+  command_length = strlen(cmd);
   write_length = write(
-    packet_command_pipe.write_fd, check_command, command_length);
+    packet_command_pipe.write_fd, cmd, command_length);
 
   if (write_length == -1) {
     return -1;
@@ -853,17 +881,35 @@ static int net_command_pipe_check(struct mtr_ctl *ctl)
 
   /*  Parse the query reply  */
   reply[read_length] = 0;
-  parse_result = parse_command(&command, reply);
+  parse_result = parse_command(result, reply);
   if (parse_result) {
     errno = parse_result;
     return -1;
   }
 
-  /*  Check that send-probe is supported  */
-  if (!strcmp(command.command_name, "feature-support")
-    && command.argument_count >= 1
-    && !strcmp(command.argument_name[0], "support")
-    && !strcmp(command.argument_value[0], "ok")) {
+  return 0;
+}
+
+
+/*  Check support for a particular feature with the mtr-packet we invoked  */
+static int net_check_feature(struct mtr_ctl *ctl, const char *feature)
+{
+  char check_command[COMMAND_BUFFER_SIZE];
+  struct command_t reply;
+
+  snprintf(
+    check_command, COMMAND_BUFFER_SIZE,
+    "1 check-support feature %s\n", feature);
+
+  if (net_synchronous_command(ctl, check_command, &reply) == -1) {
+    return -1;
+  }
+
+  /*  Check that the feature is supported  */
+  if (!strcmp(reply.command_name, "feature-support")
+    && reply.argument_count >= 1
+    && !strcmp(reply.argument_name[0], "support")
+    && !strcmp(reply.argument_value[0], "ok")) {
 
     /*  Looks good  */
     return 0;
@@ -874,6 +920,24 @@ static int net_command_pipe_check(struct mtr_ctl *ctl)
 }
 
 
+/*
+  Check the protocol selected against the mtr-packet we are using.
+  Returns zero if everything is fine, or -1 with errno for either
+  a failure during the check, or for an unsupported feature.
+*/
+static int net_packet_feature_check(struct mtr_ctl *ctl)
+{
+  if (ctl->mtrtype == IPPROTO_ICMP) {
+    return net_check_feature(ctl, "icmp");
+  } else if (ctl->mtrtype == IPPROTO_UDP) {
+    return net_check_feature(ctl, "udp");
+  } else {
+    errno = EINVAL;
+    return -1;
+  }
+}
+
+
 /*  Create the command pipe to a new mtr-packet subprocess  */
 static int net_command_pipe_open(struct mtr_ctl *ctl)
 {
@@ -954,10 +1018,14 @@ static int net_command_pipe_open(struct mtr_ctl *ctl)
       Check that we can communicate with the client.  If we failed to
       execute the mtr-packet binary, we will discover that here.
     */
-    if (net_command_pipe_check(ctl)) {
+    if (net_check_feature(ctl, "send-probe")) {
       error(EXIT_FAILURE, errno, "Failure to start mtr-packet");
     }
 
+    if (net_packet_feature_check(ctl)) {
+      error(EXIT_FAILURE, errno, "Packet type unsupported");
+    }
+
     /*  We will need non-blocking reads from the child  */
     set_fd_nonblock(packet_command_pipe.read_fd);
   }
index df3ca964d7da38a775e093004d2645913e92f759..5118f936ca91a68d597aaf51d642245fbe66118d 100644 (file)
@@ -54,10 +54,24 @@ const char *find_parameter(
     return NULL;
 }
 
+/*  Returns a feature support string for a particular probe protocol  */
+static
+const char *check_protocol_support(
+    struct net_state_t *net_state,
+    int protocol)
+{
+    if (is_protocol_supported(net_state, protocol)) {
+        return "ok";
+    } else {
+        return "no";
+    }
+}
+
 /*  Given a feature name, return a string for the check-support reply  */
 static
 const char *check_support(
-    const char *feature)
+    const char *feature,
+    struct net_state_t *net_state)
 {
     if (!strcmp(feature, "version")) {
         return PACKAGE_VERSION;
@@ -75,13 +89,22 @@ const char *check_support(
         return "ok";
     }
 
+    if (!strcmp(feature, "icmp")) {
+        return check_protocol_support(net_state, IPPROTO_ICMP);
+    }
+
+    if (!strcmp(feature, "udp")) {
+        return check_protocol_support(net_state, IPPROTO_UDP);
+    }
+
     return "no";
 }
 
 /*  Handle a check-support request by checking for a particular feature  */
 static
 void check_support_command(
-    const struct command_t *command)
+    const struct command_t *command,
+    struct net_state_t *net_state)
 {
     const char *feature;
     const char *support;
@@ -92,7 +115,7 @@ void check_support_command(
         return;
     }
 
-    support = check_support(feature);
+    support = check_support(feature, net_state);
     printf("%d feature-support support %s\n", command->token, support);
 }
 
@@ -120,6 +143,25 @@ bool decode_probe_argument(
         param->address = value;
     }
 
+    /*  Protocol for the probe  */
+    if (!strcmp(name, "protocol")) {
+        if (!strcmp(value, "icmp")) {
+            param->protocol = IPPROTO_ICMP;
+        } else if (!strcmp(value, "udp")) {
+            param->protocol = IPPROTO_UDP;
+        } else {
+            return false;
+        }
+    }
+
+    /*  Destination port for the probe  */
+    if (!strcmp(name, "port")) {
+        param->dest_port = strtol(value, &endstr, 10);
+        if (*endstr != 0) {
+            return false;
+        }
+    }
+
     /*  Time-to-live values  */
     if (!strcmp(name, "ttl")) {
         param->ttl = strtol(value, &endstr, 10);
@@ -153,6 +195,8 @@ void send_probe_command(
     /*  We will prepare a probe_param_t for send_probe.  */
     memset(&param, 0, sizeof(struct probe_param_t));
     param.command_token = command->token;
+    param.protocol = IPPROTO_ICMP;
+    param.dest_port = 7; /* Use the 'echo' port as the default destination */
     param.ttl = 255;
     param.timeout = 10;
 
@@ -180,7 +224,7 @@ void dispatch_command(
     struct net_state_t *net_state)
 {
     if (!strcmp(command->command_name, "check-support")) {
-        check_support_command(command);
+        check_support_command(command, net_state);
     } else if (!strcmp(command->command_name, "send-probe")) {
         send_probe_command(command, net_state);
     } else {
index 4eed0d754a84676a3186559fe616080ca0181231..8b36cde1153bb0d14690fe2861db812f7569ad2d 100644 (file)
@@ -20,6 +20,7 @@
 
 #include <errno.h>
 #include <string.h>
+#include <sys/socket.h>
 #include <unistd.h>
 
 #include "protocols.h"
@@ -31,31 +32,21 @@ struct checksum_source_t
     size_t size;
 };
 
-/*
-    Compute the IP checksum (or ICMP checksum) of a packet.
-    We may need to use data from multiple sources, to checksum
-    the "psuedo-header" for UDP or ICMPv6.
-*/
+/*  Compute the IP checksum (or ICMP checksum) of a packet.  */
 static
 uint16_t compute_checksum(
-    struct checksum_source_t *source,
-    int source_count)
+    const void *packet,
+    int size)
 {
-    int i, j;
-    const uint8_t *bytes;
-    size_t size;
+    const uint8_t *packet_bytes = (uint8_t *)packet;
     uint32_t sum = 0;
+    int i;
 
-    for (i = 0; i < source_count; i++) {
-        bytes = (uint8_t *)source[i].data;
-        size = source[i].size;
-
-        for (j = 0; j < size; j++) {
-            if ((j & 1) == 0) {
-                sum += bytes[j] << 8;
-            } else {
-                sum += bytes[j];
-            }
+    for (i = 0; i < size; i++) {
+        if ((i & 1) == 0) {
+            sum += packet_bytes[i] << 8;
+        } else {
+            sum += packet_bytes[i];
         }
     }
 
@@ -74,50 +65,6 @@ uint16_t compute_checksum(
     return (~sum & 0xffff);
 }
 
-/*  Compute the checksum from a single source of data  */
-static
-uint16_t simple_checksum(
-    const void *packet,
-    size_t size)
-{
-    struct checksum_source_t source;
-
-    source.data = packet;
-    source.size = size;
-
-    return compute_checksum(&source, 1);
-}
-
-/*
-    ICMPv6 and UDPv6 use a pseudo-header with a different layout
-    from the real IPv6 header for checksum purposes.  We'll fill
-    in the psuedo-header and use it to start the checksum against
-    the packet.
-*/
-static
-uint16_t pseudo6_checksum(
-    const void *ip_packet,
-    const void *packet,
-    size_t size)
-{
-    const struct IP6Header *ip = (struct IP6Header *)ip_packet;
-    struct IP6PseudoHeader pseudo;
-    struct checksum_source_t source[2];
-
-    memcpy(pseudo.saddr, ip->saddr, sizeof(struct in6_addr));
-    memcpy(pseudo.daddr, ip->daddr, sizeof(struct in6_addr));
-    pseudo.len = ip->len;
-    memset(pseudo.zero, 0, sizeof(pseudo.zero));
-    pseudo.protocol = ip->protocol;
-
-    source[0].data = &pseudo;
-    source[0].size = sizeof(struct IP6PseudoHeader);
-    source[1].data = packet;
-    source[1].size = size;
-
-    return compute_checksum(source, 2);
-}
-
 /*  Encode the IP header length field in the order required by the OS.  */
 static
 uint16_t length_byte_swap(
@@ -150,41 +97,11 @@ void construct_ip4_header(
     ip->version = 0x45;
     ip->len = length_byte_swap(net_state, packet_size);
     ip->ttl = param->ttl;
-    ip->protocol = IPPROTO_ICMP;
+    ip->protocol = param->protocol;
     memcpy(&ip->saddr, &srcaddr4->sin_addr, sizeof(uint32_t));
     memcpy(&ip->daddr, &destaddr4->sin_addr, sizeof(uint32_t));
 }
 
-/*  Construct a header for IP version 6  */
-static
-void construct_ip6_header(
-    const struct net_state_t *net_state,
-    char *packet_buffer,
-    int packet_size,
-    const struct sockaddr_storage *srcaddr,
-    const struct sockaddr_storage *destaddr,
-    const struct probe_param_t *param)
-{
-    struct IP6Header *ip;
-    int payload_size;
-    struct sockaddr_in6 *srcaddr6 = (struct sockaddr_in6 *)srcaddr;
-    struct sockaddr_in6 *destaddr6 = (struct sockaddr_in6 *)destaddr;
-
-    if (!net_state->platform.ipv6_header_constructed) {
-        return;
-    }
-
-    ip = (struct IP6Header *)&packet_buffer[0];
-    payload_size = packet_size - sizeof(struct IP6Header);
-
-    ip->version = 0x60;
-    ip->len = htons(payload_size);
-    ip->protocol = IPPROTO_ICMPV6;
-    ip->ttl = param->ttl;
-    memcpy(&ip->saddr, &srcaddr6->sin6_addr, sizeof(struct in6_addr));
-    memcpy(&ip->daddr, &destaddr6->sin6_addr, sizeof(struct in6_addr));
-}
-
 /*  Construct an ICMP header for IPv4  */
 static
 void construct_icmp4_header(
@@ -202,36 +119,99 @@ void construct_icmp4_header(
     icmp->type = ICMP_ECHO;
     icmp->id = htons(getpid());
     icmp->sequence = htons(param->command_token);
-    icmp->checksum = htons(simple_checksum(icmp, icmp_size));
+    icmp->checksum = htons(compute_checksum(icmp, icmp_size));
 }
 
 /*  Construct an ICMP header for IPv6  */
 static
-void construct_icmp6_header(
+int construct_icmp6_packet(
     const struct net_state_t *net_state,
     char *packet_buffer,
     int packet_size,
     const struct probe_param_t *param)
 {
     struct ICMPHeader *icmp;
-    int icmp_size;
 
-    if (net_state->platform.ipv6_header_constructed) {
-        icmp = (struct ICMPHeader *)&packet_buffer[sizeof(struct IP6Header)];
-        icmp_size = packet_size - sizeof(struct IP6Header);
-    } else {
-        icmp = (struct ICMPHeader *)packet_buffer;
-        icmp_size = packet_size;
-    }
+    icmp = (struct ICMPHeader *)packet_buffer;
 
     icmp->type = ICMP6_ECHO;
     icmp->id = htons(getpid());
     icmp->sequence = htons(param->command_token);
 
-    if (net_state->platform.ipv6_header_constructed) {
-        icmp->checksum = htons(
-            pseudo6_checksum(packet_buffer, icmp, icmp_size));
+    if (setsockopt(
+            net_state->platform.icmp6_send_socket, IPPROTO_IPV6,
+            IPV6_UNICAST_HOPS, &param->ttl, sizeof(int))) {
+        return -errno;
+    }
+
+    return 0;
+}
+
+/*
+    Construct a header for UDP probes.  We'll use the source port
+    as the command token, so that we can identify the probe when its
+    header is returned embedded in an ICMP reply.
+*/
+static
+void construct_udp4_header(
+    const struct net_state_t *net_state,
+    char *packet_buffer,
+    int packet_size,
+    const struct probe_param_t *param)
+{
+    struct UDPHeader *udp;
+    int udp_size;
+
+    udp = (struct UDPHeader *)&packet_buffer[sizeof(struct IPHeader)];
+    udp_size = packet_size - sizeof(struct IPHeader);
+
+    udp->srcport = htons(param->command_token);
+    udp->dstport = htons(param->dest_port);
+    udp->length = htons(udp_size);
+    udp->checksum = 0;
+}
+
+/*  Construct a header for UDPv6 probes  */
+static
+int construct_udp6_packet(
+    const struct net_state_t *net_state,
+    char *packet_buffer,
+    int packet_size,
+    const struct probe_param_t *param)
+{
+    int udp_socket = net_state->platform.udp6_send_socket;
+    struct UDPHeader *udp;
+    int udp_size;
+
+    udp = (struct UDPHeader *)packet_buffer;
+    udp_size = packet_size;
+
+    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.
+    */
+    int chksum_offset = (char *)&udp->checksum - (char *)udp;
+    if (setsockopt(
+            udp_socket, IPPROTO_IPV6,
+            IPV6_CHECKSUM, &chksum_offset, sizeof(int))) {
+
+        return -errno;
+    }
+
+    return 0;
 }
 
 /*
@@ -246,18 +226,23 @@ int compute_packet_size(
     const struct net_state_t *net_state,
     const struct probe_param_t *param)
 {
-    int packet_size = 0;
+    int packet_size;
 
     if (param->ip_version == 6) {
-        if (net_state->platform.ipv6_header_constructed) {
-            packet_size = sizeof(struct IP6Header);
-        }
+        packet_size = 0;
     } else if (param->ip_version == 4) {
         packet_size = sizeof(struct IPHeader);
     } else {
         return -EINVAL;
     }
-    packet_size += sizeof(struct ICMPHeader);
+
+    if (param->protocol == IPPROTO_ICMP) {
+        packet_size += sizeof(struct ICMPHeader);
+    } else if (param->protocol == IPPROTO_UDP) {
+        packet_size += sizeof(struct UDPHeader);
+    } else {
+        return -EINVAL;
+    }
 
     return packet_size;
 }
@@ -290,18 +275,35 @@ int construct_packet(
 
     memset(packet_buffer, 0, packet_size);
 
+    err = 0;
     if (param->ip_version == 6) {
-        construct_ip6_header(
-            net_state, packet_buffer, packet_size,
-            &src_sockaddr, dest_sockaddr, param);
-        construct_icmp6_header(
-            net_state, packet_buffer, packet_size, param);
+        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;
+        }
     } else {
         construct_ip4_header(
             net_state, packet_buffer, packet_size,
             &src_sockaddr, dest_sockaddr, param);
-        construct_icmp4_header(
-            net_state, packet_buffer, packet_size, 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;
+        }
+    }
+
+    if (err) {
+        return err;
     }
 
     return packet_size;
index 80fcfff2840119203c1bc8503abaff31e94ab32a..c3a087d7a67826adb08164328bcbcf04999cb7ee 100644 (file)
@@ -53,12 +53,13 @@ void find_and_receive_probe(
     const struct sockaddr_storage *remote_addr,
     struct timeval timestamp,
     int icmp_type,
+    int protocol,
     int icmp_id,
     int icmp_sequence)
 {
     struct probe_t *probe;
 
-    probe = find_probe(net_state, icmp_id, icmp_sequence);
+    probe = find_probe(net_state, protocol, icmp_id, icmp_sequence);
     if (probe == NULL) {
         return;
     }
@@ -66,51 +67,148 @@ void find_and_receive_probe(
     receive_probe(probe, icmp_type, remote_addr, timestamp);
 }
 
+/*
+    We've received an ICMP message with an embedded IP packet.
+    We will try to determine which of our outgoing probes
+    corresponds to the embedded IP packet and record the response.
+*/
+static
+void handle_inner_ip4_packet(
+    struct net_state_t *net_state,
+    const struct sockaddr_storage *remote_addr,
+    int icmp_result,
+    const struct IPHeader *ip,
+    int packet_length,
+    struct timeval timestamp)
+{
+    const int ip_icmp_size =
+        sizeof(struct IPHeader) + sizeof(struct ICMPHeader);
+    const int ip_udp_size =
+        sizeof(struct IPHeader) + sizeof(struct UDPHeader);
+    const struct ICMPHeader *icmp;
+    const struct UDPHeader *udp;
+
+    if (ip->protocol == IPPROTO_ICMP) {
+        if (packet_length < ip_icmp_size) {
+            return;
+        }
+
+        icmp = (struct ICMPHeader *)(ip + 1);
+
+        find_and_receive_probe(
+            net_state, remote_addr, timestamp, icmp_result,
+            IPPROTO_ICMP, icmp->id, icmp->sequence);
+    } else if (ip->protocol == IPPROTO_UDP) {
+        if (packet_length < ip_udp_size) {
+            return;
+        }
+
+        udp = (struct UDPHeader *)(ip + 1);
+
+        find_and_receive_probe(
+            net_state, remote_addr, timestamp, icmp_result,
+            IPPROTO_UDP, 0, udp->srcport);
+    }
+}
+
+/*
+    Examine the IPv6 header embedded in a returned ICMPv6 packet
+    in order to match it with a probe which we previously sent.
+*/
+static
+void handle_inner_ip6_packet(
+    struct net_state_t *net_state,
+    const struct sockaddr_storage *remote_addr,
+    int icmp_result,
+    const struct IP6Header *ip,
+    int packet_length,
+    struct timeval timestamp)
+{
+    const int ip_icmp_size =
+        sizeof(struct IP6Header) + sizeof(struct ICMPHeader);
+    const int ip_udp_size =
+        sizeof(struct IP6Header) + sizeof(struct UDPHeader);
+    const struct ICMPHeader *icmp;
+    const struct UDPHeader *udp;
+
+    if (ip->protocol == IPPROTO_ICMPV6) {
+        if (packet_length < ip_icmp_size) {
+            return;
+        }
+
+        icmp = (struct ICMPHeader *)(ip + 1);
+
+        find_and_receive_probe(
+            net_state, remote_addr, timestamp, icmp_result,
+            IPPROTO_ICMP, icmp->id, icmp->sequence);
+    } else if (ip->protocol == IPPROTO_UDP) {
+        if (packet_length < ip_udp_size) {
+            return;
+        }
+
+        udp = (struct UDPHeader *)(ip + 1);
+
+        find_and_receive_probe(
+            net_state, remote_addr, timestamp, icmp_result,
+            IPPROTO_UDP, 0, udp->srcport);
+    }
+}
+
 /*
     Decode the ICMP header received and try to find a probe which it
     is in response to.
 */
 static
-void handle_received_icmpv4_packet(
+void handle_received_icmp4_packet(
     struct net_state_t *net_state,
     const struct sockaddr_storage *remote_addr,
     const struct ICMPHeader *icmp,
     int packet_length,
     struct timeval timestamp)
 {
-    const int icmp_ip_icmp_size =
-        sizeof(struct ICMPHeader) +
-        sizeof(struct IPHeader) + sizeof(struct ICMPHeader);
+    const int icmp_ip_size =
+        sizeof(struct ICMPHeader) + sizeof(struct IPHeader);
     const struct IPHeader *inner_ip;
-    const struct ICMPHeader *inner_icmp;
+    int inner_size = packet_length - sizeof(struct ICMPHeader);
 
     /*  If we get an echo reply, our probe reached the destination host  */
     if (icmp->type == ICMP_ECHOREPLY) {
         find_and_receive_probe(
             net_state, remote_addr, timestamp,
-            ICMP_ECHOREPLY, icmp->id, icmp->sequence);
+            ICMP_ECHOREPLY, IPPROTO_ICMP, icmp->id, icmp->sequence);
     }
 
+    if (packet_length < icmp_ip_size) {
+        return;
+    }
+    inner_ip = (struct IPHeader *)(icmp + 1);
+
     /*
         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 < 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);
+        handle_inner_ip4_packet(
+            net_state, remote_addr,
+            ICMP_TIME_EXCEEDED, inner_ip, inner_size, timestamp);
+    }
 
-        find_and_receive_probe(
-            net_state, remote_addr, timestamp,
-            ICMP_TIME_EXCEEDED, inner_icmp->id, inner_icmp->sequence);
+    if (icmp->type == ICMP_DEST_UNREACH) {
+        /*
+            We'll get a ICMP_PORT_UNREACH when a non-ICMP probe
+            reaches its final destination.  (Assuming that port isn't
+            open on the destination host.)
+        */
+        if (icmp->code == ICMP_PORT_UNREACH) {
+            handle_inner_ip4_packet(
+                net_state, remote_addr,
+                ICMP_ECHOREPLY, inner_ip, inner_size, timestamp);
+        }
     }
 }
 
@@ -120,36 +218,41 @@ void handle_received_icmpv4_packet(
     constants differ.
 */
 static
-void handle_received_icmpv6_packet(
+void handle_received_icmp6_packet(
     struct net_state_t *net_state,
     const struct sockaddr_storage *remote_addr,
     const struct ICMPHeader *icmp,
     int packet_length,
     struct timeval timestamp)
 {
-    const int icmp_ip_icmp_size =
-        sizeof(struct ICMPHeader) +
-        sizeof(struct IP6Header) + sizeof(struct ICMPHeader);
+    const int icmp_ip_size =
+        sizeof(struct ICMPHeader) + sizeof(struct IP6Header);
     const struct IP6Header *inner_ip;
-    const struct ICMPHeader *inner_icmp;
+    int inner_size = packet_length - sizeof(struct ICMPHeader);
 
     if (icmp->type == ICMP6_ECHOREPLY) {
         find_and_receive_probe(
-            net_state, remote_addr, timestamp,
-            ICMP_ECHOREPLY, icmp->id, icmp->sequence);
+            net_state, remote_addr, timestamp, ICMP_ECHOREPLY,
+            IPPROTO_ICMP, icmp->id, icmp->sequence);
     }
 
-    if (icmp->type == ICMP6_TIME_EXCEEDED) {
-        if (packet_length < icmp_ip_icmp_size) {
-            return;
-        }
+    if (packet_length < icmp_ip_size) {
+        return;
+    }
+    inner_ip = (struct IP6Header *)(icmp + 1);
 
-        inner_ip = (struct IP6Header *)(icmp + 1);
-        inner_icmp = (struct ICMPHeader *)(inner_ip + 1);
+    if (icmp->type == ICMP6_TIME_EXCEEDED) {
+        handle_inner_ip6_packet(
+            net_state, remote_addr,
+            ICMP_TIME_EXCEEDED, inner_ip, inner_size, timestamp);
+    }
 
-        find_and_receive_probe(
-            net_state, remote_addr, timestamp,
-            ICMP_TIME_EXCEEDED, inner_icmp->id, inner_icmp->sequence);
+    if (icmp->type == ICMP6_DEST_UNREACH) {
+        if (icmp->code == ICMP6_PORT_UNREACH) {
+            handle_inner_ip6_packet(
+                net_state, remote_addr,
+                ICMP_ECHOREPLY, inner_ip, inner_size, timestamp);
+        }
     }
 }
 
@@ -158,7 +261,7 @@ void handle_received_icmpv6_packet(
     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.
 */
-void handle_received_ipv4_packet(
+void handle_received_ip4_packet(
     struct net_state_t *net_state,
     const struct sockaddr_storage *remote_addr,
     const void *packet,
@@ -184,7 +287,7 @@ void handle_received_ipv4_packet(
     icmp = (struct ICMPHeader *)(ip + 1);
     icmp_length = packet_length - sizeof(struct IPHeader);
 
-    handle_received_icmpv4_packet(
+    handle_received_icmp4_packet(
         net_state, remote_addr, icmp, icmp_length, timestamp);
 }
 
@@ -193,7 +296,7 @@ void handle_received_ipv4_packet(
     in received packets, so we can assume the packet we got starts
     with the ICMP packet.
 */
-void handle_received_ipv6_packet(
+void handle_received_ip6_packet(
     struct net_state_t *net_state,
     const struct sockaddr_storage *remote_addr,
     const void *packet,
@@ -204,6 +307,6 @@ void handle_received_ipv6_packet(
 
     icmp = (struct ICMPHeader *)packet;
 
-    handle_received_icmpv6_packet(
+    handle_received_icmp6_packet(
         net_state, remote_addr, icmp, packet_length, timestamp);
 }
index c59131809809bf784144a6e705cd5745bdc5a7bb..616d19c556efa87d47cc4619e3037d766f97d9b6 100644 (file)
@@ -28,14 +28,14 @@ typedef void (*received_packet_func_t)(
     int packet_length,
     struct timeval timestamp);
 
-void handle_received_ipv4_packet(
+void handle_received_ip4_packet(
     struct net_state_t *net_state,
     const struct sockaddr_storage *remote_addr,
     const void *packet,
     int packet_length,
     struct timeval timestamp);
 
-void handle_received_ipv6_packet(
+void handle_received_ip6_packet(
     struct net_state_t *net_state,
     const struct sockaddr_storage *remote_addr,
     const void *packet,
index 4c98bcc31d31cc950d93a74603b1b019cae88186..b4d4ad93ccacc02fb5490888edef71a27ba0ce21 100644 (file)
@@ -136,6 +136,7 @@ int count_in_flight_probes(
 */
 struct probe_t *find_probe(
     struct net_state_t *net_state,
+    int protocol,
     int icmp_id,
     int icmp_sequence)
 {
@@ -143,11 +144,17 @@ struct probe_t *find_probe(
     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.
+        ICMP has room for an id to check against our process, but
+        UDP doesn't.
     */
-    if (icmp_id != htons(getpid())) {
-        return NULL;
+    if (protocol == IPPROTO_ICMP) {
+        /*
+            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++) {
@@ -229,6 +236,8 @@ int find_source_addr(
     struct sockaddr_in *destaddr4;
     struct sockaddr_in6 *destaddr6;
     struct sockaddr_storage dest_with_port;
+    struct sockaddr_in *srcaddr4;
+    struct sockaddr_in6 *srcaddr6;
 
     dest_with_port = *destaddr;
 
@@ -267,5 +276,19 @@ int find_source_addr(
 
     close(sock);
 
+    /*
+        Zero the port, as we may later use this address to finding, and
+        we don't want to use the port from the socket we just created.
+    */
+    if (destaddr->ss_family == AF_INET6) {
+        srcaddr6 = (struct sockaddr_in6 *)srcaddr;
+
+        srcaddr6->sin6_port = 0;
+    } else {
+        srcaddr4 = (struct sockaddr_in *)srcaddr;
+
+        srcaddr4->sin_port = 0;
+    }
+
     return 0;
 }
index c99c852e7b1563784145b558fc89409566adbfed..6b66150654f56a85ec57bad14c3db135a6eb0c65 100644 (file)
@@ -45,6 +45,12 @@ struct probe_param_t
     /*  The IP address to probe  */
     const char *address;
 
+    /*  Protocol for the probe, using the IPPROTO_* defines  */
+    int protocol;
+
+    /*  The destination port for non-ICMP probes  */
+    int dest_port;
+
     /*  Time to live for the transmited probe  */
     int ttl;
 
@@ -81,6 +87,10 @@ void init_net_state_privileged(
 void init_net_state(
     struct net_state_t *net_state);
 
+bool is_protocol_supported(
+    struct net_state_t *net_state,
+    int protocol);
+
 bool get_next_probe_timeout(
     const struct net_state_t *net_state,
     struct timeval *timeout);
@@ -117,6 +127,7 @@ int count_in_flight_probes(
 
 struct probe_t *find_probe(
     struct net_state_t *net_state,
+    int protocol,
     int icmp_id,
     int icmp_sequence);
 
index b00efd5588de3a8cf839a35cb1c832f3f04bd41d..771d101894b8005168d923663713ee8d4df70081 100644 (file)
@@ -48,6 +48,18 @@ void init_net_state(
     }
 }
 
+/*  On Windows, we only support ICMP probes  */
+bool is_protocol_supported(
+    struct net_state_t *net_state,
+    int protocol)
+{
+    if (protocol == IPPROTO_ICMP) {
+        return true;
+    }
+
+    return false;
+}
+
 /*
     The overlapped I/O style completion routine to be called by
     Windows during an altertable wait when an ICMP probe has
index 35b3d9ded60057f6e12183c050d7833b4898e4ca..e840a01fa7876d48538d1bec5971dc2f35c3d93c 100644 (file)
 /*  Use the "jumbo" frame size as the max packet size  */
 #define PACKET_BUFFER_SIZE 9000
 
-/*  Set the IPv6 options affecting and outgoing IPv6 packet  */
-static
-void set_ipv6_socket_options(
-    int socket,
-    const struct probe_param_t *param)
-{
-    if (setsockopt(
-            socket, IPPROTO_IPV6,
-            IPV6_UNICAST_HOPS, &param->ttl, sizeof(int))) {
-
-        perror("Failure to set IPV6_UNICAST_HOPS");
-        exit(1);
-    }
-}
-
 /*  A wrapper around sendto for mixed IPv4 and IPv6 sending  */
 static
 int send_packet(
@@ -59,20 +44,26 @@ int send_packet(
     int packet_size,
     const struct sockaddr_storage *sockaddr)
 {
-    int send_socket;
+    int send_socket = 0;
     int sockaddr_length;
 
     if (sockaddr->ss_family == AF_INET6) {
-        send_socket = net_state->platform.ipv6_send_socket;
         sockaddr_length = sizeof(struct sockaddr_in6);
 
-        if (!net_state->platform.ipv6_header_constructed) {
-            set_ipv6_socket_options(send_socket, param);
+        if (param->protocol == IPPROTO_ICMP) {
+            send_socket = net_state->platform.icmp6_send_socket;
+        } else if (param->protocol == IPPROTO_UDP) {
+            send_socket = net_state->platform.udp6_send_socket;
         }
-    } else {
-        assert(sockaddr->ss_family == AF_INET);
-        send_socket = net_state->platform.ipv4_send_socket;
+    } else if (sockaddr->ss_family == AF_INET) {
         sockaddr_length = sizeof(struct sockaddr_in);
+
+        send_socket = net_state->platform.ip4_send_socket;
+    }
+
+    if (send_socket == 0) {
+        errno = EINVAL;
+        return -1;
     }
 
     return sendto(
@@ -103,6 +94,7 @@ void check_length_order(
 
     memset(&param, 0, sizeof(struct probe_param_t));
     param.ip_version = 4;
+    param.protocol = IPPROTO_ICMP;
     param.ttl = 255;
     param.address = "127.0.0.1";
 
@@ -168,7 +160,7 @@ void set_socket_nonblocking(
 
 /*  Open the raw sockets for sending/receiving IPv4 packets  */
 static
-void open_ipv4_sockets(
+void open_ip4_sockets(
     struct net_state_t *net_state)
 {
     int send_socket;
@@ -202,42 +194,28 @@ void open_ipv4_sockets(
         exit(1);
     }
 
-    net_state->platform.ipv4_send_socket = send_socket;
-    net_state->platform.ipv4_recv_socket = recv_socket;
+    net_state->platform.ip4_send_socket = send_socket;
+    net_state->platform.ip4_recv_socket = recv_socket;
 }
 
 /*  Open the raw sockets for sending/receiving IPv6 packets  */
 static
-void open_ipv6_sockets(
+void open_ip6_sockets(
     struct net_state_t *net_state)
 {
-    int send_socket;
+    int send_socket_icmp;
+    int send_socket_udp;
     int recv_socket;
-    int send_protocol;
 
-    /*
-        Linux allows us to construct our own IPv6 header, so
-        we'll prefer that method for more explicit control.
-
-        Other OSes, such as MacOS, don't allow this, and on
-        those platforms we must use setsockopt() to control
-        fields of the IP header.
-    */
-#ifdef PLATFORM_LINUX
-    net_state->platform.ipv6_header_constructed = true;
-#else
-    net_state->platform.ipv6_header_constructed = false;
-#endif
-
-    if (net_state->platform.ipv6_header_constructed) {
-        send_protocol = IPPROTO_RAW;
-    } else {
-        send_protocol = IPPROTO_ICMPV6;
+    send_socket_icmp = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
+    if (send_socket_icmp == -1) {
+        perror("Failure opening ICMPv6 send socket");
+        exit(1);
     }
 
-    send_socket = socket(AF_INET6, SOCK_RAW, send_protocol);
-    if (send_socket == -1) {
-        perror("Failure opening IPv6 send socket");
+    send_socket_udp = socket(AF_INET6, SOCK_RAW, IPPROTO_UDP);
+    if (send_socket_udp == -1) {
+        perror("Failure opening UDPv6 send socket");
         exit(1);
     }
 
@@ -249,8 +227,9 @@ void open_ipv6_sockets(
 
     set_socket_nonblocking(recv_socket);
 
-    net_state->platform.ipv6_send_socket = send_socket;
-    net_state->platform.ipv6_recv_socket = recv_socket;
+    net_state->platform.icmp6_send_socket = send_socket_icmp;
+    net_state->platform.udp6_send_socket = send_socket_udp;
+    net_state->platform.ip6_recv_socket = recv_socket;
 }
 
 /*
@@ -263,8 +242,8 @@ void init_net_state_privileged(
 {
     memset(net_state, 0, sizeof(struct net_state_t));
 
-    open_ipv4_sockets(net_state);
-    open_ipv6_sockets(net_state);
+    open_ip4_sockets(net_state);
+    open_ip6_sockets(net_state);
 }
 
 /*
@@ -274,12 +253,28 @@ void init_net_state_privileged(
 void init_net_state(
     struct net_state_t *net_state)
 {
-    set_socket_nonblocking(net_state->platform.ipv4_recv_socket);
-    set_socket_nonblocking(net_state->platform.ipv6_recv_socket);
+    set_socket_nonblocking(net_state->platform.ip4_recv_socket);
+    set_socket_nonblocking(net_state->platform.ip6_recv_socket);
 
     check_length_order(net_state);
 }
 
+/*  Returns true if we can transmit probes using the specified protocol  */
+bool is_protocol_supported(
+    struct net_state_t *net_state,
+    int protocol)
+{
+    if (protocol == IPPROTO_ICMP) {
+        return true;
+    }
+
+    if (protocol == IPPROTO_UDP) {
+        return true;
+    }
+
+    return false;
+}
+
 /*  Craft a custom ICMP packet for a network probe.  */
 void send_probe(
     struct net_state_t *net_state,
@@ -330,8 +325,19 @@ void send_probe(
     if (send_packet(
             net_state, param, packet, packet_size, &dest_sockaddr) == -1) {
 
-        perror("Failure sending probe");
-        exit(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);
+        }
+
+        free_probe(probe);
+        return;
     }
 
     probe->platform.timeout_time = probe->platform.departure_time;
@@ -401,12 +407,12 @@ void receive_replies(
     struct net_state_t *net_state)
 {
     receive_replies_from_socket(
-        net_state, net_state->platform.ipv4_recv_socket,
-        handle_received_ipv4_packet);
+        net_state, net_state->platform.ip4_recv_socket,
+        handle_received_ip4_packet);
 
     receive_replies_from_socket(
-        net_state, net_state->platform.ipv6_recv_socket,
-        handle_received_ipv6_packet);
+        net_state, net_state->platform.ip6_recv_socket,
+        handle_received_ip6_packet);
 }
 
 /*
index d0080a4c0fff6b70f7ee5d6fb155af1d93e32b8f..75c97758634ab832a300b851abb49906e27cfd06 100644 (file)
@@ -34,28 +34,25 @@ struct probe_platform_t
 struct net_state_platform_t
 {
     /*  Socket used to send raw IPv4 packets  */
-    int ipv4_send_socket;
+    int ip4_send_socket;
 
     /*  Socket used to receive IPv4 ICMP replies  */
-    int ipv4_recv_socket;
+    int ip4_recv_socket;
 
-    /*  Send socket for IPv6 packets  */
-    int ipv6_send_socket;
+    /*  Send socket for ICMPv6 packets  */
+    int icmp6_send_socket;
+
+    /*  Send socket for UDPv6 packets  */
+    int udp6_send_socket;
 
     /*  Receive socket for IPv6 packets  */
-    int ipv6_recv_socket;
+    int ip6_recv_socket;
 
     /*
         true if we should encode the IP header length in host order.
         (as opposed to network order)
     */
     bool ip_length_host_order;
-
-    /*
-        true if we are allowed to construct the IPv6 header, false if
-        we need to let the network stack do it for us.
-    */
-    bool ipv6_header_constructed;
 };
 
 #endif
index 13046c820a28a0e1729c9decbf41f33dea204de2..84d51a1d54122eed991d1ab7168467f25b9ad9d6 100644 (file)
 #define ICMP_ECHO 8
 #define ICMP_TIME_EXCEEDED 11
 
+/*  ICMP_DEST_UNREACH codes */
+#define ICMP_PORT_UNREACH 3
+
 /*  ICMPv6 type codes  */
+#define ICMP6_DEST_UNREACH 1
 #define ICMP6_TIME_EXCEEDED 3
 #define ICMP6_ECHO 128
 #define ICMP6_ECHOREPLY 129
 
+/*  ICMP6_DEST_UNREACH codes */
+#define ICMP6_PORT_UNREACH 4
+
 /*  We can't rely on header files to provide this information, because
     the fields have different names between, for instance, Linux and 
     Solaris  */
@@ -64,7 +71,7 @@ struct SCTPHeader {
 };
 
 /* Structure of an IPv4 UDP pseudoheader.  */
-struct UDPv4PHeader {
+struct UDPPseudoHeader {
     uint32_t saddr;
     uint32_t daddr;
     uint8_t zero;
index 348831d59ab04570bd0f26de3c89137ca42efe35..2fd23ef3886fecf512137006270a5ca121098bf6 100644 (file)
@@ -41,21 +41,21 @@ void wait_for_activity(
     struct timeval *select_timeout;
     int ready_count;
     int command_stream = command_buffer->command_stream;
-    int ipv4_socket = net_state->platform.ipv4_recv_socket;
-    int ipv6_socket = net_state->platform.ipv6_recv_socket;
+    int ip4_socket = net_state->platform.ip4_recv_socket;
+    int ip6_socket = net_state->platform.ip6_recv_socket;
 
     FD_ZERO(&read_set);
     FD_SET(command_stream, &read_set);
     nfds = command_stream + 1;
 
-    FD_SET(ipv4_socket, &read_set);
-    if (ipv4_socket >= nfds) {
-        nfds = ipv4_socket + 1;
+    FD_SET(ip4_socket, &read_set);
+    if (ip4_socket >= nfds) {
+        nfds = ip4_socket + 1;
     }
 
-    FD_SET(ipv6_socket, &read_set);
-    if (ipv6_socket >= nfds) {
-        nfds = ipv6_socket + 1;
+    FD_SET(ip6_socket, &read_set);
+    if (ip6_socket >= nfds) {
+        nfds = ip6_socket + 1;
     }
 
     while (true) {
index 8c90ec51f563eabfdea396d156da76cba2a52555..7a807c62725c1da7f0a1d15b5d58168a17688236 100755 (executable)
@@ -20,7 +20,6 @@
 '''Test mtr-packet's command parsing.'''
 
 
-import re
 import time
 import unittest
 
@@ -58,8 +57,6 @@ class TestCommandParse(mtrpacket.MtrPacketTest):
     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',
@@ -69,29 +66,30 @@ class TestCommandParse(mtrpacket.MtrPacketTest):
 
         for cmd in bad_commands:
             self.write_command(cmd)
-            reply = self.read_reply()
-            match = re.match(invalid_argument_regex, reply)
-            self.assertIsNotNone(match)
+            reply = self.parse_reply()
+            self.assertEqual(reply.command_name, 'invalid-argument')
 
     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$')
+            ('31 check-support feature ip-4', 'ok'),
+            ('32 check-support feature send-probe', 'ok'),
+            ('33 check-support feature bogus-feature', 'no')
         ]
 
-        for (request, regex) in feature_tests:
+        self.write_command('30 check-support feature version')
+        reply = self.parse_reply()
+        self.assertEqual(reply.token, 30)
+        self.assertEqual(reply.command_name, 'feature-support')
+        self.assertIn('support', reply.argument)
+
+        for (request, expected) in feature_tests:
             self.write_command(request)
-            reply = self.read_reply()
-            match = re.match(regex, reply)
-            self.assertIsNotNone(match)
+            reply = self.parse_reply()
+            self.assertEqual(reply.command_name, 'feature-support')
+            self.assertIn('support', reply.argument)
+            self.assertEqual(reply.argument['support'], expected)
 
     def test_command_overflow(self):
         'Test overflowing the incoming command buffer'
index d9bbf6c423b89057d704c59f84004302af123322..9ffc69c5663966135ed61de57aae964aa623c96c 100644 (file)
@@ -26,6 +26,21 @@ import sys
 import time
 import unittest
 
+#
+#  typing is used for mypy type checking, but isn't required to run,
+#  so it's okay if we can't import it.
+#
+try:
+    # pylint: disable=locally-disabled, unused-import
+    from typing import Dict, List
+except ImportError:
+    pass
+
+
+class MtrPacketExecuteError(Exception):
+    "Exception raised when MtrPacketTest can't execute mtr-packet"
+    pass
+
 
 class ReadReplyTimeout(Exception):
     'Exception raised by TestProbe.read_reply upon timeout'
@@ -39,6 +54,12 @@ class WriteCommandTimeout(Exception):
     pass
 
 
+class MtrPacketReplyParseError(Exception):
+    "Exception raised when MtrPacketReply can't parse the reply string"
+
+    pass
+
+
 def set_nonblocking(file_descriptor):  # type: (int) -> None
     'Put a file descriptor into non-blocking mode'
 
@@ -48,6 +69,40 @@ def set_nonblocking(file_descriptor):  # type: (int) -> None
     fcntl.fcntl(file_descriptor, fcntl.F_SETFL, flags | os.O_NONBLOCK)
 
 
+# pylint: disable=locally-disabled, too-few-public-methods
+class MtrPacketReply(object):
+    'A parsed reply from mtr-packet'
+
+    def __init__(self, reply):  # type: (unicode) -> None
+        self.token = 0  # type: int
+        self.command_name = None  # type: unicode
+        self.argument = {}  # type: Dict[unicode, unicode]
+
+        self.parse_reply(reply)
+
+    def parse_reply(self, reply):  # type (unicode) -> None
+        'Parses a reply string into members for the instance of this class'
+
+        tokens = reply.split()  # type List[unicode]
+
+        try:
+            self.token = int(tokens[0])
+            self.command_name = tokens[1]
+        except IndexError:
+            raise MtrPacketReplyParseError(reply)
+
+        i = 2
+        while i < len(tokens):
+            try:
+                name = tokens[i]
+                value = tokens[i + 1]
+            except IndexError:
+                raise MtrPacketReplyParseError(reply)
+
+            self.argument[name] = value
+            i += 2
+
+
 class MtrPacketTest(unittest.TestCase):
     '''Base class for tests invoking mtr-packet.
 
@@ -70,10 +125,13 @@ class MtrPacketTest(unittest.TestCase):
         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)
+        try:
+            self.packet_process = subprocess.Popen(
+                [packet_path],
+                stdin=subprocess.PIPE,
+                stdout=subprocess.PIPE)
+        except OSError:
+            raise MtrPacketExecuteError(packet_path)
 
         #  Put the mtr-packet process's stdout in non-blocking mode
         #  so that we can read from it without a timeout when
@@ -95,6 +153,14 @@ class MtrPacketTest(unittest.TestCase):
         except OSError:
             return
 
+    def parse_reply(self, timeout=10.0):  # type: (float) -> MtrPacketReply
+        '''Read the next reply from mtr-packet and parse it into
+        an MtrPacketReply object.'''
+
+        reply_str = self.read_reply(timeout)
+
+        return MtrPacketReply(reply_str)
+
     def read_reply(self, timeout=10.0):  # type: (float) -> unicode
         '''Read the next reply from mtr-packet.
 
@@ -184,4 +250,4 @@ def check_running_as_root():
     # 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")
+            'Warning: many tests require running as root\n')
old mode 100755 (executable)
new mode 100644 (file)
index 8919fa6..b4345b8
@@ -19,7 +19,6 @@
 
 '''Test sending probes and receiving respones.'''
 
-import re
 import socket
 import sys
 import time
@@ -31,25 +30,73 @@ import mtrpacket
 IPV6_TEST_HOST = 'google-public-dns-a.google.com'
 
 
-class TestProbeIPv4(mtrpacket.MtrPacketTest):
+def resolve_ipv6_address(hostname):  # type: (str) -> str
+    'Resolve a hostname to an IP version 6 address'
+
+    for addrinfo in socket.getaddrinfo(hostname, 0):
+        # pylint: disable=locally-disabled, unused-variable
+        (family, socktype, proto, name, sockaddr) = addrinfo
+
+        if family == socket.AF_INET6:
+            sockaddr6 = sockaddr  # type: tuple
+
+            (address, port, flow, scope) = sockaddr6
+            return address
+
+    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'''
 
     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)
+        reply = self.parse_reply()
+        self.assertEqual(reply.token, 14)
+        self.assertEqual(reply.command_name, 'reply')
+        self.assertIn('ip-4', reply.argument)
+        self.assertEqual(reply.argument['ip-4'], '8.8.8.8')
+        self.assertIn('round-trip-time', reply.argument)
 
     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
         #
@@ -65,16 +112,13 @@ class TestProbeIPv4(mtrpacket.MtrPacketTest):
         # 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)
+            reply = self.parse_reply()
+            self.assertEqual(reply.token, 15)
+            self.assertEqual(reply.command_name, 'no-reply')
 
     def test_exhaust_probes(self):
         'Test exhausting all available probes'
 
-        exhausted_regex = r'^[0-9]+ probes-exhausted$'
-
-        match = None
         probe_count = 4 * 1024
         token = 1024
 
@@ -86,16 +130,16 @@ class TestProbeIPv4(mtrpacket.MtrPacketTest):
 
             reply = None
             try:
-                reply = self.read_reply(0)
+                reply = self.parse_reply(0)
             except mtrpacket.ReadReplyTimeout:
                 pass
 
             if reply:
-                match = re.match(exhausted_regex, reply)
-                if match:
+                if reply.command_name == 'probes-exhausted':
                     break
 
-        self.assertIsNotNone(match)
+        self.assertIsNotNone(reply)
+        self.assertEqual(reply.command_name, 'probes-exhausted')
 
     def test_timeout_values(self):
         '''Test that timeout values wait the right amount of time
@@ -105,36 +149,34 @@ class TestProbeIPv4(mtrpacket.MtrPacketTest):
 
         begin = time.time()
         self.write_command('19 send-probe ip-4 8.8.254.254 timeout 0')
-        self.read_reply()
+        self.parse_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()
+        self.parse_reply()
         elapsed = time.time() - begin
-        self.assertGreaterEqual(elapsed, 1.0)
+        self.assertGreaterEqual(elapsed, 0.9)
         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()
+        self.parse_reply()
         elapsed = time.time() - begin
-        self.assertGreaterEqual(elapsed, 3.0)
+        self.assertGreaterEqual(elapsed, 2.9)
         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)
+        reply = self.parse_reply()
+        self.assertEqual(reply.command_name, 'ttl-expired')
+        self.assertIn('ip-4', reply.argument)
+        self.assertIn('round-trip-time', reply.argument)
 
     def test_parallel_probes(self):
         '''Test sending multiple probes in parallel
@@ -143,9 +185,6 @@ class TestProbeIPv4(mtrpacket.MtrPacketTest):
         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
 
@@ -155,17 +194,25 @@ class TestProbeIPv4(mtrpacket.MtrPacketTest):
             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:
+            reply = self.parse_reply()
+            if reply.command_name == 'no-reply':
                 continue
-            first_time = int(match.group(1))
 
-            reply = self.read_reply()
-            match = re.match(reply_regex, reply)
-            if not match:
+            self.assertEqual(reply.command_name, 'reply')
+            self.assertIn('ip-4', reply.argument)
+            self.assertEqual(reply.argument['ip-4'], '127.0.0.1')
+            self.assertIn('round-trip-time', reply.argument)
+            first_time = int(reply.argument['round-trip-time'])
+
+            reply = self.parse_reply()
+            if reply.command_name == 'no-reply':
                 continue
-            second_time = int(match.group(1))
+
+            self.assertEqual(reply.command_name, 'reply')
+            self.assertIn('ip-4', reply.argument)
+            self.assertEqual(reply.argument['ip-4'], '8.8.8.8')
+            self.assertIn('round-trip-time', reply.argument)
+            second_time = int(reply.argument['round-trip-time'])
 
             #  Ensure we got a reply from the host with the lowest latency
             #  first.
@@ -173,107 +220,98 @@ class TestProbeIPv4(mtrpacket.MtrPacketTest):
 
             success_count += 1
 
-        #  We need 95% success to pass.  This allows a few probes to be
+        #  We need 90% 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)
+        required_success = int(loop_count * 0.90)
         self.assertGreaterEqual(success_count, required_success)
 
 
-def resolve_ipv6_address(hostname):  # type: (str) -> str
-    'Resolve a hostname to an IP version 6 address'
-
-    for addrinfo in socket.getaddrinfo(hostname, 0):
-        # pylint: disable=locally-disabled, unused-variable
-        (family, socktype, proto, name, sockaddr) = addrinfo
-
-        if family == socket.AF_INET6:
-            sockaddr6 = sockaddr  # type: tuple
-
-            (address, port, flow, scope) = sockaddr6
-            return address
-
-    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
-
-
-class TestProbeIPv6(mtrpacket.MtrPacketTest):
+class TestProbeICMPv6(mtrpacket.MtrPacketTest):
     '''Test sending probes using IP version 6'''
 
-    have_ipv6 = check_for_local_ipv6()
-
     def __init__(self, *args):
         google_addr = resolve_ipv6_address(IPV6_TEST_HOST)
 
         self.google_addr = google_addr  # type: str
 
-        super(TestProbeIPv6, self).__init__(*args)
+        super(TestProbeICMPv6, self).__init__(*args)
 
-    @unittest.skipIf(not have_ipv6, 'No IPv6')
+    @unittest.skipUnless(HAVE_IPV6, 'No IPv6')
     def test_probe(self):
         "Test a probe to Google's public DNS server"
 
-        reply_regex = r'^51 reply ip-6 [0-9a-f:]+ round-trip-time [0-9]+$'
-        loopback_reply_regex = r'^52 reply ip-6 ::1 round-trip-time [0-9]+$'
-
         #  Probe Google's well-known DNS server and expect a reply
         self.write_command('51 send-probe ip-6 ' + self.google_addr)
-        reply = self.read_reply()
-        match = re.match(reply_regex, reply)
-        self.assertIsNotNone(match, reply)
+        reply = self.parse_reply()
+        self.assertEqual(reply.command_name, 'reply')
+        self.assertIn('ip-6', reply.argument)
+        self.assertIn('round-trip-time', reply.argument)
 
         #  Probe the loopback, and check the address we get a reply from is
         #  also the loopback.  While implementing IPv6, I had a bug where
         #  the low bits of the received address got zeroed.  This checks for
         #  that bug.
         self.write_command('52 send-probe ip-6 ::1')
-        reply = self.read_reply()
-        match = re.match(loopback_reply_regex, reply)
-        self.assertIsNotNone(match, reply)
+        reply = self.parse_reply()
+        self.assertEqual(reply.command_name, 'reply')
+        self.assertIn('ip-6', reply.argument)
+        self.assertIn('round-trip-time', reply.argument)
+        self.assertEqual(reply.argument['ip-6'], '::1')
 
-    @unittest.skipIf(not have_ipv6, 'No IPv6')
+    @unittest.skipUnless(HAVE_IPV6, 'No IPv6')
     def test_ttl_expired(self):
         'Test sending a probe which will have its time-to-live expire'
 
-        ttl_expired_regex = \
-            r'^53 ttl-expired ip-6 [0-9a-f:]+ round-trip-time [0-9]+$'
-
         #  Probe Goolge's DNS server, but give the probe only one hop
         #  to live.
         cmd = '53 send-probe ip-6 ' + self.google_addr + ' ttl 1'
         self.write_command(cmd)
-        reply = self.read_reply()
-        match = re.match(ttl_expired_regex, reply)
-        self.assertIsNotNone(match, reply)
+        reply = self.parse_reply()
+        self.assertEqual('ttl-expired', reply.command_name)
+        self.assertIn('ip-6', reply.argument)
+        self.assertIn('round-trip-time', reply.argument)
+
+
+class TestProbeUDP(mtrpacket.MtrPacketTest):
+    'Test transmitting probes using UDP'
+
+    def test_udp_v4(self):
+        'Test IPv4 UDP probes'
+
+        cmd = '60 send-probe protocol udp ip-4 8.8.8.8 port 164 ttl 1'
+        self.write_command(cmd)
+
+        reply = self.parse_reply()
+        self.assertEqual(reply.command_name, 'ttl-expired')
+
+        cmd = '61 send-probe protocol udp ip-4 127.0.0.1 port 164'
+        self.write_command(cmd)
+
+        reply = self.parse_reply()
+        self.assertEqual(reply.command_name, 'reply')
+        self.assertIn('ip-4', reply.argument)
+        self.assertEqual(reply.argument['ip-4'], '127.0.0.1')
+
+    @unittest.skipUnless(HAVE_IPV6, 'No IPv6')
+    def test_udp_v6(self):
+        'Test IPv6 UDP probes'
+
+        test_addr = resolve_ipv6_address(IPV6_TEST_HOST)
+
+        cmd = '62 send-probe protocol udp ip-6 ' + test_addr + \
+            ' port 164 ttl 1'
+        self.write_command(cmd)
+
+        reply = self.parse_reply()
+        self.assertEqual(reply.command_name, 'ttl-expired')
+
+        cmd = '63 send-probe protocol udp ip-6 ::1 port 164'
+        self.write_command(cmd)
 
+        reply = self.parse_reply()
+        self.assertEqual(reply.command_name, 'reply')
+        self.assertIn('ip-6', reply.argument)
+        self.assertEqual(reply.argument['ip-6'], '::1')
 
 if __name__ == '__main__':
     mtrpacket.check_running_as_root()