Blame packet/probe.c

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
}