Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Faking uinput: mount additional virtual devices without breaking container isolation #88

Draft
wants to merge 2 commits into
base: stable
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ add_subdirectory(src/core)

if (UNIX AND NOT APPLE)
add_subdirectory(src/fake-udev)
add_subdirectory(src/fake-uinput)
endif ()

option(BUILD_MOONLIGHT "Build Moonlight server" ON)
Expand Down
18 changes: 18 additions & 0 deletions src/fake-uinput/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
option(BUILD_FAKE_UINPUT "Build fake-uinput library" ON)
if (BUILD_FAKE_UINPUT)
message(STATUS "Building fake-uinput")

add_library(fake_uinput SHARED
fake-uinput.cpp)
add_library(fake-uinput::lib ALIAS fake_uinput)

target_compile_features(fake_uinput PRIVATE cxx_std_17)
option(FAKE_UINPUT_32BIT "Build fake-uinput 32-bit library" OFF)
if (FAKE_UINPUT_32BIT)
set_target_properties(fake_uinput PROPERTIES
# Force it to 32 bit
COMPILE_FLAGS "-m32" LINK_FLAGS "-m32"
# Change the name of the library to indicate it's 32-bit
OUTPUT_NAME "fake-uinput-32")
endif ()
endif ()
98 changes: 98 additions & 0 deletions src/fake-uinput/fake-uinput.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#include <cstdarg>
#include <cstring>
#include <dlfcn.h>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <linux/uinput.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>

/** https://github.com/whot/libevdev/blob/c3953e1bb8f813f3e248047d23fb0775b147da9f/libevdev/libevdev-uinput.c#L41 **/
#define SYS_INPUT_DIR "/sys/devices/virtual/input/"

#define LOG(Thing) std::cout << "[fake-uinput] " << Thing << std::endl;

typedef int (*ioctl_t)(int, unsigned long, ...);
static ioctl_t real_ioctl = nullptr;

bool load_library() {
LOG("Loading ...")
// Load the real ioctl
real_ioctl = reinterpret_cast<ioctl_t>(dlsym(RTLD_NEXT, "ioctl"));
if (!real_ioctl) {
LOG("Error: " << dlerror());
return false;
}
return true;
}

void mount(const std::filesystem::path &sysfs_path, const std::string &filename) {
std::filesystem::path path(filename);
if (!std::filesystem::exists(path)) {
// Get the major and minor numbers of the device
// They are handily available in the sysfs directory
std::ifstream major_minor_file(sysfs_path / "dev");
unsigned int major, minor;
char sep;
major_minor_file >> major >> sep >> minor;

int rc = mknod(filename.c_str(), S_IFCHR | 0666, makedev(major, minor));
if (rc == -1) {
LOG("Error creating device node: " << strerror(errno));
} else {
LOG("Created device node: " << filename);
}
}
}

/**
* glibc, BSD
* TODO: musl uses int op instead of unsigned long
*/
extern "C" int ioctl(int fd, unsigned long request, ...) {
if (!real_ioctl) {
if (!load_library()) {
LOG("Error: real_ioctl is not initialized");
return -1;
}
}

// Call the real ioctl
va_list args;
va_start(args, request);
void *arg = va_arg(args, void *);
va_end(args);
int result = real_ioctl(fd, request, arg);

if (result >= 0 && request == UI_DEV_CREATE) { // Creating a new uinput device
LOG("Intercepted UI_DEV_CREATE ioctl call");

// Get the sysfs name of the created uinput device
char buf[sizeof(SYS_INPUT_DIR) + 64] = SYS_INPUT_DIR;
auto rc = real_ioctl(fd, UI_GET_SYSNAME(sizeof(buf) - strlen(SYS_INPUT_DIR)), &buf[strlen(SYS_INPUT_DIR)]);
if (rc != -1) {
// iterate over the files under /sys/devices/virtual/input/ and find "eventX" and "jsX" files
for (auto &p : std::filesystem::directory_iterator(buf)) {
if (p.is_directory()) {
auto path = p.path();
auto filename = path.filename().string();
if (filename.find("event") != std::string::npos) {
mount(path, "/dev/input/" + filename);
} else if (filename.find("js") != std::string::npos) {
mount(path, "/dev/input/" + filename);
}
}
}

// TODO: udev? using /sys/devices/virtual/input/{buf}/uevent ?

} else {
LOG("Error getting sysname: " << strerror(errno));
}
}

// return the original result
return result;
}
25 changes: 15 additions & 10 deletions src/moonlight-server/control/input_handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -497,21 +497,26 @@ void controller_multi(const CONTROLLER_MULTI_PACKET &pkt,

// Remove the joypad, this will delete the last reference
session.joypads->update([&](state::JoypadList joypads) { return joypads.erase(pkt.controller_number); });
return;
}
} else {
// Old Moonlight doesn't support CONTROLLER_ARRIVAL, we create a default pad when it's first mentioned
selected_pad = create_new_joypad(session, connected_clients, pkt.controller_number, XBOX, ANALOG_TRIGGERS | RUMBLE);
}
std::visit(
[pkt](inputtino::Joypad &pad) {
std::uint16_t bf = pkt.button_flags;
std::uint32_t bf2 = pkt.buttonFlags2;
pad.set_pressed_buttons(bf | (bf2 << 16));
pad.set_stick(inputtino::Joypad::LS, pkt.left_stick_x, pkt.left_stick_y);
pad.set_stick(inputtino::Joypad::RS, pkt.right_stick_x, pkt.right_stick_y);
pad.set_triggers(pkt.left_trigger, pkt.right_trigger);
},
*selected_pad);

// Update the joypad state
if (selected_pad) {
std::visit(
[pkt](inputtino::Joypad &pad) {
std::uint16_t bf = pkt.button_flags;
std::uint32_t bf2 = pkt.buttonFlags2;
pad.set_pressed_buttons(bf | (bf2 << 16));
pad.set_stick(inputtino::Joypad::LS, pkt.left_stick_x, pkt.left_stick_y);
pad.set_stick(inputtino::Joypad::RS, pkt.right_stick_x, pkt.right_stick_y);
pad.set_triggers(pkt.left_trigger, pkt.right_trigger);
},
*selected_pad);
}
}

void controller_touch(const CONTROLLER_TOUCH_PACKET &pkt, state::StreamSession &session) {
Expand Down
Loading