Blame packet/probe_cygwin.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 <errno.h>
Packit b802ec
#include <stdio.h>
Packit b802ec
#include <winternl.h>
Packit b802ec
Packit b802ec
#include "protocols.h"
Packit b802ec
Packit b802ec
/*  Windows doesn't require any initialization at a privileged level  */
Packit b802ec
void init_net_state_privileged(
Packit b802ec
    struct net_state_t *net_state)
Packit b802ec
{
Packit b802ec
}
Packit b802ec
Packit b802ec
/*  Open the ICMP.DLL interface  */
Packit b802ec
void init_net_state(
Packit b802ec
    struct net_state_t *net_state)
Packit b802ec
{
Packit b802ec
    memset(net_state, 0, sizeof(struct net_state_t));
Packit b802ec
Packit b802ec
    net_state->platform.icmp4 = IcmpCreateFile();
Packit b802ec
    net_state->platform.icmp6 = Icmp6CreateFile();
Packit b802ec
Packit b802ec
    if (net_state->platform.icmp4 == INVALID_HANDLE_VALUE
Packit b802ec
        && net_state->platform.icmp6 == INVALID_HANDLE_VALUE) {
Packit b802ec
        fprintf(stderr, "Failure opening ICMP %d\n", GetLastError());
Packit b802ec
        exit(EXIT_FAILURE);
Packit b802ec
    }
Packit b802ec
}
Packit b802ec
Packit b802ec
/*
Packit b802ec
    If we succeeded at opening the ICMP file handle, we can
Packit b802ec
    assume that IP protocol version is supported.
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.icmp4 != INVALID_HANDLE_VALUE);
Packit b802ec
    } else if (ip_version == 6) {
Packit b802ec
        return (net_state->platform.icmp6 != INVALID_HANDLE_VALUE);
Packit b802ec
    }
Packit b802ec
Packit b802ec
    return false;
Packit b802ec
}
Packit b802ec
Packit b802ec
/*  On Windows, we only support ICMP probes  */
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
    return false;
Packit b802ec
}
Packit b802ec
Packit b802ec
/*  Set the back pointer to the net_state when a probe is allocated  */
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->platform.net_state = net_state;
Packit b802ec
}
Packit b802ec
Packit b802ec
/*  Free the reply buffer when the probe is freed  */
Packit b802ec
void platform_free_probe(
Packit b802ec
    struct probe_t *probe)
Packit b802ec
{
Packit b802ec
    if (probe->platform.reply4) {
Packit b802ec
        free(probe->platform.reply4);
Packit b802ec
        probe->platform.reply4 = NULL;
Packit b802ec
    }
Packit b802ec
}
Packit b802ec
Packit b802ec
/*  Report a windows error code using a platform-independent error string  */
Packit b802ec
static
Packit b802ec
void report_win_error(
Packit b802ec
    int command_token,
Packit b802ec
    int err)
Packit b802ec
{
Packit b802ec
    /*  It could be that we got no reply because of timeout  */
Packit b802ec
    if (err == IP_REQ_TIMED_OUT || err == IP_SOURCE_QUENCH) {
Packit b802ec
        printf("%d no-reply\n", command_token);
Packit b802ec
    } else if (err == IP_DEST_HOST_UNREACHABLE
Packit b802ec
               || err == IP_DEST_PORT_UNREACHABLE
Packit b802ec
               || err == IP_DEST_PROT_UNREACHABLE
Packit b802ec
               || err == IP_DEST_NET_UNREACHABLE
Packit b802ec
               || err == IP_DEST_UNREACHABLE
Packit b802ec
               || err == IP_DEST_NO_ROUTE
Packit b802ec
               || err == IP_BAD_ROUTE || err == IP_BAD_DESTINATION) {
Packit b802ec
        printf("%d no-route\n", command_token);
Packit b802ec
    } else if (err == ERROR_INVALID_NETNAME) {
Packit b802ec
        printf("%d address-not-available\n", command_token);
Packit b802ec
    } else if (err == ERROR_INVALID_PARAMETER) {
Packit b802ec
        printf("%d invalid-argument\n", command_token);
Packit b802ec
    } else {
Packit b802ec
        printf("%d unexpected-error winerror %d\n", command_token, err);
Packit b802ec
    }
Packit b802ec
}
Packit b802ec
Packit b802ec
/*
Packit b802ec
    The overlapped I/O style completion routine to be called by
Packit b802ec
    Windows during an altertable wait when an ICMP probe has
Packit b802ec
    completed, either by reply, or by ICMP.DLL timeout.
Packit b802ec
*/
Packit b802ec
static
Packit b802ec
void WINAPI on_icmp_reply(
Packit b802ec
    PVOID context,
Packit b802ec
    PIO_STATUS_BLOCK status,
Packit b802ec
    ULONG reserved)
