- 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.
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?
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:
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,
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(
}
+/*
+ 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.
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;
*round_trip_time = 0;
memset(fromaddress, 0, sizeof(ip_t));
+ memset(mpls, 0, sizeof(struct mplslen));
found_ip = false;
found_round_trip = false;
found_round_trip = true;
}
}
+
+ /* MPLS labels */
+ if (!strcmp(arg_name, "mpls")) {
+ parse_mpls_values(mpls, arg_value);
+ }
}
return found_ip && found_round_trip;
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);
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>",
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
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
}
}
+ /* 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);
memset(¶m, 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;
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)
icmp->type = ICMP_ECHO;
icmp->id = htons(getpid());
- icmp->sequence = htons(port);
+ icmp->sequence = htons(sequence);
icmp->checksum = htons(compute_checksum(icmp, icmp_size));
}
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)
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.
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)
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)
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
{
int stream_socket;
int addr_len;
+ int dest_port;
struct sockaddr_storage dest_port_addr;
struct sockaddr_storage src_port_addr;
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)) {
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;
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,
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;
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) {
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,
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 {
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) {
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,
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;
}
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,
#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
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;
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);
+ }
}
/*
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);
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) {
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;
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) {
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
}
}
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);
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) {
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;
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) {
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.
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) {
*/
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) {
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);
}
}
}
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) {
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);
}
}
}
#include <sys/socket.h>
#include <unistd.h>
+#include "command.h"
#include "platform.h"
#include "protocols.h"
#include "timeval.h"
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;
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.
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;
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);
}
/* 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;
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;
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);
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,
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);
}
{
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);
}
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) {
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);
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;
}
}
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;
(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);
}
/*
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);
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);
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,
/* 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 */
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;
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'