#include "xray.h" #include "core/utils/networkUtilities.h" #include #include #include #include #include #include #ifdef Q_OS_DARWIN #include #include #include #include #include #include #include #include #include #include #endif #ifdef Q_OS_WIN #include #include #endif #ifdef Q_OS_LINUX #include #endif bool Xray::startXray(const QString &cfg) { qDebug() << "Xray::startXray()"; auto defaultIface = NetworkUtilities::getGatewayAndIface().second; #ifdef Q_OS_LINUX m_defaultIfaceName = defaultIface.name().toUtf8(); #else m_defaultIfaceIdx = defaultIface.index(); #endif if (auto err = amnezia_xray_setsockcallback(ctxSockCallback, this); err != nullptr) { qDebug() << "[xray] sockopt failed: " << err; amnezia_xray_free(err); return false; } amnezia_xray_setloghandler(ctxLogHandler, this); QByteArray bytes = cfg.toUtf8(); // Temporary aid for manual verification of SOCKS auth (inbounds.settings.accounts). // Debug: always writes. Release: set env AMNEZIA_SAVE_XRAY_CONFIG=1 (if the daemon inherits it). { #if defined(MZ_DEBUG) const bool saveForTest = true; #else const bool saveForTest = !qgetenv("AMNEZIA_SAVE_XRAY_CONFIG").isEmpty(); #endif if (saveForTest) { #ifdef Q_OS_WIN const QString debugPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QStringLiteral("/amnezia-xray-config.json"); #else const QString debugPath = QStringLiteral("/tmp/amnezia-xray-config.json"); #endif QFile f(debugPath); if (f.open(QIODevice::WriteOnly | QIODevice::Truncate)) { f.write(bytes); f.close(); qDebug() << "[xray] wrote last config for testing:" << debugPath; } else { qWarning() << "[xray] failed to write test config to" << debugPath; } } } if (auto err = amnezia_xray_configure(bytes.data()); err != nullptr) { qDebug() << "[xray] configuration failed: " << err; amnezia_xray_free(err); return false; } if (auto err = amnezia_xray_start(); err != nullptr) { qDebug() << "[xray] failed to start: " << err; amnezia_xray_free(err); return false; } return true; } bool Xray::stopXray() { qDebug() << "Xray::stopXray()"; if (auto err = amnezia_xray_stop(); err != nullptr) { qDebug() << "[xray] failed to stop: " << err; amnezia_xray_free(err); return false; } return true; } void Xray::logHandler(char* str) { QMetaObject::invokeMethod(qApp, [str = QString::fromUtf8(str)] { qDebug() << "[xray]" << str; }, Qt::QueuedConnection); } void Xray::sockCallback(uintptr_t fd) { #ifdef Q_OS_MAC if (m_defaultIfaceIdx > 0) { setsockopt(fd, IPPROTO_IP, IP_BOUND_IF, &m_defaultIfaceIdx, sizeof(m_defaultIfaceIdx)); setsockopt(fd, IPPROTO_IPV6, IPV6_BOUND_IF, &m_defaultIfaceIdx, sizeof(m_defaultIfaceIdx)); } #endif #ifdef Q_OS_WIN if (DWORD idx = m_defaultIfaceIdx; idx > 0) { setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_IF, reinterpret_cast(&idx), sizeof(idx)); idx = htonl(idx); // IP_UNICAST_IF expects index in network byte order setsockopt(fd, IPPROTO_IP, IP_UNICAST_IF, reinterpret_cast(&idx), sizeof(idx)); } #endif #ifdef Q_OS_LINUX if (!m_defaultIfaceName.isEmpty()) { setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, m_defaultIfaceName.data(), m_defaultIfaceName.size()); } #endif }