diff --git a/Makefile b/Makefile index 7e2e96fa..5be103f7 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,7 @@ kblayout \ kblayout-compiler \ login \ mkinitrd \ +ping \ regress \ rw \ sf \ diff --git a/ping/.gitignore b/ping/.gitignore new file mode 100644 index 00000000..f68190ac --- /dev/null +++ b/ping/.gitignore @@ -0,0 +1 @@ +ping diff --git a/ping/Makefile b/ping/Makefile new file mode 100644 index 00000000..292e59f2 --- /dev/null +++ b/ping/Makefile @@ -0,0 +1,29 @@ +SOFTWARE_MEANT_FOR_SORTIX=1 +include ../build-aux/platform.mak +include ../build-aux/compiler.mak +include ../build-aux/version.mak +include ../build-aux/dirs.mak + +OPTLEVEL?=$(DEFAULT_OPTLEVEL) +CFLAGS?=$(OPTLEVEL) + +CFLAGS += -Wall -Wextra + +BINARIES = ping +MANPAGES8 = ping.8 + +all: $(BINARIES) + +.PHONY: all install clean + +install: all + mkdir -p $(DESTDIR)$(SBINDIR) + install $(BINARIES) $(DESTDIR)$(SBINDIR) + mkdir -p $(DESTDIR)$(MANDIR)/man8 + install $(MANPAGES8) $(DESTDIR)$(MANDIR)/man8 + +%: %.c + $(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) $< -o $@ + +clean: + rm -f $(BINARIES) diff --git a/ping/ping.8 b/ping/ping.8 new file mode 100644 index 00000000..defa23f5 --- /dev/null +++ b/ping/ping.8 @@ -0,0 +1,40 @@ +.Dd February 23, 2023 +.Dt PING 8 +.Os +.Sh NAME +.Nm ping +.Nd internet control message protocol echo +.Sh SYNOPSIS +.Nm +.Op Fl 46 +.Ar host +.Sh DESCRIPTION +.Nm +tests connectivity to the remote +.Ar host +by sending an Internet Control Message Protocol +.Xr ( icmp 4 ) +ECHO +message and measures how quickly it responds to the ping and how many packets +are lost. +.Pp +The options are as follows: +.Bl -tag -width "12345678" +.It Fl 4 +Ping using the Internet Prototocol version 4 +.Xr ( ip 4 ) . +.It Fl 6 +Ping using the Internet Prototocol version 6 +.Xr ( ip6 4 ) . +.El +.Sh EXIT STATUS +.Nm +runs until terminated or it exits non-zero upon a fatal error. +.Sh SEE ALSO +.Xr icmp 4 , +.Xr ip 4 , +.Xr ip6 4 , +.Xr ping 4 , +.Xr ifconfig 8 +.Sh BUGS +IPv6 is not yet implemented in the kernel. diff --git a/ping/ping.c b/ping/ping.c new file mode 100644 index 00000000..200ba831 --- /dev/null +++ b/ping/ping.c @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2017, 2023 Jonas 'Sortie' Termansen. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * ping.c + * Internet Control Message Protocol Echo. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PAYLOAD_SIZE 56 + +int main(int argc, char* argv[]) +{ + bool ipv4 = false; + bool ipv6 = false; + + int opt; + while ( (opt = getopt(argc, argv, "46")) != -1 ) + { + switch ( opt ) + { + case '4': ipv4 = true; break; + case '6': ipv6 = true; break; + default: return 1; + } + } + + if ( argc - optind < 1 ) + errx(1, "No host given"); + if ( argc - optind > 1 ) + errx(1, "Unexpected extra operand: %s", argv[optind + 1]); + const char* host = argv[optind]; + + if ( 1 < ipv4 + ipv6 ) + errx(1, "The -4 and -6 options are mutually incompatible"); + + struct addrinfo hint; + memset(&hint, 0, sizeof(hint)); + if ( ipv4 ) + hint.ai_family = AF_INET; + if ( ipv6 ) + hint.ai_family = AF_INET6; + hint.ai_socktype = SOCK_DGRAM; + hint.ai_protocol = IPPROTO_PING; + + struct addrinfo* res0 = NULL; + int status = getaddrinfo(host, NULL, &hint, &res0); + if ( status == EAI_SYSTEM ) + err(1, "%s", host); + if ( status ) + errx(1, "%s: %s", host, gai_strerror(status)); + if ( !res0 ) + errx(1, "%s: %s", host, gai_strerror(EAI_NONAME)); + + char host_address[NI_MAXHOST]; + int fd; + for ( struct addrinfo* res = res0; res; res = res->ai_next ) + { + if ( (fd = socket(res->ai_family, res->ai_socktype, + res->ai_protocol)) < 0 ) + { + if ( res->ai_next ) + continue; + err(1, "socket"); + } + if ( connect(fd, res->ai_addr, res->ai_addrlen) < 0 ) + { + close(fd); + if ( res->ai_next ) + continue; + err(1, "connect: %s", host); + } + if ( getnameinfo(res->ai_addr, res->ai_addrlen, host_address, + sizeof(host_address), NULL, 0, NI_NUMERICHOST) < 0 ) + strlcpy(host_address, "unknown", sizeof(host_address)); + break; + } + + freeaddrinfo(res0); + + printf("PING %s (%s) %zu bytes of data.\n", + host, host_address, (size_t) (PAYLOAD_SIZE + 8)); + + while ( true ) + { + unsigned char expected[PAYLOAD_SIZE]; + arc4random_buf(expected, sizeof(expected)); + struct timespec begun; + clock_gettime(CLOCK_MONOTONIC, &begun); + // TODO: Don't fail on network errors. + if ( send(fd, expected, sizeof(expected), 0) < 0 ) + err(1, "send"); + struct timespec timeout = timespec_add(timespec_make(1, 0), begun); + while ( true ) + { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + struct timespec remaining = timespec_sub(timeout, now); + if ( remaining.tv_sec < 0 ) + break; + struct pollfd pfd = { 0 }; + pfd.fd = fd; + pfd.events = POLLIN; + if ( ppoll(&pfd, 1, &remaining, NULL) <= 0 ) + break; + unsigned char gotten[PAYLOAD_SIZE]; + ssize_t amount = recv(fd, gotten, sizeof(gotten), 0); + struct timespec end; + clock_gettime(CLOCK_MONOTONIC, &end); + // TODO: Don't fail on network errors. + if ( amount < 0 ) + err(1, "recv"); + if ( amount == PAYLOAD_SIZE && + memcmp(expected, gotten, PAYLOAD_SIZE) == 0 ) + { + // TODO: Reverse DNS. + uint16_t sequence = gotten[0] << 8 | gotten[1] << 0; + struct timespec duration = timespec_sub(end, begun); + uintmax_t ms = (uintmax_t) duration.tv_sec * (uintmax_t) 1000 + + (uintmax_t) ((duration.tv_nsec / 1000) / 1000); + unsigned int us = (duration.tv_nsec / 1000) % 1000; + printf("%zu bytes from %s (%s): icmp_seq=%u time=%ju.%03u ms\n", + (size_t) (PAYLOAD_SIZE + 8), host, host_address, + sequence, ms, us); + } + } + } + + return 0; +} diff --git a/share/man/man4/ping.4 b/share/man/man4/ping.4 index a12e1270..7b9f8035 100644 --- a/share/man/man4/ping.4 +++ b/share/man/man4/ping.4 @@ -474,7 +474,8 @@ socket options was attempted to be set to a non-zero value. .Xr if 4 , .Xr inet 4 , .Xr ip 4 , -.Xr kernel 7 +.Xr kernel 7 , +.Xr ping 8 .Sh STANDARDS .Rs .%A J. Postel (ed.)