| /* 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 = SLIRP_CONFIG_VERSION_MAX, | |
| .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); | |
| } |