feat: native split-tunneling for xray (#1899)

* feat: integrated xray as a library and added split-tunneling

* fix: added copying amnezia_xray.dll to build dir

* fix: changed path on darwin

* chore: clean up getting default device

* chore: removed WSAGetLastError from sockopt logging

* fix: get rid of debug logs in xray handlers

* fix: minor fixes and xray debugging capabilities

* fix: macos default interface fix

* fix: roll-back ipv6 sockopt for mac

* fix: bind IPv6 on Windows

* fix: (win) better IPv6 handling and router fixes

* feat: prebuilts uploaded

* fix: removed redundant cmake definitions

* feat: moved xray to service process, reworked errors

* fix: return values in networkUtilities

* fix: macos build fixes

* fix: (windows) cmake fixes

* fix: (windows) compilation fix

* fix: (windows) changed location of amnezia_xray.dll

* feat: xray logs added to system service

* chore: bump xray&tun2socks versions for android

* chore: cleanup of XrayProtocol class
* removed killswitch
* removed redundant members and basic cleanup

* feat: support split-tunneling in iOS and macOS NE

* chore: update active interface index based on network path and available interfaces

* refactor: update network path handling and logging in PacketTunnelProvider

* chore: bump xray deps

---------

Co-authored-by: Yaroslav Yashin <yaroslav.yashin@gmail.com>
This commit is contained in:
Yaroslav Gurov
2025-12-15 14:54:34 +01:00
committed by GitHub
parent d669adb707
commit 54f67b3d82
21 changed files with 455 additions and 207 deletions

View File

@@ -166,7 +166,7 @@ class Xray : Protocol() {
mtu = config.mtu.toLong()
proxy = "socks5://127.0.0.1:${config.socksPort}"
device = "fd://$fd"
logLevel = "warning"
logLevel = "warn"
}
LibXray.startTun2Socks(tun2SocksConfig, fd.toLong()).isNotNullOrBlank { err ->
throw VpnStartException("Failed to start tun2socks: $err")

View File

@@ -38,7 +38,7 @@ elseif(APPLE AND NOT IOS)
endif()
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/macos/include")
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libssl.a")
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libcrypto.a")
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libcrypto.a")
elseif(IOS)
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/ios/arm64")
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/ios/arm64/libssh.a")
@@ -62,7 +62,7 @@ elseif(LINUX)
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/linux/x86_64/libssl.a")
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/linux/x86_64/libcrypto.a")
endif()
file(COPY ${OPENSSL_LIB_SSL_PATH} ${OPENSSL_LIB_CRYPTO_PATH}
DESTINATION ${OPENSSL_LIBRARIES_DIR})

View File

@@ -1,11 +1,12 @@
#include "networkUtilities.h"
#include <QtNetwork/qnetworkinterface.h>
#include <cstddef>
#ifdef Q_OS_WIN
#include <windows.h>
#include <Ipexport.h>
#include <Ws2tcpip.h>
#include <ws2ipdef.h>
#include <stdint.h>
#include <Iphlpapi.h>
#include <Iptypes.h>
#include <WinSock2.h>
@@ -30,6 +31,15 @@
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/route.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <ifaddrs.h>
#include <net/if.h>
#endif
#include <QHostAddress>
@@ -239,12 +249,14 @@ DWORD GetAdaptersAddressesWrapper(const ULONG Family,
}
#endif
QString NetworkUtilities::getGatewayAndIface()
QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
{
#ifdef Q_OS_WIN
constexpr int BUFF_LEN = 100;
char buff[BUFF_LEN] = {'\0'};
QString result;
QString resGateway;
int resIndex = -1;
PIP_ADAPTER_ADDRESSES pAdapterAddresses = nullptr;
DWORD dwRetVal =
@@ -252,7 +264,7 @@ QString NetworkUtilities::getGatewayAndIface()
if (dwRetVal != NO_ERROR) {
qDebug() << "ipv4 stack detect GetAdaptersAddresses failed.";
return "";
return {};
}
PIP_ADAPTER_ADDRESSES pCurAddress = pAdapterAddresses;
@@ -267,7 +279,9 @@ QString NetworkUtilities::getGatewayAndIface()
struct sockaddr_in addr;
if (inet_pton(AF_INET, buff, &addr.sin_addr) == 1) {
qDebug() << "this is true v4 !";
result = gw;
resGateway = gw;
resIndex = pCurAddress->IfIndex;
}
}
}
@@ -275,7 +289,7 @@ QString NetworkUtilities::getGatewayAndIface()
}
free(pAdapterAddresses);
return result;
return { resGateway, QNetworkInterface::interfaceFromIndex(resIndex) };
#endif
#ifdef Q_OS_LINUX
constexpr int BUFFER_SIZE = 100;
@@ -292,7 +306,7 @@ QString NetworkUtilities::getGatewayAndIface()
if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
perror("socket failed");
return "";
return {};
}
memset(msgbuf, 0, sizeof(msgbuf));
@@ -316,7 +330,7 @@ QString NetworkUtilities::getGatewayAndIface()
/* send msg */
if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
perror("send failed");
return "";
return {};
}
/* receive response */
@@ -325,7 +339,7 @@ QString NetworkUtilities::getGatewayAndIface()
received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0);
if (received_bytes < 0) {
perror("Error in recv");
return "";
return {};
}
nlh = (struct nlmsghdr *) ptr;
@@ -335,7 +349,7 @@ QString NetworkUtilities::getGatewayAndIface()
(nlmsg->nlmsg_type == NLMSG_ERROR))
{
perror("Error in received packet");
return "";
return {};
}
/* If we received all data break */
@@ -388,10 +402,12 @@ QString NetworkUtilities::getGatewayAndIface()
}
}
close(sock);
return gateway_address;
return { gateway_address, QNetworkInterface::interfaceFromName(interface) };
#endif
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
QString gateway;
int index = -1;
int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_FLAGS, RTF_GATEWAY};
int afinet_type[] = {AF_INET, AF_INET6};
@@ -401,17 +417,17 @@ QString NetworkUtilities::getGatewayAndIface()
size_t needed = 0;
if (sysctl(mib, sizeof(mib) / sizeof(int), nullptr, &needed, nullptr, 0) < 0)
return "";
return {};
char* buf;
if ((buf = new char[needed]) == 0)
return "";
return {};
if (sysctl(mib, sizeof(mib) / sizeof(int), buf, &needed, nullptr, 0) < 0)
{
qDebug() << "sysctl: net.route.0.0.dump";
delete[] buf;
return gateway;
return {};
}
struct rt_msghdr* rt;
@@ -449,7 +465,10 @@ QString NetworkUtilities::getGatewayAndIface()
&(reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_GATEWAY]))->sin_addr,
sizeof(struct in_addr));
if (inet_ntop(AF_INET, srcStr4, dstStr4, INET_ADDRSTRLEN) != nullptr)
{
gateway = dstStr4;
index = rt->rtm_index;
}
break;
}
}
@@ -463,7 +482,10 @@ QString NetworkUtilities::getGatewayAndIface()
&(reinterpret_cast<struct sockaddr_in6*>(sa_tab[RTAX_GATEWAY]))->sin6_addr,
sizeof(struct in6_addr));
if (inet_ntop(AF_INET6, srcStr6, dstStr6, INET6_ADDRSTRLEN) != nullptr)
{
gateway = dstStr6;
index = rt->rtm_index;
}
break;
}
}
@@ -472,6 +494,6 @@ QString NetworkUtilities::getGatewayAndIface()
free(buf);
}
return gateway;
return { gateway, QNetworkInterface::interfaceFromIndex(index) };
#endif
}

