All of lore.kernel.org
 help / color / mirror / Atom feed
From: Hannes Diethelm <hannes.diethelm@gmail.com>
To: xenomai@lists.linux.dev, rpm@xenomai.org
Cc: Hannes Diethelm <hannes.diethelm@gmail.com>
Subject: [PATCH 1/1] tidbits: net-ping: add ping tidbit using raw socket
Date: Sun, 28 Jun 2026 17:39:56 +0200	[thread overview]
Message-ID: <20260628153956.1435330-2-hannes.diethelm@gmail.com> (raw)
In-Reply-To: <20260628153956.1435330-1-hannes.diethelm@gmail.com>

This tidbit allows to ping any host and measure the response time.

Signed-off-by: Hannes Diethelm <hannes.diethelm@gmail.com>
---
 tidbits/meson.build    |   6 +
 tidbits/oob-net-ping.c | 480 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 486 insertions(+)
 create mode 100644 tidbits/oob-net-ping.c

diff --git a/tidbits/meson.build b/tidbits/meson.build
index 3713219..f24502f 100644
--- a/tidbits/meson.build
+++ b/tidbits/meson.build
@@ -6,6 +6,12 @@ executable('oob-net-icmp',
     dependencies: libevl_dep
 )
 
+executable('oob-net-ping',
+    'oob-net-ping.c',
+    install: true,
+    dependencies: libevl_dep
+)
+
 executable('oob-net-udp',
     'oob-net-udp.c',
     install: true,
diff --git a/tidbits/oob-net-ping.c b/tidbits/oob-net-ping.c
new file mode 100644
index 0000000..efbe11e
--- /dev/null
+++ b/tidbits/oob-net-ping.c
@@ -0,0 +1,480 @@
+/*
+ * SPDX-License-Identifier: MIT
+ *
+ * This tidbit demonstrates out-of-band networking, by sending
+ * ICMPv4(ECHO) requests using the raw packet socket
+ * interface. Responses are received and checked.
+ * See https://v4.xenomai.org/core/net/ for details.
+ *
+ * Copyright (C) 2026 Hannes Diethelm <hannes.diethelm@gmail.com>
+ * based on oob-net-icmp.c
+ *
+ */
+
+#include <pthread.h>
+#include <error.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <memory.h>
+#include <getopt.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <linux/if_packet.h>
+#include <netinet/ether.h>
+#include <netinet/ip_icmp.h>
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+#include <evl/thread.h>
+#include <evl/clock.h>
+#include <evl/proxy.h>
+#include <evl/net/net.h>
+
+static int verbosity = 1;
+
+static uint16_t ip_checksum(void *buf, int len)
+{
+	uint16_t *p = buf;	/* buf is assumed to be 16bit-aligned. */
+	uint32_t sum = 0;
+	int count = len;
+
+	while (count > 1) {
+		sum += *p++;
+		count -= sizeof(*p);
+	}
+
+	if (count > 0)
+		sum += *(uint8_t *)p;
+
+	while (sum >> 16)
+		sum = (sum & 0xffff) + (sum >> 16);
+
+	return ~sum & 0xffff;
+}
+
+static void dump_packet(const char *title, const unsigned char *buf, int len)
+{
+	int n = 0;
+
+	evl_printf("== dumping %s, length=%d\n", title, len);
+
+	while (len-- > 0) {
+		evl_printf("%.2x ", *buf++);
+		if ((++n % 16) == 0)
+			evl_printf("\n");
+	}
+
+	if (n % 16)
+		evl_printf("\n");
+}
+
+static void print_ip_header(const struct ip *iphdr)
+{
+	evl_printf("iphdr.ip_hl=%d\n", iphdr->ip_hl);
+	evl_printf("iphdr.ip_v=%d\n", iphdr->ip_v);
+	evl_printf("iphdr.ip_len=%d\n", ntohs(iphdr->ip_len));
+	evl_printf("iphdr.ip_id=%d\n", ntohs(iphdr->ip_id));
+	evl_printf("iphdr.ip_p=%d\n", iphdr->ip_p);
+	evl_printf("iphdr.ip_sum=%#x\n", iphdr->ip_sum);
+}
+
+static void print_icmp_reply(const void *etherbuf, size_t len)
+{
+	const struct icmphdr *icmphdr;
+	const struct ip *iphdr; /* no option behind */
+	const void *icmpdata;
+
+	dump_packet("ICMP reply", etherbuf, len);
+	iphdr = etherbuf + ETH_HLEN;
+	print_ip_header(iphdr);
+	icmphdr = (const struct icmphdr *)(iphdr + 1);
+	icmpdata = icmphdr + 1;
+	(void)icmpdata;
+	evl_printf("icmp.icmp_type=%d\n", icmphdr->type);
+	evl_printf("icmp.icmp_id=%d\n", ntohs(icmphdr->un.echo.id));
+	evl_printf("icmp.icmp_seq=%d\n", ntohs(icmphdr->un.echo.sequence));
+}
+
+static int check_icmp_reply(void *etherbuf, size_t len, uint16_t id, uint16_t sequence)
+{
+	struct ip *iphdr;
+	struct icmphdr *icmphdr;
+
+	if (len < ETH_HLEN + sizeof(struct ip) + sizeof(struct icmphdr)) {
+		if (verbosity > 1)
+			dump_packet("truncated packet", etherbuf, len);
+		return 1;
+	}
+
+	size_t datalen = len - ETH_HLEN - sizeof(struct ip) - sizeof(struct icmphdr);
+
+	iphdr = etherbuf + ETH_HLEN;
+	icmphdr = (struct icmphdr *)(iphdr + 1);
+
+	if (iphdr->ip_hl != 5 || iphdr->ip_v != 4 || iphdr->ip_p != IPPROTO_ICMP) {
+		if (verbosity > 1) {
+			dump_packet("mangled packet", etherbuf, len);
+			print_ip_header(iphdr);
+		}
+		return 1;
+	}
+
+	/* IP checksum */
+	uint16_t ip_cksum = ip_checksum(iphdr, sizeof(*iphdr));
+	if (ip_cksum != 0) {
+		evl_printf("ip checksum error\n");
+		return 1;
+	}
+
+	/* ICMP checksum */
+	uint16_t icmp_cksum = ip_checksum(icmphdr, sizeof(*icmphdr) + datalen);
+	if (icmp_cksum != 0) {
+		evl_printf("icmp checksum error\n");
+		return 1;
+	}
+
+	if (ntohs(icmphdr->un.echo.id) != id) {
+		evl_printf("id mismatch\n");
+		return 1;
+	}
+	if (ntohs(icmphdr->un.echo.sequence) != sequence) {
+		evl_printf("sequence mismatch\n");
+		return 1;
+	}
+
+	if (verbosity > 1)
+		print_icmp_reply(etherbuf, len);
+
+	return 0;
+}
+
+static size_t build_icmp_request(uint8_t *o_frame, size_t icmplen,
+				 const struct in_addr *dst_ip,
+				 const struct in_addr *src_ip,
+				 const struct ether_addr *dst_mac,
+				 const struct ether_addr *src_mac,
+				 uint16_t id,
+				 uint16_t sequence)
+{
+	struct icmphdr *icmphdr;
+	struct ip *iphdr;
+	uint16_t cksum;
+	size_t datalen;
+	size_t pktlen;
+	uint8_t *data;
+
+	pktlen = icmplen + ETH_HLEN + sizeof(*iphdr);
+	memset(o_frame, 0, pktlen);
+
+	iphdr = (struct ip *)(o_frame + ETH_HLEN);
+	icmphdr = (struct icmphdr *)(iphdr + 1);
+	data = (uint8_t *)(icmphdr + 1);
+	if (icmplen < sizeof(*icmphdr)) {
+		evl_printf("icmplen to short\n");
+		return 0;
+	}
+	datalen = icmplen - sizeof(*icmphdr);
+
+	/* MAC */
+	memcpy(o_frame, dst_mac, ETH_ALEN);
+	memcpy(o_frame + ETH_ALEN, src_mac, ETH_ALEN);
+	o_frame[ETH_HLEN - 2] = (ETH_P_IP >> 8) & 0xff;
+	o_frame[ETH_HLEN - 1] = ETH_P_IP & 0xff;
+
+	/* IPv4 */
+	iphdr->ip_hl = 5;
+	iphdr->ip_v = 4;
+	iphdr->ip_tos = 0;
+	iphdr->ip_len = htons(pktlen - ETH_HLEN);
+	iphdr->ip_id = htons(sequence + id + 0x1000);
+	iphdr->ip_off = htons(IP_DF);
+	iphdr->ip_ttl = 64;
+	iphdr->ip_p = IPPROTO_ICMP;
+	memcpy(&iphdr->ip_src, src_ip, sizeof(iphdr->ip_src));
+	memcpy(&iphdr->ip_dst, dst_ip, sizeof(iphdr->ip_dst));
+	iphdr->ip_sum = 0;
+	iphdr->ip_sum = ip_checksum(iphdr, sizeof(*iphdr));
+
+	/* ICMP */
+	icmphdr->type = ICMP_ECHO;
+	icmphdr->code = 0;
+	icmphdr->un.echo.id = htons(id);
+	icmphdr->un.echo.sequence = htons(sequence);
+	icmphdr->checksum = 0;
+
+	/* data */
+	for (size_t i = 0; i < datalen; i++)
+		data[i] = i + '0';
+
+	/* ICMP checksum */
+	cksum = ip_checksum(icmphdr, sizeof(*icmphdr) + datalen);
+	icmphdr->checksum = cksum;
+
+	if (verbosity > 1) {
+		evl_printf("ip_len=%zd, icmp_len=%zd, ip_len=%d, datalen=%ld\n",
+			   sizeof(*iphdr), sizeof(struct icmphdr),
+			   ntohs(iphdr->ip_len), datalen);
+		print_ip_header(iphdr);
+		dump_packet("ICMP request", o_frame, pktlen);
+	}
+
+	return pktlen;
+}
+
+static void usage(void)
+{
+	fprintf(stderr, "oob-net-ping -i <network-interface> -a <addr> [-d][-s]\n");
+}
+
+int main(int argc, char *argv[])
+{
+	uint8_t i_frame[ETHER_MAX_LEN], o_frame[ETHER_MAX_LEN];
+	int ret, tfd, s, c, ifindex;
+	const char *netif = NULL;
+	const char *ip_addr = NULL;
+	struct oob_msghdr msghdr;
+	struct sched_param param;
+	struct sockaddr_ll addr;
+	struct sockaddr_in dst_addr, src_addr;
+	struct sockaddr dst_hwaddr, src_hwaddr;
+	struct arpreq arp_request;
+	struct ifreq ifr;
+	struct iovec iov;
+	struct timespec ts_timeout;
+	struct timespec ts_ping_tx;
+	struct timespec ts_ping_rx;
+	uint16_t id = getpid(); //Use pid as ID randomization
+	uint16_t sequence = 0;
+	ssize_t count;
+	useconds_t delay=1000000;
+
+	while ((c = getopt(argc, argv, "i:a:w:ds")) != EOF) {
+		switch (c) {
+		case 'i':
+			netif = optarg;
+			break;
+		case 'a':
+			ip_addr = optarg;
+			break;
+		case 'w':
+			delay = atoi(optarg);
+			break;
+		case 'd':
+			verbosity = 2;
+			break;
+		case 's':
+			verbosity = 0;
+			break;
+		default:
+			usage();
+			exit(1);
+		}
+	}
+
+	dst_addr.sin_family = AF_INET;
+	dst_addr.sin_port = htons(0);
+	if (!inet_pton(AF_INET, ip_addr, &dst_addr.sin_addr))
+		error(1, EINVAL, "invalid IP address");
+
+	if (netif == NULL || ip_addr == NULL) {
+		usage();
+		exit(2);
+	}
+
+	param.sched_priority = 1;
+	ret = pthread_setschedparam(pthread_self(), SCHED_FIFO, &param);
+	if (ret)
+		error(1, ret, "pthread_setschedparam()");
+
+	tfd = evl_attach_self("oob-net-icmp:%d", getpid());
+	if (tfd < 0)
+		error(1, -tfd, "cannot attach to the EVL core");
+
+	/*
+	 * Get a UDP socket with out-of-band capabilities for solicit.
+	 * Solicit does not work with raw socket (It can not, raw captures all)
+	 */
+	s = socket(AF_INET, SOCK_DGRAM | SOCK_OOB, 0);
+	if (s < 0)
+		error(1, errno, "cannot create drgram packet socket");
+
+	if (netif) {
+		if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, netif, strlen(netif)))
+			error(1, errno, "setsockopt(SO_BINDTODEVICE)");
+		if (verbosity)
+			printf("== bound to %s\n", netif);
+	}
+	/*
+	* Guarantee a mere oob path from the first packet
+	* onward by pre-caching the route and link-layer
+	* address via an explicit neighbour solicitation
+	* before we start sending data.
+	*/
+	ret = evl_net_solicit(s, (const struct sockaddr *)&dst_addr,
+			      EVL_NEIGH_PERMANENT);
+	if (ret)
+		error(1, -ret, "evl_net_solicit()");
+
+	close(s);
+
+	/*
+	 * Get a raw socket with out-of-band capabilities.
+	 */
+	s = socket(AF_PACKET, SOCK_RAW | SOCK_OOB, 0);
+	if (s < 0)
+		error(1, errno, "cannot create raw packet socket");
+
+	/*
+	 * Get ifindex
+	 */
+	memset(&ifr, 0, sizeof(ifr));
+	strncpy(ifr.ifr_name, netif, IFNAMSIZ - 1);
+	ret = ioctl(s, SIOCGIFINDEX, &ifr);
+	if (ret < 0)
+		error(1, errno, "ioctl(SIOCGIFINDEX)");
+
+	ifindex = ifr.ifr_ifindex;
+
+	/*
+	 * Get local mac address
+	 */
+	ret = ioctl(s, SIOCGIFHWADDR, &ifr);
+	if (ret < 0)
+		error(1, errno, "ioctl(SIOCGIFHWADDR)");
+
+	src_hwaddr = ifr.ifr_hwaddr;
+
+	/*
+	 * Get the local ip address
+	 */
+	ret = ioctl(s, SIOCGIFADDR, &ifr);
+	if (ret < 0)
+		error(1, errno, "ioctl(SIOCGIFADDR)");
+
+	src_addr.sin_family = AF_INET;
+	src_addr.sin_port = htons(0);
+	src_addr.sin_addr.s_addr = ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr.s_addr;
+
+	/*
+	 * Get the destination mac address
+	 */
+	memset(&arp_request, 0, sizeof(arp_request));
+	((struct sockaddr_in *)&arp_request.arp_pa)->sin_family = AF_INET;
+	((struct sockaddr_in *)&arp_request.arp_pa)->sin_addr.s_addr = dst_addr.sin_addr.s_addr;
+	memcpy(arp_request.arp_dev, ifr.ifr_name, sizeof(arp_request.arp_dev));
+
+	ret = ioctl(s, SIOCGARP, &arp_request);
+	if (ret < 0)
+		error(1, errno, "ioctl(SIOCGARP)");
+
+	if (arp_request.arp_flags & ATF_COM) {
+		dst_hwaddr = arp_request.arp_ha;
+	} else {
+		error(1, 0, "ARP entry is incomplete or invalid.\n");
+	}
+
+	if (verbosity > 1) {
+		evl_printf("sending via %s (if%d), ip=%s, mac=%.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n",
+			ifr.ifr_name, ifindex, inet_ntoa(src_addr.sin_addr),
+			(uint8_t)src_hwaddr.sa_data[0],
+			(uint8_t)src_hwaddr.sa_data[1],
+			(uint8_t)src_hwaddr.sa_data[2],
+			(uint8_t)src_hwaddr.sa_data[3],
+			(uint8_t)src_hwaddr.sa_data[4],
+			(uint8_t)src_hwaddr.sa_data[5]);
+		evl_printf("sending to ip=%s, mac=%.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n",
+			inet_ntoa(dst_addr.sin_addr),
+			(uint8_t)dst_hwaddr.sa_data[0],
+			(uint8_t)dst_hwaddr.sa_data[1],
+			(uint8_t)dst_hwaddr.sa_data[2],
+			(uint8_t)dst_hwaddr.sa_data[3],
+			(uint8_t)dst_hwaddr.sa_data[4],
+			(uint8_t)dst_hwaddr.sa_data[5]);
+	}
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sll_ifindex = ifindex;
+	addr.sll_family = AF_PACKET;
+	addr.sll_protocol = htons(ETH_P_ALL);
+	ret = bind(s, (struct sockaddr *)&addr, sizeof(addr));
+	if (ret)
+		error(1, errno, "cannot bind packet socket");
+
+	if (verbosity)
+		evl_printf("listening to interface %s\n", netif);
+
+	for (;;) {
+		count = build_icmp_request(o_frame, 64,
+			(struct in_addr *)&dst_addr.sin_addr.s_addr,
+			(struct in_addr *)&src_addr.sin_addr.s_addr,
+			(struct ether_addr *)dst_hwaddr.sa_data,
+			(struct ether_addr *)src_hwaddr.sa_data,
+			id, sequence);
+
+		iov.iov_base = o_frame;
+		iov.iov_len = count;
+		msghdr.msg_iov = &iov;
+		msghdr.msg_iovlen = 1;
+		msghdr.msg_control = NULL;
+		msghdr.msg_controllen = 0;
+		msghdr.msg_name = NULL;
+		msghdr.msg_namelen = 0;
+		msghdr.msg_flags = 0;
+		if (verbosity > 1)
+			evl_printf("  .. ICMP request sent: %zd\n", count);
+
+		evl_read_clock(EVL_CLOCK_MONOTONIC, &ts_ping_tx);
+		count = oob_sendmsg(s, &msghdr, NULL, 0);
+		if (count < 0)
+			error(1, errno, "oob_sendmsg() failed");
+
+		iov.iov_base = i_frame;
+		iov.iov_len = sizeof(i_frame);
+		msghdr.msg_name = &addr;
+		msghdr.msg_namelen = sizeof(addr);
+
+	rx:
+		evl_read_clock(EVL_CLOCK_MONOTONIC, &ts_timeout);
+		ts_timeout.tv_sec += 1;
+		count = oob_recvmsg(s, &msghdr, &ts_timeout, 0);
+		evl_read_clock(EVL_CLOCK_MONOTONIC, &ts_ping_rx);
+		if (count < 0) {
+			if (errno != ETIMEDOUT) {
+				error(1, errno, "oob_recvmsg() failed");
+			} else {
+				evl_printf("  .. ICMP timeout!\n");
+				goto next;
+			}
+		}
+
+		if (check_icmp_reply(i_frame, count, id, sequence) != 0) {
+			evl_printf("icmp check failed, probably intercepted other package, retry rx\n");
+			goto rx;
+		}
+
+		if (verbosity > 1)
+			evl_printf("  .. ICMP reply recv: %zd\n", count);
+		if (verbosity)
+			evl_printf("[%d] count=%zd, proto=%#hx, ifindex=%d, type=%u, halen=%u, "
+				"mac=%.2x:%.2x:%.2x:%.2x:%.2x:%.2x rtt=%.1fus\n",
+				sequence, count, ntohs(addr.sll_protocol),
+				addr.sll_ifindex, addr.sll_pkttype,
+				addr.sll_halen, (uint8_t)addr.sll_addr[0],
+				(uint8_t)addr.sll_addr[1],
+				(uint8_t)addr.sll_addr[2],
+				(uint8_t)addr.sll_addr[3],
+				(uint8_t)addr.sll_addr[4],
+				(uint8_t)addr.sll_addr[5],
+				(ts_ping_rx.tv_sec - ts_ping_tx.tv_sec) * 1000000.0 +
+				(ts_ping_rx.tv_nsec - ts_ping_tx.tv_nsec) / 1000.0);
+next:
+		evl_usleep(delay);
+		sequence++;
+	}
+
+	return 0;
+}
-- 
2.47.3


  reply	other threads:[~2026-06-28 15:40 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-28 15:39 [PATCH 0/1] tidbits: net-ping: add ping tidbit using raw socket Hannes Diethelm
2026-06-28 15:39 ` Hannes Diethelm [this message]
2026-07-05 14:57   ` [PATCH 1/1] " Philippe Gerum
2026-07-05 15:27     ` Hannes Diethelm
2026-07-05 16:52       ` Philippe Gerum
2026-07-05 15:01 ` [PATCH 0/1] " Philippe Gerum
2026-07-05 15:21   ` Hannes Diethelm
2026-07-05 16:49     ` Philippe Gerum
2026-07-05 22:17       ` Hannes Diethelm
2026-07-05 23:57         ` Hannes Diethelm

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260628153956.1435330-2-hannes.diethelm@gmail.com \
    --to=hannes.diethelm@gmail.com \
    --cc=rpm@xenomai.org \
    --cc=xenomai@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.