diff --git a/CMakeLists.txt b/CMakeLists.txt index 777ede9ca..4dd83523f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -137,6 +137,7 @@ option(GDBSTUB "Enable GDB stub server for debugging" option(DEV_BRANCH "Development branch" OFF) option(DISCORD "Discord Rich Presence support" ON) option(DEBUGREGS486 "Enable debug register opeartion on 486+ CPUs" OFF) +option(LIBASAN "Enable compilation with the addresss sanitizer" OFF) if((ARCH STREQUAL "arm64") OR (ARCH STREQUAL "arm")) set(NEW_DYNAREC ON) @@ -223,4 +224,10 @@ if(NOT EMU_COPYRIGHT_YEAR) set(EMU_COPYRIGHT_YEAR 2024) endif() +# Libasan +if(LIBASAN) + add_compile_options(-fsanitize=address) + add_link_options(-fsanitize=address) +endif() + add_subdirectory(src) diff --git a/src/config.c b/src/config.c index 8039b9887..d33813e7d 100644 --- a/src/config.c +++ b/src/config.c @@ -660,6 +660,8 @@ load_network(void) nc->net_type = NET_TYPE_SLIRP; else if (!strcmp(p, "vde") || !strcmp(p, "2")) nc->net_type = NET_TYPE_VDE; + else if (!strcmp(p, "tap") || !strcmp(p, "3")) + nc->net_type = NET_TYPE_TAP; else nc->net_type = NET_TYPE_NONE; } else @@ -706,11 +708,12 @@ load_network(void) nc->net_type = NET_TYPE_SLIRP; else if (!strcmp(p, "vde") || !strcmp(p, "2")) nc->net_type = NET_TYPE_VDE; + else if (!strcmp(p, "tap") || !strcmp(p, "3")) + nc->net_type = NET_TYPE_TAP; else nc->net_type = NET_TYPE_NONE; } else nc->net_type = NET_TYPE_NONE; - sprintf(temp, "net_%02i_host_device", c + 1); p = ini_section_get_string(cat, temp, NULL); if (p != NULL) { @@ -2433,7 +2436,9 @@ save_network(void) case NET_TYPE_VDE: ini_section_set_string(cat, temp, "vde"); break; - + case NET_TYPE_TAP: + ini_section_set_string(cat, temp, "tap"); + break; default: break; } diff --git a/src/include/86box/network.h b/src/include/86box/network.h index ef9f291ad..a0d1bc3cb 100644 --- a/src/include/86box/network.h +++ b/src/include/86box/network.h @@ -52,6 +52,7 @@ #define NET_TYPE_SLIRP 1 /* use the SLiRP port forwarder */ #define NET_TYPE_PCAP 2 /* use the (Win)Pcap API */ #define NET_TYPE_VDE 3 /* use the VDE plug API */ +#define NET_TYPE_TAP 4 /* use a linux TAP device */ #define NET_MAX_FRAME 1518 /* Queue size must be a power of 2 */ @@ -126,6 +127,7 @@ typedef struct netdrv_t { extern const netdrv_t net_pcap_drv; extern const netdrv_t net_slirp_drv; extern const netdrv_t net_vde_drv; +extern const netdrv_t net_tap_drv; extern const netdrv_t net_null_drv; struct _netcard_t { @@ -155,10 +157,11 @@ typedef struct { int has_slirp; int has_pcap; int has_vde; + int has_tap; } network_devmap_t; -#define HAS_NOSLIRP_NET(x) (x.has_pcap || x.has_vde) +#define HAS_NOSLIRP_NET(x) (x.has_pcap || x.has_vde || x.has_tap) #ifdef __cplusplus extern "C" { diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 71f41f059..9d6b2efeb 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -70,5 +70,14 @@ if (UNIX) endif() endif() endif() +if (UNIX AND NOT APPLE) # Support for TAP on Linux and BSD, supposedly. + find_path(HAS_TAP "linux/if_tun.h" PATHS ${TAP_INCLUDE_DIR} "/usr/include /usr/local/include" "/opt/homebrew/include" ) + if(HAS_TAP) + add_compile_definitions(HAS_TAP) + list(APPEND net_sources net_tap.c) + else() + message(WARNING "TAP support not available. Are you on some BSD?") + endif() +endif() add_library(net OBJECT ${net_sources}) diff --git a/src/network/net_tap.c b/src/network/net_tap.c new file mode 100644 index 000000000..762f68b60 --- /dev/null +++ b/src/network/net_tap.c @@ -0,0 +1,353 @@ +/* + * 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. + * + * Linux TAP network interface for 86box. + * + * This file was created by looking at the VDE network backend + * as a reference, credit to jguillaumes. + * + * Authors: Doug Johnson + * + * + * Copyright 2023 Doug Johnson + * + * Redistribution and use in source and binary forms, with + * or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the entire + * above notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names + * of its contributors may be used to endorse or promote + * products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef _WIN32 +# error TAP networking is only supported on Linux +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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/plat_dynld.h> +#include <86box/thread.h> +#include <86box/timer.h> +#include <86box/network.h> +#include <86box/net_event.h> + +typedef struct net_tap_t { + int fd; // tap device file descriptor + netcard_t *card; + thread_t *poll_tid; + net_evt_t tx_event; + net_evt_t stop_event; + netpkt_t pkt_rx; + netpkt_t pkts_tx[NET_QUEUE_LEN]; +} net_tap_t; + +#ifdef ENABLE_TAP_LOG +int tap_do_log = ENABLE_TAP_LOG; + + +static void tap_logv(const char *fmt, va_list ap) +{ + if (tap_do_log) { + pclog_ex(fmt, ap); + } +} + +static void tap_log(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + if (tap_do_log) { + va_start(ap, fmt); + tap_logv(fmt, ap); + va_end(ap); + } + va_end(ap); +} + +#else +# define tap_log(...) \ + do { \ + } while (0) +# define tap_logv(...) \ + do { \ + } while (0) +#endif + +static void net_tap_thread(void *priv) { + enum { + NET_EVENT_STOP = 0, + NET_EVENT_TX, + NET_EVENT_RX, + NET_EVENT_TAP, + NET_EVENT_MAX, + }; + net_tap_t *tap = priv; + tap_log("TAP: poll thread started.\n"); + struct pollfd pfd[NET_EVENT_MAX]; + pfd[NET_EVENT_STOP].fd = net_event_get_fd(&tap->stop_event); + pfd[NET_EVENT_STOP].events = POLLIN | POLLPRI; + + pfd[NET_EVENT_TX].fd = net_event_get_fd(&tap->tx_event); + pfd[NET_EVENT_TX].events = POLLIN | POLLPRI; + + pfd[NET_EVENT_RX].fd = tap->fd; + pfd[NET_EVENT_RX].events = POLLIN | POLLPRI; + + pfd[NET_EVENT_TAP].fd = tap->fd; + pfd[NET_EVENT_TAP].events = POLLERR | POLLHUP | POLLPRI; + fcntl(tap->fd, F_SETFL, O_NONBLOCK); + while(1) { + ssize_t ret = poll(pfd, NET_EVENT_MAX, -1); + if (ret < 0) { + tap_log("TAP: poll error: %s\n", strerror(errno)); + net_event_set(&tap->stop_event); + break; + } + if (pfd[NET_EVENT_TAP].revents) { + tap_log("TAP: tap close/error event received.\n"); + net_event_set(&tap->stop_event); + } + if (pfd[NET_EVENT_TX].revents & POLLIN) { + net_event_clear(&tap->tx_event); + int packets = network_tx_popv(tap->card, tap->pkts_tx, + NET_QUEUE_LEN); + for(int i = 0; i < packets; i++) { + netpkt_t *pkt = &tap->pkts_tx[i]; + ssize_t ret = write(tap->fd, pkt->data, pkt->len); + if (ret < 0) { + tap_log("TAP: write error: %s\n", strerror(errno)); + } + } + } + if (pfd[NET_EVENT_RX].revents & POLLIN) { + ssize_t len = read(tap->fd, tap->pkt_rx.data, NET_MAX_FRAME); + if (len < 0) { + tap_log("TAP: read error: %s\n", strerror(errno)); + continue; + } + tap->pkt_rx.len = len; + network_rx_put_pkt(tap->card, &tap->pkt_rx); + } + if (pfd[NET_EVENT_STOP].revents & POLLIN) { + net_event_clear(&tap->stop_event); + break; + } + } +} + +void net_tap_close(void *priv) +{ + if (!priv) { + return; + } + net_tap_t *tap = priv; + tap_log("TAP: closing.\n"); + net_event_set(&tap->stop_event); + tap_log("TAP: waiting for poll thread to exit.\n"); + thread_wait(tap->poll_tid); + tap_log("TAP: poll thread exited.\n"); + for(int i = 0; i < NET_QUEUE_LEN; i++) { + free(tap->pkts_tx[i].data); + } + free(tap->pkt_rx.data); + if (tap->fd >= 0) { + close(tap->fd); + } + free(tap); +} + +void net_tap_error(char *errbuf, const char* format, ...) +{ + va_list ap; + va_start(ap, format); + vsnprintf(errbuf, NET_DRV_ERRBUF_SIZE, format, ap); + tap_log("TAP: %s", errbuf); + va_end(ap); +} + +// Error handling macro for the many ioctl calls we use in net_tap_alloc +#define ioctl_or_fail(fd, request, argp) \ + do { \ + if ((err = ioctl(fd, request, argp)) < 0) { \ + tap_log("TAP: ioctl " #request " error: %s\n", strerror(errno)); \ + goto fail; \ + } \ + } while (0) + +// Returns -ERRNO so we can get an idea what's wrong +int net_tap_alloc(const uint8_t *mac_addr, const char* bridge_dev) +{ + int fd; + struct ifreq ifr = {0}; + if ((fd = open("/dev/net/tun", O_RDWR)) < 0) { + tap_log("TAP: open error: %s\n", strerror(errno)); + return -errno; + } + ifr.ifr_flags = IFF_TAP | IFF_NO_PI; + int err; + if ((err = ioctl(fd, TUNSETIFF, &ifr)) < 0) { + tap_log("TAP: ioctl TUNSETIFF error: %s\n", strerror(errno)); + close(fd); + return -errno; + } + // Create a socket for ioctl operations + int sock; + if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + tap_log("TAP: socket error: %s\n", strerror(errno)); + close(fd); + return -errno; + } + // Bring the interface up + tap_log("TAP: Bringing interface '%s' up.\n", ifr.ifr_name); + ifr.ifr_flags = IFF_UP; + ioctl_or_fail(sock, SIOCSIFFLAGS, &ifr); + // Add interface to bridge, if specified + if (bridge_dev && bridge_dev[0] != '\0') { + // First see if the bridge exists + struct ifreq ifr_bridge; + //NOTE strncpy does not null terminate if the string is too long, I use + // snprintf or strlcpy instead + //strncpy(ifr_bridge.ifr_name, bridge_dev, IFNAMSIZ); + snprintf(ifr_bridge.ifr_name, IFNAMSIZ, "%s", bridge_dev); + if ((err = ioctl(sock, SIOCGIFINDEX, &ifr_bridge)) < 0) { + if (errno != ENODEV) { + tap_log("TAP: ioctl SIOCGIFINDEX error: %s\n", strerror(errno)); + goto fail; + } else { + // Create the bridge + ioctl_or_fail(sock, SIOCBRADDBR, &ifr_bridge); + // Set the bridge up + ifr_bridge.ifr_flags = IFF_UP; + ioctl_or_fail(sock, SIOCSIFFLAGS, &ifr_bridge); + } + } + // Get TAP index + ioctl_or_fail(sock, SIOCGIFINDEX, &ifr); + // Add the tap device to the bridge + ifr_bridge.ifr_ifindex = ifr.ifr_ifindex; + ioctl_or_fail(sock, SIOCBRADDIF, &ifr_bridge); + } + // close the socket we used for ioctl operations + close(sock); + tap_log("Allocated tap device %s\n", ifr.ifr_name); + return fd; + // cleanup point used by ioctl_or_fail macro +fail: + close(sock); + close(fd); + return -errno; +} + +void net_tap_in_available(void *priv) +{ + net_tap_t *tap = priv; + net_event_set(&tap->tx_event); +} + +void * +net_tap_init( + const netcard_t *card, + const uint8_t *mac_addr, + void *priv, + char *netdrv_errbuf) +{ + const char *bridge_dev = (void *) priv; + int tap_fd = net_tap_alloc(mac_addr, bridge_dev); + if (tap_fd < 0) { + if (tap_fd == -EPERM) { + net_tap_error( + netdrv_errbuf, + "No permissions to allocate tap device. " + "Try adding NET_CAP_ADMIN,NET_CAP_RAW to 86box (" + "sudo setcap 'CAP_NET_RAW,CAP_NET_ADMIN=eip')"); + } else { + net_tap_error( + netdrv_errbuf, + "Unable to allocate TAP device: %s", + strerror(-tap_fd)); + } + return NULL; + } + if (bridge_dev && bridge_dev[0] != '\0') { + } + net_tap_t *tap = calloc(1, sizeof(net_tap_t)); + if (!tap) { + goto alloc_fail; + } + tap->pkt_rx.data = calloc(1, NET_MAX_FRAME); + if (!tap->pkt_rx.data) { + goto alloc_fail; + } + for(int i = 0; i < NET_QUEUE_LEN; i++) { + tap->pkts_tx[i].data = calloc(1, NET_MAX_FRAME); + if (!tap->pkts_tx[i].data) { + goto alloc_fail; + } + } + tap->fd = tap_fd; + tap->card = (netcard_t *) card; + net_event_init(&tap->tx_event); + net_event_init(&tap->stop_event); + tap->poll_tid = thread_create(net_tap_thread, tap); + return tap; +alloc_fail: + net_tap_error(netdrv_errbuf, "Failed to allocate memory"); + close(tap_fd); + free(tap); + return NULL; +} + +const netdrv_t net_tap_drv = { + &net_tap_in_available, + &net_tap_init, + &net_tap_close, + NULL +}; diff --git a/src/network/network.c b/src/network/network.c index c1ee82b65..0372a57fc 100644 --- a/src/network/network.c +++ b/src/network/network.c @@ -496,6 +496,12 @@ network_attach(void *card_drv, uint8_t *mac, NETRXCB rx, NETSETLINKSTATE set_lin card->host_drv = net_vde_drv; card->host_drv.priv = card->host_drv.init(card, mac, net_cards_conf[net_card_current].host_dev_name, net_drv_error); break; +#endif +#ifdef HAS_TAP + case NET_TYPE_TAP: + card->host_drv = net_tap_drv; + card->host_drv.priv = card->host_drv.init(card, mac, net_cards_conf[net_card_current].host_dev_name, net_drv_error); + break; #endif default: card->host_drv.priv = NULL; diff --git a/src/qt/qt_mediamenu.cpp b/src/qt/qt_mediamenu.cpp index 351012d1e..11fac2a70 100644 --- a/src/qt/qt_mediamenu.cpp +++ b/src/qt/qt_mediamenu.cpp @@ -1081,6 +1081,9 @@ MediaMenu::nicUpdateMenu(int i) case NET_TYPE_VDE: netType = "VDE"; break; + case NET_TYPE_TAP: + netType = "TAP"; + break; } QString devName = DeviceConfig::DeviceName(network_card_getdevice(net_cards_conf[i].device_num), network_card_get_internal_name(net_cards_conf[i].device_num), 1); diff --git a/src/qt/qt_settingsnetwork.cpp b/src/qt/qt_settingsnetwork.cpp index 2e64175eb..e5bb67db0 100644 --- a/src/qt/qt_settingsnetwork.cpp +++ b/src/qt/qt_settingsnetwork.cpp @@ -45,6 +45,8 @@ SettingsNetwork::enableElements(Ui::SettingsNetwork *ui) auto *vde_socket_label = findChild(QString("labelSocketVDENIC%1").arg(i + 1)); auto *socket_line = findChild(QString("socketVDENIC%1").arg(i + 1)); + auto *bridge_line = findChild(QString("bridgeTAPNIC%1").arg(i + 1)); + auto *option_list_label = findChild(QString("labelOptionList%1").arg(i + 1)); auto *option_list_line = findChild(QString("lineOptionList%1").arg(i + 1)); @@ -60,6 +62,9 @@ SettingsNetwork::enableElements(Ui::SettingsNetwork *ui) vde_socket_label->setVisible(false); socket_line->setVisible(false); + // TAP + bridge_line->setEnabled(net_type_cbox->currentData().toInt() == NET_TYPE_TAP); + // PCAP intf_cbox->setVisible(false); intf_label->setVisible(false); @@ -86,6 +91,9 @@ SettingsNetwork::enableElements(Ui::SettingsNetwork *ui) intf_label->setVisible(true); break; + case NET_TYPE_TAP: + bridge_line->setVisible(true); + case NET_TYPE_SLIRP: default: break; @@ -123,6 +131,7 @@ SettingsNetwork::save() for (int i = 0; i < NET_CARD_MAX; ++i) { auto *cbox = findChild(QString("comboBoxNIC%1").arg(i + 1)); auto *socket_line = findChild(QString("socketVDENIC%1").arg(i + 1)); + auto *bridge_line = findChild(QString("bridgeTAPNIC%1").arg(i + 1)); net_cards_conf[i].device_num = cbox->currentData().toInt(); cbox = findChild(QString("comboBoxNet%1").arg(i + 1)); net_cards_conf[i].net_type = cbox->currentData().toInt(); @@ -132,6 +141,8 @@ SettingsNetwork::save() strncpy(net_cards_conf[i].host_dev_name, network_devs[cbox->currentData().toInt()].device, sizeof(net_cards_conf[i].host_dev_name) - 1); else if (net_cards_conf[i].net_type == NET_TYPE_VDE) strncpy(net_cards_conf[i].host_dev_name, socket_line->text().toUtf8().constData(), sizeof(net_cards_conf[i].host_dev_name)); + else if (net_cards_conf[i].net_type == NET_TYPE_TAP) + strncpy(net_cards_conf[i].host_dev_name, bridge_line->text().toUtf8().constData(), sizeof(net_cards_conf[i].host_dev_name)); } } @@ -198,6 +209,8 @@ SettingsNetwork::onCurrentMachineChanged(int machineId) if (network_devmap.has_vde) Models::AddEntry(model, "VDE", NET_TYPE_VDE); + Models::AddEntry(model, "TAP", NET_TYPE_TAP); + model->removeRows(0, removeRows); cbox->setCurrentIndex(cbox->findData(net_cards_conf[i].net_type)); @@ -216,13 +229,18 @@ SettingsNetwork::onCurrentMachineChanged(int machineId) } model->removeRows(0, removeRows); cbox->setCurrentIndex(selectedRow); - } + } if (net_cards_conf[i].net_type == NET_TYPE_VDE) { QString currentVdeSocket = net_cards_conf[i].host_dev_name; auto editline = findChild(QString("socketVDENIC%1").arg(i+1)); editline->setText(currentVdeSocket); } + else if (net_cards_conf[i].net_type == NET_TYPE_TAP) { + QString currentTapDevice = net_cards_conf[i].host_dev_name; + auto editline = findChild(QString("bridgeTAPNIC%1").arg(i+1)); + editline->setText(currentTapDevice); + } } } diff --git a/src/qt/qt_settingsnetwork.ui b/src/qt/qt_settingsnetwork.ui index 0c0979c46..7aff676d6 100644 --- a/src/qt/qt_settingsnetwork.ui +++ b/src/qt/qt_settingsnetwork.ui @@ -31,7 +31,6 @@ 0 - Network Card #1 @@ -156,7 +155,21 @@ - + + + + TAP Bridge Device + + + + + + + 127 + + + + Qt::Vertical @@ -171,7 +184,6 @@ - Network Card #2 @@ -296,7 +308,21 @@ - + + + + TAP Bridge Device + + + + + + + 127 + + + + Qt::Vertical @@ -311,7 +337,6 @@ - Network Card #3 @@ -436,7 +461,21 @@ - + + + + TAP Bridge Device + + + + + + + 127 + + + + Qt::Vertical @@ -451,7 +490,6 @@ - Network Card #4 @@ -576,7 +614,21 @@ - + + + + TAP Bridge Device + + + + + + + 127 + + + + Qt::Vertical @@ -591,7 +643,6 @@ -