]> git.ipfire.org Git - thirdparty/mtr.git/commitdiff
mtr-packet: MPLS decoding and local UDP port usage
authorMatt Kimball <matt.kimball@gmail.com>
Thu, 22 Dec 2016 15:28:29 +0000 (07:28 -0800)
committerMatt Kimball <matt.kimball@gmail.com>
Thu, 22 Dec 2016 15:28:29 +0000 (07:28 -0800)
mtr-packet will decode any MPLS labels embedded in an
ICMP reply which results from a probe, and report those
labels with the probe reply.

When sending a UDP probe, a local port can be specified
for probe origination.  In the same way that the legacy mtr
code found a location to store a unique identifier for the probe,
we'll use the destination port, the local port or the checksum
field, depending on what probe arguments have been specified.

Both MPLS and local UDP port options have been documented in the
mtr-packet man page.

Update the SECURITY documentation to reflect mtr-packet, and did
minor copyediting in the README.  Also, update my email address
in AUTHORS.

17 files changed:
AUTHORS
README
SECURITY
cmdpipe.c
gtk.c
mtr-packet.8.in
packet/command.c
packet/construct_unix.c
packet/construct_unix.h
packet/deconstruct_unix.c
packet/probe.c
packet/probe.h
packet/probe_cygwin.c
packet/probe_unix.c
packet/probe_unix.h
packet/protocols.h
test/probe.py

diff --git a/AUTHORS b/AUTHORS
index f40ecf47c5e353ecf1cd056bd60212e1ca94a1d8..d4c2a1f77a49f4fd1ed17537e6c8146fbbaa6de1 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,5 +1,5 @@
 
-  Matt Kimball <mkimball@xmission.com> is the primary author of mtr.
+  Matt Kimball <matt.kimball@gmail.com> is the primary author of mtr.
 
   Roger Wolff <R.E.Wolff@BitWizard.nl> is currently maintaining mtr.
 
diff --git a/README b/README
index 3ff0477b07dd3f1f46a56c0adcc6c3aa25869fa3..b2d63197843940f30fb7f63ca7d8082f3e536b17 100644 (file)
--- a/README
+++ b/README
@@ -70,15 +70,15 @@ BUILDING FOR WINDOWS
 
   Install the packages required for building:
 
-  apt-cyg install automake pkg-config make gcc-core libncurses-devel
+        apt-cyg install automake pkg-config make gcc-core libncurses-devel
 
   Build as under Unix:
 
-  ./bootstrap.sh && ./configure && make
+        ./bootstrap.sh && ./configure && make
 
   Finally, install the built binaries:
 
-  make install
+        make install
 
 WHERE CAN I GET THE LATEST VERSION OR MORE INFORMATION?
 
index a91ebac9ddf92682f8a46e353731bf1c00fce052..f9127ac65f1335cb8b0218b74a5b99eb3bbdc196 100644 (file)
--- a/SECURITY
+++ b/SECURITY
@@ -1,13 +1,14 @@
 SECURITY ISSUES RELATED TO MTR
 
-mtr requires extra privileges to send custom packets, and there are
-security implications from granting this.
+mtr invokes a sub-process, mtr-packet, which requires extra privileges
+to send custom packets, and there are security implications from
+granting this.
 
 There are several different ways to provide the privileges:
 
 1. Add limited privileges on systems that support this. (Preferred.)
 2. Run mtr as the root user.
-3. Make mtr a setuid-root binary.
+3. Make mtr-packet a setuid-root binary.
 
 Details:
 
@@ -18,56 +19,45 @@ of security privileges that are actually needed.
 
 Linux:
 On Linux, privileges are known as capabilities. The only additional
-capability that mtr needs is cap_net_raw. To give this capability
-to the mtr binary, run the following command as root:
+capability that mtr-packet needs is cap_net_raw. To give this
+capability to the mtr-packet binary, run the following command as root:
 
-# setcap cap_net_raw+ep mtr
+# setcap cap_net_raw+ep mtr-packet
 
 
 2. Run mtr as the root user.
 
 You can limit mtr usage to the root user by not putting a setuid bit
-on the mtr binary. In that case, the security implications are
+on the mtr-packet binary. In that case, the security implications are
 minimal.
 
 
-3. Make mtr a setuid-root binary.
+3. Make mtr-packet a setuid-root binary.
 
-The mtr binary can be made setuid-root, which is what "make install"
+The mtr-packet binary can be made setuid-root, which is what "make install"
 does by default.
 
-When mtr is installed as suid-root, some concern over security is
-justified.  Since version 0.21, mtr does the following two things
-after it is launched:
+When mtr-packet is installed as suid-root, some concern over security is
+justified.  mtr-packet does the following two things after it is launched:
 
-*  mtr requests a pair of raw sockets from the kernel.  
-*  mtr drops root privileges by setting the effective uid to match
-   uid or the user calling mtr.
+*  mtr-packet open sockets for sending raw packets and for receiving
+   ICMP packets.
+*  mtr-packet drops root privileges by setting the effective uid to
+   match uid or the user calling mtr.
 
-See main() in mtr.c and net_preopen() in net.c for the details of this
-process.  Note that no code from GTK+ or curses is executed before
-dropping root privileges.
+See main() in packet.c and init_net_state_privileged() in probe_unix.c
+for the details of this process.
 
-This should severely limit the possibilities of using mtr to breach
-system security.  This means the worst case scenario is as follows:
+This should limit the possibilities of using mtr to breach system security.
+The worst case scenario is as follows:
 
-Due to some oversight in the mtr code, a malicious user is able to
-overrun one of mtr's internal buffers with binary code that is
+Due to some oversight in the mtr-packet code, a malicious user is able to
+overrun one of mtr-packets's internal buffers with binary code that is
 eventually executed.  The malicious user is still not able to read
