From 062c947ce826e187e476446c256073ef829e552d Mon Sep 17 00:00:00 2001 From: arm64v8a <48624112+arm64v8a@users.noreply.github.com> Date: Sat, 22 Oct 2022 14:09:54 +0900 Subject: [PATCH] feat: macos vpn feat: macos & linux autorun --- db/ConfigBuilder.cpp | 6 ++ examples/sing-box-vpn.json | 2 +- examples/vpn-run-root.sh | 10 ++- sys/AutoRun.cpp | 168 +++++++++++++++++++++++++++++++++++-- ui/mainwindow.cpp | 10 +++ 5 files changed, 189 insertions(+), 7 deletions(-) diff --git a/db/ConfigBuilder.cpp b/db/ConfigBuilder.cpp index 97195018f..396a31376 100644 --- a/db/ConfigBuilder.cpp +++ b/db/ConfigBuilder.cpp @@ -760,6 +760,11 @@ namespace NekoRay { {"ip_cidr", QList2QJsonArray(arr)}}; cidr_rule = "," + QJsonObject2QString(rule, false); } + // + auto tun_name = "nekoray_tun"; +#ifdef Q_OS_MACOS + tun_name = "utun9"; +#endif // gen config auto configFn = ":/neko/vpn/sing-box-vpn.json"; if (QFile::exists("vpn/sing-box-vpn.json")) configFn = "vpn/sing-box-vpn.json"; @@ -769,6 +774,7 @@ namespace NekoRay { .replace("%STACK%", Preset::SingBox::VpnImplementation.value(dataStore->vpn_implementation)) .replace("%PROCESS_NAME_RULE%", process_name_rule) .replace("%CIDR_RULE%", cidr_rule) + .replace("%TUN_NAME%", tun_name) .replace("%PORT%", Int2String(dataStore->inbound_socks_port)); // write config QFile file; diff --git a/examples/sing-box-vpn.json b/examples/sing-box-vpn.json index 3d963fa64..545a80ba6 100644 --- a/examples/sing-box-vpn.json +++ b/examples/sing-box-vpn.json @@ -2,7 +2,7 @@ "inbounds": [ { "type": "tun", - "interface_name": "nekoray-tun", + "interface_name": "%TUN_NAME%", "inet4_address": "172.19.0.1/28", %IPV6_ADDRESS% "mtu": %MTU%, diff --git a/examples/vpn-run-root.sh b/examples/vpn-run-root.sh index cb8ba5bf3..e7fa50e43 100755 --- a/examples/vpn-run-root.sh +++ b/examples/vpn-run-root.sh @@ -7,6 +7,10 @@ if [ "$EUID" -ne 0 ]; then exit fi +if [ "$(uname)" == "Darwin" ]; then + IS_MACOS=1 +fi + [ -z $PORT ] && echo "Please set env PORT" && exit [ -z $TABLE_FWMARK ] && echo "Please set env TABLE_FWMARK" && exit command -v pkill >/dev/null 2>&1 || exit @@ -14,7 +18,7 @@ command -v pkill >/dev/null 2>&1 || exit BASEDIR=$(dirname "$0") cd $BASEDIR -start() { +pre_start_linux() { # set bypass: fwmark ip rule add pref 8999 fwmark $TABLE_FWMARK table main || return ip -6 rule add pref 8999 fwmark $TABLE_FWMARK table main || return @@ -22,11 +26,15 @@ start() { # for Tun2Socket iptables -I INPUT -s 172.19.0.2 -d 172.19.0.1 -p tcp -j ACCEPT ip6tables -I INPUT -s fdfe:dcba:9876::2 -d fdfe:dcba:9876::1 -p tcp -j ACCEPT +} +start() { + [ -z $IS_MACOS ] && pre_start_linux "./nekobox_core" run -c "$CONFIG_PATH" --protect-listen-path "$PROTECT_LISTEN_PATH" --protect-fwmark $TABLE_FWMARK } stop() { + [ -z $IS_MACOS ] || return for local in $BYPASS_IPS; do ip rule del to $local table main done diff --git a/sys/AutoRun.cpp b/sys/AutoRun.cpp index 4570388fa..56cda0748 100644 --- a/sys/AutoRun.cpp +++ b/sys/AutoRun.cpp @@ -1,10 +1,16 @@ #include "AutoRun.hpp" #include +#include + +// macOS headers (possibly OBJ-c) +#if defined(Q_OS_MACOS) +#include +#include +#endif #ifdef Q_OS_WIN -#include #include //设置程序自启动 appPath程序路径 @@ -54,16 +60,168 @@ bool GetProcessAutoRunSelf() { } -#else +#endif -#include +#ifdef Q_OS_MACOS void SetProcessAutoRunSelf(bool enable) { - QMessageBox::warning(nullptr, "Error", "Autorun is not yet implemented on your platform."); + // From + // https://github.com/nextcloud/desktop/blob/master/src/common/utility_mac.cpp + QString filePath = QDir(QCoreApplication::applicationDirPath() + QLatin1String("/../..")).absolutePath(); + CFStringRef folderCFStr = CFStringCreateWithCString(0, filePath.toUtf8().data(), kCFStringEncodingUTF8); + CFURLRef urlRef = CFURLCreateWithFileSystemPath(0, folderCFStr, kCFURLPOSIXPathStyle, true); + LSSharedFileListRef loginItems = LSSharedFileListCreate(0, kLSSharedFileListSessionLoginItems, 0); + + if (loginItems && enable) { + // Insert an item to the list. + LSSharedFileListItemRef item = + LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemLast, 0, 0, urlRef, 0, 0); + + if (item) CFRelease(item); + + CFRelease(loginItems); + } else if (loginItems && !enable) { + // We need to iterate over the items and check which one is "ours". + UInt32 seedValue; + CFArrayRef itemsArray = LSSharedFileListCopySnapshot(loginItems, &seedValue); + CFStringRef appUrlRefString = CFURLGetString(urlRef); + + for (int i = 0; i < CFArrayGetCount(itemsArray); i++) { + LSSharedFileListItemRef item = (LSSharedFileListItemRef) CFArrayGetValueAtIndex(itemsArray, i); + CFURLRef itemUrlRef = NULL; + + if (LSSharedFileListItemResolve(item, 0, &itemUrlRef, NULL) == noErr && itemUrlRef) { + CFStringRef itemUrlString = CFURLGetString(itemUrlRef); + + if (CFStringCompare(itemUrlString, appUrlRefString, 0) == kCFCompareEqualTo) { + LSSharedFileListItemRemove(loginItems, item); // remove it! + } + + CFRelease(itemUrlRef); + } + } + + CFRelease(itemsArray); + CFRelease(loginItems); + } + + CFRelease(folderCFStr); + CFRelease(urlRef); +} + +bool GetProcessAutoRunSelf() { + // From + // https://github.com/nextcloud/desktop/blob/master/src/common/utility_mac.cpp + // this is quite some duplicate code with setLaunchOnStartup, at some + // point we should fix this FIXME. + bool returnValue = false; + QString filePath = QDir(QCoreApplication::applicationDirPath() + QLatin1String("/../..")).absolutePath(); + CFStringRef folderCFStr = CFStringCreateWithCString(0, filePath.toUtf8().data(), kCFStringEncodingUTF8); + CFURLRef urlRef = CFURLCreateWithFileSystemPath(0, folderCFStr, kCFURLPOSIXPathStyle, true); + LSSharedFileListRef loginItems = LSSharedFileListCreate(0, kLSSharedFileListSessionLoginItems, 0); + + if (loginItems) { + // We need to iterate over the items and check which one is "ours". + UInt32 seedValue; + CFArrayRef itemsArray = LSSharedFileListCopySnapshot(loginItems, &seedValue); + CFStringRef appUrlRefString = CFURLGetString(urlRef); // no need for release + + for (int i = 0; i < CFArrayGetCount(itemsArray); i++) { + LSSharedFileListItemRef item = (LSSharedFileListItemRef) CFArrayGetValueAtIndex(itemsArray, i); + CFURLRef itemUrlRef = NULL; + + if (LSSharedFileListItemResolve(item, 0, &itemUrlRef, NULL) == noErr && itemUrlRef) { + CFStringRef itemUrlString = CFURLGetString(itemUrlRef); + + if (CFStringCompare(itemUrlString, appUrlRefString, 0) == kCFCompareEqualTo) { + returnValue = true; + } + + CFRelease(itemUrlRef); + } + } + + CFRelease(itemsArray); + } + + CFRelease(loginItems); + CFRelease(folderCFStr); + CFRelease(urlRef); + return returnValue; +} + +#endif + +#ifdef Q_OS_LINUX + +#include +#include + +#define NEWLINE "\r\n" + +// launchatlogin.cpp +// ShadowClash +// +// Created by TheWanderingCoel on 2018/6/12. +// Copyright © 2019 Coel Wu. All rights reserved. +// +QString getUserAutostartDir_private() { + QString config = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); + config += QLatin1String("/autostart/"); + return config; +} + +void SetProcessAutoRunSelf(bool enable) { + // From https://github.com/nextcloud/desktop/blob/master/src/common/utility_unix.cpp + QString appName = QCoreApplication::applicationName(); + QString userAutoStartPath = getUserAutostartDir_private(); + QString desktopFileLocation = userAutoStartPath + appName + QLatin1String(".desktop"); + QStringList appCmdList = {QApplication::applicationFilePath()}; + + // nekoray: launcher + auto launcherPath = QApplication::applicationDirPath() + "/launcher"; + if (QFile::exists(launcherPath)) { + appCmdList = QStringList{launcherPath}; + } + + if (enable) { + if (!QDir().exists(userAutoStartPath) && !QDir().mkpath(userAutoStartPath)) { + // qCWarning(lcUtility) << "Could not create autostart folder" + // << userAutoStartPath; + return; + } + + QFile iniFile(desktopFileLocation); + + if (!iniFile.open(QIODevice::WriteOnly)) { + // qCWarning(lcUtility) << "Could not write auto start entry" << + // desktopFileLocation; + return; + } + + QTextStream ts(&iniFile); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + ts.setCodec("UTF-8"); +#endif + ts << QLatin1String("[Desktop Entry]") << NEWLINE // + << QLatin1String("Name=") << appName << NEWLINE // + << QLatin1String("Exec=") << appCmdList.join(" ") << NEWLINE // + << QLatin1String("Terminal=") << "false" << NEWLINE // + << QLatin1String("Categories=") << "Network" << NEWLINE // + << QLatin1String("Type=") << "Application" << NEWLINE // + << QLatin1String("StartupNotify=") << "false" << NEWLINE // + << QLatin1String("X-GNOME-Autostart-enabled=") << "true" << NEWLINE; + ts.flush(); + iniFile.close(); + } else { + QFile::remove(desktopFileLocation); + } } bool GetProcessAutoRunSelf() { - return false; + QString appName = QCoreApplication::applicationName(); + QString desktopFileLocation = getUserAutostartDir_private() + appName + QLatin1String(".desktop"); + return QFile::exists(desktopFileLocation); } #endif diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index 6f899f549..c37e3dca6 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -1477,7 +1477,12 @@ bool MainWindow::StartVPNProcess() { }); // vpn_process->setProcessChannelMode(QProcess::ForwardedChannels); +#ifdef Q_OS_MACOS + vpn_process->start("osascript", {"-e", QString("do shell script \"%1\" with administrator privileges") + .arg("bash " + scriptPath)}); +#else vpn_process->start("pkexec", {"bash", scriptPath}); +#endif vpn_process->waitForStarted(); vpn_pid = vpn_process->processId(); // actually it's pkexec or bash PID #endif @@ -1495,7 +1500,12 @@ bool MainWindow::StopVPNProcess() { ok = ret == 0; #else QProcess p; +#ifdef Q_OS_MACOS + p.start("osascript", {"-e", QString("do shell script \"%1\" with administrator privileges") + .arg("pkill -2 -U 0 nekobox_core")}); +#else p.start("pkexec", {"pkill", "-2", "-P", Int2String(vpn_pid)}); +#endif p.waitForFinished(); ok = p.exitCode() == 0; #endif