aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSamuel Thibault <samuel.thibault@ens-lyon.org>2021-11-11 23:48:41 +0000
committerSamuel Thibault <samuel.thibault@ens-lyon.org>2021-11-11 23:48:41 +0000
commit67b4b90a55815b8717af71c154fc7d0ad7377c16 (patch)
treef7e1b761be4f98be48fab3518cde269562120550
parent8b6ebdfd536d7bfdc4832e1bd686274b23e9ccee (diff)
parenta502010c1c8814c69d02e4449ca87b7115e2727f (diff)
downloadslirp-67b4b90a55815b8717af71c154fc7d0ad7377c16.zip
slirp-67b4b90a55815b8717af71c154fc7d0ad7377c16.tar.gz
slirp-67b4b90a55815b8717af71c154fc7d0ad7377c16.tar.bz2
Merge branch 'pingtest' into 'master'
pingtest: Add a trivial ping test See merge request slirp/libslirp!91
-rw-r--r--meson.build8
-rw-r--r--test/pingtest.c488
2 files changed, 496 insertions, 0 deletions
diff --git a/meson.build b/meson.build
index cb1396a..37d3bf4 100644
--- a/meson.build
+++ b/meson.build
@@ -136,6 +136,14 @@ lib = library('slirp', sources,
install : install_devel or get_option('default_library') == 'shared',
)
+pingtest = executable('pingtest', 'test/pingtest.c',
+ link_with: [ lib ],
+ include_directories: [ 'src' ],
+ dependencies : [ platform_deps ]
+)
+
+test('ping', pingtest)
+
if install_devel
install_headers(['src/libslirp.h'], subdir : 'slirp')
diff --git a/test/pingtest.c b/test/pingtest.c
new file mode 100644
index 0000000..15249f0
--- /dev/null
+++ b/test/pingtest.c
@@ -0,0 +1,488 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/*
+ * Copyright (c) 2021 Samuel Thibault
+ */
+
+/*
+ * This simple test configures slirp and tries to ping it
+ *
+ * Note: to make this example actually be able to use the outside world, you
+ * need to either
+ * - run as root
+ * - set /proc/sys/net/ipv4/ping_group_range to allow sending ICMP echo requests
+ * - run a UDP echo server on the target
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <assert.h>
+
+#include "libslirp.h"
+
+//#define _WIN32
+#ifdef _WIN32
+//#include <sys/select.h>
+#include <winsock2.h>
+int slirp_inet_aton(const char *cp, struct in_addr *ia)
+{
+ uint32_t addr = inet_addr(cp);
+ if (addr == 0xffffffff) {
+ return 0;
+ }
+ ia->s_addr = addr;
+ return 1;
+}
+#define inet_aton slirp_inet_aton
+#else
+#include <poll.h>
+#endif
+
+/* Dumb simulation tick: 100ms */
+#define TICK 100
+
+static Slirp *slirp;
+static bool done;
+static int64_t mytime;
+
+/* Print a frame for debugging */
+static void print_frame(const uint8_t *data, size_t len) {
+ int i;
+
+ printf("\ngot packet size %zd:\n", len);
+ for (i = 0; i < len; i++) {
+ if (i && i % 16 == 0)
+ printf("\n");
+ printf("%s%02x", i % 16 ? " " : "", data[i]);
+ }
+ if (len % 16 != 0)
+ printf("\n");
+ printf("\n");
+}
+
+/* Classical 16bit checksum */
+static void checksum(uint8_t *data, size_t size, uint8_t *cksum) {
+ uint32_t sum = 0;
+ int i;
+
+ cksum[0] = 0;
+ cksum[1] = 0;
+
+ for (i = 0; i+1 < size; i += 2)
+ sum += (((uint16_t) data[i]) << 8) + data[i+1];
+ if (i < size) /* Odd number of bytes */
+ sum += ((uint16_t) data[i]) << 8;
+
+ sum = (sum & 0xffff) + (sum >> 16);
+ sum = (sum & 0xffff) + (sum >> 16);
+ sum = ~sum;
+
+ cksum[0] = sum >> 8;
+ cksum[1] = sum;
+}
+
+/* This is called when receiving a packet from the virtual network, for the
+ * guest */
+static ssize_t send_packet(const void *buf, size_t len, void *opaque) {
+ const uint8_t *data = buf;
+
+ assert(len >= 14);
+
+ if (data[12] == 0x86 &&
+ data[13] == 0xdd) {
+ /* Ignore IPv6 */
+ return len;
+ }
+
+ print_frame(data, len);
+
+ if (data[12] == 0x08 &&
+ data[13] == 0x06) {
+ /* ARP */
+ /* We expect receiving an ARP request for our address */
+
+ /* Ethernet address type */
+ assert(data[14] == 0x00);
+ assert(data[15] == 0x01);
+
+ /* IPv4 address type */
+ assert(data[16] == 0x08);
+ assert(data[17] == 0x00);
+
+ /* Ethernet addresses are 6 bytes long */
+ assert(data[18] == 0x06);
+
+ /* IPv4 addresses are 4 bytes long */
+ assert(data[19] == 0x04);
+
+ /* Opcode: ARP request */
+ assert(data[20] == 0x00);
+ assert(data[21] == 0x01);
+
+ /* Ok, reply! */
+ uint8_t myframe[] = {
+ /*** Ethernet ***/
+ /* dst */
+ 0x52, 0x55, 0x0a, 0x00, 0x02, 0x02,
+ /* src */
+ 0x52, 0x55, 0x0a, 0x00, 0x02, 0x0e,
+ /* Type: ARP */
+ 0x08, 0x06,
+
+ /* ether, IPv4, */
+ 0x00, 0x01, 0x08, 0x00,
+ /* elen, IPlen */
+ 0x06, 0x04,
+ /* ARP reply */
+ 0x00, 0x02,
+
+ /* Our ethernet address */
+ 0x52, 0x55, 0x0a, 0x00, 0x02, 0x0e,
+ /* Our IP address */
+ 0x0a, 0x00, 0x02, 0x0e,
+
+ /* Host ethernet address */
+ 0x52, 0x55, 0x0a, 0x00, 0x02, 0x02,
+ /* Host IP address */
+ 0x0a, 0x00, 0x02, 0x02,
+ };
+
+ slirp_input(slirp, myframe, sizeof(myframe));
+ }
+
+ if (data[12] == 0x08 &&
+ data[13] == 0x00) {
+ /* IPv4 */
+ assert(len >= 14 + 20);
+
+ /* We expect receiving the ICMP echo reply for our echo request */
+
+ /* IPv + hlen */
+ assert(data[14] == 0x45);
+
+ /* proto: ICMP */
+ assert(data[23] == 0x01);
+
+ /* ICMP */
+ assert(len >= 14 + 20 + 8 + 4);
+
+ /* ICMP type: reply */
+ assert(data[34] == 0x00);
+
+ /* Check the data */
+ assert(data[42] == 0xde);
+ assert(data[43] == 0xad);
+ assert(data[44] == 0xbe);
+ assert(data[45] == 0xef);
+
+ /* Got the answer! */
+ printf("got it!\n");
+ done = 1;
+ }
+
+ return len;
+}
+
+static void guest_error(const char *msg, void *opaque) {
+ printf("guest error %s\n", msg);
+}
+
+
+/*
+ * Dumb timer implementation
+ */
+static int64_t clock_get_ns(void *opaque) {
+ return mytime;
+}
+
+struct timer {
+ SlirpTimerCb cb;
+ void *cb_opaque;
+ int64_t expire;
+ struct timer *next;
+};
+
+static struct timer *timer_queue;
+
+static void *timer_new(SlirpTimerCb cb, void *cb_opaque, void *opaque) {
+ struct timer *new_timer = malloc(sizeof(*new_timer));
+ new_timer->cb = cb;
+ new_timer->cb_opaque = cb_opaque;
+ new_timer->next = NULL;
+ return new_timer;
+}
+
+static void timer_free(void *_timer, void *opaque) {
+ struct timer *timer = _timer;
+ struct timer **t;
+
+ for (t = &timer_queue; *t != NULL; *t = (*t)->next) {
+ if (*t == timer) {
+ /* Not expired yet, drop it */
+ *t = timer->next;
+ break;
+ }
+ }
+
+ free(timer);
+}
+
+static void timer_mod(void *_timer, int64_t expire_time, void *opaque) {
+ struct timer *timer = _timer;
+ struct timer **t;
+
+ timer->expire = expire_time * 1000 * 1000;
+
+ for (t = &timer_queue; *t != NULL; *t = (*t)->next) {
+ if (expire_time < (*t)->expire)
+ break;
+ }
+
+ timer->next = *t;
+ *t = timer;
+}
+
+static void timer_check(void) {
+ while (timer_queue && timer_queue->expire <= mytime)
+ {
+ struct timer *t = timer_queue;
+ printf("handling %p at time %lu\n",
+ t, (unsigned long) timer_queue->expire);
+ timer_queue = t->next;
+ t->cb(t->cb_opaque);
+ }
+}
+
+static uint32_t timer_timeout(void) {
+ if (timer_queue)
+ {
+ uint32_t timeout = (timer_queue->expire - mytime) / (1000 * 1000);
+ if (timeout < TICK)
+ return timeout;
+ }
+
+ return TICK;
+}
+
+
+/*
+ * Dumb polling implementation
+ */
+static int npoll;
+static void register_poll_fd(int fd, void *opaque) {
+ /* We might want to prepare for polling on fd */
+ npoll++;
+}
+
+static void unregister_poll_fd(int fd, void *opaque) {
+ /* We might want to clear polling on fd */
+ npoll--;
+}
+
+static void notify(void *opaque) {
+ /* No need for this in single-thread case */
+}
+
+#ifdef _WIN32
+/* select() variant */
+static fd_set readfds, writefds, exceptfds;
+static int maxfd;
+static int add_poll_cb(int fd, int events, void *opaque)
+{
+ if (events & SLIRP_POLL_IN)
+ FD_SET(fd, &readfds);
+ if (events & SLIRP_POLL_OUT)
+ FD_SET(fd, &writefds);
+ if (events & SLIRP_POLL_PRI)
+ FD_SET(fd, &exceptfds);
+ if (maxfd < fd)
+ maxfd = fd;
+ return fd;
+}
+
+static int get_revents_cb(int idx, void *opaque)
+{
+ int event = 0;
+ if (FD_ISSET(idx, &readfds))
+ event |= SLIRP_POLL_IN;
+ if (FD_ISSET(idx, &writefds))
+ event |= SLIRP_POLL_OUT;
+ if (FD_ISSET(idx, &exceptfds))
+ event |= SLIRP_POLL_PRI;
+ return event;
+}
+
+static void dopoll(uint32_t timeout) {
+ int err;
+ FD_ZERO(&readfds);
+ FD_ZERO(&writefds);
+ FD_ZERO(&exceptfds);
+ maxfd = 0;
+
+ slirp_pollfds_fill(slirp, &timeout, add_poll_cb, NULL);
+ printf("we will use timeout %u\n", (unsigned) timeout);
+
+ struct timeval tv = {
+ .tv_sec = timeout / 1000,
+ .tv_usec = (timeout % 1000) * 1000,
+ };
+ err = select(maxfd+1, &readfds, &writefds, &exceptfds, &tv);
+
+ slirp_pollfds_poll(slirp, err < 0, get_revents_cb, NULL);
+}
+#else
+/* poll() variant */
+static struct pollfd *fds;
+static int cur_poll;
+static int add_poll_cb(int fd, int events, void *opaque)
+{
+ short poll_events = 0;
+
+ assert(cur_poll < npoll);
+ fds[cur_poll].fd = fd;
+
+ if (events & SLIRP_POLL_IN)
+ poll_events |= POLLIN;
+ if (events & SLIRP_POLL_OUT)
+ poll_events |= POLLOUT;
+ if (events & SLIRP_POLL_PRI)
+ poll_events |= POLLPRI;
+ fds[cur_poll].events = poll_events;
+
+ return cur_poll++;
+}
+
+static int get_revents_cb(int idx, void *opaque)
+{
+ return fds[idx].revents;
+}
+
+static void dopoll(uint32_t timeout) {
+ int err;
+ fds = malloc(sizeof(*fds) * npoll);
+ cur_poll = 0;
+
+ slirp_pollfds_fill(slirp, &timeout, add_poll_cb, NULL);
+ printf("we will use timeout %u\n", (unsigned) timeout);
+
+ err = poll(fds, cur_poll, timeout);
+
+ slirp_pollfds_poll(slirp, err < 0, get_revents_cb, NULL);
+
+ free(fds);
+}
+#endif
+
+
+static struct SlirpCb callbacks = {
+ .send_packet = send_packet,
+ .guest_error = guest_error,
+ .clock_get_ns = clock_get_ns,
+ .timer_new = timer_new,
+ .timer_free = timer_free,
+ .timer_mod = timer_mod,
+ .register_poll_fd = register_poll_fd,
+ .unregister_poll_fd = unregister_poll_fd,
+ .notify = notify,
+};
+
+
+int main(int argc, char *argv[]) {
+ SlirpConfig config = {
+ .version = 3,
+ .restricted = false,
+ .in_enabled = true,
+ .vnetwork.s_addr = htonl(0x0a000200),
+ .vnetmask.s_addr = htonl(0xffffff00),
+ .vhost.s_addr = htonl(0x0a000202),
+ .vdhcp_start.s_addr = htonl(0x0a00020f),
+ .vnameserver.s_addr = htonl(0x0a000203),
+ .disable_host_loopback = false,
+ .enable_emu = false,
+ .disable_dns = false,
+ };
+ uint32_t timeout = 0;
+
+ printf("Slirp version %s\n", slirp_version_string());
+
+#if !defined(_WIN32)
+ inet_pton(AF_INET6, "fec0::", &config.vprefix_addr6);
+ config.vprefix_len = 64;
+ config.vhost6 = config.vprefix_addr6;
+ config.vhost6.s6_addr[15] = 2;
+ config.vnameserver6 = config.vprefix_addr6;
+ config.vnameserver6.s6_addr[15] = 2;
+ config.in6_enabled = true,
+#endif
+
+ slirp = slirp_new(&config, &callbacks, NULL);
+
+ /* Send echo request */
+ uint8_t myframe[] = {
+ /*** Ethernet ***/
+ /* dst */
+ 0x52, 0x55, 0x0a, 0x00, 0x02, 0x02,
+ /* src */
+ 0x52, 0x55, 0x0a, 0x00, 0x02, 0x0e,
+ /* Type: IPv4 */
+ 0x08, 0x00,
+
+ /*** IPv4 ***/
+ /* vhl,tos, len */
+ 0x45, 0x00, 0x00, 0x20,
+ /* id, off (DF) */
+ 0x68, 0xd7, 0x40, 0x00,
+ /* ttl,pro, cksum */
+ 0x40, 0x01, 0x00, 0x00,
+ /* src */
+ 0x0a, 0x00, 0x02, 0x0e,
+ /* dst */
+ 0x00, 0x00, 0x00, 0x00,
+
+ /*** ICMPv4 ***/
+ /* type, code, cksum */
+ 0x08, 0x00, 0x00, 0x00,
+ /* id, seq */
+ 0x01, 0xec, 0x00, 0x01,
+ /* data */
+ 0xde, 0xad, 0xbe, 0xef,
+ };
+
+ struct in_addr in_addr = { .s_addr = htonl(0x0a000202) };
+ if (argc > 1) {
+ if (inet_aton(argv[1], &in_addr) == 0) {
+ printf("usage: %s [destination IPv4 address]\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+ }
+ uint32_t addr = ntohl(in_addr.s_addr);
+ myframe[30] = addr >> 24;
+ myframe[31] = addr >> 16;
+ myframe[32] = addr >> 8;
+ myframe[33] = addr >> 0;
+
+ /* IPv4 header checksum */
+ checksum(&myframe[14], 20, &myframe[24]);
+ /* ICMP header checksum */
+ checksum(&myframe[34], 12, &myframe[36]);
+
+ slirp_input(slirp, myframe, sizeof(myframe));
+
+ /* Wait for echo reply */
+ while (!done) {
+ printf("time %lu\n", (unsigned long) mytime);
+
+ timer_check();
+ /* Here we make the virtual time wait like the real time, but we could
+ * make it wait differently */
+ timeout = timer_timeout();
+ printf("we wish timeout %u\n", (unsigned) timeout);
+
+ dopoll(timeout);
+
+ /* Fake that the tick elapsed */
+ mytime += TICK * 1000 * 1000;
+ }
+
+ slirp_cleanup(slirp);
+}