Merge branch 'pingtest' into 'master'

pingtest: Add a trivial ping test

See merge request slirp/libslirp!91
diff --git a/meson.build b/meson.build
index cb1396a..37d3bf4 100644
--- a/meson.build
+++ b/meson.build
@@ -136,6 +136,14 @@
   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);
+}