From: Matt Kimball Date: Wed, 28 Dec 2016 01:09:49 +0000 (-0800) Subject: mtr-packet: Fall back to IPv4 only support if IPv6 sockets fail to open X-Git-Tag: v0.88~13^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=refs%2Fpull%2F174%2Fhead;p=thirdparty%2Fmtr.git mtr-packet: Fall back to IPv4 only support if IPv6 sockets fail to open If we fail to open any IPv6 sockets, rather than aborting with an unrecoverable error, fall back to IPv4 only support. Socket creation might fail, for example, when Linux is booted with the kernel command-line "ipv6.disable=1". In the case where opening IPv6 sockets fail, 'check-support feature ip-6' will indicate there is no support for sending IPv6 probes. Stricter error reporting revealed that test for protocols other than ICMP were running on Cygwin. Modified the tests such that they won't run if the protocol isn't supported. --- diff --git a/packet/command.c b/packet/command.c index 39dedff..54c3a48 100644 --- a/packet/command.c +++ b/packet/command.c @@ -67,6 +67,19 @@ const char *check_protocol_support( } } +/* Return a feature support string for an IP protocol version */ +static +const char *check_ip_version_support( + struct net_state_t *net_state, + int ip_version) +{ + if (is_ip_version_supported(net_state, ip_version)) { + return "ok"; + } else { + return "no"; + } +} + /* Given a feature name, return a string for the check-support reply */ static const char *check_support( @@ -78,11 +91,11 @@ const char *check_support( } if (!strcmp(feature, "ip-4")) { - return "ok"; + return check_ip_version_support(net_state, 4); } if (!strcmp(feature, "ip-6")) { - return "ok"; + return check_ip_version_support(net_state, 6); } if (!strcmp(feature, "send-probe")) { @@ -262,6 +275,36 @@ bool decode_probe_argument( return true; } +/* + Check the probe parameters against currently supported features + and report and error if there is a problem. + + Return true if everything is okay, false otherwise. +*/ +static +bool validate_probe_parameters( + struct net_state_t *net_state, + struct probe_param_t *param) +{ + if (!is_ip_version_supported(net_state, param->ip_version)) { + printf( + "%d invalid-argument reason ip-version-not-supported\n", + param->command_token); + + return false; + } + + if (!is_protocol_supported(net_state, param->protocol)) { + printf( + "%d invalid-argument reason protocol-not-supported\n", + param->command_token); + + return false; + } + + return true; +} + /* Handle "send-probe" commands */ static void send_probe_command( @@ -291,6 +334,10 @@ void send_probe_command( } } + if (!validate_probe_parameters(net_state, ¶m)) { + return; + } + /* Send the probe using a platform specific mechanism */ send_probe(net_state, ¶m); } diff --git a/packet/probe.h b/packet/probe.h index d83e246..4a343fe 100644 --- a/packet/probe.h +++ b/packet/probe.h @@ -133,6 +133,10 @@ void init_net_state_privileged( void init_net_state( struct net_state_t *net_state); +bool is_ip_version_supported( + struct net_state_t *net_state, + int ip_version); + bool is_protocol_supported( struct net_state_t *net_state, int protocol); diff --git a/packet/probe_cygwin.c b/packet/probe_cygwin.c index 98159f7..32127fd 100644 --- a/packet/probe_cygwin.c +++ b/packet/probe_cygwin.c @@ -36,16 +36,31 @@ void init_net_state( memset(net_state, 0, sizeof(struct net_state_t)); net_state->platform.icmp4 = IcmpCreateFile(); - if (net_state->platform.icmp4 == INVALID_HANDLE_VALUE) { - fprintf(stderr, "Failure opening ICMPv4 %d\n", GetLastError()); + net_state->platform.icmp6 = Icmp6CreateFile(); + + if (net_state->platform.icmp4 == INVALID_HANDLE_VALUE + && net_state->platform.icmp6 == INVALID_HANDLE_VALUE) + { + fprintf(stderr, "Failure opening ICMP %d\n", GetLastError()); exit(EXIT_FAILURE); } +} - net_state->platform.icmp6 = Icmp6CreateFile(); - if (net_state->platform.icmp6 == INVALID_HANDLE_VALUE) { - fprintf(stderr, "Failure opening ICMPv6 %d\n", GetLastError()); - exit(EXIT_FAILURE); +/* + If we succeeded at opening the ICMP file handle, we can + assume that IP protocol version is supported. +*/ +bool is_ip_version_supported( + struct net_state_t *net_state, + int ip_version) +{ + if (ip_version == 4) { + return (net_state->platform.icmp4 != INVALID_HANDLE_VALUE); + } else if (ip_version == 6) { + return (net_state->platform.icmp6 != INVALID_HANDLE_VALUE); } + + return false; } /* On Windows, we only support ICMP probes */ diff --git a/packet/probe_unix.c b/packet/probe_unix.c index 44fd416..3f52575 100644 --- a/packet/probe_unix.c +++ b/packet/probe_unix.c @@ -182,7 +182,7 @@ void set_socket_nonblocking( /* Open the raw sockets for sending/receiving IPv4 packets */ static -void open_ip4_sockets( +int open_ip4_sockets( struct net_state_t *net_state) { int send_socket; @@ -191,8 +191,7 @@ void open_ip4_sockets( send_socket = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (send_socket == -1) { - perror("Failure opening IPv4 send socket"); - exit(EXIT_FAILURE); + return -1; } /* @@ -202,8 +201,8 @@ void open_ip4_sockets( if (setsockopt( send_socket, IPPROTO_IP, IP_HDRINCL, &trueopt, sizeof(int))) { - perror("Failure to set IP_HDRINCL"); - exit(EXIT_FAILURE); + close(send_socket); + return -1; } /* @@ -212,17 +211,20 @@ void open_ip4_sockets( */ recv_socket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (recv_socket == -1) { - perror("Failure opening IPv4 receive socket"); - exit(EXIT_FAILURE); + close(send_socket); + return -1; } + net_state->platform.ip4_present = true; net_state->platform.ip4_send_socket = send_socket; net_state->platform.ip4_recv_socket = recv_socket; + + return 0; } /* Open the raw sockets for sending/receiving IPv6 packets */ static -void open_ip6_sockets( +int open_ip6_sockets( struct net_state_t *net_state) { int send_socket_icmp; @@ -231,27 +233,30 @@ void open_ip6_sockets( send_socket_icmp = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); if (send_socket_icmp == -1) { - perror("Failure opening ICMPv6 send socket"); - exit(EXIT_FAILURE); + return -1; } send_socket_udp = socket(AF_INET6, SOCK_RAW, IPPROTO_UDP); if (send_socket_udp == -1) { - perror("Failure opening UDPv6 send socket"); - exit(EXIT_FAILURE); + close(send_socket_icmp); + + return -1; } recv_socket = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); if (recv_socket == -1) { - perror("Failure opening IPv6 receive socket"); - exit(EXIT_FAILURE); - } + close(send_socket_icmp); + close(send_socket_udp); - set_socket_nonblocking(recv_socket); + return -1; + } + net_state->platform.ip6_present = true; 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; + + return 0; } /* @@ -262,12 +267,35 @@ void open_ip6_sockets( void init_net_state_privileged( struct net_state_t *net_state) { + int ip4_err = 0; + int ip6_err = 0; + memset(net_state, 0, sizeof(struct net_state_t)); net_state->platform.next_sequence = MIN_PORT; - open_ip4_sockets(net_state); - open_ip6_sockets(net_state); + if (open_ip4_sockets(net_state)) { + ip4_err = errno; + } + if (open_ip6_sockets(net_state)) { + ip6_err = errno; + } + + /* + If we couldn't open either IPv4 or IPv6 sockets, we can't do + much, so print errors and exit. + */ + if (!net_state->platform.ip4_present + && !net_state->platform.ip6_present) { + + errno = ip4_err; + perror("Failure to open IPv4 sockets"); + + errno = ip6_err; + perror("Failure to open IPv6 sockets"); + + exit(EXIT_FAILURE); + } } /* @@ -280,10 +308,30 @@ void init_net_state( set_socket_nonblocking(net_state->platform.ip4_recv_socket); set_socket_nonblocking(net_state->platform.ip6_recv_socket); - check_length_order(net_state); + if (net_state->platform.ip4_present) { + check_length_order(net_state); + } + check_sctp_support(net_state); } +/* + Returns true if we were able to open sockets for a particular + IP protocol version. +*/ +bool is_ip_version_supported( + struct net_state_t *net_state, + int ip_version) +{ + if (ip_version == 4) { + return net_state->platform.ip4_present; + } else if (ip_version == 6) { + return net_state->platform.ip6_present; + } else { + return false; + } +} + /* Returns true if we can transmit probes using the specified protocol */ bool is_protocol_supported( struct net_state_t *net_state, @@ -588,13 +636,17 @@ void receive_replies( struct probe_t *probe; struct probe_t *probe_safe_iter; - receive_replies_from_icmp_socket( - net_state, net_state->platform.ip4_recv_socket, - handle_received_ip4_packet); + if (net_state->platform.ip4_present) { + receive_replies_from_icmp_socket( + net_state, net_state->platform.ip4_recv_socket, + handle_received_ip4_packet); + } - receive_replies_from_icmp_socket( - net_state, net_state->platform.ip6_recv_socket, - handle_received_ip6_packet); + if (net_state->platform.ip6_present) { + receive_replies_from_icmp_socket( + net_state, net_state->platform.ip6_recv_socket, + handle_received_ip6_packet); + } LIST_FOREACH_SAFE( probe, &net_state->outstanding_probes, diff --git a/packet/probe_unix.h b/packet/probe_unix.h index fcb5c88..f895a63 100644 --- a/packet/probe_unix.h +++ b/packet/probe_unix.h @@ -39,6 +39,12 @@ struct probe_platform_t /* We'll use rack sockets to send and recieve probes on Unix systems */ struct net_state_platform_t { + /* true if we were successful at opening IPv4 sockets */ + bool ip4_present; + + /* true if we were successful at opening IPv6 sockets */ + bool ip6_present; + /* Socket used to send raw IPv4 packets */ int ip4_send_socket; diff --git a/test/probe.py b/test/probe.py index 871db41..2b500c6 100755 --- a/test/probe.py +++ b/test/probe.py @@ -316,6 +316,9 @@ class TestProbeUDP(mtrpacket.MtrPacketTest): def udp_port_test(self, address): # type: (unicode) -> None 'Test UDP probes with variations on source port and dest port' + if not check_feature(self, 'udp'): + return + cmd = '80 send-probe protocol udp ' + address self.write_command(cmd) reply = self.parse_reply() @@ -356,6 +359,9 @@ class TestProbeTCP(mtrpacket.MtrPacketTest): test_basic_probe(self, 4, 'tcp') + if not check_feature(self, 'tcp'): + return + # Probe a local port assumed to be open (ssh) cmd = '80 send-probe ip-4 127.0.0.1 protocol tcp port 22' self.write_command(cmd) @@ -369,6 +375,9 @@ class TestProbeTCP(mtrpacket.MtrPacketTest): test_basic_probe(self, 6, 'tcp') + if not check_feature(self, 'tcp'): + return + # Probe a local port assumed to be open (ssh) cmd = '80 send-probe ip-6 ::1 protocol tcp port 22' self.write_command(cmd) diff --git a/ui/cmdpipe.c b/ui/cmdpipe.c index 4783e30..9ce4ce1 100644 --- a/ui/cmdpipe.c +++ b/ui/cmdpipe.c @@ -152,6 +152,21 @@ int check_packet_features( struct mtr_ctl *ctl, struct packet_command_pipe_t *cmdpipe) { + /* Check the IP protocol version */ + if (ctl->af == AF_INET6) { + if (check_feature(ctl, cmdpipe, "ip-6")) { + return -1; + } + } else if (ctl->af == AF_INET) { + if (check_feature(ctl, cmdpipe, "ip-4")) { + return -1; + } + } else { + errno = EINVAL; + return -1; + } + + /* Check the transport protocol */ if (ctl->mtrtype == IPPROTO_ICMP) { if (check_feature(ctl, cmdpipe, "icmp")) { return -1;