View File

@@ -6,7 +6,7 @@
#include <QString>
#include <QHostAddress>
#include <QNetworkReply>
#include <QtNetwork/qnetworkinterface.h>
class NetworkUtilities : public QObject
{
@@ -17,7 +17,7 @@ public:
static bool checkIPv4Format(const QString &ip);
static bool checkIpSubnetFormat(const QString &ip);
static bool checkIpv6Enabled();
static QString getGatewayAndIface();
static QPair<QString, QNetworkInterface> getGatewayAndIface();
// Returns the Interface Index that could Route to dst
static int AdapterIndexTo(const QHostAddress& dst);

View File

@@ -131,7 +131,7 @@ extension PacketTunnelProvider {
}
startHandler = completionHandler
ovpnAdapter?.connect(using: packetFlow)
ovpnAdapter?.connect(using: openVPNPacketFlow())
}
func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
@@ -153,7 +153,7 @@ extension PacketTunnelProvider {
}
func stopOpenVPN(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
ovpnLog(.info, message: "Stopping tunnel: reason: \(reason.description)")
ovpnLog(.info, message: "Stopping tunnel: reason: \(reason.amneziaDescription)")
stopHandler = completionHandler
if vpnReachability.isTracking {
@@ -293,5 +293,3 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
ovpnLog(.info, message: logMessage)
}
}
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}

