mtr-packet - send and receive network probes
.SH DESCRIPTION
.B mtr-packet
+is a tool for sending network probes to measure network connectivity and
+performance. Many network probes can be sent simultaneously by a single
+process instance of
+.B mtr-packet
+and additional probes can be generated by an instance of
+.B mtr-packet
+which already has network probes in flight. It is intended to be used
+by programs which invoke it with Unix pipes attached to its standard input
+and output streams.
+.LP
+.B mtr-packet
reads command requests from
-.I stdin,
+.IR stdin ,
each separated by a newline character, and responds with command replies to
.IR stdout ,
also each separated by a newline character. The syntactic structure of
The Internet Protocol version 6 address to probe.
.HP 7
.IP
+.B protocol
+.I PROTOCOL
+.HP 14
+.IP
+The protocol to use for the network probe.
+.B icmp
+and
+.B udp
+may be used. The default protocol is
+.BR icmp.
+.HP 7
+.IP
+.B port
+.I PORT-NUMBER
+.HP 14
+.IP
+The destination port to use for
+.B udp
+probes.
+.HP 7
+.IP
.B timeout
.I TIMEOUT-SECONDS
.HP 14
.UR http://\:www.\:bitwizard.\:nl/\:mtr/
.UE
.PP
-The mtr mailinglist was little used and is no longer active.
-.PP
For patches, bug reports, or feature requests, please open an issue on
GitHub at:
.UR https://\:github\:.com/\:traviscross/\:mtr
.UE .
.SH "SEE ALSO"
.BR mtr (8),
+.BR icmp (7),
+.BR udp (7),
TCP/IP Illustrated (Stevens, ISBN 0201633469).
.UR http://\:www.\:bitwizard.\:nl/\:mtr/
.UE
.PP
-The mtr mailinglist was little used and is no longer active.
-.PP
For patches, bug reports, or feature requests, please open an issue on
GitHub at:
.UR https://\:github\:.com/\:traviscross/\:mtr
#define MinSequence 33000
#define MaxSequence 65536
+#define COMMAND_BUFFER_SIZE 4096
#define PACKET_REPLY_BUFFER_SIZE 4096
static int packetsize; /* packet size used by ping */
int seq = new_sequence(ctl, index);
int time_to_live = index + 1;
char ip_string[INET6_ADDRSTRLEN];
+ char command[COMMAND_BUFFER_SIZE];
+ char argument[COMMAND_BUFFER_SIZE];
+ int remaining_size;
const char *ip_type;
+ const char *protocol = NULL;
/* Conver the remote IP address to a string */
if (inet_ntop(
ip_type = "ip-4";
}
- /* Send a probe using the mtr-packet subprocess */
- if (dprintf(
- packet_command_pipe.write_fd,
- "%d send-probe %s %s ttl %d\n",
- seq, ip_type, ip_string, time_to_live) < 0) {
+ if (ctl->mtrtype == IPPROTO_ICMP) {
+ protocol = "icmp";
+ } else if (ctl->mtrtype == IPPROTO_UDP) {
+ protocol = "udp";
+ } else {
+ display_close(ctl);
+ error(EXIT_FAILURE, 0, "protocol unsupported by mtr-packet interface");
+ }
+
+ snprintf(
+ command, COMMAND_BUFFER_SIZE,
+ "%d send-probe %s %s protocol %s ttl %d",
+ seq, ip_type, ip_string, protocol, time_to_live);
+
+ if (ctl->remoteport) {
+ remaining_size = COMMAND_BUFFER_SIZE - strlen(command) - 1;
+
+ snprintf(argument, COMMAND_BUFFER_SIZE, " port %d", ctl->remoteport);
+ strncat(command, argument, remaining_size);
+ }
+
+ remaining_size = COMMAND_BUFFER_SIZE - strlen(command) - 1;
+ strncat(command, "\n", remaining_size);
+ /* Send a probe using the mtr-packet subprocess */
+ if (write(packet_command_pipe.write_fd, command, strlen(command)) == -1) {
display_close(ctl);
error(EXIT_FAILURE, errno, "mtr-packet command pipe write failure");
}
}
}
-
-/* Ensure we can communicate with the mtr-packet subprocess */
-static int net_command_pipe_check(struct mtr_ctl *ctl)
+/*
+ Send a command synchronously to mtr-packet, blocking until a result
+ is available. This is intended to be used at start-up to check the
+ capabilities of mtr-packet, but probes should be sent asynchronously
+ to avoid blocking other probes and the user interface.
+*/
+static int net_synchronous_command(
+ struct mtr_ctl *ctl, const char *cmd, struct command_t *result)
{
- const char *check_command = "1 check-support feature send-probe\n";
- struct command_t command;
char reply[PACKET_REPLY_BUFFER_SIZE];
int command_length;
int write_length;
int parse_result;
/* Query send-probe support */
- command_length = strlen(check_command);
+ command_length = strlen(cmd);
write_length = write(
- packet_command_pipe.write_fd, check_command, command_length);
+ packet_command_pipe.write_fd, cmd, command_length);
if (write_length == -1) {
return -1;
/* Parse the query reply */
reply[read_length] = 0;
- parse_result = parse_command(&command, reply);
+ parse_result = parse_command(result, reply);
if (parse_result) {
errno = parse_result;
return -1;
}
- /* Check that send-probe is supported */
- if (!strcmp(command.command_name, "feature-support")
- && command.argument_count >= 1
- && !strcmp(command.argument_name[0], "support")
- && !strcmp(command.argument_value[0], "ok")) {
+ return 0;
+}
+
+
+/* Check support for a particular feature with the mtr-packet we invoked */
+static int net_check_feature(struct mtr_ctl *ctl, const char *feature)
+{
+ char check_command[COMMAND_BUFFER_SIZE];
+ struct command_t reply;
+
+ snprintf(
+ check_command, COMMAND_BUFFER_SIZE,
+ "1 check-support feature %s\n", feature);
+
+ if (net_synchronous_command(ctl, check_command, &reply) == -1) {
+ return -1;
+ }
+
+ /* Check that the feature is supported */
+ if (!strcmp(reply.command_name, "feature-support")
+ && reply.argument_count >= 1
+ && !strcmp(reply.argument_name[0], "support")
+ && !strcmp(reply.argument_value[0], "ok")) {
/* Looks good */
return 0;
}
+/*
+ Check the protocol selected against the mtr-packet we are using.
+ Returns zero if everything is fine, or -1 with errno for either
+ a failure during the check, or for an unsupported feature.
+*/
+static int net_packet_feature_check(struct mtr_ctl *ctl)
+{
+ if (ctl->mtrtype == IPPROTO_ICMP) {
+ return net_check_feature(ctl, "icmp");
+ } else if (ctl->mtrtype == IPPROTO_UDP) {
+ return net_check_feature(ctl, "udp");
+ } else {
+ errno = EINVAL;
+ return -1;
+ }
+}
+
+
/* Create the command pipe to a new mtr-packet subprocess */
static int net_command_pipe_open(struct mtr_ctl *ctl)
{
Check that we can communicate with the client. If we failed to
execute the mtr-packet binary, we will discover that here.
*/
- if (net_command_pipe_check(ctl)) {
+ if (net_check_feature(ctl, "send-probe")) {
error(EXIT_FAILURE, errno, "Failure to start mtr-packet");
}
+ if (net_packet_feature_check(ctl)) {
+ error(EXIT_FAILURE, errno, "Packet type unsupported");
+ }
+
/* We will need non-blocking reads from the child */
set_fd_nonblock(packet_command_pipe.read_fd);
}
return NULL;
}
+/* Returns a feature support string for a particular probe protocol */
+static
+const char *check_protocol_support(
+ struct net_state_t *net_state,
+ int protocol)
+{
+ if (is_protocol_supported(net_state, protocol)) {
+ return "ok";
+ } else {
+ return "no";
+ }
+}
+
/* Given a feature name, return a string for the check-support reply */
static
const char *check_support(
- const char *feature)
+ const char *feature,
+ struct net_state_t *net_state)
{
if (!strcmp(feature, "version")) {
return PACKAGE_VERSION;
return "ok";
}
+ if (!strcmp(feature, "icmp")) {
+ return check_protocol_support(net_state, IPPROTO_ICMP);
+ }
+
+ if (!strcmp(feature, "udp")) {
+ return check_protocol_support(net_state, IPPROTO_UDP);
+ }
+
return "no";
}
/* Handle a check-support request by checking for a particular feature */
static
void check_support_command(
- const struct command_t *command)
+ const struct command_t *command,
+ struct net_state_t *net_state)
{
const char *feature;
const char *support;
return;
}
- support = check_support(feature);
+ support = check_support(feature, net_state);
printf("%d feature-support support %s\n", command->token, support);
}
param->address = value;
}
+ /* Protocol for the probe */
+ if (!strcmp(name, "protocol")) {
+ if (!strcmp(value, "icmp")) {
+ param->protocol = IPPROTO_ICMP;
+ } else if (!strcmp(value, "udp")) {
+ param->protocol = IPPROTO_UDP;
+ } else {
+ return false;
+ }
+ }
+
+ /* Destination port for the probe */
+ if (!strcmp(name, "port")) {
+ param->dest_port = strtol(value, &endstr, 10);
+ if (*endstr != 0) {
+ return false;
+ }
+ }
+
/* Time-to-live values */
if (!strcmp(name, "ttl")) {
param->ttl = strtol(value, &endstr, 10);
/* We will prepare a probe_param_t for send_probe. */
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.timeout = 10;
struct net_state_t *net_state)
{
if (!strcmp(command->command_name, "check-support")) {
- check_support_command(command);
+ check_support_command(command, net_state);
} else if (!strcmp(command->command_name, "send-probe")) {
send_probe_command(command, net_state);
} else {
#include <errno.h>
#include <string.h>
+#include <sys/socket.h>
#include <unistd.h>
#include "protocols.h"
size_t size;
};
-/*
- Compute the IP checksum (or ICMP checksum) of a packet.
- We may need to use data from multiple sources, to checksum
- the "psuedo-header" for UDP or ICMPv6.
-*/
+/* Compute the IP checksum (or ICMP checksum) of a packet. */
static
uint16_t compute_checksum(
- struct checksum_source_t *source,
- int source_count)
+ const void *packet,
+ int size)
{
- int i, j;
- const uint8_t *bytes;
- size_t size;
+ const uint8_t *packet_bytes = (uint8_t *)packet;
uint32_t sum = 0;
+ int i;
- for (i = 0; i < source_count; i++) {
- bytes = (uint8_t *)source[i].data;
- size = source[i].size;
-
- for (j = 0; j < size; j++) {
- if ((j & 1) == 0) {
- sum += bytes[j] << 8;
- } else {
- sum += bytes[j];
- }
+ for (i = 0; i < size; i++) {
+ if ((i & 1) == 0) {
+ sum += packet_bytes[i] << 8;
+ } else {
+ sum += packet_bytes[i];
}
}
return (~sum & 0xffff);
}
-/* Compute the checksum from a single source of data */
-static
-uint16_t simple_checksum(
- const void *packet,
- size_t size)
-{
- struct checksum_source_t source;
-
- source.data = packet;
- source.size = size;
-
- return compute_checksum(&source, 1);
-}
-
-/*
- ICMPv6 and UDPv6 use a pseudo-header with a different layout
- from the real IPv6 header for checksum purposes. We'll fill
- in the psuedo-header and use it to start the checksum against
- the packet.
-*/
-static
-uint16_t pseudo6_checksum(
- const void *ip_packet,
- const void *packet,
- size_t size)
-{
- const struct IP6Header *ip = (struct IP6Header *)ip_packet;
- struct IP6PseudoHeader pseudo;
- struct checksum_source_t source[2];
-
- memcpy(pseudo.saddr, ip->saddr, sizeof(struct in6_addr));
- memcpy(pseudo.daddr, ip->daddr, sizeof(struct in6_addr));
- pseudo.len = ip->len;
- memset(pseudo.zero, 0, sizeof(pseudo.zero));
- pseudo.protocol = ip->protocol;
-
- source[0].data = &pseudo;
- source[0].size = sizeof(struct IP6PseudoHeader);
- source[1].data = packet;
- source[1].size = size;
-
- return compute_checksum(source, 2);
-}
-
/* Encode the IP header length field in the order required by the OS. */
static
uint16_t length_byte_swap(
ip->version = 0x45;
ip->len = length_byte_swap(net_state, packet_size);
ip->ttl = param->ttl;
- ip->protocol = IPPROTO_ICMP;
+ ip->protocol = param->protocol;
memcpy(&ip->saddr, &srcaddr4->sin_addr, sizeof(uint32_t));
memcpy(&ip->daddr, &destaddr4->sin_addr, sizeof(uint32_t));
}
-/* Construct a header for IP version 6 */
-static
-void construct_ip6_header(
- const struct net_state_t *net_state,
- char *packet_buffer,
- int packet_size,
- const struct sockaddr_storage *srcaddr,
- const struct sockaddr_storage *destaddr,
- const struct probe_param_t *param)
-{
- struct IP6Header *ip;
- int payload_size;
- struct sockaddr_in6 *srcaddr6 = (struct sockaddr_in6 *)srcaddr;
- struct sockaddr_in6 *destaddr6 = (struct sockaddr_in6 *)destaddr;
-
- if (!net_state->platform.ipv6_header_constructed) {
- return;
- }
-
- ip = (struct IP6Header *)&packet_buffer[0];
- payload_size = packet_size - sizeof(struct IP6Header);
-
- ip->version = 0x60;
- ip->len = htons(payload_size);
- ip->protocol = IPPROTO_ICMPV6;
- ip->ttl = param->ttl;
- memcpy(&ip->saddr, &srcaddr6->sin6_addr, sizeof(struct in6_addr));
- memcpy(&ip->daddr, &destaddr6->sin6_addr, sizeof(struct in6_addr));
-}
-
/* Construct an ICMP header for IPv4 */
static
void construct_icmp4_header(
icmp->type = ICMP_ECHO;
icmp->id = htons(getpid());
icmp->sequence = htons(param->command_token);
- icmp->checksum = htons(simple_checksum(icmp, icmp_size));
+ icmp->checksum = htons(compute_checksum(icmp, icmp_size));
}
/* Construct an ICMP header for IPv6 */
static
-void construct_icmp6_header(
+int construct_icmp6_packet(
const struct net_state_t *net_state,
char *packet_buffer,
int packet_size,
const struct probe_param_t *param)
{
struct ICMPHeader *icmp;
- int icmp_size;
- if (net_state->platform.ipv6_header_constructed) {
- icmp = (struct ICMPHeader *)&packet_buffer[sizeof(struct IP6Header)];
- icmp_size = packet_size - sizeof(struct IP6Header);
- } else {
- icmp = (struct ICMPHeader *)packet_buffer;
- icmp_size = packet_size;
- }
+ icmp = (struct ICMPHeader *)packet_buffer;
icmp->type = ICMP6_ECHO;
icmp->id = htons(getpid());
icmp->sequence = htons(param->command_token);
- if (net_state->platform.ipv6_header_constructed) {
- icmp->checksum = htons(
- pseudo6_checksum(packet_buffer, icmp, icmp_size));
+ if (setsockopt(
+ net_state->platform.icmp6_send_socket, IPPROTO_IPV6,
+ IPV6_UNICAST_HOPS, ¶m->ttl, sizeof(int))) {
+ return -errno;
+ }
+
+ return 0;
+}
+
+/*
+ Construct a header for UDP probes. We'll use the source port
+ as the command token, so that we can identify the probe when its
+ header is returned embedded in an ICMP reply.
+*/
+static
+void construct_udp4_header(
+ const struct net_state_t *net_state,
+ char *packet_buffer,
+ int packet_size,
+ const struct probe_param_t *param)
+{
+ struct UDPHeader *udp;
+ int udp_size;
+
+ udp = (struct UDPHeader *)&packet_buffer[sizeof(struct IPHeader)];
+ udp_size = packet_size - sizeof(struct IPHeader);
+
+ udp->srcport = htons(param->command_token);
+ udp->dstport = htons(param->dest_port);
+ udp->length = htons(udp_size);
+ udp->checksum = 0;
+}
+
+/* Construct a header for UDPv6 probes */
+static
+int construct_udp6_packet(
+ const struct net_state_t *net_state,
+ char *packet_buffer,
+ int packet_size,
+ const struct probe_param_t *param)
+{
+ int udp_socket = net_state->platform.udp6_send_socket;
+ struct UDPHeader *udp;
+ int udp_size;
+
+ udp = (struct UDPHeader *)packet_buffer;
+ udp_size = packet_size;
+
+ udp->srcport = htons(param->command_token);
+ udp->dstport = htons(param->dest_port);
+ udp->length = htons(udp_size);
+ udp->checksum = 0;
+
+ /* Set the TTL via setsockopt */
+ if (setsockopt(
+ udp_socket, IPPROTO_IPV6,
+ IPV6_UNICAST_HOPS, ¶m->ttl, sizeof(int))) {
+
+ return -errno;
}
+
+ /*
+ Instruct the kernel to put the pseudoheader checksum into the
+ UDP header.
+ */
+ int chksum_offset = (char *)&udp->checksum - (char *)udp;
+ if (setsockopt(
+ udp_socket, IPPROTO_IPV6,
+ IPV6_CHECKSUM, &chksum_offset, sizeof(int))) {
+
+ return -errno;
+ }
+
+ return 0;
}
/*
const struct net_state_t *net_state,
const struct probe_param_t *param)
{
- int packet_size = 0;
+ int packet_size;
if (param->ip_version == 6) {
- if (net_state->platform.ipv6_header_constructed) {
- packet_size = sizeof(struct IP6Header);
- }
+ packet_size = 0;
} else if (param->ip_version == 4) {
packet_size = sizeof(struct IPHeader);
} else {
return -EINVAL;
}
- packet_size += sizeof(struct ICMPHeader);
+
+ if (param->protocol == IPPROTO_ICMP) {
+ packet_size += sizeof(struct ICMPHeader);
+ } else if (param->protocol == IPPROTO_UDP) {
+ packet_size += sizeof(struct UDPHeader);
+ } else {
+ return -EINVAL;
+ }
return packet_size;
}
memset(packet_buffer, 0, packet_size);
+ err = 0;
if (param->ip_version == 6) {
- construct_ip6_header(
- net_state, packet_buffer, packet_size,
- &src_sockaddr, dest_sockaddr, param);
- construct_icmp6_header(
- net_state, packet_buffer, packet_size, param);
+ if (param->protocol == IPPROTO_ICMP) {
+ err = construct_icmp6_packet(
+ net_state, packet_buffer, packet_size, param);
+ } else if (param->protocol == IPPROTO_UDP) {
+ err = construct_udp6_packet(
+ net_state, packet_buffer, packet_size, param);
+ } else {
+ return -EINVAL;
+ }
} else {
construct_ip4_header(
net_state, packet_buffer, packet_size,
&src_sockaddr, dest_sockaddr, param);
- construct_icmp4_header(
- net_state, packet_buffer, packet_size, param);
+
+ if (param->protocol == IPPROTO_ICMP) {
+ construct_icmp4_header(
+ net_state, packet_buffer, packet_size, param);
+ } else if (param->protocol == IPPROTO_UDP) {
+ construct_udp4_header(
+ net_state, packet_buffer, packet_size, param);
+ } else {
+ return -EINVAL;
+ }
+ }
+
+ if (err) {
+ return err;
}
return packet_size;
const struct sockaddr_storage *remote_addr,
struct timeval timestamp,
int icmp_type,
+ int protocol,
int icmp_id,
int icmp_sequence)
{
struct probe_t *probe;
- probe = find_probe(net_state, icmp_id, icmp_sequence);
+ probe = find_probe(net_state, protocol, icmp_id, icmp_sequence);
if (probe == NULL) {
return;
}
receive_probe(probe, icmp_type, remote_addr, timestamp);
}
+/*
+ We've received an ICMP message with an embedded IP packet.
+ We will try to determine which of our outgoing probes
+ corresponds to the embedded IP packet and record the response.
+*/
+static
+void handle_inner_ip4_packet(
+ struct net_state_t *net_state,
+ const struct sockaddr_storage *remote_addr,
+ int icmp_result,
+ const struct IPHeader *ip,
+ int packet_length,
+ struct timeval timestamp)
+{
+ const int ip_icmp_size =
+ sizeof(struct IPHeader) + sizeof(struct ICMPHeader);
+ const int ip_udp_size =
+ sizeof(struct IPHeader) + sizeof(struct UDPHeader);
+ const struct ICMPHeader *icmp;
+ const struct UDPHeader *udp;
+
+ if (ip->protocol == IPPROTO_ICMP) {
+ if (packet_length < ip_icmp_size) {
+ return;
+ }
+
+ icmp = (struct ICMPHeader *)(ip + 1);
+
+ find_and_receive_probe(
+ net_state, remote_addr, timestamp, icmp_result,
+ IPPROTO_ICMP, icmp->id, icmp->sequence);
+ } else if (ip->protocol == IPPROTO_UDP) {
+ if (packet_length < ip_udp_size) {
+ return;
+ }
+
+ udp = (struct UDPHeader *)(ip + 1);
+
+ find_and_receive_probe(
+ net_state, remote_addr, timestamp, icmp_result,
+ IPPROTO_UDP, 0, udp->srcport);
+ }
+}
+
+/*
+ Examine the IPv6 header embedded in a returned ICMPv6 packet
+ in order to match it with a probe which we previously sent.
+*/
+static
+void handle_inner_ip6_packet(
+ struct net_state_t *net_state,
+ const struct sockaddr_storage *remote_addr,
+ int icmp_result,
+ const struct IP6Header *ip,
+ int packet_length,
+ struct timeval timestamp)
+{
+ const int ip_icmp_size =
+ sizeof(struct IP6Header) + sizeof(struct ICMPHeader);
+ const int ip_udp_size =
+ sizeof(struct IP6Header) + sizeof(struct UDPHeader);
+ const struct ICMPHeader *icmp;
+ const struct UDPHeader *udp;
+
+ if (ip->protocol == IPPROTO_ICMPV6) {
+ if (packet_length < ip_icmp_size) {
+ return;
+ }
+
+ icmp = (struct ICMPHeader *)(ip + 1);
+
+ find_and_receive_probe(
+ net_state, remote_addr, timestamp, icmp_result,
+ IPPROTO_ICMP, icmp->id, icmp->sequence);
+ } else if (ip->protocol == IPPROTO_UDP) {
+ if (packet_length < ip_udp_size) {
+ return;
+ }
+
+ udp = (struct UDPHeader *)(ip + 1);
+
+ find_and_receive_probe(
+ net_state, remote_addr, timestamp, icmp_result,
+ IPPROTO_UDP, 0, udp->srcport);
+ }
+}
+
/*
Decode the ICMP header received and try to find a probe which it
is in response to.
*/
static
-void handle_received_icmpv4_packet(
+void handle_received_icmp4_packet(
struct net_state_t *net_state,
const struct sockaddr_storage *remote_addr,
const struct ICMPHeader *icmp,
int packet_length,
struct timeval timestamp)
{
- const int icmp_ip_icmp_size =
- sizeof(struct ICMPHeader) +
- sizeof(struct IPHeader) + sizeof(struct ICMPHeader);
+ const int icmp_ip_size =
+ sizeof(struct ICMPHeader) + sizeof(struct IPHeader);
const struct IPHeader *inner_ip;
- const struct ICMPHeader *inner_icmp;
+ int inner_size = packet_length - sizeof(struct ICMPHeader);
/* If we get an echo reply, our probe reached the destination host */
if (icmp->type == ICMP_ECHOREPLY) {
find_and_receive_probe(
net_state, remote_addr, timestamp,
- ICMP_ECHOREPLY, icmp->id, icmp->sequence);
+ ICMP_ECHOREPLY, IPPROTO_ICMP, icmp->id, icmp->sequence);
}
+ if (packet_length < icmp_ip_size) {
+ return;
+ }
+ inner_ip = (struct IPHeader *)(icmp + 1);
+
/*
If we get a time exceeded, we got a response from an intermediate
host along the path to our destination.
*/
if (icmp->type == ICMP_TIME_EXCEEDED) {
- if (packet_length < icmp_ip_icmp_size) {
- return;
- }
-
/*
The IP packet inside the ICMP response contains our original
IP header. That's where we can get our original ID and
sequence number.
*/
- inner_ip = (struct IPHeader *)(icmp + 1);
- inner_icmp = (struct ICMPHeader *)(inner_ip + 1);
+ handle_inner_ip4_packet(
+ net_state, remote_addr,
+ ICMP_TIME_EXCEEDED, inner_ip, inner_size, timestamp);
+ }
- find_and_receive_probe(
- net_state, remote_addr, timestamp,
- ICMP_TIME_EXCEEDED, inner_icmp->id, inner_icmp->sequence);
+ if (icmp->type == ICMP_DEST_UNREACH) {
+ /*
+ We'll get a ICMP_PORT_UNREACH when a non-ICMP probe
+ reaches its final destination. (Assuming that port isn't
+ open on the destination host.)
+ */
+ if (icmp->code == ICMP_PORT_UNREACH) {
+ handle_inner_ip4_packet(
+ net_state, remote_addr,
+ ICMP_ECHOREPLY, inner_ip, inner_size, timestamp);
+ }
}
}
constants differ.
*/
static
-void handle_received_icmpv6_packet(
+void handle_received_icmp6_packet(
struct net_state_t *net_state,
const struct sockaddr_storage *remote_addr,
const struct ICMPHeader *icmp,
int packet_length,
struct timeval timestamp)
{
- const int icmp_ip_icmp_size =
- sizeof(struct ICMPHeader) +
- sizeof(struct IP6Header) + sizeof(struct ICMPHeader);
+ const int icmp_ip_size =
+ sizeof(struct ICMPHeader) + sizeof(struct IP6Header);
const struct IP6Header *inner_ip;
- const struct ICMPHeader *inner_icmp;
+ int inner_size = packet_length - sizeof(struct ICMPHeader);
if (icmp->type == ICMP6_ECHOREPLY) {
find_and_receive_probe(
- net_state, remote_addr, timestamp,
- ICMP_ECHOREPLY, icmp->id, icmp->sequence);
+ net_state, remote_addr, timestamp, ICMP_ECHOREPLY,
+ IPPROTO_ICMP, icmp->id, icmp->sequence);
}
- if (icmp->type == ICMP6_TIME_EXCEEDED) {
- if (packet_length < icmp_ip_icmp_size) {
- return;
- }
+ if (packet_length < icmp_ip_size) {
+ return;
+ }
+ inner_ip = (struct IP6Header *)(icmp + 1);
- inner_ip = (struct IP6Header *)(icmp + 1);
- inner_icmp = (struct ICMPHeader *)(inner_ip + 1);
+ if (icmp->type == ICMP6_TIME_EXCEEDED) {
+ handle_inner_ip6_packet(
+ net_state, remote_addr,
+ ICMP_TIME_EXCEEDED, inner_ip, inner_size, timestamp);
+ }
- find_and_receive_probe(
- net_state, remote_addr, timestamp,
- ICMP_TIME_EXCEEDED, inner_icmp->id, inner_icmp->sequence);
+ if (icmp->type == ICMP6_DEST_UNREACH) {
+ if (icmp->code == ICMP6_PORT_UNREACH) {
+ handle_inner_ip6_packet(
+ net_state, remote_addr,
+ ICMP_ECHOREPLY, inner_ip, inner_size, timestamp);
+ }
}
}
We'll check to see that it is a response to one of our probes, and
if so, report the result of the probe to our command stream.
*/
-void handle_received_ipv4_packet(
+void handle_received_ip4_packet(
struct net_state_t *net_state,
const struct sockaddr_storage *remote_addr,
const void *packet,
icmp = (struct ICMPHeader *)(ip + 1);
icmp_length = packet_length - sizeof(struct IPHeader);
- handle_received_icmpv4_packet(
+ handle_received_icmp4_packet(
net_state, remote_addr, icmp, icmp_length, timestamp);
}
in received packets, so we can assume the packet we got starts
with the ICMP packet.
*/
-void handle_received_ipv6_packet(
+void handle_received_ip6_packet(
struct net_state_t *net_state,
const struct sockaddr_storage *remote_addr,
const void *packet,
icmp = (struct ICMPHeader *)packet;
- handle_received_icmpv6_packet(
+ handle_received_icmp6_packet(
net_state, remote_addr, icmp, packet_length, timestamp);
}
int packet_length,
struct timeval timestamp);
-void handle_received_ipv4_packet(
+void handle_received_ip4_packet(
struct net_state_t *net_state,
const struct sockaddr_storage *remote_addr,
const void *packet,
int packet_length,
struct timeval timestamp);
-void handle_received_ipv6_packet(
+void handle_received_ip6_packet(
struct net_state_t *net_state,
const struct sockaddr_storage *remote_addr,
const void *packet,
*/
struct probe_t *find_probe(
struct net_state_t *net_state,
+ int protocol,
int icmp_id,
int icmp_sequence)
{
struct probe_t *probe;
/*
- If the ICMP id doesn't match our process ID, it wasn't a
- probe generated by this process, so ignore it.
+ ICMP has room for an id to check against our process, but
+ UDP doesn't.
*/
- if (icmp_id != htons(getpid())) {
- return NULL;
+ if (protocol == IPPROTO_ICMP) {
+ /*
+ If the ICMP id doesn't match our process ID, it wasn't a
+ probe generated by this process, so ignore it.
+ */
+ if (icmp_id != htons(getpid())) {
+ return NULL;
+ }
}
for (i = 0; i < MAX_PROBES; i++) {
struct sockaddr_in *destaddr4;
struct sockaddr_in6 *destaddr6;
struct sockaddr_storage dest_with_port;
+ struct sockaddr_in *srcaddr4;
+ struct sockaddr_in6 *srcaddr6;
dest_with_port = *destaddr;
close(sock);
+ /*
+ Zero the port, as we may later use this address to finding, and
+ we don't want to use the port from the socket we just created.
+ */
+ if (destaddr->ss_family == AF_INET6) {
+ srcaddr6 = (struct sockaddr_in6 *)srcaddr;
+
+ srcaddr6->sin6_port = 0;
+ } else {
+ srcaddr4 = (struct sockaddr_in *)srcaddr;
+
+ srcaddr4->sin_port = 0;
+ }
+
return 0;
}
/* The IP address to probe */
const char *address;
+ /* Protocol for the probe, using the IPPROTO_* defines */
+ int protocol;
+
+ /* The destination port for non-ICMP probes */
+ int dest_port;
+
/* Time to live for the transmited probe */
int ttl;
void init_net_state(
struct net_state_t *net_state);
+bool is_protocol_supported(
+ struct net_state_t *net_state,
+ int protocol);
+
bool get_next_probe_timeout(
const struct net_state_t *net_state,
struct timeval *timeout);
struct probe_t *find_probe(
struct net_state_t *net_state,
+ int protocol,
int icmp_id,
int icmp_sequence);
}
}
+/* On Windows, we only support ICMP probes */
+bool is_protocol_supported(
+ struct net_state_t *net_state,
+ int protocol)
+{
+ if (protocol == IPPROTO_ICMP) {
+ return true;
+ }
+
+ return false;
+}
+
/*
The overlapped I/O style completion routine to be called by
Windows during an altertable wait when an ICMP probe has
/* Use the "jumbo" frame size as the max packet size */
#define PACKET_BUFFER_SIZE 9000
-/* Set the IPv6 options affecting and outgoing IPv6 packet */
-static
-void set_ipv6_socket_options(
- int socket,
- const struct probe_param_t *param)
-{
- if (setsockopt(
- socket, IPPROTO_IPV6,
- IPV6_UNICAST_HOPS, ¶m->ttl, sizeof(int))) {
-
- perror("Failure to set IPV6_UNICAST_HOPS");
- exit(1);
- }
-}
-
/* A wrapper around sendto for mixed IPv4 and IPv6 sending */
static
int send_packet(
int packet_size,
const struct sockaddr_storage *sockaddr)
{
- int send_socket;
+ int send_socket = 0;
int sockaddr_length;
if (sockaddr->ss_family == AF_INET6) {
- send_socket = net_state->platform.ipv6_send_socket;
sockaddr_length = sizeof(struct sockaddr_in6);
- if (!net_state->platform.ipv6_header_constructed) {
- set_ipv6_socket_options(send_socket, param);
+ if (param->protocol == IPPROTO_ICMP) {
+ send_socket = net_state->platform.icmp6_send_socket;
+ } else if (param->protocol == IPPROTO_UDP) {
+ send_socket = net_state->platform.udp6_send_socket;
}
- } else {
- assert(sockaddr->ss_family == AF_INET);
- send_socket = net_state->platform.ipv4_send_socket;
+ } else if (sockaddr->ss_family == AF_INET) {
sockaddr_length = sizeof(struct sockaddr_in);
+
+ send_socket = net_state->platform.ip4_send_socket;
+ }
+
+ if (send_socket == 0) {
+ errno = EINVAL;
+ return -1;
}
return sendto(
memset(¶m, 0, sizeof(struct probe_param_t));
param.ip_version = 4;
+ param.protocol = IPPROTO_ICMP;
param.ttl = 255;
param.address = "127.0.0.1";
/* Open the raw sockets for sending/receiving IPv4 packets */
static
-void open_ipv4_sockets(
+void open_ip4_sockets(
struct net_state_t *net_state)
{
int send_socket;
exit(1);
}
- net_state->platform.ipv4_send_socket = send_socket;
- net_state->platform.ipv4_recv_socket = recv_socket;
+ net_state->platform.ip4_send_socket = send_socket;
+ net_state->platform.ip4_recv_socket = recv_socket;
}
/* Open the raw sockets for sending/receiving IPv6 packets */
static
-void open_ipv6_sockets(
+void open_ip6_sockets(
struct net_state_t *net_state)
{
- int send_socket;
+ int send_socket_icmp;
+ int send_socket_udp;
int recv_socket;
- int send_protocol;
- /*
- Linux allows us to construct our own IPv6 header, so
- we'll prefer that method for more explicit control.
-
- Other OSes, such as MacOS, don't allow this, and on
- those platforms we must use setsockopt() to control
- fields of the IP header.
- */
-#ifdef PLATFORM_LINUX
- net_state->platform.ipv6_header_constructed = true;
-#else
- net_state->platform.ipv6_header_constructed = false;
-#endif
-
- if (net_state->platform.ipv6_header_constructed) {
- send_protocol = IPPROTO_RAW;
- } else {
- send_protocol = IPPROTO_ICMPV6;
+ send_socket_icmp = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
+ if (send_socket_icmp == -1) {
+ perror("Failure opening ICMPv6 send socket");
+ exit(1);
}
- send_socket = socket(AF_INET6, SOCK_RAW, send_protocol);
- if (send_socket == -1) {
- perror("Failure opening IPv6 send socket");
+ send_socket_udp = socket(AF_INET6, SOCK_RAW, IPPROTO_UDP);
+ if (send_socket_udp == -1) {
+ perror("Failure opening UDPv6 send socket");
exit(1);
}
set_socket_nonblocking(recv_socket);
- net_state->platform.ipv6_send_socket = send_socket;
- net_state->platform.ipv6_recv_socket = recv_socket;
+ net_state->platform.icmp6_send_socket = send_socket_icmp;
+ net_state->platform.udp6_send_socket = send_socket_udp;
+ net_state->platform.ip6_recv_socket = recv_socket;
}
/*
{
memset(net_state, 0, sizeof(struct net_state_t));
- open_ipv4_sockets(net_state);
- open_ipv6_sockets(net_state);
+ open_ip4_sockets(net_state);
+ open_ip6_sockets(net_state);
}
/*
void init_net_state(
struct net_state_t *net_state)
{
- set_socket_nonblocking(net_state->platform.ipv4_recv_socket);
- set_socket_nonblocking(net_state->platform.ipv6_recv_socket);
+ set_socket_nonblocking(net_state->platform.ip4_recv_socket);
+ set_socket_nonblocking(net_state->platform.ip6_recv_socket);
check_length_order(net_state);
}
+/* Returns true if we can transmit probes using the specified protocol */
+bool is_protocol_supported(
+ struct net_state_t *net_state,
+ int protocol)
+{
+ if (protocol == IPPROTO_ICMP) {
+ return true;
+ }
+
+ if (protocol == IPPROTO_UDP) {
+ return true;
+ }
+
+ return false;
+}
+
/* Craft a custom ICMP packet for a network probe. */
void send_probe(
struct net_state_t *net_state,
if (send_packet(
net_state, param, packet, packet_size, &dest_sockaddr) == -1) {
- perror("Failure sending probe");
- exit(1);
+ if (errno == ENETDOWN) {
+ printf("%d network-down\n", param->command_token);
+ } else if (errno == ENETUNREACH) {
+ printf("%d no-route\n", param->command_token);
+ } else if (errno == EINVAL) {
+ printf("%d invalid-argument\n", param->command_token);
+ } else {
+ perror("Failure sending probe");
+ exit(1);
+ }
+
+ free_probe(probe);
+ return;
}
probe->platform.timeout_time = probe->platform.departure_time;
struct net_state_t *net_state)
{
receive_replies_from_socket(
- net_state, net_state->platform.ipv4_recv_socket,
- handle_received_ipv4_packet);
+ net_state, net_state->platform.ip4_recv_socket,
+ handle_received_ip4_packet);
receive_replies_from_socket(
- net_state, net_state->platform.ipv6_recv_socket,
- handle_received_ipv6_packet);
+ net_state, net_state->platform.ip6_recv_socket,
+ handle_received_ip6_packet);
}
/*
struct net_state_platform_t
{
/* Socket used to send raw IPv4 packets */
- int ipv4_send_socket;
+ int ip4_send_socket;
/* Socket used to receive IPv4 ICMP replies */
- int ipv4_recv_socket;
+ int ip4_recv_socket;
- /* Send socket for IPv6 packets */
- int ipv6_send_socket;
+ /* Send socket for ICMPv6 packets */
+ int icmp6_send_socket;
+
+ /* Send socket for UDPv6 packets */
+ int udp6_send_socket;
/* Receive socket for IPv6 packets */
- int ipv6_recv_socket;
+ int ip6_recv_socket;
/*
true if we should encode the IP header length in host order.
(as opposed to network order)
*/
bool ip_length_host_order;
-
- /*
- true if we are allowed to construct the IPv6 header, false if
- we need to let the network stack do it for us.
- */
- bool ipv6_header_constructed;
};
#endif
#define ICMP_ECHO 8
#define ICMP_TIME_EXCEEDED 11
+/* ICMP_DEST_UNREACH codes */
+#define ICMP_PORT_UNREACH 3
+
/* ICMPv6 type codes */
+#define ICMP6_DEST_UNREACH 1
#define ICMP6_TIME_EXCEEDED 3
#define ICMP6_ECHO 128
#define ICMP6_ECHOREPLY 129
+/* ICMP6_DEST_UNREACH codes */
+#define ICMP6_PORT_UNREACH 4
+
/* We can't rely on header files to provide this information, because
the fields have different names between, for instance, Linux and
Solaris */
};
/* Structure of an IPv4 UDP pseudoheader. */
-struct UDPv4PHeader {
+struct UDPPseudoHeader {
uint32_t saddr;
uint32_t daddr;
uint8_t zero;
struct timeval *select_timeout;
int ready_count;
int command_stream = command_buffer->command_stream;
- int ipv4_socket = net_state->platform.ipv4_recv_socket;
- int ipv6_socket = net_state->platform.ipv6_recv_socket;
+ int ip4_socket = net_state->platform.ip4_recv_socket;
+ int ip6_socket = net_state->platform.ip6_recv_socket;
FD_ZERO(&read_set);
FD_SET(command_stream, &read_set);
nfds = command_stream + 1;
- FD_SET(ipv4_socket, &read_set);
- if (ipv4_socket >= nfds) {
- nfds = ipv4_socket + 1;
+ FD_SET(ip4_socket, &read_set);
+ if (ip4_socket >= nfds) {
+ nfds = ip4_socket + 1;
}
- FD_SET(ipv6_socket, &read_set);
- if (ipv6_socket >= nfds) {
- nfds = ipv6_socket + 1;
+ FD_SET(ip6_socket, &read_set);
+ if (ip6_socket >= nfds) {
+ nfds = ip6_socket + 1;
}
while (true) {
'''Test mtr-packet's command parsing.'''
-import re
import time
import unittest
def test_invalid_argument(self):
'Test sending invalid arguments with probe requests'
- invalid_argument_regex = r'^[0-9]+ invalid-argument$'
-
bad_commands = [
'22 send-probe',
'23 send-probe ip-4 str-value',
for cmd in bad_commands:
self.write_command(cmd)
- reply = self.read_reply()
- match = re.match(invalid_argument_regex, reply)
- self.assertIsNotNone(match)
+ reply = self.parse_reply()
+ self.assertEqual(reply.command_name, 'invalid-argument')
def test_versioning(self):
'Test version checks and feature support checks'
feature_tests = [
- ('30 check-support feature version',
- r'^30 feature-support support [0-9]+\.[0-9a-z\-\.]+$'),
- ('31 check-support feature ip-4',
- r'^31 feature-support support ok$'),
- ('32 check-support feature send-probe',
- r'^32 feature-support support ok$'),
- ('33 check-support feature bogus-feature',
- r'^33 feature-support support no$')
+ ('31 check-support feature ip-4', 'ok'),
+ ('32 check-support feature send-probe', 'ok'),
+ ('33 check-support feature bogus-feature', 'no')
]
- for (request, regex) in feature_tests:
+ self.write_command('30 check-support feature version')
+ reply = self.parse_reply()
+ self.assertEqual(reply.token, 30)
+ self.assertEqual(reply.command_name, 'feature-support')
+ self.assertIn('support', reply.argument)
+
+ for (request, expected) in feature_tests:
self.write_command(request)
- reply = self.read_reply()
- match = re.match(regex, reply)
- self.assertIsNotNone(match)
+ reply = self.parse_reply()
+ self.assertEqual(reply.command_name, 'feature-support')
+ self.assertIn('support', reply.argument)
+ self.assertEqual(reply.argument['support'], expected)
def test_command_overflow(self):
'Test overflowing the incoming command buffer'
import time
import unittest
+#
+# typing is used for mypy type checking, but isn't required to run,
+# so it's okay if we can't import it.
+#
+try:
+ # pylint: disable=locally-disabled, unused-import
+ from typing import Dict, List
+except ImportError:
+ pass
+
+
+class MtrPacketExecuteError(Exception):
+ "Exception raised when MtrPacketTest can't execute mtr-packet"
+ pass
+
class ReadReplyTimeout(Exception):
'Exception raised by TestProbe.read_reply upon timeout'
pass
+class MtrPacketReplyParseError(Exception):
+ "Exception raised when MtrPacketReply can't parse the reply string"
+
+ pass
+
+
def set_nonblocking(file_descriptor): # type: (int) -> None
'Put a file descriptor into non-blocking mode'
fcntl.fcntl(file_descriptor, fcntl.F_SETFL, flags | os.O_NONBLOCK)
+# pylint: disable=locally-disabled, too-few-public-methods
+class MtrPacketReply(object):
+ 'A parsed reply from mtr-packet'
+
+ def __init__(self, reply): # type: (unicode) -> None
+ self.token = 0 # type: int
+ self.command_name = None # type: unicode
+ self.argument = {} # type: Dict[unicode, unicode]
+
+ self.parse_reply(reply)
+
+ def parse_reply(self, reply): # type (unicode) -> None
+ 'Parses a reply string into members for the instance of this class'
+
+ tokens = reply.split() # type List[unicode]
+
+ try:
+ self.token = int(tokens[0])
+ self.command_name = tokens[1]
+ except IndexError:
+ raise MtrPacketReplyParseError(reply)
+
+ i = 2
+ while i < len(tokens):
+ try:
+ name = tokens[i]
+ value = tokens[i + 1]
+ except IndexError:
+ raise MtrPacketReplyParseError(reply)
+
+ self.argument[name] = value
+ i += 2
+
+
class MtrPacketTest(unittest.TestCase):
'''Base class for tests invoking mtr-packet.
packet_path = os.environ.get('MTR_PACKET', './mtr-packet')
self.reply_buffer = ''
- self.packet_process = subprocess.Popen(
- [packet_path],
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE)
+ try:
+ self.packet_process = subprocess.Popen(
+ [packet_path],
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE)
+ except OSError:
+ raise MtrPacketExecuteError(packet_path)
# Put the mtr-packet process's stdout in non-blocking mode
# so that we can read from it without a timeout when
except OSError:
return
+ def parse_reply(self, timeout=10.0): # type: (float) -> MtrPacketReply
+ '''Read the next reply from mtr-packet and parse it into
+ an MtrPacketReply object.'''
+
+ reply_str = self.read_reply(timeout)
+
+ return MtrPacketReply(reply_str)
+
def read_reply(self, timeout=10.0): # type: (float) -> unicode
'''Read the next reply from mtr-packet.
# pylint: disable=locally-disabled, no-member
if sys.platform != 'cygwin' and os.getuid() > 0:
sys.stderr.write(
- "Warning: Many tests require running as root\n")
+ 'Warning: many tests require running as root\n')
'''Test sending probes and receiving respones.'''
-import re
import socket
import sys
import time
IPV6_TEST_HOST = 'google-public-dns-a.google.com'
-class TestProbeIPv4(mtrpacket.MtrPacketTest):
+def resolve_ipv6_address(hostname): # type: (str) -> str
+ 'Resolve a hostname to an IP version 6 address'
+
+ for addrinfo in socket.getaddrinfo(hostname, 0):
+ # pylint: disable=locally-disabled, unused-variable
+ (family, socktype, proto, name, sockaddr) = addrinfo
+
+ if family == socket.AF_INET6:
+ sockaddr6 = sockaddr # type: tuple
+
+ (address, port, flow, scope) = sockaddr6
+ return address
+
+ raise LookupError(hostname)
+
+
+def check_for_local_ipv6():
+ '''Check for IPv6 support on the test host, to see if we should skip
+ the IPv6 tests'''
+
+ addrinfo = socket.getaddrinfo(IPV6_TEST_HOST, 1, socket.AF_INET6)
+ if len(addrinfo):
+ addr = addrinfo[0][4]
+
+ # Create a UDP socket and check to see it can be connected to
+ # IPV6_TEST_HOST. (Connecting UDP requires no packets sent, just
+ # a route present.)
+ sock = socket.socket(
+ socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
+
+ connect_success = False
+ try:
+ sock.connect(addr)
+ connect_success = True
+ except socket.error:
+ pass
+
+ sock.close()
+
+ if not connect_success:
+ sys.stderr.write(
+ 'This host has no IPv6. Skipping IPv6 tests.\n')
+
+ return connect_success
+
+
+HAVE_IPV6 = check_for_local_ipv6()
+
+
+class TestProbeICMPv4(mtrpacket.MtrPacketTest):
'''Test sending probes using IP version 4'''
def test_probe(self):
'Test sending regular ICMP probes to known addresses'
- reply_regex = r'^14 reply ip-4 8.8.8.8 round-trip-time [0-9]+$'
-
# Probe Google's well-known DNS server and expect a reply
self.write_command('14 send-probe ip-4 8.8.8.8')
- reply = self.read_reply()
- match = re.match(reply_regex, reply)
- self.assertIsNotNone(match)
+ reply = self.parse_reply()
+ self.assertEqual(reply.token, 14)
+ self.assertEqual(reply.command_name, 'reply')
+ self.assertIn('ip-4', reply.argument)
+ self.assertEqual(reply.argument['ip-4'], '8.8.8.8')
+ self.assertIn('round-trip-time', reply.argument)
def test_timeout(self):
'Test timeouts when sending to a non-existant address'
- no_reply_regex = r'^15 no-reply$'
-
#
# Probe a non-existant address, and expect no reply
#
# pylint: disable=locally-disabled, unused-variable
for i in range(16):
self.write_command('15 send-probe ip-4 8.8.254.254 timeout 1')
- reply = self.read_reply()
- match = re.match(no_reply_regex, reply)
- self.assertIsNotNone(match)
+ reply = self.parse_reply()
+ self.assertEqual(reply.token, 15)
+ self.assertEqual(reply.command_name, 'no-reply')
def test_exhaust_probes(self):
'Test exhausting all available probes'
- exhausted_regex = r'^[0-9]+ probes-exhausted$'
-
- match = None
probe_count = 4 * 1024
token = 1024
reply = None
try:
- reply = self.read_reply(0)
+ reply = self.parse_reply(0)
except mtrpacket.ReadReplyTimeout:
pass
if reply:
- match = re.match(exhausted_regex, reply)
- if match:
+ if reply.command_name == 'probes-exhausted':
break
- self.assertIsNotNone(match)
+ self.assertIsNotNone(reply)
+ self.assertEqual(reply.command_name, 'probes-exhausted')
def test_timeout_values(self):
'''Test that timeout values wait the right amount of time
begin = time.time()
self.write_command('19 send-probe ip-4 8.8.254.254 timeout 0')
- self.read_reply()
+ self.parse_reply()
elapsed = time.time() - begin
self.assertLess(elapsed, 0.5)
begin = time.time()
self.write_command('20 send-probe ip-4 8.8.254.254 timeout 1')
- self.read_reply()
+ self.parse_reply()
elapsed = time.time() - begin
- self.assertGreaterEqual(elapsed, 1.0)
+ self.assertGreaterEqual(elapsed, 0.9)
self.assertLess(elapsed, 1.5)
begin = time.time()
self.write_command('21 send-probe ip-4 8.8.254.254 timeout 3')
- self.read_reply()
+ self.parse_reply()
elapsed = time.time() - begin
- self.assertGreaterEqual(elapsed, 3.0)
+ self.assertGreaterEqual(elapsed, 2.9)
self.assertLess(elapsed, 3.5)
def test_ttl_expired(self):
'Test sending a probe which will have its time-to-live expire'
- ttl_expired_regex = \
- r'^16 ttl-expired ip-4 [0-9\.]+ round-trip-time [0-9]+$'
-
# Probe Goolge's DNS server, but give the probe only one hop
# to live.
self.write_command('16 send-probe ip-4 8.8.8.8 ttl 1')
- reply = self.read_reply()
- match = re.match(ttl_expired_regex, reply)
- self.assertIsNotNone(match)
+ reply = self.parse_reply()
+ self.assertEqual(reply.command_name, 'ttl-expired')
+ self.assertIn('ip-4', reply.argument)
+ self.assertIn('round-trip-time', reply.argument)
def test_parallel_probes(self):
'''Test sending multiple probes in parallel
a probe to a distant host immeidately followed by a probe to
the local host.'''
- reply_regex = \
- r'^[0-9]+ reply ip-4 [0-9\.]+ round-trip-time ([0-9]+)$'
-
success_count = 0
loop_count = 32
self.write_command('17 send-probe ip-4 8.8.8.8 timeout 1')
self.write_command('18 send-probe ip-4 127.0.0.1 timeout 1')
- reply = self.read_reply()
- match = re.match(reply_regex, reply)
- if not match:
+ reply = self.parse_reply()
+ if reply.command_name == 'no-reply':
continue
- first_time = int(match.group(1))
- reply = self.read_reply()
- match = re.match(reply_regex, reply)
- if not match:
+ self.assertEqual(reply.command_name, 'reply')
+ self.assertIn('ip-4', reply.argument)
+ self.assertEqual(reply.argument['ip-4'], '127.0.0.1')
+ self.assertIn('round-trip-time', reply.argument)
+ first_time = int(reply.argument['round-trip-time'])
+
+ reply = self.parse_reply()
+ if reply.command_name == 'no-reply':
continue
- second_time = int(match.group(1))
+
+ self.assertEqual(reply.command_name, 'reply')
+ self.assertIn('ip-4', reply.argument)
+ self.assertEqual(reply.argument['ip-4'], '8.8.8.8')
+ self.assertIn('round-trip-time', reply.argument)
+ second_time = int(reply.argument['round-trip-time'])
# Ensure we got a reply from the host with the lowest latency
# first.
success_count += 1
- # We need 95% success to pass. This allows a few probes to be
+ # We need 90% success to pass. This allows a few probes to be
# occasionally dropped by the network without failing the test.
- required_success = int(loop_count * 0.95)
+ required_success = int(loop_count * 0.90)
self.assertGreaterEqual(success_count, required_success)
-def resolve_ipv6_address(hostname): # type: (str) -> str
- 'Resolve a hostname to an IP version 6 address'
-
- for addrinfo in socket.getaddrinfo(hostname, 0):
- # pylint: disable=locally-disabled, unused-variable
- (family, socktype, proto, name, sockaddr) = addrinfo
-
- if family == socket.AF_INET6:
- sockaddr6 = sockaddr # type: tuple
-
- (address, port, flow, scope) = sockaddr6
- return address
-
- raise LookupError(hostname)
-
-
-def check_for_local_ipv6():
- '''Check for IPv6 support on the test host, to see if we should skip
- the IPv6 tests'''
-
- addrinfo = socket.getaddrinfo(IPV6_TEST_HOST, 1, socket.AF_INET6)
- if len(addrinfo):
- addr = addrinfo[0][4]
-
- # Create a UDP socket and check to see it can be connected to
- # IPV6_TEST_HOST. (Connecting UDP requires no packets sent, just
- # a route present.)
- sock = socket.socket(
- socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
-
- connect_success = False
- try:
- sock.connect(addr)
- connect_success = True
- except socket.error:
- pass
-
- sock.close()
-
- if not connect_success:
- sys.stderr.write(
- 'This host has no IPv6. Skipping IPv6 tests.\n')
-
- return connect_success
-
-
-class TestProbeIPv6(mtrpacket.MtrPacketTest):
+class TestProbeICMPv6(mtrpacket.MtrPacketTest):
'''Test sending probes using IP version 6'''
- have_ipv6 = check_for_local_ipv6()
-
def __init__(self, *args):
google_addr = resolve_ipv6_address(IPV6_TEST_HOST)
self.google_addr = google_addr # type: str
- super(TestProbeIPv6, self).__init__(*args)
+ super(TestProbeICMPv6, self).__init__(*args)
- @unittest.skipIf(not have_ipv6, 'No IPv6')
+ @unittest.skipUnless(HAVE_IPV6, 'No IPv6')
def test_probe(self):
"Test a probe to Google's public DNS server"
- reply_regex = r'^51 reply ip-6 [0-9a-f:]+ round-trip-time [0-9]+$'
- loopback_reply_regex = r'^52 reply ip-6 ::1 round-trip-time [0-9]+$'
-
# Probe Google's well-known DNS server and expect a reply
self.write_command('51 send-probe ip-6 ' + self.google_addr)
- reply = self.read_reply()
- match = re.match(reply_regex, reply)
- self.assertIsNotNone(match, reply)
+ reply = self.parse_reply()
+ self.assertEqual(reply.command_name, 'reply')
+ self.assertIn('ip-6', reply.argument)
+ self.assertIn('round-trip-time', reply.argument)
# Probe the loopback, and check the address we get a reply from is
# also the loopback. While implementing IPv6, I had a bug where
# the low bits of the received address got zeroed. This checks for
# that bug.
self.write_command('52 send-probe ip-6 ::1')
- reply = self.read_reply()
- match = re.match(loopback_reply_regex, reply)
- self.assertIsNotNone(match, reply)
+ reply = self.parse_reply()
+ self.assertEqual(reply.command_name, 'reply')
+ self.assertIn('ip-6', reply.argument)
+ self.assertIn('round-trip-time', reply.argument)
+ self.assertEqual(reply.argument['ip-6'], '::1')
- @unittest.skipIf(not have_ipv6, 'No IPv6')
+ @unittest.skipUnless(HAVE_IPV6, 'No IPv6')
def test_ttl_expired(self):
'Test sending a probe which will have its time-to-live expire'
- ttl_expired_regex = \
- r'^53 ttl-expired ip-6 [0-9a-f:]+ round-trip-time [0-9]+$'
-
# Probe Goolge's DNS server, but give the probe only one hop
# to live.
cmd = '53 send-probe ip-6 ' + self.google_addr + ' ttl 1'
self.write_command(cmd)
- reply = self.read_reply()
- match = re.match(ttl_expired_regex, reply)
- self.assertIsNotNone(match, reply)
+ reply = self.parse_reply()
+ self.assertEqual('ttl-expired', reply.command_name)
+ self.assertIn('ip-6', reply.argument)
+ self.assertIn('round-trip-time', reply.argument)
+
+
+class TestProbeUDP(mtrpacket.MtrPacketTest):
+ 'Test transmitting probes using UDP'
+
+ def test_udp_v4(self):
+ 'Test IPv4 UDP probes'
+
+ cmd = '60 send-probe protocol udp ip-4 8.8.8.8 port 164 ttl 1'
+ self.write_command(cmd)
+
+ reply = self.parse_reply()
+ self.assertEqual(reply.command_name, 'ttl-expired')
+
+ cmd = '61 send-probe protocol udp ip-4 127.0.0.1 port 164'
+ self.write_command(cmd)
+
+ reply = self.parse_reply()
+ self.assertEqual(reply.command_name, 'reply')
+ self.assertIn('ip-4', reply.argument)
+ self.assertEqual(reply.argument['ip-4'], '127.0.0.1')
+
+ @unittest.skipUnless(HAVE_IPV6, 'No IPv6')
+ def test_udp_v6(self):
+ 'Test IPv6 UDP probes'
+
+ test_addr = resolve_ipv6_address(IPV6_TEST_HOST)
+
+ cmd = '62 send-probe protocol udp ip-6 ' + test_addr + \
+ ' port 164 ttl 1'
+ self.write_command(cmd)
+
+ reply = self.parse_reply()
+ self.assertEqual(reply.command_name, 'ttl-expired')
+
+ cmd = '63 send-probe protocol udp ip-6 ::1 port 164'
+ self.write_command(cmd)
+ reply = self.parse_reply()
+ self.assertEqual(reply.command_name, 'reply')
+ self.assertIn('ip-6', reply.argument)
+ self.assertEqual(reply.argument['ip-6'], '::1')
if __name__ == '__main__':
mtrpacket.check_running_as_root()