Packit b802ec
{
Packit b802ec
    struct probe_t *probe = (struct probe_t *) context;
Packit b802ec
    struct net_state_t *net_state = probe->platform.net_state;
Packit b802ec
    int icmp_type;
Packit b802ec
    int round_trip_us = 0;
Packit b802ec
    int reply_count;
Packit b802ec
    int reply_status = 0;
Packit b802ec
    struct sockaddr_storage remote_addr;
Packit b802ec
    struct sockaddr_in *remote_addr4;
Packit b802ec
    struct sockaddr_in6 *remote_addr6;
Packit b802ec
    ICMP_ECHO_REPLY32 *reply4;
Packit b802ec
    ICMPV6_ECHO_REPLY *reply6;
Packit b802ec
Packit b802ec
    if (probe->platform.ip_version == 6) {
Packit b802ec
        reply6 = probe->platform.reply6;
Packit b802ec
        reply_count = Icmp6ParseReplies(reply6, sizeof(ICMPV6_ECHO_REPLY));
Packit b802ec
Packit b802ec
        if (reply_count > 0) {
Packit b802ec
            reply_status = reply6->Status;
Packit b802ec
Packit b802ec
            /*  Unfortunately, ICMP.DLL only has millisecond precision  */
Packit b802ec
            round_trip_us = reply6->RoundTripTime * 1000;
Packit b802ec
Packit b802ec
            remote_addr6 = (struct sockaddr_in6 *) &remote_addr;
Packit b802ec
            remote_addr6->sin6_family = AF_INET6;
Packit b802ec
            remote_addr6->sin6_port = 0;
Packit b802ec
            remote_addr6->sin6_flowinfo = 0;
Packit b802ec
            memcpy(&remote_addr6->sin6_addr, reply6->AddressBits,
Packit b802ec
                   sizeof(struct in6_addr));
Packit b802ec
            remote_addr6->sin6_scope_id = 0;
Packit b802ec
        }
Packit b802ec
    } else {
Packit b802ec
        reply4 = probe->platform.reply4;
Packit b802ec
        reply_count = IcmpParseReplies(reply4, sizeof(ICMP_ECHO_REPLY));
Packit b802ec
Packit b802ec
        if (reply_count > 0) {
Packit b802ec
            reply_status = reply4->Status;
Packit b802ec
Packit b802ec
            /*  Unfortunately, ICMP.DLL only has millisecond precision  */
Packit b802ec
            round_trip_us = reply4->RoundTripTime * 1000;
Packit b802ec
Packit b802ec
            remote_addr4 = (struct sockaddr_in *) &remote_addr;
Packit b802ec
            remote_addr4->sin_family = AF_INET;
Packit b802ec
            remote_addr4->sin_port = 0;
Packit b802ec
            remote_addr4->sin_addr.s_addr = reply4->Address;
Packit b802ec
        }
Packit b802ec
    }
Packit b802ec
Packit b802ec
    if (reply_count == 0) {
Packit b802ec
        reply_status = GetLastError();
Packit b802ec
    }
Packit b802ec
Packit b802ec
    icmp_type = -1;
Packit b802ec
    if (reply_status == IP_SUCCESS) {
Packit b802ec
        icmp_type = ICMP_ECHOREPLY;
Packit b802ec
    } else if (reply_status == IP_TTL_EXPIRED_TRANSIT
Packit b802ec
               || reply_status == IP_TTL_EXPIRED_REASSEM) {
Packit b802ec
        icmp_type = ICMP_TIME_EXCEEDED;
Packit b802ec
    }
Packit b802ec
Packit b802ec
    if (icmp_type != -1) {
Packit b802ec
        /*  Record probe result  */
Packit b802ec
        respond_to_probe(net_state, probe, icmp_type,
Packit b802ec
                         &remote_addr, round_trip_us, 0, NULL);
Packit b802ec
    } else {
Packit b802ec
        report_win_error(probe->token, reply_status);
Packit b802ec
        free_probe(net_state, probe);
Packit b802ec
    }
Packit b802ec
}
Packit b802ec
Packit b802ec
/*  Use ICMP.DLL's send echo support to send a probe  */
Packit b802ec
static
Packit b802ec
void icmp_send_probe(
Packit b802ec
    struct net_state_t *net_state,
Packit b802ec
    struct probe_t *probe,
Packit b802ec
    const struct probe_param_t *param,
Packit b802ec
    struct sockaddr_storage *src_sockaddr,
Packit b802ec
    struct sockaddr_storage *dest_sockaddr,
Packit b802ec
    char *payload,
Packit b802ec
    int payload_size)
