/src/core/kernel/V2RayKernelInteractions.cpp

https://github.com/Qv2ray/Qv2ray · C++ · 368 lines · 299 code · 42 blank · 27 comment · 36 complexity · 2bc07bc41c9eace9c59d55c75c2b34b0 MD5 · raw file

  1. #include "V2RayKernelInteractions.hpp"
  2. #include "APIBackend.hpp"
  3. #include "common/QvHelpers.hpp"
  4. #include "core/connection/ConnectionIO.hpp"
  5. #include <QProcess>
  6. namespace Qv2ray::core::kernel
  7. {
  8. std::pair<bool, std::optional<QString>> V2RayKernelInstance::CheckAndSetCoreExecutableState(const QString &vCorePath)
  9. {
  10. #ifdef Q_OS_UNIX
  11. // For Linux/macOS users: if they cannot execute the core,
  12. // then we shall grant the permission to execute it.
  13. QFile coreFile(vCorePath);
  14. if (!coreFile.permissions().testFlag(QFileDevice::ExeUser))
  15. {
  16. DEBUG(MODULE_VCORE, "Core file not executable. Trying to enable.")
  17. const auto result = coreFile.setPermissions(coreFile.permissions().setFlag(QFileDevice::ExeUser));
  18. if (!result)
  19. {
  20. DEBUG(MODULE_VCORE, "Failed to enable executable permission.")
  21. const auto message = tr("Core file is lacking executable permission for the current user.") % //
  22. tr("Qv2ray tried to set, but failed because permission denied.");
  23. return { false, message };
  24. }
  25. else
  26. {
  27. DEBUG(MODULE_VCORE, "Core executable permission set.")
  28. }
  29. }
  30. else
  31. {
  32. DEBUG(MODULE_VCORE, "Core file is executable.")
  33. }
  34. // Also do the same thing for v2ctl.
  35. // TODO: Simplify This / Extract This Creepy Thing
  36. const auto coreControlFilePath =
  37. QDir::cleanPath(QFileInfo(coreFile).absoluteDir().path() + QDir::separator() + "v2ctl" QV2RAY_EXECUTABLE_FILENAME_SUFFIX);
  38. QFile coreControlFile(coreControlFilePath);
  39. if (!coreControlFile.permissions().testFlag(QFileDevice::ExeUser))
  40. {
  41. DEBUG(MODULE_VCORE, "Core control file not executable. Trying to enable.")
  42. const auto result = coreControlFile.setPermissions(coreFile.permissions().setFlag(QFileDevice::ExeUser));
  43. if (!result)
  44. {
  45. DEBUG(MODULE_VCORE, "Failed to enable executable permission for core control.")
  46. const auto message = tr("Core control file is lacking executable permission for the current user.") + //
  47. tr("Qv2ray tried to set, but failed because permission denied.");
  48. return { false, message };
  49. }
  50. else
  51. {
  52. DEBUG(MODULE_VCORE, "Core control executable permission set.")
  53. }
  54. }
  55. else
  56. {
  57. DEBUG(MODULE_VCORE, "Core control file is executable.")
  58. }
  59. return { true, std::nullopt };
  60. #endif
  61. // For Windows and other users: just skip this check.
  62. DEBUG(MODULE_VCORE, "Skipped check and set core executable state.")
  63. return { true, tr("Check is skipped") };
  64. }
  65. bool V2RayKernelInstance::ValidateKernel(const QString &vCorePath, const QString &vAssetsPath, QString *message)
  66. {
  67. QFile coreFile(vCorePath);
  68. if (!coreFile.exists())
  69. {
  70. DEBUG(MODULE_VCORE, "V2Ray core file cannot be found.")
  71. *message = tr("V2Ray core executable not found.");
  72. return false;
  73. }
  74. // Use open() here to prevent `executing` a folder, which may have the
  75. // same name as the V2Ray core.
  76. if (!coreFile.open(QFile::ReadOnly))
  77. {
  78. DEBUG(MODULE_VCORE, "V2Ray core file cannot be opened, possibly be a folder?")
  79. *message = tr("V2Ray core file cannot be opened, please ensure there's a file instead of a folder.");
  80. return false;
  81. }
  82. coreFile.close();
  83. // Get Core ABI.
  84. const auto [abi, err] = kernel::abi::deduceKernelABI(vCorePath);
  85. if (err)
  86. {
  87. LOG(MODULE_VCORE, "Core ABI deduction failed: " + ACCESS_OPTIONAL_VALUE(err))
  88. *message = ACCESS_OPTIONAL_VALUE(err);
  89. return false;
  90. }
  91. LOG(MODULE_VCORE, "Core ABI: " + kernel::abi::abiToString(ACCESS_OPTIONAL_VALUE(abi)))
  92. // Get Compiled ABI
  93. auto compiledABI = kernel::abi::COMPILED_ABI_TYPE;
  94. LOG(MODULE_VCORE, "Host ABI: " + kernel::abi::abiToString(compiledABI))
  95. // Check ABI Compatibility.
  96. switch (kernel::abi::checkCompatibility(compiledABI, ACCESS_OPTIONAL_VALUE(abi)))
  97. {
  98. case kernel::abi::ABI_NOPE:
  99. {
  100. LOG(MODULE_VCORE, "Host is incompatible with core")
  101. *message = tr("V2Ray core is incompatible with your platform.\r\n" //
  102. "Expected core ABI is %1, but got actual %2.\r\n" //
  103. "Maybe you have downloaded the wrong core?")
  104. .arg(kernel::abi::abiToString(compiledABI), kernel::abi::abiToString(ACCESS_OPTIONAL_VALUE(abi)));
  105. return false;
  106. }
  107. case kernel::abi::ABI_MAYBE:
  108. {
  109. LOG(MODULE_VCORE, "WARNING: Host maybe incompatible with core");
  110. break;
  111. }
  112. case kernel::abi::ABI_PERFECT:
  113. {
  114. LOG(MODULE_VCORE, "Host is compatible with core");
  115. break;
  116. }
  117. }
  118. // Check executable permissions.
  119. const auto [isExecutableOk, strExecutableErr] = CheckAndSetCoreExecutableState(vCorePath);
  120. if (!isExecutableOk)
  121. {
  122. *message = strExecutableErr.value_or("");
  123. return false;
  124. }
  125. //
  126. // Check file existance.
  127. // From: https://www.v2fly.org/chapter_02/env.html#asset-location
  128. //
  129. bool hasGeoIP = FileExistsIn(QDir(vAssetsPath), "geoip.dat");
  130. bool hasGeoSite = FileExistsIn(QDir(vAssetsPath), "geosite.dat");
  131. if (!hasGeoIP && !hasGeoSite)
  132. {
  133. DEBUG(MODULE_VCORE, "V2Ray assets path contains none of those two files.")
  134. *message = tr("V2Ray assets path is not valid.");
  135. return false;
  136. }
  137. if (!hasGeoIP)
  138. {
  139. DEBUG(MODULE_VCORE, "No geoip.dat in assets path, aborting.")
  140. *message = tr("No geoip.dat in assets path.");
  141. return false;
  142. }
  143. if (!hasGeoSite)
  144. {
  145. DEBUG(MODULE_VCORE, "No geosite.dat in assets path, aborting.")
  146. *message = tr("No geosite.dat in assets path.");
  147. return false;
  148. }
  149. // Check if V2Ray core returns a version number correctly.
  150. QProcess proc;
  151. #ifdef Q_OS_WIN32
  152. // nativeArguments are required for Windows platform, without a
  153. // reason...
  154. proc.setProcessChannelMode(QProcess::MergedChannels);
  155. proc.setProgram(vCorePath);
  156. proc.setNativeArguments("--version");
  157. proc.start();
  158. #else
  159. proc.start(vCorePath, { "--version" });
  160. #endif
  161. proc.waitForStarted();
  162. proc.waitForFinished();
  163. auto exitCode = proc.exitCode();
  164. if (exitCode != 0)
  165. {
  166. DEBUG(MODULE_VCORE, "VCore failed with an exit code: " + QSTRN(exitCode))
  167. *message = tr("V2Ray core failed with an exit code: ") + QSTRN(exitCode);
  168. return false;
  169. }
  170. QString output = proc.readAllStandardOutput();
  171. LOG(MODULE_VCORE, "V2Ray output: " + SplitLines(output).join(";"))
  172. if (SplitLines(output).isEmpty())
  173. {
  174. *message = tr("V2Ray core returns empty string.");
  175. return false;
  176. }
  177. *message = SplitLines(output).first();
  178. return true;
  179. }
  180. bool V2RayKernelInstance::ValidateConfig(const QString &path)
  181. {
  182. QString V2RayCheckResult;
  183. auto kernelPath = GlobalConfig.kernelConfig.KernelPath();
  184. auto assetsPath = GlobalConfig.kernelConfig.AssetsPath();
  185. if (ValidateKernel(kernelPath, assetsPath, &V2RayCheckResult))
  186. {
  187. DEBUG(MODULE_VCORE, "V2Ray version: " + V2RayCheckResult)
  188. // Append assets location env.
  189. QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
  190. env.insert("V2RAY_LOCATION_ASSET", assetsPath);
  191. //
  192. QProcess process;
  193. process.setProcessEnvironment(env);
  194. DEBUG(MODULE_VCORE, "Starting V2Ray core with test options")
  195. process.start(kernelPath, QStringList{ "-test", "-config", path }, QIODevice::ReadWrite | QIODevice::Text);
  196. process.waitForFinished();
  197. if (process.exitCode() != 0)
  198. {
  199. QString output = QString(process.readAllStandardOutput());
  200. QvMessageBoxWarn(nullptr, tr("Configuration Error"), output.mid(output.indexOf("anti-censorship.") + 17));
  201. return false;
  202. }
  203. else
  204. {
  205. DEBUG(MODULE_VCORE, "Config file check passed.")
  206. return true;
  207. }
  208. }
  209. else
  210. {
  211. QvMessageBoxWarn(nullptr, tr("Cannot start V2Ray"), //
  212. tr("V2Ray core settings is incorrect.") + NEWLINE + NEWLINE + //
  213. tr("The error is: ") + NEWLINE + V2RayCheckResult);
  214. return false;
  215. }
  216. }
  217. V2RayKernelInstance::V2RayKernelInstance(QObject *parent) : QObject(parent)
  218. {
  219. vProcess = new QProcess();
  220. connect(vProcess, &QProcess::readyReadStandardOutput, this,
  221. [&]() { emit OnProcessOutputReadyRead(vProcess->readAllStandardOutput().trimmed()); });
  222. connect(vProcess, &QProcess::stateChanged, [&](QProcess::ProcessState state) {
  223. DEBUG(MODULE_VCORE, "V2Ray kernel process status changed: " + QVariant::fromValue(state).toString())
  224. // If V2Ray crashed AFTER we start it.
  225. if (KernelStarted && state == QProcess::NotRunning)
  226. {
  227. LOG(MODULE_VCORE, "V2Ray kernel crashed.")
  228. StopConnection();
  229. emit OnProcessErrored("V2Ray kernel crashed.");
  230. }
  231. });
  232. apiWorker = new APIWorker();
  233. qRegisterMetaType<StatisticsType>();
  234. qRegisterMetaType<QMap<StatisticsType, QvStatsSpeed>>();
  235. connect(apiWorker, &APIWorker::onAPIDataReady, this, &V2RayKernelInstance::OnNewStatsDataArrived);
  236. KernelStarted = false;
  237. }
  238. std::optional<QString> V2RayKernelInstance::StartConnection(const CONFIGROOT &root)
  239. {
  240. if (KernelStarted)
  241. {
  242. LOG(MODULE_VCORE, "Status is invalid, expect STOPPED when calling StartConnection")
  243. return tr("Invalid V2Ray Instance Status.");
  244. }
  245. // Write the final configuration to the disk.
  246. QString json = JsonToString(root);
  247. StringToFile(json, QV2RAY_GENERATED_FILE_PATH);
  248. //
  249. auto filePath = QV2RAY_GENERATED_FILE_PATH;
  250. if (ValidateConfig(filePath))
  251. {
  252. QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
  253. env.insert("V2RAY_LOCATION_ASSET", GlobalConfig.kernelConfig.AssetsPath());
  254. vProcess->setProcessEnvironment(env);
  255. vProcess->start(GlobalConfig.kernelConfig.KernelPath(), { "-config", filePath }, QIODevice::ReadWrite | QIODevice::Text);
  256. vProcess->waitForStarted();
  257. DEBUG(MODULE_VCORE, "V2Ray core started.")
  258. KernelStarted = true;
  259. QMap<bool, QMap<QString, QString>> tagProtocolMap;
  260. for (const auto isOutbound : { GlobalConfig.uiConfig.graphConfig.useOutboundStats, false })
  261. {
  262. for (const auto &item : root[isOutbound ? "outbounds" : "inbounds"].toArray())
  263. {
  264. const auto tag = item.toObject()["tag"].toString("");
  265. if (tag == API_TAG_INBOUND)
  266. continue;
  267. if (tag.isEmpty())
  268. {
  269. LOG(MODULE_VCORE, "Ignored inbound with empty tag.")
  270. continue;
  271. }
  272. tagProtocolMap[isOutbound][tag] = item.toObject()["protocol"].toString();
  273. }
  274. }
  275. apiEnabled = false;
  276. if (StartupOption.noAPI)
  277. {
  278. LOG(MODULE_VCORE, "API has been disabled by the command line arguments")
  279. }
  280. else if (!GlobalConfig.kernelConfig.enableAPI)
  281. {
  282. LOG(MODULE_VCORE, "API has been disabled by the global config option")
  283. }
  284. else if (tagProtocolMap.isEmpty())
  285. {
  286. LOG(MODULE_VCORE, "RARE: API is disabled since no inbound tags configured. This is usually caused by a bad complex config.")
  287. }
  288. else
  289. {
  290. DEBUG(MODULE_VCORE, "Starting API")
  291. apiWorker->StartAPI(tagProtocolMap);
  292. apiEnabled = true;
  293. }
  294. return std::nullopt;
  295. }
  296. else
  297. {
  298. KernelStarted = false;
  299. return tr("V2Ray kernel failed to start.");
  300. }
  301. }
  302. void V2RayKernelInstance::StopConnection()
  303. {
  304. if (apiEnabled)
  305. {
  306. apiWorker->StopAPI();
  307. apiEnabled = false;
  308. }
  309. // Set this to false BEFORE close the Process, since we need this flag
  310. // to capture the real kernel CRASH
  311. KernelStarted = false;
  312. vProcess->close();
  313. // Block until V2Ray core exits
  314. // Should we use -1 instead of waiting for 30secs?
  315. vProcess->waitForFinished();
  316. }
  317. V2RayKernelInstance::~V2RayKernelInstance()
  318. {
  319. if (KernelStarted)
  320. {
  321. StopConnection();
  322. }
  323. delete apiWorker;
  324. delete vProcess;
  325. }
  326. } // namespace Qv2ray::core::kernel