-from or write to any system files which they wouldn't normally have
-permission to read or write to, respectively.  The only privilege
-gained is access to the raw socket descriptors, which would allow
-the malicious user to listen to all ICMP packets arriving at the
-system, and to send forged packets with arbitrary contents.
-
-The mtr-code does its best to prevent calling of external library
-code before dropping privileges. It seems that C++ library code has 
-the ability to issue a "please execute me before calling main" to the
-loader/linker.  That would mean that we're still vulnerable to 
-errors in that code. This is why I would prefer to drop the backends, 
-have mtr-core always run in "raw" mode, and have the backends interpret
-the output from the mtr-core. Maybe a nice project for a college-level
-student.
+from or write to any system files other than those normally accessible
+by the user running mtr.  The only privileges gained are access to the raw
+socket, which would allow the malicious user to listen to all ICMP packets
+arriving at the system, and to send forged packets with arbitrary contents.
 
 
 If you have further questions or comments about security issues,
index 20add252e93476a203e3cf173a56c8a834bcfec2..3a38ec7364e10d4873bd86a6d218ecd265f54607 100644 (file)
--- a/cmdpipe.c
+++ b/cmdpipe.c
@@ -417,6 +417,11 @@ void send_probe_command(
             command, COMMAND_BUFFER_SIZE, "port", ctl->remoteport);
     }
 
