mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-08 14:33:23 +00:00
132 lines
3.8 KiB
C++
132 lines
3.8 KiB
C++
#include "xray.h"
|
|
#include "core/utils/networkUtilities.h"
|
|
|
|
#include <QDebug>
|
|
#include <QFile>
|
|
#include <QNetworkInterface>
|
|
#include <QCoreApplication>
|
|
#include <amnezia_xray.h>
|
|
#include <qdebug.h>
|
|
|
|
#ifdef Q_OS_DARWIN
|
|
#include <arpa/inet.h>
|
|
#include <cerrno>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <ifaddrs.h>
|
|
#include <net/if.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/ip.h>
|
|
#include <sys/socket.h>
|
|
#endif
|
|
#ifdef Q_OS_WIN
|
|
#include <winsock2.h>
|
|
#include <ws2tcpip.h>
|
|
#endif
|
|
#ifdef Q_OS_LINUX
|
|
#include <sys/socket.h>
|
|
#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<char *>(&idx), sizeof(idx));
|
|
idx = htonl(idx); // IP_UNICAST_IF expects index in network byte order
|
|
setsockopt(fd, IPPROTO_IP, IP_UNICAST_IF, reinterpret_cast<char *>(&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
|
|
}
|