aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicholas Ngai <nicholas@ngai.me>2021-11-29 16:22:00 -0800
committerNicholas Ngai <nicholas@ngai.me>2021-11-29 16:22:00 -0800
commitbdcd7db57a8d3dbbb6c59cff00aa922d71cd7728 (patch)
treeb63a07000aafc10fa5fedc11e1a8f15372de6022
parentd2eb3b45aae71b3831fbf658d18b78b53eba23f9 (diff)
parent5bf953e2f92d5c426c2a6b5db5e95b6bdf1f439b (diff)
downloadslirp-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.yml33
-rw-r--r--meson.build8
-rw-r--r--src/bootp.c2
-rw-r--r--src/ip_icmp.c63
-rw-r--r--src/libslirp.h9
-rw-r--r--src/slirp.c31
-rw-r--r--src/slirp.h1
-rw-r--r--src/socket.c5
-rw-r--r--src/socket.h5
-rw-r--r--src/state.c2
-rw-r--r--src/tcp_input.c2
-rw-r--r--src/tcp_subr.c2
-rw-r--r--src/udp.c4
-rw-r--r--src/udp6.c2
-rw-r--r--test/pingtest.c488
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;
diff --git a/src/udp.c b/src/udp.c
index 48bd8ae..74013b1 100644
--- a/src/udp.c
+++ b/src/udp.c
@@ -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;
diff --git a/src/udp6.c b/src/udp6.c
index efeac5c..effdf77 100644
--- a/src/udp6.c
+++ b/src/udp6.c
@@ -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);
+}