Packit b802ec
{
Packit b802ec
    IP_OPTION_INFORMATION option;
Packit b802ec
    DWORD timeout;
Packit b802ec
    DWORD send_result;
Packit b802ec
    int reply_size;
Packit b802ec
    int err;
Packit b802ec
    struct sockaddr_in *dest_sockaddr4;
Packit b802ec
    struct sockaddr_in6 *src_sockaddr6;
Packit b802ec
    struct sockaddr_in6 *dest_sockaddr6;
Packit b802ec
Packit b802ec
    if (param->timeout > 0) {
Packit b802ec
        timeout = 1000 * param->timeout;
Packit b802ec
    } else {
Packit b802ec
        /*
Packit b802ec
           IcmpSendEcho2 will return invalid argument on a timeout of 
Packit b802ec
           zero.  Our Unix implementation allows it.  Bump up the timeout
Packit b802ec
           to 1 millisecond.
Packit b802ec
         */
Packit b802ec
        timeout = 1;
Packit b802ec
    }
Packit b802ec
Packit b802ec
    memset(&option, 0, sizeof(IP_OPTION_INFORMATION32));
Packit b802ec
    option.Ttl = param->ttl;
Packit b802ec
Packit b802ec
    if (param->ip_version == 6) {
Packit b802ec
        reply_size = sizeof(ICMPV6_ECHO_REPLY) + payload_size;
Packit b802ec
    } else {
Packit b802ec
        reply_size = sizeof(ICMP_ECHO_REPLY32) + payload_size;
Packit b802ec
    }
Packit b802ec
Packit b802ec
    probe->platform.reply4 = malloc(reply_size);
Packit b802ec
    if (probe->platform.reply4 == NULL) {
Packit b802ec
        perror("failure to allocate reply buffer");
Packit b802ec
        exit(EXIT_FAILURE);
Packit b802ec
    }
Packit b802ec
Packit b802ec
    if (param->ip_version == 6) {
Packit b802ec
        src_sockaddr6 = (struct sockaddr_in6 *) src_sockaddr;
Packit b802ec
        dest_sockaddr6 = (struct sockaddr_in6 *) dest_sockaddr;
Packit b802ec
Packit b802ec
        send_result = Icmp6SendEcho2(net_state->platform.icmp6, NULL,
Packit b802ec
                                     (FARPROC) on_icmp_reply, probe,
Packit b802ec
                                     src_sockaddr6, dest_sockaddr6,
Packit b802ec
                                     payload, payload_size, &option,
Packit b802ec
                                     probe->platform.reply6, reply_size,
Packit b802ec
                                     timeout);
Packit b802ec
    } else {
Packit b802ec
        dest_sockaddr4 = (struct sockaddr_in *) dest_sockaddr;
Packit b802ec
Packit b802ec
        send_result = IcmpSendEcho2(net_state->platform.icmp4, NULL,
Packit b802ec
                                    (FARPROC) on_icmp_reply, probe,
Packit b802ec
                                    dest_sockaddr4->sin_addr.s_addr,
Packit b802ec
                                    payload, payload_size, &option,
Packit b802ec
                                    probe->platform.reply4, reply_size,
Packit b802ec
                                    timeout);
Packit b802ec
    }
Packit b802ec
Packit b802ec
    if (send_result == 0) {
Packit b802ec
        err = GetLastError();
Packit b802ec
Packit b802ec
        /*
Packit b802ec
           ERROR_IO_PENDING is expected for asynchronous probes,
Packit b802ec
           but any other error is unexpected.
Packit b802ec
         */
Packit b802ec
        if (err != ERROR_IO_PENDING) {
Packit b802ec
            report_win_error(probe->token, err);
Packit b802ec
            free_probe(net_state, probe);
Packit b802ec
        }
Packit b802ec
    }
Packit b802ec
}
Packit b802ec
Packit b802ec
/*  Fill the payload of the packet as specified by the probe parameters  */
Packit b802ec
static
Packit b802ec
int fill_payload(
Packit b802ec
    const struct probe_param_t *param,
Packit b802ec
    char *payload,
Packit b802ec
    int payload_buffer_size)
