/* 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 #include #include #include #include #include /*#include -- deliberately not including this */ #include #include #include #include #include #include #include #if defined HAVE_IDN2_H && defined HAVE_IDN2 # include #elif defined HAVE_IDNA_H # include #endif #include #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 */ }