|
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 |
}
|