/* * 86Box A hypervisor and IBM PC system emulator that specializes in * running old operating systems and software designed for IBM * PC systems and compatibles from 1981 through fairly recent * system designs based on the PCI bus. * * This file is part of the 86Box distribution. * * Handle SLiRP library processing. * * Some of the code was borrowed from libvdeslirp * * * * * Authors: Fred N. van Kempen, * RichardG, * * Copyright 2017-2019 Fred N. van Kempen. * Copyright 2020 RichardG. */ #include #include #include #include #include #include #include #define HAVE_STDARG_H #include <86box/86box.h> #include <86box/device.h> #include <86box/plat.h> #include <86box/network.h> #include <86box/machine.h> #include <86box/timer.h> #include <86box/config.h> /* SLiRP can use poll() or select() for socket polling. While poll() is better, it's slow and limited on Windows, so use select() there. */ #ifndef _WIN32 # define SLIRP_USE_POLL 1 #endif #ifdef SLIRP_USE_POLL # ifdef _WIN32 # include # define poll WSAPoll # else # include # endif #endif typedef struct { Slirp *slirp; void *mac; const netcard_t *card; /* netcard attached to us */ volatile thread_t *poll_tid; event_t *poll_state; uint8_t stop; #ifdef SLIRP_USE_POLL uint32_t pfd_len, pfd_size; struct pollfd *pfd; #else uint32_t nfds; fd_set rfds, wfds, xfds; #endif } slirp_t; static slirp_t *slirp; #ifdef ENABLE_SLIRP_LOG int slirp_do_log = ENABLE_SLIRP_LOG; static void slirp_log(const char *fmt, ...) { va_list ap; if (slirp_do_log) { va_start(ap, fmt); pclog_ex(fmt, ap); va_end(ap); } } #else #define slirp_log(fmt, ...) #endif static void net_slirp_guest_error(const char *msg, void *opaque) { slirp_log("SLiRP: guest_error: %s\n", msg); } static int64_t net_slirp_clock_get_ns(void *opaque) { return (TIMER_USEC ? (tsc / (TIMER_USEC / 1000)) : 0); } static void * net_slirp_timer_new(SlirpTimerCb cb, void *cb_opaque, void *opaque) { pc_timer_t *timer = malloc(sizeof(pc_timer_t)); timer_add(timer, cb, cb_opaque, 0); return timer; } static void net_slirp_timer_free(void *timer, void *opaque) { timer_stop(timer); } static void net_slirp_timer_mod(void *timer, int64_t expire_timer, void *opaque) { timer_set_delay_u64(timer, expire_timer); } static void net_slirp_register_poll_fd(int fd, void *opaque) { (void) fd; (void) opaque; } static void net_slirp_unregister_poll_fd(int fd, void *opaque) { (void) fd; (void) opaque; } static void net_slirp_notify(void *opaque) { (void) opaque; } int net_slirp_send_packet(const void *qp, size_t pkt_len, void *opaque) { slirp_t *slirp = (slirp_t *) opaque; uint8_t *mac = slirp->mac; uint32_t mac_cmp32[2]; uint16_t mac_cmp16[2]; if (!(slirp->card->set_link_state && slirp->card->set_link_state(slirp->card->priv)) && !(slirp->card->wait && slirp->card->wait(slirp->card->priv))) { slirp_log("SLiRP: received %d-byte packet\n", pkt_len); /* Received MAC. */ mac_cmp32[0] = *(uint32_t *) (((uint8_t *) qp) + 6); mac_cmp16[0] = *(uint16_t *) (((uint8_t *) qp) + 10); /* Local MAC. */ mac_cmp32[1] = *(uint32_t *) mac; mac_cmp16[1] = *(uint16_t *) (mac + 4); if ((mac_cmp32[0] != mac_cmp32[1]) || (mac_cmp16[0] != mac_cmp16[1])) { network_queue_put(0, slirp->card->priv, (uint8_t *) qp, pkt_len); } return pkt_len; } else { slirp_log("SLiRP: ignored %d-byte packet\n", pkt_len); } return 0; } static int net_slirp_add_poll(int fd, int events, void *opaque) { slirp_t *slirp = (slirp_t *) opaque; #ifdef SLIRP_USE_POLL if (slirp->pfd_len >= slirp->pfd_size) { int newsize = slirp->pfd_size + 16; struct pollfd *new = realloc(slirp->pfd, newsize * sizeof(struct pollfd)); if (new) { slirp->pfd = new; slirp->pfd_size = newsize; } } if ((slirp->pfd_len < slirp->pfd_size)) { int idx = slirp->pfd_len++; slirp->pfd[idx].fd = fd; int pevents = 0; if (events & SLIRP_POLL_IN) pevents |= POLLIN; if (events & SLIRP_POLL_OUT) pevents |= POLLOUT; # ifndef _WIN32 /* Windows does not support some events. */ if (events & SLIRP_POLL_ERR) pevents |= POLLERR; if (events & SLIRP_POLL_PRI) pevents |= POLLPRI; if (events & SLIRP_POLL_HUP) pevents |= POLLHUP; # endif slirp->pfd[idx].events = pevents; return idx; } else return -1; #else if (events & SLIRP_POLL_IN) FD_SET(fd, &slirp->rfds); if (events & SLIRP_POLL_OUT) FD_SET(fd, &slirp->wfds); if (events & SLIRP_POLL_PRI) FD_SET(fd, &slirp->xfds); if (fd > slirp->nfds) slirp->nfds = fd; return fd; #endif } static int net_slirp_get_revents(int idx, void *opaque) { slirp_t *slirp = (slirp_t *) opaque; int ret = 0; #ifdef SLIRP_USE_POLL int events = slirp->pfd[idx].revents; if (events & POLLIN) ret |= SLIRP_POLL_IN; if (events & POLLOUT) ret |= SLIRP_POLL_OUT; if (events & POLLPRI) ret |= SLIRP_POLL_PRI; if (events & POLLERR) ret |= SLIRP_POLL_ERR; if (events & POLLHUP) ret |= SLIRP_POLL_HUP; #else if (FD_ISSET(idx, &slirp->rfds)) ret |= SLIRP_POLL_IN; if (FD_ISSET(idx, &slirp->wfds)) ret |= SLIRP_POLL_OUT; if (FD_ISSET(idx, &slirp->xfds)) ret |= SLIRP_POLL_PRI; #endif return ret; } static void slirp_tic(slirp_t *slirp) { int ret2; uint32_t tmo; /* Let SLiRP create a list of all open sockets. */ #ifdef SLIRP_USE_POLL tmo = -1; slirp->pfd_len = 0; #else slirp->nfds = -1; FD_ZERO(&slirp->rfds); FD_ZERO(&slirp->wfds); FD_ZERO(&slirp->xfds); #endif slirp_pollfds_fill(slirp->slirp, &tmo, net_slirp_add_poll, slirp); /* Now wait for something to happen, or at most 'tmo' usec. */ #ifdef SLIRP_USE_POLL ret2 = poll(slirp->pfd, slirp->pfd_len, tmo); #else if (tmo < 0) tmo = 500; struct timeval tv; tv.tv_sec = 0; tv.tv_usec = tmo; ret2 = select(slirp->nfds + 1, &slirp->rfds, &slirp->wfds, &slirp->xfds, &tv); #endif /* If something happened, let SLiRP handle it. */ slirp_pollfds_poll(slirp->slirp, (ret2 <= 0), net_slirp_get_revents, slirp); } static const SlirpCb slirp_cb = { .send_packet = net_slirp_send_packet, .guest_error = net_slirp_guest_error, .clock_get_ns = net_slirp_clock_get_ns, .timer_new = net_slirp_timer_new, .timer_free = net_slirp_timer_free, .timer_mod = net_slirp_timer_mod, .register_poll_fd = net_slirp_register_poll_fd, .unregister_poll_fd = net_slirp_unregister_poll_fd, .notify = net_slirp_notify }; /* Handle the receiving of frames. */ static void poll_thread(void *arg) { slirp_t *slirp = (slirp_t *) arg; event_t *evt; int tx; slirp_log("SLiRP: initializing...\n"); /* Set the IP addresses to use. */ struct in_addr net = { .s_addr = htonl(0x0a000200) }; /* 10.0.2.0 */ struct in_addr mask = { .s_addr = htonl(0xffffff00) }; /* 255.255.255.0 */ struct in_addr host = { .s_addr = htonl(0x0a000202) }; /* 10.0.2.2 */ struct in_addr dhcp = { .s_addr = htonl(0x0a00020f) }; /* 10.0.2.15 */ struct in_addr dns = { .s_addr = htonl(0x0a000203) }; /* 10.0.2.3 */ struct in_addr bind = { .s_addr = htonl(0x00000000) }; /* 0.0.0.0 */ struct in6_addr ipv6dummy; /* contents don't matter; we're not doing IPv6 */ /* Initialize SLiRP. */ slirp->slirp = slirp_init(0, 1, net, mask, host, 0, ipv6dummy, 0, ipv6dummy, NULL, NULL, NULL, NULL, dhcp, dns, ipv6dummy, NULL, NULL, &slirp_cb, arg); if (!slirp->slirp) { slirp_log("SLiRP: initialization failed\n"); return; } /* Set up port forwarding. */ int udp, from, to, i = 0; char *category = "SLiRP Port Forwarding"; char key[16]; while (1) { sprintf(key, "%d_udp", i); udp = config_get_int(category, key, 0); sprintf(key, "%d_from", i); from = config_get_int(category, key, 0); if (from < 1) break; sprintf(key, "%d_to", i); to = config_get_int(category, key, from); if (slirp_add_hostfwd(slirp->slirp, udp, bind, from, dhcp, to) == 0) pclog("SLiRP: Forwarded %s port host:%d to guest:%d\n", udp ? "UDP" : "TCP", from, to); else pclog("SLiRP: Failed to forward %s port host:%d to guest:%d\n", udp ? "UDP" : "TCP", from, to); i++; } /* Start polling. */ slirp_log("SLiRP: polling started.\n"); thread_set_event(slirp->poll_state); /* Create a waitable event. */ evt = thread_create_event(); while (!slirp->stop) { /* Request ownership of the queue. */ network_wait(1); /* Wait for a poll request. */ network_poll(); /* Stop processing if asked to. */ if (slirp->stop) break; /* See if there is any work. */ slirp_tic(slirp); /* Wait for the next packet to arrive. */ tx = network_tx_queue_check(); if (tx) network_do_tx(); /* If we did not get anything, wait a while. */ if (!tx) thread_wait_event(evt, 10); /* Release ownership of the queue. */ network_wait(0); } /* No longer needed. */ if (evt) thread_destroy_event(evt); slirp_log("SLiRP: polling stopped.\n"); thread_set_event(slirp->poll_state); /* Free here instead of immediately freeing the global slirp on the main thread to avoid a race condition. */ slirp_cleanup(slirp->slirp); free(slirp); } /* Initialize SLiRP for use. */ int net_slirp_init(void) { return 0; } /* Initialize SLiRP for use. */ int net_slirp_reset(const netcard_t *card, uint8_t *mac) { slirp_t *new_slirp = malloc(sizeof(slirp_t)); memset(new_slirp, 0, sizeof(slirp_t)); new_slirp->mac = mac; new_slirp->card = card; #ifdef SLIRP_USE_POLL new_slirp->pfd_size = 16 * sizeof(struct pollfd); new_slirp->pfd = malloc(new_slirp->pfd_size); memset(new_slirp->pfd, 0, new_slirp->pfd_size); #endif /* Save the callback info. */ slirp = new_slirp; slirp_log("SLiRP: creating thread...\n"); slirp->poll_state = thread_create_event(); slirp->poll_tid = thread_create(poll_thread, new_slirp); thread_wait_event(slirp->poll_state, -1); return 0; } void net_slirp_close(void) { if (!slirp) return; slirp_log("SLiRP: closing\n"); /* Tell the polling thread to shut down. */ slirp->stop = 1; /* Tell the thread to terminate. */ if (slirp->poll_tid) { network_busy(0); /* Wait for the thread to finish. */ slirp_log("SLiRP: waiting for thread to end...\n"); thread_wait_event(slirp->poll_state, -1); slirp_log("SLiRP: thread ended\n"); thread_destroy_event(slirp->poll_state); } /* Shutdown work is done by the thread on its local copy of slirp. */ slirp = NULL; } /* Send a packet to the SLiRP interface. */ void net_slirp_in(uint8_t *pkt, int pkt_len) { if (!slirp || !slirp->slirp) return; slirp_log("SLiRP: sending %d-byte packet\n", pkt_len); slirp_input(slirp->slirp, (const uint8_t *) pkt, pkt_len); } /* Stubs to stand in for the parts of libslirp we skip compiling. */ void ncsi_input(void *slirp, const uint8_t *pkt, int pkt_len) {} void ip6_init(void *slirp) {} void ip6_cleanup(void *slirp) {} void ip6_input(void *m) {} int ip6_output(void *so, void *m, int fast) { return 0; } void in6_compute_ethaddr(struct in6_addr ip, uint8_t *eth) {} bool in6_equal(const void *a, const void *b) { return 0; } const struct in6_addr in6addr_any = { { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } } }; const struct in6_addr in6addr_loopback = { { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 } } }; int udp6_output(void *so, void *m, void *saddr, void *daddr) { return 0; } void icmp6_send_error(void *m, uint8_t type, uint8_t code) {} void ndp_send_ns(void *slirp, struct in6_addr addr) {} bool ndp_table_search(void *slirp, struct in6_addr ip_addr, uint8_t *out_ethaddr) { return 0; } void tftp_input(void *srcsas, void *m) {}