Files
cd-amn c0cae0ff01 fix: outbound freedom for xray (#2479)
* fix: outbound freedom for xray on linux

* fix: outbound freedom for xray on macOS

* build: auto-generate pf rules based on the build type
2026-05-04 19:39:07 +08:00

134 lines
3.8 KiB
C++

#include "xray.h"
#include "core/utils/networkUtilities.h"
#ifdef Q_OS_MAC
#include "router_mac.h"
#endif
#include <QDebug>
#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>
#include "xray_defs.h"
#endif
bool Xray::startXray(const QString &cfg)
{
qDebug() << "Xray::startXray()";
const auto gatewayAndIface = NetworkUtilities::getGatewayAndIface();
const QString defaultGateway = gatewayAndIface.first;
const QNetworkInterface defaultIface = gatewayAndIface.second;
#ifdef Q_OS_LINUX
m_defaultIfaceName = defaultIface.name().toUtf8();
#else
m_defaultIfaceIdx = defaultIface.index();
#endif
if (defaultIface.index() > 0) {
qDebug() << "[xray] using uplink interface:" << defaultIface.name() << "(" << defaultIface.index() << ")";
}
#ifdef Q_OS_MAC
m_uplinkIfaceName = defaultIface.name();
m_uplinkGateway = defaultGateway;
if (!m_uplinkIfaceName.isEmpty()) {
const bool installed = RouterMac::Instance().routeAddXray(m_uplinkIfaceName, m_uplinkGateway);
if (!installed) {
qWarning() << "[xray] failed to install xray routes on" << m_uplinkIfaceName;
}
}
#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();
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()";
bool success = true;
if (auto err = amnezia_xray_stop(); err != nullptr) {
qDebug() << "[xray] failed to stop: " << err;
amnezia_xray_free(err);
success = false;
}
#ifdef Q_OS_MAC
if (!m_uplinkIfaceName.isEmpty()) {
RouterMac::Instance().routeDeleteXray(m_uplinkIfaceName, m_uplinkGateway);
}
m_uplinkIfaceName.clear();
m_uplinkGateway.clear();
#endif
return success;
}
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());
setsockopt(fd, SOL_SOCKET, SO_MARK, &amnezia::xray::xrayTrafficMark, sizeof(amnezia::xray::xrayTrafficMark));
}
#endif
}