+    if (ctl->localport) {
+        append_command_argument(
+            command, COMMAND_BUFFER_SIZE, "localport", ctl->localport);
+    }
+
 #ifdef SO_MARK
     if (ctl->mark) {
         append_command_argument(
@@ -435,6 +440,72 @@ void send_probe_command(
 }
 
 
+/*
+    Parse a comma separated field of mpls values, filling out the mplslen
+    structure with mpls labels.
+*/
+static
+void parse_mpls_values(
+    struct mplslen *mpls,
+    char *value_str)
+{
+    char *next_value = value_str;
+    char *end_of_value;
+    int value;
+    int label_count = 0;
+    int label_field = 0;
+
+    while (*next_value) {
+        value = strtol(next_value, &end_of_value, 10);
+
+        /*  Failure to advance means an invalid numeric value  */
+        if (end_of_value == next_value) {
+            return;
+        }
+
+        /*  If the next character is not a comma or a NUL, we have
+            an invalid string  */
+        if (*end_of_value == ',') {
+            next_value = end_of_value + 1;
+        } else if (*end_of_value == 0) {
+            next_value = end_of_value;
+        } else {
+            return;
+        }
+
+        /*
+            Store the converted value in the next field of the MPLS
+            structure.
+        */
+        if (label_field == 0) {
+            mpls->label[label_count] = value;
+        } else if (label_field == 1) {
+            mpls->exp[label_count] = value;
+        } else if (label_field == 2) {
+            mpls->s[label_count] = value;
+        } else if (label_field == 3) {
+            mpls->ttl[label_count] = value;
+        }
+
+        label_field++;
+        if (label_field > 3) {
+            label_field = 0;
+            label_count++;
+        }
+
+        /*
+            If we've used up all MPLS labels in the structure, return with
+            what we've got
+        */
+        if (label_count >= MAXLABELS) {
+            break;
+        }
+    }
+
+    mpls->labels = label_count;
+}
+
+
 /*
     Extract the IP address and round trip time from a reply to a probe.
     Returns true if both arguments are found in the reply, false otherwise.
@@ -444,7 +515,8 @@ bool parse_reply_arguments(
     struct mtr_ctl *ctl,
     struct command_t *reply,
     ip_t *fromaddress,
-    int *round_trip_time)
+    int *round_trip_time,
+    struct mplslen *mpls)
 {
     bool found_round_trip;
     bool found_ip;
@@ -454,6 +526,7 @@ bool parse_reply_arguments(
 
     *round_trip_time = 0;
     memset(fromaddress, 0, sizeof(ip_t));
+    memset(mpls, 0, sizeof(struct mplslen));
 
     found_ip = false;
     found_round_trip = false;
@@ -487,6 +560,11 @@ bool parse_reply_arguments(
                 found_round_trip = true;
             }
         }
+
+        /*  MPLS labels  */
+        if (!strcmp(arg_name, "mpls")) {
+            parse_mpls_values(mpls, arg_value);
+        }
     }
 
     return found_ip && found_round_trip;
@@ -582,10 +660,8 @@ void handle_command_reply(
         If the reply had an IP address and a round trip time, we can
         record the result.
     */
-    if (parse_reply_arguments(ctl, &reply, &fromaddress, &round_trip_time)) {
-        /* MPLS decoding */
-        memset(&mpls, 0, sizeof(struct mplslen));
-        mpls.labels = 0;
+    if (parse_reply_arguments(
+            ctl, &reply, &fromaddress, &round_trip_time, &mpls)) {
 
         reply_func(
             ctl, seq_num, &mpls, (void *) &fromaddress, round_trip_time);
diff --git a/gtk.c b/gtk.c
index 6a37a9a3b74c49e4b7acbc3d0adf8d8688101142..97ab0a3c4865a280e14deccb6dd07109924cfe9d 100644 (file)
--- a/gtk.c
+++ b/gtk.c
@@ -130,7 +130,7 @@ static gint About_clicked(ATTRIBUTE_UNUSED GtkWidget *Button,
                          ATTRIBUTE_UNUSED gpointer data)
 {
   static const gchar *authors[] = {
-        "Matt Kimball <mkimball@xmission.com>",
+        "Matt Kimball <matt.kimball@gmail.com>",
         "Roger Wolff <R.E.Wolff@BitWizard.nl>",
         "Bohdan Vlasyuk <bohdan@cec.vstu.vinnica.ua>",
         "Evgeniy Tretyak <evtr@ukr.net>",
index 9dab7e68f7bfee774bc563fa12a9132305516c4d..97b46aff8d3e41bf7345e59de1c9f6b7bd864827 100644 (file)
@@ -117,6 +117,15 @@ or
 probes.
 .HP 7
 .IP
+.B localport
+.I PORT-NUMBER
+.HP 14
+.IP
+For
+.B udp
+probes, the local port number from which to send probes.
+.HP 7
+.IP
 .B timeout
 .I TIMEOUT-SECONDS
 .HP 14
@@ -274,6 +283,25 @@ The time which passed between the transmission of the probe and its
 response.  The time is provided as a integral number of microseconds
 elapsed.
 .HP 7
+.IP
+.B mpls
+.I MPLS-LABEL-LIST
+.HP 14
+.IP
+A list of Multiprotocol Label Switching values returned
+with the probe response.
+If the
+.B mpls
+argument is present, one or more MPLS labels will be represented by
+a comma separated list of values.  The values are provided in groups
+of four.  The first four values in the list correspond to the
+first MPLS label, the next four values correspond to the second MPLS
+label, and so on.  The values are provided in this order:
+.IR label ,
+.IR experimental-use ,
+.IR bottom-of-stack ,
+.IR ttl .
+.HP 7
 .TP
 .B no-route
 There was no route to the host used in a
index 0533938be3804a5343ed4db6ab3f5fa0f8b96fed..faee5a852ff6b960f54ef8c0c80da084ad5f59b6 100644 (file)
@@ -184,6 +184,23 @@ bool decode_probe_argument(
         }
     }
 
+    /*  The local port to send UDP probes from  */
+    if (!strcmp(name, "localport")) {
+        param->local_port = strtol(value, &endstr, 10);
+        if (*endstr != 0) {
+            return false;
+        }
+
+        /*
+            Don't allow using a local port which requires
+            privileged binding.
+        */
+        if (param->local_port < 1024) {
+            param->local_port = 0;
+            return false;
+        }
+    }
+
     /*  The "type of service" field for the IP header  */
     if (!strcmp(name, "tos")) {
         param->type_of_service = strtol(value, &endstr, 10);
@@ -250,7 +267,6 @@ void send_probe_command(
     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.packet_size = 128;
     param.timeout = 10;
index 055fd498d8392791687a969643337887e514b390..58004265fbbe1b8d9446638a040c738b80203319 100644 (file)
@@ -131,7 +131,7 @@ void construct_ip4_header(
 static
 void construct_icmp4_header(
     const struct net_state_t *net_state,
-    int port,
+    int sequence,
     char *packet_buffer,
     int packet_size,
     const struct probe_param_t *param)
@@ -146,7 +146,7 @@ void construct_icmp4_header(
 
     icmp->type = ICMP_ECHO;
     icmp->id = htons(getpid());
-    icmp->sequence = htons(port);
+    icmp->sequence = htons(sequence);
     icmp->checksum = htons(compute_checksum(icmp, icmp_size));
 }
 
@@ -154,7 +154,7 @@ void construct_icmp4_header(
 static
 int construct_icmp6_packet(
     const struct net_state_t *net_state,
-    int port,
+    int sequence,
     char *packet_buffer,
     int packet_size,
     const struct probe_param_t *param)
@@ -167,11 +167,49 @@ int construct_icmp6_packet(
 
     icmp->type = ICMP6_ECHO;
     icmp->id = htons(getpid());
-    icmp->sequence = htons(port);
+    icmp->sequence = htons(sequence);
 
     return 0;
 }
 
+/*
+    Set the port numbers for an outgoing UDP probe.
+    There is limited space in the header for a sequence number
+    to identify the probe upon return.
+
+    We store the sequence number in the destination port, the local
+    port, or the checksum.  The location chosen depends upon which
+    probe parameters have been requested.
+*/
+static
+void set_udp_ports(
+    struct UDPHeader *udp,
+    int sequence,
+    const struct probe_param_t *param)
+{
+    if (param->dest_port) {
+        udp->dstport = htons(param->dest_port);
+
+        if (param->local_port) {
+            udp->srcport = htons(param->local_port);
+            udp->checksum = htons(sequence);
+        } else {
+            udp->srcport = htons(sequence);
+            udp->checksum = 0;
+        }
+    } else {
+        udp->dstport = htons(sequence);
+
+        if (param->local_port) {
+            udp->srcport = htons(param->local_port);
+        } else {
+            udp->srcport = htons(getpid());
+        }
+
+        udp->checksum = 0;
+    }
+}
+
 /*
     Construct a header for UDP probes, using the port number associated
     with the probe.
@@ -179,7 +217,7 @@ int construct_icmp6_packet(
 static
 void construct_udp4_header(
     const struct net_state_t *net_state,
-    int port,
+    int sequence,
     char *packet_buffer,
     int packet_size,
     const struct probe_param_t *param)
@@ -192,17 +230,15 @@ void construct_udp4_header(
 
     memset(udp, 0, sizeof(struct UDPHeader));
 
-    udp->srcport = htons(port);
-    udp->dstport = htons(param->dest_port);
+    set_udp_ports(udp, sequence, param);
     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,
-    int port,
+    int sequence,
     char *packet_buffer,
     int packet_size,
     const struct probe_param_t *param)
@@ -216,10 +252,8 @@ int construct_udp6_packet(
 
     memset(udp, 0, sizeof(struct UDPHeader));
 
-    udp->srcport = htons(port);
-    udp->dstport = htons(param->dest_port);
+    set_udp_ports(udp, sequence, param);
     udp->length = htons(udp_size);
-    udp->checksum = 0;
 
     /*
         Instruct the kernel to put the pseudoheader checksum into the
@@ -328,6 +362,7 @@ int open_stream_socket(
 {
     int stream_socket;
     int addr_len;
+    int dest_port;
     struct sockaddr_storage dest_port_addr;
     struct sockaddr_storage src_port_addr;
 
@@ -363,8 +398,15 @@ int open_stream_socket(
         return -1;
     }
 
+    if (param->dest_port) {
+        dest_port = param->dest_port;
+    } else {
+        /*  Use http if no port is specified  */
+        dest_port = HTTP_PORT;
+    }
+
     /*  Attempt a connection  */
-    construct_addr_port(&dest_port_addr, dest_sockaddr, param->dest_port);
+    construct_addr_port(&dest_port_addr, dest_sockaddr, dest_port);
     if (connect(
             stream_socket, (struct sockaddr *)&dest_port_addr, addr_len)) {
 
@@ -416,6 +458,9 @@ int compute_packet_size(
         packet_size += sizeof(struct ICMPHeader);
     } else if (param->protocol == IPPROTO_UDP) {
         packet_size += sizeof(struct UDPHeader);
+
+        /*  We may need to put the sequence number in the payload  */
+        packet_size += sizeof(int);
     } else {
         errno = EINVAL;
         return -1;
@@ -445,7 +490,7 @@ static
 int construct_ip4_packet(
     const struct net_state_t *net_state,
     int *packet_socket,
-    int port,
+    int sequence,
     char *packet_buffer,
     int packet_size,
     const struct sockaddr_storage *src_sockaddr,
@@ -468,10 +513,10 @@ int construct_ip4_packet(
 
         if (param->protocol == IPPROTO_ICMP) {
             construct_icmp4_header(
-                net_state, port, packet_buffer, packet_size, param);
+                net_state, sequence, packet_buffer, packet_size, param);
         } else if (param->protocol == IPPROTO_UDP) {
             construct_udp4_header(
-                net_state, port, packet_buffer, packet_size, param);
+                net_state, sequence, packet_buffer, packet_size, param);
         } else {
             errno = EINVAL;
             return -1;
@@ -480,7 +525,7 @@ int construct_ip4_packet(
 
     if (is_stream_protocol) {
         send_socket = open_stream_socket(
-            net_state, param->protocol, port,
+            net_state, param->protocol, sequence,
             src_sockaddr, dest_sockaddr, param);
 
         if (send_socket == -1) {
@@ -519,7 +564,7 @@ static
 int construct_ip6_packet(
     const struct net_state_t *net_state,
     int *packet_socket,
-    int port,
+    int sequence,
     char *packet_buffer,
     int packet_size,
     const struct sockaddr_storage *src_sockaddr,
@@ -539,14 +584,14 @@ int construct_ip6_packet(
         send_socket = net_state->platform.icmp6_send_socket;
 
         if (construct_icmp6_packet(
-                net_state, port, packet_buffer, packet_size, param)) {
+                net_state, sequence, 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, port, packet_buffer, packet_size, param)) {
+                net_state, sequence, packet_buffer, packet_size, param)) {
             return -1;
         }
     } else {
@@ -556,7 +601,7 @@ int construct_ip6_packet(
 
     if (is_stream_protocol) {
         send_socket = open_stream_socket(
-            net_state, param->protocol, port,
+            net_state, param->protocol, sequence,
             src_sockaddr, dest_sockaddr, param);
 
         if (send_socket == -1) {
@@ -598,7 +643,7 @@ int construct_ip6_packet(
 int construct_packet(
     const struct net_state_t *net_state,
     int *packet_socket,
-    int port,
+    int sequence,
     char *packet_buffer,
     int packet_buffer_size,
     const struct sockaddr_storage *dest_sockaddr,
@@ -625,13 +670,15 @@ int construct_packet(
 
     if (param->ip_version == 6) {
         if (construct_ip6_packet(
-                net_state, packet_socket, port, packet_buffer, packet_size,
+                net_state, packet_socket, sequence,
+                packet_buffer, packet_size,
                 &src_sockaddr, dest_sockaddr, param)) {
             return -1;
         }
     } else if (param->ip_version == 4) {
         if (construct_ip4_packet(
-                net_state, packet_socket, port, packet_buffer, packet_size,
+                net_state, packet_socket, sequence,
+                packet_buffer, packet_size,
                 &src_sockaddr, dest_sockaddr, param)) {
             return -1;
         }
index 976c8f15c9ca47a4db1aab6733ad361706a8fb4f..1f0ae48ef2717f77f57d499be8c2d24e20dce2e9 100644 (file)
@@ -24,7 +24,7 @@
 int construct_packet(
     const struct net_state_t *net_state,
     int *packet_socket,
-    int port,
+    int sequence,
     char *packet_buffer,
     int packet_buffer_size,
     const struct sockaddr_storage *dest_sockaddr,
index cbef8156d147e15b6e49e07b292d61b51532fe2c..8065dd7128d4607562045b0c919bf62a6181718e 100644 (file)
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 
 #include "protocols.h"
 
+#define MAX_MPLS_LABELS 8
+
 /*
     Given an ICMP id + ICMP sequence, find the match probe we've
     transmitted and if found, respond to the command which sent it
@@ -35,7 +38,9 @@ void find_and_receive_probe(
     int icmp_type,
     int protocol,
     int icmp_id,
-    int icmp_sequence)
+    int icmp_sequence,
+    int mpls_count,
+    struct mpls_label_t *mpls)
 {
     struct probe_t *probe;
 
@@ -44,7 +49,41 @@ void find_and_receive_probe(
         return;
     }
 
-    receive_probe(probe, icmp_type, remote_addr, timestamp);
+    receive_probe(
+        probe, icmp_type, remote_addr, timestamp, mpls_count, mpls);
+}
+
+/*
+    Handle a UDP packet received embedded in an ICMP reply.
+    The sequence number identifying the probe might be in
+    the source port number, the destination port number, or
+    the checksum.  We'll check all three.
+*/
+static
+void handle_inner_udp_packet(
+    struct net_state_t *net_state,
+    const struct sockaddr_storage *remote_addr,
+    int icmp_result,
+    const struct UDPHeader *udp,
+    int udp_length,
+    struct timeval *timestamp,
+    int mpls_count,
+    struct mpls_label_t *mpls)
+{
+    struct probe_t *probe;
+
+    probe = find_probe(net_state, IPPROTO_UDP, 0, udp->dstport);
+    if (probe == NULL) {
+        probe = find_probe(net_state, IPPROTO_UDP, 0, udp->srcport);
+    }
+    if (probe == NULL) {
+        probe = find_probe(net_state, IPPROTO_UDP, 0, udp->checksum);
+    }
+
+    if (probe != NULL) {
+        receive_probe(
+            probe, icmp_result, remote_addr, timestamp, mpls_count, mpls);
+    }
 }
 
 /*
@@ -59,7 +98,9 @@ void handle_inner_ip4_packet(
     int icmp_result,
     const struct IPHeader *ip,
     int packet_length,
-    struct timeval *timestamp)
+    struct timeval *timestamp,
+    int mpls_count,
+    struct mpls_label_t *mpls)
 {
     const int ip_icmp_size =
         sizeof(struct IPHeader) + sizeof(struct ICMPHeader);
@@ -73,6 +114,7 @@ void handle_inner_ip4_packet(
     const struct UDPHeader *udp;
     const struct TCPHeader *tcp;
     const struct SCTPHeader *sctp;
+    int udp_length;
 
     if (ip->protocol == IPPROTO_ICMP) {
         if (packet_length < ip_icmp_size) {
@@ -83,17 +125,18 @@ void handle_inner_ip4_packet(
 
         find_and_receive_probe(
             net_state, remote_addr, timestamp, icmp_result,
-            IPPROTO_ICMP, icmp->id, icmp->sequence);
+            IPPROTO_ICMP, icmp->id, icmp->sequence, mpls_count, mpls);
     } else if (ip->protocol == IPPROTO_UDP) {
         if (packet_length < ip_udp_size) {
             return;
         }
 
         udp = (struct UDPHeader *)(ip + 1);
+        udp_length = packet_length - sizeof(struct IPHeader);
 
-        find_and_receive_probe(
-            net_state, remote_addr, timestamp, icmp_result,
-            IPPROTO_UDP, 0, udp->srcport);
+        handle_inner_udp_packet(
+            net_state, remote_addr, icmp_result, udp, udp_length,
+            timestamp, mpls_count, mpls);
     } else if (ip->protocol == IPPROTO_TCP) {
         if (packet_length < ip_tcp_size) {
             return;
@@ -103,7 +146,7 @@ void handle_inner_ip4_packet(
 
         find_and_receive_probe(
             net_state, remote_addr, timestamp, icmp_result,
-            IPPROTO_TCP, 0, tcp->srcport);
+            IPPROTO_TCP, 0, tcp->srcport, mpls_count, mpls);
 #ifdef IPPROTO_SCTP
     } else if (ip->protocol == IPPROTO_SCTP) {
         if (packet_length < ip_sctp_size) {
@@ -114,7 +157,7 @@ void handle_inner_ip4_packet(
 
         find_and_receive_probe(
             net_state, remote_addr, timestamp, icmp_result,
-            IPPROTO_SCTP, 0, sctp->srcport);
+            IPPROTO_SCTP, 0, sctp->srcport, mpls_count, mpls);
 #endif
     }
 }
@@ -130,7 +173,9 @@ void handle_inner_ip6_packet(
     int icmp_result,
     const struct IP6Header *ip,
     int packet_length,
-    struct timeval *timestamp)
+    struct timeval *timestamp,
+    int mpls_count,
+    struct mpls_label_t *mpls)
 {
     const int ip_icmp_size =
         sizeof(struct IP6Header) + sizeof(struct ICMPHeader);
@@ -144,6 +189,7 @@ void handle_inner_ip6_packet(
     const struct UDPHeader *udp;
     const struct TCPHeader *tcp;
     const struct SCTPHeader *sctp;
+    int udp_length;
 
     if (ip->protocol == IPPROTO_ICMPV6) {
         if (packet_length < ip_icmp_size) {
@@ -154,17 +200,18 @@ void handle_inner_ip6_packet(
 
         find_and_receive_probe(
             net_state, remote_addr, timestamp, icmp_result,
-            IPPROTO_ICMP, icmp->id, icmp->sequence);
+            IPPROTO_ICMP, icmp->id, icmp->sequence, mpls_count, mpls);
     } else if (ip->protocol == IPPROTO_UDP) {
         if (packet_length < ip_udp_size) {
             return;
         }
 
         udp = (struct UDPHeader *)(ip + 1);
+        udp_length = packet_length - sizeof(struct IP6Header);
 
-        find_and_receive_probe(
-            net_state, remote_addr, timestamp, icmp_result,
-            IPPROTO_UDP, 0, udp->srcport);
+        handle_inner_udp_packet(
+            net_state, remote_addr, icmp_result, udp, udp_length,
+            timestamp, mpls_count, mpls);
     } else if (ip->protocol == IPPROTO_TCP) {
         if (packet_length < ip_tcp_size) {
             return;
@@ -173,7 +220,7 @@ void handle_inner_ip6_packet(
         tcp = (struct TCPHeader *)(ip + 1);
         find_and_receive_probe(
             net_state, remote_addr, timestamp, icmp_result,
-            IPPROTO_TCP, 0, tcp->srcport);
+            IPPROTO_TCP, 0, tcp->srcport, mpls_count, mpls);
 #ifdef IPPROTO_SCTP
     } else if (ip->protocol == IPPROTO_SCTP) {
         if (packet_length < ip_sctp_size) {
@@ -184,11 +231,111 @@ void handle_inner_ip6_packet(
 
         find_and_receive_probe(
             net_state, remote_addr, timestamp, icmp_result,
-            IPPROTO_SCTP, 0, sctp->srcport);
+            IPPROTO_SCTP, 0, sctp->srcport, mpls_count, mpls);
 #endif
     }
 }
 
+/*  Convert an ICMP MPLS extension object into an mpls_label_t structure  */
+static
+int decode_mpls_object(
+    struct ICMPExtensionObject *icmp_obj,
+    int obj_len,
+    struct mpls_label_t *mpls,
+    int mpls_count)
+{
+    int label_bytes;
+    int labels_present;
+    int i;
+    struct ICMPExtMPLSLabel *ext_mpls;
+    struct ICMPExtMPLSLabel *ext_label;
+    struct mpls_label_t *label;
+
+    label_bytes = obj_len - sizeof(struct ICMPExtensionObject);
+    labels_present = label_bytes / sizeof(struct ICMPExtMPLSLabel);
+
+    ext_mpls = (struct ICMPExtMPLSLabel *)(icmp_obj + 1);
+    for (i = 0; i < mpls_count && i < labels_present; i++) {
+        ext_label = &ext_mpls[i];
+        label = &mpls[i];
+
+        memset(label, 0, sizeof(struct mpls_label_t));
+
+        label->label =
+            ext_label->label[0] << 12 |
+            ext_label->label[1] << 4 |
+            ext_label->label[2] >> 4;
+        label->experimental_use = (ext_label->label[2] & 0x0E) >> 1;
+        label->bottom_of_stack = ext_label->label[2] & 0x01;
+        label->ttl = ext_label->ttl;
+    }
+
+    return i;
+}
+
+/*  Extract MPLS labels from the ICMP extension header, if present  */
+static
+int decode_mpls_labels(
+    const struct ICMPHeader *icmp,
+    int packet_length,
+    struct mpls_label_t *mpls,
+    int mpls_count)
+{
+    const int icmp_orig_icmp_ext_size =
+        sizeof(struct ICMPHeader) + ICMP_ORIGINAL_DATAGRAM_MIN_SIZE +
+        sizeof(struct ICMPExtensionHeader);
+    char *inner_packet;
+    char *icmp_object_bytes;
+    struct ICMPExtensionHeader *icmp_ext;
+    struct ICMPExtensionObject *icmp_obj;
+    int remaining_size;
+    int obj_len;
+
+    if (packet_length < icmp_orig_icmp_ext_size)
+    {
+        return 0;
+    }
+
+    inner_packet = (char *)(icmp + 1);
+    icmp_ext = (struct ICMPExtensionHeader *)
+        (inner_packet + ICMP_ORIGINAL_DATAGRAM_MIN_SIZE);
+
+    if ((icmp_ext->version & 0xF0) != 0x20) {
+        return 0;
+    }
+
+    remaining_size = packet_length - icmp_orig_icmp_ext_size;
+    icmp_object_bytes = (char *)(icmp_ext + 1);
+
+    /*
+        Iterate through the chain of extension objects, looking for
+        an MPLS label extension.
+    */
+    while (remaining_size >= sizeof(struct ICMPExtensionObject))
+    {
+        icmp_obj = (struct ICMPExtensionObject *)icmp_object_bytes;
+        obj_len = ntohs(icmp_obj->len);
+
+        if (obj_len > remaining_size) {
+            return 0;
+        }
+        if (obj_len < sizeof(struct ICMPExtensionObject)) {
+            return 0;
+        }
+
+        if (icmp_obj->classnum == ICMP_EXT_MPLS_CLASSNUM &&
+                icmp_obj->ctype == ICMP_EXT_MPLS_CTYPE) {
+
+            return decode_mpls_object(icmp_obj, obj_len, mpls, mpls_count);
+        }
+
+        remaining_size -= obj_len;
+        icmp_object_bytes += obj_len;
+    }
+
+    return 0;
+}
+
 /*
     Decode the ICMP header received and try to find a probe which it
     is in response to.
@@ -205,12 +352,18 @@ void handle_received_icmp4_packet(
         sizeof(struct ICMPHeader) + sizeof(struct IPHeader);
     const struct IPHeader *inner_ip;
     int inner_size = packet_length - sizeof(struct ICMPHeader);
+    int mpls_count;
+    struct mpls_label_t mpls[MAX_MPLS_LABELS];
+
+    mpls_count = decode_mpls_labels(
+        icmp, packet_length, mpls, MAX_MPLS_LABELS);
 
     /*  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, IPPROTO_ICMP, icmp->id, icmp->sequence);
+            ICMP_ECHOREPLY, IPPROTO_ICMP, icmp->id, icmp->sequence,
+            mpls_count, mpls);
     }
 
     if (packet_length < icmp_ip_size) {
@@ -230,7 +383,8 @@ void handle_received_icmp4_packet(
         */
         handle_inner_ip4_packet(
             net_state, remote_addr,
-            ICMP_TIME_EXCEEDED, inner_ip, inner_size, timestamp);
+            ICMP_TIME_EXCEEDED, inner_ip, inner_size, timestamp,
+            mpls_count, mpls);
     }
 
     if (icmp->type == ICMP_DEST_UNREACH) {
@@ -242,7 +396,8 @@ void handle_received_icmp4_packet(
         if (icmp->code == ICMP_PORT_UNREACH) {
             handle_inner_ip4_packet(
                 net_state, remote_addr,
-                ICMP_ECHOREPLY, inner_ip, inner_size, timestamp);
+                ICMP_ECHOREPLY, inner_ip, inner_size, timestamp,
+                mpls_count, mpls);
         }
     }
 }
@@ -264,11 +419,16 @@ void handle_received_icmp6_packet(
         sizeof(struct ICMPHeader) + sizeof(struct IP6Header);
     const struct IP6Header *inner_ip;
     int inner_size = packet_length - sizeof(struct ICMPHeader);
+    int mpls_count;
+    struct mpls_label_t mpls[MAX_MPLS_LABELS];
+
+    mpls_count = decode_mpls_labels(
+        icmp, packet_length, mpls, MAX_MPLS_LABELS);
 
     if (icmp->type == ICMP6_ECHOREPLY) {
         find_and_receive_probe(
             net_state, remote_addr, timestamp, ICMP_ECHOREPLY,
-            IPPROTO_ICMP, icmp->id, icmp->sequence);
+            IPPROTO_ICMP, icmp->id, icmp->sequence, mpls_count, mpls);
     }
 
     if (packet_length < icmp_ip_size) {
@@ -279,14 +439,16 @@ void handle_received_icmp6_packet(
     if (icmp->type == ICMP6_TIME_EXCEEDED) {
         handle_inner_ip6_packet(
             net_state, remote_addr,
-            ICMP_TIME_EXCEEDED, inner_ip, inner_size, timestamp);
+            ICMP_TIME_EXCEEDED, inner_ip, inner_size, timestamp,
+            mpls_count, mpls);
     }
 
     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);
+                ICMP_ECHOREPLY, inner_ip, inner_size, timestamp,
+                mpls_count, mpls);
         }
     }
 }
index ea364dead8168587cc6d7f276670271bd7927dd8..7817866dcae56c6d11b35eb7ce1e8f2b75f7af98 100644 (file)
@@ -27,6 +27,7 @@
 #include <sys/socket.h>
 #include <unistd.h>
 
+#include "command.h"
 #include "platform.h"
 #include "protocols.h"
 #include "timeval.h"
@@ -145,8 +146,8 @@ int count_in_flight_probes(
 struct probe_t *find_probe(
     struct net_state_t *net_state,
     int protocol,
-    int icmp_id,
-    int icmp_sequence)
+    int id,
+    int sequence)
 {
     int i;
     struct probe_t *probe;
@@ -160,22 +161,62 @@ struct probe_t *find_probe(
             If the ICMP id doesn't match our process ID, it wasn't a
             probe generated by this process, so ignore it.
         */
-        if (icmp_id != htons(getpid())) {
+        if (id != htons(getpid())) {
             return NULL;
         }
     }
 
+
     for (i = 0; i < MAX_PROBES; i++) {
         probe = &net_state->probes[i];
 
-        if (probe->used && htons(probe->port) == icmp_sequence) {
-            return probe;
+        if (probe->used) {
+            if (htons(probe->sequence) == sequence) {
+                return probe;
+            }
         }
     }
 
     return NULL;
 }
 
+/*
+    Format a list of MPLS labels into a string appropriate for including
+    as an argument to a probe reply.
+*/
+static
+void format_mpls_string(
+    char *str,
+    int buffer_size,
+    int mpls_count,
+    const struct mpls_label_t *mpls_list)
+{
+    int i;
+    char *append_pos = str;
+    const struct mpls_label_t *mpls;
+
+    strcpy(str, "");
+
+    for (i = 0; i < mpls_count; i++) {
+        mpls = &mpls_list[i];
+
+        if (i > 0) {
+            strncat(append_pos, ",", buffer_size - 1);
+
+            buffer_size -= strlen(append_pos);
+            append_pos += strlen(append_pos);
+        }
+
+        snprintf(
+            append_pos, buffer_size, "%d,%d,%d,%d",
+            mpls->label, mpls->experimental_use,
+            mpls->bottom_of_stack, mpls->ttl);
+
+        buffer_size -= strlen(append_pos);
+        append_pos += strlen(append_pos);
+    }
+}
+
 /*
     After a probe reply has arrived, respond to the command request which
     sent the probe.
@@ -184,9 +225,14 @@ void respond_to_probe(
     struct probe_t *probe,
     int icmp_type,
     const struct sockaddr_storage *remote_addr,
-    unsigned int round_trip_us)
+    unsigned int round_trip_us,
+    int mpls_count,
+    const struct mpls_label_t *mpls)
 {
     char ip_text[IP_TEXT_LENGTH];
+    char response[COMMAND_BUFFER_SIZE];
+    char mpls_str[COMMAND_BUFFER_SIZE];
+    int remaining_size;
     const char *result;
     const char *ip_argument;
     struct sockaddr_in *sockaddr4;
@@ -217,10 +263,22 @@ void respond_to_probe(
         exit(1);
     }
 
-    printf(
-        "%d %s %s %s round-trip-time %d\n",
+    snprintf(
+        response, COMMAND_BUFFER_SIZE,
+        "%d %s %s %s round-trip-time %d",
         probe->token, result, ip_argument, ip_text, round_trip_us);
 
+    if (mpls_count) {
+        format_mpls_string(mpls_str, COMMAND_BUFFER_SIZE, mpls_count, mpls);
+
+        remaining_size = COMMAND_BUFFER_SIZE - strlen(response) - 1;
+        strncat(response, " mpls ", remaining_size);
+
+        remaining_size = COMMAND_BUFFER_SIZE - strlen(response) - 1;
+        strncat(response, mpls_str, remaining_size);
+    }
+
+    puts(response);
     free_probe(probe);
 }
 
index 47ddd221aaf681b37ba62b607c1f4d5d6ebd3f63..653838810ede17ef8f73de785bffebecbeb25526 100644 (file)
@@ -54,6 +54,9 @@ struct probe_param_t
     /*  The destination port for non-ICMP probes  */
     int dest_port;
 
+    /*  The local port number to use when sending probes  */
+    int local_port;
+
     /*  The "type of service" field in the IP header  */
     int type_of_service;
 
@@ -80,10 +83,12 @@ struct probe_t
     bool used;
 
     /*
-        The port number to use when binding sockets for this probe.
         Also the ICMP sequence ID used to identify the probe.
+
+        Also used as the port number to use when binding stream protocol
+        sockets for this probe.  (i.e. TCP or SCTP)
     */
-    int port;
+    int sequence;
 
     /*  Command token of the probe request  */
     int token;
@@ -105,6 +110,14 @@ struct net_state_t
     struct net_state_platform_t platform;
 };
 
+struct mpls_label_t
+{
+    uint32_t label;
+    uint8_t experimental_use;
+    uint8_t bottom_of_stack;
+    uint8_t ttl;
+};
+
 void init_net_state_privileged(
     struct net_state_t *net_state);
 
@@ -133,7 +146,9 @@ void respond_to_probe(
     struct probe_t *probe,
     int icmp_type,
     const struct sockaddr_storage *remote_addr,
-    unsigned int round_trip_us);
+    unsigned int round_trip_us,
+    int mpls_count,
+    const struct mpls_label_t *mpls);
 
 int decode_dest_addr(
     const struct probe_param_t *param,
index 417fce784c4d100e20b47a5e4ab24afb06134b54..0818f52ae748f232029a0b797ff142952332e8e7 100644 (file)
@@ -161,7 +161,8 @@ void WINAPI on_icmp_reply(
 
     if (icmp_type != -1) {
         /*  Record probe result  */
-        respond_to_probe(probe, icmp_type, &remote_addr, round_trip_us);
+        respond_to_probe(
+            probe, icmp_type, &remote_addr, round_trip_us, 0, NULL);
     } else {
         fprintf(stderr, "Unexpected ICMP result %d\n", icmp_type);
     }
index df245e4f09162dc13e7d4942a4a52835d29ea0b0..cdfd6992a58931f1adec4b1e7b23e2653baa1c18 100644 (file)
@@ -261,7 +261,7 @@ void init_net_state_privileged(
 {
     memset(net_state, 0, sizeof(struct net_state_t));
 
-    net_state->platform.next_port = MIN_PORT;
+    net_state->platform.next_sequence = MIN_PORT;
 
     open_ip4_sockets(net_state);
     open_ip6_sockets(net_state);
@@ -354,7 +354,7 @@ void send_probe(
     }
 
     packet_size = construct_packet(
-        net_state, &probe->platform.socket, probe->port,
+        net_state, &probe->platform.socket, probe->sequence,
         packet, PACKET_BUFFER_SIZE, &probe->remote_addr, param);
 
     if (packet_size < 0) {
@@ -365,7 +365,8 @@ void send_probe(
             prepared for that.
         */
         if (errno == ECONNREFUSED) {
-            receive_probe(probe, ICMP_ECHOREPLY, &probe->remote_addr, NULL);
+            receive_probe(
+                probe, ICMP_ECHOREPLY, &probe->remote_addr, NULL, 0, NULL);
         } else {
             report_packet_error(param->command_token);
             free_probe(probe);
@@ -394,10 +395,10 @@ void platform_alloc_probe(
     struct net_state_t *net_state,
     struct probe_t *probe)
 {
-    probe->port = net_state->platform.next_port++;
+    probe->sequence = net_state->platform.next_sequence++;
 
-    if (net_state->platform.next_port > MAX_PORT) {
-        net_state->platform.next_port = MIN_PORT;
+    if (net_state->platform.next_sequence > MAX_PORT) {
+        net_state->platform.next_sequence = MIN_PORT;
     }
 }
 
@@ -422,7 +423,9 @@ void receive_probe(
     struct probe_t *probe,
     int icmp_type,
     const struct sockaddr_storage *remote_addr,
-    struct timeval *timestamp)
+    struct timeval *timestamp,
+    int mpls_count,
+    struct mpls_label_t *mpls)
 {
     unsigned int round_trip_us;
     struct timeval *departure_time = &probe->platform.departure_time;
@@ -441,7 +444,8 @@ void receive_probe(
         (timestamp->tv_sec - departure_time->tv_sec) * 1000000 +
         timestamp->tv_usec - departure_time->tv_usec;
 
-    respond_to_probe(probe, icmp_type, remote_addr, round_trip_us);
+    respond_to_probe(
+        probe, icmp_type, remote_addr, round_trip_us, mpls_count, mpls);
 }
 
 /*
@@ -555,7 +559,8 @@ void receive_replies_from_probe_socket(
         assume our probe arrived at the destination.
     */
     if (!err || err == ECONNREFUSED) {
-        receive_probe(probe, ICMP_ECHOREPLY, &probe->remote_addr, NULL);
+        receive_probe(
+            probe, ICMP_ECHOREPLY, &probe->remote_addr, NULL, 0, NULL);
     } else {
         errno = err;
         report_packet_error(probe->token);
index 51bba8b4e4ae309ab260dc13beecb4dc07661edd..8a36e295eb3dd1b972bbda0f05f8669de1453f49 100644 (file)
@@ -64,11 +64,12 @@ struct net_state_platform_t
     bool sctp_support;
 
     /*  The next port number to use when creating a new probe  */
-    int next_port;
+    int next_sequence;
 };
 
 struct net_state_t;
 struct probe_t;
+struct mpls_label_t;
 
 void set_socket_nonblocking(
     int socket);
@@ -77,7 +78,9 @@ void receive_probe(
     struct probe_t *probe,
     int icmp_type,
     const struct sockaddr_storage *remote_addr,
-    struct timeval *timestamp);
+    struct timeval *timestamp,
+    int mpls_count,
+    struct mpls_label_t *mpls);
 
 int gather_probe_sockets(
     const struct net_state_t *net_state,
index 84d51a1d54122eed991d1ab7168467f25b9ad9d6..ea3f07e457a009b03709a9182fb2d72cc693374c 100644 (file)
 /*  ICMP6_DEST_UNREACH codes */
 #define ICMP6_PORT_UNREACH 4
 
+/*
+    The minimum size of the ICMP "original datagram" when
+    using ICMP extensions
+*/
+#define ICMP_ORIGINAL_DATAGRAM_MIN_SIZE 128
+
+/*  The classnum and type of MPLS labels in an ICMP extension object  */
+#define ICMP_EXT_MPLS_CLASSNUM 1
+#define ICMP_EXT_MPLS_CTYPE 1
+
+#define HTTP_PORT 80
+
 /*  We can't rely on header files to provide this information, because
     the fields have different names between, for instance, Linux and 
     Solaris  */
@@ -48,6 +60,28 @@ struct ICMPHeader {
     uint16_t sequence;
 };
 
+/*  ICMP extension header, which might contain MPLS labels  */
+/*  See RFC 4884  */
+struct ICMPExtensionHeader {
+    uint8_t version;
+    uint8_t reserved;
+    uint16_t checksum;
+};
+
+/*  An object in an extended ICMP object  */
+struct ICMPExtensionObject {
+    uint16_t len;
+    uint8_t classnum;
+    uint8_t ctype;
+};
+
+/*  An MPLS label included in an ICMP extension  */
+/*  See RFC 4950  */
+struct ICMPExtMPLSLabel {
+    uint8_t label[3];  // Low 4 bits are Experimental Use, Stack
+    uint8_t ttl;
+};
+
 /* Structure of an UDP header.  */
 struct UDPHeader {
     uint16_t srcport;
index a31e09c21184a873d7fa94a686c2c927d1df5562..20ca8926d88706723a5025113d35cc59f75c0d12 100755 (executable)
@@ -313,17 +313,39 @@ class TestProbeICMPv6(mtrpacket.MtrPacketTest):
 class TestProbeUDP(mtrpacket.MtrPacketTest):
     'Test transmitting probes using UDP'
 
+    def udp_port_test(self, address):  # type: (unicode) -> None
+        'Test UDP probes with variations on source port and dest port'
+
+        cmd = '80 send-probe protocol udp ' + address
+        self.write_command(cmd)
+        reply = self.parse_reply()
+        self.assertEqual('reply', reply.command_name)
+
+        cmd = '81 send-probe protocol udp port 990 ' + address
+        self.write_command(cmd)
+        reply = self.parse_reply()
+        self.assertEqual('reply', reply.command_name)
+
+        cmd = '82 send-probe protocol udp localport 1991 ' + address
+        self.write_command(cmd)
+        reply = self.parse_reply()
+        self.assertEqual('reply', reply.command_name)
+
     def test_udp_v4(self):
         'Test IPv4 UDP probes'
 
         test_basic_probe(self, 4, 'udp')
 
+        self.udp_port_test('ip-4 127.0.0.1')
+
     @unittest.skipUnless(mtrpacket.HAVE_IPV6, 'No IPv6')
     def test_udp_v6(self):
         'Test IPv6 UDP probes'
 
         test_basic_probe(self, 6, 'udp')
 
+        self.udp_port_test('ip-6 ::1')
+
 
 class TestProbeTCP(mtrpacket.MtrPacketTest):
     'Test TCP probe support'