|
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 <arpa/inet.h>
|
|
Packit |
b802ec |
#include <assert.h>
|
|
Packit |
b802ec |
#include <errno.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 "command.h"
|
|
Packit |
b802ec |
#include "platform.h"
|
|
Packit |
b802ec |
#include "protocols.h"
|
|
Packit |
b802ec |
#include "timeval.h"
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
#define IP_TEXT_LENGTH 64
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/* Convert the destination address from text to sockaddr */
|
|
Packit |
b802ec |
int decode_address_string(
|
|
Packit |
b802ec |
int ip_version,
|
|
Packit |
b802ec |
const char *address_string,
|
|
Packit |
b802ec |
struct sockaddr_storage *address)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
struct in_addr addr4;
|
|
Packit |
b802ec |
struct in6_addr addr6;
|
|
Packit |
b802ec |
struct sockaddr_in *sockaddr4;
|
|
Packit |
b802ec |
struct sockaddr_in6 *sockaddr6;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (address == NULL) {
|
|
Packit |
b802ec |
errno = EINVAL;
|
|
Packit |
b802ec |
return -1;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (ip_version == 6) {
|
|
Packit |
b802ec |
sockaddr6 = (struct sockaddr_in6 *) address;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (inet_pton(AF_INET6, address_string, &addr6) != 1) {
|
|
Packit |
b802ec |
errno = EINVAL;
|
|
Packit |
b802ec |
return -1;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
sockaddr6->sin6_family = AF_INET6;
|
|
Packit |
b802ec |
sockaddr6->sin6_port = 0;
|
|
Packit |
b802ec |
sockaddr6->sin6_flowinfo = 0;
|
|
Packit |
b802ec |
sockaddr6->sin6_addr = addr6;
|
|
Packit |
b802ec |
sockaddr6->sin6_scope_id = 0;
|
|
Packit |
b802ec |
} else if (ip_version == 4) {
|
|
Packit |
b802ec |
sockaddr4 = (struct sockaddr_in *) address;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (inet_pton(AF_INET, address_string, &addr4) != 1) {
|
|
Packit |
b802ec |
errno = EINVAL;
|
|
Packit |
b802ec |
return -1;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
sockaddr4->sin_family = AF_INET;
|
|
Packit |
b802ec |
sockaddr4->sin_port = 0;
|
|
Packit |
b802ec |
sockaddr4->sin_addr = addr4;
|
|
Packit |
b802ec |
} else {
|
|
Packit |
b802ec |
errno = EINVAL;
|
|
Packit |
b802ec |
return -1;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
return 0;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
Resolve the probe parameters into a remote and local address
|
|
Packit |
b802ec |
for the probe.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
int resolve_probe_addresses(
|
|
Packit |
b802ec |
const struct probe_param_t *param,
|
|
Packit |
b802ec |
struct sockaddr_storage *dest_sockaddr,
|
|
Packit |
b802ec |
struct sockaddr_storage *src_sockaddr)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
if (decode_address_string
|
|
Packit |
b802ec |
(param->ip_version, param->remote_address, dest_sockaddr)) {
|
|
Packit |
b802ec |
return -1;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (param->local_address) {
|
|
Packit |
b802ec |
if (decode_address_string
|
|
Packit |
b802ec |
(param->ip_version, param->local_address, src_sockaddr)) {
|
|
Packit |
b802ec |
return -1;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
} else {
|
|
Packit |
b802ec |
if (find_source_addr(src_sockaddr, dest_sockaddr)) {
|
|
Packit |
b802ec |
return -1;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
return 0;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/* Allocate a structure for tracking a new probe */
|
|
Packit |
b802ec |
struct probe_t *alloc_probe(
|
|
Packit |
b802ec |
struct net_state_t *net_state,
|
|
Packit |
b802ec |
int token)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
struct probe_t *probe;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (net_state->outstanding_probe_count >= MAX_PROBES) {
|
|
Packit |
b802ec |
return NULL;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
probe = malloc(sizeof(struct probe_t));
|
|
Packit |
b802ec |
if (probe == NULL) {
|
|
Packit |
b802ec |
return NULL;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
memset(probe, 0, sizeof(struct probe_t));
|
|
Packit |
b802ec |
probe->token = token;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
platform_alloc_probe(net_state, probe);
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
net_state->outstanding_probe_count++;
|
|
Packit |
b802ec |
LIST_INSERT_HEAD(&net_state->outstanding_probes, probe,
|
|
Packit |
b802ec |
probe_list_entry);
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
return probe;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/* Mark a probe tracking structure as unused */
|
|
Packit |
b802ec |
void free_probe(
|
|
Packit |
b802ec |
struct net_state_t *net_state,
|
|
Packit |
b802ec |
struct probe_t *probe)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
LIST_REMOVE(probe, probe_list_entry);
|
|
Packit |
b802ec |
net_state->outstanding_probe_count--;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
platform_free_probe(probe);
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
free(probe);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
Find an existing probe structure by ICMP id and sequence number.
|
|
Packit |
b802ec |
Returns NULL if non is found.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
struct probe_t *find_probe(
|
|
Packit |
b802ec |
struct net_state_t *net_state,
|
|
Packit |
b802ec |
int protocol,
|
|
Packit |
b802ec |
int id,
|
|
Packit |
b802ec |
int sequence)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
struct probe_t *probe;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
ICMP has room for an id to check against our process, but
|
|
Packit |
b802ec |
UDP doesn't.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
if (protocol == IPPROTO_ICMP) {
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
If the ICMP id doesn't match our process ID, it wasn't a
|
|
Packit |
b802ec |
probe generated by this process, so ignore it.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
if (id != htons(getpid())) {
|
|
Packit |
b802ec |
return NULL;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
LIST_FOREACH(probe, &net_state->outstanding_probes, probe_list_entry) {
|
|
Packit |
b802ec |
if (htons(probe->sequence) == sequence) {
|
|
Packit |
b802ec |
return probe;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
return NULL;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
Format a list of MPLS labels into a string appropriate for including
|
|
Packit |
b802ec |
as an argument to a probe reply.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
static
|
|
Packit |
b802ec |
void format_mpls_string(
|
|
Packit |
b802ec |
char *str,
|
|
Packit |
b802ec |
int buffer_size,
|
|
Packit |
b802ec |
int mpls_count,
|
|
Packit |
b802ec |
const struct mpls_label_t *mpls_list)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
int i;
|
|
Packit |
b802ec |
char *append_pos = str;
|
|
Packit |
b802ec |
const struct mpls_label_t *mpls;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/* Start with an empty string */
|
|
Packit |
b802ec |
str[0] = 0;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
for (i = 0; i < mpls_count; i++) {
|
|
Packit |
b802ec |
mpls = &mpls_list[i];
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (i > 0) {
|
|
Packit |
b802ec |
strncat(append_pos, ",", buffer_size - 1);
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
buffer_size -= strlen(append_pos);
|
|
Packit |
b802ec |
append_pos += strlen(append_pos);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
snprintf(append_pos, buffer_size, "%d,%d,%d,%d",
|
|
Packit |
b802ec |
mpls->label, mpls->experimental_use,
|
|
Packit |
b802ec |
mpls->bottom_of_stack, mpls->ttl);
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
buffer_size -= strlen(append_pos);
|
|
Packit |
b802ec |
append_pos += strlen(append_pos);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
After a probe reply has arrived, respond to the command request which
|
|
Packit |
b802ec |
sent the probe.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
void respond_to_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 |
unsigned int round_trip_us,
|
|
Packit |
b802ec |
int mpls_count,
|
|
Packit |
b802ec |
const struct mpls_label_t *mpls)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
char ip_text[IP_TEXT_LENGTH];
|
|
Packit |
b802ec |
char response[COMMAND_BUFFER_SIZE];
|
|
Packit |
b802ec |
char mpls_str[COMMAND_BUFFER_SIZE];
|
|
Packit |
b802ec |
int remaining_size;
|
|
Packit |
b802ec |
const char *result;
|
|
Packit |
b802ec |
const char *ip_argument;
|
|
Packit |
b802ec |
struct sockaddr_in *sockaddr4;
|
|
Packit |
b802ec |
struct sockaddr_in6 *sockaddr6;
|
|
Packit |
b802ec |
void *addr;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (icmp_type == ICMP_TIME_EXCEEDED) {
|
|
Packit |
b802ec |
result = "ttl-expired";
|
|
Packit |
b802ec |
} else {
|
|
Packit |
b802ec |
assert(icmp_type == ICMP_ECHOREPLY);
|
|
Packit |
b802ec |
result = "reply";
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (remote_addr->ss_family == AF_INET6) {
|
|
Packit |
b802ec |
ip_argument = "ip-6";
|
|
Packit |
b802ec |
sockaddr6 = (struct sockaddr_in6 *) remote_addr;
|
|
Packit |
b802ec |
addr = &sockaddr6->sin6_addr;
|
|
Packit |
b802ec |
} else {
|
|
Packit |
b802ec |
ip_argument = "ip-4";
|
|
Packit |
b802ec |
sockaddr4 = (struct sockaddr_in *) remote_addr;
|
|
Packit |
b802ec |
addr = &sockaddr4->sin_addr;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (inet_ntop(remote_addr->ss_family, addr, ip_text, IP_TEXT_LENGTH) ==
|
|
Packit |
b802ec |
NULL) {
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
perror("inet_ntop failure");
|
|
Packit |
b802ec |
exit(EXIT_FAILURE);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
snprintf(response, COMMAND_BUFFER_SIZE,
|
|
Packit |
b802ec |
"%d %s %s %s round-trip-time %d",
|
|
Packit |
b802ec |
probe->token, result, ip_argument, ip_text, round_trip_us);
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (mpls_count) {
|
|
Packit |
b802ec |
format_mpls_string(mpls_str, COMMAND_BUFFER_SIZE, mpls_count,
|
|
Packit |
b802ec |
mpls);
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
remaining_size = COMMAND_BUFFER_SIZE - strlen(response) - 1;
|
|
Packit |
b802ec |
strncat(response, " mpls ", remaining_size);
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
remaining_size = COMMAND_BUFFER_SIZE - strlen(response) - 1;
|
|
Packit |
b802ec |
strncat(response, mpls_str, remaining_size);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
puts(response);
|
|
Packit |
b802ec |
free_probe(net_state, probe);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
Find the source address for transmitting to a particular destination
|
|
Packit |
b802ec |
address. Remember that hosts can have multiple addresses, for example
|
|
Packit |
b802ec |
a unique address for each network interface. So we will bind a UDP
|
|
Packit |
b802ec |
socket to our destination and check the socket address after binding
|
|
Packit |
b802ec |
to get the source for that destination, which will allow the kernel
|
|
Packit |
b802ec |
to do the routing table work for us.
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
(connecting UDP sockets, unlike TCP sockets, doesn't transmit any packets.
|
|
Packit |
b802ec |
It's just an association.)
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
int find_source_addr(
|
|
Packit |
b802ec |
struct sockaddr_storage *srcaddr,
|
|
Packit |
b802ec |
const struct sockaddr_storage *destaddr)
|
|
Packit |
b802ec |
{
|
|
Packit |
b802ec |
int sock;
|
|
Packit |
b802ec |
int len;
|
|
Packit |
b802ec |
struct sockaddr_in *destaddr4;
|
|
Packit |
b802ec |
struct sockaddr_in6 *destaddr6;
|
|
Packit |
b802ec |
struct sockaddr_storage dest_with_port;
|
|
Packit |
b802ec |
struct sockaddr_in *srcaddr4;
|
|
Packit |
b802ec |
struct sockaddr_in6 *srcaddr6;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
dest_with_port = *destaddr;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
MacOS requires a non-zero sin_port when used as an
|
|
Packit |
b802ec |
address for a UDP connect. If we provide a zero port,
|
|
Packit |
b802ec |
the connect will fail. We aren't actually sending
|
|
Packit |
b802ec |
anything to the port.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
if (destaddr->ss_family == AF_INET6) {
|
|
Packit |
b802ec |
destaddr6 = (struct sockaddr_in6 *) &dest_with_port;
|
|
Packit |
b802ec |
destaddr6->sin6_port = htons(1);
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
len = sizeof(struct sockaddr_in6);
|
|
Packit |
b802ec |
} else {
|
|
Packit |
b802ec |
destaddr4 = (struct sockaddr_in *) &dest_with_port;
|
|
Packit |
b802ec |
destaddr4->sin_port = htons(1);
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
len = sizeof(struct sockaddr_in);
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
sock = socket(destaddr->ss_family, SOCK_DGRAM, IPPROTO_UDP);
|
|
Packit |
b802ec |
if (sock == -1) {
|
|
Packit |
b802ec |
return -1;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (connect(sock, (struct sockaddr *) &dest_with_port, len)) {
|
|
Packit |
b802ec |
close(sock);
|
|
Packit |
b802ec |
return -1;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
if (getsockname(sock, (struct sockaddr *) srcaddr, &len)) {
|
|
Packit |
b802ec |
close(sock);
|
|
Packit |
b802ec |
return -1;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
close(sock);
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
/*
|
|
Packit |
b802ec |
Zero the port, as we may later use this address to finding, and
|
|
Packit |
b802ec |
we don't want to use the port from the socket we just created.
|
|
Packit |
b802ec |
*/
|
|
Packit |
b802ec |
if (destaddr->ss_family == AF_INET6) {
|
|
Packit |
b802ec |
srcaddr6 = (struct sockaddr_in6 *) srcaddr;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
srcaddr6->sin6_port = 0;
|
|
Packit |
b802ec |
} else {
|
|
Packit |
b802ec |
srcaddr4 = (struct sockaddr_in *) srcaddr;
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
srcaddr4->sin_port = 0;
|
|
Packit |
b802ec |
}
|
|
Packit |
b802ec |
|
|
Packit |
b802ec |
return 0;
|
|
Packit |
b802ec |
}
|