View File

@@ -176,7 +176,7 @@ extension PacketTunnelProvider {
}
func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
wg_log(.info, message: "Stopping tunnel: reason: \(reason.description)")
wg_log(.info, message: "Stopping tunnel: reason: \(reason.amneziaDescription)")
wgAdapter?.stop { error in
ErrorNotifier.removeLastErrorFile()

View File

@@ -107,6 +107,8 @@ extension PacketTunnelProvider {
return
}
self?.updateActiveInterfaceIndexForCurrentPath()
// Launch xray
self?.setupAndStartXray(configData: updatedData) { xrayError in
if let xrayError {
@@ -133,6 +135,15 @@ extension PacketTunnelProvider {
completionHandler()
}
func sockCallback(fd: uintptr_t) {
if activeIfaceIdx != 0 {
withUnsafePointer(to: activeIfaceIdx) { ptr in
setsockopt(Int32(fd), IPPROTO_IP, IP_BOUND_IF, ptr, socklen_t(MemoryLayout<UInt32>.size))
setsockopt(Int32(fd), IPPROTO_IPV6, IPV6_BOUND_IF, ptr, socklen_t(MemoryLayout<UInt32>.size))
}
}
}
private func setupAndStartXray(configData: Data,
completionHandler: @escaping (Error?) -> Void) {
let path = Constants.cachesDirectory.appendingPathComponent("config.json", isDirectory: false).path
@@ -142,6 +153,17 @@ extension PacketTunnelProvider {
return
}
updateActiveInterfaceIndexForCurrentPath()
let ctx = Unmanaged.passUnretained(self).toOpaque()
let cb: libxray_sockcallback = { (fd, ctx) in
guard let ctx = ctx else { return }
let instance = Unmanaged<PacketTunnelProvider>.fromOpaque(ctx).takeUnretainedValue()
instance.sockCallback(fd: fd)
}
LibXraySetSockCallback(cb, ctx)
LibXrayRunXray(nil,
path,
Int64.max)

View File

@@ -1,5 +1,6 @@
import Foundation
import NetworkExtension
import Network
import os
import Darwin
import OpenVPNAdapter
@@ -38,6 +39,12 @@ struct Constants {
class PacketTunnelProvider: NEPacketTunnelProvider {
var wgAdapter: WireGuardAdapter?
var ovpnAdapter: OpenVPNAdapter?
private lazy var openVPNPacketFlowAdapter = PacketTunnelFlowAdapter(flow: packetFlow)
private let pathMonitorQueue = DispatchQueue(label: Constants.processQueueName + ".path-monitor")
private let pathMonitor = NWPathMonitor()
private var didReceiveInitialPathUpdate = false
private var currentPath: Network.NWPath?
private var currentPathSignature: String?
var splitTunnelType: Int?
var splitTunnelSites: [String]?
@@ -47,6 +54,73 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
var startHandler: ((Error?) -> Void)?
var stopHandler: (() -> Void)?
var protoType: TunnelProtoType?
var activeIfaceIdx: UInt32 = 0
func openVPNPacketFlow() -> OpenVPNAdapterPacketFlow {
openVPNPacketFlowAdapter
}
override init() {
super.init()
pathMonitor.pathUpdateHandler = { [weak self] path in
guard let self else { return }
self.currentPath = path
let signature = self.pathSignature(for: path)
let hasMeaningfulChange = self.currentPathSignature != signature
self.currentPathSignature = signature
self.updateActiveInterfaceIndex(for: path)
guard self.didReceiveInitialPathUpdate else {
self.didReceiveInitialPathUpdate = true
return
}
guard hasMeaningfulChange, self.protoType != nil else { return }
DispatchQueue.main.async {
self.handle(networkChange: path) { _ in }
}
}
pathMonitor.start(queue: pathMonitorQueue)
currentPath = pathMonitor.currentPath
currentPathSignature = pathSignature(for: pathMonitor.currentPath)
}
func updateActiveInterfaceIndex(for path: Network.NWPath?) {
guard let path else {
activeIfaceIdx = 0
return
}
let preferredTypes: [NWInterface.InterfaceType] = [.wiredEthernet, .wifi, .cellular, .other]
let nonLoopbackInterfaces = path.availableInterfaces.filter { $0.type != .loopback }
let activeInterfaces = nonLoopbackInterfaces.filter { path.usesInterfaceType($0.type) }
let candidate = preferredTypes.compactMap { type in
activeInterfaces.first { $0.type == type }
}.first ?? activeInterfaces.first ?? nonLoopbackInterfaces.first
if let candidate {
activeIfaceIdx = UInt32(candidate.index)
} else {
activeIfaceIdx = 0
}
}
func updateActiveInterfaceIndexForCurrentPath() {
if let currentPath {
currentPathSignature = pathSignature(for: currentPath)
updateActiveInterfaceIndex(for: currentPath)
return
}
currentPath = pathMonitor.currentPath
currentPathSignature = pathSignature(for: pathMonitor.currentPath)
updateActiveInterfaceIndex(for: pathMonitor.currentPath)
}
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let message = String(data: messageData, encoding: .utf8) else {
@@ -104,6 +178,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
return
}
didReceiveInitialPathUpdate = false
updateActiveInterfaceIndexForCurrentPath()
switch protoType {
case .wireguard:
startWireguard(activationAttemptId: activationAttemptId,
@@ -157,28 +234,63 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
of object: Any?,
change: [NSKeyValueChangeKey: Any]?,
context: UnsafeMutableRawPointer?) {
guard Constants.kDefaultPathKey != keyPath else { return }
// Since iOS 11, we have observed that this KVO event fires repeatedly when connecting over Wifi,
// even though the underlying network has not changed (i.e. `isEqualToPath` returns false),
// leading to "wakeup crashes" due to excessive network activity. Guard against false positives by
// comparing the paths' string description, which includes properties not exposed by the class
guard let lastPath: NWPath = change?[.oldKey] as? NWPath,
let defPath = defaultPath,
lastPath != defPath || lastPath.description != defPath.description else {
guard Constants.kDefaultPathKey == keyPath else {
return
}
DispatchQueue.main.async { [weak self] in
guard let self, self.defaultPath != nil else { return }
self.handle(networkChange: self.defaultPath!) { _ in }
}
}
private func handle(networkChange changePath: NWPath, completion: @escaping (Error?) -> Void) {
private func handle(networkChange changePath: Network.NWPath, completion: @escaping (Error?) -> Void) {
updateActiveInterfaceIndex(for: changePath)
wg_log(.info, message: "Tunnel restarted.")
startTunnel(options: nil, completionHandler: completion)
}
}
private extension PacketTunnelProvider {
func pathSignature(for path: Network.NWPath) -> String {
var signatureComponents = [String(describing: path.status)]
signatureComponents.append(path.isExpensive ? "exp" : "noexp")
signatureComponents.append(path.isConstrained ? "con" : "nocon")
let preferredTypes: [NWInterface.InterfaceType] = [.wiredEthernet, .wifi, .cellular, .loopback, .other]
let sortedInterfaces = path.availableInterfaces.sorted { lhs, rhs in
if lhs.type == rhs.type {
return lhs.index < rhs.index
}
let lhsOrder = preferredTypes.firstIndex(of: lhs.type) ?? preferredTypes.count
let rhsOrder = preferredTypes.firstIndex(of: rhs.type) ?? preferredTypes.count
if lhsOrder == rhsOrder {
return lhs.index < rhs.index
}
return lhsOrder < rhsOrder
}
for interface in sortedInterfaces {
let typeName: String
switch interface.type {
case .wiredEthernet: typeName = "ethernet"
case .wifi: typeName = "wifi"
case .cellular: typeName = "cellular"
case .loopback: typeName = "loopback"
case .other: typeName = "other"
@unknown default: typeName = "unknown"
}
signatureComponents.append("\(typeName):\(interface.index)")
}
// Include currently used interface preference ordering
for type in preferredTypes {
let usesType = path.usesInterfaceType(type)
signatureComponents.append("uses-\(type):\(usesType)")
}
return signatureComponents.joined(separator: "|")
}
}
extension WireGuardLogLevel {
var osLogLevel: OSLogType {
switch self {
@@ -190,8 +302,27 @@ extension WireGuardLogLevel {
}
}
extension NEProviderStopReason: CustomStringConvertible {
public var description: String {
final class PacketTunnelFlowAdapter: NSObject, OpenVPNAdapterPacketFlow {
private let flow: NEPacketTunnelFlow
init(flow: NEPacketTunnelFlow) {
self.flow = flow
super.init()
}
@objc(readPacketsWithCompletionHandler:)
func readPackets(completionHandler: @escaping ([Data], [NSNumber]) -> Void) {
flow.readPackets(completionHandler: completionHandler)
}
@objc(writePackets:withProtocols:)
func writePackets(_ packets: [Data], withProtocols protocols: [NSNumber]) -> Bool {
flow.writePackets(packets, withProtocols: protocols)
}
}
extension NEProviderStopReason {
var amneziaDescription: String {
switch self {
case .none:
return "No specific reason"
@@ -223,6 +354,8 @@ extension NEProviderStopReason: CustomStringConvertible {
return "The current console user changed"
case .connectionFailed:
return "The connection failed"
case .internalError:
return "The network extension reported an internal error"
case .sleep:
return "A stop reason indicating the VPNC enabled disconnect on sleep and the device went to sleep"
case .appUpdate:

View File

@@ -165,7 +165,7 @@ bool LinuxRouteMonitor::rtmSendRoute(int action, int flags, int type,
if (rtm->rtm_type == RTN_THROW) {
struct in_addr ip4;
inet_pton(AF_INET, NetworkUtilities::getGatewayAndIface().toUtf8(), &ip4);
inet_pton(AF_INET, NetworkUtilities::getGatewayAndIface().first.toUtf8(), &ip4);
nlmsg_append_attr(nlmsg, sizeof(buf), RTA_GATEWAY, &ip4, sizeof(ip4));
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_PRIORITY, 0);
rtm->rtm_type = RTN_UNICAST;

View File

@@ -1,17 +1,19 @@
#include "xrayprotocol.h"
#include "core/ipcclient.h"
#include "utilities.h"
#include "core/networkUtilities.h"
#include <QCryptographicHash>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkInterface>
#include "core/networkUtilities.h"
#include "utilities.h"
#include <QJsonDocument>
XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) : VpnProtocol(configuration, parent)
{
readXrayConfiguration(configuration);
m_routeGateway = NetworkUtilities::getGatewayAndIface();
m_routeGateway = NetworkUtilities::getGatewayAndIface().first;
m_vpnGateway = amnezia::protocols::xray::defaultLocalAddr;
m_vpnLocalAddress = amnezia::protocols::xray::defaultLocalAddr;
m_t2sProcess = IpcClient::InterfaceTun2Socks();
@@ -25,71 +27,18 @@ XrayProtocol::~XrayProtocol()
ErrorCode XrayProtocol::start()
{
qDebug().noquote() << "XrayProtocol xrayExecPath():" << xrayExecPath();
qDebug() << "XrayProtocol::start()";
if (!QFileInfo::exists(xrayExecPath())) {
setLastError(ErrorCode::XrayExecutableMissing);
return lastError();
}
IpcClient::Interface()->xrayStart(QJsonDocument(m_xrayConfig).toJson());
#ifdef QT_DEBUG
m_xrayCfgFile.setAutoRemove(false);
#endif
m_xrayCfgFile.open();
QString config = QJsonDocument(m_xrayConfig).toJson();
config.replace(m_remoteHost, m_remoteAddress);
m_xrayCfgFile.write(config.toUtf8());
m_xrayCfgFile.close();
QStringList args = QStringList() << "-c" << m_xrayCfgFile.fileName() << "-format=json";
qDebug().noquote() << "XrayProtocol::start()" << xrayExecPath() << args.join(" ");
m_xrayProcess.setProcessChannelMode(QProcess::MergedChannels);
m_xrayProcess.setProgram(xrayExecPath());
if (Utils::processIsRunning(Utils::executable("xray", false))) {
qDebug().noquote() << "kill previos xray";
Utils::killProcessByName(Utils::executable("xray", false));
}
m_xrayProcess.setArguments(args);
connect(&m_xrayProcess, &QProcess::readyReadStandardOutput, this, [this]() {
#ifdef QT_DEBUG
qDebug().noquote() << "xray:" << m_xrayProcess.readAllStandardOutput();
#endif
});
connect(&m_xrayProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this,
[this](int exitCode, QProcess::ExitStatus exitStatus) {
qDebug().noquote() << "XrayProtocol finished, exitCode, exitStatus" << exitCode << exitStatus;
setConnectionState(Vpn::ConnectionState::Disconnected);
if ((exitStatus != QProcess::NormalExit) || (exitCode != 0)) {
emit protocolError(amnezia::ErrorCode::XrayExecutableCrashed);
emit setConnectionState(Vpn::ConnectionState::Error);
}
});
m_xrayProcess.start();
m_xrayProcess.waitForStarted();
if (m_xrayProcess.state() == QProcess::ProcessState::Running) {
setConnectionState(Vpn::ConnectionState::Connecting);
QThread::msleep(1000);
return startTun2Sock();
} else
return ErrorCode::XrayExecutableMissing;
setConnectionState(Vpn::ConnectionState::Connecting);
return startTun2Sock();
}
ErrorCode XrayProtocol::startTun2Sock()
{
m_t2sProcess->start();
#ifdef Q_OS_WIN
m_configData.insert("inetAdapterIndex", NetworkUtilities::AdapterIndexTo(QHostAddress(m_remoteAddress)));
#endif
connect(m_t2sProcess.data(), &IpcProcessTun2SocksReplica::stateChanged, this,
[&](QProcess::ProcessState newState) { qDebug() << "PrivilegedProcess stateChanged" << newState; });
@@ -99,11 +48,10 @@ ErrorCode XrayProtocol::startTun2Sock()
setConnectionState(Vpn::ConnectionState::Connecting);
QList<QHostAddress> dnsAddr;
dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns1).toString()));
dnsAddr.push_back(QHostAddress(m_primaryDNS));
// We don't use secondary DNS if primary DNS is AmneziaDNS
if (!m_configData.value(amnezia::config_key::dns1).toString().
contains(amnezia::protocols::dns::amneziaDnsIp)) {
dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns2).toString()));
if (!m_primaryDNS.contains(amnezia::protocols::dns::amneziaDnsIp)) {
dnsAddr.push_back(QHostAddress(m_secondaryDNS));
}
#ifdef Q_OS_WIN
QThread::msleep(8000);
@@ -117,37 +65,13 @@ ErrorCode XrayProtocol::startTun2Sock()
QThread::msleep(1000);
IpcClient::Interface()->createTun("tun2", amnezia::protocols::xray::defaultLocalAddr);
IpcClient::Interface()->updateResolvers("tun2", dnsAddr);
#endif
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
// killSwitch toggle
if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) {
m_configData.insert("vpnServer", m_remoteAddress);
IpcClient::Interface()->enableKillSwitch(m_configData, 0);
}
#endif
if (m_routeMode == Settings::RouteMode::VpnAllSites) {
IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "0.0.0.0/1");
IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "128.0.0.0/1");
IpcClient::Interface()->routeAddList(m_routeGateway, QStringList() << m_remoteAddress);
IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "1.0.0.0/8" << "2.0.0.0/7" << "4.0.0.0/6" << "8.0.0.0/5" << "16.0.0.0/4" << "32.0.0.0/3" << "64.0.0.0/2" << "128.0.0.0/1");
}
IpcClient::Interface()->StopRoutingIpv6();
#ifdef Q_OS_WIN
IpcClient::Interface()->updateResolvers("tun2", dnsAddr);
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
for (int i = 0; i < netInterfaces.size(); i++) {
for (int j = 0; j < netInterfaces.at(i).addressEntries().size(); j++) {
// killSwitch toggle
if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) {
if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) {
IpcClient::Interface()->enableKillSwitch(m_configData, netInterfaces.at(i).index());
}
m_configData.insert("vpnAdapterIndex", netInterfaces.at(i).index());
m_configData.insert("vpnGateway", m_vpnGateway);
m_configData.insert("vpnServer", m_remoteAddress);
IpcClient::Interface()->enablePeerTraffic(m_configData);
}
}
}
#endif
setConnectionState(Vpn::ConnectionState::Connected);
}
@@ -167,8 +91,6 @@ ErrorCode XrayProtocol::startTun2Sock()
void XrayProtocol::stop()
{
#ifdef AMNEZIA_DESKTOP
QRemoteObjectPendingReply<bool> disableKillSwitchResp = IpcClient::Interface()->disableKillSwitch();
disableKillSwitchResp.waitForFinished(1000);
QRemoteObjectPendingReply<bool> StartRoutingIpv6Resp = IpcClient::Interface()->StartRoutingIpv6();
StartRoutingIpv6Resp.waitForFinished(1000);
QRemoteObjectPendingReply<bool> restoreResolvers = IpcClient::Interface()->restoreResolvers();
@@ -179,9 +101,9 @@ void XrayProtocol::stop()
#endif
#endif
qDebug() << "XrayProtocol::stop()";
m_xrayProcess.disconnect();
m_xrayProcess.kill();
m_xrayProcess.waitForFinished(3000);
IpcClient::Interface()->xrayStop();
if (m_t2sProcess) {
m_t2sProcess->stop();
QThread::msleep(200);
@@ -190,26 +112,13 @@ void XrayProtocol::stop()
setConnectionState(Vpn::ConnectionState::Disconnected);
}
QString XrayProtocol::xrayExecPath()
{
#ifdef Q_OS_WIN
return Utils::executable(QString("xray/xray"), true);
#else
return Utils::executable(QString("xray"), true);
#endif
}
void XrayProtocol::readXrayConfiguration(const QJsonObject &configuration)
{
m_configData = configuration;
QJsonObject xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::Xray)).toObject();
if (xrayConfiguration.isEmpty()) {
xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::SSXray)).toObject();
}
m_xrayConfig = xrayConfiguration;
m_localPort = QString(amnezia::protocols::xray::defaultLocalProxyPort).toInt();
m_remoteHost = configuration.value(amnezia::config_key::hostName).toString();
m_remoteAddress = NetworkUtilities::getIPAddress(m_remoteHost);
m_routeMode = static_cast<Settings::RouteMode>(configuration.value(amnezia::config_key::splitTunnelType).toInt());
m_primaryDNS = configuration.value(amnezia::config_key::dns1).toString();
m_secondaryDNS = configuration.value(amnezia::config_key::dns2).toString();

View File

@@ -3,8 +3,8 @@
#include "QProcess"
#include "containers/containers_defs.h"
#include "openvpnprotocol.h"
#include "core/ipcclient.h"
#include "vpnprotocol.h"
#include "settings.h"
class XrayProtocol : public VpnProtocol
@@ -17,29 +17,16 @@ public:
ErrorCode startTun2Sock();
void stop() override;
protected:
private:
void readXrayConfiguration(const QJsonObject &configuration);
protected:
QJsonObject m_xrayConfig;
private:
static QString xrayExecPath();
static QString tun2SocksExecPath();
private:
int m_localPort;
QString m_remoteHost;
QString m_remoteAddress;
Settings::RouteMode m_routeMode;
QJsonObject m_configData;
QString m_primaryDNS;
QString m_secondaryDNS;
#ifndef Q_OS_IOS
QProcess m_xrayProcess;
QSharedPointer<IpcProcessTun2SocksReplica> m_t2sProcess;
#endif
QTemporaryFile m_xrayCfgFile;
};
#endif // XRAYPROTOCOL_H