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.
This commit is contained in:
Martin Michelsen 2020-09-27 13:15:14 -07:00 committed by Léo Lam
parent dbf7c40886
commit a9486d087f
No known key found for this signature in database
GPG Key ID: 0DF30F9081000741
7 changed files with 173 additions and 7 deletions

View File

@ -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

View File

@ -0,0 +1,127 @@
// Copyright 2020 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#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<sockaddr*>(&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

View File

@ -136,6 +136,12 @@ std::unique_ptr<IEXIDevice> EXIDevice_Create(const TEXIDevices device_type, cons
result = std::make_unique<CEXIETHERNET>(BBADeviceType::TAP);
break;
#if defined(__APPLE__)
case EXIDEVICE_ETHTAPSERVER:
result = std::make_unique<CEXIETHERNET>(BBADeviceType::TAPSERVER);
break;
#endif
case EXIDEVICE_ETHXLINK:
result = std::make_unique<CEXIETHERNET>(BBADeviceType::XLINK);
break;

View File

@ -33,6 +33,9 @@ enum TEXIDevices : int
EXIDEVICE_MEMORYCARDFOLDER,
EXIDEVICE_AGP,
EXIDEVICE_ETHXLINK,
#if defined(__APPLE__)
EXIDEVICE_ETHTAPSERVER,
#endif
EXIDEVICE_NONE = 0xFF
};

View File

@ -48,6 +48,12 @@ CEXIETHERNET::CEXIETHERNET(BBADeviceType type)
m_network_interface = std::make_unique<TAPNetworkInterface>(this);
INFO_LOG_FMT(SP1, "Created TAP physical network interface.");
break;
#if defined(__APPLE__)
case BBADeviceType::TAPSERVER:
m_network_interface = std::make_unique<TAPServerNetworkInterface>(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

View File

@ -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:

View File

@ -100,12 +100,16 @@ void GameCubePane::CreateWidgets()
// Add SP1 devices
for (const auto& entry :
{std::make_pair(tr("<Nothing>"), 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<std::pair<QString, ExpansionInterface::TEXIDevices>> sp1Entries{
std::make_pair(tr("<Nothing>"), 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);
}