blob: 063628f711e48f19adcc8e0f9e5d612c391e06e3 [file] [log] [blame]
/* SPDX-License-Identifier: BSD-3-Clause */
/*
* Copyright (c) 2021-2022, 2024 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
#define inet_aton slirp_inet_aton
#else
#include <sys/socket.h>
#include <arpa/inet.h>
#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 %zu:\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 slirp_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 {
SlirpTimerId id;
void *cb_opaque;
int64_t expire;
struct timer *next;
};
static struct timer *timer_queue;
static void *timer_new_opaque(SlirpTimerId id, void *cb_opaque, void *opaque) {
struct timer *new_timer = malloc(sizeof(*new_timer));
new_timer->id = id;
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(Slirp *slirp) {
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;
slirp_handle_timer(slirp, t->id, 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_socket(slirp_os_socket fd, void *opaque) {
/* We might want to prepare for polling on fd */
npoll++;
}
static void unregister_poll_socket(slirp_os_socket 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 unsigned int maxfd;
static int add_poll_cb(slirp_os_socket 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_socket(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(slirp_os_socket 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_socket(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_opaque = timer_new_opaque,
.timer_free = timer_free,
.timer_mod = timer_mod,
.register_poll_socket = register_poll_socket,
.unregister_poll_socket = unregister_poll_socket,
.notify = notify,
};
int main(int argc, char *argv[]) {
SlirpConfig config = {
.version = 4,
.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(slirp);
/* 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);
}