|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
mtr -- a network diagnostic tool
|
|
Packit |
b802ec |
Copyright (C) 2016 Matt Kimball
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
This program is free software; you can redistribute it and/or modify
|
|
Packit |
b802ec |
it under the terms of the GNU General Public License version 2 as
|
|
Packit |
b802ec |
published by the Free Software Foundation.
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
This program is distributed in the hope that it will be useful,
|
|
Packit |
b802ec |
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
Packit |
b802ec |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
Packit |
b802ec |
GNU General Public License for more details.
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
You should have received a copy of the GNU General Public License
|
|
Packit |
b802ec |
along with this program; if not, write to the Free Software
|
|
Packit |
b802ec |
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
#include "probe.h"
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
#include <assert.h>
|
|
Packit |
b802ec |
#include <errno.h>
|
|
Packit |
b802ec |
#include <fcntl.h>
|
|
Packit |
b802ec |
#include <stdio.h>
|
|
Packit |
b802ec |
#include <stdlib.h>
|
|
Packit |
b802ec |
#include <string.h>
|
|
Packit |
b802ec |
#include <sys/socket.h>
|
|
Packit |
b802ec |
#include <unistd.h>
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
#include "platform.h"
|
|
Packit |
b802ec |
#include "protocols.h"
|
|
Packit |
b802ec |
#include "construct_unix.h"
|
|
Packit |
b802ec |
#include "deconstruct_unix.h"
|
|
Packit |
b802ec |
#include "timeval.h"
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/* A wrapper around sendto for mixed IPv4 and IPv6 sending */
|
|
Packit |
b802ec |
static
|
|
Packit |
b802ec |
int send_packet(
|
|
Packit |
b802ec |
const struct net_state_t *net_state,
|
|
Packit |
b802ec |
const struct probe_param_t *param,
|
|
Packit |
b802ec |
const char *packet,
|
|
Packit |
b802ec |
int packet_size,
|
|
Packit |
b802ec |
const struct sockaddr_storage *sockaddr)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
int send_socket = 0;
|
|
Packit |
b802ec |
int sockaddr_length;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (sockaddr->ss_family == AF_INET6) {
|
|
Packit |
b802ec |
sockaddr_length = sizeof(struct sockaddr_in6);
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (param->protocol == IPPROTO_ICMP) {
|
|
Packit |
b802ec |
send_socket = net_state->platform.icmp6_send_socket;
|
|
Packit |
b802ec |
} else if (param->protocol == IPPROTO_UDP) {
|
|
Packit |
b802ec |
send_socket = net_state->platform.udp6_send_socket;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
} else if (sockaddr->ss_family == AF_INET) {
|
|
Packit |
b802ec |
sockaddr_length = sizeof(struct sockaddr_in);
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
send_socket = net_state->platform.ip4_send_socket;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (send_socket == 0) {
|
|
Packit |
b802ec |
errno = EINVAL;
|
|
Packit |
b802ec |
return -1;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
return sendto(send_socket, packet, packet_size, 0,
|
|
Packit |
b802ec |
(struct sockaddr *) sockaddr, sockaddr_length);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
Nearly all fields in the IP header should be encoded in network byte
|
|
Packit |
b802ec |
order prior to passing to send(). However, the required byte order of
|
|
Packit |
b802ec |
the length field of the IP header is inconsistent between operating
|
|
Packit |
b802ec |
systems and operating system versions. FreeBSD 11 requires the length
|
|
Packit |
b802ec |
field in network byte order, but some older versions of FreeBSD
|
|
Packit |
b802ec |
require host byte order. OS X requires the length field in host
|
|
Packit |
b802ec |
byte order. Linux will accept either byte order.
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
Test for a byte order which works by sending a ping to localhost.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
static
|
|
Packit |
b802ec |
void check_length_order(
|
|
Packit |
b802ec |
struct net_state_t *net_state)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
char packet[PACKET_BUFFER_SIZE];
|
|
Packit |
b802ec |
struct probe_param_t param;
|
|
Packit |
b802ec |
struct sockaddr_storage dest_sockaddr;
|
|
Packit |
b802ec |
struct sockaddr_storage src_sockaddr;
|
|
Packit |
b802ec |
ssize_t bytes_sent;
|
|
Packit |
b802ec |
int packet_size;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
memset(¶m, 0, sizeof(struct probe_param_t));
|
|
Packit |
b802ec |
param.ip_version = 4;
|
|
Packit |
b802ec |
param.protocol = IPPROTO_ICMP;
|
|
Packit |
b802ec |
param.ttl = 255;
|
|
Packit |
b802ec |
param.remote_address = "127.0.0.1";
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (resolve_probe_addresses(¶m, &dest_sockaddr, &src_sockaddr)) {
|
|
Packit |
b802ec |
fprintf(stderr, "Error decoding localhost address\n");
|
|
Packit |
b802ec |
exit(EXIT_FAILURE);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/* First attempt to ping the localhost with network byte order */
|
|
Packit |
b802ec |
net_state->platform.ip_length_host_order = false;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
packet_size = construct_packet(net_state, NULL, MIN_PORT,
|
|
Packit |
b802ec |
packet, PACKET_BUFFER_SIZE,
|
|
Packit |
b802ec |
&dest_sockaddr, &src_sockaddr, ¶m;;
|
|
Packit |
b802ec |
if (packet_size < 0) {
|
|
Packit |
b802ec |
perror("Unable to send to localhost");
|
|
Packit |
b802ec |
exit(EXIT_FAILURE);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
bytes_sent =
|
|
Packit |
b802ec |
send_packet(net_state, ¶m, packet, packet_size,
|
|
Packit |
b802ec |
&dest_sockaddr);
|
|
Packit |
b802ec |
if (bytes_sent > 0) {
|
|
Packit |
b802ec |
return;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/* Since network byte order failed, try host byte order */
|
|
Packit |
b802ec |
net_state->platform.ip_length_host_order = true;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
packet_size = construct_packet(net_state, NULL, MIN_PORT,
|
|
Packit |
b802ec |
packet, PACKET_BUFFER_SIZE,
|
|
Packit |
b802ec |
&dest_sockaddr, &src_sockaddr, ¶m;;
|
|
Packit |
b802ec |
if (packet_size < 0) {
|
|
Packit |
b802ec |
perror("Unable to send to localhost");
|
|
Packit |
b802ec |
exit(EXIT_FAILURE);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
bytes_sent =
|
|
Packit |
b802ec |
send_packet(net_state, ¶m, packet, packet_size,
|
|
Packit |
b802ec |
&dest_sockaddr);
|
|
Packit |
b802ec |
if (bytes_sent < 0) {
|
|
Packit |
b802ec |
perror("Unable to send with swapped length");
|
|
Packit |
b802ec |
exit(EXIT_FAILURE);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
Check to see if SCTP is support. We can't just rely on checking
|
|
Packit |
b802ec |
if IPPROTO_SCTP is defined, because while that is necessary,
|
|
Packit |
b802ec |
MacOS as of "Sierra" defines IPPROTO_SCTP, but creating an SCTP
|
|
Packit |
b802ec |
socket results in an error.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
static
|
|
Packit |
b802ec |
void check_sctp_support(
|
|
Packit |
b802ec |
struct net_state_t *net_state)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
#ifdef IPPROTO_SCTP
|
|
Packit |
b802ec |
int sctp_socket;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
sctp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP);
|
|
Packit |
b802ec |
if (sctp_socket != -1) {
|
|
Packit |
b802ec |
close(sctp_socket);
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
net_state->platform.sctp_support = true;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
#endif
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/* Set a socket to non-blocking mode */
|
|
Packit |
b802ec |
void set_socket_nonblocking(
|
|
Packit |
b802ec |
int socket)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
int flags;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
flags = fcntl(socket, F_GETFL, 0);
|
|
Packit |
b802ec |
if (flags == -1) {
|
|
Packit |
b802ec |
perror("Unexpected socket F_GETFL error");
|
|
Packit |
b802ec |
exit(EXIT_FAILURE);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (fcntl(socket, F_SETFL, flags | O_NONBLOCK)) {
|
|
Packit |
b802ec |
perror("Unexpected socket F_SETFL O_NONBLOCK error");
|
|
Packit |
b802ec |
exit(EXIT_FAILURE);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/* Open the raw sockets for sending/receiving IPv4 packets */
|
|
Packit |
b802ec |
static
|
|
Packit |
b802ec |
int open_ip4_sockets(
|
|
Packit |
b802ec |
struct net_state_t *net_state)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
int send_socket;
|
|
Packit |
b802ec |
int recv_socket;
|
|
Packit |
b802ec |
int trueopt = 1;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
send_socket = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
|
|
Packit |
b802ec |
if (send_socket == -1) {
|
|
Packit |
b802ec |
return -1;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
We will be including the IP header in transmitted packets.
|
|
Packit |
b802ec |
Linux doesn't require this, but BSD derived network stacks do.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
if (setsockopt
|
|
Packit |
b802ec |
(send_socket, IPPROTO_IP, IP_HDRINCL, &trueopt, sizeof(int))) {
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
close(send_socket);
|
|
Packit |
b802ec |
return -1;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
Open a second socket with IPPROTO_ICMP because we are only
|
|
Packit |
b802ec |
interested in receiving ICMP packets, not all packets.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
recv_socket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
|
|
Packit |
b802ec |
if (recv_socket == -1) {
|
|
Packit |
b802ec |
close(send_socket);
|
|
Packit |
b802ec |
return -1;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
net_state->platform.ip4_present = true;
|
|
Packit |
b802ec |
net_state->platform.ip4_send_socket = send_socket;
|
|
Packit |
b802ec |
net_state->platform.ip4_recv_socket = recv_socket;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
return 0;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/* Open the raw sockets for sending/receiving IPv6 packets */
|
|
Packit |
b802ec |
static
|
|
Packit |
b802ec |
int open_ip6_sockets(
|
|
Packit |
b802ec |
struct net_state_t *net_state)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
int send_socket_icmp;
|
|
Packit |
b802ec |
int send_socket_udp;
|
|
Packit |
b802ec |
int recv_socket;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
send_socket_icmp = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
|
|
Packit |
b802ec |
if (send_socket_icmp == -1) {
|
|
Packit |
b802ec |
return -1;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
send_socket_udp = socket(AF_INET6, SOCK_RAW, IPPROTO_UDP);
|
|
Packit |
b802ec |
if (send_socket_udp == -1) {
|
|
Packit |
b802ec |
close(send_socket_icmp);
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
return -1;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
recv_socket = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
|
|
Packit |
b802ec |
if (recv_socket == -1) {
|
|
Packit |
b802ec |
close(send_socket_icmp);
|
|
Packit |
b802ec |
close(send_socket_udp);
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
return -1;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
net_state->platform.ip6_present = true;
|
|
Packit |
b802ec |
net_state->platform.icmp6_send_socket = send_socket_icmp;
|
|
Packit |
b802ec |
net_state->platform.udp6_send_socket = send_socket_udp;
|
|
Packit |
b802ec |
net_state->platform.ip6_recv_socket = recv_socket;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
return 0;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
The first half of the net state initialization. Since this
|
|
Packit |
b802ec |
happens with elevated privileges, this is kept as minimal
|
|
Packit |
b802ec |
as possible to minimize security risk.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
void init_net_state_privileged(
|
|
Packit |
b802ec |
struct net_state_t *net_state)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
int ip4_err = 0;
|
|
Packit |
b802ec |
int ip6_err = 0;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
memset(net_state, 0, sizeof(struct net_state_t));
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
net_state->platform.next_sequence = MIN_PORT;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (open_ip4_sockets(net_state)) {
|
|
Packit |
b802ec |
ip4_err = errno;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
if (open_ip6_sockets(net_state)) {
|
|
Packit |
b802ec |
ip6_err = errno;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
If we couldn't open either IPv4 or IPv6 sockets, we can't do
|
|
Packit |
b802ec |
much, so print errors and exit.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
if (!net_state->platform.ip4_present
|
|
Packit |
b802ec |
&& !net_state->platform.ip6_present) {
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
errno = ip4_err;
|
|
Packit |
b802ec |
perror("Failure to open IPv4 sockets");
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
errno = ip6_err;
|
|
Packit |
b802ec |
perror("Failure to open IPv6 sockets");
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
exit(EXIT_FAILURE);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
The second half of net state initialization, which is run
|
|
Packit |
b802ec |
at normal privilege levels.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
void init_net_state(
|
|
Packit |
b802ec |
struct net_state_t *net_state)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
set_socket_nonblocking(net_state->platform.ip4_recv_socket);
|
|
Packit |
b802ec |
set_socket_nonblocking(net_state->platform.ip6_recv_socket);
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (net_state->platform.ip4_present) {
|
|
Packit |
b802ec |
check_length_order(net_state);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
check_sctp_support(net_state);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
Returns true if we were able to open sockets for a particular
|
|
Packit |
b802ec |
IP protocol version.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
bool is_ip_version_supported(
|
|
Packit |
b802ec |
struct net_state_t *net_state,
|
|
Packit |
b802ec |
int ip_version)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
if (ip_version == 4) {
|
|
Packit |
b802ec |
return net_state->platform.ip4_present;
|
|
Packit |
b802ec |
} else if (ip_version == 6) {
|
|
Packit |
b802ec |
return net_state->platform.ip6_present;
|
|
Packit |
b802ec |
} else {
|
|
Packit |
b802ec |
return false;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/* Returns true if we can transmit probes using the specified protocol */
|
|
Packit |
b802ec |
bool is_protocol_supported(
|
|
Packit |
b802ec |
struct net_state_t * net_state,
|
|
Packit |
b802ec |
int protocol)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
if (protocol == IPPROTO_ICMP) {
|
|
Packit |
b802ec |
return true;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (protocol == IPPROTO_UDP) {
|
|
Packit |
b802ec |
return true;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (protocol == IPPROTO_TCP) {
|
|
Packit |
b802ec |
return true;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
#ifdef IPPROTO_SCTP
|
|
Packit |
b802ec |
if (protocol == IPPROTO_SCTP) {
|
|
Packit |
b802ec |
return net_state->platform.sctp_support;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
#endif
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
return false;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/* Report an error during send_probe based on the errno value */
|
|
Packit |
b802ec |
static
|
|
Packit |
b802ec |
void report_packet_error(
|
|
Packit |
b802ec |
int command_token)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
if (errno == EINVAL) {
|
|
Packit |
b802ec |
printf("%d invalid-argument\n", command_token);
|
|
Packit |
b802ec |
} else if (errno == ENETDOWN) {
|
|
Packit |
b802ec |
printf("%d network-down\n", command_token);
|
|
Packit |
b802ec |
} else if (errno == ENETUNREACH) {
|
|
Packit |
b802ec |
printf("%d no-route\n", command_token);
|
|
Packit |
b802ec |
} else if (errno == EHOSTUNREACH) {
|
|
Packit |
b802ec |
printf("%d no-route\n", command_token);
|
|
Packit |
b802ec |
} else if (errno == EPERM) {
|
|
Packit |
b802ec |
printf("%d permission-denied\n", command_token);
|
|
Packit |
b802ec |
} else if (errno == EADDRINUSE) {
|
|
Packit |
b802ec |
printf("%d address-in-use\n", command_token);
|
|
Packit |
b802ec |
} else if (errno == EADDRNOTAVAIL) {
|
|
Packit |
b802ec |
printf("%d address-not-available\n", command_token);
|
|
Packit |
b802ec |
} else {
|
|
Packit |
b802ec |
printf("%d unexpected-error errno %d\n", command_token, errno);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/* Craft a custom ICMP packet for a network probe. */
|
|
Packit |
b802ec |
void send_probe(
|
|
Packit |
b802ec |
struct net_state_t *net_state,
|
|
Packit |
b802ec |
const struct probe_param_t *param)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
char packet[PACKET_BUFFER_SIZE];
|
|
Packit |
b802ec |
struct probe_t *probe;
|
|
Packit |
b802ec |
int packet_size;
|
|
Packit |
b802ec |
struct sockaddr_storage src_sockaddr;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
probe = alloc_probe(net_state, param->command_token);
|
|
Packit |
b802ec |
if (probe == NULL) {
|
|
Packit |
b802ec |
printf("%d probes-exhausted\n", param->command_token);
|
|
Packit |
b802ec |
return;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (resolve_probe_addresses(param, &probe->remote_addr, &src_sockaddr)) {
|
|
Packit |
b802ec |
printf("%d invalid-argument\n", param->command_token);
|
|
Packit |
b802ec |
free_probe(net_state, probe);
|
|
Packit |
b802ec |
return;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (gettimeofday(&probe->platform.departure_time, NULL)) {
|
|
Packit |
b802ec |
perror("gettimeofday failure");
|
|
Packit |
b802ec |
exit(EXIT_FAILURE);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
packet_size =
|
|
Packit |
b802ec |
construct_packet(net_state, &probe->platform.socket,
|
|
Packit |
b802ec |
probe->sequence, packet, PACKET_BUFFER_SIZE,
|
|
Packit |
b802ec |
&probe->remote_addr, &src_sockaddr, param);
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (packet_size < 0) {
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
When using a stream protocol, FreeBSD will return ECONNREFUSED
|
|
Packit |
b802ec |
when connecting to localhost if the port doesn't exist,
|
|
Packit |
b802ec |
even if the socket is non-blocking, so we should be
|
|
Packit |
b802ec |
prepared for that.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
if (errno == ECONNREFUSED) {
|
|
Packit |
b802ec |
receive_probe(net_state, probe, ICMP_ECHOREPLY,
|
|
Packit |
b802ec |
&probe->remote_addr, NULL, 0, NULL);
|
|
Packit |
b802ec |
} else {
|
|
Packit |
b802ec |
report_packet_error(param->command_token);
|
|
Packit |
b802ec |
free_probe(net_state, probe);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
return;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (packet_size > 0) {
|
|
Packit |
b802ec |
if (send_packet(net_state, param,
|
|
Packit |
b802ec |
packet, packet_size, &probe->remote_addr) == -1) {
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
report_packet_error(param->command_token);
|
|
Packit |
b802ec |
free_probe(net_state, probe);
|
|
Packit |
b802ec |
return;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
probe->platform.timeout_time = probe->platform.departure_time;
|
|
Packit |
b802ec |
probe->platform.timeout_time.tv_sec += param->timeout;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/* When allocating a probe, assign it a unique port number */
|
|
Packit |
b802ec |
void platform_alloc_probe(
|
|
Packit |
b802ec |
struct net_state_t *net_state,
|
|
Packit |
b802ec |
struct probe_t *probe)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
probe->sequence = net_state->platform.next_sequence++;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (net_state->platform.next_sequence > MAX_PORT) {
|
|
Packit |
b802ec |
net_state->platform.next_sequence = MIN_PORT;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
When freeing the probe, close the socket for the probe,
|
|
Packit |
b802ec |
if one has been opened
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
void platform_free_probe(
|
|
Packit |
b802ec |
struct probe_t *probe)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
if (probe->platform.socket) {
|
|
Packit |
b802ec |
close(probe->platform.socket);
|
|
Packit |
b802ec |
probe->platform.socket = 0;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
Compute the round trip time of a just-received probe and pass it
|
|
Packit |
b802ec |
to the platform agnostic response handling.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
void receive_probe(
|
|
Packit |
b802ec |
struct net_state_t *net_state,
|
|
Packit |
b802ec |
struct probe_t *probe,
|
|
Packit |
b802ec |
int icmp_type,
|
|
Packit |
b802ec |
const struct sockaddr_storage *remote_addr,
|
|
Packit |
b802ec |
struct timeval *timestamp,
|
|
Packit |
b802ec |
int mpls_count,
|
|
Packit |
b802ec |
struct mpls_label_t *mpls)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
unsigned int round_trip_us;
|
|
Packit |
b802ec |
struct timeval *departure_time = &probe->platform.departure_time;
|
|
Packit |
b802ec |
struct timeval now;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (timestamp == NULL) {
|
|
Packit |
b802ec |
if (gettimeofday(&now, NULL)) {
|
|
Packit |
b802ec |
perror("gettimeofday failure");
|
|
Packit |
b802ec |
exit(EXIT_FAILURE);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
timestamp = &now;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
round_trip_us =
|
|
Packit |
b802ec |
(timestamp->tv_sec - departure_time->tv_sec) * 1000000 +
|
|
Packit |
b802ec |
timestamp->tv_usec - departure_time->tv_usec;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
respond_to_probe(net_state, probe, icmp_type,
|
|
Packit |
b802ec |
remote_addr, round_trip_us, mpls_count, mpls);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
Read all available packets through our receiving raw socket, and
|
|
Packit |
b802ec |
handle any responses to probes we have preivously sent.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
static
|
|
Packit |
b802ec |
void receive_replies_from_icmp_socket(
|
|
Packit |
b802ec |
struct net_state_t *net_state,
|
|
Packit |
b802ec |
int socket,
|
|
Packit |
b802ec |
received_packet_func_t handle_received_packet)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
char packet[PACKET_BUFFER_SIZE];
|
|
Packit |
b802ec |
int packet_length;
|
|
Packit |
b802ec |
struct sockaddr_storage remote_addr;
|
|
Packit |
b802ec |
socklen_t sockaddr_length;
|
|
Packit |
b802ec |
struct timeval timestamp;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/* Read until no more packets are available */
|
|
Packit |
b802ec |
while (true) {
|
|
Packit |
b802ec |
sockaddr_length = sizeof(struct sockaddr_storage);
|
|
Packit |
b802ec |
packet_length = recvfrom(socket, packet, PACKET_BUFFER_SIZE, 0,
|
|
Packit |
b802ec |
(struct sockaddr *) &remote_addr,
|
|
Packit |
b802ec |
&sockaddr_length);
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
Get the time immediately after reading the packet to
|
|
Packit |
b802ec |
keep the timing as precise as we can.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
if (gettimeofday(×tamp, NULL)) {
|
|
Packit |
b802ec |
perror("gettimeofday failure");
|
|
Packit |
b802ec |
exit(EXIT_FAILURE);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (packet_length == -1) {
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
EAGAIN will be returned if there is no current packet
|
|
Packit |
b802ec |
available.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
if (errno == EAGAIN) {
|
|
Packit |
b802ec |
return;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
EINTER will be returned if we received a signal during
|
|
Packit |
b802ec |
receive.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
if (errno == EINTR) {
|
|
Packit |
b802ec |
continue;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
perror("Failure receiving replies");
|
|
Packit |
b802ec |
exit(EXIT_FAILURE);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
handle_received_packet(net_state, &remote_addr, packet,
|
|
Packit |
b802ec |
packet_length, ×tamp);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
Attempt to send using the probe's socket, in order to check whether
|
|
Packit |
b802ec |
the connection has completed, for stream oriented protocols such as
|
|
Packit |
b802ec |
TCP.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
static
|
|
Packit |
b802ec |
void receive_replies_from_probe_socket(
|
|
Packit |
b802ec |
struct net_state_t *net_state,
|
|
Packit |
b802ec |
struct probe_t *probe)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
int probe_socket;
|
|
Packit |
b802ec |
struct timeval zero_time;
|
|
Packit |
b802ec |
int err;
|
|
Packit |
b802ec |
int err_length = sizeof(int);
|
|
Packit |
b802ec |
fd_set write_set;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
probe_socket = probe->platform.socket;
|
|
Packit |
b802ec |
if (!probe_socket) {
|
|
Packit |
b802ec |
return;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
FD_ZERO(&write_set);
|
|
Packit |
b802ec |
FD_SET(probe_socket, &write_set);
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
zero_time.tv_sec = 0;
|
|
Packit |
b802ec |
zero_time.tv_usec = 0;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (select(probe_socket + 1, NULL, &write_set, NULL, &zero_time) == -1) {
|
|
Packit |
b802ec |
if (errno == EAGAIN) {
|
|
Packit |
b802ec |
return;
|
|
Packit |
b802ec |
} else {
|
|
Packit |
b802ec |
perror("probe socket select error");
|
|
Packit |
b802ec |
exit(EXIT_FAILURE);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
If the socket is writable, the connection attempt has completed.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
if (!FD_ISSET(probe_socket, &write_set)) {
|
|
Packit |
b802ec |
return;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (getsockopt(probe_socket, SOL_SOCKET, SO_ERROR, &err, &err_length)) {
|
|
Packit |
b802ec |
perror("probe socket SO_ERROR");
|
|
Packit |
b802ec |
exit(EXIT_FAILURE);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
If the connection complete successfully, or was refused, we can
|
|
Packit |
b802ec |
assume our probe arrived at the destination.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
if (!err || err == ECONNREFUSED) {
|
|
Packit |
b802ec |
receive_probe(net_state, probe, ICMP_ECHOREPLY,
|
|
Packit |
b802ec |
&probe->remote_addr, NULL, 0, NULL);
|
|
Packit |
b802ec |
} else {
|
|
Packit |
b802ec |
errno = err;
|
|
Packit |
b802ec |
report_packet_error(probe->token);
|
|
Packit |
b802ec |
free_probe(net_state, probe);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/* Check both the IPv4 and IPv6 sockets for incoming packets */
|
|
Packit |
b802ec |
void receive_replies(
|
|
Packit |
b802ec |
struct net_state_t *net_state)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
struct probe_t *probe;
|
|
Packit |
b802ec |
struct probe_t *probe_safe_iter;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (net_state->platform.ip4_present) {
|
|
Packit |
b802ec |
receive_replies_from_icmp_socket(net_state,
|
|
Packit |
b802ec |
net_state->platform.
|
|
Packit |
b802ec |
ip4_recv_socket,
|
|
Packit |
b802ec |
handle_received_ip4_packet);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (net_state->platform.ip6_present) {
|
|
Packit |
b802ec |
receive_replies_from_icmp_socket(net_state,
|
|
Packit |
b802ec |
net_state->platform.
|
|
Packit |
b802ec |
ip6_recv_socket,
|
|
Packit |
b802ec |
handle_received_ip6_packet);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
LIST_FOREACH_SAFE(probe, &net_state->outstanding_probes,
|
|
Packit |
b802ec |
probe_list_entry, probe_safe_iter) {
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
receive_replies_from_probe_socket(net_state, probe);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
Put all of our probe sockets in the read set used for an upcoming
|
|
Packit |
b802ec |
select so we can wake when any of them become readable.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
int gather_probe_sockets(
|
|
Packit |
b802ec |
const struct net_state_t *net_state,
|
|
Packit |
b802ec |
fd_set * write_set)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
int probe_socket;
|
|
Packit |
b802ec |
int nfds;
|
|
Packit |
b802ec |
const struct probe_t *probe;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
nfds = 0;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
LIST_FOREACH(probe, &net_state->outstanding_probes, probe_list_entry) {
|
|
Packit |
b802ec |
probe_socket = probe->platform.socket;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (probe_socket) {
|
|
Packit |
b802ec |
FD_SET(probe_socket, write_set);
|
|
Packit |
b802ec |
if (probe_socket >= nfds) {
|
|
Packit |
b802ec |
nfds = probe_socket + 1;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
return nfds;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
Check for any probes for which we have not received a response
|
|
Packit |
b802ec |
for some time, and report a time-out, assuming that we won't
|
|
Packit |
b802ec |
receive a future reply.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
void check_probe_timeouts(
|
|
Packit |
b802ec |
struct net_state_t *net_state)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
struct timeval now;
|
|
Packit |
b802ec |
struct probe_t *probe;
|
|
Packit |
b802ec |
struct probe_t *probe_safe_iter;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (gettimeofday(&now, NULL)) {
|
|
Packit |
b802ec |
perror("gettimeofday failure");
|
|
Packit |
b802ec |
exit(EXIT_FAILURE);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
LIST_FOREACH_SAFE(probe, &net_state->outstanding_probes,
|
|
Packit |
b802ec |
probe_list_entry, probe_safe_iter) {
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (compare_timeval(probe->platform.timeout_time, now) < 0) {
|
|
Packit |
b802ec |
/* Report timeout to the command stream */
|
|
Packit |
b802ec |
printf("%d no-reply\n", probe->token);
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
free_probe(net_state, probe);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
Find the remaining time until the next probe times out.
|
|
Packit |
b802ec |
This may be a negative value if the next probe timeout has
|
|
Packit |
b802ec |
already elapsed.
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
Returns false if no probes are currently outstanding, and true
|
|
Packit |
b802ec |
if a timeout value for the next probe exists.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
bool get_next_probe_timeout(
|
|
Packit |
b802ec |
const struct net_state_t *net_state,
|
|
Packit |
b802ec |
struct timeval *timeout)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
bool have_timeout;
|
|
Packit |
b802ec |
const struct probe_t *probe;
|
|
Packit |
b802ec |
struct timeval now;
|
|
Packit |
b802ec |
struct timeval probe_timeout;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (gettimeofday(&now, NULL)) {
|
|
Packit |
b802ec |
perror("gettimeofday failure");
|
|
Packit |
b802ec |
exit(EXIT_FAILURE);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
have_timeout = false;
|
|
Packit |
b802ec |
LIST_FOREACH(probe, &net_state->outstanding_probes, probe_list_entry) {
|
|
Packit |
b802ec |
probe_timeout.tv_sec =
|
|
Packit |
b802ec |
probe->platform.timeout_time.tv_sec - now.tv_sec;
|
|
Packit |
b802ec |
probe_timeout.tv_usec =
|
|
Packit |
b802ec |
probe->platform.timeout_time.tv_usec - now.tv_usec;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
normalize_timeval(&probe_timeout);
|
|
Packit |
b802ec |
if (have_timeout) {
|
|
Packit |
b802ec |
if (compare_timeval(probe_timeout, *timeout) < 0) {
|
|
Packit |
b802ec |
/* If this probe has a sooner timeout, store it instead */
|
|
Packit |
b802ec |
*timeout = probe_timeout;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
} else {
|
|
Packit |
b802ec |
*timeout = probe_timeout;
|
|
Packit |
b802ec |
have_timeout = true;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
return have_timeout;
|
|
Packit |
b802ec |
}
|