From 45bcbc75fd2ae1eec31154edf4bebaff5d726823 Mon Sep 17 00:00:00 2001 From: Doug Johnson Date: Sun, 6 Aug 2023 20:11:36 -0600 Subject: [PATCH 1/2] networking: add Linux-specific TAP mode to network interface This addition was motivated by my lack of knowledge of VDE and my familiarity with the Linux networking stack. The driver automatically manages the creation of TAP devices and their association with bridges, such that configurations which specify the same bridge will be connected together. It also automatically handles the creation of bridge devices for convenience's sake. --- src/config.c | 9 +- src/include/86box/network.h | 5 +- src/network/CMakeLists.txt | 9 + src/network/net_tap.c | 353 ++++++++++++++++++++++++++++++++++ src/network/network.c | 6 + src/qt/qt_mediamenu.cpp | 3 + src/qt/qt_settingsnetwork.cpp | 18 +- src/qt/qt_settingsnetwork.ui | 78 ++++++-- 8 files changed, 463 insertions(+), 18 deletions(-) create mode 100644 src/network/net_tap.c diff --git a/src/config.c b/src/config.c index 2f1561f18..8284fec6d 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) { @@ -2309,7 +2312,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 fa6408790..ec8c70abb 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 */ @@ -125,6 +126,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 { @@ -154,10 +156,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 0d42cbd8d..cbdff7b1d 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -67,5 +67,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 7c02609ae..9ad502cfb 100644 --- a/src/network/network.c +++ b/src/network/network.c @@ -480,6 +480,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 1fa5283d2..41687c925 100644 --- a/src/qt/qt_mediamenu.cpp +++ b/src/qt/qt_mediamenu.cpp @@ -1061,6 +1061,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 2aa3705fd..4411d4d4c 100644 --- a/src/qt/qt_settingsnetwork.cpp +++ b/src/qt/qt_settingsnetwork.cpp @@ -38,12 +38,13 @@ SettingsNetwork::enableElements(Ui::SettingsNetwork *ui) auto *intf_cbox = findChild(QString("comboBoxIntf%1").arg(i + 1)); auto *conf_btn = findChild(QString("pushButtonConf%1").arg(i + 1)); auto *socket_line = findChild(QString("socketVDENIC%1").arg(i + 1)); - + auto *bridge_line = findChild(QString("bridgeTAPNIC%1").arg(i + 1)); int netType = net_type_cbox->currentData().toInt(); bool adaptersEnabled = netType == NET_TYPE_NONE || netType == NET_TYPE_SLIRP || netType == NET_TYPE_VDE - || (netType == NET_TYPE_PCAP && intf_cbox->currentData().toInt() > 0); + || (netType == NET_TYPE_PCAP && intf_cbox->currentData().toInt() > 0) + || netType == NET_TYPE_TAP; intf_cbox->setEnabled(net_type_cbox->currentData().toInt() == NET_TYPE_PCAP); nic_cbox->setEnabled(adaptersEnabled); @@ -54,6 +55,7 @@ SettingsNetwork::enableElements(Ui::SettingsNetwork *ui) else conf_btn->setEnabled(adaptersEnabled && network_card_has_config(nic_cbox->currentData().toInt())); socket_line->setEnabled(net_type_cbox->currentData().toInt() == NET_TYPE_VDE); + bridge_line->setEnabled(net_type_cbox->currentData().toInt() == NET_TYPE_TAP); } } @@ -86,6 +88,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(); @@ -95,6 +98,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)); } } } @@ -152,7 +157,7 @@ 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(net_cards_conf[i].net_type); @@ -171,12 +176,17 @@ 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 8f1eb5a79..efb25a16a 100644 --- a/src/qt/qt_settingsnetwork.ui +++ b/src/qt/qt_settingsnetwork.ui @@ -144,7 +144,21 @@ - + + + + TAP Bridge Device + + + + + + + 127 + + + + Qt::Vertical @@ -259,6 +273,13 @@ + + + + 127 + + + @@ -266,7 +287,14 @@ - + + + + TAP Bridge Device + + + + Qt::Vertical @@ -394,7 +422,21 @@ - + + + + TAP Bridge Device + + + + + + + 127 + + + + Qt::Vertical @@ -508,13 +550,6 @@ - - - - 127 - - - @@ -522,7 +557,28 @@ - + + + + 127 + + + + + + + TAP Bridge Device + + + + + + + 127 + + + + Qt::Vertical From f4485b595731d307e4f28deb50288e7cc36b1abf Mon Sep 17 00:00:00 2001 From: Doug Johnson Date: Wed, 3 Jan 2024 20:39:15 -0700 Subject: [PATCH 2/2] Add address sanitizer support for debugging double free --- CMakeLists.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d7b3f6f0..f9f39a8d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -138,6 +138,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(WIN32) set(QT ON) @@ -212,4 +213,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)