sbin_PROGRAMS = mtr mtr-packet
TESTS = \
test/cmdparse.py \
+ test/param.py \
test/probe.py
TEST_FILES = \
PATHFILES =
CLEANFILES = $(PATHFILES)
EXTRA_DIST += $(PATHFILES:=.in)
-edit_cmd = sed \
- -e 's|@VERSION[@]|$(VERSION)|g'
-%.8: $(srcdir)/%.8.in
- @ rm -f $@ $@.tmp
- $(AM_V_at) $(MKDIR_P) $$(dirname $@)
- $(AM_V_GEN) srcdir=''; \
- test -f ./$@.in || srcdir=$(srcdir)/; \
- $(edit_cmd) $${srcdir}$@.in >$@.tmp
- @ mv $@.tmp $@
+#
+# We would use % pattern matching here, but that is a GNU make
+# extension and doesn't work on FreeBSD.
+#
+mtr-packet.8: $(srcdir)/mtr-packet.8.in
+ $(AM_V_GEN) $(srcdir)/mangen.sh "$(VERSION)" \
+ $(srcdir)/mtr-packet.8.in $@
+
+mtr.8: $(srcdir)/mtr.8.in
+ $(AM_V_GEN) $(srcdir)/mangen.sh "$(VERSION)" \
+ $(srcdir)/mtr.8.in $@
$(PATHFILES): Makefile
else # if CYGWIN
+check_PROGRAMS = mtr-packet-listen
+
mtr_packet_SOURCES += \
packet/command_unix.c packet/command_unix.h \
packet/construct_unix.c packet/construct_unix.h \
packet/probe_unix.c packet/probe_unix.h \
packet/wait_unix.c
+mtr_packet_listen_SOURCES = \
+ test/packet_listen.c
+
endif # if CYGWIN
doing the linking into a terminal window, and add "-lcurses" by hand.
Then it will link. Help on how to catch this in autoconf appreciated.
- On Mac OS X the nameserver8_compat.h needs to be included. I put the
- include inside an "#if 0" section in the file "dns.c". If someone
- knows how to make this automatic using autoconf / the configure script,
- please tell me....
+ Building on MacOS should not require any special steps.
- This should now also work:
- ./configure CFLAGS="-arch i386 -arch x86_64" LIBS="-lresolv" \
- --without-gtk --disable-endian-check --disable-dependency-tracking
+BUILDING FOR WINDOWS
+ Building for Windows requires Cygwin. To obtain Cygwin, see
+ https://cygwin.com/install.html. When installing Cygwin, select
+ the 'lynx' package for installation. lynx is required by apt-cyg.
+ Next, install apt-cyg for easy installation of the remaining
+ components. See https://github.com/transcode-open/apt-cyg.
+
+ Install the packages required for building:
+
+ apt-cyg install automake pkg-config make gcc-core libncurses-devel
+
+ Build as under Unix:
+
+ ./bootstrap.sh && ./configure && make
+
+ Finally, install the built binaries:
+
+ make install
WHERE CAN I GET THE LATEST VERSION OR MORE INFORMATION?
--- /dev/null
+#!/bin/sh
+
+#
+# Generate the man pages.
+#
+# We are just here to substitute the @VERSION@ string with our real version.
+#
+
+if [ $# -lt 3 ]; then
+ echo Usage: mangen.sh VERSION IN OUT
+ exit 1
+fi
+
+sed -e "s|@VERSION[@]|$1|g" $2 >$3
through which the probe will travel before a response is generated by an
intermediate network host.
.HP 7
+.IP
+.B size
+.I PACKET-SIZE
+.HP 14
+.IP
+The size of the packet used to send the probe, in bytes, including the
+Internet Protocol header and transport protocol header.
+.HP 7
+.IP
+.B bitpattern
+.I PATTERN-VALUE
+.HP 14
+.IP
+The packet payload is filled with bytes of the value specified.
+Valid pattern values are in the range 0 through 255.
+.HP 7
+.IP
+.IP
+.B tos
+.I TYPE-OF-SERVICE
+.HP 14
+.IP
+In the case of IPv4, the "type of service" field in the IP header
+is set to this value. In the case of IPv6, the "traffic class"
+field is set.
+.HP 7
+.IP
+.B mark
+.I ROUTING-MARK
+.HP 14
+.IP
+The packet mark value to be used by mark-based routing.
+(Available only on Linux.)
+.HP 7
.TP
.B check-support
Check for support for a particular feature in this version of
Some features which can be checked are
.BR send-probe ,
.BR ip-4 ,
+.BR ip-6 ,
+.BR icmp ,
+.BR udp ,
and
-.BR ip-6 .
+.BR mark .
The feature
.B version
can be checked to retrieve the version of
A probe could not be sent because there are already too many unresolved
probes in flight.
.TP
+.B permission-denied
+The operating system denied permission to send the probe with the
+specified options.
+.TP
.B invalid-argument
The command request contained arguments which are invalid.
.TP
return seq;
}
-/* Attempt to find the host at a particular number of hops away */
-static void net_send_query(struct mtr_ctl *ctl, int index)
+static void net_construct_base_command(
+ struct mtr_ctl *ctl, char *command, int buffer_size, int command_token)
{
- 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;
}
snprintf(
- command, COMMAND_BUFFER_SIZE,
- "%d send-probe %s %s protocol %s ttl %d",
- seq, ip_type, ip_string, protocol, time_to_live);
+ command, buffer_size,
+ "%d send-probe %s %s protocol %s",
+ command_token, ip_type, ip_string, protocol);
+}
+
+static void net_append_command_argument(
+ char *command, int buffer_size, char *name, int value)
+{
+ char argument[COMMAND_BUFFER_SIZE];
+ int remaining_size;
+
+ remaining_size = buffer_size - strlen(command) - 1;
+
+ snprintf(argument, buffer_size, " %s %d", name, value);
+ strncat(command, argument, remaining_size);
+}
+
+/* Attempt to find the host at a particular number of hops away */
+static void net_send_query(struct mtr_ctl *ctl, int index, int packet_size)
+{
+ int seq = new_sequence(ctl, index);
+ int time_to_live = index + 1;
+ char command[COMMAND_BUFFER_SIZE];
+ int remaining_size;
+
+ net_construct_base_command(ctl, command, COMMAND_BUFFER_SIZE, seq);
+
+ net_append_command_argument(
+ command, COMMAND_BUFFER_SIZE, "size", packet_size);
+
+ net_append_command_argument(
+ command, COMMAND_BUFFER_SIZE, "bitpattern", ctl->bitpattern);
+
+ net_append_command_argument(
+ command, COMMAND_BUFFER_SIZE, "tos", ctl->tos);
+
+ net_append_command_argument(
+ command, COMMAND_BUFFER_SIZE, "ttl", time_to_live);
if (ctl->remoteport) {
- remaining_size = COMMAND_BUFFER_SIZE - strlen(command) - 1;
+ net_append_command_argument(
+ command, COMMAND_BUFFER_SIZE, "port", ctl->remoteport);
+ }
- snprintf(argument, COMMAND_BUFFER_SIZE, " port %d", ctl->remoteport);
- strncat(command, argument, remaining_size);
+#ifdef SO_MARK
+ if (ctl->mark) {
+ net_append_command_argument(
+ command, COMMAND_BUFFER_SIZE, "mark", ctl->mark);
}
+#endif
remaining_size = COMMAND_BUFFER_SIZE - strlen(command) - 1;
strncat(command, "\n", remaining_size);
display_close(ctl);
error(EXIT_FAILURE, 0, "mtr-packet reported invalid argument");
}
+
+ if (!strcmp(reply_name, "permission-denied")) {
+ display_close(ctl);
+ error(EXIT_FAILURE, 0, "Permission denied");
+ }
+
+ if (!strcmp(reply_name, "unexpected-error")) {
+ display_close(ctl);
+ error(EXIT_FAILURE, 0, "Unexpected mtr-packet error");
+ }
}
}
}
- net_send_query(ctl, batch_at);
+ net_send_query(ctl, batch_at, abs(packetsize));
for (i=ctl->fstTTL-1;i<batch_at;i++) {
if ( addrcmp( (void *) &(host[i].addr), (void *) &ctl->unspec_addr, ctl->af ) == 0 )
int command_length;
int write_length;
int read_length;
- int parse_result;
/* Query send-probe support */
command_length = strlen(cmd);
/* Parse the query reply */
reply[read_length] = 0;
- parse_result = parse_command(result, reply);
- if (parse_result) {
- errno = parse_result;
+ if (parse_command(result, reply)) {
return -1;
}
static int net_packet_feature_check(struct mtr_ctl *ctl)
{
if (ctl->mtrtype == IPPROTO_ICMP) {
- return net_check_feature(ctl, "icmp");
+ if (net_check_feature(ctl, "icmp")) {
+ return -1;
+ }
} else if (ctl->mtrtype == IPPROTO_UDP) {
- return net_check_feature(ctl, "udp");
+ if (net_check_feature(ctl, "udp")) {
+ return -1;
+ }
} else {
errno = EINVAL;
return -1;
}
+
+#ifdef SO_MARK
+ if (ctl->mark) {
+ if (net_check_feature(ctl, "mark")) {
+ return -1;
+ }
+ }
+#endif
+
+ return 0;
}
/* Tokenize the string using whitespace */
token_count = tokenize_command(tokens, max_tokens, command_string);
if (token_count < 2) {
- return EINVAL;
+ errno = EINVAL;
+ return -1;
}
/* Expect the command token to be a numerical value */
errno = 0;
command->token = strtol(tokens[0], NULL, 10);
if (errno) {
- return EINVAL;
+ errno = EINVAL;
+ return -1;
}
command->command_name = tokens[1];
while (i < token_count) {
/* It's an error if we get a name without a key */
if (i + 1 >= token_count) {
- return EINVAL;
+ errno = EINVAL;
+ return -1;
}
/* It's an error if we get more arguments than we have space for */
if (command->argument_count >= MAX_COMMAND_ARGUMENTS) {
- return EINVAL;
+ errno = EINVAL;
+ return -1;
}
command->argument_name[command->argument_count] = tokens[i];
return check_protocol_support(net_state, IPPROTO_UDP);
}
+#ifdef SO_MARK
+ if (!strcmp(feature, "mark")) {
+ return "ok";
+ }
+#endif
+
return "no";
}
}
}
+ /* The "type of service" field for the IP header */
+ if (!strcmp(name, "tos")) {
+ param->type_of_service = strtol(value, &endstr, 10);
+ if (*endstr != 0) {
+ return false;
+ }
+ }
+
+ /* The Linux packet mark for mark-based routing */
+ if (!strcmp(name, "mark")) {
+ param->routing_mark = strtol(value, &endstr, 10);
+ if (*endstr != 0) {
+ return false;
+ }
+ }
+
+ /* The size of the packet (including headers) */
+ if (!strcmp(name, "size")) {
+ param->packet_size = strtol(value, &endstr, 10);
+ if (*endstr != 0) {
+ return false;
+ }
+ }
+
+ /* The packet's bytes will be filled with this value */
+ if (!strcmp(name, "bitpattern")) {
+ param->bit_pattern = strtol(value, &endstr, 10);
+ if (*endstr != 0) {
+ return false;
+ }
+ }
+
/* Time-to-live values */
if (!strcmp(name, "ttl")) {
param->ttl = strtol(value, &endstr, 10);
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;
for (i = 0; i < command->argument_count; i++) {
}
/*
- Return EPIPE if the command stream has been closed. Otherwise, not much
- to do for Cygwin, since we are using Overlapped I/O to read commands.
+ Return with errno EPIPE if the command stream has been closed.
+ Otherwise, not much to do for Cygwin, since we are using Overlapped I/O
+ to read commands.
*/
int read_commands(
struct command_buffer_t *buffer)
{
if (!buffer->platform.pipe_open) {
- return EPIPE;
+ errno = EPIPE;
+ return -1;
}
return 0;
/* If the command stream has been closed, read will return zero. */
if (read_count == 0)
{
- return EPIPE;
+ errno = EPIPE;
+ return -1;
}
if (read_count > 0) {
#include "construct_unix.h"
#include <errno.h>
+#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
ip = (struct IPHeader *)&packet_buffer[0];
+ memset(ip, 0, sizeof(struct IPHeader));
+
ip->version = 0x45;
+ ip->tos = param->type_of_service;
ip->len = length_byte_swap(net_state, packet_size);
ip->ttl = param->ttl;
ip->protocol = param->protocol;
icmp = (struct ICMPHeader *)&packet_buffer[sizeof(struct IPHeader)];
icmp_size = packet_size - sizeof(struct IPHeader);
+ memset(icmp, 0, sizeof(struct ICMPHeader));
+
icmp->type = ICMP_ECHO;
icmp->id = htons(getpid());
icmp->sequence = htons(param->command_token);
icmp = (struct ICMPHeader *)packet_buffer;
+ memset(icmp, 0, sizeof(struct ICMPHeader));
+
icmp->type = ICMP6_ECHO;
icmp->id = htons(getpid());
icmp->sequence = htons(param->command_token);
- if (setsockopt(
- net_state->platform.icmp6_send_socket, IPPROTO_IPV6,
- IPV6_UNICAST_HOPS, ¶m->ttl, sizeof(int))) {
- return -errno;
- }
-
return 0;
}
udp = (struct UDPHeader *)&packet_buffer[sizeof(struct IPHeader)];
udp_size = packet_size - sizeof(struct IPHeader);
+ memset(udp, 0, sizeof(struct UDPHeader));
+
udp->srcport = htons(param->command_token);
udp->dstport = htons(param->dest_port);
udp->length = htons(udp_size);
udp = (struct UDPHeader *)packet_buffer;
udp_size = packet_size;
+ memset(udp, 0, sizeof(struct UDPHeader));
+
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.
if (setsockopt(
udp_socket, IPPROTO_IPV6,
IPV6_CHECKSUM, &chksum_offset, sizeof(int))) {
-
- return -errno;
+ return -1;
}
return 0;
{
int packet_size;
+ /* Start by determining the full size, including omitted headers */
if (param->ip_version == 6) {
- packet_size = 0;
+ packet_size = sizeof(struct IP6Header);
} else if (param->ip_version == 4) {
packet_size = sizeof(struct IPHeader);
} else {
- return -EINVAL;
+ errno = EINVAL;
+ return -1;
}
if (param->protocol == IPPROTO_ICMP) {
} else if (param->protocol == IPPROTO_UDP) {
packet_size += sizeof(struct UDPHeader);
} else {
- return -EINVAL;
+ errno = EINVAL;
+ return -1;
+ }
+
+ /*
+ If the requested size from send-probe is greater, extend the
+ packet size.
+ */
+ if (param->packet_size > packet_size) {
+ packet_size = param->packet_size;
+ }
+
+ /*
+ Since we don't explicitly construct the IPv6 header, we
+ need to account for it in our transmitted size.
+ */
+ if (param->ip_version == 6) {
+ packet_size -= sizeof(struct IP6Header);
}
return packet_size;
}
+/* Construct a packet for an IPv4 probe */
+int construct_ip4_packet(
+ const struct net_state_t *net_state,
+ char *packet_buffer,
+ int packet_size,
+ const struct sockaddr_storage *src_sockaddr,
+ const struct sockaddr_storage *dest_sockaddr,
+ const struct probe_param_t *param)
+{
+ construct_ip4_header(
+ net_state, packet_buffer, packet_size,
+ src_sockaddr, dest_sockaddr, 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 {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /*
+ The routing mark requires CAP_NET_ADMIN, as opposed to the
+ CAP_NET_RAW which we are sometimes explicitly given.
+ If we don't have CAP_NET_ADMIN, this will fail, so we'll
+ only set the mark if the user has explicitly requested it.
+
+ Unfortunately, this means that once the mark is set, it won't
+ be set on the socket again until a new mark is explicitly
+ specified.
+ */
+#ifdef SO_MARK
+ if (param->routing_mark) {
+ if (setsockopt(
+ net_state->platform.ip4_send_socket,
+ SOL_SOCKET, SO_MARK, ¶m->routing_mark, sizeof(int))) {
+ return -1;
+ }
+ }
+#endif
+
+ return 0;
+}
+
+/* Construct a packet for an IPv6 probe */
+int construct_ip6_packet(
+ const struct net_state_t *net_state,
+ char *packet_buffer,
+ int packet_size,
+ const struct sockaddr_storage *src_sockaddr,
+ const struct sockaddr_storage *dest_sockaddr,
+ const struct probe_param_t *param)
+{
+ int send_socket;
+
+ if (param->protocol == IPPROTO_ICMP) {
+ send_socket = net_state->platform.icmp6_send_socket;
+
+ if (construct_icmp6_packet(
+ net_state, 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, packet_buffer, packet_size, param)) {
+ return -1;
+ }
+ } else {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* The traffic class in IPv6 is analagous to ToS in IPv4 */
+ if (setsockopt(
+ send_socket, IPPROTO_IPV6,
+ IPV6_TCLASS, ¶m->type_of_service, sizeof(int))) {
+ return -1;
+ }
+
+ /* Set the time-to-live */
+ if (setsockopt(
+ send_socket, IPPROTO_IPV6,
+ IPV6_UNICAST_HOPS, ¶m->ttl, sizeof(int))) {
+ return -1;
+ }
+
+#ifdef SO_MARK
+ if (param->routing_mark) {
+ if (setsockopt(
+ send_socket,
+ SOL_SOCKET, SO_MARK, ¶m->routing_mark, sizeof(int))) {
+ return -1;
+ }
+ }
+#endif
+
+ return 0;
+}
+
/* Construct a probe packet based on the probe parameters */
int construct_packet(
const struct net_state_t *net_state,
const struct sockaddr_storage *dest_sockaddr,
const struct probe_param_t *param)
{
- int err;
int packet_size;
struct sockaddr_storage src_sockaddr;
packet_size = compute_packet_size(net_state, param);
if (packet_size < 0) {
- return packet_size;
+ return -1;
}
if (packet_buffer_size < packet_size) {
- return -EINVAL;
+ errno = EINVAL;
+ return -1;
}
- err = find_source_addr(&src_sockaddr, dest_sockaddr);
- if (err) {
- return err;
+ if (find_source_addr(&src_sockaddr, dest_sockaddr)) {
+ return -1;
}
- memset(packet_buffer, 0, packet_size);
+ memset(packet_buffer, param->bit_pattern, packet_size);
- err = 0;
if (param->ip_version == 6) {
- 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;
+ if (construct_ip6_packet(
+ net_state, packet_buffer, packet_size,
+ &src_sockaddr, dest_sockaddr, param)) {
+ return -1;
}
- } else {
- construct_ip4_header(
- net_state, packet_buffer, packet_size,
- &src_sockaddr, dest_sockaddr, 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;
+ } else if (param->ip_version == 4) {
+ if (construct_ip4_packet(
+ net_state, packet_buffer, packet_size,
+ &src_sockaddr, dest_sockaddr, param)) {
+ return -1;
}
- }
-
- if (err) {
- return err;
+ } else {
+ errno = EINVAL;
+ return -1;
}
return packet_size;
char **argv)
{
bool command_pipe_open;
- int err;
struct command_buffer_t command_buffer;
struct net_state_t net_state;
receive_replies(&net_state);
if (command_pipe_open) {
- err = read_commands(&command_buffer);
- if (err == EPIPE)
- {
- command_pipe_open = false;
+ if (read_commands(&command_buffer)) {
+ if (errno == EPIPE)
+ {
+ command_pipe_open = false;
+ }
}
}
struct sockaddr_in6 *sockaddr6;
if (param->address == NULL) {
- return EINVAL;
+ errno = EINVAL;
+ return -1;
}
if (param->ip_version == 6) {
sockaddr6 = (struct sockaddr_in6 *)dest_sockaddr;
if (inet_pton(AF_INET6, param->address, &dest_addr6) != 1) {
- return EINVAL;
+ errno = EINVAL;
+ return -1;
}
sockaddr6->sin6_family = AF_INET6;
sockaddr4 = (struct sockaddr_in *)dest_sockaddr;
if (inet_pton(AF_INET, param->address, &dest_addr4) != 1) {
- return EINVAL;
+ errno = EINVAL;
+ return -1;
}
sockaddr4->sin_family = AF_INET;
sockaddr4->sin_port = 0;
sockaddr4->sin_addr = dest_addr4;
} else {
- return EINVAL;
+ errno = EINVAL;
+ return -1;
}
return 0;
void free_probe(
struct probe_t *probe)
{
+ platform_free_probe(probe);
+
probe->used = false;
}
sock = socket(destaddr->ss_family, SOCK_DGRAM, IPPROTO_UDP);
if (sock == -1) {
- return -errno;
+ return -1;
}
if (connect(sock, (struct sockaddr *)&dest_with_port, len)) {
close(sock);
- return -errno;
+ return -1;
}
if (getsockname(sock, (struct sockaddr *)srcaddr, &len)) {
close(sock);
- return -errno;
+ return -1;
}
close(sock);
#define MAX_PROBES 1024
+/* Use the "jumbo" frame size as the max packet size */
+#define PACKET_BUFFER_SIZE 9000
+
/* Parameters for sending a new probe */
struct probe_param_t
{
/* The destination port for non-ICMP probes */
int dest_port;
+ /* The "type of service" field in the IP header */
+ int type_of_service;
+
+ /* The packet "mark" used for mark-based routing on Linux */
+ int routing_mark;
+
/* Time to live for the transmited probe */
int ttl;
+ /* The packet size (in bytes) including protocol headers */
+ int packet_size;
+
+ /* The value with which to fill the bytes of the packet. */
+ int bit_pattern;
+
/* The number of seconds to wait before assuming the probe was lost */
int timeout;
};
struct net_state_t *net_state,
int token);
+void platform_free_probe(
+ struct probe_t *probe);
+
void free_probe(
struct probe_t *probe);
return false;
}
+/* Free the reply buffer when the probe is freed */
+void platform_free_probe(
+ struct probe_t *probe)
+{
+ if (probe->platform.reply4) {
+ free(probe->platform.reply4);
+ probe->platform.reply4 = NULL;
+ }
+}
+
/*
The overlapped I/O style completion routine to be called by
Windows during an altertable wait when an ICMP probe has
ICMPV6_ECHO_REPLY *reply6;
if (probe->platform.ip_version == 6) {
- reply6 = &probe->platform.reply6;
+ reply6 = probe->platform.reply6;
reply_count = Icmp6ParseReplies(reply6, sizeof(ICMPV6_ECHO_REPLY));
if (reply_count > 0) {
remote_addr6->sin6_scope_id = 0;
}
} else {
- reply4 = &probe->platform.reply4;
+ reply4 = probe->platform.reply4;
reply_count = IcmpParseReplies(reply4, sizeof(ICMP_ECHO_REPLY));
if (reply_count > 0) {
}
}
-/* Send a new probe using ICMP.DLL's send echo mechanism */
-void send_probe(
+/* Use ICMP.DLL's send echo support to send a probe */
+static
+void icmp_send_probe(
struct net_state_t *net_state,
- const struct probe_param_t *param)
+ struct probe_t *probe,
+ const struct probe_param_t *param,
+ struct sockaddr_storage *src_sockaddr,
+ struct sockaddr_storage *dest_sockaddr,
+ char *payload,
+ int payload_size)
{
IP_OPTION_INFORMATION option;
- DWORD send_result;
DWORD timeout;
- struct probe_t *probe;
- struct sockaddr_storage dest_sockaddr;
- struct sockaddr_storage src_sockaddr;
+ DWORD send_result;
+ int reply_size;
struct sockaddr_in *dest_sockaddr4;
struct sockaddr_in6 *src_sockaddr6;
struct sockaddr_in6 *dest_sockaddr6;
- if (decode_dest_addr(param, &dest_sockaddr)) {
- printf("%d invalid-argument\n", param->command_token);
- return;
- }
-
if (param->timeout > 0) {
timeout = 1000 * param->timeout;
} else {
timeout = 1;
}
- probe = alloc_probe(net_state, param->command_token);
- if (probe == NULL) {
- printf("%d probes-exhausted\n", param->command_token);
- return;
+ memset(&option, 0, sizeof(IP_OPTION_INFORMATION32));
+ option.Ttl = param->ttl;
+
+ if (param->ip_version == 6) {
+ reply_size = sizeof(ICMPV6_ECHO_REPLY) + payload_size;
+ } else {
+ reply_size = sizeof(ICMP_ECHO_REPLY32) + payload_size;
}
- if (find_source_addr(&src_sockaddr, &dest_sockaddr)) {
- fprintf(stderr, "error finding source address\n");
+ probe->platform.reply4 = malloc(reply_size);
+ if (probe->platform.reply4 == NULL) {
+ perror("failure to allocate reply buffer");
exit(1);
}
- probe->platform.ip_version = param->ip_version;
-
- memset(&option, 0, sizeof(IP_OPTION_INFORMATION32));
- option.Ttl = param->ttl;
-
if (param->ip_version == 6) {
- src_sockaddr6 = (struct sockaddr_in6 *)&src_sockaddr;
- dest_sockaddr6 = (struct sockaddr_in6 *)&dest_sockaddr;
+ src_sockaddr6 = (struct sockaddr_in6 *)src_sockaddr;
+ dest_sockaddr6 = (struct sockaddr_in6 *)dest_sockaddr;
send_result = Icmp6SendEcho2(
net_state->platform.icmp6, NULL,
(FARPROC)on_icmp_reply, probe,
- src_sockaddr6, dest_sockaddr6, NULL, 0, &option,
- &probe->platform.reply6, sizeof(ICMPV6_ECHO_REPLY), timeout);
+ src_sockaddr6, dest_sockaddr6, payload, payload_size, &option,
+ probe->platform.reply6, reply_size, timeout);
} else {
- dest_sockaddr4 = (struct sockaddr_in *)&dest_sockaddr;
+ dest_sockaddr4 = (struct sockaddr_in *)dest_sockaddr;
+
send_result = IcmpSendEcho2(
net_state->platform.icmp4, NULL,
(FARPROC)on_icmp_reply, probe,
- dest_sockaddr4->sin_addr.s_addr, NULL, 0, &option,
- &probe->platform.reply4, sizeof(ICMP_ECHO_REPLY), timeout);
+ dest_sockaddr4->sin_addr.s_addr, payload, payload_size, &option,
+ probe->platform.reply4, reply_size, timeout);
}
if (send_result == 0) {
}
}
+/* Fill the payload of the packet as specified by the probe parameters */
+static
+int fill_payload(
+ const struct probe_param_t *param,
+ char *payload,
+ int payload_buffer_size)
+{
+ int ip_icmp_size;
+ int payload_size;
+
+ if (param->ip_version == 6) {
+ ip_icmp_size = sizeof(struct IP6Header) + sizeof(struct ICMPHeader);
+ } else if (param->ip_version == 4) {
+ ip_icmp_size = sizeof(struct IPHeader) + sizeof(struct ICMPHeader);
+ } else {
+ errno = EINVAL;
+ return -1;
+ }
+
+ payload_size = param->packet_size - ip_icmp_size;
+ if (payload_size < 0) {
+ payload_size = 0;
+ }
+
+ if (payload_size > payload_buffer_size) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ memset(payload, param->bit_pattern, payload_size);
+
+ return payload_size;
+}
+
+/* Decode the probe parameters and send a probe */
+void send_probe(
+ struct net_state_t *net_state,
+ const struct probe_param_t *param)
+{
+ struct probe_t *probe;
+ struct sockaddr_storage dest_sockaddr;
+ struct sockaddr_storage src_sockaddr;
+ char payload[PACKET_BUFFER_SIZE];
+ int payload_size;
+
+ if (decode_dest_addr(param, &dest_sockaddr)) {
+ printf("%d invalid-argument\n", param->command_token);
+ return;
+ }
+
+ probe = alloc_probe(net_state, param->command_token);
+ if (probe == NULL) {
+ printf("%d probes-exhausted\n", param->command_token);
+ return;
+ }
+
+ if (find_source_addr(&src_sockaddr, &dest_sockaddr)) {
+ fprintf(stderr, "error finding source address\n");
+ exit(1);
+ }
+
+ probe->platform.ip_version = param->ip_version;
+
+ payload_size = fill_payload(param, payload, PACKET_BUFFER_SIZE);
+ if (payload_size < 0) {
+ perror("Error construction packet");
+ exit(1);
+ }
+
+ icmp_send_probe(
+ net_state, probe, param,
+ &src_sockaddr, &dest_sockaddr, payload, payload_size);
+}
+
/*
On Windows, an implementation of receive_replies is unnecessary, because,
unlike Unix, replies are completed using Overlapped I/O during an
int ip_version;
union {
- ICMP_ECHO_REPLY32 reply4;
- ICMPV6_ECHO_REPLY reply6;
+ ICMP_ECHO_REPLY32 *reply4;
+ ICMPV6_ECHO_REPLY *reply6;
};
};
#include "deconstruct_unix.h"
#include "timeval.h"
-/* Use the "jumbo" frame size as the max packet size */
-#define PACKET_BUFFER_SIZE 9000
-
/* A wrapper around sendto for mixed IPv4 and IPv6 sending */
static
int send_packet(
packet_size = construct_packet(
net_state, packet, PACKET_BUFFER_SIZE, &dest_sockaddr, ¶m);
if (packet_size < 0) {
- errno = -packet_size;
perror("Unable to send to localhost");
exit(1);
}
packet_size = construct_packet(
net_state, packet, PACKET_BUFFER_SIZE, &dest_sockaddr, ¶m);
if (packet_size < 0) {
- errno = -packet_size;
perror("Unable to send to localhost");
exit(1);
}
return false;
}
+/* Report an error during send_probe based on the errno value */
+static
+void report_packet_error(
+ const struct probe_param_t *param)
+{
+ if (errno == EINVAL) {
+ printf("%d invalid-argument\n", param->command_token);
+ } else 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 == EPERM) {
+ printf("%d permission-denied\n", param->command_token);
+ } else {
+ printf("%d unexpected-error errno %d\n", param->command_token, errno);
+ }
+}
+
/* Craft a custom ICMP packet for a network probe. */
void send_probe(
struct net_state_t *net_state,
packet_size = construct_packet(
net_state, packet, PACKET_BUFFER_SIZE, &dest_sockaddr, param);
if (packet_size < 0) {
- if (packet_size == -EINVAL) {
- printf("%d invalid-argument\n", param->command_token);
- } else if (packet_size == -ENETDOWN) {
- printf("%d network-down\n", param->command_token);
- } else if (packet_size == -ENETUNREACH) {
- printf("%d no-route\n", param->command_token);
- } else {
- errno = -packet_size;
- perror("Failure constructing packet");
- exit(1);
- }
+ report_packet_error(param);
return;
}
if (send_packet(
net_state, param, packet, packet_size, &dest_sockaddr) == -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);
- }
-
+ report_packet_error(param);
free_probe(probe);
return;
}
probe->platform.timeout_time.tv_sec += param->timeout;
}
+/* Nothing is needed for freeing Unix probes */
+void platform_free_probe(
+ struct probe_t *probe)
+{
+}
+
/*
Read all available packets through our receiving raw socket, and
handle any responses to probes we have preivously sent.
*/
+static
void receive_replies_from_socket(
struct net_state_t *net_state,
int socket,
import fcntl
import os
import select
+import socket
import subprocess
import sys
import time
pass
+IPV6_TEST_HOST = 'google-public-dns-a.google.com'
+
+
class MtrPacketExecuteError(Exception):
"Exception raised when MtrPacketTest can't execute mtr-packet"
pass
pass
+class PacketListenError(Exception):
+ 'Exception raised when we have unexpected results from mtr-packet-listen'
+
+ 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)
+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()
+
+
# pylint: disable=locally-disabled, too-few-public-methods
class MtrPacketReply(object):
'A parsed reply from mtr-packet'
i += 2
+class PacketListen(object):
+ 'A test process which listens for a single packet'
+
+ def __init__(self, *args):
+ self.process_args = list(args) # type: List[unicode]
+ self.listen_process = None # type: subprocess.Popen
+ self.token = None # type: unicode
+ self.attrib = None # type: Dict[unicode, unicode]
+
+ def __enter__(self):
+ try:
+ self.listen_process = subprocess.Popen(
+ ['./mtr-packet-listen'] + self.process_args,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE)
+ except OSError:
+ raise PacketListenError('unable to launch mtr-packet-listen')
+
+ status = self.listen_process.stdout.readline().decode('utf-8')
+ if status != 'status listening\n':
+ raise PacketListenError('unexpected status')
+
+ self.token = str(self.listen_process.pid)
+
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self.wait_for_exit()
+
+ self.attrib = {}
+ for line in self.listen_process.stdout.readlines():
+ tokens = line.decode('utf-8').split()
+
+ if len(tokens) >= 2:
+ name = tokens[0]
+ value = tokens[1]
+
+ self.attrib[name] = value
+
+ self.listen_process.stdin.close()
+ self.listen_process.stdout.close()
+
+ def wait_for_exit(self):
+ '''Poll the subprocess for up to ten seconds, until it exits.
+
+ We need to wait for its exit to ensure we are able to read its
+ output.'''
+
+ wait_time = 10
+ wait_step = 0.1
+
+ steps = int(wait_time / wait_step)
+
+ exit_value = None
+
+ # pylint: disable=locally-disabled, unused-variable
+ for i in range(steps):
+ exit_value = self.listen_process.poll()
+ if exit_value is not None:
+ break
+
+ time.sleep(wait_step)
+
+ if exit_value is None:
+ raise PacketListenError('mtr-packet-listen timeout')
+
+ if exit_value != 0:
+ raise PacketListenError('mtr-packet-listen unexpected error')
+
+
class MtrPacketTest(unittest.TestCase):
'''Base class for tests invoking mtr-packet.
--- /dev/null
+/*
+ mtr -- a network diagnostic tool
+ Copyright (C) 2016 Matt Kimball
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <netinet/in.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include "packet/protocols.h"
+
+#define MAX_PACKET_SIZE 9000
+
+/*
+ Check to see if the packet we've recieved is intended for this test
+ process. We expected the ICMP sequence number to be equal to our
+ process ID.
+*/
+bool is_packet_for_us4(
+ char *packet,
+ int packet_size)
+{
+ int ip_icmp_size = sizeof(struct IPHeader) + sizeof(struct ICMPHeader);
+ int expected_sequence;
+ struct IPHeader *ip;
+ struct ICMPHeader *icmp;
+
+ if (packet_size < ip_icmp_size) {
+ return false;
+ }
+
+ ip = (struct IPHeader *)packet;
+ icmp = (struct ICMPHeader *)(ip + 1);
+
+ expected_sequence = htons(getpid());
+ if (icmp->sequence == expected_sequence) {
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ Check to see if the ICMPv6 packet is for us.
+ Unlike ICMPv4 packets, ICMPv6 packets don't include the IP header.
+*/
+bool is_packet_for_us6(
+ char *packet,
+ int packet_size)
+{
+ int expected_sequence;
+ struct ICMPHeader *icmp;
+
+ if (packet_size < sizeof(struct ICMPHeader)) {
+ return false;
+ }
+
+ icmp = (struct ICMPHeader *)packet;
+
+ expected_sequence = htons(getpid());
+ if (icmp->sequence == expected_sequence) {
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ Check that all the bytes in the body of the packet have the same value.
+ If so, return that value. If not, return -1.
+*/
+int get_packet_pattern(
+ unsigned char *packet,
+ int packet_size)
+{
+ int fill_value;
+ int i;
+
+ if (packet_size <= 0) {
+ return -1;
+ }
+
+ fill_value = packet[0];
+ for (i = 1; i < packet_size; i++) {
+ if (packet[i] != fill_value) {
+ return -1;
+ }
+ }
+
+ return fill_value;
+}
+
+/* Print information about the ICMPv4 packet we received */
+void dump_packet_info4(
+ char *packet,
+ int packet_size)
+{
+ int ip_icmp_size = sizeof(struct IPHeader) + sizeof(struct ICMPHeader);
+ int pattern;
+ struct IPHeader *ip;
+ struct ICMPHeader *icmp;
+ unsigned char *body;
+ int body_size;
+
+ ip = (struct IPHeader *)packet;
+ icmp = (struct ICMPHeader *)(ip + 1);
+ body = (unsigned char *)(icmp + 1);
+ body_size = packet_size - ip_icmp_size;
+
+ printf("size %d\n", packet_size);
+ printf("tos %d\n", ip->tos);
+
+ pattern = get_packet_pattern(body, body_size);
+ if (pattern < 0) {
+ printf("bitpattern none\n");
+ } else {
+ printf("bitpattern %d\n", pattern);
+ }
+}
+
+/* Print information about an ICMPv6 packet */
+void dump_packet_info6(
+ char *packet,
+ int packet_size)
+{
+ int pattern;
+ struct ICMPHeader *icmp;
+ unsigned char *body;
+ int body_size;
+ int total_size;
+
+ icmp = (struct ICMPHeader *)packet;
+ body = (unsigned char *)(icmp + 1);
+ body_size = packet_size - sizeof(struct ICMPHeader);
+
+ total_size = packet_size + sizeof(struct IP6Header);
+ printf("size %d\n", total_size);
+
+ pattern = get_packet_pattern(body, body_size);
+ if (pattern < 0) {
+ printf("bitpattern none\n");
+ } else {
+ printf("bitpattern %d\n", pattern);
+ }
+}
+
+/* Receive ICMP packets until we get one intended for this test process */
+void loop_on_receive(
+ int icmp_socket,
+ int ip_version)
+{
+ int packet_size;
+ char packet[MAX_PACKET_SIZE];
+
+ while (true) {
+ packet_size = recv(icmp_socket, packet, MAX_PACKET_SIZE, 0);
+ if (packet_size < -1) {
+ perror("Failure during receive");
+ exit(EXIT_FAILURE);
+ }
+
+ if (ip_version == 6) {
+ if (is_packet_for_us6(packet, packet_size)) {
+ dump_packet_info6(packet, packet_size);
+ return;
+ }
+ } else {
+ if (is_packet_for_us4(packet, packet_size)) {
+ dump_packet_info4(packet, packet_size);
+ return;
+ }
+ }
+ }
+}
+
+/* Parse the commandline arguments */
+void parse_cmdline(
+ int argc,
+ char **argv,
+ int *ip_version)
+{
+ int opt;
+
+ *ip_version = 4;
+
+ while ((opt = getopt(argc, argv, "46")) != -1) {
+ if (opt == '4') {
+ *ip_version = 4;
+ }
+
+ if (opt == '6') {
+ *ip_version = 6;
+ }
+ }
+}
+
+/*
+ A helper for mtr-packet testing which waits for an ICMP packet
+ intended for this test process, and then prints information about
+ it.
+*/
+int main(
+ int argc,
+ char **argv)
+{
+ int icmp_socket;
+ int ip_version;
+
+ parse_cmdline(argc, argv, &ip_version);
+
+ if (ip_version == 6) {
+ icmp_socket = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
+ } else {
+ icmp_socket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
+ }
+ if (icmp_socket < 0) {
+ perror("Failure opening listening socket");
+ exit(EXIT_FAILURE);
+ }
+
+ printf("status listening\n");
+ fflush(stdout);
+
+ loop_on_receive(icmp_socket, ip_version);
+
+ return EXIT_SUCCESS;
+}
--- /dev/null
+#!/usr/bin/env python
+#
+# mtr -- a network diagnostic tool
+# Copyright (C) 2016 Matt Kimball
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+
+'''Test probe customization parameters'''
+
+import sys
+import unittest
+
+import mtrpacket
+
+
+@unittest.skipIf(sys.platform == 'cygwin', 'No Cygwin test')
+class TestParameters(mtrpacket.MtrPacketTest):
+ 'Use parameter arguments to mtr-packet and examine the resulting packet'
+
+ def test_size(self):
+ 'Test probes sent with an explicit packet size'
+
+ with mtrpacket.PacketListen('-4') as listen:
+ cmd = listen.token + ' send-probe ip-4 127.0.0.1 size 512'
+
+ self.write_command(cmd)
+
+ self.assertEqual(listen.attrib['size'], '512')
+
+ def test_pattern(self):
+ 'Test probes are filled with the requested bit pattern'
+
+ with mtrpacket.PacketListen('-4') as listen:
+ cmd = listen.token + ' send-probe ip-4 127.0.0.1 bitpattern 44'
+
+ self.write_command(cmd)
+
+ self.assertEqual(listen.attrib['bitpattern'], '44')
+
+ def test_tos(self):
+ 'Test setting the TOS field'
+
+ with mtrpacket.PacketListen('-4') as listen:
+ cmd = listen.token + ' send-probe ip-4 127.0.0.1 tos 62'
+
+ self.write_command(cmd)
+
+ self.assertEqual(listen.attrib['tos'], '62')
+
+
+@unittest.skipIf(sys.platform == 'cygwin', 'No Cygwin test')
+class TestIPv6Parameters(mtrpacket.MtrPacketTest):
+ 'Test packet paramter customization for IPv6'
+
+ @unittest.skipUnless(mtrpacket.HAVE_IPV6, 'No IPv6')
+ def test_param(self):
+ 'Test a variety of packet parameters'
+
+ with mtrpacket.PacketListen('-6') as listen:
+ param = 'size 256 bitpattern 51 tos 77'
+ cmd = listen.token + ' send-probe ip-6 ::1 ' + param
+
+ self.write_command(cmd)
+
+ self.assertEqual(listen.attrib['size'], '256')
+ self.assertEqual(listen.attrib['bitpattern'], '51')
+
+
+if __name__ == '__main__':
+ mtrpacket.check_running_as_root()
+ unittest.main()
'''Test sending probes and receiving respones.'''
import socket
-import sys
import time
import unittest
import mtrpacket
-IPV6_TEST_HOST = 'google-public-dns-a.google.com'
-
-
def resolve_ipv6_address(hostname): # type: (str) -> str
'Resolve a hostname to an IP version 6 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'''
'''Test sending probes using IP version 6'''
def __init__(self, *args):
- google_addr = resolve_ipv6_address(IPV6_TEST_HOST)
+ google_addr = resolve_ipv6_address(mtrpacket.IPV6_TEST_HOST)
self.google_addr = google_addr # type: str
super(TestProbeICMPv6, self).__init__(*args)
- @unittest.skipUnless(HAVE_IPV6, 'No IPv6')
+ @unittest.skipUnless(mtrpacket.HAVE_IPV6, 'No IPv6')
def test_probe(self):
"Test a probe to Google's public DNS server"
self.assertIn('round-trip-time', reply.argument)
self.assertEqual(reply.argument['ip-6'], '::1')
- @unittest.skipUnless(HAVE_IPV6, 'No IPv6')
+ @unittest.skipUnless(mtrpacket.HAVE_IPV6, 'No IPv6')
def test_ttl_expired(self):
'Test sending a probe which will have its time-to-live expire'
self.assertIn('ip-4', reply.argument)
self.assertEqual(reply.argument['ip-4'], '127.0.0.1')
- @unittest.skipUnless(HAVE_IPV6, 'No IPv6')
+ @unittest.skipUnless(mtrpacket.HAVE_IPV6, 'No IPv6')
def test_udp_v6(self):
'Test IPv6 UDP probes'
- test_addr = resolve_ipv6_address(IPV6_TEST_HOST)
+ test_addr = resolve_ipv6_address(mtrpacket.IPV6_TEST_HOST)
cmd = '62 send-probe protocol udp ip-6 ' + test_addr + \
' port 164 ttl 1'