diff options
author | Nicholas Ngai <nicholas@ngai.me> | 2021-11-29 16:22:00 -0800 |
---|---|---|
committer | Nicholas Ngai <nicholas@ngai.me> | 2021-11-29 16:22:00 -0800 |
commit | bdcd7db57a8d3dbbb6c59cff00aa922d71cd7728 (patch) | |
tree | b63a07000aafc10fa5fedc11e1a8f15372de6022 | |
parent | d2eb3b45aae71b3831fbf658d18b78b53eba23f9 (diff) | |
parent | 5bf953e2f92d5c426c2a6b5db5e95b6bdf1f439b (diff) | |
download | slirp-bdcd7db57a8d3dbbb6c59cff00aa922d71cd7728.zip slirp-bdcd7db57a8d3dbbb6c59cff00aa922d71cd7728.tar.gz slirp-bdcd7db57a8d3dbbb6c59cff00aa922d71cd7728.tar.bz2 |
Merge branch 'master' into hostfwd-unix
Signed-off-by: Nicholas Ngai <nicholas@ngai.me>
-rw-r--r-- | .gitlab-ci.yml | 33 | ||||
-rw-r--r-- | meson.build | 8 | ||||
-rw-r--r-- | src/bootp.c | 2 | ||||
-rw-r--r-- | src/ip_icmp.c | 63 | ||||
-rw-r--r-- | src/libslirp.h | 9 | ||||
-rw-r--r-- | src/slirp.c | 31 | ||||
-rw-r--r-- | src/slirp.h | 1 | ||||
-rw-r--r-- | src/socket.c | 5 | ||||
-rw-r--r-- | src/socket.h | 5 | ||||
-rw-r--r-- | src/state.c | 2 | ||||
-rw-r--r-- | src/tcp_input.c | 2 | ||||
-rw-r--r-- | src/tcp_subr.c | 2 | ||||
-rw-r--r-- | src/udp.c | 4 | ||||
-rw-r--r-- | src/udp6.c | 2 | ||||
-rw-r--r-- | test/pingtest.c | 488 |
15 files changed, 613 insertions, 44 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 46e58a9..2512ef1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -41,3 +41,36 @@ Coverity: --form token=$COVERITY_SCAN_TOKEN --form email=$GITLAB_USER_EMAIL --form file=@cov-int.tar.gz --form version="`git describe --tags`" --form description="`git describe --tags` / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID " + +integration-slirp4netns: + variables: + SLIRP4NETNS_VERSION: "v1.1.12" + # Consumed by `make benchmark` + BENCHMARK_IPERF3_DURATION: "10" + script: + # Install libslirp + - meson build + - ninja -C build install + # Register the path of libslirp.so.0 + - echo /usr/local/lib64 >/etc/ld.so.conf.d/libslirp.conf + - ldconfig + # Install the dependencies of slirp4netns and its test suite + # TODO: install udhcpc for `slirp4netns/tests/test-slirp4netns-dhcp.sh` (currently skipped, due to lack of udhcpc) + - dnf install -y autoconf automake findutils iperf3 iproute iputils jq libcap-devel libseccomp-devel nmap-ncat util-linux + # Check whether the runner environment is configured correctly + - unshare -rn true || (echo Make sure you have relaxed seccomp and appamor && exit 1) + - unshare -rn ip tap add tap0 mode tap || (echo Make sure you have /dev/net/tun && exit 1) + # Install slirp4netns + - git clone https://github.com/rootless-containers/slirp4netns -b "${SLIRP4NETNS_VERSION}" + - cd slirp4netns + - ./autogen.sh + - ./configure + - make + - make install + - slirp4netns --version + # Run slirp4netns integration test + - make distcheck || (cat $(find . -name 'test-suite.log' ) && exit 1) + # Run benchmark test to ensure that libslirp can actually handle packets, with several MTU configurations + - make benchmark MTU=1500 + - make benchmark MTU=512 + - make benchmark MTU=65520 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/src/bootp.c b/src/bootp.c index d78d61b..1a3517e 100644 --- a/src/bootp.c +++ b/src/bootp.c @@ -369,7 +369,7 @@ void bootp_input(struct mbuf *m) { struct bootp_t *bp = mtod_check(m, sizeof(struct bootp_t)); - if (bp && bp->bp_op == BOOTP_REQUEST) { + if (!m->slirp->disable_dhcp && bp && bp->bp_op == BOOTP_REQUEST) { bootp_reply(m->slirp, bp, m_end(m)); } } diff --git a/src/ip_icmp.c b/src/ip_icmp.c index 9fba653..4e47d51 100644 --- a/src/ip_icmp.c +++ b/src/ip_icmp.c @@ -91,8 +91,32 @@ static int icmp_send(struct socket *so, struct mbuf *m, int hlen) struct ip *ip = mtod(m, struct ip *); struct sockaddr_in addr; + /* + * The behavior of reading SOCK_DGRAM+IPPROTO_ICMP sockets is inconsistent + * between host OSes. On Linux, only the ICMP header and payload is + * included. On macOS/Darwin, the socket acts like a raw socket and + * includes the IP header as well. On other BSDs, SOCK_DGRAM+IPPROTO_ICMP + * sockets aren't supported at all, so we treat them like raw sockets. It + * isn't possible to detect this difference at runtime, so we must use an + * #ifdef to determine if we need to remove the IP header. + */ +#ifdef CONFIG_BSD + so->so_type = IPPROTO_IP; +#else + so->so_type = IPPROTO_ICMP; +#endif + so->s = slirp_socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); if (so->s == -1) { + if (errno == EAFNOSUPPORT + || errno == EPROTONOSUPPORT + || errno == EACCES) { + /* Kernel doesn't support or allow ping sockets. */ + so->so_type = IPPROTO_IP; + so->s = slirp_socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + } + } + if (so->s == -1) { return -1; } so->slirp->cb->register_poll_fd(so->s, so->slirp->opaque); @@ -108,7 +132,6 @@ static int icmp_send(struct socket *so, struct mbuf *m, int hlen) so->so_faddr = ip->ip_dst; so->so_laddr = ip->ip_src; so->so_iptos = ip->ip_tos; - so->so_type = IPPROTO_ICMP; so->so_state = SS_ISFCONNECTED; so->so_expire = curtime + SO_EXPIRE; @@ -184,7 +207,7 @@ void icmp_input(struct mbuf *m, int hlen) struct sockaddr_storage addr; int ttl; - so = socreate(slirp); + so = socreate(slirp, IPPROTO_ICMP); if (icmp_send(so, m, hlen) == 0) { /* We could send this as ICMP, good! */ return; @@ -208,7 +231,6 @@ void icmp_input(struct mbuf *m, int hlen) so->so_laddr = ip->ip_src; so->so_lport = htons(9); so->so_iptos = ip->ip_tos; - so->so_type = IPPROTO_ICMP; so->so_state = SS_ISFCONNECTED; /* Send the packet */ @@ -478,31 +500,24 @@ void icmp_receive(struct socket *so) id = icp->icmp_id; len = recv(so->s, icp, M_ROOM(m), 0); - /* - * The behavior of reading SOCK_DGRAM+IPPROTO_ICMP sockets is inconsistent - * between host OSes. On Linux, only the ICMP header and payload is - * included. On macOS/Darwin, the socket acts like a raw socket and - * includes the IP header as well. On other BSDs, SOCK_DGRAM+IPPROTO_ICMP - * sockets aren't supported at all, so we treat them like raw sockets. It - * isn't possible to detect this difference at runtime, so we must use an - * #ifdef to determine if we need to remove the IP header. - */ -#ifdef CONFIG_BSD - if (len >= sizeof(struct ip)) { - struct ip *inner_ip = mtod(m, struct ip *); - int inner_hlen = inner_ip->ip_hl << 2; - if (inner_hlen > len) { + + if (so->so_type == IPPROTO_IP) { + if (len >= sizeof(struct ip)) { + struct ip *inner_ip = mtod(m, struct ip *); + int inner_hlen = inner_ip->ip_hl << 2; + if (inner_hlen > len) { + len = -1; + errno = -EINVAL; + } else { + len -= inner_hlen; + memmove(icp, (unsigned char *)icp + inner_hlen, len); + } + } else { len = -1; errno = -EINVAL; - } else { - len -= inner_hlen; - memmove(icp, (unsigned char *)icp + inner_hlen, len); } - } else { - len = -1; - errno = -EINVAL; } -#endif + icp->icmp_id = id; m->m_data -= hlen; diff --git a/src/libslirp.h b/src/libslirp.h index 5760d53..1e75501 100644 --- a/src/libslirp.h +++ b/src/libslirp.h @@ -68,12 +68,13 @@ typedef struct SlirpCb { void (*register_poll_fd)(int fd, void *opaque); /* Unregister a fd */ void (*unregister_poll_fd)(int fd, void *opaque); - /* Kick the io-thread, to signal that new events may be processed */ + /* Kick the io-thread, to signal that new events may be processed because some TCP buffer + * can now receive more data, i.e. slirp_socket_can_recv will return 1. */ void (*notify)(void *opaque); } SlirpCb; #define SLIRP_CONFIG_VERSION_MIN 1 -#define SLIRP_CONFIG_VERSION_MAX 3 +#define SLIRP_CONFIG_VERSION_MAX 4 typedef struct SlirpConfig { /* Version must be provided */ @@ -119,6 +120,10 @@ typedef struct SlirpConfig { * Fields introduced in SlirpConfig version 3 begin */ bool disable_dns; /* slirp will not redirect/serve any DNS packet */ + /* + * Fields introduced in SlirpConfig version 4 begin + */ + bool disable_dhcp; /* slirp will not reply to any DHCP requests */ } SlirpConfig; /* Create a new instance of a slirp stack */ diff --git a/src/slirp.c b/src/slirp.c index a669b45..568064f 100644 --- a/src/slirp.c +++ b/src/slirp.c @@ -143,6 +143,10 @@ static int get_dns_addr_libresolv(int af, void *pdns_addr, void *cached_addr, union res_sockaddr_union servers[NI_MAXSERV]; int count; int found; + void *addr; + + // we only support IPv4 and IPv4, we assume it's one or the other + assert(af == AF_INET || af == AF_INET6); if (res_ninit(&state) != 0) { return -1; @@ -155,11 +159,16 @@ static int get_dns_addr_libresolv(int af, void *pdns_addr, void *cached_addr, if (af == servers[i].sin.sin_family) { found++; } + if (af == AF_INET) { + addr = &servers[i].sin.sin_addr; + } else { // af == AF_INET6 + addr = &servers[i].sin6.sin6_addr; + } // we use the first found entry if (found == 1) { - memcpy(pdns_addr, &servers[i].sin.sin_addr, addrlen); - memcpy(cached_addr, &servers[i].sin.sin_addr, addrlen); + memcpy(pdns_addr, addr, addrlen); + memcpy(cached_addr, addr, addrlen); if (scope_id) { *scope_id = 0; } @@ -171,10 +180,7 @@ static int get_dns_addr_libresolv(int af, void *pdns_addr, void *cached_addr, break; } else if (slirp_debug & DBG_MISC) { char s[INET6_ADDRSTRLEN]; - const char *res = inet_ntop(servers[i].sin.sin_family, - &servers[i].sin.sin_addr, - s, - sizeof(s)); + const char *res = inet_ntop(af, addr, s, sizeof(s)); if (!res) { res = " (string conversion error)"; } @@ -451,6 +457,12 @@ Slirp *slirp_new(const SlirpConfig *cfg, const SlirpCb *callbacks, void *opaque) slirp->disable_dns = false; } + if (cfg->version >= 4) { + slirp->disable_dhcp = cfg->disable_dhcp; + } else { + slirp->disable_dhcp = false; + } + return slirp; } @@ -607,7 +619,10 @@ void slirp_pollfds_fill(Slirp *slirp, uint32_t *timeout, /* * Set for reading (and urgent data) if we are connected, can - * receive more, and we have room for it XXX /2 ? + * receive more, and we have room for it. + * + * If sb is already half full, we will wait for the guest to consume it, + * and notify again in sbdrop() when the sb becomes less than half full. */ if (CONN_CANFRCV(so) && (so->so_snd.sb_cc < (so->so_snd.sb_datalen / 2))) { @@ -1353,6 +1368,8 @@ size_t slirp_socket_can_recv(Slirp *slirp, struct in_addr guest_addr, } if (!CONN_CANFRCV(so) || so->so_snd.sb_cc >= (so->so_snd.sb_datalen / 2)) { + /* If the sb is already half full, we will wait for the guest to consume it, + * and notify again in sbdrop() when the sb becomes less than half full. */ return 0; } diff --git a/src/slirp.h b/src/slirp.h index 89d79f3..64ab0dd 100644 --- a/src/slirp.h +++ b/src/slirp.h @@ -139,6 +139,7 @@ struct Slirp { struct in6_addr vprefix_addr6; uint8_t vprefix_len; struct in6_addr vhost_addr6; + bool disable_dhcp; /* slirp will not reply to any DHCP requests */ struct in_addr vdhcp_startaddr; struct in_addr vnameserver_addr; struct in6_addr vnameserver_addr6; diff --git a/src/socket.c b/src/socket.c index a92cfd3..a170676 100644 --- a/src/socket.c +++ b/src/socket.c @@ -43,11 +43,12 @@ struct socket *solookup(struct socket **last, struct socket *head, * It is the responsibility of the caller to * insque() it into the correct linked-list */ -struct socket *socreate(Slirp *slirp) +struct socket *socreate(Slirp *slirp, int type) { struct socket *so = g_new(struct socket, 1); memset(so, 0, sizeof(struct socket)); + so->so_type = type; so->so_state = SS_NOFDREF; so->s = -1; so->s_aux = -1; @@ -814,7 +815,7 @@ struct socket *tcpx_listen(Slirp *slirp, */ g_assert(!((flags & SS_HOSTFWD) && (flags & SS_FACCEPTONCE))); - so = socreate(slirp); + so = socreate(slirp, IPPROTO_TCP); /* Don't tcp_attach... we don't need so_snd nor so_rcv */ so->so_tcpcb = tcp_newtcpcb(so); diff --git a/src/socket.h b/src/socket.h index ac77c5e..ca8c103 100644 --- a/src/socket.h +++ b/src/socket.h @@ -72,7 +72,8 @@ struct socket { uint8_t so_iptos; /* Type of service */ uint8_t so_emu; /* Is the socket emulated? */ - uint8_t so_type; /* Type of socket, UDP or TCP */ + uint8_t so_type; /* Protocol of the socket. May be 0 if loading old + * states. */ int32_t so_state; /* internal state flags SS_*, below */ struct tcpcb *so_tcpcb; /* pointer to TCP protocol control block */ @@ -176,7 +177,7 @@ static inline void sockaddr_copy(struct sockaddr *dst, socklen_t dstlen, const s struct socket *solookup(struct socket **, struct socket *, struct sockaddr_storage *, struct sockaddr_storage *); -struct socket *socreate(Slirp *); +struct socket *socreate(Slirp *, int); void sofree(struct socket *); int soread(struct socket *); int sorecvoob(struct socket *); diff --git a/src/state.c b/src/state.c index 22af77b..8708547 100644 --- a/src/state.c +++ b/src/state.c @@ -344,7 +344,7 @@ int slirp_state_load(Slirp *slirp, int version_id, SlirpReadCb read_cb, while (slirp_istream_read_u8(&f)) { int ret; - struct socket *so = socreate(slirp); + struct socket *so = socreate(slirp, -1); ret = slirp_vmstate_load_state(&f, &vmstate_slirp_socket, so, version_id); diff --git a/src/tcp_input.c b/src/tcp_input.c index 45706fe..1d72479 100644 --- a/src/tcp_input.c +++ b/src/tcp_input.c @@ -423,7 +423,7 @@ findso: if ((tiflags & (TH_SYN | TH_FIN | TH_RST | TH_URG | TH_ACK)) != TH_SYN) goto dropwithreset; - so = socreate(slirp); + so = socreate(slirp, IPPROTO_TCP); tcp_attach(so); sbreserve(&so->so_snd, TCP_SNDSPACE); diff --git a/src/tcp_subr.c b/src/tcp_subr.c index d1a95f0..8feff5b 100644 --- a/src/tcp_subr.c +++ b/src/tcp_subr.c @@ -524,7 +524,7 @@ void tcp_connect(struct socket *inso) /* FACCEPTONCE already have a tcpcb */ so = inso; } else { - so = socreate(slirp); + so = socreate(slirp, IPPROTO_TCP); tcp_attach(so); so->lhost = inso->lhost; so->so_ffamily = inso->so_ffamily; @@ -177,7 +177,7 @@ void udp_input(register struct mbuf *m, int iphlen) * If there's no socket for this packet, * create one */ - so = socreate(slirp); + so = socreate(slirp, IPPROTO_UDP); if (udp_attach(so, AF_INET) == -1) { DEBUG_MISC(" udp_attach errno = %d-%s", errno, strerror(errno)); sofree(so); @@ -374,7 +374,7 @@ struct socket *udpx_listen(Slirp *slirp, socklen_t addrlen; int save_errno; - so = socreate(slirp); + so = socreate(slirp, IPPROTO_UDP); so->s = slirp_socket(haddr->sa_family, SOCK_DGRAM, 0); if (so->s < 0) { save_errno = errno; @@ -95,7 +95,7 @@ void udp6_input(struct mbuf *m) if (so == NULL) { /* If there's no socket for this packet, create one. */ - so = socreate(slirp); + so = socreate(slirp, IPPROTO_UDP); if (udp_attach(so, AF_INET6) == -1) { DEBUG_MISC(" udp6_attach errno = %d-%s", errno, strerror(errno)); sofree(so); 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); +} |