Packit b802ec
{
Packit b802ec
    int ip_icmp_size;
Packit b802ec
    int payload_size;
Packit b802ec
Packit b802ec
    if (param->ip_version == 6) {
Packit b802ec
        ip_icmp_size =
Packit b802ec
            sizeof(struct IP6Header) + sizeof(struct ICMPHeader);
Packit b802ec
    } else if (param->ip_version == 4) {
Packit b802ec
        ip_icmp_size = sizeof(struct IPHeader) + sizeof(struct ICMPHeader);
Packit b802ec
    } else {
Packit b802ec
        errno = EINVAL;
Packit b802ec
        return -1;
Packit b802ec
    }
Packit b802ec
Packit b802ec
    payload_size = param->packet_size - ip_icmp_size;
Packit b802ec
    if (payload_size < 0) {
Packit b802ec
        payload_size = 0;
Packit b802ec
    }
Packit b802ec
Packit b802ec
    if (payload_size > payload_buffer_size) {
Packit b802ec
        errno = EINVAL;
Packit b802ec
        return -1;
Packit b802ec
    }
Packit b802ec
Packit b802ec
    memset(payload, param->bit_pattern, payload_size);
Packit b802ec
Packit b802ec
    return payload_size;
Packit b802ec
}
Packit b802ec
Packit b802ec
/*  Decode the probe parameters and send a 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
    struct probe_t *probe;
Packit b802ec
    struct sockaddr_storage dest_sockaddr;
Packit b802ec
    struct sockaddr_storage src_sockaddr;
Packit b802ec
    char payload[PACKET_BUFFER_SIZE];
Packit b802ec
    int payload_size;
Packit b802ec
Packit b802ec
    if (resolve_probe_addresses(param, &dest_sockaddr, &src_sockaddr)) {
Packit b802ec
        printf("%d invalid-argument\n", param->command_token);
Packit b802ec
        return;
Packit b802ec
    }
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
    probe->platform.ip_version = param->ip_version;
Packit b802ec
Packit b802ec
    payload_size = fill_payload(param, payload, PACKET_BUFFER_SIZE);
Packit b802ec
    if (payload_size < 0) {
Packit b802ec
        perror("Error construction packet");
Packit b802ec
        exit(EXIT_FAILURE);
Packit b802ec
    }
Packit b802ec
Packit b802ec
    icmp_send_probe(net_state, probe, param,
Packit b802ec
                    &src_sockaddr, &dest_sockaddr, payload, payload_size);
Packit b802ec
}
Packit b802ec
Packit b802ec
/*
Packit b802ec
    On Windows, an implementation of receive_replies is unnecessary, because,
Packit b802ec
    unlike Unix, replies are completed using Overlapped I/O during an
Packit b802ec
    alertable wait, and don't require explicit reads.
Packit b802ec
*/
Packit b802ec
void receive_replies(
Packit b802ec
    struct net_state_t *net_state)
Packit b802ec
{
Packit b802ec
}
Packit b802ec
Packit b802ec
/*
Packit b802ec
    On Windows, an implementation of check_probe_timeout is unnecesary because
Packit b802ec
    timeouts are managed by ICMP.DLL, including a call to the I/O completion
Packit b802ec
    routine when the time fully expires.
Packit b802ec
*/
Packit b802ec
void check_probe_timeouts(
Packit b802ec
    struct net_state_t *net_state)
Packit b802ec
{
Packit b802ec
}
Packit b802ec
Packit b802ec
/*
Packit b802ec
    As in the case of check_probe_timeout, getting the next probe timeout is
Packit b802ec
    unnecessary under Windows, as ICMP.DLL manages timeouts for us.
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
    return false;
Packit b802ec
}