From: Matt Kimball Date: Thu, 22 Dec 2016 15:28:29 +0000 (-0800) Subject: mtr-packet: MPLS decoding and local UDP port usage X-Git-Tag: v0.88~15^2~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7abc1cbd78161cb116ede72b4bff7cdff8d847c6;p=thirdparty%2Fmtr.git mtr-packet: MPLS decoding and local UDP port usage 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. --- diff --git a/AUTHORS b/AUTHORS index f40ecf4..d4c2a1f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,5 +1,5 @@ - Matt Kimball is the primary author of mtr. + Matt Kimball is the primary author of mtr. Roger Wolff is currently maintaining mtr. diff --git a/README b/README index 3ff0477..b2d6319 100644 --- 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? diff --git a/SECURITY b/SECURITY index a91ebac..f9127ac 100644 --- 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, diff --git a/cmdpipe.c b/cmdpipe.c index 20add25..3a38ec7 100644 --- 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 6a37a9a..97ab0a3 100644 --- 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 ", + "Matt Kimball ", "Roger Wolff ", "Bohdan Vlasyuk ", "Evgeniy Tretyak ", diff --git a/mtr-packet.8.in b/mtr-packet.8.in index 9dab7e6..97b46af 100644 --- a/mtr-packet.8.in +++ b/mtr-packet.8.in @@ -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 diff --git a/packet/command.c b/packet/command.c index 0533938..faee5a8 100644 --- a/packet/command.c +++ b/packet/command.c @@ -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(¶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; diff --git a/packet/construct_unix.c b/packet/construct_unix.c index 055fd49..5800426 100644 --- a/packet/construct_unix.c +++ b/packet/construct_unix.c @@ -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; } diff --git a/packet/construct_unix.h b/packet/construct_unix.h index 976c8f1..1f0ae48 100644 --- a/packet/construct_unix.h +++ b/packet/construct_unix.h @@ -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, diff --git a/packet/deconstruct_unix.c b/packet/deconstruct_unix.c index cbef815..8065dd7 100644 --- a/packet/deconstruct_unix.c +++ b/packet/deconstruct_unix.c @@ -20,9 +20,12 @@ #include #include +#include #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); } } } diff --git a/packet/probe.c b/packet/probe.c index ea364de..7817866 100644 --- a/packet/probe.c +++ b/packet/probe.c @@ -27,6 +27,7 @@ #include #include +#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); } diff --git a/packet/probe.h b/packet/probe.h index 47ddd22..6538388 100644 --- a/packet/probe.h +++ b/packet/probe.h @@ -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, diff --git a/packet/probe_cygwin.c b/packet/probe_cygwin.c index 417fce7..0818f52 100644 --- a/packet/probe_cygwin.c +++ b/packet/probe_cygwin.c @@ -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); } diff --git a/packet/probe_unix.c b/packet/probe_unix.c index df245e4..cdfd699 100644 --- a/packet/probe_unix.c +++ b/packet/probe_unix.c @@ -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); diff --git a/packet/probe_unix.h b/packet/probe_unix.h index 51bba8b..8a36e29 100644 --- a/packet/probe_unix.h +++ b/packet/probe_unix.h @@ -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, diff --git a/packet/protocols.h b/packet/protocols.h index 84d51a1..ea3f07e 100644 --- a/packet/protocols.h +++ b/packet/protocols.h @@ -37,6 +37,18 @@ /* 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; diff --git a/test/probe.py b/test/probe.py index a31e09c..20ca892 100755 --- a/test/probe.py +++ b/test/probe.py @@ -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'