mirror of
https://git.savannah.gnu.org/git/inetutils.git
synced 2026-01-12 00:19:39 +08:00
380 lines
8.5 KiB
C
380 lines
8.5 KiB
C
/*
|
|
Copyright (C) 2008-2025 Free Software Foundation, Inc.
|
|
|
|
This file is part of GNU Inetutils.
|
|
|
|
GNU Inetutils is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or (at
|
|
your option) any later version.
|
|
|
|
GNU Inetutils is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see `http://www.gnu.org/licenses/'. */
|
|
|
|
#include <config.h>
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/file.h>
|
|
#include <sys/time.h>
|
|
|
|
#include <netinet/in.h>
|
|
/*#include <netinet/ip_icmp.h> -- deliberately not including this */
|
|
#include <arpa/inet.h>
|
|
#include <netdb.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#if defined HAVE_IDN2_H && defined HAVE_IDN2
|
|
# include <idn2.h>
|
|
#elif defined HAVE_IDNA_H
|
|
# include <idna.h>
|
|
#endif
|
|
|
|
#include <timespec.h>
|
|
|
|
#include "ping.h"
|
|
|
|
static int useless_ident = 0; /* Relevant at least for Linux. */
|
|
|
|
static size_t _ping_packetsize (PING * p);
|
|
|
|
size_t
|
|
_ping_packetsize (PING *p)
|
|
{
|
|
if (p->ping_type == ICMP_TIMESTAMP || p->ping_type == ICMP_TIMESTAMPREPLY)
|
|
return ICMP_TSLEN;
|
|
|
|
if (p->ping_type == ICMP_ADDRESS || p->ping_type == ICMP_ADDRESSREPLY)
|
|
return ICMP_MASKLEN;
|
|
|
|
return PING_HEADER_LEN + p->ping_datalen;
|
|
}
|
|
|
|
PING *
|
|
ping_init (int type, int ident)
|
|
{
|
|
int fd;
|
|
struct protoent *proto;
|
|
PING *p;
|
|
|
|
/* Initialize raw ICMP socket */
|
|
proto = getprotobyname ("icmp");
|
|
if (!proto)
|
|
{
|
|
fprintf (stderr, "ping: unknown protocol icmp.\n");
|
|
return NULL;
|
|
}
|
|
|
|
fd = socket (AF_INET, SOCK_RAW, proto->p_proto);
|
|
if (fd < 0)
|
|
{
|
|
if (errno == EPERM || errno == EACCES)
|
|
{
|
|
errno = 0;
|
|
|
|
/* At least Linux can allow subprivileged users to send ICMP
|
|
* packets formally encapsulated and built as a datagram socket,
|
|
* but then the identity number is set by the kernel itself.
|
|
*/
|
|
fd = socket (AF_INET, SOCK_DGRAM, proto->p_proto);
|
|
if (fd < 0)
|
|
{
|
|
if (errno == EPERM || errno == EACCES
|
|
|| errno == EPROTONOSUPPORT)
|
|
fprintf (stderr,
|
|
"ping: Lacking privilege for icmp socket.\n");
|
|
else
|
|
fprintf (stderr, "ping: %s\n", strerror (errno));
|
|
|
|
return NULL;
|
|
}
|
|
|
|
useless_ident++; /* SOCK_DGRAM overrides our set identity. */
|
|
}
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
/* Allocate PING structure and initialize it to default values */
|
|
p = malloc (sizeof (*p));
|
|
if (!p)
|
|
{
|
|
close (fd);
|
|
return p;
|
|
}
|
|
|
|
memset (p, 0, sizeof (*p));
|
|
|
|
p->ping_fd = fd;
|
|
p->ping_type = type;
|
|
p->ping_count = 0;
|
|
p->ping_interval = PING_DEFAULT_INTERVAL;
|
|
p->ping_datalen = sizeof (icmphdr_t);
|
|
/* Make sure we use only 16 bits in this field, id for icmp is a unsigned short. */
|
|
p->ping_ident = ident & 0xFFFF;
|
|
p->ping_cktab_size = PING_CKTABSIZE;
|
|
p->ping_start_time = current_timespec ();
|
|
return p;
|
|
}
|
|
|
|
void
|
|
ping_reset (PING *p)
|
|
{
|
|
p->ping_num_xmit = 0;
|
|
p->ping_num_recv = 0;
|
|
p->ping_num_rept = 0;
|
|
}
|
|
|
|
void
|
|
ping_set_type (PING *p, int type)
|
|
{
|
|
p->ping_type = type;
|
|
}
|
|
|
|
int
|
|
ping_xmit (PING *p)
|
|
{
|
|
int i, buflen;
|
|
|
|
if (_ping_setbuf (p, USE_IPV6))
|
|
return -1;
|
|
|
|
buflen = _ping_packetsize (p);
|
|
|
|
/* Mark sequence number as sent */
|
|
_PING_CLR (p, p->ping_num_xmit);
|
|
|
|
/* Encode ICMP header */
|
|
switch (p->ping_type)
|
|
{
|
|
case ICMP_ECHO:
|
|
icmp_echo_encode (p->ping_buffer, buflen, p->ping_ident,
|
|
p->ping_num_xmit);
|
|
break;
|
|
|
|
case ICMP_TIMESTAMP:
|
|
icmp_timestamp_encode (p->ping_buffer, buflen, p->ping_ident,
|
|
p->ping_num_xmit);
|
|
break;
|
|
|
|
case ICMP_ADDRESS:
|
|
icmp_address_encode (p->ping_buffer, buflen, p->ping_ident,
|
|
p->ping_num_xmit);
|
|
break;
|
|
|
|
default:
|
|
icmp_generic_encode (p->ping_buffer, buflen, p->ping_type,
|
|
p->ping_ident, p->ping_num_xmit);
|
|
break;
|
|
}
|
|
|
|
i = sendto (p->ping_fd, (char *) p->ping_buffer, buflen, 0,
|
|
(struct sockaddr *) &p->ping_dest.ping_sockaddr,
|
|
sizeof (struct sockaddr_in));
|
|
if (i < 0)
|
|
return -1;
|
|
else
|
|
{
|
|
p->ping_num_xmit++;
|
|
if (i != buflen)
|
|
printf ("ping: wrote %s %d chars, ret=%d\n",
|
|
p->ping_hostname, buflen, i);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
my_echo_reply (PING *p, icmphdr_t *icmp)
|
|
{
|
|
struct ip *orig_ip = &icmp->icmp_ip;
|
|
icmphdr_t *orig_icmp = (icmphdr_t *) (orig_ip + 1);
|
|
|
|
return (orig_ip->ip_dst.s_addr == p->ping_dest.ping_sockaddr.sin_addr.s_addr
|
|
&& orig_ip->ip_p == IPPROTO_ICMP
|
|
&& orig_icmp->icmp_type == ICMP_ECHO
|
|
&& (ntohs (orig_icmp->icmp_id) == p->ping_ident || useless_ident));
|
|
}
|
|
|
|
int
|
|
ping_recv (PING *p)
|
|
{
|
|
socklen_t fromlen = sizeof (p->ping_from.ping_sockaddr);
|
|
int n, rc;
|
|
icmphdr_t *icmp;
|
|
struct ip *ip;
|
|
int dupflag;
|
|
|
|
n = recvfrom (p->ping_fd,
|
|
(char *) p->ping_buffer, _PING_BUFLEN (p, USE_IPV6), 0,
|
|
(struct sockaddr *) &p->ping_from.ping_sockaddr, &fromlen);
|
|
if (n < 0)
|
|
return -1;
|
|
|
|
rc = icmp_generic_decode (p->ping_buffer, n, &ip, &icmp);
|
|
if (rc < 0)
|
|
{
|
|
/*FIXME: conditional */
|
|
fprintf (stderr, "packet too short (%d bytes) from %s\n", n,
|
|
inet_ntoa (p->ping_from.ping_sockaddr.sin_addr));
|
|
return -1;
|
|
}
|
|
|
|
switch (icmp->icmp_type)
|
|
{
|
|
case ICMP_ECHOREPLY:
|
|
case ICMP_TIMESTAMPREPLY:
|
|
case ICMP_ADDRESSREPLY:
|
|
/* case ICMP_ROUTERADV: */
|
|
|
|
if (ntohs (icmp->icmp_id) != p->ping_ident && useless_ident == 0)
|
|
return -1;
|
|
|
|
if (rc)
|
|
fprintf (stderr, "checksum mismatch from %s\n",
|
|
inet_ntoa (p->ping_from.ping_sockaddr.sin_addr));
|
|
|
|
p->ping_num_recv++;
|
|
if (_PING_TST (p, ntohs (icmp->icmp_seq)))
|
|
{
|
|
p->ping_num_rept++;
|
|
p->ping_num_recv--;
|
|
dupflag = 1;
|
|
}
|
|
else
|
|
{
|
|
_PING_SET (p, ntohs (icmp->icmp_seq));
|
|
dupflag = 0;
|
|
}
|
|
|
|
if (p->ping_event.handler)
|
|
(*p->ping_event.handler) (dupflag ? PEV_DUPLICATE : PEV_RESPONSE,
|
|
p->ping_closure,
|
|
&p->ping_dest.ping_sockaddr,
|
|
&p->ping_from.ping_sockaddr, ip, icmp, n);
|
|
break;
|
|
|
|
case ICMP_ECHO:
|
|
case ICMP_TIMESTAMP:
|
|
case ICMP_ADDRESS:
|
|
return -1;
|
|
|
|
default:
|
|
if (!my_echo_reply (p, icmp))
|
|
return -1;
|
|
|
|
if (p->ping_event.handler)
|
|
(*p->ping_event.handler) (PEV_NOECHO,
|
|
p->ping_closure,
|
|
&p->ping_dest.ping_sockaddr,
|
|
&p->ping_from.ping_sockaddr, ip, icmp, n);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
ping_set_event_handler (PING *ping, ping_efp pf, void *closure)
|
|
{
|
|
ping->ping_event.handler = pf;
|
|
ping->ping_closure = closure;
|
|
}
|
|
|
|
void
|
|
ping_set_packetsize (PING *ping, size_t size)
|
|
{
|
|
ping->ping_datalen = size;
|
|
}
|
|
|
|
int
|
|
ping_set_dest (PING *ping, const char *host)
|
|
{
|
|
#if HAVE_DECL_GETADDRINFO
|
|
int rc;
|
|
struct addrinfo hints, *res;
|
|
char *rhost;
|
|
|
|
# if defined HAVE_IDN || defined HAVE_IDN2
|
|
rc = idna_to_ascii_lz (host, &rhost, 0); /* RHOST is allocated. */
|
|
if (rc)
|
|
return 1;
|
|
host = rhost;
|
|
# else
|
|
rhost = NULL;
|
|
# endif
|
|
|
|
memset (&hints, 0, sizeof (hints));
|
|
hints.ai_family = AF_INET;
|
|
hints.ai_flags = AI_CANONNAME;
|
|
# ifdef AI_IDN
|
|
hints.ai_flags |= AI_IDN;
|
|
# endif
|
|
# ifdef AI_CANONIDN
|
|
hints.ai_flags |= AI_CANONIDN;
|
|
# endif
|
|
|
|
rc = getaddrinfo (host, NULL, &hints, &res);
|
|
|
|
if (rc)
|
|
{
|
|
free (rhost);
|
|
return 1;
|
|
}
|
|
|
|
memcpy (&ping->ping_dest.ping_sockaddr, res->ai_addr, res->ai_addrlen);
|
|
if (res->ai_canonname)
|
|
ping->ping_hostname = strdup (res->ai_canonname);
|
|
else
|
|
# if defined HAVE_IDN || defined HAVE_IDN2
|
|
ping->ping_hostname = host;
|
|
# else
|
|
ping->ping_hostname = strdup (host);
|
|
# endif
|
|
|
|
freeaddrinfo (res);
|
|
|
|
return 0;
|
|
#else /* !HAVE_DECL_GETADDRINFO */
|
|
|
|
struct sockaddr_in *s_in = &ping->ping_dest.ping_sockaddr;
|
|
s_in->sin_family = AF_INET;
|
|
# ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN
|
|
s_in->sin_len = sizeof (*s_in);
|
|
# endif
|
|
if (inet_aton (host, &s_in->sin_addr))
|
|
ping->ping_hostname = strdup (host);
|
|
else
|
|
{
|
|
struct hostent *hp;
|
|
# if defined HAVE_IDN || defined HAVE_IDN2
|
|
char *rhost;
|
|
int rc;
|
|
|
|
rc = idna_to_ascii_lz (host, &rhost, 0);
|
|
if (rc)
|
|
return 1;
|
|
hp = gethostbyname (rhost);
|
|
free (rhost);
|
|
# else/* !HAVE_IDN && !HAVE_IDN2 */
|
|
hp = gethostbyname (host);
|
|
# endif
|
|
if (!hp)
|
|
return 1;
|
|
|
|
s_in->sin_family = hp->h_addrtype;
|
|
if (hp->h_length > (int) sizeof (s_in->sin_addr))
|
|
hp->h_length = sizeof (s_in->sin_addr);
|
|
|
|
memcpy (&s_in->sin_addr, hp->h_addr, hp->h_length);
|
|
ping->ping_hostname = strdup (hp->h_name);
|
|
}
|
|
return 0;
|
|
#endif /* !HAVE_DECL_GETADDRINFO */
|
|
}
|