From a9486d087fe6e67b6ff1ee10e6f3a9f73b07ddfe Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sun, 27 Sep 2020 13:15:14 -0700 Subject: [PATCH] Add tap-like fake Ethernet network interface for macOS TunTap has recently become unmaintained, and it seems Apple wants developers to move away from kexts in general. TunTap currently takes some finagling to work on Catalina, and it may not work at all on Big Sur, necessitating a non-kext-based solution. Fortunately, fake Ethernet devices were introduced in Sierra and can be used similarly to tap adapters. This commit adds a new type of BBA interface implementation which uses fake Ethernet devices via tapserver (https://github.com/fuzziqersoftware/tapserver) to communicate with the host. This implementation was tested with PSO Episodes I & II, which can successfully connect to a private server running locally. This implementation is only available on macOS, since that's the only place it's needed - Windows/Linux/Unix are unaffected by TunTap being deprecated. --- Source/Core/Core/CMakeLists.txt | 1 + .../Core/Core/HW/EXI/BBA/TAPServer_Apple.cpp | 127 ++++++++++++++++++ Source/Core/Core/HW/EXI/EXI_Device.cpp | 6 + Source/Core/Core/HW/EXI/EXI_Device.h | 3 + .../Core/Core/HW/EXI/EXI_DeviceEthernet.cpp | 6 + Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h | 21 ++- .../Core/DolphinQt/Settings/GameCubePane.cpp | 16 ++- 7 files changed, 173 insertions(+), 7 deletions(-) create mode 100644 Source/Core/Core/HW/EXI/BBA/TAPServer_Apple.cpp diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index 72835324a7..de8fd8ac93 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -623,6 +623,7 @@ if(WIN32) elseif(APPLE) target_sources(core PRIVATE HW/EXI/BBA/TAP_Apple.cpp + HW/EXI/BBA/TAPServer_Apple.cpp HW/EXI/BBA/XLINK_KAI_BBA.cpp HW/WiimoteReal/IOdarwin.h HW/WiimoteReal/IOdarwin_private.h diff --git a/Source/Core/Core/HW/EXI/BBA/TAPServer_Apple.cpp b/Source/Core/Core/HW/EXI/BBA/TAPServer_Apple.cpp new file mode 100644 index 0000000000..d7a0156b5d --- /dev/null +++ b/Source/Core/Core/HW/EXI/BBA/TAPServer_Apple.cpp @@ -0,0 +1,127 @@ +// Copyright 2020 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include +#include +#include +#include + +#include "Common/Logging/Log.h" +#include "Common/StringUtil.h" +#include "Core/HW/EXI/EXI_Device.h" +#include "Core/HW/EXI/EXI_DeviceEthernet.h" + +namespace ExpansionInterface +{ +// This interface is only implemented on macOS, since macOS needs a replacement +// for TunTap when the kernel extension is no longer supported. This interface +// only appears in the menu on macOS, so on other platforms, it does nothing and +// refuses to activate. + +constexpr char socket_path[] = "/tmp/dolphin-tap"; + +bool CEXIETHERNET::TAPServerNetworkInterface::Activate() +{ + if (IsActivated()) + return true; + + sockaddr_un sun = {}; + if (sizeof(socket_path) > sizeof(sun.sun_path)) + { + ERROR_LOG(SP1, "Socket path is too long, unable to init BBA"); + return false; + } + sun.sun_family = AF_UNIX; + strcpy(sun.sun_path, socket_path); + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) + { + ERROR_LOG(SP1, "Couldn't create socket, unable to init BBA"); + return false; + } + + if (connect(fd, reinterpret_cast(&sun), sizeof(sun)) == -1) + { + ERROR_LOG(SP1, "Couldn't connect socket (%d), unable to init BBA", errno); + close(fd); + fd = -1; + return false; + } + + INFO_LOG(SP1, "BBA initialized.\n"); + return RecvInit(); +} + +bool CEXIETHERNET::TAPServerNetworkInterface::SendFrame(const u8* frame, u32 size) +{ + { + const std::string s = ArrayToString(frame, size, 0x10); + INFO_LOG(SP1, "SendFrame %x\n%s\n", size, s.c_str()); + } + + auto size16 = u16(size); + if (write(fd, &size16, 2) != 2) + { + ERROR_LOG(SP1, "SendFrame(): could not write size field\n"); + return false; + } + int written_bytes = write(fd, frame, size); + if (u32(written_bytes) != size) + { + ERROR_LOG(SP1, "SendFrame(): expected to write %d bytes, instead wrote %d", size, + written_bytes); + return false; + } + else + { + m_eth_ref->SendComplete(); + return true; + } +} + +void CEXIETHERNET::TAPServerNetworkInterface::ReadThreadHandler() +{ + while (!readThreadShutdown.IsSet()) + { + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + + timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 50000; + if (select(fd + 1, &rfds, nullptr, nullptr, &timeout) <= 0) + continue; + + u16 size; + if (read(fd, &size, 2) != 2) + { + ERROR_LOG(SP1, "Failed to read size field from BBA, err=%d", errno); + } + else + { + int read_bytes = read(fd, m_eth_ref->mRecvBuffer.get(), size); + if (read_bytes < 0) + { + ERROR_LOG(SP1, "Failed to read packet data from BBA, err=%d", errno); + } + else if (readEnabled.IsSet()) + { + std::string data_string = ArrayToString(m_eth_ref->mRecvBuffer.get(), read_bytes, 0x10); + INFO_LOG(SP1, "Read data: %s", data_string.c_str()); + m_eth_ref->mRecvBufferLength = read_bytes; + m_eth_ref->RecvHandlePacket(); + } + } + } +} + +bool CEXIETHERNET::TAPServerNetworkInterface::RecvInit() +{ + readThread = std::thread(&CEXIETHERNET::TAPServerNetworkInterface::ReadThreadHandler, this); + return true; +} + +} // namespace ExpansionInterface diff --git a/Source/Core/Core/HW/EXI/EXI_Device.cpp b/Source/Core/Core/HW/EXI/EXI_Device.cpp index c590b75c3b..926492b160 100644 --- a/Source/Core/Core/HW/EXI/EXI_Device.cpp +++ b/Source/Core/Core/HW/EXI/EXI_Device.cpp @@ -136,6 +136,12 @@ std::unique_ptr EXIDevice_Create(const TEXIDevices device_type, cons result = std::make_unique(BBADeviceType::TAP); break; +#if defined(__APPLE__) + case EXIDEVICE_ETHTAPSERVER: + result = std::make_unique(BBADeviceType::TAPSERVER); + break; +#endif + case EXIDEVICE_ETHXLINK: result = std::make_unique(BBADeviceType::XLINK); break; diff --git a/Source/Core/Core/HW/EXI/EXI_Device.h b/Source/Core/Core/HW/EXI/EXI_Device.h index cf3476c15b..8822e0d7be 100644 --- a/Source/Core/Core/HW/EXI/EXI_Device.h +++ b/Source/Core/Core/HW/EXI/EXI_Device.h @@ -33,6 +33,9 @@ enum TEXIDevices : int EXIDEVICE_MEMORYCARDFOLDER, EXIDEVICE_AGP, EXIDEVICE_ETHXLINK, +#if defined(__APPLE__) + EXIDEVICE_ETHTAPSERVER, +#endif EXIDEVICE_NONE = 0xFF }; diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.cpp index 75103900ff..7c21f6f3fa 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.cpp @@ -48,6 +48,12 @@ CEXIETHERNET::CEXIETHERNET(BBADeviceType type) m_network_interface = std::make_unique(this); INFO_LOG_FMT(SP1, "Created TAP physical network interface."); break; +#if defined(__APPLE__) + case BBADeviceType::TAPSERVER: + m_network_interface = std::make_unique(this); + INFO_LOG(SP1, "Created tapserver physical network interface."); + break; +#endif case BBADeviceType::XLINK: // TODO start BBA with network link down, bring it up after "connected" response from XLink diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h index 1637b47633..38970ff0fc 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h +++ b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h @@ -202,6 +202,9 @@ enum class BBADeviceType { TAP, XLINK, +#if defined(__APPLE__) + TAPSERVER, +#endif }; class CEXIETHERNET : public IEXIDevice @@ -337,7 +340,7 @@ private: void RecvStart() override; void RecvStop() override; - private: + protected: #if defined(WIN32) || defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || \ defined(__OpenBSD__) std::thread readThread; @@ -356,6 +359,22 @@ private: #endif }; +#if defined(__APPLE__) + class TAPServerNetworkInterface : public TAPNetworkInterface + { + public: + explicit TAPServerNetworkInterface(CEXIETHERNET* eth_ref) : TAPNetworkInterface(eth_ref) {} + + public: + bool Activate() override; + bool SendFrame(const u8* frame, u32 size) override; + bool RecvInit() override; + + private: + void ReadThreadHandler(); + }; +#endif + class XLinkNetworkInterface : public NetworkInterface { public: diff --git a/Source/Core/DolphinQt/Settings/GameCubePane.cpp b/Source/Core/DolphinQt/Settings/GameCubePane.cpp index abcec0d0a8..c0af9c653c 100644 --- a/Source/Core/DolphinQt/Settings/GameCubePane.cpp +++ b/Source/Core/DolphinQt/Settings/GameCubePane.cpp @@ -100,12 +100,16 @@ void GameCubePane::CreateWidgets() // Add SP1 devices - for (const auto& entry : - {std::make_pair(tr(""), ExpansionInterface::EXIDEVICE_NONE), - std::make_pair(tr("Dummy"), ExpansionInterface::EXIDEVICE_DUMMY), - std::make_pair(tr("Broadband Adapter (TAP)"), ExpansionInterface::EXIDEVICE_ETH), - std::make_pair(tr("Broadband Adapter (XLink Kai)"), - ExpansionInterface::EXIDEVICE_ETHXLINK)}) + std::vector> sp1Entries{ + std::make_pair(tr(""), ExpansionInterface::EXIDEVICE_NONE), + std::make_pair(tr("Dummy"), ExpansionInterface::EXIDEVICE_DUMMY), + std::make_pair(tr("Broadband Adapter (TAP)"), ExpansionInterface::EXIDEVICE_ETH), + std::make_pair(tr("Broadband Adapter (XLink Kai)"), ExpansionInterface::EXIDEVICE_ETHXLINK)}; +#if defined(__APPLE__) + sp1Entries.emplace_back(std::make_pair(tr("Broadband Adapter (tapserver)"), + ExpansionInterface::EXIDEVICE_ETHTAPSERVER)); +#endif + for (const auto& entry : sp1Entries) { m_slot_combos[2]->addItem(entry.first, entry.second); }